fix(edge): populate Event.duration_s from detector metrics (FIX 3)
edge/engine.py _mk hardcoded duration_s=0.0 for every event. The §7.3 wire contract / P1-B dashboard expects the real incident duration for no_motion, immobility, and ghost events. _mk now derives duration_s from the metrics dict: immobility_s (fall no_motion, immobility watchdog) or waited_s (ghost), falling back to 0.0 for instantaneous confirms (fall, zone). Tests: tests/test_engine.py adds test_no_motion_event_carries_duration, driving a track to NO_MOTION and asserting the emitted event's duration_s is > 0 and equals metrics["immobility_s"].
This commit is contained in:
parent
3240428380
commit
f46ed40088
2 changed files with 24 additions and 1 deletions
|
|
@ -34,8 +34,10 @@ class EventEngine:
|
||||||
|
|
||||||
def _mk(self, etype: EventType, now: float, track_id=None, zone_id=None,
|
def _mk(self, etype: EventType, now: float, track_id=None, zone_id=None,
|
||||||
metrics=None, spatial=None) -> Event:
|
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,
|
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],
|
def process(self, active_tracks: list[Track], lost_tracks: list[Track],
|
||||||
now: float) -> list[Event]:
|
now: float) -> list[Event]:
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,27 @@ def test_cooldown_suppresses_duplicate():
|
||||||
again = eng.process([tr], [], now=0.7)
|
again = eng.process([tr], [], now=0.7)
|
||||||
assert all(e.event_type != EventType.FALL for e in again)
|
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():
|
def test_allow_cooldown_primitive():
|
||||||
eng = _engine() # EngineConfig(camera_id="cam-01", cooldown_s=30.0)
|
eng = _engine() # EngineConfig(camera_id="cam-01", cooldown_s=30.0)
|
||||||
key = ("FALL", 1)
|
key = ("FALL", 1)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue