From 1020304dcadf74af82c8ea33907a6a69abd4ff20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9C=A0=EC=B0=BD=EC=9A=B1?= Date: Sat, 11 Jul 2026 20:34:57 +0900 Subject: [PATCH] =?UTF-8?q?feat(edge):=20Event=20model=20+=20severity=20ma?= =?UTF-8?q?pping=20(=C2=A77.3=20payload)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- edge/events.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_events.py | 22 +++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 edge/events.py create mode 100644 tests/test_events.py diff --git a/edge/events.py b/edge/events.py new file mode 100644 index 0000000..48fd08c --- /dev/null +++ b/edge/events.py @@ -0,0 +1,64 @@ +from dataclasses import dataclass +from enum import Enum + + +class EventType(str, Enum): + FALL = "fall" + NO_MOTION = "no_motion" + IMMOBILITY = "immobility" + GHOST = "ghost" + ZONE_WARNING = "zone_warning" + ZONE_RESTRICTED = "zone_restricted" + PINCH = "pinch" + MONITORING_DEGRADED = "monitoring_degraded" + DEVICE_FAULT = "device_fault" + + +class Severity(str, Enum): + CRITICAL = "critical" + HIGH = "high" + WARNING = "warning" + INFO = "info" + + +_SEVERITY = { + EventType.NO_MOTION: Severity.CRITICAL, + EventType.GHOST: Severity.CRITICAL, + EventType.FALL: Severity.HIGH, + EventType.ZONE_RESTRICTED: Severity.HIGH, + EventType.PINCH: Severity.HIGH, + EventType.IMMOBILITY: Severity.WARNING, + EventType.ZONE_WARNING: Severity.WARNING, + EventType.MONITORING_DEGRADED: Severity.WARNING, + EventType.DEVICE_FAULT: Severity.WARNING, +} + + +def severity_for(event_type: EventType) -> Severity: + return _SEVERITY.get(event_type, Severity.INFO) + + +@dataclass(frozen=True) +class Event: + event_type: EventType + severity: Severity + camera_id: str + track_id: int | None + zone_id: str | None + occurred_at: float + duration_s: float + metrics: dict + spatial: tuple | None # (x, y) 유령사고 등 공간 기준 + + def to_payload(self, device_id: str) -> dict: + return { + "device_id": device_id, + "camera_id": self.camera_id, + "event_type": self.event_type.value, + "severity": self.severity.value, + "track_id": self.track_id, + "zone_id": self.zone_id, + "occurred_at": self.occurred_at, + "duration_s": round(self.duration_s, 2), + "metrics": self.metrics, + } diff --git a/tests/test_events.py b/tests/test_events.py new file mode 100644 index 0000000..8250b54 --- /dev/null +++ b/tests/test_events.py @@ -0,0 +1,22 @@ +from edge.events import EventType, Severity, Event, severity_for + + +def test_severity_mapping(): + assert severity_for(EventType.NO_MOTION) == Severity.CRITICAL + assert severity_for(EventType.ZONE_RESTRICTED) == Severity.HIGH + assert severity_for(EventType.FALL) == Severity.HIGH + assert severity_for(EventType.IMMOBILITY) == Severity.WARNING + assert severity_for(EventType.ZONE_WARNING) == Severity.WARNING + + +def test_event_to_payload_shape(): + e = Event(EventType.FALL, Severity.HIGH, camera_id="cam-01", track_id=7, + zone_id=None, occurred_at=1000.0, duration_s=3.2, + metrics={"torso_angle": 78.0}, spatial=None) + p = e.to_payload(device_id="pi-01") + assert p["device_id"] == "pi-01" + assert p["event_type"] == "fall" + assert p["severity"] == "high" + assert p["track_id"] == 7 + assert p["metrics"]["torso_angle"] == 78.0 + assert "snapshots" not in p # snapshots는 Alerter가 붙임