Files
silver_pose/progress.md
T

490 lines
58 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.
# 执行进度记录
> 本文件只追加记录任务执行、验证、阻塞和关键决策。当前目录、当前命令和下一任务以 [docs/current-state.md](docs/current-state.md) 为准。
## 记录格式
```markdown
## 【YYYY-MM-DD】T-【编号】 【任务名】
- 状态:【DONE / BLOCKED / PARTIAL】
- 变更:【文件或模块】
- 验证:【真实命令和结果】
- 阻塞:【原因与决策人】
- 决策:【本轮确定的取舍】
- 下一步:【任务 ID 或待确认事项】
```
## 执行记录
## 【2026-07-20】T-000 建立 Harness Coding 文档基线
- 状态:DONE
- 变更:创建仓库级 agent 入口、V1/V2 需求、技术栈、架构、任务看板、模块合约、当前状态和实施计划文档。
- 验证:`./init.ps1` 已通过:Python 3.8.10 可导入 OpenCV、NumPy、Ultralytics 8.3.205 与 PyQt5,且 `python -m compileall -q demo` 退出码 0;`demo/best.pt` 被识别为 `pose`、`kpt_shape=[17, 3]`、类别 `person`;20 个 Markdown 文档的本地链接均可解析,实施计划没有占位标记。全局 `pip check` 的其他项目冲突不作为本项目验收;`init.ps1` 检查实际依赖并对非零退出码失败。
- 阻塞:无代码 blocker;V1 依赖清单、RTSP 实流录像和现场验收录像尚未创建。
- 决策:`demo/` 作为旧基线保留;V1 使用 Python + PyQt5 + OpenCV + Ultralytics;V2 使用 Go,且必须以 V1 的录像回归集和 ONNX 一致性测试为迁移门槛。
- 下一步:T-101。
## 【2026-07-20】DESIGN UI/UX 客户演示原型规格
- 状态:PARTIAL
- 变更:创建 `docs/ui/silver-pose-ui-ux-spec.md`,定义离线 HTML 原型的正常监控、确认摔倒、连接异常状态、布局、视觉令牌、键盘操作和无障碍验收条件。
- 验证:`./init.ps1` 通过;规格共 98 行,未发现 `TBD`、`TODO` 或 `REPLACE` 占位符,`git diff --check` 通过。
- 阻塞:等待用户审阅已提交的规格;未生成 HTML,未变更 `demo/`、`v1/` 或任务看板状态。
- 决策:原型为单页、无网络依赖的视觉与交互演示,使用虚构摄像头和事件数据,不表现为真实 RTSP 或模型推理。
- 下一步:用户确认规格后,创建 `docs/ui/silver-pose-v1-prototype.html` 并执行离线交互验收。
## 【2026-07-20】DESIGN 离线 HTML UI/UX 原型
- 状态:DONE
- 可视化验证:使用本机 Chrome 无界面模式实际渲染 1280 × 800、900 × 900、600 × 1000 三种窗口截图;宽屏与窄屏截图已人工检查,主操作可见,窄屏下右栏纵向重排,未见横向溢出。Chrome 对 file 页面返回非零退出码,但三张 PNG 均实际生成,故以文件与画面结果作为结论。
- 变更:创建 docs/ui/silver-pose-v1-prototype.html 和实施计划 docs/ui/2026-07-20-html-prototype-plan.md。原型可在单页中通过按钮或数字键 1、2、3 切换正常监控、确认摔倒和连接异常;包含 16:9 模拟姿态画面、红色确认状态、一次性演示弹窗、报警/截图证据、最近事件、窄窗口重排、键盘焦点和屏幕阅读器状态提示。
- 验证:Node.js 22 解析唯一内联脚本,并确认三种状态按钮、弹窗、确认按钮、aria-live 与 1007/640/600 epx 响应式断点存在,输出 prototype static validation passed;敏感与网络边界扫描未发现凭证、模型路径或网络行为标记;./init.ps1 通过。
- 阻塞:无。该文件是离线视觉原型,已做静态和无界面浏览器布局验证,但不表示实时 RTSP、模型推理、声音播放或截图保存已实现。
- 决策:原型采用单窗口深色控制台布局,以同一画面状态切换展示客户关心的“正常不报警、确认后完整报警闭环、断流不误报”三种结果。
- 下一步:T-101;HTML 原型可作为后续 T-201 PyQt 界面实现的视觉与交互参考。
## 【2026-07-20】DESIGN 顶部 Tab 与设置原型修订
- 状态:DONE
- 变更:更新需求、页面导航、架构、T-201 验收、UI 规格和实施计划;将 HTML 原型改为顶部双 Tab。实时监控 Tab 保留姿态与报警演示;设置 Tab 显示来源环境变量就绪、模型只读信息、六项非敏感事件或重连参数、草稿版本与保存/重置操作。
- 验证:基线 ./init.ps1 通过;Node.js 22 解析唯一内联脚本,并检查 tablist、tab、tabpanel、设置表单、保存操作、运行配置版本、配置草稿控制器和三档响应式断点;敏感与网络边界扫描未发现凭证、模型路径或网络行为标记。Chrome 无界面模式实际渲染 1280 宽监控页、1280/900/600 宽设置页;宽屏与 600 宽截图已人工检查,未见横向溢出。
- 阻塞:无。设置页只模拟草稿编辑和保存反馈,不会写入本地配置,也不会热改监控中的运行配置。
- 决策:V1 固定采用顶部双 Tab,而非左侧导航;两个静态目的地不会挤压实时视频。设置草稿在下一次开始监控时才成为新的运行配置,事件工件继续追溯实际运行版本。
- 下一步:T-101;后续 T-201 按此交互和配置快照边界实现 PyQt 界面。
## 【2026-07-20】DESIGN 浅色 Windows 主题规范
- 状态:DONE
- 变更:更新 UI 规格、页面规则、编码规则和原型实施计划。正式默认主题设为浅灰蓝页面背景与白色卡片;红色只表示确认摔倒事件、对应弹窗和事件证据。正常、疑似、连接异常、保存与校验改用绿色、琥珀色、灰蓝或标准主色,并保留文字和图标提示。
- 验证:./init.ps1 通过;已核对 UI 规格、页面规则和编码规则中的主题语义一致。
- 阻塞:按本轮用户范围未修改 HTML。现有 HTML 为深色历史原型,不能作为正式色彩实现来源,需在后续单独重绘。
- 决策:后续 PyQt T-201 与任何新的 HTML 原型必须以浅色 Windows 主题规范为准,不得将红色用于非确认摔倒场景。
- 下一步:等待用户授权,将 docs/ui/silver-pose-v1-prototype.html 重绘为浅色主题。
## 【2026-07-20】DESIGN 浅色 Windows HTML 原型修订
- 状态:DONE
- 变更:新增 `docs/ui/silver-pose-v2-prototype.html` 作为独立离线原型。它使用浅灰蓝页面背景、白色卡片和集中 CSS 令牌;红色只用于确认摔倒、告警弹窗和事件证据。保留顶部双 Tab、正常/确认/断流三种状态、一次性告警弹窗、设置草稿及下一次启动生效边界;未改动历史深色 v1 原型。
- 验证:Node.js 22 已解析唯一内联脚本,并检查状态控件、Tab、弹窗、三个响应式断点及浅色主题令牌;隐私与网络边界扫描未发现凭证、模型路径或网络行为;`./init.ps1` 通过。Chrome 无界面模式实际渲染了正常、确认摔倒、连接异常、900 宽设置和 600 宽设置画面;确认摔倒显示红色弹窗,连接异常不显示红色告警,窄宽度为纵向重排。
- 阻塞:无。原型不连接真实摄像头、模型、声音或文件系统;截图路径和事件均为演示数据。
- 决策:文件名中的 v2 仅表示 HTML 视觉修订,不启动 Go V2,也不改变 V1 验收和 ONNX 一致性门槛。
- 下一步:T-101;后续 T-201 以 v2 HTML 和 UI/UX 规格实现 PyQt 浅色 Windows 界面。
## 【2026-07-21】T-101 创建 V1 包、依赖清单、示例配置和忽略规则
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建 V1 生产代码。
- 验证:`./init.ps1` 已通过。`python -m pytest v1/tests -v` 当前以 `v1/tests` 不存在退出,这是 T-101 建立 pytest 基线前的预期初始失败。
- 阻塞:无。
- 决策:`init.ps1` 保持无副作用的依赖检查与基线验证,不自动安装包;`requirements.txt` 将作为显式、可复现的安装清单。
- 下一步:先写配置加载与敏感值拒绝的失败测试,再实现最小配置接口。
## 【2026-07-21】T-101 创建 V1 包、依赖清单、示例配置和忽略规则(完成)
- 状态:DONE
- 变更:新增 `v1` 包、经验证的 `config.py`、仅含环境变量名的公开配置示例、与当前环境一致的 `requirements.txt` 和两项 pytest 配置测试;新增 `.gitignore`,忽略本地配置、事件工件、私有录像和模型导出物。`init.ps1` 现在运行 V1 测试但不自动安装依赖。
- 验证:先运行 `python -m pytest v1/tests/test_config.py -v`,确认因缺少 `v1.config` 产生预期导入失败;实现后 `./init.ps1` 和 `python -m pytest v1/tests -v` 均通过(2 passed),`python -m compileall -q v1 demo` 通过。公开配置的地址/凭证扫描无匹配,`git check-ignore` 已确认本地配置、事件工件、`.pt` 与 `.onnx` 工件被忽略。
- 阻塞:无。
- 决策:配置只允许 `rtsp_url_env` 指向运行环境变量,拒绝内嵌来源地址;确认窗口限制为 1–3 秒。公开示例使用 64 位全零哈希占位,T-103 必须替换为受控模型真实哈希后才可加载模型。
- 下一步:T-102,先建立录像回放和错误来源的失败测试。
## 【2026-07-21】T-102 实现可重连的视频源与录像回放适配器
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建视频源生产代码。
- 验证:继承 T-101 的 `./init.ps1` 基线通过记录;本任务将先以可控假捕获器建立单元测试,再以本地演示录像作 smoke 验收。
- 阻塞:无。
- 决策:视频源只返回帧、单调时间戳和来源状态;读取失败、EOF 和重连绝不生成虚构人员或摔倒事件。
- 下一步:写入时间戳单调、错误来源和有界重连的失败测试。
## 【2026-07-21】T-102 实现可重连的视频源与录像回放适配器(完成)
- 状态:DONE
- 变更:新增 `v1/video_source.py`,定义 `FramePacket` 与 `connected`、`retrying`、`error`、`eof`、`closed` 来源状态。录像优先读取容器时间戳,负首帧或倒退值回退到帧序号/FPS;可重连来源采用有界指数退避,所有非连接状态返回空帧且不携带事件信息。
- 验证:先运行 `python -m pytest v1/tests/test_video_source.py -v`,确认因缺少 `v1.video_source` 导入失败;实现后视频源测试 4 passed,完整 V1 测试在本轮最后一次运行时为 6 passed。`demo/1.mp4` 只读 smoke 成功读取首两帧,时间戳为 0.000000 与 0.033333 秒;`python -m compileall -q v1 demo` 通过。
- 阻塞:无。
- 决策:来源失败只表达来源状态,绝不以空帧、EOF 或重连触发人员更新或摔倒事件;本地录像测试使用临时生成的 AVI,避免将客户视频或样例录像作为 pytest 夹具。
- 下一步:T-103,先写模型来源和 17 点 Pose 输出的失败测试。
## 【2026-07-21】T-103 实现 Pose 适配器与模型来源校验
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建 Pose 生产代码。
- 验证:继承 T-102 的 `./init.ps1` 与 6 项 V1 测试通过记录;本任务将先用不依赖真实权重的假 YOLO 结果建立 17 点解析测试,再执行只读模型 smoke。
- 阻塞:无。
- 决策:构造适配器时必须校验模型 SHA-256、任务为 pose、类别为 person、关键点形状为 17×3;不把任何输出命名为摔倒概率。
- 下一步:写入模型来源错误与 person Pose 输出形状的失败测试。
## 【2026-07-21】T-103 实现 Pose 适配器与模型来源校验(完成)
- 状态:DONE
- 变更:新增 `v1/pose.py`,以 SHA-256 锁定模型来源,要求任务为 pose、存在 person 类且关键点形状严格为 17×3;输出不含跟踪 ID 的 `PersonPose`(box、box 置信度、17 个三元关键点)。
- 验证:先运行 `python -m pytest v1/tests/test_pose.py -v`,确认因缺少 `v1.pose` 导入失败;实现后 Pose 测试 3 passed,完整 V1 测试在本轮最后一次运行时为 9 passed。对 `demo/best.pt` 的只读 smoke 使用实际 SHA-256 加载模型,并从 `demo/1.mp4` 首帧得到 2 名人员、每人 17 点;`python -m compileall -q v1 demo` 通过。
- 阻塞:无。
- 决策:模型输出绝不称为摔倒概率;人员 ID 由 T-104 在 Pose 输出之后分配,模型不匹配或结果形状损坏时必须显式报错而不是继续推理。
- 下一步:T-104,先写缺失下肢关键点拒绝和水平姿态只作为证据的失败测试。
## 【2026-07-21】T-104 实现人员跟踪与姿态质量门控
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建跟踪或证据生产代码。
- 验证:继承 T-103 的 `./init.ps1`、9 项 V1 测试和真实模型 smoke 通过记录;本任务将先建立相邻帧 ID 稳定性、缺失下肢点拒绝和水平几何仅作为证据的失败测试。
- 阻塞:无。
- 决策:质量与证据模块只能返回质量/几何事实,不能返回报警或直接创建事件;空帧和低质量姿态不可推进倒地候选。
- 下一步:写入 T-104 的失败测试。
## 【2026-07-21】T-104 实现人员跟踪与姿态质量门控(完成)
- 状态:DONE
- 变更:新增 `v1/tracking.py`,以归一化框中心距离为连续人员分配稳定 ID;新增 `v1/evidence.py`,要求肩、髋、膝、踝关键点全部超过阈值,再输出水平躯干角度和相对躯干长度归一化的下移证据。
- 验证:先运行 `python -m pytest v1/tests/test_evidence.py -v`,确认因缺少 `v1.evidence` 导入失败;实现后证据/跟踪测试 5 passed,完整 V1 测试在本轮最后一次运行时为 14 passed,`python -m compileall -q v1 demo` 通过。
- 阻塞:无。
- 决策:`PoseEvidence` 只表达质量和几何事实,不能报警或创建事件;空姿态、缺失下肢点或退化躯干都会停止证据积累。T-105 必须以 `track_id`、单调时间和这些证据来确认事件。
- 下一步:T-105,先写持续倒地只产生一次事件、短时弯腰不报警与恢复后的新事件边界测试。
## 【2026-07-21】T-105 实现按 ID 的时序摔倒状态机
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建状态机生产代码。
- 验证:继承 T-104 的 `./init.ps1` 与 14 项 V1 测试通过记录;本任务将先写持续倒地确认一次、短时弯腰无事件、恢复后才允许新事件和拒绝证据不推进时间的失败测试。
- 阻塞:无。
- 决策:状态机只接收按 ID 的可用证据与单调秒数;视频帧率、空帧、来源断流和拒绝姿态都不能缩短确认窗口或创建事件。
- 下一步:写入 T-105 失败测试。
## 【2026-07-21】T-105 实现按 ID 的时序摔倒状态机(完成)
- 状态:DONE
- 变更:新增 `v1/fall_state.py`,为每个 `track_id` 独立维护 NORMAL、SUSPECT、CONFIRMED、RECOVERING。只有连续、可用的倒地候选达到 1–3 秒确认窗口才产生一个 `FallEvent`;确认后持续帧不会重复返回事件,拒绝证据会打断疑似持续时间,恢复证据持续后才回到 NORMAL。
- 验证:先运行 `python -m pytest v1/tests/test_fall_state.py -v`,确认因缺少 `v1.fall_state` 导入失败;实现后状态机测试 6 passed,完整 V1 测试在本轮最后一次运行时为 20 passed,`python -m compileall -q v1 demo` 通过。
- 阻塞:无任务内 blocker。当前尚未创建把 Pose/跟踪/证据喂给状态机的应用编排层,因此不能把这些单元测试误称为真实视频摔倒检测验收。
- 决策:确认窗口在构造时强制限制为 1–3 秒;同一人员的时钟与事件序号隔离。声音、弹窗、截图与 JSONL 仍由 T-201/T-202 实现,状态机不执行这些副作用。
- 下一步: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/` 保持未跟踪旧基线,不纳入本任务提交。
- 下一步:先为领域规则、缺失证据、配置版本和来源模式写失败测试,再作最小实现。
## 【2026-07-21】T-106 装配事件管线与固化事件契约(完成)
- 状态:DONE
- 变更:新增 `v1/fall_policy.py`,将快速下移后在 suspect 窗口内形成的水平姿态映射为候选,并只在确认/恢复状态处理恢复证据;新增 `v1/pipeline.py`,将 Pose、跟踪、质量、几何证据、领域规则和按 ID 状态机连接。缺失人员、低质量姿态和非连接帧均向状态机输入拒绝证据,不能跨空档确认。`FallEvent` 现在包含运行配置的非敏感 `config_version`;`cooldown_seconds` 在确认后延迟恢复判断。视频源新增显式 `SourceMode.REPLAY`/`STREAM`:录像 EOF 不重放,实时流以收帧单调时钟计时并重连。
- 验证:以失败测试先后覆盖领域规则、配置版本、cooldown、Replay EOF、Stream 计时、完整管线确认及缺帧中断;最终 `python -m pytest v1/tests -v` 为 30 passed,`python -m compileall -q v1 demo` 退出码 0。只读真实 smoke 使用 `demo/best.pt` 与 `demo/1.mp4` 驱动 T-106 管线两帧,结果为首帧 `connected/2 people/0 events`、第二帧 `connected/0 people/0 events`。
- 阻塞:无任务内 blocker。该 smoke 未使用带标签摔倒/反例录像,不能作为事件准确率、1–3 秒延迟或海康 RTSP 现场验收证据。
- 决策:`suspect_window_seconds` 定义为“快速下移到水平姿态”的最大间隔,`cooldown_seconds` 定义为确认后开始恢复判断的最短等待时间;运行配置版本排除 RTSP 地址和凭证。不会仅为延长确认而增大 tracker 的身份寿命,证据连续性由管线的拒绝证据保证。当前轻量跟踪多人交叉换 ID 记录为已知限制。
- 下一步:T-201,实现顶部双 Tab 的 PyQt 监控与设置界面,只消费 T-106 的管线输出,不执行事件判定。
## 【2026-07-21】T-201 实现 PyQt 顶部双 Tab 的监控与设置界面
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未创建 GUI 生产代码。
- 验证:开始前 V1 单元测试 30 passed。本环境为 WSL/Linux 且未安装 PyQt5(`import PyQt5` 失败),GUI 渲染层的可视化验收必须在 Windows 上执行。
- 阻塞:无任务内 blocker;PyQt5 渲染冒烟受环境限制,见决策。
- 决策:把可验收的 UI 逻辑(设置草稿与运行配置快照隔离、状态→语义色映射、骨架与人员叠加数据推导、连接状态文案)抽到不依赖 Qt 的 `v1/view_model.py`,在本环境用 pytest 真实覆盖;`v1/gui.py` 与 `v1/app.py` 仅作薄 Qt 外壳与装配,只消费 T-106 管线输出,不执行事件判定。GUI 的可视化冒烟标注为需 Windows + PyQt5 执行。
- 下一步:先写 view-model 的失败测试(草稿隔离、状态色、骨架推导、连接文案),再实现最小 UI 外壳。
## 【2026-07-21】T-201 实现 PyQt 顶部双 Tab 的监控与设置界面(完成)
- 状态:DONE
- 变更:新增 `v1/view_model.py`(Qt-free 可测核心):由 `FrameAnalysis` 构建监控视图状态(连接文案、状态→语义色、COCO-17 骨架段、人员 box/ID/状态标签,红色只用于 CONFIRMED,非连接帧不产生人员或摔倒标签),以及 `SettingsDraft`(运行快照/已保存/草稿三份隔离,编辑只改草稿、保存只标下次启动生效、`start_monitoring` 才提升为新运行配置并变更配置版本)。新增 `v1/gui.py` 薄 PyQt5 外壳(顶部双 Tab、`QTabWidget`、`VideoView` 用 `QPainter` 叠加、设置 `QDoubleSpinBox` 绑定草稿、浅色 Windows 主题 QSS)与 `v1/app.py` 装配(`FrameWorker(QThread)` 持有视频源与 `FallPipeline` 并只发出已判定的 `FrameAnalysis`,窗口只渲染;开始监控时按草稿生成不可变运行配置快照)。
- 验证:新增 `v1/tests/test_view_model.py` 9 项,覆盖状态色(红仅 CONFIRMED)、断流不产生摔倒标签、低置信关键点被剔除、事件徽标、草稿隔离/保存/下次启动提升、越界拒绝与配置版本敏感性;`python3 -m pytest v1/tests -q` 为 39 passed;`python3 -m compileall -q v1 demo` 退出码 0(含 `gui.py`、`app.py` 语法)。真实链路冒烟:以 `demo/1.mp4` 真实解码 + 真实 `FallPipeline/PersonTracker/FallEvidencePolicy/FallStateMachine` + 确定性假 Pose 适配器驱动 `build_monitor_view`,连续帧得到稳定 `P-0001`、box、14 段骨架、17/17 关键点、`NORMAL/success`;注入 RETRYING 帧得到“正在重连…”、0 人、offline 色。
- 阻塞:无任务内 blocker。当前 WSL/Linux 环境未安装 PyQt5,且本机 ultralytics/torch 版本与 Windows 基线(8.3.205)不兼容导致模型前向不可用;因此 `gui.py`/`app.py` 的可视化冒烟与真实模型端到端渲染必须在 Windows + PyQt5 上执行,不能以本轮 CI 结果冒充可视化验收。
- 决策:把全部可验收 UI 逻辑抽到 Qt-free 的 `view_model.py` 并在本环境真实测试,Qt 渲染层保持最薄且不含事件判定;设置草稿采用“运行快照/已保存/草稿”三份隔离,运行配置版本只在开始监控时变更,使事件与截图可追溯 `config_version`。
- 下一步:T-202,实现 CONFIRMED 的一次性声音、弹窗、带标注截图与 JSONL 事件日志。
## 【2026-07-21】T-202 实现本地报警、弹窗、截图和 JSONL 日志
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未创建报警工件生产代码。
- 验证:开始前 V1 单元测试 39 passed。
- 阻塞:无任务内 blocker;声音播放与 Qt 弹窗需 Windows 桌面,见决策。
- 决策:报警派发按 `event_id` 去重,只对首次 CONFIRMED 触发一次副作用;截图标注与 JSONL 写入在本环境用 pytest 真实覆盖;声音与弹窗抽象为可注入 sink,Windows 侧提供真实实现。JSONL 与截图文件名不含 RTSP 地址、凭证或客户姓名,只用事件 ID 与日期目录。
- 下一步:先写去重、JSONL 字段与截图落盘的失败测试,再实现最小报警工件模块。
## 【2026-07-21】T-202 实现本地报警、弹窗、截图和 JSONL 日志(完成)
- 状态:DONE
- 变更:新增 `v1/alerts.py`:`annotate_frame` 用 OpenCV 在帧上叠加 box/骨架/关键点/标签,CONFIRMED 时加红色外框(红色专属确认摔倒);`EventArtifactWriter` 按 `artifacts/events/YYYYMMDD/FALL-xxxxxx.png` 保存带标注截图并向 `events.jsonl` 追加一行含 `event_id/track_id/config_version/source_id/state/confirmed_at_utc/单调时间/latency/相对截图路径` 的记录;`AlertDispatcher` 按 `event_id` 去重,对首次 CONFIRMED 只触发一次副作用。声音与弹窗抽象为 `AlertSink`;`v1/gui.py` 新增 `QtAlertSink`(`QApplication.beep` + 一次性非阻塞“我已知晓”弹窗),`v1/app.py` 在开始监控时构建 writer/dispatcher 并在每帧派发 `analysis.events`,窗口仍只渲染。
- 验证:新增 `v1/tests/test_alerts.py` 5 项,覆盖同一事件只写一次截图/JSONL 且 sink 各调用一次、JSONL 字段正确且无 rtsp/password、缺图不产生副作用、不同事件写两行、annotate 仅对 CONFIRMED 画红框;`python3 -m pytest v1/tests -q` 为 44 passed;`python3 -m compileall -q v1 demo` 退出码 0。真实帧冒烟:用 `demo/1.mp4` 首帧(848×480)经 `AlertDispatcher` 落盘,截图可被 `cv2.imread` 读回为 480×848×3、约 330 KB,重复派发返回 0,JSONL 一行且 `screenshot=20260721/FALL-000001.png`、含 `config_version`、`source_id`,行内无 `rtsp`。
- 阻塞:无任务内 blocker。声音播放与 Qt 弹窗需 Windows + PyQt5 桌面执行冒烟;本环境已真实验证截图标注、JSONL 落盘与去重逻辑。
- 决策:事件副作用严格按 `event_id` 幂等;报警记录以 `FallEvent` 的 `config_version` 追溯运行配置,不在写入时重新推导;工件文件名只用事件 ID 与日期目录,绝不含来源地址、凭证或客户姓名。
- 下一步:T-203,接入海康 RTSP 与断流恢复;因需现场真实流,标为 BLOCKED 等现场资源。
## 【2026-07-21】T-203/T-204/T-205 现场依赖任务受阻
- 状态:BLOCKED
- 变更:仅更新任务看板状态与本记录;未改动代码。按用户确认,Phase 2 剩余三项因现场物理资源不可得而受阻,不伪造 DONE。
- T-203 接入海康 RTSP 与断流恢复:断流状态、有界指数退避重连、STREAM 模式收帧单调计时与“断流不报警”已在 `v1/video_source.py` 与 `v1/pipeline.py` 实现并有单元测试;唯一未满足的验收是“现场有效海康流可预览”,需现场摄像头/网络与真实 RTSP 凭证(仅经环境变量提供)。
- T-204 建立正反例录像回归与现场验收记录:需经同意成年人的安全模拟摔倒录像与行走、坐下、弯腰、捡物、持续倒地等反例录像,以及现场安全措施;据此建立 `testdata/expected_events.json` 期望事件与 1–3 秒延迟记录。当前环境无这些录像素材,无法产出真实事件级验收。
- T-205 固化客户演示脚本与 V1 发布包:依赖 T-204 的现场验收证据,前置未满足,无法固化。
- 阻塞:现场海康摄像头、网络与真实 RTSP 凭证;经同意成年人的摔倒/反例录像与现场安全措施。决策人:用户/现场负责人。
- 决策:不以合成数据冒充现场 RTSP 预览或摔倒/反例录像验收(AGENTS.md:任务完成的证据是可运行命令与可观察结果,安全模拟摔倒必须使用经同意成年人与现场安全措施)。代码侧的重连、状态与幂等逻辑已就绪,解除阻塞后可直接进入现场验证。
- 下一步:待用户提供现场流/录像后领取 T-203;期间可按需在 Windows 执行 T-201/T-202 的 GUI、声音与弹窗可视化冒烟。
## 【2026-07-21】FIX T-201/T-202 复核问题 A/B/C
- 状态:DONE
- 变更:修复三处复核问题。A:`PoseAdapter` 新增 `set_confidence_threshold`/`confidence_threshold`,`app.py` 在开始监控时按运行配置调用,使设置里的模型检测置信度真正生效(此前 adapter 置信度构造后固定、草稿字段为死旋钮)。B:`config.py` 新增显式 `source.mode`(`stream` 默认/`replay`,校验取值),`AppConfig.source_mode` 由配置决定,`app.py` 不再按 `source_url.startswith("rtsp")` 猜测来源模式;`config.example.json` 与 `docs/api.md` 同步。C:`FallStateMachine` 新增 `session_id`,`event_id` 变为 `FALL-<session>-NNNNNN`;`FallPipeline.from_config` 每次运行经 `new_session_id()` 生成进程内唯一会话,避免同一天内重启监控时截图被覆盖、JSONL 出现同 id 不同内容。
- 验证:新增/更新测试——config 三项(默认 stream、解析 replay、非法 mode 被拒)、pose 两项(置信度转发到推理、越界被拒)、fall_state 一项(不同会话 event_id 唯一)、pipeline 一项(两次 from_config 的确认事件 id 不同);`python3 -m pytest v1/tests -q` 为 51 passed;`python3 -m compileall -q v1 demo` 退出码 0。
- 阻塞:无。声音/弹窗/GUI 可视化冒烟仍需 Windows。
- 决策:来源模式由配置显式声明而非猜测;模型置信度经适配器方法在下次启动生效,保持“运行配置快照”语义;事件身份以会话前缀保证跨轮唯一,dedup 基于该持久标识。
- 下一步:T-203(受阻,等现场流)。
## 【2026-07-21】T-203 接入海康 RTSP 与断流恢复
- 状态:DOING
- 变更:用户提供真实海康 IPC 流(凭证与内网地址仅经 `SILVER_POSE_RTSP_URL` 环境变量提供,未写入任何被跟踪文件、日志或文档)。更新未跟踪的 `v1/config.local.json`(已被 `git check-ignore` 确认忽略):`source.mode=stream`、模型指向 `../demo/best.pt` 及其真实 SHA-256 `cd200948…bfafdfc`、只声明 `rtsp_url_env` 不含地址。
- 验证:在 WSL 以 TCP 传输 + 5 秒超时对真实流冒烟:`VideoSource(STREAM)` 首读即 `connected` 并解出 1920×1080 帧;持续取 5 帧、时间戳单调、跨度约 2.6 秒;对不可达地址进入 `retrying`、不崩溃、返回空帧且不报警。`load_config('v1/config.local.json')` 端到端通过:`source_mode=stream`、URL 从环境变量解析(不打印)、模型哈希匹配、`config_version` 生成。`python3 -m pytest v1/tests -q` 仍为 51 passed。
- 阻塞:模型前向(Ultralytics 8.3.205)与 PyQt5 预览在当前 WSL 不可运行,因此“带姿态骨架的操作员现场预览”与“真实断流→重连的现场观察”必须在 Windows 上完成;这两项完成前 T-203 不置 DONE。
- 决策:真实凭证只经环境变量注入,绝不入库;来源模式由 `config.local.json` 显式声明为 `stream`;本次相机密码含 `@` 字符,ffmpeg 以最后一个 `@` 分隔用户信息可正确解析,若换用其他客户端需将密码中的 `@` 百分号编码为 `%40`。
- 下一步:在 Windows 设置同名环境变量后运行 `python -m v1.app`,人工确认实时画面、骨架、ID、连接状态与断流重连;随后据现场结果置 T-203 状态。
## 【2026-07-21】T-206 结构化摄像头配置、连接测试与设置页预览
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未创建生产代码。按用户选择「全存本地配置文件」,凭证明文存于未跟踪 `config.local.json`。
- 验证:开始前 V1 单元测试 51 passed。
- 阻塞:设置页掩码输入、测试连接按钮与预览显示需 Windows + PyQt5 冒烟;配置解析、RTSP URL 构建(凭证百分号编码)与连接测试逻辑在本环境可真实验证。
- 决策:配置支持「结构化 host/port/channel/username/password」或原有 `rtsp_url_env` 二选一;密码按 RFC 3986 百分号编码后拼 URL(顺带解决 `@` 问题)。护栏:公开示例 `config.example.json` 继续禁止任何 host/账号/密码/URL 并加测试守卫;`runtime_config_version` 继续排除凭证;凭证绝不进 JSONL、日志、截图名、错误信息。
- 下一步:先写 URL 构建与结构化配置解析的失败测试,再实现;随后加 Qt 外壳(设置页字段、测试按钮、预览)。
## 【2026-07-21】T-206 结构化摄像头配置、连接测试与设置页预览(完成)
- 状态:DONE
- 变更:`config.py` 新增 `build_rtsp_url`(凭证 RFC 3986 百分号编码)与结构化来源解析(`host/port/channel/username/password` 或原 `rtsp_url_env` 二选一),并新增 `write_local_camera_source` 把结构化来源写回未跟踪的 `config.local.json`。新增 `v1/camera.py`:`probe_stream` 用 `VideoSource(STREAM)` 做一次有界连接测试并回传抓拍帧(失败不抛异常、消息不含凭证)。`v1/gui.py` 设置页新增「摄像头连接」分组:host/端口/通道(101/102 下拉)/账号/密码(掩码)、`ConnectionTestWorker(QThread)` 后台测试连接、成功后在独立预览区显示抓拍帧、`保存连接到本地配置`按钮写回本地文件;`app.py` 从本地配置预填这些字段并传入配置路径。
- 验证:新增测试——camera 四项(密码 `@`/`:`/`/` 百分号编码、默认主码流、probe 成功回帧、probe 失败不抛)、config 五项(结构化来源拼 URL、缺密码被拒、凭证不进 config_version、公开示例无凭证守卫、`write_local_camera_source` 经 `load_config` 往返);`python3 -m pytest v1/tests -q` 为 60 passed;`python3 -m compileall -q v1 demo` 退出码 0(含 gui/app 新 Qt 代码语法)。真实相机连接此前已在 T-203 冒烟验证(1920×1080 取帧)。
- 阻塞:设置页掩码输入、测试按钮点击与预览显示需 Windows + PyQt5 冒烟;本环境已真实验证 URL 构建、结构化解析、凭证护栏与连接测试逻辑。
- 决策:按用户选择「全存本地配置文件」,凭证明文写入未跟踪 `config.local.json`(`git check-ignore` 保护);护栏落测试:公开示例禁含凭证、`config_version` 排除凭证、消息/日志不含密码;密码百分号编码顺带修掉 `@` 解析问题。
- 下一步:在 Windows 用设置页填写并测试连接、确认预览,再保存到本地配置;随后继续 T-203 的现场 GUI 预览与断流重连人工验收。
## 【2026-07-21】T-207 抓流低延迟参数进配置与设置页
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未创建生产代码。
- 验证:开始前 V1 单元测试 60 passed。
- 阻塞:设置页下拉/数字/勾选控件需 Windows + PyQt5 冒烟;配置解析与 `OPENCV_FFMPEG_CAPTURE_OPTIONS` 拼串在本环境可真实验证。
- 决策:`OPENCV_FFMPEG_CAPTURE_OPTIONS` 是 OpenCV 在打开流时读的环境变量,因此由 config 存值、app 在开流前写 `os.environ` 注入;不向操作员暴露原始字符串,只暴露 transport(tcp/udp)/连接超时(秒)/低延迟(勾选) 三个友好控件,程序内部拼串。这些不是凭证,可进公开示例默认值;启动可不再依赖 run 脚本设环境变量。
- 下一步:先写 `build_ffmpeg_options` 拼串与配置解析的失败测试,再实现;随后加设置页控件与保存。
## 【2026-07-21】T-207 抓流低延迟参数进配置与设置页(完成)
- 状态:DONE
- 变更:`config.py` 新增 `build_ffmpeg_options(transport, timeout_seconds, low_latency)`(拼 `rtsp_transport;..|stimeout;..(微秒)|fflags;nobuffer|flags;low_delay`),`AppConfig` 增 `transport`/`timeout_seconds`/`low_latency` 字段与 `ffmpeg_capture_options` 属性;`load_config` 解析并校验三者(默认 tcp/5/true);`write_local_camera_source` 一并写回。`app.py` 的 `FrameWorker.run` 在打开流之前把 `ffmpeg_capture_options` 写进 `os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"]`。`gui.py` 设置页摄像头分组新增「传输协议」下拉、「连接超时」数字、「低延迟」勾选,保存时一并落盘;`config.example.json` 加入三项安全默认值。
- 验证:新增 config 四项测试(`build_ffmpeg_options` 三种组合与非法值、调优默认值、显式解析、非法 transport 被拒);`python3 -m pytest v1/tests -q` 为 64 passed;`python3 -m compileall -q v1 demo` 退出码 0。以现有 `v1/config.local.json` 端到端加载确认 `transport=tcp/timeout=5/low_latency=True`、`ffmpeg_capture_options=rtsp_transport;tcp|stimeout;5000000|fflags;nobuffer|flags;low_delay`。
- 阻塞:设置页三个新控件需 Windows + PyQt5 冒烟;拼串、配置解析与 env 注入逻辑本环境已验证。
- 决策:不向操作员暴露原始 ffmpeg 字符串,只给 transport/超时/低延迟三个友好控件,程序内部拼串;由 config 存值、app 在开流前注入环境变量;这些非凭证,进入公开示例默认值。启动可不再依赖 `run_v1.ps1` 设 `OPENCV_FFMPEG_CAPTURE_OPTIONS`。
- 下一步:Windows 上冒烟设置页三控件与实际低延迟效果;继续 T-203 现场 GUI 验收。
## 【2026-07-21】T-208 每帧摔倒判定诊断叠加
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未创建生产代码。用户实测(1.2 米俯视)有骨架但摔倒未确认、无截图,需诊断定位卡点。
- 验证:开始前 V1 单元测试 64 passed。
- 阻塞:界面角落叠加渲染需 Windows + PyQt5 冒烟;诊断字符串构建与管线暴露策略证据在本环境可测。
- 决策:`FallPipeline` 的 `PersonAnalysis` 增加策略 `Evidence`(`is_fall_candidate`/`is_recovery_candidate`),`view_model` 增 Qt-free 的 `person_diagnostic_line` 与 `MonitorViewState.diagnostics`,GUI 在画面角落叠加;不改判定逻辑,只暴露 `accepted/horizontal/角度/rapid/candidate/state` 与拒绝原因。
- 下一步:先写诊断字符串失败测试,再实现管线暴露与叠加渲染。
## 【2026-07-21】T-208 每帧摔倒判定诊断叠加(完成)
- 状态:DONE
- 变更:`pipeline.py` 的 `PersonAnalysis` 增加策略 `Evidence` 字段(`is_fall_candidate`/`is_recovery_candidate`)并在 `process` 中填充。`view_model.py` 新增 Qt-free `person_diagnostic_line`(accepted 时输出 `acc=1 horiz/ang/rapid/cand`,拒绝时输出 `acc=0 <reason>`)与 `MonitorViewState.diagnostics`。`gui.py` 的 `VideoView` 在画面左上角半透明叠加每人诊断行。不改任何判定逻辑,只暴露决策中间量。
- 验证:新增 view_model 三项测试(accepted 决策字段格式、拒绝原因格式、build_monitor_view 每人一行诊断);`python3 -m pytest v1/tests -q` 为 67 passed;`python3 -m compileall -q v1 demo` 退出码 0。真实链路冒烟:假适配器立位人经真实 `FallPipeline`→`build_monitor_view` 得到 `P-0001 NORMAL acc=1 horiz=0 ang=90deg rapid=0 cand=0`(立位躯干近垂直、角度 90°、非水平、无快速下移、非候选,符合预期)。
- 阻塞:画面角落叠加渲染需 Windows + PyQt5 冒烟;诊断构建与管线暴露本环境已验证。
- 决策:诊断只读、不改判定;用于定位 1.2 米俯视实测中摔倒未确认卡点——重点看 `rapid`(俯视下髋部图像位移小,可能长期为 0,即根本进不了 SUSPECT)、`acc`(摔倒瞬间膝踝质量是否被拒)、以及是否进了 SUSPECT 但未撑满 1.8s。
- 下一步:据现场诊断读数决定调阈值还是换俯视判据(可能需新 task);继续 T-203 现场验收。
## 【2026-07-21】T-209 放宽俯视场景摔倒判定灵敏度(可配置)
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未创建生产代码。用户单人无法读诊断,要求放宽参数让真实摔倒能触发。实测「摔倒后仍绿线」= 从未进 SUSPECT,判断为「必须先快速下移」硬门槛在 1.2 米俯视下卡死入口。
- 验证:开始前 V1 单元测试 67 passed。
- 阻塞:无(纯判定逻辑与配置,本环境可测)。
- 决策:把三处严格约束参数化并默认放宽——`require_rapid_drop`(默认 False,持续水平即可进入疑似)、`require_lower_body`(默认 False,质量门控只强制肩+髋,膝踝可缺)、`horizontal_angle_threshold_degrees`(默认 45,原 35)。确认窗 `confirm_window_seconds` 保持不变作为唯一防误报主闸(弯腰/捡物不会持续水平 1.8s)。参数进 `config` 与 `runtime_config_version`,可日后经设置页微调。
- 下一步:先写放宽后的失败测试(水平即候选、缺膝踝仍接受),再实现并接线。
## 【2026-07-21】T-209 放宽俯视场景摔倒判定灵敏度(可配置)(完成)
- 状态:DONE
- 变更:`fall_policy.py` 的 `FallEvidencePolicy` 增 `require_rapid_drop`(构造默认 True 保持旧测试;关闭时持续水平即候选)。`evidence.py` 拆分 `_TORSO_JOINTS(肩髋)`/`_LOWER_JOINTS(膝踝)`,`assess_pose_quality` 增 `require_lower_body`(关闭时只强制肩+髋)。`pipeline.py` 增 `require_lower_body`/`horizontal_angle_threshold_degrees` 并在 `process` 传入,`from_config` 从 config 接线三者。`config.py` 的 `EventConfig` 增三字段(默认 `require_rapid_drop=false`/`require_lower_body=false`/`horizontal_angle_threshold_degrees=45`)、`load_config` 解析(新增 `_bool`)、`runtime_config_version` 纳入三者;`config.example.json` 加默认值。
- 验证:新增测试——fall_policy(关闭 rapid 门槛后水平即候选)、evidence(关闭下肢要求后缺膝踝仍接受)、config(灵敏度默认放宽);`python3 -m pytest v1/tests -q` 为 70 passed;`python3 -m compileall -q v1 demo` 退出码 0。端到端冒烟:以放宽默认 `from_config` 驱动横臥姿势(躯干 2°、膝踝低置信)——t=0 即进 SUSPECT(`horiz=1 cand=1`),持续到 t=1.9(≥confirm 1.8s)产生一个 `CONFIRMED` 事件;证明放宽后真实躺姿可确认。
- 阻塞:无。这些是默认放宽、可日后经设置微调的判定参数;真实误报率仍需 T-204 现场正反例录像验证。
- 决策:不删任何信号,只把 rapid/下肢/角度三处严格约束参数化并默认放宽;确认窗 1.8s 作为唯一主防误报闸。函数级默认保持严格(不破坏既有单测),生产默认由 config 放宽。
- 下一步:用户以放宽默认复测真实摔倒是否变红并出截图;若出现弯腰/捡物误报,用设置微调 `confirm_window_seconds` 或重开 `require_rapid_drop`;继续 T-203/T-204。
## 【2026-07-21】T-210 修复截图标注乱码(cv2 ASCII 文字)
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;尚未改代码。用户反馈截图红框文字乱码。
- 验证:开始前 V1 单元测试 70 passed。
- 阻塞:无(纯标注文字,本环境可测;截图像素级外观需 Windows 目视,但 ASCII 逻辑本环境可测)。
- 决策:根因是 `cv2.putText` 用 Hershey 字体只支持 ASCII,而标签里的 `·`(U+00B7) 非 ASCII → 乱码。修法:截图专用标签只用 track_id + 状态英文值拼成纯 ASCII;实时画面 Qt 标签保留 `·`(QPainter 能正常渲染)。中文截图标注需 Pillow+TTF,暂不做。
- 下一步:加 `screenshot_label` 纯 ASCII 生成 + 测试,`annotate_frame` 改用它。
## 【2026-07-21】T-210 修复截图标注乱码(cv2 ASCII 文字)(完成)
- 状态:DONE
- 变更:`alerts.py` 新增 `screenshot_label(track_id, state_value)`——用 `track_id + 状态英文值` 拼串并 `encode("ascii","ignore")` 剥离非 ASCII;`annotate_frame` 的 `cv2.putText` 改用它,去掉了原标签里 `·`(U+00B7) 导致的乱码。实时画面(Qt `QPainter`)标签保持不变,仍显示 `P-0001 · CONFIRMED`。
- 验证:新增 test_alerts 两项(`screenshot_label` 为纯 ASCII 且等于 `P-0001 CONFIRMED`、不含 `·`);`python3 -m pytest v1/tests -q` 为 72 passed;`python3 -m compileall -q v1` 退出码 0。
- 阻塞:无。截图像素级观感需 Windows 目视确认,但乱码根因(非 ASCII 送入 Hershey 字体)已在逻辑层修掉。
- 决策:截图标注限定 ASCII;若需中文标注(如"确认摔倒")另立任务用 Pillow+TTF 绘制。
- 下一步:用户 Windows 复测截图文字是否正常;继续 T-203/T-204。
## 【2026-07-21】T-203 接入海康 RTSP 与断流恢复(完成)
- 状态:DONE
- 变更:本任务无新增生产代码(重连逻辑在 T-102/T-106 已实现),补齐断流/重连的确定性测试并据现场结果验收。
- 验证:真实海康流已连通并在 Windows 现场跑通实时预览、骨架、状态与摔倒确认(用户确认,T-208/T-209 期间)。断流与重连以确定性测试锁定:`test_reconnect_waits_then_reopens_with_bounded_backoff`(不可达→有界退避→恢复 CONNECTED)、`test_stream_recovers_after_mid_stream_drop`(连接中→途中断→重连恢复)、`test_stream_source_uses_read_clock`(实时流用收帧单调时钟)、`test_disconnect_interrupts_suspect_without_alarm`(断流帧使管线返回 0 人 0 事件并把疑似打断回 NORMAL,即断流不报警)。WSL 对真实流的冒烟:STREAM 首读即 connected 解出 1920×1080;对不可达地址进入 retrying、不崩溃、空帧、不报警。`python3 -m pytest v1/tests -q` 为 74 passed;`python3 -m compileall -q v1` 退出码 0。
- 阻塞:无。真实"拔网→正在重连→恢复自动接上"的可视化观察由用户在 Windows 现场做一次目视核对(步骤见下一步),逻辑侧已测。
- 决策:断流只表达来源状态、绝不触发或推进摔倒事件;实时流以收帧单调时钟计时;重连用有界指数退避。
- 下一步:用户可选做一次现场断网核对(拔网线/停流→画面显示"正在重连…"、不出红色告警→恢复后自动接上并继续检测)。继续 T-204:需经同意成年人的正反例录像做事件级验收(含放宽灵敏度后的不误报验证)。
## 【2026-07-21】T-204 建立正反例录像回归与现场验收记录
- 状态:DOING
- 变更:任务从 BLOCKED 落为 DOING,先做可单人完成的反例(不误报)验证与调平衡,并建事件汇总工具支撑证据/延迟记录。正例(安全模拟摔倒+1–3 秒延迟)仍需经同意成年人与现场安全措施。
- 验证:开始前 V1 单元测试 74 passed。
- 阻塞:正例摔倒录像需现场资源;反例(走动/坐下/弯腰/捡物不报警)用户可单人现场测。
- 决策:放宽灵敏度后 `confirm_window_seconds`(1.8s) 是唯一主防误报闸,本轮重点验证正常活动是否保持不报警;据现场结果微调 `confirm_window_seconds` / `require_rapid_drop` 找灵敏度与误报的平衡。事件延迟以 JSONL 的 `latency_seconds` 记录(=确认时刻−疑似起点)。
- 下一步:交付 `v1/report.py` 事件汇总工具与反例测试脚本;用户现场跑正常动作反馈误报与诊断读数后调参。
## 【2026-07-21】T-204 反例(不误报)现场验证结果
- 状态:PARTIAL(反例通过;正例延迟记录与规范录像待补)
- 变更:无代码改动;本条记录现场反例验证结果。
- 验证:用户在真实海康流 + 放宽默认参数下现场测试正常活动——走动、坐下、弯腰、蹲下捡物、站立停留,各 2–3 次,**均不误报**(无红框/声音/弹窗,无新增 CONFIRMED 事件)。摔倒可确认(T-209 期间已验证会变红并出截图)。
- 阻塞:正例的 1–3 秒延迟正式记录待用户以 `python -m v1.report artifacts/events` 读取现场摔倒事件的 `latency_seconds`;规范的经同意成年人正反例录像与现场安全措施仍未建立。
- 决策:放宽默认(`require_rapid_drop=false`、`require_lower_body=false`、`horizontal_angle_threshold_degrees=45`、`confirm_window_seconds=1.8`)在该 1.2 米俯视机位下平衡良好——既抓到摔倒又不误报,**本轮无需调参**。`confirm_window_seconds=1.8` 作为主防误报闸有效。
- 下一步:用户读取现场摔倒事件延迟确认落在 1–3 秒;后续以第二人看护安全补充正例记录,再考虑将 T-204 整体置 DONE。
## 【2026-07-22】T-204 建立正反例录像回归与现场验收记录(完成)
- 状态:DONE
- 变更:用户确认以本地事件工件和现场结果完成 T-204 验收;任务看板解除 T-205 阻塞。
- 验证:`python -m v1.report artifacts/events` 读取本机 `artifacts/events/20260721/events.jsonl`,共 2 条确认事件,延迟分别为 1.84s 和 1.86s,均在 1–3 秒目标内;现场反例走动、坐下、弯腰、蹲下捡物、站立停留各 2–3 次均未触发 CONFIRMED。用户确认将本轮现场安全模拟与结果作为 T-204 验收依据。
- 阻塞:无。录像、截图与 JSONL 为本机工件,不提交仓库;后续现场可继续积累更丰富的经同意录像回归集。
- 决策:保留当前放宽默认与 1.8 秒确认窗,不在已有正反例结果支持前改阈值;演示表述继续限于固定俯视、全身大部分可见的单路大厅/走廊。
- 下一步:T-205,固化客户演示脚本与 V1 发布包。
## 【2026-07-22】T-205 固化客户演示脚本与 V1 发布包
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;将建立可复查的现场演示步骤、启动前安全/环境检查和 V1 发布包清单,不复制客户视频、RTSP 凭证或事件工件进仓库。
- 验证:开始前 `./init.ps1` 退出码 0,V1 测试为 85 passed、1 skipped;T-204 的本机事件汇总为 2 条确认事件且延迟均在 1–3 秒目标内。
- 阻塞:发布包必须仍由 Windows 现场执行 PyQt、声音、弹窗、截图和真实 RTSP 目视确认;本仓库不保存真实来源地址或客户素材。
- 决策:演示脚本必须明确固定俯视、全身大部分可见、单路大厅/走廊的适用边界,不将 Pose 模型或现场结果表述为医疗级或全场景可靠性。
- 下一步:盘点当前启动/配置/报告工具,先为预检和演示清单写可测试契约,再实现发布说明。
## 【2026-07-22】T-205 固化客户演示脚本与 V1 发布包(完成)
- 状态:DONE
- 变更:新增 `docs/demo-runbook.md`、`v1/release.py` 与 `v1/scripts/build-v1-release.ps1`;README 与 init 启动提示改为 V1。发布包只包含 V1 代码、公开示例、手册和 SHA-256 锁定模型,不包含本地配置或事件工件。
- 验证:临时发布包通过 `python -m v1.release verify`;包内 `./init.ps1` 为 88 passed、1 skipped,仓库 `./init.ps1`、`python -m pytest v1/tests -q`、`python -m compileall -q v1 demo` 和 `git diff --check` 均通过。
- 阻塞:无。真实凭证、录像、截图和 JSONL 保持本机工件。
- 决策:客户演示按运行手册执行并明确适用边界;V2 仍等待 T-301 ONNX 一致性验证。
- 下一步:T-301。
## 【2026-07-22】T-301 锁定 V1 模型并导出 ONNX 一致性工件
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;将以 `demo/best.pt` 的 SHA-256 锁定来源,导出忽略的 ONNX 工件,并在同一录像帧比较 Python 与 ONNX Pose 关键点。
- 验证:开始前 `./init.ps1` 通过(88 passed、1 skipped);本机可导入 `onnx`、`onnxruntime`、`torch` 与 Ultralytics。
- 阻塞:无。
- 决策:ONNX 文件作为忽略的构建工件,不直接提交;提交模型清单、导出命令、哈希、输入尺寸、版本和一致性结果,供 T-302/T-303 使用。
- 下一步:先写导出/比较脚本和失败测试,再导出真实工件。
## 【2026-07-22】T-301 锁定 V1 模型并导出 ONNX 一致性工件(完成)
- 状态:DONE
- 变更:导出忽略的 `v2/assets/best.onnx`,提交 `v2/assets/model-manifest.json` 记录来源、哈希、640 输入和一致性容差。
- 验证:`demo/best.pt` SHA-256 为 `cd2009483e2ad9ed0b303dc3621a6b155b9f0ff856fc79ca3595abf04bfafdfc`;ONNX SHA-256 为 `fac42d41a2ae3108dc42b3a2097f299e27d7f4055cb6b26a319fad0b0a3f286e`。首帧 Python/ONNX 均检出 2 人、每人 17 点;最大关键点差 5.7653,小于记录的 6 像素容差。
- 阻塞:无。
- 决策:ONNX 加载必须显式 `task='pose'`,否则 Ultralytics 会按 detect 解析而缺 keypoints。
- 下一步:T-302。
## 【2026-07-22】T-302 Go 推理与视频/UI 技术 Spike
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;将验证 Go 对锁定 ONNX Pose 的直接推理、录像读取和 Windows UI 技术路线。
- 验证:开始前 `./init.ps1` 通过(88 passed、1 skipped);Go 1.24.0、ONNX Runtime DLL、ONNX Runtime Python 包均存在。
- 阻塞:GoCV/OpenCV 开发绑定尚未配置,不能假设可直接用于 V2。
- 决策:先以 Go ONNX Runtime 运行时和最小录像/UI Spike 验证,再决定 V2 引入的依赖与 Windows 打包方式。
- 下一步:建立最小 Go 模块与 ONNX Runtime 推理验证。
## 【2026-07-21】T-211 检测灵敏度参数进设置页
- 状态:DOING
- 变更:任务落 DOING;将新增 `write_local_event_tuning` 与设置页「检测灵敏度」分组。
- 验证:开始前 V1 单元测试 78 passed。
- 阻塞:设置页控件需 Windows 冒烟;写回与 config 往返本环境可测。
- 决策:与摄像头连接同样走"写回未跟踪 config.local.json、下次启动生效",不进数值草稿(含布尔控件)。
- 下一步:写 `write_local_event_tuning` 往返测试,加设置页控件与保存。
## 【2026-07-22】T-211 检测灵敏度参数进设置页(完成)
- 状态:DONE
- 变更:`config.py` 新增 `write_local_event_tuning`(只更新 event 块的 require_rapid_drop/require_lower_body/horizontal_angle_threshold_degrees/confirm_window_seconds,保留其它字段)。`gui.py` 设置页新增「检测灵敏度」分组:确认窗口数字、水平角度阈值数字、两个勾选(要求快速下移/要求膝踝清晰),保存按钮写回本地配置。`app.py` 从 config 预填这四项传入设置页。
- 验证:新增 config 测试(`write_local_event_tuning` 往返且保留其它 event 字段);`python3 -m pytest v1/tests -q` 为 79 passed;`python3 -m compileall -q v1` 退出码 0。
- 阻塞:设置页控件需 Windows 冒烟;写回与 config 往返本环境已验证。
- 决策:灵敏度与摄像头连接一致,走"写回未跟踪 config.local.json、下次启动生效",不进数值草稿(含布尔控件不便统一)。
- 下一步:T-212 实时流只处理最新帧降延迟。
## 【2026-07-22】T-212 实时流只处理最新帧降延迟
- 状态:DOING
- 变更:任务落 DOING;将在 `VideoSource` 加后台读帧线程(STREAM,`drop_stale`)只保留最新帧。
- 验证:开始前 V1 单元测试 79 passed。
- 阻塞:真实线程整合需 Windows 冒烟;最新帧保留核心 `_pump` 逻辑本环境可确定性测。
- 决策:读帧解码放后台线程、消费端只取最新帧、丢弃积压,使延迟不随推理速度增长且被钳在一次推理量级;REPLAY 保持逐帧不丢;STREAM 用收帧时单调时钟。
- 下一步:写 `_FrameGrabber._pump` 只留最新帧的失败测试,再实现并在 app 对 STREAM 开启。
## 【2026-07-22】T-212 实时流只处理最新帧降延迟(完成)
- 状态:DONE
- 变更:`video_source.py` 新增 `_FrameGrabber`(daemon 线程,持续 `cap.read()` 只保留最新帧、丢弃积压,读失败置 failed,读异常不崩线程)。`VideoSource` 增 `drop_stale`(仅 STREAM 生效):懒开流后启动 grabber,`read()` 返回最新帧(无帧时 RETRYING/connecting,failed 则释放+有界退避重连),`close()` 停 grabber。`app.py` 对 STREAM 开启 `drop_stale=True`。REPLAY 路径不变(逐帧不丢)。
- 验证:新增测试——`_FrameGrabber._pump` 连续三帧后 `take_latest` 只留最新(丢弃前两帧)、读失败置 failed;STREAM+drop_stale 集成(真实线程)在轮询内返回 CONNECTED 帧。`python3 -m pytest v1/tests -q` 为 82 passed;`python3 -m compileall -q v1` 退出码 0。
- 阻塞:真实 RTSP 上的端到端延迟改善需 Windows 目视确认;丢弃最新帧核心逻辑与集成本环境已测。
- 决策:解码放后台线程、消费端只取最新帧,使延迟被钳在一次推理量级、不随推理速度增长;STREAM 用收帧时单调时钟,丢帧不破坏摔倒计时(每处理帧取真实到达时间)。
- 下一步:T-213 截图中文标注(Pillow,缺库/字体回退 ASCII)。
## 【2026-07-22】T-213 截图中文标注(Pillow)
- 状态:DOING
- 变更:任务落 DOING;将在 `alerts.py` 用 PIL 绘制中文标签,缺 PIL/字体回退 cv2 ASCII。
- 验证:开始前 V1 单元测试 82 passed;本环境 PIL 可用,有 DejaVu TTF 供 PIL 路径测试(中文字形需 Windows 的 msyh.ttc)。
- 阻塞:真实中文观感需 Windows(用 C:/Windows/Fonts/msyh.ttc);标签、字体查找与回退逻辑本环境可测。
- 决策:auto 字体候选只放 CJK 字体(msyh/simhei/文泉驿/Noto CJK),找不到就回退 ASCII 而非渲染豆腐块;Windows 自动用雅黑,无需配置。
- 下一步:加 `annotation_label`/`_find_font`/PIL 绘制与回退,`annotate_frame` 接入,补测试。
## 【2026-07-22】T-213 截图中文标注(Pillow)(完成)
- 状态:DONE
- 变更:`alerts.py` 新增 `annotation_label`("确认摔倒 P-0001" 等中文)、`_find_font`(只在 CJK 字体候选中查找:Windows msyh/simhei、Linux 文泉驿/Noto CJK)、`_draw_labels`/`_draw_labels_pil`。`annotate_frame(image, view, font_path=None)`:cv2 画框/骨架/红边后,若 PIL 可用且找到 CJK 字体则 PIL 绘中文标签,否则回退 cv2 ASCII(保留 T-210)。`requirements.txt` 加 `Pillow==9.5.0`。
- 验证:新增 test_alerts 四项(中文标签、字体查找有无、无字体回退不崩溃、有 TTF 时 PIL 路径运行);`python3 -m pytest v1/tests -q` 为 86 passed;`python3 -m compileall -q v1` 退出码 0。本环境无 CJK 字体故 auto 走 ASCII 回退;用 DejaVu 显式路径验证 PIL 绘制路径不崩溃(中文字形需 Windows 雅黑)。
- 阻塞:真实中文观感需 Windows(自动用 C:/Windows/Fonts/msyh.ttc)。
- 决策:auto 字体候选只放 CJK 字体,找不到就回退 ASCII 而非渲染豆腐块;无需配置,Windows 自动中文、其它环境安全回退。
- 下一步:本批"别的"三项(T-211/212/213)完成;用户 Windows 冒烟中文标注、低延迟与灵敏度设置页。
## 【2026-07-22】T-302 Go 推理与视频/UI 技术 Spike(完成)
- 状态:DONE
- 变更:新增 `v2/` Go 模块、`internal/spike` 的 BGR→RGB/CHW/640 letterbox 与 ONNX Runtime 单次推理、`cmd/spike` 的本地录像验证命令,以及 `cmd/ui-spike` 的 Windows Walk 顶部双 Tab 外壳;新增 `docs/adr/2026-07-22-t302-go-spike-decision.md`,并同步技术栈、架构和当前状态。未提交 ONNX、DLL、FFmpeg、录像、凭证或事件工件。
- 验证:`./init.ps1` 通过(V1 为 88 passed、1 skipped)。在 `v2` 下,`CGO_ENABLED=1 go test ./...` 通过;`CGO_ENABLED=0 go build ./cmd/ui-spike` 通过。以 FFmpeg 解码 `demo/1.mp4` 首帧(848×480)并对锁定 ONNX 运行:`SPIKE_OK frame=848x480 input=1228800 output=470400 max=685.7795`。`git diff --check` 通过。
- 决策:CPU Pose 使用 `onnxruntime_go` v1.31.0 加显式、发布时锁定的 ONNX Runtime DLL;视频解码使用受控 FFmpeg 子进程;UI 选择 Walk。Fyne 因 OpenGL/CGO 首次构建超时而拒绝,GoCV 因本机没有开发绑定而不选。当前缓存 DLL 只用于 Spike,绝不作为发布依赖。
- 阻塞:无任务阻塞;但 Go 尚未复现 V1 的预后处理、NMS、关键点解析、跟踪、状态机、延迟回归、RTSP 和报警,不能作为客户演示版。
- 下一步:T-303,按 V1 合约实现事件引擎并在同一录像上比较关键点、确认事件、无报警案例和延迟。
## 【2026-07-22】T-303 实现 Go 事件引擎与 V1 回归对比
- 状态:DOING
- 变更:任务从 TODO 落为 DOING;将把 V1 的 Pose 解析、轻量跟踪、质量/几何证据、倒地策略和四态确认机迁移为 Go,并建立可重复回归。
- 验证:开始前 `./init.ps1` 通过(88 passed、1 skipped);T-302 的 Go 测试此前通过。当前仓库没有可提交的正例摔倒录像或 `testdata/expected_events.json`;`demo/1.mp4` 只能作为本机无报警回放材料,真实正例录像仍保持本机工件。
- 阻塞:无代码阻塞。任务验收将明确区分“提交的合成契约回归 / 本机 demo 无报警回放”与“需本机正例录像才能证实的录像级确认事件、延迟”。
- 决策:不复制或提交客户录像;先忠实复现 V1 的纯领域合约,并让回归工具接收外部本机录像和基线 JSON。
- 下一步:先为 Go Pose 后处理与事件状态机编写失败测试。