chore(tasks): start T-106 event pipeline

This commit is contained in:
ila
2026-07-21 11:47:07 +08:00
parent 1f6bbd8f98
commit 906e06565e
4 changed files with 128 additions and 6 deletions
+2 -1
View File
@@ -19,12 +19,13 @@
| T-103 | 实现 Pose 适配器与模型来源校验 | T-102 | 输出 person box、17 点和置信度;错误模型或哈希不符时给出明确错误。 | DONE | | T-103 | 实现 Pose 适配器与模型来源校验 | T-102 | 输出 person box、17 点和置信度;错误模型或哈希不符时给出明确错误。 | DONE |
| T-104 | 实现人员跟踪与姿态质量门控 | T-103 | 连续人员维持 ID;低质量、缺失膝踝或空帧不会产生倒地候选。 | DONE | | T-104 | 实现人员跟踪与姿态质量门控 | T-103 | 连续人员维持 ID;低质量、缺失膝踝或空帧不会产生倒地候选。 | DONE |
| T-105 | 实现按 ID 的时序摔倒状态机 | T-104 | 正例在配置秒数内确认;坐下、弯腰、短时低姿态回到 NORMAL;事件副作用只触发一次。 | DONE | | T-105 | 实现按 ID 的时序摔倒状态机 | T-104 | 正例在配置秒数内确认;坐下、弯腰、短时低姿态回到 NORMAL;事件副作用只触发一次。 | DONE |
| T-106 | 装配事件管线与固化事件契约 | T-105 | Pose → 跟踪 → 质量/几何证据 → 领域规则 → 按 ID 状态机可回放运行;缺失/低质量证据会中断确认;全部公开事件参数生效;事件可追溯运行配置版本;录像 EOF 不会重放。 | DOING |
## Phase 2 · V1 演示闭环 ## Phase 2 · V1 演示闭环
| ID | 任务 | 依赖 | 验收要点 | 状态 | | ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| T-201 | 实现 PyQt 顶部双 Tab 的监控与设置界面 | T-105 | 实时监控 Tab 显示视频、骨架、ID、NORMAL/SUSPECT/CONFIRMED 和连接状态;设置 Tab 只编辑非敏感草稿并明确下次启动生效;UI 不直接执行事件计算。 | TODO | | T-201 | 实现 PyQt 顶部双 Tab 的监控与设置界面 | T-106 | 实时监控 Tab 显示视频、骨架、ID、NORMAL/SUSPECT/CONFIRMED 和连接状态;设置 Tab 只编辑非敏感草稿并明确下次启动生效;UI 不直接执行事件计算。 | TODO |
| T-202 | 实现本地报警、弹窗、截图和 JSONL 日志 | T-201 | CONFIRMED 产生一次声音、一次弹窗、一张带标注截图和一条事件日志。 | TODO | | T-202 | 实现本地报警、弹窗、截图和 JSONL 日志 | T-201 | CONFIRMED 产生一次声音、一次弹窗、一张带标注截图和一条事件日志。 | TODO |
| T-203 | 接入海康 RTSP 与断流恢复 | T-202 | 现场有效流可预览;断流状态明确、可重连且不报警。 | TODO | | T-203 | 接入海康 RTSP 与断流恢复 | T-202 | 现场有效流可预览;断流状态明确、可重连且不报警。 | TODO |
| T-204 | 建立正反例录像回归与现场验收记录 | T-203 | 摔倒、行走、坐下、弯腰、捡物和持续倒地都有预期结果;报警延迟记录在 1–3 秒。 | TODO | | T-204 | 建立正反例录像回归与现场验收记录 | T-203 | 摔倒、行走、坐下、弯腰、捡物和持续倒地都有预期结果;报警延迟记录在 1–3 秒。 | TODO |
+5 -5
View File
@@ -5,17 +5,17 @@
## 当前快照 ## 当前快照
- 日期:2026-07-21 - 日期:2026-07-21
- 阶段:V1 事件引擎基线已建立;T-105 已验收,等待 T-201。 - 阶段:V1 事件引擎基线已建立;T-106(事件管线与事件契约)进行中。
- 已验证环境:Windows PowerShell;Python 3.8.10;Ultralytics 8.3.205;PyQt5 可导入。 - 已验证环境:Windows PowerShell;Python 3.8.10;Ultralytics 8.3.205;PyQt5 可导入。
- 旧生产基线:`demo/main.py`、`demo/fall_detection_gui.py`、`demo/detect_fall.py`、`demo/best.pt`。 - 旧生产基线:`demo/main.py`、`demo/fall_detection_gui.py`、`demo/detect_fall.py`、`demo/best.pt`。
- V1 代码:已建立安全配置、视频源、Pose、跟踪、质量/几何证据,以及 `v1/fall_state.py` 的按 ID 四态事件机;PyQt GUI、声音、弹窗、截图、JSONL 和真实 RTSP 接入尚未实现。 - V1 代码:已建立安全配置、视频源、Pose、跟踪、质量/几何证据,以及 `v1/fall_state.py` 的按 ID 四态事件机;Pose/跟踪/证据到状态机的事件管线与事件契约正在由 T-106 装配。PyQt GUI、声音、弹窗、截图、JSONL 和真实 RTSP 接入尚未实现。
- V2 代码:`v2/` 目录存在但尚无实现。 - V2 代码:`v2/` 目录存在但尚无实现。
- 非代码设计工件:docs/ui/silver-pose-ui-ux-spec.md、docs/ui/2026-07-20-html-prototype-plan.md、docs/ui/silver-pose-v1-prototype.html 与 docs/ui/silver-pose-v2-prototype.html 已建立。v2 HTML 是符合正式浅色 Windows 规范的当前视觉参考:浅灰蓝底、白色卡片,红色只表示确认摔倒、其弹窗和事件证据;文件名中的 v2 只表示原型设计修订,不能理解为 Go V2 实现已开始。v1 HTML 保留为历史深色对照。两者均使用顶部双 Tab、设置草稿与状态交互,且画面、事件和时间都是模拟数据,不连接真实摄像头、模型或网络,也不改变 Phase 1 任务顺序。 - 非代码设计工件:docs/ui/silver-pose-ui-ux-spec.md、docs/ui/2026-07-20-html-prototype-plan.md、docs/ui/silver-pose-v1-prototype.html 与 docs/ui/silver-pose-v2-prototype.html 已建立。v2 HTML 是符合正式浅色 Windows 规范的当前视觉参考:浅灰蓝底、白色卡片,红色只表示确认摔倒、其弹窗和事件证据;文件名中的 v2 只表示原型设计修订,不能理解为 Go V2 实现已开始。v1 HTML 保留为历史深色对照。两者均使用顶部双 Tab、设置草稿与状态交互,且画面、事件和时间都是模拟数据,不连接真实摄像头、模型或网络,也不改变 Phase 1 任务顺序。
- 测试:`python -m compileall -q demo` 已通过;`python -m pytest v1/tests -v` 当前有 20 项配置/视频源/Pose/跟踪/证据/状态机测试并已通过。`demo/1.mp4` 的首两帧回放时间戳已验证为 0.000000 与 0.033333 秒,首帧 Pose smoke 得到 2 名人员、每人 17 点。`init.ps1` 会检查运行时依赖、编译旧基线并运行 V1 测试,但不会安装软件包。 - 测试:`python -m compileall -q demo` 已通过;`python -m pytest v1/tests -v` 当前有 20 项配置/视频源/Pose/跟踪/证据/状态机测试并已通过。`demo/1.mp4` 的首两帧回放时间戳已验证为 0.000000 与 0.033333 秒,首帧 Pose smoke 得到 2 名人员、每人 17 点。`init.ps1` 会检查运行时依赖、编译旧基线并运行 V1 测试,但不会安装软件包。
- 模型:`demo/best.pt` 可加载为 YOLO Pose,类别 `person`,`kpt_shape=[17, 3]`;与 `D:\PythonP\fall_detection\best.pt` 哈希一致。 - 模型:`demo/best.pt` 可加载为 YOLO Pose,类别 `person`,`kpt_shape=[17, 3]`;与 `D:\PythonP\fall_detection\best.pt` 哈希一致。
- 当前标准启动:`./init.ps1`。 - 当前标准启动:`./init.ps1`。
- 当前标准验证:`python -m compileall -q demo`。 - 当前标准验证:`python -m compileall -q demo`。
- 当前 blocker:尚未把 Pose/跟踪/证据连接到状态机;回归录像及事件标签尚未创建;真实海康 RTSP 流尚未接入。 - 当前 blocker:T-106 尚未完成 Pose/跟踪/证据到状态机的事件管线、缺失证据中断语义与运行配置版本契约;回归录像及事件标签尚未创建;真实海康 RTSP 流尚未接入。
全局环境的 `pip check` 存在其他项目的包冲突,因此它不是 Silver Pose 的验收命令。`init.ps1` 只检查本项目实际导入的 OpenCV、NumPy、Ultralytics 与 PyQt5,并在命令非零退出时失败。 全局环境的 `pip check` 存在其他项目的包冲突,因此它不是 Silver Pose 的验收命令。`init.ps1` 只检查本项目实际导入的 OpenCV、NumPy、Ultralytics 与 PyQt5,并在命令非零退出时失败。
@@ -33,8 +33,8 @@
## 任务状态 ## 任务状态
- 已完成:T-000(Harness 文档与旧基线快照)、T-101(V1 安全配置基线)、T-102(视频源与录像回放)、T-103(Pose 适配器与模型校验)、T-104(跟踪与姿态质量证据)、T-105(按 ID 时序状态机)。 - 已完成:T-000(Harness 文档与旧基线快照)、T-101(V1 安全配置基线)、T-102(视频源与录像回放)、T-103(Pose 适配器与模型校验)、T-104(跟踪与姿态质量证据)、T-105(按 ID 时序状态机)。
- 正在进行:无。 - 正在进行:T-106(装配事件管线与固化事件契约)。
- 下一个可领取:T-201。 - 下一个可领取:无;完成 T-106 后为 T-201。
## 当前可运行内容 ## 当前可运行内容
+112
View File
@@ -0,0 +1,112 @@
# T-101〜T-105 代码审核记录
- 日期:2026-07-21
- 范围:T-101(安全配置)、T-102(视频源)、T-103(Pose 适配器)、T-104(跟踪与证据)、T-105(时序状态机)
- 视角:全栈开发工程师
- 基线验证:`python -m pytest v1/tests -q` → 20 passed
- 结论:单模块工艺整体偏高,问题集中在**系统装配**——五个 task 的单元测试各自通过,但当前拼不成一条能判摔倒的完整链路。进入 T-201 之前应先补第 1–3 项。
## 总体评价
- 模块职责与 [04-architecture.md](../04-architecture.md) 的模块表严格对应;边界克制得当:Pose 不做业务结论、evidence 不报警、状态机不做副作用。
- 防御性校验到位:SHA-256 锁模型、`pose`/`person`/17×3 契约校验、config 拒绝内嵌 RTSP 凭证。
- 依赖注入(`capture_factory` / `model_factory`)使单元测试无需真实权重或摄像头,TDD 流程真实。
- 跨重连的时间戳单调性已正确处理(`VideoSource._last_timestamp` 不在重连时重置)。
## 问题清单(按优先级)
### P1 · 两个 `Evidence` 类型没有桥接,摔倒判定核心逻辑缺失
- `v1/evidence.py` 产出 `PoseEvidence`(`horizontal_pose`、`rapid_vertical_change`、`horizontal_angle_degrees`…)。
- `v1/fall_state.py` 消费另一个 `Evidence`(`is_fall_candidate`、`is_recovery_candidate`)。
- 全仓库无任何代码把前者映射为后者。即"什么几何证据算 `is_fall_candidate` / `is_recovery_candidate`"这一真正的领域决策目前为空。
- 影响:看似完成度高,实际把最有业务风险的一环留在无人负责的缝隙。
- 建议:在 T-201 之前明确它落在 `app.py` 还是 `evidence.py`,并为该映射单独写正反例测试,不要混入 GUI task。
### P2 · config 有两个字段被解析校验但从不消费(配置-现实漂移)
- `event.suspect_window_seconds` 与 `event.cooldown_seconds` 在 `v1/config.py` 完整解析并出现在 `config.example.json`,但全仓库无消费点。
- `cooldown_seconds` 尤其冲突:[04-architecture.md](../04-architecture.md) 状态模型写"经配置冷却与恢复稳定站立 → RECOVERING",而 `fall_state.py` 中 CONFIRMED→RECOVERING 一收到 recovery 证据即转,无任何 cooldown。
- 违反项目纪律"需求或代码现实变化时必须同步"。
- 建议:要么接线实现,要么从 config 与示例中删除,避免"看似可配、实则无效"的旋钮。
### P3 · `FallEvent` 缺 `config_version`
- [04-architecture.md](../04-architecture.md) 要求 FallEvent 记录 `config_version`,使截图/JSONL 可追溯实际阈值。
- 当前 `v1/fall_state.py` 的 `FallEvent` 无该字段。
- 建议:现在就在 dataclass 补上,避免 T-202 写 JSONL 时无法追溯。
### P4 · 轨迹过期(2s) 与确认窗口(最长3s)互相打架
- `PersonTracker` 默认 `max_age_seconds=2.0`,确认窗口可配到 3.0s。
- 摔倒过程恰是遮挡、姿态最不稳、Pose 最易丢的时刻。Pose 丢失 >2s 时此人拿到新 `track_id`,状态机视为新人从 NORMAL 起算,之前累积的 SUSPECT 时间丢失 → 漏报。
- 建议:`max_age` 至少对齐/大于确认窗口,或让状态在短暂 track 断裂中存活。
### P5 · 贪心最近邻跟踪,顺序依赖
- `_nearest_available_track` 按 pose 出现顺序逐个抢最近 track,非全局最优(非匈牙利匹配)。两人靠近/交叉时可能换 ID。
- 固定机位、稀疏大厅场景大概率够用,属已知局限。
- 建议:在文档写明限制,避免后期误判为 bug;多人密集场景再考虑升级匹配算法。
### P6 · 回放默认 `reconnect=True` 是使用陷阱
- `VideoSource` 默认 `reconnect=True` 面向 RTSP。对有限本地录像,EOF 时会无限重试重开(从第 0 帧重放)而非报 EOF。
- 回归测试/smoke 必须显式传 `reconnect=False`。
- 建议:对文件路径做启发式默认,或在文档强调。
### P7 · RTSP 时间戳可靠性未验证
- 状态机 1–3s 判定依赖 `CAP_PROP_POS_MSEC`,实况 RTSP 上该值常为 0 或乱跳。虽有 frame_index/fps 与 wall-clock 回退,但真实流上窗口计时准确性未验证。
- 建议:列为 T-203 关键风险,接入真实流后专项验证窗口计时。
## 建议处理顺序
1. 先补 P1 / P2 / P3(属事件引擎本身,非 GUI 活),再开 T-201。
2. P4 / P6 改动成本低,可顺手处理。
3. P5 / P7 记入已知风险,分别在多人场景与 T-203 真实流阶段处置。
## 独立复核补充与处理建议
复核结论:上述 P1、P2、P5、P6、P7 的事实判断成立;P3、P4 也成立,但实现方式需要避免引入新的事件一致性或身份错配问题。当前 20 项测试均通过,只能证明各模块的局部契约,不能证明完整数据流能对摔倒作出正确判定。
### P0 · 缺失 Pose 尚未中断“连续证据”
- `FallStateMachine.update()` 只会在某个 `track_id` 有输入时更新时间;跟踪器和状态机都没有“本帧该人员缺失”的显式合约。
- 某人已进入 `SUSPECT` 后,若短暂丢失 Pose、但仍未超过 tracker 的 2 秒过期时间,下一次以同一 ID 回来时,状态机可能把缺失的时间算入确认窗口。这与“倒地证据持续”的需求不符,可能造成误确认。
- 处理:事件管线必须为已知但本帧缺失或低质量的人员输入拒绝证据,或定义并测试独立的 `max_evidence_gap_seconds`;不得把 tracker 的身份保留时间当作证据连续时间。
### 对原建议的工程化校正
- **P1:规则应落在领域层,不落在 GUI。** 建议保留 `evidence.py` 输出的几何事实,新增明确的领域规则/管线层,将每人连续的 `PoseEvidence` 映射为状态机 `Evidence`。该层必须覆盖突然倒地、持续倒地、坐下、弯腰、捡物、短暂低姿态和短暂丢 Pose 的正反例。
- **P2:先定义字段语义,再接线。** `suspect_window_seconds` 应明确描述“突发下移到持续倒地”可接受的时间关系;`cooldown_seconds` 应明确描述确认后何时允许进入恢复或产生下一事件。若不能定义可验收语义,应从公开配置中移除,不能保留无效旋钮。
- **P3:先统一事件契约。** `04-architecture.md` 要求 `FallEvent` 记录 `config_version`,而 `api.md` 说 T-202 的报警记录补充配置版本,二者目前不一致。应将不可变的 `config_version` 纳入 `FallEvent` 或统一定义的事件记录,并同步 V1/V2 合约;不能只在 JSONL 写入时临时拼接。
- **P4:不能只把 `max_age_seconds` 调大。** 这样可能在遮挡后把另一人错误延续为原 ID。身份保留、短时遮挡和证据连续性必须分别建模;P0 的缺失证据处理是其前提。
- **P6:使用显式来源模式,而非隐式默认。** 应由“录像回放”明确产生 EOF,由“实时流”明确允许重连;仅靠调用者记住 `reconnect=False` 或按字符串猜测路径都容易造成回归测试与生产行为漂移。
- **P7:T-203 应验证并区分计时源。** 录像使用容器 PTS/帧序号时间轴;实时 RTSP 使用成功收帧时的 `time.monotonic()` 作为事件计时依据。不能只假设 `CAP_PROP_POS_MSEC` 在实况流上可靠。
### 推荐的任务调整
不建议把上述工作混入 T-201 的 GUI 实现。应在 T-105 后插入一个独立的事件集成任务,例如:
| ID | 任务 | 依赖 | 验收要点 |
| --- | --- | --- | --- |
| T-106 | 装配事件管线与固化事件契约 | T-105 | Pose → 跟踪 → 质量/几何证据 → 领域规则 → 按 ID 状态机可回放运行;P0 的缺失证据行为受测试保护;所有公开事件参数均生效;事件含实际配置版本;录像 EOF 不会重放。 |
完成 T-106 后,T-201 才接收真实管线输出的画面、骨架、ID、连接状态和 `NORMAL/SUSPECT/CONFIRMED` 状态。GUI 只负责显示与配置草稿,不能承载摔倒判定规则。P5 作为当前稀疏人员固定机位的已知限制记录;P7 则作为 T-203 接入真实海康流时的阻断验收项。
## 第二轮复核补充(全栈视角)
对上一节独立复核的结论予以确认:P0 事实成立且优先级判断正确,P3/P4/P6 是对首轮建议的有效纠偏。以下两点为在此基础上的工程细化,建议一并纳入 T-106。
### P0 的修法首选状态机内建守卫,而非依赖调用者注入拒绝证据
- 上一节给出两个方向:① 管线为“本帧缺失/低质量”的已知人员喂拒绝证据;② 定义并测试 `max_evidence_gap_seconds`。
- 方向 ① 要求编排层每帧枚举**所有存活 track**(不仅是本帧检测到的),漏掉任何一个就退化为误确认;正确性依赖调用者每次都记得注入,防呆性差。
- 方向 ② 是调用者无关的内建守卫:状态机记录 `last_fall_candidate_at`,当 `now - last_fall_candidate_at` 超过 `max_evidence_gap_seconds` 时,即使本次是 fall candidate 也必须重置 SUSPECT 计时、不得把间隔折算进确认窗口。
- 建议:以 ② 为主、① 为辅,并在 T-106 验收中固化“SUSPECT 中间出现超过 gap 阈值的证据空档 → 不确认”的正反例测试。
### 乱序时间戳应降级处理,不应直接抛异常
- 当前 `FallStateMachine.update()` 对 `now < last_updated_at` 直接 `raise ValueError("timestamps must be monotonic per track")`。
- 一旦 P7 落实后计时源切换,或某 track 以重置时钟复现,这个硬 `raise` 会让整条实时管线崩溃,而不是容错降级——对一个需要长时间无人值守运行的告警 demo 是不可接受的失败模式。
- 建议:把乱序帧改为**丢弃该帧并记录状态**(不推进时间、不产生事件、可选计一次告警计数),而非中断。该改动与 P7 的显式计时源在 T-106 一并处理。
+9
View File
@@ -161,3 +161,12 @@
- 阻塞:无任务内 blocker。当前尚未创建把 Pose/跟踪/证据喂给状态机的应用编排层,因此不能把这些单元测试误称为真实视频摔倒检测验收。 - 阻塞:无任务内 blocker。当前尚未创建把 Pose/跟踪/证据喂给状态机的应用编排层,因此不能把这些单元测试误称为真实视频摔倒检测验收。
- 决策:确认窗口在构造时强制限制为 1–3 秒;同一人员的时钟与事件序号隔离。声音、弹窗、截图与 JSONL 仍由 T-201/T-202 实现,状态机不执行这些副作用。 - 决策:确认窗口在构造时强制限制为 1–3 秒;同一人员的时钟与事件序号隔离。声音、弹窗、截图与 JSONL 仍由 T-201/T-202 实现,状态机不执行这些副作用。
- 下一步:T-201,实现顶部双 Tab 的 PyQt 监控/设置界面,并保持其不直接执行事件判断。 - 下一步:T-201,实现顶部双 Tab 的 PyQt 监控/设置界面,并保持其不直接执行事件判断。
## 【2026-07-21】T-106 装配事件管线与固化事件契约
- 状态:DOING
- 变更:根据 `docs/review/2026-07-21-t101-t105-review.md` 在 T-105 与 T-201 之间新增 T-106;T-201 依赖改为 T-106。任务将补齐 PoseEvidence 到状态机 Evidence 的领域映射、缺失/低质量证据中断、配置字段实际生效、配置版本可追溯,以及录像/实时流的显式来源行为。
- 验证:开始前 `./init.ps1` 退出码 0,V1 单元测试 20 passed。
- 阻塞:无。
- 决策:不把事件判断规则混入 T-201 的 PyQt UI;T-106 先提供真实、可测试的管线输出。`demo/` 保持未跟踪旧基线,不纳入本任务提交。
- 下一步:先为领域规则、缺失证据、配置版本和来源模式写失败测试,再作最小实现。