Each temperature sensor used to be its own "room", so a room with two
sensors counted twice and comfort importance was tied to sensors.
zones.py adds Zone: a name, one or more sensors with sensor weights, a
comfort weight, an optional target offset and adjacent zones (made
symmetric). Sensor weights only combine readings; comfort weights only
steer the MPC; neither touches the learned thermal coefficients.
Zones are config subentries (HA's native "Add zone" / edit / delete on
the integration page), with validation that a sensor belongs to one
zone and is not an outdoor source, and a sensor-weight step for
multi-sensor zones. Initial setup still asks for room sensors and
weights and creates one zone per sensor. Configure no longer asks for
rooms or weights.
Config entry version 3. Migrating from 2 turns each room sensor into a
one-sensor zone with its comfort weight and the sensor's entity ID as
the zone key, so learned models, forecast entities (now linked to their
zone, so deleting a zone removes them) and accuracy statistics carry
over. Older versions cannot load a version 3 entry; rolling back needs
the HA backup.
A zone learns only across polls that used the same sensors (a sensor
dropping out shifts the mean by its offset); the new poll outcome
"sensors_changed" reports this. Status renames: input_quality.zones
(per-sensor detail nested), excluded_zones, retired_zone_models; adds
zone_names. Invalid stored comfort weights now fall back to 1.0, so the
invalid_weights status is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
During a first test the Status entity only said "learning" for the
first half hour (the radiator-lag wait after a restart), and the
attributes explaining why were hard to find. Add two sensors:
- Learning Progress (%): for the least trained room, the mean of its
three readiness requirements (200 samples, 30 ON, 30 OFF), each capped
at 100 %; 100 % means ready. Attributes show the counts.
- Last Sample: an enum of the last poll's learning outcome, with English
and Romanian state names (e.g. "Waiting for radiators to settle"). Its
history shows when learning happens.
Polls blocked by missing shared inputs now report waiting_for_inputs.
Readiness thresholds move to const.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was no evidence of how accurate the thermal model is: the only
number was an in-sample 5-minute error.
At every poll, record each room's reading, the outdoor temperature, the
radiator-lag estimate and the model parameters. 15, 30, 60 and 120
minutes later, replay the room from that poll with the parameters it
had then (out of sample), the outdoor temperature held constant and the
radiator lag that actually followed from the relay's real history, and
compare with the reading now. A no-change forecast is scored alongside.
Only spans with regular polls and a well-known lag are scored. 7 days
of per-room statistics are saved with the model (optional "evaluation"
payload key; about 18 KB at most).
Four new sensors, Heating MPC Forecast Error 15/30/60/120 min, show the
mean absolute error with bias, persistence error, skill and per-room
detail. On the synthetic lagged plant the 60-minute error is 0.03 °C
against 0.39 °C for persistence.
Room forecast entities gain forecast_15min and plan_15min attributes
and are labelled counterfactual. Volatile attributes are unrecorded.
The model is now saved after scoring, so saved statistics are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Choosing room and weather freshness limits required knowing how long
each sensor normally goes without reporting, which HA does not show.
Add max_age_seconds_24h to every source in input_quality: the largest
report age seen at polls over the last 24 hours (hourly buckets, reset
on reload, underestimates by up to one poll interval).
input_quality and weather_comparison change every poll, so the Recorder
stored a new attribute row every five minutes. Mark them unrecorded;
they remain visible on the Status entity. Model coefficients and other
attributes are still recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>