Files
cold_display_guard/README_zh.md
skye.yue 45e2cf70f7 feat: enrich webhook payloads with downstream event table fields
Add missing fields (event_code, camera_ip, started_at, ended_at,
dwell_seconds, is_discarded, alerted_at, etc.) to both batch_event
and case_event payloads. Introduce source_id config for payload
injection and infer_camera_ip to extract IP from RTSP stream URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-10 17:04:58 +08:00

255 lines
7.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.

# 冷藏展示柜食品批次计时报警
这是一个独立项目,用于单摄像头监控冷藏展示柜和同画面垃圾桶,记录每个展示区域内食品批次的放置时长,并发现超过自定义报警时间后的异常处理行为。
## 已确认业务规则
- 摄像头同时看到展示柜和垃圾桶。
- 展示柜食品区域支持 1 到 10 个自定义区域。
- 食品区域使用阿拉伯数字标注:`1``2``3` ...
- 垃圾桶 ROI 独立标定,不占用食品区域编号。
- 每个区域可以放多份食品,但这些食品按同一批次计时。
- 同一区域不允许混批,必须清空后才能放入新批次。
- 食品放入区域时记录开始时间。
- 区域清空时记录结束时间。
- 未达到报警阈值前清空视为正常消耗。
- 食品在区域内达到 `max_dwell_seconds` 时先产生 `time_alarm`
- 已报警食品从区域移出后,必须在确认窗口内看到垃圾桶投放动作。
- 如果已报警食品移出后没有丢到垃圾桶里,报警事件升级为 `warning_escalated` 警告事件。
- 已报警食品拿出后又放回展示柜,触发违规事件。
## 当前实现范围
当前版本先实现纯业务状态机,不依赖摄像头模型。后续视觉模块只需要输出标准观察数据:
```json
{
"ts": "2026-04-27T10:00:00+08:00",
"zone_counts": {
"1": 1,
"2": 0
},
"trash_deposit": false
}
```
程序会输出 JSONL 事件,例如:
- `batch_started`
- `time_alarm`
- `batch_consumed`
- `batch_pending_disposal`
- `batch_discarded`
- `warning_escalated`
- `mixed_batch_violation`
- `overdue_return_violation`
## 配置
示例配置在 `config/example.toml`
默认阈值:
- 时间报警阈值:`10800` 秒,也就是 3 小时;管理页按分钟输入,例如 20 分钟会保存为 `1200`
- 垃圾桶投放确认窗口:`120`
食品区域配置示例:
```toml
[layout]
zone_count = 3
zone_ids = ["1", "2", "3"]
[[zones]]
id = "1"
label = "区域 1"
polygon = [[0.1, 0.1], [0.3, 0.1], [0.3, 0.3]]
[trash]
roi = [[0.7, 0.7], [0.9, 0.7], [0.9, 0.9]]
```
## 区域标定
项目现在有正式管理页,前端默认 `23000`,后端默认 `19080`
```bash
scripts/run_manage_api.sh
```
另开一个终端:
```bash
scripts/run_web.sh
```
打开:
```text
http://127.0.0.1:23000
```
管理页支持:
- 配置 RTSP 地址和阈值
- 从 RTSP 拉取一帧截图
- 设置 1 到 10 个食品区域
- 标定数字食品区域和垃圾桶 ROI
- 直接保存标定结果到项目配置文件
- 查看事件汇总、区域序号、停留时间、报警和警告事件
- 查看本地处置单状态,并手工标记为已处理
项目仍保留 `tools/calibrator` 作为轻量单页标定工具,但正式使用建议走 `23000` 管理页。
## 管理 API
默认后端:
```text
http://127.0.0.1:19080
```
主要接口:
- `GET /api/manage/health`
- `GET /api/manage/config`
- `PUT /api/manage/config`
- `POST /api/manage/snapshot`
- `PUT /api/manage/calibration`
- `GET /api/manage/summary`
- `GET /api/manage/events`
- `GET /api/manage/cases`
- `GET /api/manage/cases/summary`
- `POST /api/manage/cases/{case_id}/handle`
- `POST /api/manage/webhooks/case-update`
- `GET /api/manage/webhooks/retries`
- `POST /api/manage/webhooks/retries/drain`
`/api/manage/webhooks/case-update` 需要请求头 `X-Webhook-Token`,并且请求体里的 `status` 目前固定为 `handled`
`/api/manage/webhooks/retries` 用于查看最新重试状态,`/api/manage/webhooks/retries/drain` 用于手动触发一次到期重试补偿。
## 运行识别计时进程
管理页只负责配置和查看数据。要产生数据,还需要启动运行进程:
```bash
scripts/run_runtime.sh
```
运行进程会:
1. 按配置读取 RTSP。
2.`ffmpeg` 周期抓取小尺寸 RGB 帧。
3. 按标定区域做占用变化检测。
4. 判断垃圾桶区域是否有明显投放动作。
5. 调用批次计时状态机。
6.`time_alarm``batch_pending_disposal``warning_escalated` 映射到本地处置单状态。
7. 写入 `logs/events.jsonl``logs/cases.jsonl``logs/runtime_diagnostics.jsonl`
8. 按配置向外部系统推送事件 webhook 和处置单 webhook。
当前视觉版本是可运行的启发式版本:
- 每个格口输出 `0/1` 占用状态,不识别单份数量。
- 启动后的前几帧用于建立空柜基线,默认 `3` 帧。
- 如果启动时格口里已经有食品,系统会把它当作基线,后续要等画面变化后才会产生计时事件。
- 真实生产精度后续应接食品检测模型。
可选运行参数可以放在配置文件的 `[runtime]` 中:
```toml
[runtime]
sample_interval_seconds = 5.0
frame_width = 640
frame_height = 360
capture_timeout_seconds = 12.0
baseline_frames = 3
sample_stride_pixels = 4
occupancy_mean_delta = 55.0
occupancy_texture_delta = 18.0
occupancy_dark_luma_threshold = 80.0
occupancy_dark_fraction = 0.06
occupancy_texture_dark_fraction = 0.04
occupancy_bright_luma_threshold = 220.0
occupancy_bright_reflection_fraction = 0.18
occupancy_reflection_dark_fraction = 0.10
occupancy_reflection_bright_dark_ratio = 2.0
occupancy_confirm_frames = 2
empty_confirm_frames = 2
trash_motion_delta = 18.0
trash_sustained_motion_delta = 8.0
trash_sustained_motion_frames = 2
trash_motion_cooldown_seconds = 3
diagnostics_path = "logs/runtime_diagnostics.jsonl"
[case_sink]
path = "logs/cases.jsonl"
[alarm_snapshot_upload]
enabled = true
service_url = "https://ota.zhengxinshipin.com"
secret = "change-me-in-production"
object_key_prefix = "cold-display-guard/alarms"
connect_timeout_seconds = 5
read_timeout_seconds = 20
encode_timeout_seconds = 10
[webhook_retry_sink]
path = "logs/webhook_retry.jsonl"
[webhooks]
enabled = true
event_url = "https://example.com/runtime-events"
case_url = "https://example.com/case-events"
source_id = "cold-display-guard"
callback_token = "shared-secret"
connect_timeout_seconds = 3
read_timeout_seconds = 5
retry_backoff_seconds = 30
retry_batch_limit = 20
retry_max_attempts = 5
retry_max_backoff_seconds = 1800
```
运行时会额外记录:
- `logs/cases.jsonl`:本地处置单状态变更
- `logs/webhook_retry.jsonl`Webhook 重试队列状态快照
- `logs/webhook_delivery.jsonl`Webhook 投递结果审计
当某一轮识别结果里出现 `severity=alarm``severity=warning` 的事件时,运行时会直接复用当前检测帧:
1.`ffmpeg` 把当前 RGB 帧编码成 JPEG
2. 通过 `https://ota.zhengxinshipin.com` 的 chunk-upload API 上传
3. 把上传返回的 `object_key` 追加到对应 webhook payload
相关 webhook 字段:
- `event_code`:下游事件列表可直接使用的稳定编码,当前取批次 ID
- `camera_id` / `camera_ip`:来源设备和摄像头 IP
- `zone_id` / `zone_label`:所属区域
- `started_at`:开始计时时间点
- `ended_at` / `removed_at`:取出时间点
- `dwell_seconds`:当前批次累计计时时长
- `is_discarded` / `discarded_at`:是否已丢弃及丢弃时间点
- `created_at`:该条外部事件记录的创建时间
- `alerted_at` / `alarm_at`:时长告警时间点
- `updated_at`:该条外部事件记录的最新更新时间
- `snapshot_upload_status``uploaded``error`
- `snapshot_object_key`:上传成功后的 OSS 路径
- `snapshot_file_name`:上传文件名
- `snapshot_captured_at`:抓帧时间
- `snapshot_upload_error`:上传失败原因,仅失败时返回
## 本地测试
```bash
PYTHONPATH=src python3 -m unittest discover -s tests -v
```
前端测试和构建:
```bash
node --test web/test/zone-state.test.js
cd web && pnpm build
```