Extract the panel snapshot builder into core (task 9047a97b) #148

3 Commits
Author SHA1 Message Date
overseer 5d2357ee30 Tie coordinator wrapper to snapshot builder; single clock read
build_snapshot now takes now ONLY and derives today = now.date()
inside (P3-1): a single source of truth so the calendar date and
wall-clock time can never disagree across two reads. Coordinator
call site updated.

The expected completed_at in tests/test_core_snapshot.py is now built
from pinned_now.astimezone(datetime.timezone.utc).isoformat() instead
of a literal '+00:00' UTC string (P3-2), so the assertion is
timezone-independent rather than relying on the hass fixture's UTC
timezone.

Pin the coordinator's clock to pinned_now, run async_refresh, and
assert coordinator.data == built (the frozen dataclasses make == a
full deep compare) — the REQUIRED tie-in that proves the coordinator
wrapper actually delegates to build_snapshot, which the literal
assertions alone could not (P2).
2026-09-21 17:40:51 -04:00
overseer c18246d58f Move panel payload shaping into core; pin the snapshot clock
PR #148 review (CHANGES_REQUIRED) follow-up:

- Move the panel payload dict shaper (_instance_payload) from the
  HA-importing sensor.py into core/snapshot.py as instance_payload, and
  have sensor.py import it from core. The API panel route now shares ONE
  shaping implementation with the HA sensor layer (done -> completed,
  on_time passthrough, missed omitted unless include_missed).
- build_snapshot takes the HA-local now explicitly instead of
  reading datetime.now(time_zone) internally, so date and time come from
  a single clock read and the overdue check is pinned in tests. The
  coordinator passes the same now it already resolved; time_zone is
  dropped (now is timezone-aware).
- Strengthen tests/test_core_snapshot.py with explicit literal expected
  values instead of self-equality: an AWAY child (2-week absent-week
  schedule + a presence override => next_present), a due-timed quest
  exercised as overdue under a pinned 12:00 now, a completed quest
  (done -> completed, on_time True), cycle_day == 2, and a constructed
  missed view proving omission from the panel payload and retention in
  the admin payload.
- Docstring cleanups: state plainly that settings is currently
  unused (no 'wired' implication); correct the coordinator docstring
  (settings are passed in, not resolved off the entry); trim the
  coordinator __all__ to what other modules actually import.
- Derive the seed/materialize date from coordinator._local_now() so the
  seed date and snapshot date cannot diverge near midnight; drop the
  redundant bo/ghost/list_children leftovers from the prior seed.
2026-09-21 17:37:16 -04:00
overseer 6741a1a210 Extract the panel snapshot builder into core (task 9047a97b)
The household snapshot assembly that lived inline in the coordinator's
_async_update_data is extracted into a pure, Home-Assistant-free
core/snapshot.py: the QuestInstanceView / ChildDaySnapshot /
NestQuestSnapshot dataclasses, the _cycle_day helper, and a new async
build_snapshot(database, settings, today, time_zone) builder.

The coordinator is now a thin DataUpdateCoordinator wrapper that
resolves the local timezone and settings and delegates to
build_snapshot; the dataclasses and _cycle_day are re-exported from the
coordinator module so existing sensor.py / binary_sensor.py imports keep
working unchanged (entity_ids and per-child instance attribute payload
shape unchanged). The API service's panel snapshot route (next task)
shares this one implementation.

Tests that monkeypatched derive_state on the coordinator module now
patch core.snapshot.derive_state (the module that now owns the call).
A new test seeds a household and asserts build_snapshot's output equals
the coordinator's snapshot for the same database and date. Suite green
at 1588.
2026-09-21 17:29:04 -04:00