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:
249
docs/managed-queue-webhook.md
Normal file
249
docs/managed-queue-webhook.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Managed Queue Webhook 对接说明
|
||||
|
||||
本文档说明 `managed/store_dwell_alert` 和 `managed/people_flow_project` 两个工程新增的排队统计与 webhook 推送结构,便于接收方按统一协议完成对接。
|
||||
|
||||
## 业务规则
|
||||
|
||||
- 统计窗口固定为每 30 分钟一个窗口。
|
||||
- 每个窗口统计两类人数:
|
||||
- `over_threshold_count`:该窗口内累计排队时间大于等于 5 分钟的人数。
|
||||
- `under_threshold_count`:该窗口内累计排队时间小于 5 分钟但大于 0 的人数。
|
||||
- 一阶段排队等级规则:
|
||||
- `crowded`:`over_threshold_count > 5`
|
||||
- `normal`:`2 <= over_threshold_count <= 5`
|
||||
- `few`:`over_threshold_count < 2`
|
||||
- 状态变化规则:
|
||||
- `queue_increased`:`normal -> crowded` 或 `few -> crowded`
|
||||
- `queue_decreased`:`normal -> few` 或 `crowded -> few`
|
||||
- `queue_normalized`:`crowded -> normal` 或 `few -> normal`
|
||||
- `unchanged`:窗口等级未变化
|
||||
- `initial`:首个统计窗口,没有上一个窗口可比较
|
||||
|
||||
## 配置方式
|
||||
|
||||
### store_dwell_alert
|
||||
|
||||
配置文件示例:`managed/store_dwell_alert/config/config.example.yaml`
|
||||
|
||||
```yaml
|
||||
thresholds:
|
||||
queue_time_threshold_seconds: 300
|
||||
crowded_count_threshold: 5
|
||||
normal_count_threshold: 2
|
||||
pause_timeout_seconds: 300
|
||||
alert_cooldown_seconds: 600
|
||||
|
||||
webhook:
|
||||
url: "https://receiver.example.com/queue-report"
|
||||
timeout_seconds: 5.0
|
||||
```
|
||||
|
||||
### people_flow_project
|
||||
|
||||
配置文件示例:`managed/people_flow_project/config/config.example.yaml`
|
||||
|
||||
```yaml
|
||||
queue:
|
||||
enabled: true
|
||||
area: [0.0, 0.0, 1.0, 1.0]
|
||||
area_mode: "normalized"
|
||||
queue_time_threshold_seconds: 300
|
||||
crowded_count_threshold: 5
|
||||
normal_count_threshold: 2
|
||||
pause_timeout_seconds: 5
|
||||
source_id: "queue_cam_01"
|
||||
|
||||
webhook:
|
||||
url: "https://receiver.example.com/queue-report"
|
||||
timeout_seconds: 5.0
|
||||
event_log_path: "outputs/rtsp_stream/webhook_events.jsonl"
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `queue.area` 用于限定排队区域,默认 `[0, 0, 1, 1]` 表示全画面。
|
||||
- `queue.area_mode=normalized` 表示区域坐标按画面宽高归一化。
|
||||
- 两个工程都会先把 webhook 数据写入本地结果文件,再尝试 HTTP POST。
|
||||
|
||||
## 推送时机
|
||||
|
||||
- 两个工程都会在每个 30 分钟窗口结束时推送一次 `half_hour_report`。
|
||||
- `store_dwell_alert` 仍会继续生成本地事件日志;用于对接的窗口报文以本文档的 `half_hour_report` 结构为准。
|
||||
|
||||
## 公共字段
|
||||
|
||||
两个工程的 webhook 都包含以下公共字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --------------- | ------ | ---------------------------------------------------------------------------------------------- |
|
||||
| `event` | string | 固定为 `half_hour_report` |
|
||||
| `project_type` | string | 工程类型,值为 `store_dwell_alert` 或 `people_flow_project` |
|
||||
| `source_id` | string | 数据源标识。`store_dwell_alert` 使用 `camera_id`,`people_flow_project` 使用 `queue.source_id` |
|
||||
| `window_start` | string | 窗口开始时间,ISO 8601 |
|
||||
| `window_end` | string | 窗口结束时间,ISO 8601 |
|
||||
| `queue_metrics` | object | 排队统计主体 |
|
||||
|
||||
`queue_metrics` 结构:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------------------------ | ----------- | ------------------------------------------------------------------------------------ |
|
||||
| `queue_time_threshold_seconds` | integer | 长等待阈值,当前默认 300 秒 |
|
||||
| `over_threshold_count` | integer | 窗口内累计排队时间大于等于阈值的人数 |
|
||||
| `under_threshold_count` | integer | 窗口内累计排队时间小于阈值但大于 0 的人数 |
|
||||
| `queue_level` | string | `few` / `normal` / `crowded` |
|
||||
| `previous_queue_level` | string/null | 上一个窗口的等级 |
|
||||
| `status_change` | string | `initial` / `unchanged` / `queue_increased` / `queue_decreased` / `queue_normalized` |
|
||||
| `people` | array | 当前窗口内参与统计的人员列表 |
|
||||
|
||||
`queue_metrics.people[]` 结构:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --------------- | ------- | ------------------------------------- |
|
||||
| `person_id` | string | 人员标识 |
|
||||
| `queue_seconds` | integer | 该窗口内累计排队秒数 |
|
||||
| `bucket` | string | `over_threshold` 或 `under_threshold` |
|
||||
|
||||
## store_dwell_alert 完整报文
|
||||
|
||||
`store_dwell_alert` 会额外带上门店停留会话明细:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "half_hour_report",
|
||||
"project_type": "store_dwell_alert",
|
||||
"camera_id": "store_cam_01",
|
||||
"source_id": "store_cam_01",
|
||||
"window_start": "2026-05-08T09:00:00+08:00",
|
||||
"window_end": "2026-05-08T09:30:00+08:00",
|
||||
"active_customer_count": 3,
|
||||
"active_customers": [
|
||||
{
|
||||
"person_id": "cust_101",
|
||||
"session_id": "cust_101-s1",
|
||||
"role": "customer",
|
||||
"status": "active",
|
||||
"dwell_seconds": 820,
|
||||
"window_queue_seconds": 820
|
||||
},
|
||||
{
|
||||
"person_id": "cust_102",
|
||||
"session_id": "cust_102-s1",
|
||||
"role": "customer",
|
||||
"status": "active",
|
||||
"dwell_seconds": 460,
|
||||
"window_queue_seconds": 460
|
||||
}
|
||||
],
|
||||
"closed_customers": [
|
||||
{
|
||||
"person_id": "cust_103",
|
||||
"session_id": "cust_103-s1",
|
||||
"final_dwell_seconds": 260,
|
||||
"window_queue_seconds": 260
|
||||
}
|
||||
],
|
||||
"staff_seen_count": 1,
|
||||
"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",
|
||||
"people": [
|
||||
{
|
||||
"person_id": "cust_101",
|
||||
"queue_seconds": 820,
|
||||
"bucket": "over_threshold"
|
||||
},
|
||||
{
|
||||
"person_id": "cust_102",
|
||||
"queue_seconds": 460,
|
||||
"bucket": "over_threshold"
|
||||
},
|
||||
{
|
||||
"person_id": "cust_103",
|
||||
"queue_seconds": 260,
|
||||
"bucket": "under_threshold"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## people_flow_project 完整报文
|
||||
|
||||
`people_flow_project` 会额外带上过线统计和属性统计结果:
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "half_hour_report",
|
||||
"project_type": "people_flow_project",
|
||||
"source_type": "rtsp",
|
||||
"source": "rtsp://user:password@camera-ip:554/h264/ch1/main/av_stream",
|
||||
"source_id": "queue_cam_01",
|
||||
"window_index": 12,
|
||||
"window_start": "2026-05-08T09:00:00+08:00",
|
||||
"window_end": "2026-05-08T09:30:00+08:00",
|
||||
"window_duration_seconds": 1800,
|
||||
"config_path": "/opt/people_flow_project/config/local.yaml",
|
||||
"line": {
|
||||
"coordinates": [0.1, 0.55, 0.9, 0.55],
|
||||
"mode": "normalized"
|
||||
},
|
||||
"total_people": 7,
|
||||
"age_counts": {
|
||||
"minor": 1,
|
||||
"adult": 5,
|
||||
"senior": 1
|
||||
},
|
||||
"gender_counts": {
|
||||
"male": 4,
|
||||
"female": 3
|
||||
},
|
||||
"unknown_attributes": 2,
|
||||
"tracks": [
|
||||
{
|
||||
"track_id": 1,
|
||||
"direction": "in",
|
||||
"age": 26,
|
||||
"age_bucket": "adult",
|
||||
"gender": "male",
|
||||
"samples_used": 3
|
||||
}
|
||||
],
|
||||
"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",
|
||||
"people": [
|
||||
{
|
||||
"person_id": "track_12",
|
||||
"queue_seconds": 810,
|
||||
"bucket": "over_threshold"
|
||||
},
|
||||
{
|
||||
"person_id": "track_21",
|
||||
"queue_seconds": 180,
|
||||
"bucket": "under_threshold"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 接收方建议
|
||||
|
||||
- 按 `event + project_type + source_id + window_end` 做幂等去重。
|
||||
- 业务判断优先读取 `queue_metrics.queue_level` 和 `queue_metrics.status_change`。
|
||||
- 如果只关心图片里的需求,最少只需要解析:
|
||||
- `source_id`
|
||||
- `window_start`
|
||||
- `window_end`
|
||||
- `queue_metrics.over_threshold_count`
|
||||
- `queue_metrics.under_threshold_count`
|
||||
- `queue_metrics.queue_level`
|
||||
- `queue_metrics.status_change`
|
||||
Reference in New Issue
Block a user