Release 0.2.2

Bump the manifest to 0.2.2 and add CHANGELOG.md with upgrade, reset and
rollback notes covering all reliability work since 0.2.1. The README
drops its accumulated "Unreleased" sections in favor of the changelog
and gains a Diagnostics section explaining the Status attributes and how
to choose freshness limits. The package check now fails if the manifest
version has no changelog entry. ROADMAP marks the Milestone 0 items now
backed by tests and resets the next-session backlog; AUDIT.md records
this round's findings.

Still observation-only. Tested in the HA Core harness with HA 2025.12.0
and 2026.2.3; not on HAOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
buzeraandClaude Opus 5.5 committed 2026-10-26 21:55:02 +02:00
1 parent 67fbda79a6
commit cd8b65ac2f
6 files changed
+236 -145

No files matched your search

+23
View File
@@ -183,3 +183,26 @@ compilation, package/translation validation and `git diff --check` all pass.
`python3 -m unittest discover -s tests` also passes on Python 3.9.6. The new `python3 -m unittest discover -s tests` also passes on Python 3.9.6. The new
synthetic-plant and windup tests were also run against the original code to confirm synthetic-plant and windup tests were also run against the original code to confirm
they fail there. they fail there.
## 0.2.2 release follow-up
Scope as above; HA Core tests now run on 2025.12.0 and 2026.2.3 (Python 3.13).
No HAOS, device or deployment testing. Still no actuator code.
| Area | Finding | Repair / evidence |
|---|---|---|
| Learning, medium | Polling saw only the relay's current state and last change. Two or more changes between polls (an on/off round trip) left the lag unknown and paused learning for about 30 minutes each time. | Read-only `async_track_state_change_event` listener records each change; all changes between polls are integrated. HA delivers these events one loop iteration late, so the current state stays authoritative and fills in a change not yet recorded. HA Core test: a round trip keeps the error bound below 0.05 (0.46 with the listener disabled). |
| Lifecycle, medium | HA runs unload callbacks for `ConfigEntryNotReady`/`ConfigEntryError` but not for unexpected exceptions during setup, so anything registered with `async_on_unload` before such an error would leak. | Setup removes the listener and coordinator itself on any failure and registers the listener for unload only after success. Offline test with a failing platform setup. |
| Persistence, medium | Saving wrote only configured rooms, so removing a room in Options (even briefly) discarded its model. | Validated models of removed rooms are kept (at most 24) under an optional `retired_learners` key and restored on re-adding. HA Core options round-trip test. Payload `schema_version` stays 2; v0.2.1 was checked by hand to read the new payload. |
| Diagnostics, low | Choosing freshness limits required knowing each sensor's report interval, which HA does not show. | `max_age_seconds_24h` per source (largest age at polls, hourly buckets, reset on reload). |
| Resources, low | `input_quality` changes every poll (report ages), creating a new Recorder attribute row every five minutes. | `input_quality` and `weather_comparison` marked unrecorded; HA Core test checks `state_info`. |
| Compatibility | Only HA 2025.12.0 was tested. | Added the newest published fixtures (HA 2026.2.3) as a CI matrix entry; all checks pass locally with no deprecation reports for this integration. Newer HA releases remain untested. |
Not changed, by decision: a stale room still pauses learning and forecasts for all
rooms (needs a policy decision); the lag estimate is not persisted (an HA restart
resets the relay's `last_changed`, so continuity cannot be verified anyway).
**Verification.** `sh scripts/check.sh` passes in both test environments (pip
check, 65 offline tests, 18 HA Core tests, compilation, package/translation/changelog
checks, `git diff --check`). The offline tests also pass on Python 3.9.6.
+92
View File
@@ -0,0 +1,92 @@
# Changelog
Engineering findings and test evidence behind each change are in [AUDIT.md](AUDIT.md).
## 0.2.2 — 2026-10-26
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.<entry_id>`, 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.<entry_id>`). The component folder stays until removed
manually.
- MPC behavior otherwise unchanged; read-only / shadow-only.
+92 -134
View File
@@ -1,4 +1,4 @@
# Heating MPC 0.2.1 — custom integration for Home Assistant OS # Heating MPC 0.2.2 — custom integration for Home Assistant OS
**Experimental, observation-only (shadow mode).** It **never** changes the Generic Thermostat, the physical relay, the boiler's setpoint, `heat/off`, or Comfort/Away/Sleep. No Docker add-on, MQTT or InfluxDB connection is needed. **Experimental, observation-only (shadow mode).** It **never** changes the Generic Thermostat, the physical relay, the boiler's setpoint, `heat/off`, or Comfort/Away/Sleep. No Docker add-on, MQTT or InfluxDB connection is needed.
@@ -7,7 +7,7 @@ This project targets Home Assistant OS on a Raspberry Pi 5, with one existing ON
## Install / upgrade ## Install / upgrade
1. Create a Home Assistant backup. 1. Create a Home Assistant backup.
2. Copy the entire folder `custom_components/heating_mpc` into `/config/custom_components/heating_mpc` (replace its contents when upgrading from v0.1.0). 2. Copy the entire folder `custom_components/heating_mpc` into `/config/custom_components/heating_mpc` (replace its contents when upgrading; read the upgrade notes in [CHANGELOG.md](CHANGELOG.md) first).
3. Restart Home Assistant **Core**. 3. Restart Home Assistant **Core**.
4. If new: **Settings → Devices & services → Add integration → Heating MPC**. If installed already: select **Heating MPC → Configure** (Options). 4. If new: **Settings → Devices & services → Add integration → Heating MPC**. If installed already: select **Heating MPC → Configure** (Options).
5. Choose the existing thermostat and physical heating switch (read-only), then individual room sensor entities. 5. Choose the existing thermostat and physical heating switch (read-only), then individual room sensor entities.
@@ -18,7 +18,7 @@ This project targets Home Assistant OS on a Raspberry Pi 5, with one existing ON
## Reconfigurable weather inputs ## Reconfigurable weather inputs
| Measurement | Required? | Used in the thermal model in 0.2.0? | | Measurement | Required? | Used in the thermal model? |
|---|---|---| |---|---|---|
| Outdoor temperature | **Yes** (1–10 prioritized sensors) | **Yes**: estimates thermal losses; constant over the 2 h forecast window | | Outdoor temperature | **Yes** (1–10 prioritized sensors) | **Yes**: estimates thermal losses; constant over the 2 h forecast window |
| Wind speed | No | No, diagnostics/source comparison only | | Wind speed | No | No, diagnostics/source comparison only |
@@ -38,10 +38,10 @@ Switching only the **weather-provider sensors** does not reset learned thermal p
## Model, sampling and output ## Model, sampling and output
- Every 5 minutes: reads HA sensor states and observed relay state; learns a per-room linear thermal model `dT = a*(T_out-T_room) + b*effective_heating + c`. - Every 5 minutes: reads HA sensor states and observed relay state; learns a per-room linear thermal model `dT = a*(T_out-T_room) + b*effective_heating + c`.
- Learns only from valid 5-minute transitions; uses a smoothed heating-demand proxy to approximate radiator lag. - Learns only from valid 5-minute transitions; uses a smoothed heating-demand proxy to approximate radiator lag. Relay changes are timestamped by a read-only state listener, so the lag estimate follows every switch between polls.
- After at least 200 transitions and samples with both heating states, runs an ON/OFF MPC search for the next 60–180 minutes (default 120) in 15-minute command blocks. Protects against rapid simulated toggling. - After at least 200 transitions and samples with both heating states, runs an ON/OFF MPC search for the next 60–180 minutes (default 120) in 15-minute command blocks. Protects against rapid simulated toggling.
- Uses room comfort weights and the **existing** Generic Thermostat's target and heat/off state. **The simulated ON/OFF demand never operates hardware**. - Uses room comfort weights and the **existing** Generic Thermostat's target and heat/off state. **The simulated ON/OFF demand never operates hardware**.
- Persists model parameters using HA's `.storage` so HA restarts do not discard the trained data. - Persists model parameters using HA's `.storage` so HA restarts do not discard the trained data. Models of rooms removed in Configure are kept (up to 24) and restored if the room is added back.
Entities (entity IDs may vary): Entities (entity IDs may vary):
@@ -59,143 +59,101 @@ Entities (entity IDs may vary):
- No weather forecast: assumes outdoor temperature remains constant during simulated horizon. - No weather forecast: assumes outdoor temperature remains constant during simulated horizon.
- No Recorder/InfluxDB training backfill. It learns only from samples collected after installation. - No Recorder/InfluxDB training backfill. It learns only from samples collected after installation.
- The demand switch is not a burner-flame or real gas consumption measurement. - The demand switch is not a burner-flame or real gas consumption measurement.
- Full HAOS/UI runtime integration has **not** been tested; Python/model/unit/source tests are offline checks only. - Not yet tested on HAOS or with real devices. Automated tests run the integration in a Home Assistant Core test harness (HA 2025.12.0 and 2026.2.3) with simulated entities.
- Home Assistant Core APIs and config-flow selectors can change; take a backup before trying this experimental custom integration. - Home Assistant Core APIs and config-flow selectors can change; take a backup before trying this experimental custom integration.
## Offline tests ## Diagnostics
The **Heating MPC Status** entity's state is one of `waiting_for_sensors`,
`learning`, `paused` (thermostat not in `heat`), `waiting_for_target`,
`invalid_weights`, `ready`, `inputs_changed` or `prediction_error`. Proposed heat
is available only while it is `ready`. Useful attributes:
- `input_quality` — for each room and each weather source (grouped by metric):
`status` (`valid`, `missing`, `unknown`, `unavailable`, `stale`,
`invalid_timestamp`, `future_timestamp` or `invalid_value_or_unit`),
`age_seconds` (time since the sensor last reported to HA) and
`max_age_seconds_24h` (largest age seen at polls in the last 24 hours; resets on
reload and can read up to one poll, 5 minutes, low).
- `heat_lag` and `heat_lag_uncertainty` — the radiator-lag estimate (0 cold, 1 fully
warm) and its error bound. Learning waits while the bound is above 0.1, for
example for about 30 minutes after an HA restart.
- `sample_result` — why the last poll was or was not learned: `first_sample`,
`learned`, `partly_rejected` (an outlier in some room), `interval_out_of_range`,
`relay_changed`, `outside_source_changed` or `lag_uncertain`.
- `models` — per-room coefficients, accepted sample counts and the in-sample
5-minute error (`mae_5m`), which is **not** a forecast accuracy.
- `retired_room_models` — removed rooms whose learned models are kept.
`input_quality` and `weather_comparison` change every poll, so they are not
written to the Recorder; the other attributes are.
### Choosing freshness limits
Room and weather report-age limits are set during setup and in **Configure**
(5–1440 minutes; defaults 30 for rooms and 120 for weather). A room sensor older
than its limit pauses learning and forecasts for **all** rooms until it reports
again. A stale outdoor source falls back to the next one in its list; optional
weather metrics never block. Recovery starts a new sampling interval, so nothing is
learned across an outage.
Within the limit, the last reading counts as current, so a too-generous limit lets
a frozen value be learned for a while. A too-tight limit pauses the integration
whenever a sensor is quietly between reports. Freshness uses HA's `last_reported`:
the time a sensor last reported to HA, even with an unchanged value. It is not the
age of the physical measurement, so a provider re-publishing cached data looks
fresh, and sensors that report only on change can look stale in a steady room.
After a day of running, set each limit comfortably above (about 1.5–2×) the largest
`max_age_seconds_24h` among the sensors it covers. The relay and thermostat have no
age limit.
## Release notes
See [CHANGELOG.md](CHANGELOG.md) for changes and upgrade notes per version, and
[AUDIT.md](AUDIT.md) for audit findings and test evidence.
## Developer checks and Forgejo/Gitea Actions
The HA Core test suite runs against **Home Assistant 2025.12.0** (the minimum in
`hacs.json`, `requirements-test.txt`) and **2026.2.3** (newest published test
fixtures, `requirements-test-latest.txt`), both on Python 3.13. Use a dedicated
development virtual environment; these test dependencies are not installed with
the integration and do not change its local, standard-library model inference.
```bash
python3.13 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-test.txt # or requirements-test-latest.txt
sh scripts/check.sh
```
The check script runs dependency consistency checks, the offline tests, the HA
Core tests, compilation, package metadata, translation and changelog checks, and
diff whitespace checks. The 2025.12.0 requirements pin a compatible DNS library
because pycares 5 breaks that release's dependency. The script also disables a
transitive HA test dependency's optional metadata download. The offline tests also
run without Home Assistant installed:
```bash ```bash
python -m unittest discover -s tests -v python -m unittest discover -s tests -v
``` ```
## v0.2.1: integration visibility and clean uninstall
- Corrects the manifest classification from `helper` to `service`. Existing config entries should move from the Helpers listing to **Settings → Devices & services → Integrations** after replacing the files and restarting Home Assistant Core. No deletion/reconfiguration is needed to upgrade.
- When the integration config entry is deleted from the Integrations page, Home Assistant removes its registered entities, and `async_remove_entry` removes the locally persisted learned-model data (`.storage/heating_mpc.<entry_id>`). The custom component source folder remains on disk until manually removed.
- MPC behavior is otherwise unchanged and remains read-only / shadow-only.
## Unreleased: v0.2.1 reliability audit
See [AUDIT.md](AUDIT.md) for findings, fixes, test evidence and remaining gaps.
No active control was added; the manifest version has not been advanced.
- Fix config-entry v1 migration, obsolete room forecast cleanup and coordinator
cleanup after a raised platform setup error.
- Validate saved models atomically and add an explicit payload schema version.
Compatible legacy coefficients survive; readiness now requires at least 30 new
accepted ON and 30 OFF samples **per room** when loading legacy models without
per-room counters. The existing 200-total-sample requirement also applies.
- Reject intervals containing relay changes or outside-provider changes. Accept
only 270–330-second intervals and normalize small timing differences. This can
slow learning; it never generates extra heating cycles.
- Normalize thermostat targets to Celsius; reject invalid weights and optimizer
parameters. Status can report `invalid_weights` or `inputs_changed`; neither
publishes a proposal. Input changes during calculation discard the prediction.
- Model diagnostics include each room's accepted total/ON/OFF sample counts.
Predictions remain **counterfactual**: the proposed plan is not the actual
Generic Thermostat's future command sequence.
Offline lifecycle tests use HA doubles. Actual HA/HAOS lifecycle
validation and held-out prediction accuracy remain unfinished. Back up before
trying these changes; follow the safe trial and rollback notes in AUDIT.md.
## Unreleased: sensor freshness
Room and weather report-age limits are available during setup and in Options
(5–1440 minutes). Defaults are 30 minutes for rooms and 120 minutes for weather;
existing entries use these defaults until changed. Celsius/Fahrenheit handling,
compatible learned models and entity IDs are unchanged.
Stale room readings or an absence of fresh outdoor temperature sources pause
learning and predictions. Weather selection skips stale sources; optional weather
outages do not block predictions. Recovery begins a new sampling interval, so no
training spans an outage. Status attribute `input_quality` lists each room and each
weather source by metric, with report age in seconds and a reason: `valid`,
`missing`, `unknown`, `unavailable`, `stale`, `invalid_timestamp`,
`future_timestamp` or `invalid_value_or_unit`.
Freshness uses HA `last_reported`, falling back to `last_updated` only on older HA
versions without that field. It measures HA state reports, not the age of the
original physical measurement: a provider republishing cached data can look fresh.
Some integrations report only changed values; choose limits appropriate to their
reporting behavior. The relay and thermostat are event-driven references and do
not receive these numeric-sensor age limits. Sensor alignment and relay event
history remain future work. These checks run on the existing five-minute poll.
After backing up and upgrading, check report ages in Status, adjust limits in
Options to match your sensors, and verify fallback/recovery with an unavailable
source. Generic Thermostat remains the sole controller. No HAOS trial was performed.
## Unreleased: radiator-lag tracking and learner stability
See the second-pass section of [AUDIT.md](AUDIT.md). Still shadow-only; no new
options, translations or entities; the manifest version is unchanged.
- **Radiator lag now follows the relay.** Previously the lag estimate jumped to
fully hot or fully cold at each relay change, which biased the learned heating
coefficient low and gave wrong starting points to forecasts right after a boiler
cycle. A relay change between polls is now integrated at its timestamp.
- **Learning pauses briefly when the lag is unknown**: after an HA restart, a relay
outage, or two relay changes inside one five-minute poll. The Status attribute
`heat_lag_uncertainty` shows the error bound; learning resumes once it is at most
0.1, about 32 minutes after a restart. Forecasts continue meanwhile.
- **Long OFF periods no longer destabilize learning.** The learner's covariance is
bounded, so a summer without heating cannot make coefficients jump when heating
resumes, and older saved models with large covariance are loaded rather than reset.
- **Optional weather sources can be removed in Configure.** Clearing wind, cloud or
any additional metric now saves an empty list instead of restoring the old sources.
- The proposed-heat entity is no longer marked unavailable when only a
diagnostic weather sensor or the thermostat's current temperature changed during
the calculation.
- New Status attributes: `heat_lag`, `heat_lag_uncertainty`, `sample_result` (why the
last poll was or was not learned).
**After upgrading:** saved models load unchanged, but coefficients learned before
this change were fitted with the wrong lag. Expect them to re-converge over a few
days of heating and judge forecasts only on data after the upgrade, or delete and
re-add the integration for a clean model (this deletes learned data). After a
restart, check that `sample_result` moves from `lag_uncertain` to `learned` within
about 35 minutes. Generic Thermostat remains the sole relay controller. No HAOS
trial was performed.
## Developer checks and Forgejo/Gitea Actions
The HA Core test suite targets **Home Assistant 2025.12.0 on Python 3.13**,
matching the minimum version in `hacs.json`. Run in a dedicated development
virtual environment; these test dependencies are not installed in the integration
and do not change its local, standard-library model inference.
```bash
python3.13 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements-test.txt
sh scripts/check.sh
```
The check script runs dependency consistency checks, 56 offline tests, 16 tests
using the real HA Core/fixture package, compilation, lightweight package metadata
and translation checks, and diff whitespace checks. A compatible DNS dependency
is pinned because pycares 5 breaks the older HA dependency's import. The script
also disables a transitive HA test dependency's optional metadata download.
HA tests cover setup, config/options forms, migration, reload and entity cleanup, HA tests cover setup, config/options forms, migration, reload and entity cleanup,
scheduled sampling and poll cancellation, stored model restoration/deletion, scheduled sampling and poll cancellation, the relay listener, stored model
missing inputs, manual off/presets and optimizer failure. A fixture rejects all restoration, retention and deletion, missing inputs, manual off/presets, optimizer
HA service calls throughout each test. HA Core runs locally with simulated input failure and Recorder exclusions. A fixture rejects all HA service calls throughout
entities and mocked storage I/O; this does not validate HAOS, physical devices, each test, and a static test fails if actuator service calls are added to the
disk failures or full process restarts. The original standalone unittest command source. HA Core runs locally with simulated input entities and mocked storage I/O;
still works without installing HA. this does not validate HAOS, physical devices, disk failures or full process restarts.
`.gitea/workflows/tests.yml` runs the same checks for pushes and pull requests on `.gitea/workflows/tests.yml` runs the same checks for both HA versions on pushes
the dedicated `bs-node` Actions runner label (`bs-node:docker://node:22-bookworm`). Repository Actions must be enabled. and pull requests, on the dedicated `bs-node` Actions runner label
The runner must support the checkout and setup-uv actions. CI uses pinned uv (`bs-node:docker://node:22-bookworm`). Repository Actions must be enabled, and the
0.9.6 to create a Python 3.13 virtual environment from standalone Python builds; runner must support the checkout and setup-uv actions. CI uses pinned uv 0.9.6 to
create a Python 3.13 virtual environment from standalone Python builds;
`setup-python` cannot provision this version in the Debian Bookworm job image. `setup-python` cannot provision this version in the Debian Bookworm job image.
The environment includes pip for the existing check script. Action caching is Action caching is disabled to avoid requiring GitHub cache services on Gitea.
explicitly disabled to avoid requiring GitHub cache services on Gitea.
Dependencies, Python builds and action downloads need internet access in CI; Dependencies, Python builds and action downloads need internet access in CI;
model inference does not. Remote CI execution has not yet been verified. Local model inference does not. Remote CI execution has not yet been verified; local
validation used macOS and Python 3.13; Linux runner validation remains necessary. validation used macOS.
No runtime integration behavior or manifest version changed in this test commit.
+25 -10
View File
@@ -10,6 +10,9 @@ Success means predictable comfort, understandable decisions, safe operation and
## Current baseline — v0.2.1 ## Current baseline — v0.2.1
> **Current release: 0.2.2** — Milestone 0 reliability work on top of this
> baseline; see [CHANGELOG.md](CHANGELOG.md). The lists below describe 0.2.1.
**Implemented (experimental):** **Implemented (experimental):**
- [x] Native Home Assistant integration with UI-based Config Flow and Options Flow. - [x] Native Home Assistant integration with UI-based Config Flow and Options Flow.
@@ -41,14 +44,14 @@ Current behavior: **shadow mode only**. `generic_thermostat` remains the one and
- [x] Perform a source audit of v0.2.1: pure model, optimizer, config/options, coordinator update timing, persistent model and entity lifecycle. - [x] Perform a source audit of v0.2.1: pure model, optimizer, config/options, coordinator update timing, persistent model and entity lifecycle.
- [x] Add Home Assistant test harness coverage for initial setup, update, restart/reload, options changes, missing sensors, `heat/off`, target changes, teardown and uninstall. - [x] Add Home Assistant test harness coverage for initial setup, update, restart/reload, options changes, missing sensors, `heat/off`, target changes, teardown and uninstall.
- [ ] Verify that **zero physical-device service calls** occur in observation mode, including restart and failed update cases. - [x] Verify that **zero physical-device service calls** occur in observation mode, including restart and failed update cases. *(0.2.2: service-call guard on every HA Core test, including cold start with a stored model and optimizer failure, plus a static source check. Not yet observed on HAOS.)*
- [ ] Establish deterministic handling of 5-minute transitions, polling jitter, timestamp discontinuities, missing data and real switch transitions between samples. - [x] Establish deterministic handling of 5-minute transitions, polling jitter, timestamp discontinuities, missing data and real switch transitions between samples. *(0.2.2: relay changes timestamped by a read-only listener; offline and HA Core tests. Sensor alignment remains open.)*
- [x] Add input freshness thresholds and diagnostics that identify exactly which source is missing/stale/invalid. - [x] Add input freshness thresholds and diagnostics that identify exactly which source is missing/stale/invalid.
- [ ] Verify that source switching, entity renaming, adding/removing rooms and replacing the relay do not silently reuse incompatible fitted coefficients. - [x] Verify that source switching, entity renaming, adding/removing rooms and replacing the relay do not silently reuse incompatible fitted coefficients. *(0.2.2: tests for outdoor-source switching, adding, removing and re-adding rooms, and relay replacement. A renamed sensor is handled as removing one room and adding another; its old model is retained under the old ID.)*
- [ ] Version persisted model schema; test corrupted/older state and migration/rollback behavior. - [ ] Version persisted model schema; test corrupted/older state and migration/rollback behavior.
- [ ] Verify calculated sample totals and successful/failed fitting counters; expose reasons why training/prediction is unavailable. - [ ] Verify calculated sample totals and successful/failed fitting counters; expose reasons why training/prediction is unavailable.
- [ ] Add CI, linting, tests and packaging validation (including HACS metadata/translations). - [ ] Add CI, linting, tests and packaging validation (including HACS metadata/translations).
- [ ] Update installation/release documentation and remove packaged `__pycache__`/`.pyc` files if present. - [x] Update installation/release documentation and remove packaged `__pycache__`/`.pyc` files if present. *(0.2.2: CHANGELOG with upgrade/rollback notes; package check rejects tracked bytecode.)*
**Exit criteria:** No control writes; reproducible tests covering HA lifecycle; clean upgrade/removal; clear status during outages; no significant model-data loss or silent corruption on restart. **Exit criteria:** No control writes; reproducible tests covering HA lifecycle; clean upgrade/removal; clear status during outages; no significant model-data loss or silent corruption on restart.
@@ -78,6 +81,10 @@ shadow-only tripwire test. Relay transitions between polls are now integrated at
their timestamp; multiple changes within one poll still need event-based relay their timestamp; multiple changes within one poll still need event-based relay
history before the transition item below can be checked. history before the transition item below can be checked.
Release 0.2.2 adds that event-based relay history (read-only listener), keeps
models of rooms removed in Options, shows each sensor's longest report gap, stops
recording volatile attributes, and tests against HA 2025.12.0 and 2026.2.3.
## Milestone 1 — Explainable forecasts and dashboards (target v0.3.0) ## Milestone 1 — Explainable forecasts and dashboards (target v0.3.0)
**Objective:** Users can verify what the model predicts and whether it is actually accurate. **Objective:** Users can verify what the model predicts and whether it is actually accurate.
@@ -154,13 +161,21 @@ Only after the Milestone 3 gate has been satisfied:
- Guaranteeing a fuel saving based solely on relay-state history. - Guaranteeing a fuel saving based solely on relay-state history.
- Auto-switching the user's HVAC mode between `off` and `heat`. - Auto-switching the user's HVAC mode between `off` and `heat`.
## Priority backlog for the next Codex session ## Priority backlog for the next session
1. Confirm the imported repository contents match v0.2.1; run existing tests and write an honest baseline audit. Items 1–3 of the original backlog are done (see AUDIT.md and CHANGELOG.md 0.2.2).
2. Review HA lifecycle, polling/sample timing, options reload, storage/cleanup and failure handling; fix P0 problems before features.
3. Add a test harness (HA fixtures/mocks) and a shadow-only invariant test prohibiting all actuator service calls. 1. **HAOS acceptance of 0.2.2 by the user:** install, restart, options reload, room
4. Add forecast recording and held-out evaluation, then expose a trajectory usable by an HA dashboard. removal and re-adding, manual heat/off, a missing sensor, uninstall on a
5. Only then consider richer weather inputs and the live-control **design**; do not implement live control without separate authorization. disposable entry; confirm Generic Thermostat behavior is unchanged.
2. Finish Milestone 0: decide the stale-room policy (pause all rooms, as now, or
keep learning healthy rooms); committed rollback/migration fixtures; failed-fit
counters; lint/type checks; remote CI run.
3. Milestone 1: record forecasts with their input snapshot and evaluate them
walk-forward at 15/30/60/120 minutes against persistence, replaying the actual
relay sequence; then expose trajectories for a dashboard.
4. Only then consider richer weather inputs and the live-control **design**; do not
implement live control without separate authorization.
## Definition of done for each milestone ## Definition of done for each milestone
+1 -1
View File
@@ -9,5 +9,5 @@
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": [], "requirements": [],
"single_config_entry": true, "single_config_entry": true,
"version": "0.2.1" "version": "0.2.2"
} }
+3
View File
@@ -11,6 +11,9 @@ assert manifest["config_flow"] is True
assert manifest["iot_class"] == "local_polling" assert manifest["iot_class"] == "local_polling"
assert not manifest["requirements"], "Model inference should remain dependency-free" assert not manifest["requirements"], "Model inference should remain dependency-free"
json.loads((root / "hacs.json").read_text()) json.loads((root / "hacs.json").read_text())
# A version bump needs release notes (AGENTS.md: no version without documentation).
assert f"## {manifest['version']} " in (root / "CHANGELOG.md").read_text(), \
f"CHANGELOG.md has no entry for {manifest['version']}"
def keys(value, prefix=()): def keys(value, prefix=()):