HVAC Runtime-vs-Temperature Characterization (WS2 summer slice)¶
Date: 2026-07-07 Ticket: #29 — Energy WS2: finish HVAC characterization Status: Approved (design settled via brainstorm)
Goal¶
Deliver the data-ready, summer-appropriate slice of WS2:
- A one-time diagnostic that computes, per AC condenser, how strongly daily runtime correlates with the daily outdoor high — flagging any system whose runtime does not track temperature (possible stuck relay / thermostat fault / refrigerant problem).
- Live Grafana artifacts on the Home Intelligence dashboard: a runtime-vs-outdoor-high scatter and per-floor runtime stat cards.
The remaining WS2 tasks (heat-pump crossover, simultaneous heat/cool conflict, gas-furnace blower draw) are winter/season-gated and explicitly out of scope here.
Data availability — VERIFIED (2026-07-07)¶
Confirmed by direct InfluxQL query against the live InfluxDB (db=homeassistant, add-on a0d7b954-influxdb):
| Series | Measurement | Field | Tag | Daily reduction | Window confirmed |
|---|---|---|---|---|---|
| Outdoor high | state | temperature | entity_id='forecast_home' | max() GROUP BY time(1d) | 2026-06-12 → 07-07 (26 d; heat wave 100/101/101°F Jul 2–4) |
| Condenser runtime (×3) | h | value | entity_id='{first,second,third}_floor_condenser_runtime_today' | max() GROUP BY time(1d) | 2026-06-13 → 07-07 (25 d; 4–11 h/day) |
weather.forecast_home has no unit_of_measurement on its (string) state, so default_measurement: state (influxdb_ha.yaml) routes its numeric temperature attribute into the state measurement. The entity_id tag carries no sensor. prefix (house convention). Both series exceed the ≥21-day bar the ticket requires. No new HA entities are needed — the runtime booleans/history_stats sensors and the weather integration already exist.
Existing infrastructure reused (no change)¶
binary_sensor.{first,second,third}_floor_condenser_running(>200 W threshold)sensor.{first,second,third}_floor_condenser_runtime_today(history_stats, hours, midnight reset)sensor.em_condenser_floor{1,2,3}_power(raw watts)weather.forecast_home(outdoor temp / forecast)- Grafana HVAC row with the live "HVAC Power — Condensers + Air Handler" panel
Component 1 — One-time diagnostic script¶
File: scripts/analysis/hvac_runtime_correlation.py (repo-committed, run manually — not automated).
What it does: 1. Reads InfluxDB connection from the environment (host/db/user/password), mirroring how CI's Grafana-verify step authenticates. Queries the two daily series above for all three condensers over the full available window. 2. Pairs runtime↔outdoor-high by day (inner join on the GROUP BY time(1d) buckets; drop any day missing either value — e.g. the partial install day). 3. Computes per condenser: Pearson r, OLS slope (hours runtime per °F outdoor high), intercept, sample size n, and the outdoor-high range covered. 4. Verdict per condenser: OK if r ≥ 0.4, else FLAG (weak/no temperature coupling — the ticket's stuck-relay / thermostat / refrigerant signal). In a July cooling season a healthy AC should show a strong positive r. 5. Prints a summary table to stdout and writes the findings (r / slope / verdict per condenser, plus the analysis window) into projects/house_energy_strategy.md → WS2.
Dependencies: Python ≥3.11 + requests (InfluxDB HTTP API) and stdlib statistics (statistics.correlation for Pearson r, 3.10+; statistics.linear_regression for slope, 3.11+) — no numpy/pandas. Keep the dependency surface minimal so it runs anywhere the repo is checked out.
Threshold: r < 0.4 ⇒ FLAG. A single documented constant at the top of the script; adjust in one place if real data argues for stricter/looser.
Component 2 — Grafana live artifacts¶
Edited in grafana/dashboard.json (source of truth; deployed via CI deploy-grafana, file-provider auto-reload ~30 s, no HA restart). Added to the existing HVAC row.
2a. Runtime vs. Outdoor-High — dual-axis timeseries panel¶
Chosen over a literal x-y scatter because the xychart panel's manual x/y mapping is schema-fragile on a provisioned, file-managed Grafana 12 dashboard (no UI to nudge it) and the guard tests can't catch a mis-mapped render. The quantitative correlation (Pearson r + slope) is delivered by the Component-1 diagnostic; this panel's job is the glanceable "does runtime track temperature?" read, which a dual-axis timeseries shows reliably.
- Queries (4): three condenser daily-runtime (
max("value")fromh, per*_condenser_runtime_today,GROUP BY time(1d)) + one outdoor daily-high (max("temperature")fromstate,entity_id='forecast_home',GROUP BY time(1d)). All use$timeFilterandfill(none). - Panel: reuse the exact schema of the existing "HVAC Power — Condensers (per floor)" timeseries panel (id 35) — proven, guards-clean. Left Y-axis = runtime hours (3 series,
barsdraw style, one colour per condenser); right Y-axis (field override on the outdoor-high series) = °F as aline. - Time range: dashboard default; meaningful from ~21 days onward.
2b. Per-floor runtime stat cards — 3 × Stat panel¶
- One Stat panel per condenser: today's runtime (last value of
*_condenser_runtime_today, measurementh) as the big number (unit: hours), with a daily-history sparkline graph mode behind it. - Colour thresholds left neutral (informational, not alerting — short-cycle alerts already exist in automations.yaml).
Grafana guards (Tier-0 tests enforce): real datasource UID (no ${DS_INFLUXDB}), no now()-24h literal, valid JSON. New panels follow the existing panels' datasource-UID pattern.
Testing¶
make test— Tier-0 lint/pytest covers JSON validity + Grafana datasource guards; no new HA entities so Tier-3 entity-ref is unaffected. The diagnostic script is not part of the deploy gate (analysis tool), but must run cleanly end-to-end before findings are recorded.- Manual verification after
deploy-grafana: the dual-axis timeseries renders runtime bars + outdoor-high line tracking together, three stat cards show plausible hours + sparklines, panels resolve their datasource (no "datasource not found").
Docs¶
projects/house_energy_strategy.md— record the correlation findings under WS2 and check off: "Build runtime vs. outdoor temperature correlation for each condenser" and "HVAC health dashboard: per-floor runtime stat cards".docs/grafana.md— document the two new panels + their InfluxQL queries (mirrors how the existing ⚡ Energy / HVAC panels are documented).- No user-manual (
docs/*.mdhousehold) page — this is an analytical/diagnostic surface, not a household-facing feature.
Out of scope¶
- Winter/season-gated WS2 items: heat-pump crossover temperature, simultaneous heat/cool conflict detection, gas-furnace blower-draw characterization (
em_attic_furnace_poweris office-contaminated per #26;em_crawl_furnace_powerclean but idle in summer). - #35 — the physical panel walk to identify the C4 phantom load (separate ticket, hands-on).
- Any HVAC control — Ecobee owns heating/cooling; this is observation only.
- Runtime-normalized-by-sqft comparison — deferred; not required for the health-check goal.
Acceptance criteria¶
- [ ]
scripts/analysis/hvac_runtime_correlation.pyruns against live InfluxDB and prints per-condenserr, slope,n, and OK/FLAG verdict. - [ ] Findings recorded in
projects/house_energy_strategy.mdWS2; the two WS2 checkboxes ticked. - [ ]
grafana/dashboard.jsongains the dual-axis runtime-vs-temp timeseries + 3 runtime Stat panels in the HVAC row. - [ ]
make testpasses (JSON valid, Grafana guards green). - [ ] After deploy, panels render live with resolved datasources (screenshot check).
- [ ]
docs/grafana.mdupdated for the new panels.