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:
1 parent
67fbda79a6
commit
cd8b65ac2f
6 files changed
+236
-145
No files matched your search
@@ -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
|
||||
synthetic-plant and windup tests were also run against the original code to confirm
|
||||
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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -7,7 +7,7 @@ This project targets Home Assistant OS on a Raspberry Pi 5, with one existing ON
|
||||
## Install / upgrade
|
||||
|
||||
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**.
|
||||
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.
|
||||
@@ -18,7 +18,7 @@ This project targets Home Assistant OS on a Raspberry Pi 5, with one existing ON
|
||||
|
||||
## 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 |
|
||||
| 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
|
||||
|
||||
- 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.
|
||||
- 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):
|
||||
|
||||
@@ -59,143 +59,101 @@ Entities (entity IDs may vary):
|
||||
- No weather forecast: assumes outdoor temperature remains constant during simulated horizon.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## 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
|
||||
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,
|
||||
scheduled sampling and poll cancellation, stored model restoration/deletion,
|
||||
missing inputs, manual off/presets and optimizer failure. A fixture rejects all
|
||||
HA service calls throughout each test. HA Core runs locally with simulated input
|
||||
entities and mocked storage I/O; this does not validate HAOS, physical devices,
|
||||
disk failures or full process restarts. The original standalone unittest command
|
||||
still works without installing HA.
|
||||
scheduled sampling and poll cancellation, the relay listener, stored model
|
||||
restoration, retention and deletion, missing inputs, manual off/presets, optimizer
|
||||
failure and Recorder exclusions. A fixture rejects all HA service calls throughout
|
||||
each test, and a static test fails if actuator service calls are added to the
|
||||
source. HA Core runs locally with simulated input entities and mocked storage I/O;
|
||||
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
|
||||
the dedicated `bs-node` Actions runner label (`bs-node:docker://node:22-bookworm`). Repository Actions must be enabled.
|
||||
The 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;
|
||||
`.gitea/workflows/tests.yml` runs the same checks for both HA versions on pushes
|
||||
and pull requests, on the dedicated `bs-node` Actions runner label
|
||||
(`bs-node:docker://node:22-bookworm`). Repository Actions must be enabled, and the
|
||||
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.
|
||||
The environment includes pip for the existing check script. Action caching is
|
||||
explicitly disabled to avoid requiring GitHub cache services on Gitea.
|
||||
Action caching is disabled to avoid requiring GitHub cache services on Gitea.
|
||||
Dependencies, Python builds and action downloads need internet access in CI;
|
||||
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.
|
||||
No runtime integration behavior or manifest version changed in this test commit.
|
||||
model inference does not. Remote CI execution has not yet been verified; local
|
||||
validation used macOS.
|
||||
+25
-10
@@ -10,6 +10,9 @@ Success means predictable comfort, understandable decisions, safe operation and
|
||||
|
||||
## 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):**
|
||||
|
||||
- [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] 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.
|
||||
- [ ] Establish deterministic handling of 5-minute transitions, polling jitter, timestamp discontinuities, missing data and real switch transitions between samples.
|
||||
- [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.)*
|
||||
- [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.
|
||||
- [ ] 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.
|
||||
- [ ] 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).
|
||||
- [ ] 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.
|
||||
|
||||
@@ -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
|
||||
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)
|
||||
|
||||
**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.
|
||||
- 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.
|
||||
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.
|
||||
4. Add forecast recording and held-out evaluation, then expose a trajectory usable by an HA dashboard.
|
||||
5. Only then consider richer weather inputs and the live-control **design**; do not implement live control without separate authorization.
|
||||
Items 1–3 of the original backlog are done (see AUDIT.md and CHANGELOG.md 0.2.2).
|
||||
|
||||
1. **HAOS acceptance of 0.2.2 by the user:** install, restart, options reload, room
|
||||
removal and re-adding, manual heat/off, a missing sensor, uninstall on a
|
||||
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
|
||||
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"requirements": [],
|
||||
"single_config_entry": true,
|
||||
"version": "0.2.1"
|
||||
"version": "0.2.2"
|
||||
}
|
||||
@@ -11,6 +11,9 @@ assert manifest["config_flow"] is True
|
||||
assert manifest["iot_class"] == "local_polling"
|
||||
assert not manifest["requirements"], "Model inference should remain dependency-free"
|
||||
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=()):
|
||||
|
||||
Reference in new issue
Block a user