first commit
This commit is contained in:
commit
b54624be96
226 files changed
+108840
No files matched your search
@@ -0,0 +1,97 @@
|
||||
"""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()
|
||||
Reference in new issue
Block a user