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:
1 parent
8eef04c385
commit
35370958fc
5 files changed
+340
-58
No files matched your search
@@ -26,14 +26,24 @@ heat source** and **Add window** create items that can each be edited or deleted
|
||||
later. **Configure** holds everything else: thermostat, relay, weather sources,
|
||||
report-age limits, MPC settings and model options.
|
||||
|
||||
**Zones** are rooms or areas. Each has a name, one or more temperature sensors with
|
||||
sensor weights (how much each counts in the zone temperature), a comfort weight
|
||||
(how much the zone matters to the MPC), an optional target offset relative to the
|
||||
thermostat's target, and optional adjacent zones it exchanges heat with. Sensor and
|
||||
comfort weights never change the learned thermal coefficients. A sensor belongs to
|
||||
one zone only. Upgrading from 0.3 turns each room sensor into a one-sensor zone
|
||||
with the same comfort weight; to group two sensors of one room, edit one zone to
|
||||
include both and delete the other.
|
||||
**Zones** are rooms or areas. Each has a name, one or more temperature sensors, a
|
||||
comfort weight (how much the zone matters to the MPC), an optional target offset
|
||||
relative to the thermostat's target, and optional adjacent zones it exchanges heat
|
||||
with. A zone with several sensors gets a second step, **Sensor weights**: one
|
||||
numbered field per sensor (listed above the fields), setting how much each counts
|
||||
in the zone temperature. A sensor belongs to one zone only. Upgrading from 0.3
|
||||
turns each room sensor into a one-sensor zone with the same comfort weight; to
|
||||
group two sensors of one room, edit one zone to include both and delete the other.
|
||||
|
||||
**What a change does to the learned model.** The zone temperature is defined by
|
||||
its sensors and the ratio of their weights; changing either recalibrates that
|
||||
zone (its coefficients are kept as a starting point, but their confidence, sample
|
||||
counts and accuracy statistics start over, so *Ready* has to be earned again).
|
||||
Scaling all of a zone's sensor weights by the same factor changes nothing. Adding,
|
||||
editing or removing a heat source, a neighbour, a window, a window's orientation
|
||||
or the recovery time resets only the coefficients that depend on it. Comfort
|
||||
weight, target offset and names never touch the model. Each recalibration is
|
||||
listed in the Status attribute `model_changes`.
|
||||
|
||||
**Heat sources** are equipment whose electricity ends up as heat in a zone, such as
|
||||
a UPS feeding computers. Settings: an active-power sensor (W or kW; VA and % are
|
||||
@@ -48,7 +58,11 @@ modeled; the persistent load is already part of the zone's learned behavior.
|
||||
reports *on* when closed (inverted), the direction the outside of the window faces
|
||||
(degrees clockwise from north; default 345°, editable) and an opening factor
|
||||
(1.0 normal, lower for tilted, higher for large). Unknown or unavailable states are
|
||||
treated as unknown, never as closed.
|
||||
treated as unknown, never as closed: a window last seen open counts as possibly
|
||||
open (the forecast assumes it open, the zone's base model does not learn), and an
|
||||
opening that ended while the sensor was unknown is logged as unconfirmed, with its
|
||||
longest possible duration. Recovery after closing is learned only from a confirmed
|
||||
close.
|
||||
|
||||
Model options in **Configure**: how fast extra electrical heat is assumed to fade in
|
||||
forecasts (half-life, default 30 minutes), how long an open window is assumed to
|
||||
@@ -107,13 +121,16 @@ Entities (entity IDs may vary):
|
||||
- **Heating MPC Last Sample** — what the last poll did for learning (for example
|
||||
*Learned*, *Relay switched*, *Waiting for radiators to settle*); its history shows
|
||||
when learning happens.
|
||||
- **Heating MPC Forecast Error 15/30/60/120 min** — how far the thermal model's
|
||||
forecasts were from what actually happened (see *Forecast accuracy*).
|
||||
- **Heating MPC Model Error 15/30/60/120 min** — how well the thermal model
|
||||
explains what happened, replayed with the inputs that actually occurred.
|
||||
- **Heating MPC Forecast Error** — how far published forecasts were from what
|
||||
happened, using only what was known when they were made (see *Forecast
|
||||
accuracy*).
|
||||
|
||||
## Known limitations / safety
|
||||
|
||||
- **Do not** connect the proposed binary sensor directly to the heating switch or build an automation from it. A production controller would need watchdog, manual override, safety handling, and an exclusive relay owner with Generic Thermostat fallback.
|
||||
- Weather variables other than temperature, wind and gusts are **captured but not fitted**. Wind and gusts are used only for open windows. Keep optional model terms only when **Forecast Error** shows they help (`improvement_vs_base`).
|
||||
- Weather variables other than temperature, wind and gusts are **captured but not fitted**. Wind and gusts are used only for open windows. Keep optional model terms only when **Model Error** shows they help (`improvement_vs_base`).
|
||||
- One temperature per zone (no separate wall mass), configured response times, and single-scenario forecasts of electrical heat and windows. See [docs/MODEL.md](docs/MODEL.md#limitations).
|
||||
- All rooms share one heating actuator. The model does not command individual radiators.
|
||||
- No weather forecast: assumes outdoor temperature remains constant during simulated horizon.
|
||||
@@ -142,7 +159,10 @@ with it OFF, which can take a few days depending on how often the boiler runs.
|
||||
The **Heating MPC Status** entity's state is one of `waiting_for_sensors`,
|
||||
`no_zones` (add a zone), `learning`, `paused` (thermostat not in `heat`), `waiting_for_target`, `ready`,
|
||||
`inputs_changed` or `prediction_error`. Proposed heat is available only while it
|
||||
is `ready`. Useful attributes:
|
||||
is `ready`. `inputs_changed` means an input moved meaningfully while the plan was
|
||||
being computed (beyond 0.25 °C, 50 W, 1 m/s wind, 2 m/s gust or 20° direction, or
|
||||
any change of a window, the relay or the thermostat); `changed_inputs` lists them,
|
||||
the plan is discarded and the next poll computes a fresh one. Useful attributes:
|
||||
|
||||
- `input_quality` — per zone (with each of its sensors) and per weather source
|
||||
(grouped by metric): `status` (`valid`, `missing`, `unknown`, `unavailable`,
|
||||
@@ -156,8 +176,10 @@ is `ready`. Useful attributes:
|
||||
- `heat_sources` — per source: measured and effective power (after removing
|
||||
included sources), baseline, variable power above it, thermal response, whether it
|
||||
has settled.
|
||||
- `windows` — per window: state, minutes open, wind direction relative to the
|
||||
window's facing and the resulting exposure (1 = wind straight at it).
|
||||
- `windows` — per window: state (`open`, `closed` or `unknown`), the last known
|
||||
state and for how long the sensor has been unknown, minutes open, wind direction
|
||||
relative to the window's facing and the resulting exposure (1 = wind straight at
|
||||
it).
|
||||
`ventilation` — per zone: current opening, recovery, temperature before opening,
|
||||
its contribution in °C/h and the ventilation coefficients. `ventilation_events` —
|
||||
the last five openings with temperatures before, lowest, at closing and 30
|
||||
@@ -170,14 +192,20 @@ is `ready`. Useful attributes:
|
||||
`ventilation` (only window-affected zones; given to the ventilation model),
|
||||
`partly_rejected` (an outlier in some zone), `interval_out_of_range`,
|
||||
`relay_changed`, `outside_source_changed`, `lag_uncertain` or `waiting_for_inputs`.
|
||||
- `models` — per-zone base coefficients, accepted sample counts and the in-sample
|
||||
5-minute error (`mae_5m`, **not** a forecast accuracy), `model_in_use` and the
|
||||
extended layer's coefficients with confidence.
|
||||
- `models` — per-zone base coefficients with their `confidence`, accepted sample
|
||||
counts and the in-sample 5-minute error (`mae_5m`, **not** a forecast accuracy),
|
||||
`model_in_use` and the extended layer's coefficients with confidence. Confidence
|
||||
(0 to 1) is the share of a coefficient's information that comes from data rather
|
||||
than the starting assumption. It grows only while the data varies that
|
||||
coefficient's input: weeks with the relay always off leave `heat` at 0, however
|
||||
many samples there are.
|
||||
- `model_changes` — the last recalibrations: when, which zone, which coefficients
|
||||
were reset and why.
|
||||
- `retired_zone_models` — removed zones whose learned models are kept.
|
||||
|
||||
`input_quality`, `weather_comparison`, `heat_sources`, `windows`, `ventilation`
|
||||
and `ventilation_events` change every poll, so they are not written to the
|
||||
Recorder; the other attributes are.
|
||||
`input_quality`, `weather_comparison`, `heat_sources`, `windows`, `ventilation`,
|
||||
`ventilation_events` and `model_changes` are not written to the Recorder; the other
|
||||
attributes are.
|
||||
|
||||
### Choosing freshness limits
|
||||
|
||||
@@ -216,22 +244,35 @@ age limit.
|
||||
|
||||
## Forecast accuracy
|
||||
|
||||
The four **Forecast Error** sensors measure the thermal model against reality,
|
||||
continuously and out of sample. At every poll the integration records each zone's
|
||||
reading, the outdoor temperature, the radiator-lag estimate and the model's
|
||||
parameters. 15, 30, 60 and 120 minutes later it replays the zones from the earlier
|
||||
reading using the parameters it had **then**, the outdoor temperature held at its
|
||||
starting value, and the radiator heat that actually followed from the relay's real
|
||||
ON/OFF history. The result is compared with the current reading.
|
||||
Two different questions, two kinds of sensor:
|
||||
|
||||
This tests the thermal model, not the MPC plan, because the plan is never
|
||||
executed. All zones are replayed together, with the electrical heat and window
|
||||
openings that actually happened, and the base model is scored on the same spans. Each sensor's state is the mean absolute error (°C) over the last 7 days,
|
||||
across all zones. Attributes, all in °C except counts:
|
||||
- **Model Error** (four sensors, 15/30/60/120 min): *does the thermal model explain
|
||||
what happened?* Replayed with the relay, electrical heat and windows that
|
||||
actually followed. Useful for judging the model and its options. It is **not**
|
||||
a forecast accuracy: a real forecast does not know when the relay will switch or
|
||||
a window will open.
|
||||
- **Forecast Error** (one sensor): *how good were the forecasts as published?*
|
||||
Uses only what was known when each forecast was made.
|
||||
|
||||
- `samples` — number of scored forecasts. Consecutive forecasts overlap, so they
|
||||
are not independent; treat small counts as indicative only.
|
||||
- `bias` — mean error: negative means the model predicts too cold.
|
||||
### Model Error
|
||||
|
||||
At every poll the integration records each zone's reading, the outdoor
|
||||
temperature, the radiator-lag estimate and the model's parameters. 15, 30, 60 and
|
||||
120 minutes later it replays all zones together from that reading using the
|
||||
parameters it had **then**, the outdoor temperature held at its starting value,
|
||||
and the relay, electrical heat and window history that actually followed. The base
|
||||
model is scored on the same spans. Each sensor's state is the mean absolute error
|
||||
(°C) over the last 7 days, across all zones. Attributes, all in °C except counts:
|
||||
|
||||
- `samples` — number of scored forecasts; `effective_samples` — roughly
|
||||
independent ones. Forecasts start every 5 minutes, so 60-minute forecasts
|
||||
overlap twelvefold, and zones share one boiler, so `effective_samples` is
|
||||
`samples × 5 / horizon / zones`.
|
||||
- `quality` — `insufficient` below 5 effective samples (the state then stays
|
||||
unknown; the numbers are still in the attributes), `indicative` below 30, `ok`
|
||||
from 30.
|
||||
- `mae`, `rmse`, `bias` — mean absolute, root-mean-square and mean error
|
||||
(negative bias: the model predicts too cold).
|
||||
- `persistence_mae` — error of simply assuming no change; the baseline to beat.
|
||||
- `skill` — `1 - mae / persistence_mae`: 1 is perfect, 0 is no better than
|
||||
assuming no change, negative is worse. Empty while the temperature did not change.
|
||||
@@ -240,13 +281,35 @@ across all zones. Attributes, all in °C except counts:
|
||||
make it worse: switch that option off).
|
||||
- `zones` — the same numbers per zone (not written to the Recorder).
|
||||
|
||||
Forecasts are scored only where the inputs were known: polls 4.5–5.5 minutes
|
||||
apart, a usable reading at the start and the end, and a well-known radiator-lag
|
||||
estimate throughout. A missing relay, outdoor temperature or all zones restarts the
|
||||
replay window, so scores resume 15–120 minutes later. Statistics are kept with the
|
||||
model and survive restarts; deleting the model resets them.
|
||||
Spans are scored only where the inputs were known: polls 4.5–5.5 minutes apart, a
|
||||
usable reading at the start and the end, and a well-known radiator-lag estimate
|
||||
throughout. A missing relay, outdoor temperature or all zones restarts the replay
|
||||
window, so scores resume 15–120 minutes later. Statistics are kept with the model
|
||||
and survive restarts; recalibrating a zone or deleting the model resets them.
|
||||
|
||||
What it does not cover: outdoor temperature changes during the horizon (held
|
||||
### Forecast Error
|
||||
|
||||
Each time a plan is published, its forecast is recorded as it was: start
|
||||
temperatures, the trajectory, the plan, the model revision and the assumptions
|
||||
(outdoor temperature and wind held, electrical heat fading, open windows closing).
|
||||
When the forecast's horizon comes, it is scored against the reading, but only if
|
||||
the relay actually followed the plan: in every 15-minute block up to that horizon
|
||||
the relay must have been in the planned state for at least 12 of the 15 minutes.
|
||||
Otherwise the forecast is not an error measurement, and is counted instead:
|
||||
|
||||
- `counterfactual` — the relay did something else: Generic Thermostat, which
|
||||
controls it, disagreed with the plan. Expected in observation mode whenever the
|
||||
two disagree.
|
||||
- `unverifiable` — the relay history is incomplete for that span.
|
||||
- `missing` — no poll near the due time, or no reading for a zone.
|
||||
|
||||
The state is the 60-minute MAE once its quality is at least `indicative`; the
|
||||
attribute `horizons` (not written to the Recorder) gives MAE, RMSE, bias, the
|
||||
no-change error, `improvement_vs_persistence`, sample counts, quality, the counts
|
||||
above and the per-zone figures for each horizon. Statistics cover 7 days and
|
||||
survive restarts; forecasts not yet due when HA restarts are dropped.
|
||||
|
||||
What neither covers: outdoor temperature changes during the horizon (held
|
||||
constant, as in the MPC), and periods when the model was still learning (included,
|
||||
so early numbers are pessimistic). The roadmap's working target is about
|
||||
0.3–0.4 °C at 60 minutes with clearly positive skill, before any control is
|
||||
@@ -287,7 +350,8 @@ The brand icons in `custom_components/heating_mpc/brand/` are drawn by
|
||||
`python scripts/make_brand_icons.py` (uses Pillow from the test requirements);
|
||||
the package check verifies their sizes and transparency.
|
||||
|
||||
HA tests cover setup, config/options forms, migration, reload and entity cleanup,
|
||||
HA tests cover setup, config/options forms, migration, reload (a single relay
|
||||
listener and polling coordinator after repeated reloads) and entity cleanup,
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user