From b9137720072ad271aa513fb96467fb64fa473cb6 Mon Sep 17 00:00:00 2001 From: orchestrator-agent Date: Sat, 19 Sep 2026 14:47:37 -0400 Subject: [PATCH] Report when remaining counts are unavailable, not all-clear 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. --- .../automation/end_of_day_report.yaml | 8 ++-- tests/test_blueprint_end_of_day.py | 44 ++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/custom_components/nestquest/blueprints/automation/end_of_day_report.yaml b/custom_components/nestquest/blueprints/automation/end_of_day_report.yaml index e1ab3f0..7809903 100644 --- a/custom_components/nestquest/blueprints/automation/end_of_day_report.yaml +++ b/custom_components/nestquest/blueprints/automation/end_of_day_report.yaml @@ -66,8 +66,10 @@ conditions: - condition: template value_template: "{{ send_report | bool }}" # Skip an all-clear day unless the household opted into - # send-even-when-clear: the guard is false when no present child - # has any quests remaining today. + # send-even-when-clear: the guard is false only when every present + # child has a usable remaining count of zero. A MISSING or + # non-numeric remaining sensor reads as -1 and counts as open — + # a degraded state must reach the parent, not look like all-clear. - condition: template value_template: | {%- set ns = namespace(any_open=false) -%} @@ -79,7 +81,7 @@ conditions: | selectattr('attributes.child_id', 'eq', due.attributes.child_id) | map(attribute='state') | list -%} - {%- if remaining_values and (remaining_values | first | int) > 0 -%} + {%- if not remaining_values or (remaining_values | first | int(-1)) != 0 -%} {%- set ns.any_open = true -%} {%- endif -%} {%- endif -%} diff --git a/tests/test_blueprint_end_of_day.py b/tests/test_blueprint_end_of_day.py index 82b49ee..58856c5 100644 --- a/tests/test_blueprint_end_of_day.py +++ b/tests/test_blueprint_end_of_day.py @@ -293,4 +293,46 @@ def test_end_of_day_report_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_end_of_day_report_guard_true_when_remaining_unavailable() -> None: + """A present child whose remaining sensor is missing or reads + 'unavailable' must NOT look like all-clear: the degraded state + reaches the parent (Codex review regression).""" + states = make_states( + sensors=[ + TemplateState( + "sensor.nestquest_alice_quests_due_today", + "3", + {"child_name": "Alice", "child_id": 1, "present": True}, + ), + TemplateState( + "sensor.nestquest_alice_quests_remaining_today", + "unavailable", + {"child_id": 1}, + ), + ], + ) + rendered = render( + clear_guard(end_of_day_report()), + states=states, + send_even_when_clear=False, + ) + assert rendered.strip() == "True" + + # A missing remaining sensor entirely: also report-worthy. + states_missing = make_states( + sensors=[ + TemplateState( + "sensor.nestquest_alice_quests_due_today", + "3", + {"child_name": "Alice", "child_id": 1, "present": True}, + ), + ], + ) + rendered_missing = render( + clear_guard(end_of_day_report()), + states=states_missing, + send_even_when_clear=False, + ) + assert rendered_missing.strip() == "True"