"""Fast sample scan of IoT flocks API — day/date mapping without full pagination.""" import json import urllib.request from collections import defaultdict from datetime import datetime, timedelta, timezone BASE = "https://dashboard.cpsp.id/api/iot/flocks/" CYCLE_START = datetime(2026, 5, 22).date() TZ = timezone(timedelta(hours=7)) 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 summarize_records(records: list) -> None: by_day: dict[int, list] = defaultdict(list) for r in records: day = r["payload"]["data"].get("day") if day is not None: by_day[day].append(r) print(f" unique days: {len(by_day)} range {min(by_day)}..{max(by_day)}") for day in sorted(by_day)[:3]: r = max(by_day[day], key=lambda x: parse_dt(x["fetched_at"])) d = r["payload"]["data"] cal = CYCLE_START + timedelta(days=day - 1) print( f" day {day} -> {cal} | wind={d.get('wind')} hum={d.get('humidity')} " f"temp={d.get('actualTemperature')} water={d.get('water')} HSI={d.get('HSI')}" ) if len(by_day) > 6: print(" ...") for day in sorted(by_day)[-3:]: r = max(by_day[day], key=lambda x: parse_dt(x["fetched_at"])) d = r["payload"]["data"] cal = CYCLE_START + timedelta(days=day - 1) print( f" day {day} -> {cal} | wind={d.get('wind')} hum={d.get('humidity')} " f"temp={d.get('actualTemperature')} water={d.get('water')} HSI={d.get('HSI')}" ) def main() -> None: p1 = fetch_page(1) total = p1["count"] page_size = len(p1["results"]) last_page = (total + page_size - 1) // page_size print(f"API total={total} page_size={page_size} pages={last_page}") r0 = p1["results"][0] d0 = r0["payload"]["data"] coop = d0.get("coop") or {} print(f"flock_id={r0['flock_id']} floor={d0.get('name')} site={coop.get('kode')}") print(f"kandang field (Mongo id)={d0.get('kandang')}") # Sample: first pages (newest), last pages (oldest), and mid pages sample_pages = sorted({1, 2, 3, 10, 50, 100, 200, 400, 600, last_page, last_page - 1, last_page - 2}) all_records = [] for page in sample_pages: if page < 1 or page > last_page: continue data = fetch_page(page) all_records.extend(data["results"]) days = {r["payload"]["data"].get("day") for r in data["results"]} fdates = {parse_dt(r["fetched_at"]).astimezone(TZ).date() for r in data["results"]} print(f"page {page:4d}: days={min(days)}..{max(days)} fetched_dates={min(fdates)}..{max(fdates)}") # Roll up sampled data: latest fetched_at per cycle day daily: dict[int, dict] = {} for r in all_records: day = r["payload"]["data"].get("day") if day is None: continue ts = parse_dt(r["fetched_at"]) if day not in daily or ts > daily[day]["ts"]: daily[day] = {"ts": ts, "r": r} print(f"\nFrom sample: {len(daily)} unique cycle days seen") missing = [d for d in range(1, 50) if d not in daily] print(f"Missing in sample (1..49): {missing}") print("\nField mapping -> iot_panel (exclude experience_temperature):") print(" date = cycle_start + (day - 1) # 2026-05-22 + day-1") print(" wind_speed = payload.data.wind") print(" humidity = payload.data.humidity # likely /10 for %") print(" water_total = payload.data.water") print(" average_temperature = payload.data.actualTemperature # likely /10 for °C") print(" experience_temperature = formula (HSI? TBD)") print("\nSample rollup table:") print(f"{'day':>3} {'date':>10} {'wind':>5} {'hum':>6} {'temp_raw':>8} {'water':>6} {'HSI':>7}") for day in range(1, 50): cal = CYCLE_START + timedelta(days=day - 1) entry = daily.get(day) if not entry: continue d = entry["r"]["payload"]["data"] print( f"{day:3d} {cal} {d.get('wind', 0):5} {d.get('humidity', 0):6} " f"{d.get('actualTemperature', 0):8} {d.get('water', 0):6} {d.get('HSI', 0):7.2f}" ) if __name__ == "__main__": main()