feat(v1): add local alerts, screenshot and JSONL event log
AlertDispatcher de-duplicates by event_id and fires each side effect once per CONFIRMED: annotated screenshot, one JSONL line (config_version, source_id, no rtsp/credentials), sound and popup via injectable sink. 44 tests pass; real-frame artifact smoke verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from typing import Dict, Optional
|
||||
import numpy as np
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
from v1.alerts import AlertRecord, AlertSink
|
||||
from v1.view_model import (
|
||||
FIELD_BOUNDS,
|
||||
DraftValidationError,
|
||||
@@ -69,6 +70,32 @@ def bgr_to_qimage(image: np.ndarray) -> QtGui.QImage:
|
||||
).copy()
|
||||
|
||||
|
||||
class QtAlertSink(AlertSink):
|
||||
"""Desktop sound + one non-blocking popup per confirmed event (Windows smoke)."""
|
||||
|
||||
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
|
||||
self._parent = parent
|
||||
|
||||
def play_sound(self, event) -> None:
|
||||
QtWidgets.QApplication.beep()
|
||||
|
||||
def show_popup(self, record: AlertRecord) -> None:
|
||||
box = QtWidgets.QMessageBox(self._parent)
|
||||
box.setIcon(QtWidgets.QMessageBox.Warning)
|
||||
box.setWindowTitle("确认摔倒")
|
||||
box.setText(
|
||||
"人员 {0} 确认摔倒\n确认延迟 {1:.2f} 秒\n截图:{2}".format(
|
||||
record.event.track_id,
|
||||
record.event.latency_seconds,
|
||||
record.screenshot_path.name,
|
||||
)
|
||||
)
|
||||
box.setStandardButtons(QtWidgets.QMessageBox.Ok)
|
||||
box.button(QtWidgets.QMessageBox.Ok).setText("我已知晓")
|
||||
box.setModal(False)
|
||||
box.show()
|
||||
|
||||
|
||||
class VideoView(QtWidgets.QWidget):
|
||||
"""Paint the latest frame plus box/skeleton/id/state overlays."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user