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
@@ -46,6 +46,22 @@ class FreshnessTests(unittest.TestCase):
|
||||
self.assertEqual(history.observe('sensor.a', later, 300), 300)
|
||||
self.assertEqual(history.observe('sensor.b', later, 30), 30)
|
||||
|
||||
def test_inputs_differ_ignores_noise_but_not_meaningful_changes(self):
|
||||
before = {('temperature', 'sensor.room'): 20.0, ('power', 'sensor.ups'): 210.0,
|
||||
('wind_direction', 'sensor.dir'): 355.0, ('wind', 'sensor.wind'): 4.0,
|
||||
('window', 'binary_sensor.w'): False, ('thermostat', 'climate.t'): ('heat', 22, None)}
|
||||
noise = {**before, ('temperature', 'sensor.room'): 20.1, ('power', 'sensor.ups'): 240.0,
|
||||
('wind_direction', 'sensor.dir'): 5.0, ('wind', 'sensor.wind'): 4.6}
|
||||
self.assertEqual(quality.inputs_differ(before, noise), []) # 10° across north is small
|
||||
for key, value in ((('temperature', 'sensor.room'), 20.5), (('power', 'sensor.ups'), 610.0),
|
||||
(('wind_direction', 'sensor.dir'), 40.0), (('window', 'binary_sensor.w'), True),
|
||||
(('window', 'binary_sensor.w'), None), (('thermostat', 'climate.t'), ('heat', 21, None)),
|
||||
(('temperature', 'sensor.room'), None)):
|
||||
with self.subTest(key=key, value=value):
|
||||
self.assertEqual(quality.inputs_differ(before, {**before, key: value}), [key])
|
||||
self.assertEqual(quality.inputs_differ(before, {**before, ('power', 'sensor.new'): 5.0}),
|
||||
[('power', 'sensor.new')])
|
||||
|
||||
def test_freshness_settings_translated(self):
|
||||
for name in ('strings.json', 'translations/en.json', 'translations/ro.json'):
|
||||
data = json.loads((ROOT / name).read_text())
|
||||
|
||||
Reference in new issue
Block a user