diff --git a/edge/engine.py b/edge/engine.py index 1b567e2..bea72a4 100644 --- a/edge/engine.py +++ b/edge/engine.py @@ -34,8 +34,10 @@ class EventEngine: def _mk(self, etype: EventType, now: float, track_id=None, zone_id=None, metrics=None, spatial=None) -> Event: + m = metrics or {} + duration_s = m.get("immobility_s") or m.get("waited_s") or 0.0 return Event(etype, severity_for(etype), self.cfg.camera_id, track_id, - zone_id, now, 0.0, metrics or {}, spatial) + zone_id, now, duration_s, m, spatial) def process(self, active_tracks: list[Track], lost_tracks: list[Track], now: float) -> list[Event]: diff --git a/tests/test_engine.py b/tests/test_engine.py index db09b00..014ced7 100644 --- a/tests/test_engine.py +++ b/tests/test_engine.py @@ -48,6 +48,27 @@ def test_cooldown_suppresses_duplicate(): again = eng.process([tr], [], now=0.7) assert all(e.event_type != EventType.FALL for e in again) +def test_no_motion_event_carries_duration(): + eng = EventEngine( + FallDetector(FallConfig(prone_confirm_s=0.5, no_motion_s=1.0, motion_thr=0.05)), + ImmobilityWatchdog(ImmobilityConfig(immobile_s=999)), + GhostIncidentDetector(GhostConfig()), + ZoneIntrusionDetector([], ZoneConfig()), + EngineConfig(camera_id="cam-01", cooldown_s=30.0), + ) + tr = Track(1, deque(maxlen=90)) + _lying_track(tr, 0.0); eng.process([tr], [], now=0.0) + _lying_track(tr, 0.6); eng.process([tr], [], now=0.6) # -> fall + _lying_track(tr, 1.0); eng.process([tr], [], now=1.0) # still + _lying_track(tr, 2.0) + events = eng.process([tr], [], now=2.0) # immobile >1.0s -> no_motion + no_motion_events = [e for e in events if e.event_type == EventType.NO_MOTION] + assert len(no_motion_events) == 1 + e = no_motion_events[0] + assert e.duration_s > 0 + assert e.duration_s == e.metrics["immobility_s"] + + def test_allow_cooldown_primitive(): eng = _engine() # EngineConfig(camera_id="cam-01", cooldown_s=30.0) key = ("FALL", 1)