Files
kangkang/docs/superpowers/specs/2026-05-31-abnormal-quick-capture-design.md
link2026 adb589af16 feat(quick): 异常项快拍改为局部小框 + VL 识别
将「异常项快拍」从复用整页报告归档流程,改造成独立的局部识别路径:
小框拍局部 → Qwen-VL 只抽 indicators → 用户确认逐项编辑 → 存成独立
Indicator(不建 Report、不留原图,与「记录指标」统一落库)。

- RegionCameraView: AVFoundation 实时预览 + 居中小框,快门后按
  metadataOutputRectConverted 裁剪到框内区域;含裁剪纯函数与权限态。
- VLPrompts.regionExtraction(): 局部识别 prompt,严格 JSON 只要 indicators。
- CaptureService.recognizeRegion(): 临时文件推理后即删,不写 Vault;
  新增 parseIndicatorsJSON / extractBalancedJSON 解析容错。
- QuickRegionConfirmView: 异常项高亮置顶、默认勾选,可编辑/增删/选纳入。
- QuickRegionCaptureFlow: 状态机 idle→analyzing→confirm,30s 超时回退手动。
- RootView: .quick 路由改指向新流程(.archive 仍走 UnifiedCaptureFlow)。
- 删除 5 个无引用的旧 mockup(A1/A2/A3/SmartFramer/QuickCaptureFlow)。

模拟器无相机退化为相册整图;小框裁剪坐标需真机验证。
设计见 docs/superpowers/specs/2026-05-31-abnormal-quick-capture-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:12:36 +08:00

5.5 KiB
Raw Blame History

异常项快拍(局部小框 + VL 识别)— 设计

日期:2026-05-31 · 分支:feat/w2-ai-foundation 需求:异常项快拍要拍摄局部,采用小框拍局部,用 Qwen-VL 识别被拍区域→检测项目结构化数据; 存储前用户确认;最后只存参数和异常值,可和「记录指标」统一保存。

1. 现状与缺口

  • RecordSheet.quick(标题「异常项快拍」)已存在,但 RootView.recordFlow(.quick) 当前直接路由到 UnifiedCaptureFlow —— 与「体检报告归档」(.archive)完全一样,走的是整页文档扫描,没有局部小框, 也会把整份当 Report + 原图存档。这与需求(局部 / 只存数值 / 不留图 / 并入指标)不符。
  • Features/Quick/A1ViewfinderView / A2ConfirmView / SmartFramer / QuickCaptureFlow / A3BatchView 均为早期 mockup,全树无外部引用(纯孤儿)。A1ViewfinderView 有小框引导和 AVFoundation 预览,但快门未接线(capturePhoto() 从不触发)、不裁剪

2. 目标流程

RecordSheet(.quick)
  → QuickRegionCaptureFlow(状态机)
       ├ 真机: RegionCameraView(实时预览 + 居中小框 + 快门 → 裁剪到小框的 UIImage)
       └ 模拟器: PhotoPickerSheet(无小框,整图送 VL)
  → CaptureService.recognizeRegion(imageData:) ──actor──► AIRuntime.analyzeReport ─► VLSession
                                                              ↑ VLPrompts.regionExtraction()
  → QuickRegionConfirmView(逐项可编辑 + 勾选纳入 + 测量时间;异常项高亮置顶)
  → 保存:勾选项各插入一条独立 Indicator(无 Report、无 Asset);ctx.save()

红线遵守:UI 不直接调 AIRuntime,经 CaptureService(§3.1);AIRuntime actor 串行(复用既有 VL 路径, 不新增并发);无新增 @Model,不触发 SwiftData 迁移。

3. 组件

