Document the stabilization pass

README: window unknown states, what configuration changes do to the learned
model, inputs_changed tolerances, coefficient confidence, model_changes, and
Model Error vs Forecast Error. docs/MODEL.md: configuration-change table,
confidence semantics, both validation modes. CHANGELOG gains an Unreleased
section (version unchanged); AUDIT and ROADMAP record findings, evidence and
remaining risks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
buzeraandClaude Opus 5.5 committed 2026-10-27 03:55:06 +02:00
1 parent 8eef04c385
commit 35370958fc
5 files changed
+340 -58

No files matched your search

+64 -11
View File
@@ -93,9 +93,16 @@ measure the input side or lower the heat fraction accordingly.
Present when a zone has an enabled window. Learned separately, from the part of
the measured change the closed-window layers cannot explain, only while a window is
open, its state is unknown, or the zone is recovering. During those periods the
base and extended layers do **not** learn, so window events cannot inflate `loss`
or shift `bias`.
open or the zone is recovering, and only while every window state in the zone is
known. While a window is open, possibly open (its sensor is unknown and it was last
seen open) or the zone is recovering, the base and extended layers do **not**
learn, so window events cannot inflate `loss` or shift `bias`.
An unknown window state is never read as closed. A window last seen open stays
assumed open in forecasts; one last seen closed contributes no ventilation term.
If a window closes while its sensor was unknown, the event's duration is reported
as a range and the recovery term is not learned from it, because the closing time
is not known.
`vent_wind_dir` is bounded by `vent_wind` (|vent_wind_dir| ≤ vent_wind), so the
combined wind term never goes negative: a leeward window still ventilates.
@@ -128,11 +135,20 @@ outlier rejection above 0.8 °C per 5 minutes.
Each coefficient reports:
* `confidence`: the share of its information that comes from data rather than the
prior (0 = prior only);
prior (0 = prior only), from its posterior variance. It is not a sample count:
it grows only while the data varies the coefficient's input independently of
the others, and with forgetting it falls again during long periods without such
variation. The base layer reports it too (`loss`, `heat`, `bias`);
* `std`: its standard error, using a running estimate of the unexplained noise;
* `samples`: how many accepted samples actually exercised it;
* `identified`: at least 30 exercising samples and confidence ≥ 0.8.
The base layer has no per-coefficient sample counts or standard errors; its
`confidence` alone shows identification. In the tests, two weeks of samples with
the relay always off and a steady indoor-outdoor difference leave `heat` at 0 and
`loss` and `bias` below 0.8, while four days with the relay cycling and the
weather changing bring all three above 0.8.
A coefficient can move without being identified. Example from the tests: two rooms
whose temperature difference stays fairly steady cannot separate exchange between
them from each room's bias; the learner finds a positive coupling but correctly
@@ -154,6 +170,27 @@ A zone learns from the interval between two polls only when:
No experiments are run and the boiler is never switched to gather data: everything
is learned passively.
## Configuration changes
Every learned component stores a fingerprint of the configuration it was learned
under (`custom_components/heating_mpc/compatibility.py`). At startup, components
whose fingerprint changed are reset; the rest are kept.
| Change | Effect |
|---|---|
| A zone's sensors, or the ratio of their weights | Zone recalibrated: coefficients kept as a starting point; uncertainty, counts and accuracy statistics reset. Its extended and ventilation layers start over too (their loss, heat and bias values kept as a starting point) |
| Scaling all of a zone's weights equally | Nothing (the normalized measurement is unchanged) |
| A heat source's sensor, heat share, baseline, response time or "included in" | `internal` of its zone; the source's baseline samples if its effective power changed |
| A neighbour's sensors or weights | `couple:<neighbour>` |
| A window's sensor, polarity or opening factor | that zone's ventilation terms; `vent_cross` of the other zones |
| A window's facing direction | `vent_wind_dir` |
| Recovery time constant | `recovery` |
| Comfort weight, target offset, names | Nothing (not physical) |
Models saved before fingerprints existed (0.4.0) are assumed to match the
configuration they are loaded with, and are fingerprinted at once. Every reset is
logged in `model_changes` with its reason.
## Forecasts
Forecasts simulate all zones together, step by step, over the MPC horizon (60–180
@@ -177,18 +214,34 @@ they describe what would happen under the MPC's proposed plan.
## Validation
The Forecast Error sensors score forecasts that are valid for comparison with
measured temperatures. At every poll the model parameters are frozen; 15, 30, 60
and 120 minutes later all zones are replayed from that poll using the relay,
electrical heat and window history that actually followed (outdoor temperature held
at its start value), and compared with the readings. The base layer and a
no-change forecast are scored on exactly the same spans:
Two separate measurements, because they answer different questions.
* `mae`, `bias`: the full model's error;
**Model accuracy (Model Error sensors)** scores the model, not a forecast. At
every poll the model parameters are frozen; 15, 30, 60 and 120 minutes later all
zones are replayed from that poll using the relay, electrical heat and window
history that actually followed (outdoor temperature held at its start value),
and compared with the readings. The base layer and a no-change forecast are
scored on exactly the same spans:
* `mae`, `rmse`, `bias`: the full model's error;
* `persistence_mae`, `skill`: against assuming no change;
* `base_model_mae`, `improvement_vs_base`: against the base layer alone. A feature
that does not improve this should be switched off (for example the gust term).
**Forecast accuracy (Forecast Error sensor)** scores forecasts as published: the
start readings, trajectory, plan, model revision and assumptions of the moment
they were made, nothing replaced with hindsight. Because the plan is the MPC's
proposal while Generic Thermostat controls the relay, a forecast is scored at a
horizon only if the relay was in the planned state for at least 12 minutes of
every 15-minute block up to it; otherwise it is counted as `counterfactual` (or
`unverifiable` when the relay history is incomplete) and never as an error.
Both report how much evidence there is. Forecasts start every 5 minutes, so at
horizon h consecutive forecasts share most of their span, and all zones share one
boiler: `effective_samples = samples × 5 / h / zones`. Below 5 the quality is
`insufficient` and the sensor's state stays unknown; below 30 it is
`indicative`.
Results on synthetic apartments (the tests; these check the method, not real-world
accuracy):