"""Extract real 10-minute IoT snapshots for the Sukawarna Kandang 2 cycle window. The public IoT API (dashboard.cpsp.id/api/iot/flocks/) polls the coop every 10 minutes, so a full cycle day holds up to 144 rows. This script scans the API's history for flock 686df69f407b21002da8750c (Kandang 2 Lantai 1) inside the seeded cycle window 2026-05-22 .. 2026-07-09 and writes every snapshot to ``iot_panel_10min_extract.json``. The Django seed command reads this cache so Kandang 2 Lantai 1 is seeded from real sensor readings; the other floors and Kandang 1 are generated from it. """ import json import sys import urllib.request from datetime import datetime, timedelta, timezone from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(SCRIPT_DIR)) from experience_temperature import LANTAI_1_IOT_FLOCK_ID, panel_fields_from_payload BASE = "https://dashboard.cpsp.id/api/iot/flocks/" FLOCK_ID = LANTAI_1_IOT_FLOCK_ID CYCLE_START = datetime(2026, 5, 22).date() CYCLE_END = datetime(2026, 7, 9).date() TZ = timezone(timedelta(hours=7)) OUT = SCRIPT_DIR / "iot_panel_10min_extract.json" def parse_dt(value: str) -> datetime: return datetime.fromisoformat(value.replace("Z", "+00:00")) def fetch_page(page: int) -> dict: with urllib.request.urlopen(f"{BASE}?page={page}", timeout=60) as resp: return json.loads(resp.read()) def main() -> None: p1 = fetch_page(1) last_page = (p1["count"] + len(p1["results"]) - 1) // len(p1["results"]) hi = min(last_page, 320) print(f"Scanning pages 1..{hi} (of {last_page}) for flock {FLOCK_ID}", flush=True) # key: (date, slot_index) -> mapped row (keep the latest snapshot per 10-min slot) rows_by_slot: dict[tuple[str, int], dict] = {} in_window = 0 for page in range(1, hi + 1): for r in fetch_page(page)["results"]: if r["flock_id"] != FLOCK_ID: continue fetched = r.get("fetched_at") if not fetched: continue ts = parse_dt(fetched).astimezone(TZ) cal = ts.date() if not (CYCLE_START <= cal <= CYCLE_END): continue in_window += 1 d = r["payload"]["data"] age = (cal - CYCLE_START).days + 1 fields = panel_fields_from_payload(d, age) slot = (ts.hour * 60 + ts.minute) // 10 key = (str(cal), slot) rows_by_slot[key] = { "timestamp": ts.isoformat(), "date": str(cal), "age": age, **fields, } if page % 50 == 0: print(f" page {page}/{hi} — in-window so far: {in_window}", flush=True) rows = [rows_by_slot[k] for k in sorted(rows_by_slot, key=lambda k: (k[0], k[1]))] out = { "flock_id": FLOCK_ID, "cycle_start": str(CYCLE_START), "cycle_end": str(CYCLE_END), "count": len(rows), "rows": rows, } OUT.write_text(json.dumps(out, indent=2), encoding="utf-8") per_date: dict[str, int] = {} for row in rows: per_date[row["date"]] = per_date.get(row["date"], 0) + 1 print(f"\nSnapshots in window: {in_window}; unique slots written: {len(rows)}") print(f"Unique dates: {len(per_date)} (expect 49)") counts = sorted(set(per_date.values())) print(f"Rows-per-date distribution: {counts}") low = [d for d, c in per_date.items() if c < 144] print(f"Dates with fewer than 144 rows ({len(low)}): {low[:20]}") print(f"Wrote {OUT}") if __name__ == "__main__": main()