# Changelog Engineering findings and test evidence behind each change are in [AUDIT.md](AUDIT.md). ## 0.4.0 — 2026-10-27 Thermal zones, electrical heat sources, windows with ventilation and wind, and heat exchange between zones (Milestone 2, first part). Still **observation-only**: no new service calls, the boiler and thermostat are untouched. Tested in the Home Assistant Core harness with HA 2025.12.0 and 2026.2.3; not yet on HAOS. ### Upgrade notes - **Back up Home Assistant first.** The config entry moves to version 3: each room sensor becomes a one-sensor **zone** with its comfort weight, and the sensor's entity ID stays the zone's key, so learned models, forecast entities and accuracy statistics carry over unchanged. No model reset is needed. - **Downgrading needs the backup.** Older versions refuse a version 3 entry. - Configure no longer asks for room sensors or weights; manage zones with **Add zone** and each zone's edit button on the integration page. - Status attributes renamed: `input_quality.rooms` → `input_quality.zones` (each zone lists its sensors), `excluded_rooms` → `excluded_zones`, `retired_room_models` → `retired_zone_models`. The Forecast Error attribute `rooms` is now `zones`, keyed by zone name. The `invalid_weights` status is gone (invalid stored weights fall back to 1.0). Update templates that used these. - New layers start with no assumed effect and need data: the electrical term needs gaming sessions (or other load changes), the ventilation terms need window openings. Watch `improvement_vs_base` on the Forecast Error sensors before relying on them; switch off optional terms that do not help. ### Added - **Zones** (config subentries): name, several sensors with sensor weights, comfort weight, target offset and adjacent zones. Sensor and comfort weights never change learned thermal coefficients. - **Heat sources**: active power (W/kW; VA and % rejected), zone, heat fraction, automatic 24-hour baseline or manual value, "included in" another source (no double counting), thermal response time, enable/disable. - **Windows**: contact sensor (optional inverted polarity; unknown stays unknown), zone, facing direction (default 345°), opening factor, enable/disable; a bounded log of ventilation events. - **Wind direction** and **wind gust** weather sources with priorities and fallback; knots and ft/s accepted. - Model options: electrical-heat forecast half-life, assumed open-window time, recovery time constant, and wind / gust / cross-ventilation terms. - Per-zone diagnostics on each zone's forecast entity: current temperature, +15/+30/+60/+120-minute forecast, contributions in °C/h, coefficients with confidence; Status lists heat sources, windows, ventilation and recent events. - Forecast Error sensors: `base_model_mae` and `improvement_vs_base`. - Poll outcomes `sensors_changed` and `ventilation`, with English and Romanian names. ### Changed - Each zone's model is layered: the unchanged base model (fallback and comparison), an extended layer with electrical heat and neighbours, and a separate ventilation layer. Zones with an open, unknown or recovering window are kept out of base and extended learning. - Forecasts simulate all zones together, with electrical heat fading to baseline and open windows closing after the configured time; the plan search uses an exact compiled step and stays fast at 180 minutes. - Accuracy scoring replays all zones with the actual relay, electrical heat and window history, and scores the base model on the same spans. ### Persisted data - Model payload `schema_version` stays 2 with optional new keys: `zone_models`, `ventilation_models`, `heat_sources` (baseline samples) and `ventilation` (events). Forecast statistics gain a base-model column; 0.3 statistics load. ### Development - New pure modules: `learning.py` (named-feature RLS), `zones.py`, `heat_sources.py`, `windows.py`, `zone_model.py`; [docs/MODEL.md](docs/MODEL.md) documents the model. - Synthetic-apartment tests for gaming heat, ventilation with wind and recovery, and coupled rooms, each checking recovered coefficients and out-of-sample improvement over the base model. ## 0.3.0 — 2026-10-26 First part of Milestone 1: the integration now measures how accurate its thermal model is. Still **observation-only**; no new options. Upgrading from an earlier version: also read the notes of each version in between. ### Added - Four sensors, **Heating MPC Forecast Error 15/30/60/120 min**: the thermal model's mean absolute error over the last 7 days, scored out of sample against what actually happened, using the relay's real ON/OFF history. Attributes show the bias, the error of a no-change forecast (`persistence_mae`), `skill` and per-room details. See README, "Forecast accuracy". - **Heating MPC Learning Progress** (% towards forecasts being used) and **Heating MPC Last Sample** (what the last poll did for learning, with English and Romanian state names), so learning activity is visible without opening attributes. - Room forecast entities: `forecast_15min` (temperature every 15 minutes over the horizon) and `plan_15min` (the proposed ON/OFF plan), labelled counterfactual. - Status: `rejected_samples` per room in `models`, `sample_results_since_start` counting how each poll was handled, and `sample_result: waiting_for_inputs` when the relay, thermostat, outdoor temperature or every room is missing. ### Changed - The model is saved after each poll's forecasts are scored, with the forecast statistics (about 8 KB for five rooms, at most 18 KB). ### Upgrade notes - Six new entities appear (four forecast-error sensors, learning progress, last sample). The error sensors stay unknown until forecasts can be scored: 15 minutes after start for the 15-minute sensor, 2 hours for the 120-minute one. Early numbers include the learning phase and are pessimistic. - Rollback to 0.2.x is safe: older versions ignore the new saved keys, and the six new sensors become unavailable (remove them in the entity settings if you roll back permanently). ### Development - Bug-finding lint (ruff: pyflakes, bugbear) in the checks and CI. - Tests for restoring a model saved by 0.2.1 and for 0.2.1 being able to read today's saved model. - `.DS_Store` files are ignored and rejected by the package check. ## 0.2.3 — 2026-10-26 A stale or failed room sensor no longer pauses the whole integration. Still **observation-only**. Upgrading from 0.2.1 (0.2.2 was not deployed): follow the 0.2.2 upgrade notes below as well. ### Changed - A room sensor that is stale, unavailable, missing or invalid is now **left out** instead of pausing everything: the weighted temperature, learning and forecasts continue with the other rooms, whose comfort weights are re-normalized. Learning and forecasts pause only if no room is usable, or if the relay, thermostat or outdoor temperature is missing. Readiness considers only the rooms used. ### Added - Checkbox **Keep using the last value of a room sensor that stopped reporting** (setup and Configure, default off), for sensors that report only when the value changes. A stale sensor's last value is then still used, for up to 24 hours without a report. Unavailable or invalid sensors are always left out. - Status attributes `excluded_rooms` and `weight_coverage` (share of the total comfort weight covered by the rooms used), and a per-room `used` flag in `input_quality`. - Integration icon (a radiator with a forecast curve) in **Settings → Devices & services**, with a dark-theme variant. Needs Home Assistant 2026.3 or newer; older versions show the generic placeholder. ### Upgrade notes - The new checkbox appears in **Configure**; existing installations start with it off. If a room is often listed in `excluded_rooms` while its temperature is steady, either raise that room's report-age limit or turn the checkbox on. - Rollback to 0.2.2 or 0.2.1 is safe; older versions ignore the new setting. ## 0.2.2 — 2026-10-26 (not deployed) Reliability release (Milestone 0). Still **observation-only**: the integration never calls the relay, the thermostat or any other service. No new options or UI text; entity IDs and settings carry over. Tested in a Home Assistant Core test harness with HA 2025.12.0 and 2026.2.3; not yet tested on HAOS or with real devices. ### Upgrade notes - Back up Home Assistant, replace `custom_components/heating_mpc`, restart Core. - Saved models load, but coefficients learned by 0.2.1 were fitted with a broken radiator-lag estimate (see Fixed). Either let them re-converge over a few days of heating, or reset the model: disable the integration, delete `/config/.storage/heating_mpc.`, restart Core, enable it again. Deleting the file while the integration runs does not work; it saves the model again. Deleting and re-adding the integration also resets it, but loses the settings. - After each HA restart, learning pauses for about 30 minutes (`sample_result: lag_uncertain`) because the radiator state is unknown. Forecasts continue. - After a day of running, compare each sensor's `max_age_seconds_24h` in Status with the freshness limits in Configure (see README, "Choosing freshness limits"). - Rollback: restore the previous folder (or the backup) and restart. 0.2.1 reads models saved by 0.2.2 and ignores the new keys. ### Fixed - The radiator-lag estimate jumped to fully hot or fully cold at every relay change. In simulation this learned the heating gain about a third too low, and forecasts made just after a boiler cycle started from the wrong state. - The learner's covariance grew without limit during long OFF periods (for example a summer), which could destabilize learning when heating resumed and eventually make a restart discard the saved model. - Optional weather sources could not be removed in Configure. - A finished forecast was discarded, making proposed heat unavailable for five minutes, when only a diagnostic weather sensor or the thermostat's current temperature changed during the calculation. - Config entries from 0.1 were not migrated. - Forecast entities of removed rooms stayed in the entity registry. - Malformed or physically implausible saved models could crash setup or be used. - ON/OFF readiness counts included rejected samples and other rooms' history. - Polls 4–7 minutes apart, or containing a relay change, were learned as normal 5-minute samples. - Thermostat targets were read as Celsius on Fahrenheit installations. - Invalid room weights or optimizer settings could cause crashes. - 30/45-minute horizons reported their end point as a 60-minute forecast. ### Added - Room and weather report-age limits in setup and Configure (5–1440 minutes, defaults 30 and 120). Stale outdoor sources fall back to the next one; a stale room pauses learning and forecasts until it reports again. - A read-only listener on the relay, so all relay changes between polls are timestamped and integrated. - Models of rooms removed in Configure are kept (up to 24) and restored if the room is added back. - Status attributes: `input_quality` (per-source state and report age, including `max_age_seconds_24h`), `heat_lag`, `heat_lag_uncertainty`, `sample_result`, `retired_room_models`, and per-room accepted ON/OFF counts in `models`. - Statuses `invalid_weights` and `inputs_changed`. ### Changed - Readiness now also requires 30 accepted ON and 30 accepted OFF samples per room. - Learning skips intervals with a relay change, an outdoor-source switch or an uncertain lag estimate, and polls not 270–330 seconds apart. - `input_quality` and `weather_comparison` are no longer written to the Recorder; they change every poll. Other attributes are still recorded. ### Persisted data - Model payload `schema_version` 2 (0.2.1 wrote no version). Optional keys: per-room `on_count`/`off_count` and `retired_learners`. Older readers ignore them. ### Development - Home Assistant Core test harness for HA 2025.12.0 and 2026.2.3, a Gitea Actions matrix over both, package and translation validation, and a static test that fails if actuator service calls are added. ## 0.2.1 - Corrects the manifest classification from `helper` to `service`. Existing config entries move from Helpers to **Settings → Devices & services → Integrations** after replacing the files and restarting Home Assistant Core. - Deleting the config entry removes its entities and the learned model (`.storage/heating_mpc.`). The component folder stays until removed manually. - MPC behavior otherwise unchanged; read-only / shadow-only.