Download app/batchcal.py from rairo/ziso-api: direct link, hf CLI and curl.
- Browser
- Download file 8.75 kB
-
https://huggingface.co/spaces/rairo/ziso-api/resolve/main/app/batchcal.py
- Command line
-
hf download hf://spaces/rairo/ziso-api/app/batchcal.py
-
curl -L -o batchcal.py https://huggingface.co/spaces/rairo/ziso-api/resolve/main/app/batchcal.py
8.75 kB
| """The batch as a calendar, and any stretch of it in close-up.""" | |
| from __future__ import annotations | |
| from collections import defaultdict | |
| from datetime import date, datetime, time, timedelta | |
| from google.cloud import firestore | |
| from . import config, flock, metrics, store, timeline | |
| def _bounds(coop: dict) -> tuple[date, date]: | |
| today = store.local(store.now()).date() | |
| f = coop.get("flock") | |
| if f: | |
| placed = date.fromisoformat(f["placed"]) | |
| return placed, max(today, placed + timedelta(days=f.get("sell_day") or 35)) | |
| return today - timedelta(days=27), today | |
| def _start(d: date) -> datetime: | |
| return datetime.combine(d, time.min, tzinfo=config.TZ) | |
| def _since(coop_id: str, name: str, start: datetime, end: datetime) -> list[dict]: | |
| q = (store.coop_ref(coop_id).collection(name) | |
| .where(filter=firestore.FieldFilter("ts", ">=", start)) | |
| .where(filter=firestore.FieldFilter("ts", "<", end))) | |
| return [{"id": d.id, **d.to_dict()} for d in q.stream()] | |
| def _tone(day: dict, alarm_n: int) -> str: | |
| if not day.get("readings"): | |
| return "none" | |
| care = day.get("care") or 0 | |
| tone = "good" if care >= 85 else "watch" if care >= 70 else "act" | |
| if day.get("dry_minutes", 0) >= 30: | |
| tone = "act" | |
| elif day.get("dry_minutes", 0) >= 10 and tone == "good": | |
| tone = "watch" | |
| if alarm_n and tone == "good": | |
| tone = "watch" | |
| return tone | |
| def _plan(coop: dict) -> dict[str, list[str]]: | |
| f = coop.get("flock") | |
| if not f: | |
| return {} | |
| placed = date.fromisoformat(f["placed"]) | |
| marks: dict[str, list[str]] = defaultdict(list) | |
| for day, text in flock.VACCINES: | |
| marks[(placed + timedelta(days=day)).isoformat()].append("vaccine:" + text.split(" in the")[0]) | |
| for name, a, _ in flock.PHASES[1:]: | |
| marks[(placed + timedelta(days=a)).isoformat()].append("feed:" + name) | |
| for d in range(7, 50, 7): | |
| marks[(placed + timedelta(days=d)).isoformat()].append("weigh") | |
| marks[(placed + timedelta(days=f.get("sell_day") or 35)).isoformat()].append("sell") | |
| return marks | |
| def month(coop: dict) -> dict: | |
| first, last = _bounds(coop) | |
| today = store.local(store.now()).date() | |
| start, end = _start(first), _start(last + timedelta(days=1)) | |
| hours = timeline.hours_between(coop["id"], start, min(end, store.now())) | |
| by_day: dict[str, list[dict]] = defaultdict(list) | |
| for h in hours: | |
| by_day[h["hour"][:8]].append(h) | |
| alarms_by, calls_by, ledger_by = defaultdict(int), defaultdict(int), defaultdict(list) | |
| for a in _since(coop["id"], "alarms", start, end): | |
| alarms_by[store.local(a["ts"]).date().isoformat()] += 1 | |
| for c in _since(coop["id"], "calls", start, end): | |
| calls_by[store.local(c["ts"]).date().isoformat()] += 1 | |
| f = coop.get("flock") | |
| if f: | |
| for r in flock.ledger(coop["id"], f["batch"]): | |
| ledger_by[store.local(r["ts"]).date().isoformat()].append(r["kind"]) | |
| plan = _plan(coop) | |
| placed = date.fromisoformat(f["placed"]) if f else None | |
| days = [] | |
| d = first | |
| while d <= last: | |
| key = d.isoformat() | |
| summary = metrics._day(by_day.get(d.strftime("%Y%m%d"), [])) | |
| days.append({ | |
| "date": key, "age": (d - placed).days if placed else None, | |
| "future": d > today, "today": d == today, | |
| "tone": "future" if d > today else _tone(summary, alarms_by[key]), | |
| "care": summary.get("care"), "alarms": alarms_by[key], "calls": calls_by[key], | |
| "records": ledger_by[key], "plan": plan.get(key, []), | |
| }) | |
| d += timedelta(days=1) | |
| return {"first": first.isoformat(), "last": last.isoformat(), "today": today.isoformat(), "days": days} | |
| def _frames(hours: list[dict]) -> list[dict]: | |
| picks = [] | |
| for h in hours: | |
| for n in h.get("notes", []): | |
| if n.get("frame"): | |
| picks.append({"ts": n["ts"], "frame": n["frame"], "text": n["text"]}) | |
| last_by_day: dict[str, dict] = {} | |
| for h in hours: | |
| if h.get("frame"): | |
| last_by_day[h["hour"][:8]] = {"ts": h["last_ts"], "frame": h["frame"], "text": ""} | |
| picks += list(last_by_day.values()) | |
| picks = sorted(picks, key=lambda p: p["ts"])[-4:] | |
| return [{"time": store.local(p["ts"]).strftime("%a %H:%M"), "url": store.frame_url(p["frame"]), | |
| "text": p["text"]} for p in picks] | |
| def _story(summary: dict, per_day: list[dict], alarm_list: list[dict], n_days: int) -> str: | |
| if not summary.get("readings"): | |
| return "No camera readings in this stretch." | |
| bits = [] | |
| calm = sum(1 for d in per_day if d.get("care") and d["care"] >= 85 and d.get("dry_minutes", 0) < 10) | |
| bits.append(f"{calm} of {n_days} days calm and comfortable." if n_days > 1 else | |
| ("A calm, comfortable day." if calm else "A day that needed attention.")) | |
| if summary.get("dry_minutes", 0) >= 10: | |
| bits.append(f"The drinker was dry for about {summary['dry_minutes']} minutes in all.") | |
| if summary.get("cold", 0) >= 0.08: | |
| bits.append(f"The flock huddled {round(summary['cold'] * 100)}% of the time.") | |
| if summary.get("hot", 0) >= 0.05: | |
| bits.append(f"They panted {round(summary['hot'] * 100)}% of the time.") | |
| if alarm_list: | |
| bits.append(f"{len(alarm_list)} alarm{'s' if len(alarm_list) > 1 else ''} raised.") | |
| return " ".join(bits) | |
| def span(coop: dict, first: date, last: date) -> dict: | |
| if last < first: | |
| first, last = last, first | |
| last = min(last, first + timedelta(days=41)) | |
| start, end = _start(first), _start(last + timedelta(days=1)) | |
| hours = timeline.hours_between(coop["id"], start, min(end, store.now())) | |
| summary = metrics._day(hours) | |
| by_day: dict[str, list[dict]] = defaultdict(list) | |
| for h in hours: | |
| by_day[h["hour"][:8]].append(h) | |
| n_days = (last - first).days + 1 | |
| keys = [(first + timedelta(days=i)).strftime("%Y%m%d") for i in range(n_days)] | |
| per_day = [{"day": k, **metrics._day(by_day.get(k, []))} for k in keys] | |
| grid = [] | |
| for k in keys[-14:]: | |
| lookup = {h["hour"][8:10]: h for h in by_day.get(k, [])} | |
| grid.append({"day": k, "hours": [metrics.cell(lookup.get(f"{i:02d}")) for i in range(24)]}) | |
| alarm_list = _since(coop["id"], "alarms", start, end) | |
| calls = _since(coop["id"], "calls", start, end) | |
| out = { | |
| "first": first.isoformat(), "last": last.isoformat(), "days": n_days, | |
| "summary": summary, "per_day": per_day, "grid": grid, | |
| "story": _story(summary, per_day, alarm_list, n_days), | |
| "alarms": [{"time": store.local(a["ts"]).strftime("%a %H:%M"), "message": a["message"], | |
| "status": a["status"]} for a in sorted(alarm_list, key=lambda a: a["ts"])], | |
| "calls": [{"time": store.local(c["ts"]).strftime("%a %H:%M"), "name": c.get("name") or "Caller", | |
| "duration_s": c.get("duration_s"), "said": next((l["text"] for l in c.get("transcript", []) | |
| if l.get("who") == "coop"), "")} | |
| for c in sorted(calls, key=lambda c: c["ts"])], | |
| "frames": _frames(hours), | |
| "flock": None, "records": [], | |
| } | |
| f = coop.get("flock") | |
| if f: | |
| st = flock.state(coop) | |
| placed = date.fromisoformat(f["placed"]) | |
| a0, a1 = (first - placed).days, (last - placed).days | |
| factor = st["growth"]["factor"] if st else 0.75 | |
| alive = st["birds"]["alive"] if st else f["birds_placed"] | |
| feed_kg = sum(flock.target(d)["feed_g"] or 0 for d in range(max(1, a0), max(1, a1) + 1)) * alive / 1000 | |
| out["flock"] = { | |
| "age_from": a0, "age_to": a1, | |
| "weight_from": round(flock.target(max(0, a0))["weight_g"] * factor / 1000, 2), | |
| "weight_to": round(flock.target(max(0, a1))["weight_g"] * factor / 1000, 2), | |
| "feed_kg": round(feed_kg), "feed_bags": round(feed_kg / flock.BAG_KG, 1), | |
| "feed_usd": round(feed_kg * f["prices"]["feed_per_kg"]), | |
| "plan": [{"date": k, "what": v} for k, v in sorted(_plan(coop).items()) if first.isoformat() <= k <= last.isoformat()], | |
| } | |
| out["records"] = [{"time": store.local(r["ts"]).strftime("%a %d %b"), "kind": r["kind"], | |
| "quantity": r.get("quantity"), "unit": r.get("unit"), "amount_usd": r.get("amount_usd"), | |
| "note": r.get("note")} for r in flock.ledger(coop["id"], f["batch"]) | |
| if first <= store.local(r["ts"]).date() <= last] | |
| return out | |