3.1 RegionCameraView.swift(新建,取代 A1ViewfinderView)

  • AVFoundation 实时预览,videoGravity = .resizeAspectFill
  • 居中局部小框(屏宽 ~84% × 高 ~140pt,虚线框 + 半透明遮罩挖空),提示「把异常项放进框里 · 对准一两行」。
  • 底部快门键、顶部取消键。
  • 拍照后:previewLayer.metadataOutputRectConverted(fromLayerRect: 小框rect) → 归一化裁剪 rect; 先把照片方向 bake 成 .up,再按归一化 rect 裁 CGImage,回调裁剪后的 UIImage
  • 相机权限:被拒时显示「去设置开启相机」态。
  • 纯函数 RegionImageCropper.crop(_:normalizedRect:) + UIImage.normalizedUp(),与 View 解耦便于推理/复用。

3.2 VLPrompts.regionExtraction()(加进 VLPrompts.swift)

  • 说明「这是报告的局部照片,可能只有一两行指标」。
  • 严格 JSON,只要 {"indicators":[{name,value,unit,range,status}]},不要报告元信息。
  • status 由 value 与 range 自判;range 保留原文;不发明指标,看不清整行跳过。
  • 2 个 few-shot(单行 / 两行)。

3.3 CaptureService.recognizeRegion(imageData: Data)(加进 CaptureService.swift)

  • 把 JPEG 写临时文件(NSTemporaryDirectory,.completeFileProtection),defer 删除。
  • prepareVL()analyzeReport(imageURLs:[temp], prompt: regionExtraction())
  • 新增 parseIndicatorsJSON(_:):复用 extractJSONObject + parseIndicator,抽出 indicators 数组, 返回 [ParsedReport.ParsedIndicator]。失败抛 CaptureError(UI 回退手动录入)。

3.4 QuickRegionCaptureFlow.swift(新建,状态机)

  • Phase { idle, analyzing(UIImage), confirm(items, warning) }
  • 裁剪图 → analyzing → Task:JPEG 编码 → recognizeRegion → confirm。
  • 30s 超时哨兵 → confirm(空 + warning);各类错误 → confirm(空 + warning)。
  • 无 Vault 资产需清理(临时文件已在 service 内删除);取消即关闭。

3.5 QuickRegionConfirmView.swift(新建,确认 UI)

  • 头部「核对异常项 · 只存数值,不保留照片」+ 内存中的裁剪缩略图(仅核对用,不持久化)。
  • 测量时间 DatePicker(默认 now)。
  • 指标列表:逐项可编辑(name/value/unit/range/status)+ 勾选「纳入保存」。 异常(high/low)项红色高亮、置顶、默认勾选;正常项默认也勾选(用户可取消),体现「只存参数和异常值」由用户掌控。
  • 「加一项」手动补充(VL 空结果回退)。
  • 底栏:取消 / 保存到记录(N 项)。

3.6 RootView 路由

  • .quick → QuickRegionCaptureFlow(onClose:)(原为 UnifiedCaptureFlow)。

3.7 清理

  • 删除 5 个孤儿 mockup:A1ViewfinderView / A2ConfirmView / SmartFramer / QuickCaptureFlow / A3BatchView。

4. 数据落库

  • 每个勾选项 → 一条 Indicator(name,value,unit,range,status,capturedAt,note=nil,pinned=false,seriesKey=nil)
  • 不建 Report,不存 Asset(原图丢弃)→ 符合「最后只存参数和异常值」。
  • 与「记录指标」自由输入路径落库一致(同一 Indicator 表,进记录时间线;不带 seriesKey 不强制进趋势)。

5. 取舍

  • 裁剪 vs 整图:需求明确「小框拍局部 / 识别被拍区域」,故真机裁剪到小框(也提升小目标 VL 准确率、降 token)。 模拟器无实时小框 → 退化为整图(与既有 UnifiedCaptureFlow 模拟器退化一致)。
  • 不留图:遵循「只存参数和异常值」与隐私基线,临时文件推理后即删,不写 Vault、不建 Asset。
  • 正常项是否保存:默认全部勾选、异常项高亮,正常项可手动取消 —— 不静默丢弃用户可能想留的读数。
  • 不动既有归档流程:UnifiedCaptureFlow / B3 / C2 不变;本功能只重写 .quick 这一条路径。