Files
silver_pose/docs/review/2026-07-21-t101-t105-review.md
T

113 lines
9.8 KiB
Markdown
Raw 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.
# 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 一并处理。