first commit

This commit is contained in:
Alberto-Audrix committed 2026-09-08 15:21:23 +07:00
commit b54624be96
226 files changed
+108840

No files matched your search

+175
View File
@@ -0,0 +1,175 @@
"""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