home-assistant/automations/ac_precool_before_peak.yaml
jm 1e3c1001d0 Harden AC rules with workday+weather fallbacks (v2)
Replace the three live AC automations in place (same ids -> same entity_ids,
continuous history/traces):

* pre-cool & peak-setback: the workday check moves out of a blocking
  `condition` into template logic that falls back to a plain Mon-Fri test when
  binary_sensor.workday_sensor is unavailable/unknown. Previously a dead sensor
  silently skipped the run.
* pre-cool: weather.get_forecasts now uses continue_on_error, and a
  `fc is defined and is mapping` guard falls back to an assumed 88F high (->70F
  regular pre-cool) when the forecast hard-errors or returns nothing.
* peak-ends: unchanged behavior (runs daily), documented only.

Add AUTOMATIONS.md (identity model, per-rule spec, failure-mode table, deploy
steps) and deploy.py (pushes automations/*.yaml to the HA config API by id).

Fallbacks verified empirically against the live automations via execution
traces before this commit. Also removed throwaway server-side debug automations
(three TEST spoofs + the ac_flap_1758_1820 experiment).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 21:09:48 -04:00

56 lines
2.9 KiB
YAML

# ac_precool_before_peak — pre-cool the house before the utility peak window
#
# id / entity : ac_precool_before_peak -> automation.ac_pre_cool_before_peak
# trigger : 12:00 local (America/New_York)
# purpose : On work days, pre-cool before the 14:00-19:00 peak, harder when
# the forecast high is hotter (>=90 -> 68, >=85 -> 70, >=80 -> 71,
# below 80 -> no pre-cool).
#
# Graceful degradation (v2) — a broken dependency must NOT skip pre-cooling:
# * workday sensor unavailable/unknown -> fall back to a plain Mon-Fri check.
# * weather forecast fails or returns nothing -> assume a regular hot day
# (high 88F -> 70F). weather.get_forecasts runs with continue_on_error so a
# hard service error can't abort the run; the `fc is defined and is mapping`
# guard catches the soft-failure (empty/None) case.
#
# NOTE: templates are kept on single lines on purpose — YAML folded scalars can
# inject stray whitespace/newlines into Jinja. Readability lives in this header.
#
# v2 (2026-07-09): added workday + weather fallbacks. See AUTOMATIONS.md.
id: ac_precool_before_peak
alias: AC - Pre-cool before peak
description: >-
v2 (2026-07-09). Pre-cools before the 2-7pm peak on work days. Falls back to
Mon-Fri if the workday sensor fails, and to a regular pre-cool (assume 88F high
-> 70F) if the weather forecast fails. Managed in git; see AUTOMATIONS.md.
mode: single
triggers:
- trigger: time
at: "12:00:00"
conditions: []
actions:
- action: weather.get_forecasts
continue_on_error: true
target:
entity_id: weather.forecast_4315aspenhill
data:
type: daily
response_variable: fc
- variables:
is_workday: "{% set wd = states('binary_sensor.workday_sensor') %}{% if wd in ['on','off'] %}{{ wd == 'on' }}{% else %}{{ now().weekday() < 5 }}{% endif %}"
today_high: "{% if fc is defined and fc is mapping and 'weather.forecast_4315aspenhill' in fc and fc['weather.forecast_4315aspenhill'].forecast | default([]) | length > 0 %}{% set today = now().date() %}{% set entries = fc['weather.forecast_4315aspenhill'].forecast | selectattr('datetime') | list %}{% set match = entries | selectattr('datetime','search', today | string) | list %}{{ (match[0].temperature if match else entries[0].temperature) | float(88) }}{% else %}88{% endif %}"
setpoint: "{% set h = today_high | float(88) %}{% if h >= 90 %}68{% elif h >= 85 %}70{% elif h >= 80 %}71{% else %}none{% endif %}"
- if:
- condition: template
value_template: "{{ is_workday and setpoint != 'none' }}"
then:
- action: climate.set_hvac_mode
target:
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
data:
hvac_mode: cool
- action: climate.set_temperature
target:
entity_id: climate.t6_pro_z_wave_programmable_thermostat_with_smartstart
data:
temperature: "{{ setpoint | int }}"