Never publish a plan computed from inputs that have since changed
Confirmed: the check run after the optimizer compared only zone sensors, outdoor temperature, thermostat and relay. A window opening, a UPS load change or a wind change during the calculation went unnoticed; nothing prevented an older update that finished late from overwriting a newer result; and an update in flight during unload still saved. - Every input a prediction uses is captured as a normalized snapshot (zone and outdoor temperatures, wind speed/direction/gust, heat-source power, window states, relay state and switch time, thermostat mode and target) and compared after the calculation with tolerances (0.25 °C, 50 W, 1 m/s, 2 m/s gusts, 20° direction around the circle; exact for relay, thermostat and windows). A meaningful change discards the plan and lists the inputs in changed_inputs; noise does not. No immediate recalculation: an off-schedule poll would break the 5-minute learning interval; the next regular poll computes a fresh plan. - Updates are numbered; one overtaken by a newer update, or running when the entry unloads, keeps the newer data and skips saving and optimizing. Configuration changes reload the entry, so they are covered. - A per-entry storage lock makes a reloaded coordinator restore only after the previous one's save finished, and integration removal waits for it too, so a late save cannot recreate a deleted model file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
099c63cd02
commit
d73e32a4b9
5 files changed
+236
-27
No files matched your search
@@ -37,3 +37,37 @@ class ReportAgeHistory:
|
||||
else:
|
||||
buckets.append([hour, age_seconds])
|
||||
return max(age for start, age in buckets if hour - 24 < start <= hour)
|
||||
|
||||
|
||||
# How much an input may move while a prediction is computed before the result no
|
||||
# longer describes the current situation. Below these, changes are sensor noise.
|
||||
INPUT_TOLERANCES = {
|
||||
"temperature": 0.25, # °C, zone sensors and outdoor sources
|
||||
"power": 50.0, # W, heat-source sensors
|
||||
"wind": 1.0, # m/s
|
||||
"wind_gust": 2.0, # m/s
|
||||
"wind_direction": 20.0, # degrees, compared around the circle
|
||||
}
|
||||
|
||||
|
||||
def inputs_differ(before: dict, after: dict) -> list:
|
||||
"""Inputs whose change is meaningful: keys are (kind, entity), values normalized.
|
||||
|
||||
Kinds without a tolerance (relay, thermostat mode and target, window states,
|
||||
configuration revision) must match exactly; a value appearing or disappearing
|
||||
(for example a sensor becoming unavailable) always counts.
|
||||
"""
|
||||
changed = []
|
||||
for key in sorted(set(before) | set(after), key=str):
|
||||
old, new = before.get(key), after.get(key)
|
||||
tolerance = INPUT_TOLERANCES.get(key[0])
|
||||
if old is None or new is None or tolerance is None:
|
||||
if old != new:
|
||||
changed.append(key)
|
||||
continue
|
||||
difference = abs(new - old)
|
||||
if key[0] == "wind_direction":
|
||||
difference = min(difference, 360.0 - difference)
|
||||
if difference > tolerance:
|
||||
changed.append(key)
|
||||
return changed
|
||||
Reference in new issue
Block a user