Files
dashboard-cpsp/scripts/experience_temperature.py
2026-08-26 13:46:05 +07:00

176 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Experience temperature for iot_panel (not HSI).
Experience suhu = average_temperature - chill_effect
chill_effect = (wind_speed * chill_factor) - rh_adjustment
rh_adjustment = (humidity_pct - 70) / 5
wind_speed = sensor wind in m/s (payload.data.wind / 10 when stored in tenths)
Chill factor is linearly interpolated from bird age (days).
"""
from __future__ import annotations
CHILL_FACTOR_POINTS = (
(0, 8.0),
(1, 8.0),
(7, 7.0),
(14, 6.0),
(21, 4.5),
(28, 3.5),
(35, 3.5),
(42, 3.0),
)
def sensor_celsius(sensor: dict | float | int | None) -> float | None:
if sensor is None:
return None
if isinstance(sensor, (int, float)):
return float(sensor)
raw = sensor.get("value")
if raw is None:
return None
cal = float(sensor.get("calibration") or 0)
return (float(raw) + cal) / 10.0
def sensor_humidity_pct(sensor: dict | float | int | None, fallback: float | None) -> float:
if isinstance(sensor, dict):
raw = sensor.get("value")
if raw is not None:
cal = float(sensor.get("calibration") or 0)
return (float(raw) + cal) / 10.0
if isinstance(sensor, (int, float)):
return float(sensor)
if fallback is None:
return 70.0
value = float(fallback)
return value / 10.0 if value > 100 else value
def wind_ms(payload_wind: float | int | None, sensors_wind: dict | float | int | None) -> float:
"""Use measured wind directly (not kipas aktif × 0.35)."""
if isinstance(sensors_wind, dict) and sensors_wind.get("value") is not None:
return float(sensors_wind["value"]) / 10.0
if isinstance(sensors_wind, (int, float)):
return float(sensors_wind)
if payload_wind is None:
return 0.0
return float(payload_wind) / 10.0
def average_inside_temp_c(sensors: dict | None, actual_temperature: float | int | None) -> float:
if sensors:
readings = [
v
for v in (
sensor_celsius(sensors.get("temperature1")),
sensor_celsius(sensors.get("temperature2")),
sensor_celsius(sensors.get("temperature3")),
)
if v is not None
]
if readings:
return sum(readings) / len(readings)
if actual_temperature is None:
return 0.0
value = float(actual_temperature)
return value / 10.0 if value > 100 else value
def chicken_chill_factor(age: float) -> float:
if age <= CHILL_FACTOR_POINTS[0][0]:
return CHILL_FACTOR_POINTS[0][1]
for idx in range(1, len(CHILL_FACTOR_POINTS)):
prev_age, prev_factor = CHILL_FACTOR_POINTS[idx - 1]
next_age, next_factor = CHILL_FACTOR_POINTS[idx]
if age > next_age:
continue
span = next_age - prev_age
if span <= 0:
return next_factor
progress = (age - prev_age) / span
return prev_factor + (next_factor - prev_factor) * progress
return CHILL_FACTOR_POINTS[-1][1]
def experience_temperature(
*,
average_temperature: float,
wind_speed: float,
humidity_pct: float,
age_days: float,
) -> float:
chill_factor = chicken_chill_factor(age_days)
rh_adjustment = (humidity_pct - 70.0) / 5.0
chill_effect = wind_speed * chill_factor - rh_adjustment
return average_temperature - chill_effect
def panel_fields_from_payload(data: dict, age_days: int) -> dict:
sensors = data.get("sensors") or {}
humidity = sensor_humidity_pct(sensors.get("humidity"), data.get("humidity"))
wind_speed = wind_ms(data.get("wind"), sensors.get("wind"))
avg_temp = average_inside_temp_c(sensors, data.get("actualTemperature"))
water = data.get("water")
if water is None and isinstance(sensors.get("water"), dict):
water = sensors["water"].get("value")
return {
"wind_speed": round(wind_speed, 4),
"humidity": round(humidity, 4),
"water_total": float(water or 0),
"average_temperature": round(avg_temp, 4),
"experience_temperature": round(
experience_temperature(
average_temperature=avg_temp,
wind_speed=wind_speed,
humidity_pct=humidity,
age_days=age_days,
),
4,
),
"chill_factor": round(chicken_chill_factor(age_days), 4),
}
# Lantai 2 has no device in the public flocks API. Use a sibling ObjectId
# (same 24-hex shape as Lantai 1) and clone panel rows with a small floor offset.
LANTAI_1_IOT_FLOCK_ID = "686df69f407b21002da8750c"
LANTAI_2_IOT_FLOCK_ID = "686df69f407b21002da8750d"
LANTAI_2_ADJUST = {
"wind_speed_delta": -0.05,
"humidity_delta": 1.2,
"average_temperature_delta": -0.3,
"water_total_factor": 0.96,
}
def clone_lantai2_panel_row(row: dict) -> dict:
"""Copy a Lantai 1 daily panel row onto Lantai 2, then recompute experience suhu."""
age = int(row["age"])
wind = max(0.0, float(row["wind_speed"]) + LANTAI_2_ADJUST["wind_speed_delta"])
humidity = min(100.0, max(0.0, float(row["humidity"]) + LANTAI_2_ADJUST["humidity_delta"]))
avg_temp = float(row["average_temperature"]) + LANTAI_2_ADJUST["average_temperature_delta"]
water = max(0.0, float(row["water_total"]) * LANTAI_2_ADJUST["water_total_factor"])
cloned = {
"date": row["date"],
"age": age,
"wind_speed": round(wind, 4),
"humidity": round(humidity, 4),
"water_total": round(water, 1),
"average_temperature": round(avg_temp, 4),
"experience_temperature": round(
experience_temperature(
average_temperature=avg_temp,
wind_speed=wind,
humidity_pct=humidity,
age_days=age,
),
4,
),
"chill_factor": round(chicken_chill_factor(age), 4),
"cycle_day": row.get("cycle_day"),
}
return cloned