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).
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.
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.
HA event triggers expose the payload as trigger.event.data, not
trigger.payload; fix the blueprint, its tests, and the README
examples, with a regression guard.
README gains the blueprint section: all four blueprints, their
inputs, the wiring table to the options-flow notification settings,
the celebration helper setup, event/sensor payload reference, and a
copy-pasteable example package. Tests assert every event trigger
references a real NestQuest event and no reminder is produced for an
absent child.
Child ids sharing a prefix no longer suppress each other (split and
compare entries for equality), and queued mode keeps a second child's
day-complete event from being dropped while the first notify runs.
Triggers on nestquest_child_day_complete (never a zero-quest day),
notifies with the payload's child name, and dedupes per child per
day via an input_text helper holding <child_id>:<today> entries
pruned to today — a re-cleared day does not re-fire, but a second
child clearing the same day still celebrates.
A missing or non-numeric remaining sensor reads as -1 in the
all-clear guard, so a degraded day reaches the parent instead of
looking like a clear day.
Defaults to 20:00 local — deliberately before the midnight missed
sweep, reading the same day's data. Every present child gets a
completion line; owed lines name still-open quests; absent children
are omitted; nothing sends when all clear unless opted in.
The modern HA action schema passes title and message to the notify
service only under data; fix the morning summary and afternoon
reminder blueprints and the test accessors accordingly.
Lists only present children who still owe quests; sends nothing at
all when every present child is clear (a false template condition
stops the action). Blueprint test helpers move to a shared module.
Duplicate child names get HA registry suffixes (_2); the template now
iterates the due sensors (which carry present/child_id/child_name as
attributes) and pairs counts by child_id, so suffixed duplicates are
listed and the household rollup is excluded by the presence guard.
Iterates present children from the Feature 10 presence sensors, lists
what each owes today from the remaining sensor, omits absent children
entirely. Blueprint inputs (time, notify service, send toggle); the
notify target is the household's choice, never hard-coded. PyYAML and
Jinja2 join the dev-only dependencies for blueprint template tests.
Notify target (empty default, household fills their own service),
three strict HH:MM automation times (08:00/15:00/20:00 — the report
deliberately before the midnight sweep), and four independent enable
toggles, all leniently stored and round-tripped.