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