Files
managed-portal/docs/managed-queue-webhook.md
skye.yue ea618fd674 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.
2026-05-09 11:35:55 +08:00

250 lines
8.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`