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