2026-07-20 22:05:55 +08:00
# 执行进度记录
> 本文件只追加记录任务执行、验证、阻塞和关键决策。当前目录、当前命令和下一任务以 [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 22:24:38 +08:00
## 【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 22:39:52 +08:00
## 【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 22:50:57 +08:00
## 【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 22:57:37 +08:00
## 【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 23:06:54 +08:00
## 【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 09:42:55 +08:00
## 【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 09:45:42 +08:00
## 【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 09:46:18 +08:00
## 【2026-07-21】T-102 实现可重连的视频源与录像回放适配器
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建视频源生产代码。
- 验证:继承 T-101 的 `./init.ps1` 基线通过记录;本任务将先以可控假捕获器建立单元测试,再以本地演示录像作 smoke 验收。
- 阻塞:无。
- 决策:视频源只返回帧、单调时间戳和来源状态;读取失败、EOF 和重连绝不生成虚构人员或摔倒事件。
- 下一步:写入时间戳单调、错误来源和有界重连的失败测试。
2026-07-21 09:49:28 +08:00
## 【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 09:49:44 +08:00
## 【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 09:53:03 +08:00
## 【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 09:53:29 +08:00
## 【2026-07-21】T-104 实现人员跟踪与姿态质量门控
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建跟踪或证据生产代码。
- 验证:继承 T-103 的 `./init.ps1` 、9 项 V1 测试和真实模型 smoke 通过记录;本任务将先建立相邻帧 ID 稳定性、缺失下肢点拒绝和水平几何仅作为证据的失败测试。
- 阻塞:无。
- 决策:质量与证据模块只能返回质量/几何事实,不能返回报警或直接创建事件;空帧和低质量姿态不可推进倒地候选。
- 下一步:写入 T-104 的失败测试。
2026-07-21 09:57:00 +08:00
## 【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 09:57:23 +08:00
## 【2026-07-21】T-105 实现按 ID 的时序摔倒状态机
- 状态:DOING
- 变更:任务已从 TODO 落为 DOING;尚未创建状态机生产代码。
- 验证:继承 T-104 的 `./init.ps1` 与 14 项 V1 测试通过记录;本任务将先写持续倒地确认一次、短时弯腰无事件、恢复后才允许新事件和拒绝证据不推进时间的失败测试。
- 阻塞:无。
- 决策:状态机只接收按 ID 的可用证据与单调秒数;视频帧率、空帧、来源断流和拒绝姿态都不能缩短确认窗口或创建事件。
- 下一步:写入 T-105 失败测试。
2026-07-21 09:59:53 +08:00
## 【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 11:47:07 +08:00
## 【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 11:57:22 +08:00
## 【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 19:36:10 +08:00
## 【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 19:43:49 +08:00
## 【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 19:44:36 +08:00
## 【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 19:49:20 +08:00
## 【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 19:50:19 +08:00
## 【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 20:54:34 +08:00
## 【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 21:44:46 +08:00
## 【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 22:34:59 +08:00
## 【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 22:44:09 +08:00
## 【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 22:51:49 +08:00
## 【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 22:56:14 +08:00
## 【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 23:10:53 +08:00
## 【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 23:14:05 +08:00
## 【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 23:23:45 +08:00
## 【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 23:29:16 +08:00
## 【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 23:37:18 +08:00
## 【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 23:38:53 +08:00
## 【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 23:49:21 +08:00
## 【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 23:51:59 +08:00
## 【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 23:55:40 +08:00
## 【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-21 23:58:52 +08:00
2026-07-22 15:05:31 +08:00
## 【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 15:05:58 +08:00
## 【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 15:10:11 +08:00
## 【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 15:12:29 +08:00
## 【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 15:14:41 +08:00
## 【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 15:46:49 +08:00
## 【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 23:58:52 +08:00
## 【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 00:00:51 +08:00
## 【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 00:02:01 +08:00
## 【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 00:04:00 +08:00
## 【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 00:08:01 +08:00
## 【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 00:10:07 +08:00
## 【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 16:01:28 +08:00
## 【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 16:05:36 +08:00
## 【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 后处理与事件状态机编写失败测试。
2026-07-22 16:18:16 +08:00
## 【2026-07-22】T-303 实现与无报警录像回归(部分完成)
- 状态:PARTIAL(代码与无报警回归完成;真实正例录像回归待本机素材)
- 变更:新增 `v2/internal/pose` (114 补边、双线性缩放后 uint8 量化、BGR→RGB/CHW、YOLOv8 Pose NMS、17 点与坐标还原)、`v2/internal/fall` (归一化中心点跟踪、姿态质量/几何证据、倒地策略、四态状态机、事件 ID)和 `v2/cmd/regression` ( FFmpeg 回放→ONNX Pose→事件引擎)。`spike` 的预处理现委托给同一正式实现,ONNX Runtime session 可跨帧复用。
- 验证:Go 单元测试覆盖 letterbox 补边/通道布局、NMS 坐标还原、输入长度、事件确认、缺帧中断和持续站立不报警;`CGO_ENABLED=1 go test ./...` 通过。本机 317 帧回放:V1 为 253 个有人帧、263 人次、0 个 CONFIRMED; Go 为 255 个有人帧、265 人次、0 个 CONFIRMED。二者无报警结论一致。合成正例以同一 V1 策略/状态机合约在 1.81 秒生成一个 CONFIRMED, Go 测试同样通过。
- 阻塞:无客户或正例录像被提交;仓库也没有经同意的正例回放素材。因此不能把合成契约或无报警录像表述为真实正例的 Go/V1 等价,也不能把 T-303 标为 DONE。
- 决策:保持 T-303 为 DOING;回归命令接受本机路径,不把录像、截图、事件 JSONL、RTSP 地址或凭证写进仓库。解码器差异造成同一无报警录像的人次计数轻微差异,事件而非逐帧检测人次是当前验收基线。
- 下一步:提供或指定一段经同意、无敏感信息的本机正例录像及其 V1 输出后,运行同一命令比对确认事件与 1–3 秒延迟;通过后才可完成 T-303,继而领取 T-304。
2026-07-22 20:49:41 +08:00
## 【2026-07-22】T-303 回归素材性质更正与 `demo/1.mp4` 漏检诊断
- 状态:DOING(更正此前将该视频称作“无报警回放”的错误;T-303 尚未完成)。
- 变更:更新 `docs/03-tech-stack.md` 、`docs/04-architecture.md` 、`docs/current-state.md` ,并新增 `docs/review/2026-07-22-demo1-fall-diagnosis.md` 。不改模型、不调阈值、不提交视频或诊断帧。
- 验证:用户确认 `demo/1.mp4` 为真实摔倒。V1 锁定模型和默认事件配置回放 317 帧为 0 个 `CONFIRMED` ,因此这是漏检而非无报警通过。第 72–74 帧(2.400–2.467 s)仅有 3 帧横向候选并处于 `SUSPECT` ;第 75 帧目标最高 Pose 框分为 0.012,第 90 帧以 `conf=0.01` 推理仍无任何人体 Pose。检测阈值降至 0.15、0.10 均仍为 0 事件。Go 同一回放也为 0 事件,但不能作为真实正例一致性通过证据。
- 阻塞:缺少一段经同意、符合固定俯视大厅/走廊和全身大部分可见边界的本机正例录像,且该录像须先在 V1 产生可记录的 1–3 秒确认事件,才能完成 Go/V1 真实正例对比。
- 决策:`demo/1.mp4` 作为本机域外摔倒漏检风险样本,不纳入 T-303 正例验收;不以缩短确认窗或单纯降低阈值伪造修复。若要覆盖室外斜视、字幕遮挡等场景,另立模型数据适配/评估任务。
- 下一步:取得符合部署边界的正例录像及 V1 基线后执行 T-303 的真实正例回归。
2026-07-22 21:08:01 +08:00
## 【2026-07-22】T-303 人工确认完成与 T-304 启动
- 状态:T-303 为 DONE(用户手动确认);T-304 为 DOING。
- 变更:按用户明确授权将 T-303 从 `DOING` 改为 `DONE` ,并领取 T-304。`docs/06-tasks.md` 与 `docs/current-state.md` 已同步;T-303 的 `demo/1.mp4` 漏检诊断保留,不将人工任务状态改写为正例录像已通过。
- 验证:启动前 `./init.ps1` 通过:88 passed、1 skipped。
- 阻塞:T-304 实现阶段无代码 blocker;最终现场验收仍需要未跟踪的本机 RTSP 凭证、网络和现场视频。发布依赖的 ONNX Runtime DLL、FFmpeg/FFprobe 还需锁定来源、版本与 SHA-256。
- 决策:T-304 继续使用 Go、Walk、受控 FFmpeg 和 ONNX Runtime;保持单路固定机位边界、浅色 Windows UI,红色仅用于 CONFIRMED 与关联报警证据。
- 下一步:审查 V2 当前 Spike、V1 报警契约和 UI 规格,编写并执行 T-304 的可测试实施计划。
2026-07-22 21:28:57 +08:00
## 【2026-07-22】T-304 Go 实时演示、报警和打包(实现部分完成)
- 状态:DOING(代码与本机发布目录已完成;现场演示验收待执行)。
- 变更:新增 `v2/internal/config` (仅环境变量 RTSP、脱敏配置版本)、`internal/source` ( FFprobe/FFmpeg、有界重连、最新帧)、`internal/render` /`internal/alert` (姿态叠加、一次 PNG/JSONL)、`internal/monitor` (后台 Pose→事件→证据,画面与告警分离队列)、`internal/ui` 的浅色 Walk 双 Tab 窗口、`cmd/silver-pose` 、发布脚本与 V2 现场手册。红色只由 `CONFIRMED` 使用;来源错误不进入事件引擎且不含 URL。
- 验证:`CGO_ENABLED=1 go test ./...` 通过;`CGO_ENABLED=1 go build ./cmd/silver-pose` 通过;缺少 `SILVER_POSE_RTSP_URL` 的命令冒烟以非零退出且只显示环境变量名。`build-v2-release.ps1` 已从本机显式输入生成忽略的 `artifacts/v2-release-check` ,包含 `silver-pose.exe` 、ONNX、ONNX Runtime DLL、FFmpeg、FFprobe、公开配置、手册和 `runtime-manifest.json` 。清单记录 ONNX SHA-256 `fac42d41a2ae3108dc42b3a2097f299e27d7f4055cb6b26a319fad0b0a3f286e` 、DLL/工具哈希与 FFmpeg 版本。
- 阻塞:最终验收需现场未跟踪 RTSP 凭证、可用海康流、现场网络与经同意的安全模拟动作;当前不能伪造实时画面、声音、弹窗、截图或 1–3 秒正例延迟。`demo/1.mp4` 继续只作为域外漏检风险样本。
- 决策:发布脚本不从 PATH、Python 包或 Go 缓存隐式取运行依赖,全部由显式参数输入并记录哈希;事件队列不随最新帧丢弃,确保 UI 不因慢刷新遗漏确认告警。
- 下一步:用本机 `config.local.json` 与现场 RTSP 启动发布目录程序,按 `docs/v2-demo-runbook.md` 逐项验证在线/反例/确认/断流恢复,再决定是否将 T-304 置 DONE。
2026-07-22 22:49:01 +08:00
## 【2026-07-22】T-305 V2 UI 框架评估:Gio Spike
- 状态:DOING
- 变更:任务落 DOING;将在 `v2/cmd/gio-spike` 用 Gio 搭最小窗口,验证能否绕开 Walk 的 `TTM_ADDTOOL` 启动故障并贴 `*image.RGBA` 。
- 验证:开始前——WSL 为 Go 1.21.10 而 `v2/go.mod` 要求 go 1.24.0,且 Gio 的 Linux 构建需 CGo+X11、本环境无显示,故 Gio spike 无法在 WSL 编译/运行,必须在 Windows `go run ./cmd/gio-spike` 验证(与 PyQt5 同类环境限制)。V2 非 UI 链路(config/source/pose/fall/render/alert)此前已实现,仅 Walk UI 因缺 Common Controls v6 清单在 `ui.NewWindow` 崩溃(`TTM_ADDTOOL failed` ,见 obsidian 笔记)。
- 阻塞:Gio 依赖需在能联网且 Go≥1.24 的环境 `go get gioui.org` ;真实窗口显示需 Windows 目视。
- 决策:Gio 不用 Win32 common controls,从根上不存在 `TTM_ADDTOOL` /manifest 问题;`internal/render.Render` 已返回 `*image.RGBA` , Gio 用 `paint.NewImageOp` 直接贴,UI 层很薄;纯 Go 单 exe 符合"无 Go 环境运行"目标且不带 WebView2。新建 `cmd/gio-spike` ,不动现有 Walk `cmd/ui-spike` (保留对照)。
- 下一步:写 `cmd/gio-spike` 最小窗口(双 Tab + 贴一张 RGBA + 开始/停止按钮),给 Windows 运行命令;据结果决定修 Walk 还是迁 Gio。
2026-07-23 08:45:21 +08:00
## 【2026-07-23】T-305 V2 UI Gio Spike(交叉编译验证通过,待 Windows 目视)
- 状态:DOING(代码完成 + 交叉编译验证;窗口目视需 Windows)
- 变更:新增 `v2/cmd/gio-spike/main.go` ——Gio 最小窗口:浅灰蓝底、顶部双 Tab(实时监控/设置)、贴一张合成 `*image.RGBA` (模拟 render 输出,带 6px 红边)、开始/停止按钮,关闭即退出。不动现有 Walk `cmd/ui-spike` (保留对照)。`go.mod` 加 `gioui.org v0.10.1` , `go.sum` 由 `go mod tidy` 补全。
- 验证:本环境为 Go 1.21/无显示,无法运行 Gio;改用 `GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/gio-spike` **交叉编译到 Windows** 对真实 Gio Windows API 做类型检查——`BUILD_EXIT=0` ,产出 11 MB 纯 Go 单 exe( CGO_ENABLED=0 通过,证明 Gio 的 Windows 版无需 CGo,符合"无 Go 环境单文件运行"目标)。因此代码对 Windows API 编译无误,`go run ./cmd/gio-spike` 在 Windows 上有高把握能起窗。
- 阻塞:窗口能否真正显示/关闭、无 `TTM_ADDTOOL` ,需用户在 Windows 目视一次(Gio 不用 Win32 common controls,理论上不会出该错)。
- 决策:Gio 从根上规避 Walk 的 comctl/manifest 问题;`internal/render` 已出 `*image.RGBA` , Gio 用 `paint.NewImageOp` 直接贴,UI 层很薄。若 Windows 目视通过,则 V2 UI 由 Walk 迁 Gio;否则回退"给 Walk 嵌 Common Controls v6 清单"方案。
- 下一步:用户在 `v2/` 下 `go run ./cmd/gio-spike` 确认起窗;通过则据此把 `internal/ui` 迁 Gio、更新架构文档。
2026-07-23 14:16:40 +08:00
## 【2026-07-23】T-305 V2 UI Gio Spike(完成)
- 状态:DONE
- 变更:无(延续上一条 spike 实现)。
- 验证:用户在 Windows `go run ./cmd/gio-spike` 确认——窗口正常显示、有带红边的 `*image.RGBA` 图和顶部双 Tab、**未再出现 `TTM_ADDTOOL` **。结合本环境 `GOOS=windows CGO_ENABLED=0` 交叉编译通过(11MB 纯 Go 单 exe),Gio 方案在 Windows 上可行且自包含。
- 阻塞:无。
- 决策:**V2 UI 从 Walk 迁移到 Gio**。理由:Gio 从根上不用 Win32 common controls,规避 comctl/manifest 一类问题;`internal/render` 已出 `*image.RGBA` , Gio 直接 `paint.NewImageOp` 贴,UI 层薄;纯 Go 单 exe 符合"无 Go 环境运行"且不带 WebView2。已更新架构文档 `internal/ui` 为 Gio;新增迁移任务 T-306。
- 下一步:T-306 用 Gio 重写 `internal/ui` ,保持 `NewWindow/Run/Present/PresentAlert/ReportIssue` 接口不变。
2026-07-23 14:20:54 +08:00
## 【2026-07-23】T-306 V2 UI 从 Walk 迁移到 Gio
- 状态:DOING
- 变更:任务落 DOING;将用 Gio 重写 `v2/internal/ui/window.go` ,保持 `Window` 公开接口(`NewWindow/Run/Present/PresentAlert/ReportIssue` )与 `spec.go` 的 `ViewModel` /`MonitorWindowSpec` 不变;`cmd/silver-pose` 、`monitor` 、`alert` 等不动。
- 验证:开始前 `GOOS=windows CGO_ENABLED=0 go build ./cmd/gio-spike` 通过。迁移每步以 `GOOS=windows` 交叉编译验证(本环境无法运行 Gio)。
- 阻塞:真实窗口/声音/弹窗目视需 Windows。
- 决策:后台 `Present/PresentAlert/ReportIssue` 只在带锁共享区更新最新帧/状态/待弹窗并 `Invalidate()` , Gio 事件循环在独立 goroutine 只渲染最新帧(取代 Walk 的 `main.Synchronize` );声音用 Windows `MessageBeep` syscall( `sound_windows.go` + 非 Windows 空实现),弹窗自绘遮罩。`ViewModel` 逻辑复用、`spec_test.go` 不受影响。
- 下一步:写 Gio `window.go` + sound 文件,交叉编译通过后接 `cmd/silver-pose` 验证。
2026-07-23 14:26:19 +08:00
## 【2026-07-23】T-306 V2 UI 从 Walk 迁移到 Gio(代码完成 + 交叉编译验证)
- 状态:DOING(迁移代码完成 + Windows 交叉编译通过;窗口/声音/弹窗目视需 Windows)
- 变更:用 Gio 重写 `v2/internal/ui/window.go` ,保持 `Window` 公开接口(`NewWindow/Run/Present/PresentAlert/ReportIssue` )与 `spec.go` 的 `ViewModel` /`MonitorWindowSpec` 不变。后台 `Present/PresentAlert/ReportIssue` 只在带锁共享区更新最新帧/状态/待弹窗并 `Invalidate()` ; Gio 事件循环在独立 goroutine 渲染最新帧(取代 Walk 的 `main.Synchronize` )。双 Tab(监控/设置)、`widget.Image` 贴 render 的 `*image.RGBA` 、确认弹窗自绘遮罩+“我已知晓”、声音用 `MessageBeep` ( `sound_windows.go` syscall + `sound_other.go` 空实现)。`cmd/silver-pose` /`monitor` /`alert` 未改。`go mod tidy` 移除 `lxn/walk` 、`lxn/win` 、`govaluate.v3` ,新增 `golang.org/x/sys` (声音)。
- 验证:`GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build ./internal/ui/... ./cmd/ui-spike/... ./cmd/gio-spike/...` → 全部退出码 0( Gio Windows 版无 CGo,可对真实 Windows API 型检)。`grep lxn/walk` 全仓库无剩余引用。`cmd/silver-pose` 全量构建需 Windows + `CGO_ENABLED=1` ( onnxruntime_go 依赖 CGo,迁移前即如此),本环境无 mingw 故不构建。
- 阻塞:真实窗口显示、声音、弹窗目视需 Windows。`go run ./cmd/ui-spike` 为 CGo-free,可单独目视迁移后的 Gio 窗口外壳;`go run ./cmd/silver-pose` 需 `CGO_ENABLED=1` 起全量管线。
- 决策:`internal/ui` 正式由 Walk 迁至 Gio,彻底消除 comctl/manifest( `TTM_ADDTOOL` )问题;UI 只渲染已判定的最新帧,事件计算仍在后台。
- 下一步:用户 Windows 目视 `go run ./cmd/ui-spike` (外壳)与 `go run ./cmd/silver-pose` (全量,`CGO_ENABLED=1` )确认起窗、贴视频、双 Tab、确认弹窗与声音;通过则 T-306 与 T-304 一并推进 DONE。
2026-07-23 14:28:48 +08:00
## 【2026-07-23】T-306 V2 UI Walk→Gio(完成)
- 状态:DONE
- 变更:无(延续上一条迁移实现)。
- 验证:用户在 Windows `go run ./cmd/ui-spike` 确认——Gio 窗口正常显示、**不再出现 `TTM_ADDTOOL` **。结合本环境 `GOOS=windows CGO_ENABLED=0` 对 `internal/ui` /`ui-spike` /`gio-spike` 交叉编译通过、`grep lxn/walk` 全仓库无剩余引用,迁移完成。
- 阻塞:无。`cmd/silver-pose` 全量(含 onnxruntime, `CGO_ENABLED=1` )的实时视频/弹窗/声音目视仍属 T-304 现场验收范围。
- 决策:V2 UI 正式为 Gio; `internal/ui` 只渲染已判定的最新帧,后台经 `Invalidate` 上屏;comctl/manifest 一类问题从根消除。
- 下一步:T-304——`go run ./cmd/silver-pose` ( `CGO_ENABLED=1` )现场跑实时 RTSP → 红色画面/声音/弹窗/截图,验完置 DONE。
2026-07-23 16:04:09 +08:00
## 【2026-07-23】T-304 修复:onnxruntime DLL 版本不匹配(降 onnxruntime_go 匹配 1.19.2)
- 状态:进行中(现场启动修复)
- 变更:`onnxruntime_go` 从 v1.31.0 降到 **v1.12.1** 。根因:v1.31.0 请求 `ORT_API_VERSION 26` ( onnxruntime 1.26),而现场 DLL 是 Python3.8 的 onnxruntime **1.19.2** ( API 19),旧 DLL `GetApi(26)` 返回 NULL → “无法初始化 ONNX Runtime”。`onnxruntime_go` 模块版本与 onnxruntime 版本非一一对应(探测:v1.15/1.16/1.17→API20/1.20, v1.12.1→**API19/1.19**)。选 v1.12.1 使其匹配现场 1.19.2 DLL,用户无需下载新 DLL 或改配置。另在 `cmd/silver-pose` 补日志打印 ONNX/引擎/管线初始化的真实错误。
- 验证:`ort.go` 用到的 `NewAdvancedSession/NewEmptyTensor/NewShape/SetSharedLibraryPath/InitializeEnvironment/DestroyEnvironment/AdvancedSession.Run/Destroy/Tensor.GetData` 在 v1.12.1 签名一致;本地 `gcc 9.4.0` 下 `CGO_ENABLED=1 go build ./internal/spike` 通过(真机同样可编);`go mod tidy` 通过;Gio UI `GOOS=windows CGO_ENABLED=0` 仍编译通过。
- 阻塞:真机 `go run ./cmd/silver-pose` ( Windows + CGO=1)需用户跑,确认不再报 ONNX 初始化失败、能起管线。
- 决策:优先复用现场已有 1.19.2 DLL(降 onnxruntime_go),而非要求下载 1.26 DLL(该版本在现场未必可得,且 py38 DLL 是既有资产)。V2 后续发布包仍应按 config.example 的 `runtime/` 自带匹配 DLL。
- 下一步:用户重跑现场验收;仍失败则贴控制台真实错误行。
2026-07-23 20:56:22 +08:00
## 【2026-07-23】T-307 V2 监控界面优化
- 状态:DOING
- 变更:任务落 DOING。V2 Gio 监控页优化:运行状态/检测状态压一行、开始停止按钮缩小同行、红色说明并入事件行;右侧 3×3 倒序最近确认截图画廊;双击缩略图弹全图。
- 验证:开始前 V2 现场已跑通(连流/检测/截图)。改动每步 `GOOS=windows CGO_ENABLED=0 go build -mod=vendor ./internal/ui/...` 交叉编译验证;本环境无法运行 Gio,窗口目视需 Windows。
- 阻塞:窗口目视需 Windows。
- 决策:画廊数据源用 `Present` 里的确认帧(`update.Image` 是 render 输出的截图内容)+ `update.Events` 记录,按 event_id 去重、UI 线程外收集;`paint.ImageOp` 每项只建一次并缓存,避免逐帧重解;双击用点击时间窗判定,全图弹窗复用遮罩 Stack。
- 下一步:改 window.go 布局与画廊,交叉编译通过后用户 Windows 目视。
2026-07-23 20:58:45 +08:00
## 【2026-07-23】T-307 V2 监控界面优化(代码完成 + 交叉编译验证)
- 状态:DOING(代码完成 + Windows 交叉编译通过;窗口目视需 Windows)
- 变更:重写 `v2/internal/ui/window.go` 布局——右栏:①运行状态+检测状态压一行(Critical 红)②开始/停止用 `smallButton` (小内边距/字号)同一行 ③"事件"文本与"红色=确认摔倒"说明同一行 ④剩余区域 `layoutGallery` 3×3 倒序展示最近 9 张确认截图 ⑤双击缩略图(350ms 内两次点击)经 `layoutViewer` 全屏遮罩弹全图、"关闭"按钮退出。画廊数据源:`Present` 收到含确认事件的 `update` ( `update.Image` 即 render 截图内容)时按 event_id 去重入队(UI 线程外),`layout` 里每项建一次 `paint.ImageOp` 缓存、超 9 张淘汰最老。确认弹窗与图片弹窗共用遮罩 Stack。
- 验证:`GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -mod=vendor ./internal/ui/... ./cmd/ui-spike/...` 退出码 0。未引入新依赖(仅 stdlib `time` ),vendor 不变。本环境无法运行 Gio,布局/双击目视需 Windows。
- 阻塞:窗口目视需 Windows。
- 决策:缩略图与全图复用同一 `ImageOp` (Gio 只上传一次纹理,缩略图 cell 用 `Fit:Contain` 缩放);`gallery` 存指针避免拷贝 `widget.Clickable` ; `viewing` 存 `*galleryItem` 指针,列表重排也稳定。
- 下一步:用户 Windows `go run ./cmd/silver-pose` 目视新布局与画廊、双击看原图;据反馈微调间距/比例后置 DONE。
2026-07-23 21:12:20 +08:00
## 【2026-07-23】T-306/T-307 收尾与 T-308 启动
- T-306( Gio 迁移):用户 Windows 现场跑通(连流/检测/截图),置 DONE。
- T-307(紧凑状态+3×3画廊+双击原图):代码完成并现场生效,置 DONE;其画廊尺寸将被 T-308 的 2×3 覆盖。
- T-308 落 DOING:分三段——A 界面四项(80%窗口/2×3画廊/Esc关弹窗/按钮灰显)、B V2 配置支持结构化摄像头(账号密码,百分号编码,仅未跟踪本地配置)、C Gio 可编辑设置表单(摄像头+常用参数,保存下次启动生效)。用户明确要求内网演示要账号密码框;合规做法=凭证仅入未跟踪 config.local.json,公开示例禁凭证、凭证不进 config_version/日志/事件(V2 已脱敏 ffmpeg 日志、版本已排除 URL)。顺带解决手动 %40 编码痛点。
- 验证:每段 `GOOS=windows CGO_ENABLED=0 go build -mod=vendor` 交叉编译;配置层本地 go test;窗口目视需 Windows。
- 下一步:先做 A。
2026-07-23 21:30:23 +08:00
## 【2026-07-23】T-308 A/B/C 代码完成(交叉编译验证)
- 状态:DOING(三段代码完成 + Windows 交叉编译/本地 go test 通过;窗口目视需 Windows)
- A(界面四项):80% 屏幕开窗(GetSystemMetrics)、画廊 2× 3 且右栏缩小、Esc 关全图弹窗(key focus+filter)、开始/停止按运行状态灰显禁用(gtx.Disabled;硬失败 ReportIssue 复位 running)。`GOOS=windows` 交叉编译通过。
- B( V2 结构化摄像头):`config` 接受 host/port/channel/username/password(仅本地)或 rtsp_url_env, `net/url` 自动百分号编码(修掉手动 %40);新增 `WriteLocalCameraSource` /`WriteLocalEventTuning` ;护栏保留(拒绝整段 url/rtsp_url、凭证不进 config_version、公开示例无凭证测试)。本地 `go test ./internal/config` 通过。
- C(Gio 可编辑设置表单):设置页改为可编辑——摄像头(host/端口/通道/账号/密码掩码/传输/超时/低延迟)与常用参数(关键点/确认窗/水平角/模型置信度/快速下移/膝踝),两个保存按钮写回本地配置;`cmd/silver-pose` 预填(读本地文件)、传 ConfigPath,控制器在每次开始监控时 `config.Load` 重载使保存生效。UI 交叉编译通过、gofmt 干净。
- 阻塞:`cmd/silver-pose` 全量构建需 Windows+CGo(onnxruntime)+Gio;窗口目视/表单交互需 Windows。
- 决策:账号密码按用户要求进设置(内网演示),但仅存未跟踪 config.local.json、UI 掩码、不入日志/版本/事件;source.id 用固定/原值避免把内网 IP 写进 JSONL。设置改动"下次开始监控生效"经控制器重载 config 实现(对齐 V1 草稿→运行快照语义)。
- 下一步:用户 Windows `go run ./cmd/silver-pose` 目视——80% 窗口、2×3 画廊、Esc 关图、按钮灰显、设置页可编辑并保存后重启监控生效。