110 lines
4.0 KiB
Python
110 lines
4.0 KiB
Python
"""Analyze dashboard.cpsp.id IoT flocks API for date/day mapping."""
|
|
import json
|
|
import urllib.request
|
|
from collections import Counter, defaultdict
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
BASE = "https://dashboard.cpsp.id/api/iot/flocks/"
|
|
CYCLE_START = datetime(2026, 5, 22).date()
|
|
CYCLE_END = datetime(2026, 7, 9).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:
|
|
url = f"{BASE}?page={page}"
|
|
with urllib.request.urlopen(url, timeout=60) as resp:
|
|
return json.loads(resp.read())
|
|
|
|
|
|
def main() -> None:
|
|
meta = fetch_page(1)
|
|
total = meta["count"]
|
|
page_size = len(meta["results"])
|
|
last_page = (total + page_size - 1) // page_size
|
|
print(f"total={total} page_size={page_size} last_page={last_page}")
|
|
|
|
sample_pages = [1, 2, 3, last_page, last_page - 1]
|
|
records = []
|
|
for page in sample_pages:
|
|
data = fetch_page(page)
|
|
records.extend(data["results"])
|
|
print(f" fetched page {page}: {len(data['results'])} rows")
|
|
|
|
flock_ids = Counter(r["flock_id"] for r in records)
|
|
print(f"unique flock_ids in sample: {dict(flock_ids)}")
|
|
|
|
r0 = records[0]["payload"]["data"]
|
|
coop = r0.get("coop") or {}
|
|
print(f"coop kode={coop.get('kode')} name floor={r0.get('name')} kandang={r0.get('kandang')}")
|
|
|
|
by_day = defaultdict(list)
|
|
by_fetch_date = defaultdict(list)
|
|
for r in records:
|
|
d = r["payload"]["data"]
|
|
day = d.get("day")
|
|
if day is not None:
|
|
by_day[day].append(r)
|
|
fetched = r.get("fetched_at") or d.get("lastUpdate")
|
|
if fetched:
|
|
cal = parse_dt(fetched).astimezone(TZ).date()
|
|
by_fetch_date[cal].append(r)
|
|
|
|
print(f"day range: {min(by_day)}..{max(by_day)} ({len(by_day)} unique)")
|
|
print(f"fetched_at date range: {min(by_fetch_date)}..{max(by_fetch_date)} ({len(by_fetch_date)} unique)")
|
|
|
|
print("\nSample day -> metrics (first record per day):")
|
|
for day in sorted(by_day):
|
|
if day not in (1, 2, 3, 34, 35, 36, 48, 49):
|
|
continue
|
|
d = by_day[day][0]["payload"]["data"]
|
|
fetched = by_day[day][0].get("fetched_at", "")[:19]
|
|
cal = CYCLE_START + timedelta(days=day - 1)
|
|
print(
|
|
f" day {day:2d} (cycle cal {cal}) fetched={fetched} "
|
|
f"wind={d.get('wind')} hum={d.get('humidity')} temp={d.get('actualTemperature')} "
|
|
f"water={d.get('water')} HSI={d.get('HSI')}"
|
|
)
|
|
|
|
# Full scan for one flock: aggregate one row per cycle day using `day` field
|
|
print("\nScanning all pages for daily rollup by `day` (last snapshot per day)...")
|
|
daily: dict[int, dict] = {}
|
|
for page in range(1, last_page + 1):
|
|
for r in fetch_page(page)["results"]:
|
|
d = r["payload"]["data"]
|
|
day = d.get("day")
|
|
if day is None:
|
|
continue
|
|
fetched = r.get("fetched_at")
|
|
if not fetched:
|
|
continue
|
|
ts = parse_dt(fetched)
|
|
prev = daily.get(day)
|
|
if prev is None or ts > prev["ts"]:
|
|
daily[day] = {"ts": ts, "r": r}
|
|
|
|
print(f"Collected {len(daily)} unique cycle days from full API")
|
|
missing = [d for d in range(1, 50) if d not in daily]
|
|
print(f"Missing days 1..49: {missing}")
|
|
|
|
print("\nFull cycle rollup (mapped to calendar via start 2026-05-22):")
|
|
print(f"{'day':>3} {'date':>10} {'wind':>6} {'humidity':>8} {'avg_temp':>8} {'water':>8} {'HSI':>8}")
|
|
for day in range(1, 50):
|
|
entry = daily.get(day)
|
|
cal = CYCLE_START + timedelta(days=day - 1)
|
|
if not entry:
|
|
print(f"{day:3d} {cal} — missing —")
|
|
continue
|
|
d = entry["r"]["payload"]["data"]
|
|
print(
|
|
f"{day:3d} {cal} {d.get('wind', 0):6} {d.get('humidity', 0):8} "
|
|
f"{d.get('actualTemperature', 0):8} {d.get('water', 0):8} {d.get('HSI', 0):8.2f}"
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|