Refactor store dwell alert management API and dwell engine

- Updated argument parsing in manage_api.py to include new threshold parameters.
- Enhanced _config_payload to include thresholds and webhook configurations.
- Modified _build_summary to track queue metrics and adjust alert reporting.
- Refactored DwellEngine to utilize queue thresholds for alerting and reporting.
- Added queue metrics calculations and status change tracking in dwell_engine.py.
- Updated notifier.py to support posting JSON events to webhooks.
- Adjusted example configuration to reflect new threshold parameters.
- Enhanced Docker entrypoint script for better process management.
- Updated tests to cover new queue metrics and thresholds.
- Improved ManagedServiceDetail and ManagedServices Vue components to display queue metrics.
This commit is contained in:
2026-05-09 11:35:55 +08:00
parent be5014c582
commit ea618fd674
26 changed files with 1605 additions and 117 deletions

View File

@@ -16,7 +16,15 @@ def build_client(project_root: Path):
" rtsp_url: rtsp://before-update\n"
" output_dir: outputs\n"
"rtsp:\n"
" output_subdir: rtsp_stream\n",
" output_subdir: rtsp_stream\n"
"queue:\n"
" source_id: queue_cam_01\n"
" queue_time_threshold_seconds: 300\n"
" crowded_count_threshold: 5\n"
" normal_count_threshold: 2\n"
"webhook:\n"
" url: https://example.test/webhook\n"
" event_log_path: outputs/rtsp_stream/webhook_events.jsonl\n",
encoding="utf-8",
)
@@ -24,13 +32,23 @@ def build_client(project_root: Path):
windows_dir = rtsp_dir / "windows"
windows_dir.mkdir(parents=True, exist_ok=True)
latest_payload = {
"event": "half_hour_report",
"source_type": "rtsp",
"source_id": "queue_cam_01",
"window_start": "2026-04-16T09:30:00+08:00",
"window_end": "2026-04-16T10:00:00+08:00",
"total_people": 7,
"age_counts": {"minor": 1, "adult": 5, "senior": 1},
"gender_counts": {"male": 4, "female": 3},
"unknown_attributes": 2,
"queue_metrics": {
"queue_time_threshold_seconds": 300,
"over_threshold_count": 6,
"under_threshold_count": 2,
"queue_level": "crowded",
"previous_queue_level": "normal",
"status_change": "queue_increased",
},
"tracks": [
{"track_id": 1, "direction": "in"},
{"track_id": 2, "direction": "out"},
@@ -47,6 +65,14 @@ def build_client(project_root: Path):
"window_start": "2026-04-16T09:00:00+08:00",
"window_end": "2026-04-16T09:30:00+08:00",
"total_people": 5,
"queue_metrics": {
"queue_time_threshold_seconds": 300,
"over_threshold_count": 2,
"under_threshold_count": 1,
"queue_level": "normal",
"previous_queue_level": None,
"status_change": "initial",
},
"age_counts": {"minor": 0, "adult": 4, "senior": 1},
"gender_counts": {"male": 2, "female": 3},
"unknown_attributes": 1,
@@ -58,7 +84,12 @@ def build_client(project_root: Path):
json.dumps(latest_payload),
encoding="utf-8",
)
(project_root / "outputs" / "rtsp_run.log").write_text("rtsp ok\n", encoding="utf-8")
(project_root / "outputs" / "rtsp_run.log").write_text(
"rtsp ok\n", encoding="utf-8"
)
(rtsp_dir / "webhook_events.jsonl").write_text(
json.dumps(latest_payload) + "\n", encoding="utf-8"
)
app = create_app(config_path)
app.testing = True
@@ -85,6 +116,8 @@ def test_get_manage_config(tmp_path: Path):
assert response.json["config_path"] == str(config_path)
assert response.json["runtime"]["rtsp_url"] == "rtsp://before-update"
assert response.json["rtsp"]["output_subdir"] == "rtsp_stream"
assert response.json["queue"]["source_id"] == "queue_cam_01"
assert response.json["webhook"]["url"] == "https://example.test/webhook"
def test_put_manage_config_updates_rtsp_url(tmp_path: Path):
@@ -111,8 +144,15 @@ def test_get_manage_summary(tmp_path: Path):
assert response.json["result_type"] == "people_flow_project"
assert response.json["last_result_time"] == "2026-04-16T10:00:00+08:00"
assert response.json["metrics"]["total_people"] == 7
assert response.json["metrics"]["queue_level"] == "crowded"
assert response.json["metrics"]["over_threshold_count"] == 6
assert response.json["metrics"]["under_threshold_count"] == 2
assert response.json["metrics"]["status_change"] == "queue_increased"
assert response.json["metrics"]["direction_counts"] == {"in": 2, "out": 1}
assert response.json["metrics"]["recent_window_stats"][0]["window_end"] == "2026-04-16T10:00:00+08:00"
assert (
response.json["metrics"]["recent_window_stats"][0]["window_end"]
== "2026-04-16T10:00:00+08:00"
)
def test_get_manage_windows(tmp_path: Path):
@@ -126,6 +166,7 @@ def test_get_manage_windows(tmp_path: Path):
assert response.json["page_size"] == 1
assert response.json["items"][0]["window_end"] == "2026-04-16T10:00:00+08:00"
assert response.json["items"][0]["total_people"] == 7
assert response.json["items"][0]["queue_level"] == "crowded"
def test_get_manage_files(tmp_path: Path):
@@ -137,6 +178,7 @@ def test_get_manage_files(tmp_path: Path):
assert {item["path"] for item in response.json["files"]} == {
"outputs/rtsp_run.log",
"outputs/rtsp_stream/latest.json",
"outputs/rtsp_stream/webhook_events.jsonl",
"outputs/rtsp_stream/windows/stats_2026-04-16_09-00-00.json",
"outputs/rtsp_stream/windows/stats_2026-04-16_09-30-00.json",
}