"""Handle IoT panel push readings (webhook from panel IoT devices).""" from __future__ import annotations from datetime import date, datetime, timezone as dt_timezone from zoneinfo import ZoneInfo from django.utils import timezone from apps.farms.models import Cycle, Flock from apps.operations.models import IotPanel WIB = ZoneInfo("Asia/Jakarta") 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), ) class IotPushError(Exception): def __init__(self, message: str, status_code: int | None = None): super().__init__(message) self.status_code = status_code def _to_float(value) -> float: raw = float(value) return raw / 10.0 if raw > 100 else raw 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 cycle_age_for_date(cycle_start: date, snapshot_date: date) -> int: if snapshot_date < cycle_start: return 1 return (snapshot_date - cycle_start).days + 1 def handle_iot_push(flock: Flock, payload: dict) -> IotPanel: now = timezone.now() snapshot_date = now.astimezone(WIB).date() avg_temp = _to_float(payload.get("suhu", 0)) humidity = _to_float(payload.get("kelembapan", 0)) wind_speed = _to_float(payload.get("wind", 0)) water_total = float(payload.get("flow", 0) or 0) cycle = ( Cycle.objects.filter( kandang_id=flock.kandang_id, start_date__lte=snapshot_date, end_date__gte=snapshot_date, ) .order_by("-start_date") .first() ) age = cycle_age_for_date(cycle.start_date, snapshot_date) if cycle else 1 experience_temp = round( _experience_temperature( average_temperature=avg_temp, wind_speed=wind_speed, humidity_pct=humidity, age_days=age, ), 4, ) panel, _created = IotPanel.objects.update_or_create( flock=flock, timestamp=now, defaults={ "date": snapshot_date, "wind_speed": round(wind_speed, 4), "humidity": round(humidity, 4), "water_total": water_total, "average_temperature": round(avg_temp, 4), "experience_temperature": experience_temp, }, ) return panel