将「异常项快拍」从复用整页报告归档流程,改造成独立的局部识别路径: 小框拍局部 → 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>
5.5 KiB
5.5 KiB
异常项快拍(局部小框 + 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这一条路径。