Reproduced: a window unknown for a whole 15-minute span was replayed as
closed and scored as a Model Error sample (MAE 0.76 °C on one sample).
- A zone's replay is complete only if every step's model inputs were
observed: window openings (and a confirmed recovery), the wind, direction
and other zones' windows that an open window's terms use, and, for the
extended layer, electrical heat and neighbours, whose own replay must be
complete too.
- Incomplete spans are counted, not scored; the base model's error on them
is kept separately (it needs only the relay and outdoor temperature).
Spans that could not be replayed (uncertain radiator lag, no reading at
the end) are counted as skipped. Model Error sensors show incomplete,
skipped and incomplete_base_model_mae; stored data gains an "excluded"
section and older data loads unchanged.
- Window inputs no longer stand in for unknown values: the facing term is
unknown while a window is open and the wind direction is not, and the
cross-ventilation input is unknown while another zone's window is. With
every window closed, opening terms are 0 and need no weather input.
Learning refuses such samples; forecasts still assume calm air.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reproduced: with a window sensor already unavailable at startup, the
forecast assumed the window closed, exactly as for a window last seen
closed, and the first reading of an already open window was logged as a
confirmed opening.
- An opening or closing is confirmed only when witnessed: the previous
reading was the other state with no unknown period between. A first
reading, or a change across an outage or restart, is unconfirmed.
- Last known states, open events and recovery are saved (also whenever a
window's state changes) and restored as unknown until the sensor
reports; saved state for another sensor or polarity is ignored. Downtime
counts as unknown time of an interrupted event.
- Forecast assumption per window: observed; last seen open, assumed open
only until the configured open time after it was last seen (never
renewed); last seen closed or never observed, no ventilation but marked
uncertain. Status shows each window's assumption; zone forecasts list
their uncertain windows.
- Events report a duration only when both ends are confirmed, otherwise a
minimum (and a maximum when the opening was seen).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Confirmed: when an open window's contact sensor became unknown or
unavailable, the tracker ended the ventilation event as a close at the
poll time, started recovery at full weight and forecasts assumed the
window closed. (Learning was already excluded for that zone.)
Each window is now OPEN, CLOSED or UNKNOWN with its last known state,
since when, and since when it is unknown:
- an unknown sensor keeps an open window's event (and the zone's
"open" status) going; forecasts use the last known state;
- a close observed only after an outage is recorded as unconfirmed:
closed=None with the reported time and a maximum duration; recovery
is assumed from that report but not used for any learning;
- opening seen only after an outage starts an event without a
temperature-before, so its recovery is not learned either;
- events count minutes with an unknown sensor; Status shows each
window's last known state and unknown minutes;
- one unknown sensor only blocks learning for its own zone.
Predictions with an unavailable wind sensor now keep an open window's
natural ventilation (calm air) instead of dropping the term; learning
still refuses unknown inputs.
Regression tests for every OPEN/CLOSED/UNKNOWN transition, a six-hour
outage, multiple windows with one unknown, and missing wind during
ventilation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a window adds heat exchange that the envelope model mistook for
poor insulation or a constant cooling bias.
windows.py adds windows as config subentries ("Add window"): a contact
sensor (optionally inverted; unknown/unavailable stays unknown, never
"closed"), its zone, the direction its outside faces (default 345°,
editable) and an opening factor. Each poll computes the share of the
interval each window was open (one change located by its timestamp, a
round trip is unknown), the wind exposure with proper circular angles
(meteorological "from" direction vs the window's facing) and a recovery
weight after closing that decays towards 0, pulling the air back
towards its pre-ventilation temperature. A bounded event log records
opening/closing, duration, temperatures before/lowest/at close/30 min
after, weather and other open windows.
While a zone's window is open, unknown or recovering, the base and
extended models skip it; a separate ventilation learner fits the
closed-window model's residual with natural exchange, wind speed, a
directional term bounded by the wind term (a leeward window still
ventilates), recovery, and optional gust excess and cross-ventilation
terms. Forecasts assume an open window closes after a configurable time
(default 15 min), then recovers. New poll outcome "ventilation".
Weather gains optional wind direction (degrees, 360 -> 0) and wind gust
sources with the same priorities and fallback; knots and ft/s accepted.
On a synthetic week with ventilation events every four hours and a
daily outdoor swing, the configured zone keeps loss/heat/bias close to
the truth and recovers the combined natural+wind effect within 15 %,
recovery within 15 % and direction within 30 %. The same data without
the window configured produces a -0.013 °C/5 min cooling bias and an
11 % inflated boiler effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>