feat: add cold display alarm flow and labeled snapshots
This commit is contained in:
@@ -140,6 +140,96 @@ class BatchEngineTests(unittest.TestCase):
|
||||
self.assertEqual(alarm_events[0]["current_count"], 1)
|
||||
self.assertIn("alerted_at", alarm_events[0])
|
||||
|
||||
def test_pre_warning_emits_once_before_alarm_threshold(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
pre_warning_seconds=60,
|
||||
max_dwell_seconds=120,
|
||||
alarm_removal_seconds=30,
|
||||
trash_confirmation_seconds=30,
|
||||
zone_ids=("1",),
|
||||
)
|
||||
engine = BatchEngine(settings)
|
||||
engine.process(obs(self.t0, {"1": 1}))
|
||||
|
||||
pre_warning_events = engine.process(obs(self.t0 + timedelta(seconds=60), {"1": 1}))
|
||||
repeated_events = engine.process(obs(self.t0 + timedelta(seconds=90), {"1": 1}))
|
||||
|
||||
self.assertEqual([event["event"] for event in pre_warning_events], ["time_pre_warning"])
|
||||
self.assertEqual(pre_warning_events[0]["severity"], "warning")
|
||||
self.assertEqual(pre_warning_events[0]["state"], "pre_warning")
|
||||
self.assertEqual(pre_warning_events[0]["pre_warning_seconds"], 60)
|
||||
self.assertEqual(pre_warning_events[0]["pre_warned_at"], (self.t0 + timedelta(seconds=60)).isoformat())
|
||||
self.assertEqual(pre_warning_events[0]["current_count"], 1)
|
||||
self.assertEqual(repeated_events, [])
|
||||
|
||||
def test_pre_warning_removed_before_alarm_is_auto_handled(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
pre_warning_seconds=60,
|
||||
max_dwell_seconds=120,
|
||||
alarm_removal_seconds=30,
|
||||
trash_confirmation_seconds=30,
|
||||
zone_ids=("1",),
|
||||
)
|
||||
engine = BatchEngine(settings)
|
||||
engine.process(obs(self.t0, {"1": 1}))
|
||||
engine.process(obs(self.t0 + timedelta(seconds=60), {"1": 1}))
|
||||
|
||||
events = engine.process(obs(self.t0 + timedelta(seconds=90), {"1": 0}))
|
||||
|
||||
self.assertEqual([event["event"] for event in events], ["pre_warning_handled"])
|
||||
self.assertEqual(events[0]["severity"], "info")
|
||||
self.assertEqual(events[0]["state"], "handled")
|
||||
self.assertEqual(events[0]["handled_source"], "auto_removed_before_alarm")
|
||||
self.assertEqual(events[0]["ended_at"], (self.t0 + timedelta(seconds=90)).isoformat())
|
||||
|
||||
def test_alarm_removal_timeout_emits_once_before_late_removal(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
pre_warning_seconds=60,
|
||||
max_dwell_seconds=120,
|
||||
alarm_removal_seconds=30,
|
||||
trash_confirmation_seconds=30,
|
||||
zone_ids=("1",),
|
||||
)
|
||||
engine = BatchEngine(settings)
|
||||
engine.process(obs(self.t0, {"1": 1}))
|
||||
engine.process(obs(self.t0 + timedelta(seconds=60), {"1": 1}))
|
||||
alarm_events = engine.process(obs(self.t0 + timedelta(seconds=120), {"1": 1}))
|
||||
|
||||
timeout_events = engine.process(obs(self.t0 + timedelta(seconds=151), {"1": 1}))
|
||||
repeated_events = engine.process(obs(self.t0 + timedelta(seconds=160), {"1": 1}))
|
||||
removal_events = engine.process(obs(self.t0 + timedelta(seconds=170), {"1": 0}, trash=True))
|
||||
|
||||
self.assertEqual([event["event"] for event in alarm_events], ["time_alarm"])
|
||||
self.assertEqual(alarm_events[0]["alarm_removal_deadline"], (self.t0 + timedelta(seconds=150)).isoformat())
|
||||
self.assertEqual([event["event"] for event in timeout_events], ["alarm_removal_timeout"])
|
||||
self.assertEqual(timeout_events[0]["severity"], "alarm")
|
||||
self.assertEqual(timeout_events[0]["state"], "alarm_removal_timeout")
|
||||
self.assertEqual(timeout_events[0]["reason"], "alarmed_batch_not_removed_after_alarm_window")
|
||||
self.assertEqual(repeated_events, [])
|
||||
self.assertEqual([event["event"] for event in removal_events], ["batch_pending_disposal", "batch_discarded"])
|
||||
|
||||
def test_alarmed_batch_removed_within_alarm_window_does_not_emit_removal_timeout(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
pre_warning_seconds=60,
|
||||
max_dwell_seconds=120,
|
||||
alarm_removal_seconds=30,
|
||||
trash_confirmation_seconds=30,
|
||||
zone_ids=("1",),
|
||||
)
|
||||
engine = BatchEngine(settings)
|
||||
engine.process(obs(self.t0, {"1": 1}))
|
||||
engine.process(obs(self.t0 + timedelta(seconds=60), {"1": 1}))
|
||||
engine.process(obs(self.t0 + timedelta(seconds=120), {"1": 1}))
|
||||
|
||||
events = engine.process(obs(self.t0 + timedelta(seconds=150), {"1": 0}, trash=True))
|
||||
|
||||
self.assertEqual([event["event"] for event in events], ["batch_pending_disposal", "batch_discarded"])
|
||||
self.assertTrue(all(event["event"] != "alarm_removal_timeout" for event in events))
|
||||
|
||||
def test_alarmed_batch_removed_without_trash_deposit_escalates_warning(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
@@ -260,6 +350,37 @@ class BatchEngineTests(unittest.TestCase):
|
||||
self.assertEqual(removal_events[0]["batch_id"], "batch_000124")
|
||||
self.assertEqual(removal_events[0]["dwell_seconds"], 1400)
|
||||
|
||||
def test_restore_keeps_alarm_removal_timeout_deadline_after_runtime_restart(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
pre_warning_seconds=60,
|
||||
max_dwell_seconds=120,
|
||||
alarm_removal_seconds=30,
|
||||
trash_confirmation_seconds=30,
|
||||
zone_ids=("1",),
|
||||
)
|
||||
engine = BatchEngine(settings)
|
||||
engine.restore_from_events(
|
||||
[
|
||||
{
|
||||
"event": "time_alarm",
|
||||
"zone_id": "1",
|
||||
"batch_id": "batch_000124",
|
||||
"started_at": self.t0.isoformat(),
|
||||
"pre_warned_at": (self.t0 + timedelta(seconds=60)).isoformat(),
|
||||
"alerted_at": (self.t0 + timedelta(seconds=120)).isoformat(),
|
||||
"current_count": 1,
|
||||
"state": "alerted",
|
||||
},
|
||||
],
|
||||
active_zone_counts={"1": 1},
|
||||
)
|
||||
|
||||
timeout_events = engine.process(obs(self.t0 + timedelta(seconds=151), {"1": 1}))
|
||||
|
||||
self.assertEqual([event["event"] for event in timeout_events], ["alarm_removal_timeout"])
|
||||
self.assertEqual(timeout_events[0]["batch_id"], "batch_000124")
|
||||
|
||||
def test_restore_skips_active_false_positive_when_latest_zone_count_is_empty(self) -> None:
|
||||
settings = EngineSettings(
|
||||
camera_id="test_cam",
|
||||
|
||||
Reference in New Issue
Block a user