Keep models of rooms removed in Options

Saving wrote only the currently configured rooms, so removing a room in
Configure (even by accident) discarded its learned model at the next
save, and adding it back started from scratch.

Keep validated models of removed rooms (at most 24, oldest dropped
first) in an optional retired_learners payload key and restore them when
the room is added back. Payload schema_version stays 2: the key is
additive and older 0.2.x readers ignore it. Replacing the relay still
starts a fresh model, as before. Status lists retired_room_models.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
buzeraandClaude Opus 5.5 committed 2026-10-26 21:50:36 +02:00
1 parent 955c679f5e
commit c0e4de832b
3 files changed
+72 -7

No files matched your search

+26
View File
@@ -498,3 +498,29 @@ class BoundaryTests(unittest.IsolatedAsyncioTestCase):
for _ in range(coordinator.MAX_RELAY_EVENTS):
self.c.record_relay_state(self.now, 'on')
self.assertTrue(self.c._relay_events_dropped)
async def test_removed_room_model_is_kept_and_restored(self):
old = coordinator.Learner(count=500, on_count=250, off_count=250)
self.c._store.async_load.return_value = {'schema_version': 2, 'learners': {
'sensor.room': coordinator.Learner(count=7).as_dict(), 'sensor.gone': old.as_dict()}}
await self.c.async_restore_model()
self.assertEqual(self.c.learners['sensor.room'].count, 7)
await self.sample()
self.assertEqual((await self.sample())['retired_room_models'], ['sensor.gone'])
payload = self.c._store.async_save.call_args.args[0]
self.assertEqual(set(payload['learners']), {'sensor.room'})
self.assertEqual(payload['retired_learners']['sensor.gone']['count'], 500)
self.entry.options['room_entities'] = ['sensor.room', 'sensor.gone']
new = coordinator.HeatingMPCCoordinator(self.hass, self.entry)
new._store.async_load.return_value = payload
await new.async_restore_model()
self.assertEqual(new.learners['sensor.gone'].count, 500)
self.assertEqual(new.learners['sensor.room'].count, 8)
self.assertEqual(new._retired, {})
async def test_retired_room_models_are_bounded_and_validated(self):
retired = {f'sensor.old{i}': coordinator.Learner(count=i + 1).as_dict() for i in range(30)}
retired.update({'sensor.bad': 'broken', 'sensor.empty': coordinator.Learner().as_dict()})
self.c._store.async_load.return_value = {'retired_learners': retired}
await self.c.async_restore_model()
self.assertEqual(list(self.c._retired), [f'sensor.old{i}' for i in range(6, 30)])