From 3a2e6faa6942b6f4fc925e3a3870d7c0a7694904 Mon Sep 17 00:00:00 2001 From: orchestrator-agent Date: Sat, 19 Sep 2026 14:52:12 -0400 Subject: [PATCH] Document the automation package and test event validity 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. --- README.md | 88 +++++++++++++++++++++++++++++ tests/test_blueprint_afternoon.py | 26 ++++++++- tests/test_blueprint_celebration.py | 22 ++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 441ad74..0ed4e2b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,94 @@ actions: message: "Every quest cleared — legendary!" ``` +## Automation Blueprints + +NestQuest ships four automation blueprints in +`custom_components/nestquest/blueprints/automation/`. Import them in +Home Assistant under **Settings → Automations & Scenes → Blueprints** +(or place the files in your `config/blueprints/automation/` folder), +then create one automation per blueprint, filling the inputs with your +household's choices. + +### Wiring to the integration's notification settings + +The integration's options flow records your notification choices — +notify target, morning/afternoon/end-of-day times, and a toggle per +automation. The blueprints take their own inputs at import time; +fill them from the same values you stored in the options flow (the +integration does not push its settings into imported automations): + +| Blueprint input | Options-flow counterpart | +|---|---| +| `morning_time` (default 08:00) | `morning_summary_time` | +| `afternoon_time` (default 15:00) | `afternoon_reminder_time` | +| `report_time` (default 20:00) | `end_of_day_report_time` | +| `send_summary` / `send_reminder` / `send_report` / `celebrate` | the four `*_enabled` toggles | +| `notify_target` | `notify_target` (your notify service, e.g. `notify.mobile_app_your_phone`) | + +The end-of-day report deliberately runs BEFORE the midnight missed +sweep and reads the same day's data — do not schedule it at the +day-rollover time. + +### The four blueprints + +1. **Morning summary** (`morning_summary.yaml`) — at the configured + time, lists every child whose `binary_sensor.nestquest__present_today` + is on and what they owe from `sensor.nestquest__quests_remaining_today`. + Children not present today are omitted entirely. + +2. **Afternoon reminder** (`afternoon_reminder.yaml`) — at the + configured time, notifies only about present children who still + have open quests. Sends nothing at all when every present child is + clear. + +3. **End-of-day report** (`end_of_day_report.yaml`) — one message per + evening: each present child's completion count and the quests they + still owe (the ones the midnight sweep will mark + `nestquest_quest_missed`). Absent children are omitted. Sends + nothing on an all-clear day unless `send_even_when_clear` is on. + +4. **Day-complete celebration** (`day_complete_celebration.yaml`) — + triggers on the `nestquest_child_day_complete` event (which only + fires when quests were owed and all are complete, so a zero-quest + day never celebrates). Create one `input_text` helper + (`nestquest_last_celebrated`) for the dedupe: the automation + records each celebrated child as `:` and + celebrates each child at most once per day — a re-cleared day does + not re-fire, but a second child clearing the same day still + celebrates. + +### Example package + +```yaml +# One automation per blueprint — Settings → Automations & Scenes → +# Create → Blueprint, then pick each NestQuest blueprint. +# +# morning_summary: morning_time 08:00, notify_target notify.mobile_app_your_phone +# afternoon_reminder: afternoon_time 15:00, same notify target +# end_of_day_report: report_time 20:00, send_even_when_clear false, same target +# day_complete_celebration: input_text.nestquest_last_celebrated, +# celebration_message "Legendary! {{ trigger.payload.child_name }} cleared every quest today!" +# +# Every blueprint has its own send/celebrate toggle input; turn an +# automation off entirely from HA's automation menu. +``` + +### Events the automations rely on + +| Event | Payload | +|---|---| +| `nestquest_quest_completed` | child_id, child_name, instance_id, quest_title, window, due_date, due_time, occurred_at (UTC ISO-8601), was_on_time | +| `nestquest_quest_uncompleted` | child_id, child_name, instance_id, quest_title, window, due_date, due_time, occurred_at | +| `nestquest_child_day_complete` | child_id, child_name, quests_due, quests_completed, occurred_at | +| `nestquest_quest_missed` | child_id, child_name, instance_id, quest_title, window, due_date, due_time, occurred_at (fired by the nightly sweep) | + +Sensors the automations read: `sensor.nestquest__quests_due_today` +(attributes: `child_id`, `child_name`, `present`, `instances`, +`admin_instances`), `..._quests_remaining_today`, +`..._quests_completed_today`, and +`binary_sensor.nestquest__present_today`. + ## Developer Setup and Testing This project uses [`uv`](https://github.com/astral-sh/uv) for fast Python package and dependency management. diff --git a/tests/test_blueprint_afternoon.py b/tests/test_blueprint_afternoon.py index 7c13c77..d721bbc 100644 --- a/tests/test_blueprint_afternoon.py +++ b/tests/test_blueprint_afternoon.py @@ -241,4 +241,28 @@ def test_afternoon_reminder_never_hard_codes_notify_target() -> None: assert re.search(r"notify\.[a-z_]+", target_description), ( "the input description should show a placeholder example" ) - assert single_action(bp)["action"] == BlueprintInput("notify_target") \ No newline at end of file + assert single_action(bp)["action"] == BlueprintInput("notify_target") + +def test_afternoon_reminder_absent_child_regression() -> None: + """The Feature 11 guardrail, stated as one regression test: no + reminder message is ever produced for an absent child.""" + states = make_states( + sensors=[ + TemplateState( + "sensor.nestquest_bob_quests_due_today", + "3", + {"child_name": "Bob", "child_id": 1, "present": False}, + ), + TemplateState( + "sensor.nestquest_bob_quests_remaining_today", + "3", + {"child_id": 1}, + ), + ], + ) + guard = afternoon_conditions(afternoon_reminder())[-1] + assert render(guard["value_template"], states=states).strip() == "False" + message = render( + notify_data(afternoon_reminder())["message"], states=states + ) + assert "Bob" not in message diff --git a/tests/test_blueprint_celebration.py b/tests/test_blueprint_celebration.py index 9d61277..2e20b81 100644 --- a/tests/test_blueprint_celebration.py +++ b/tests/test_blueprint_celebration.py @@ -272,3 +272,25 @@ def test_celebration_mode_is_queued() -> None: """Two children completing at nearly the same time must both be processed: queued mode does not drop the second event.""" assert celebration()["mode"] == "queued" + + +def test_every_blueprint_event_reference_is_real() -> None: + """Every event a blueprint triggers on is one NestQuest actually + fires or defines (Feature 10/11) — no invented event names.""" + from tests.blueprint_helpers import load_blueprints + + real_events = { + "nestquest_quest_completed", + "nestquest_quest_uncompleted", + "nestquest_quest_missed", + "nestquest_child_day_complete", + } + for name, bp in load_blueprints().items(): + for trigger in bp.get("triggers", []): + if trigger.get("trigger") == "event": + assert trigger["event_type"] in real_events, ( + f"{name}: invented event {trigger['event_type']!r}" + ) + assert celebration()["triggers"][0]["event_type"] == ( + "nestquest_child_day_complete" + )