Files
kangkang/CLAUDE.md
link2026 b80fae35c9 docs(w2): mark plan tasks 1-7/9 done + sync CLAUDE.md §8 + write W2 retro
- plan: flip 43 checkboxes done across Task 1-7/9; Task 8 (manual speed
  baseline) and Task 10 (this retro) intentionally left open
- CLAUDE.md §8: AI/ ⚠️ partial (AIRuntime/LLMSession/ModelStore/TokenChunk
  done, VLSession/Prompts/ pending); FileVault ; add Debug/DebugAIRunner ;
  drop bold from "W2 当前" and tag W2-W3 row 进行中
- new retros/2026-05-31-w2.md: status table, TBD speed baseline,
  off-plan Symptom/Timeline/ArchiveListView/AppIcon/Swift6 cleanup,
  Swift 6 + Simulator sandbox learnings, W3 prep checklist
2026-05-25 23:36:16 +08:00

13 KiB

康康 —— 工程前提

这是一个 6 周决赛 demo 项目。今天是 2026-05-25,处于 W1末/W2初。 任何 IDE/Claude 会话开始干活前,先读这份文件。


1. 产品定位

  • 名字:康康(对内代号 Kangkang)
  • 形态:iOS 原生 App,SwiftUI + SwiftData
  • 核心卖点:100% 本地推理的个人健康影像档案 + 大白话解读 + 本地 RAG 问答
  • 目标用户:不愿把体检/化验报告交给云端的普通人
  • 明确不做:医疗诊断、剂量推荐、急诊判断、医生预约、社交、广告、内购、数据上云、账号系统

2. 技术栈 / 选型(已锁定,不要再讨论)

选型 备注
UI SwiftUI iOS 17+,用 @Observable / @Model
持久化 SwiftData 见 §5 数据模型
图表 Swift Charts iOS 16+ 原生
AI 运行时 MLX Swift (Apple 官方) 不要建议 Core ML / llama.cpp / Ollama
LLM Qwen3-1.7B 4bit (HF: mlx-community/Qwen3-1.7B-4bit) ~1.0GB,负责文本生成、关键词抽取、趋势解读
VL Qwen2.5-VL-3B-Instruct 4bit (HF: mlx-community/Qwen2.5-VL-3B-Instruct-4bit) ~2.0GB,负责拍照→结构化指标
文档扫描 VisionKit VNDocumentCameraView 不要自己写透视校正
Face ID LocalAuthentication
Live Activity ActivityKit + WidgetExtension demo 杀手锏,真机才能测

不引入:任何云服务 SDK、任何 embedding 模型(RAG 用结构化检索,不用语义)、任何账号系统、任何分析 SDK。


3. AI 链路核心规则

3.1 模块边界(强制)

UI → CaptureService / AskService / TrendService → AIRuntime → MLX
                                                       ↓
                                                  Persistence
  • UI 永远不直接调 AIRuntime。所有 AI 调用必须经过 *Service 层,这样 UI 可以注入 mock、可以预览。
  • AIRuntimeactor 单例,串行化。同一时刻只允许一个推理任务,MLX 共享显存,并发会 OOM。CaptureService 拍照时如果 AskService 正在流式生成,要在队列里排队。
  • *Service 不直接读写 SwiftData 主上下文。要么传入 ModelContext,要么走 ServiceLocator,方便测试。

3.2 VL pipeline(拍一张 = 一条流程)

重要:快拍(1.x) 和 报告归档(2.x) 已经合并成统一 CaptureService,UI 不再有 A1-A3 和 B1-B4 两条独立路径。流程:

拍照 → 写 Vault(加密目录) → VL 推理(要求输出 JSON,含 kind=single|report)
     → 解析容错(失败回退到手动录入,不卡死)
     → 单项走 A2ConfirmView,整份走 B3MetaView
     → 保存到 Indicator/Report + 关联 Asset

VL prompt 必须:

  • 明确要求"只输出 JSON,不要解释"
  • 带 2 个 few-shot 示例(单项 + 多项)
  • 异常状态由 VL 模型基于参考范围直接判断,不要再二次调用 LLM

3.3 RAG(结构化检索,不做 embedding)

两段式调用:

  1. 用 Qwen3-1.7B 抽取意图 + 关键词,输出 JSON {indicators, time_range, intent},~50 token,<1s
  2. SwiftData 按关键词检索 ≤ 10 条记录,拼 ChatRAG prompt,流式生成回答

第 1 步失败时回退到"近 30 天全表扫描",不卡死。 引用回链:回答中 [1][2] 后处理为可点击 Pill,点击跳源记录详情。

3.4 Live Activity

  • VL 推理 / RAG 生成开始时启动 Activity
  • 每 0.5s 通过 AIRuntime.lastDecodeRate 推送 tok/s
  • 推理完成保留 2s 显示"已完成 · 0.8s"再 dismiss
  • 只能真机测,模拟器不显示。W5 末预留时间。

4. 模型分发

  • 模型放 Application Support/Models/,首启动用 URLSession.downloadTask 拉,带断点续传 + 进度条
  • 总体积 ~3GB,WiFi 提示必须有
  • App 在模型未就绪时仍可启动,但所有 AI 入口显示"模型未就绪,前往下载"
  • ModelStore 必须提供旁路接口:允许把模型预拷进沙盒(demo 现场重装时用)

5. 数据模型(SwiftData)

现有 3 个 @Model,要新增 2 个:

// 已有(在 Models/Models.swift)
@Model class Indicator { name, value, unit, range, statusRaw, note, capturedAt }
@Model class Report    { title, typeRaw, reportDate, institution, note, summary, pageCount, createdAt }
@Model class DiaryEntry { content, createdAt }

// 待加字段
// Indicator + report: Report?  反向关系
// Indicator + asset: Asset?    关联原图
// Indicator + pinned: Bool     C2 "关联到趋势" 后置 true,Trends 默认展示 pinned 指标
// Report    + indicators: [Indicator]  @Relationship cascade
// Report    + assets: [Asset]          @Relationship cascade
// DiaryEntry + tags: [String]          VL/LLM 抽取的标签

// 待加 @Model
@Model class Asset {
    var relativePath: String   // 相对 Vault/ 的路径
    var mimeType: String
    var bytes: Int
    var createdAt: Date
}

@Model class ChatTurn {
    var question: String
    var answer: String
    var referencedIndicatorIDs: [String]
    var referencedReportIDs: [String]
    var createdAt: Date
    var decodeRate: Double     // 该轮问答推理速度,Me 页性能展示
}

原图存储: Asset 只存元数据 + 相对路径,真实 JPEG 落在 Application Support/Vault/,目录用 .completeFileProtection(iOS 硬件级加密,不要自己造 AES 轮子)。


6. 安全 / 隐私(已收敛 — 不要扩展)

不做
Application Support/Vault/ 全目录 .completeFileProtection 自实现 AES 加密
SwiftData store 文件 .completeFileProtection
Face ID 启动锁(可选开关,默认关)
永久删除(SwiftData 硬删 + Asset 文件 unlink)
离线运行(自然结果,不用单独做)
截屏黑屏防护(iOS 没有官方 API,不做)
加密 ZIP 导出

唯一的"导出"是 9.4 分享文字摘要(只分享解读文本,不带原图)。


7. 信息架构

TabBar:  [首页]  [+ 记录]  [趋势]  [我的]
            │       │         │       │
            │       │         │       └─ 模型管理 / Face ID / 关于
            │       │         └─ 折线图 + AI 一句话解读
            │       └─ Modal: 选择 拍一张 / 写日记 / 问问看
            └─ 问候 + 今日摘要 + 时间线 + 影像档案入口
  • 3 Tab 不变,中间 + 号是 Sheet
  • AI 问答以 Modal Sheet 形式出现,不占 Tab
  • "问问看"入口除了在 RecordSheet 里,首页摘要卡片下方也有一个常驻入口
  • 历史时间线在首页下半部分,不单独开 Tab

7.1 档案库 C1 / C2 导航(看的一半)

录入流程(拍照→VL→编辑→存)只是"录的一半"。"看的一半"由 C1 列表 + C2 详情承担——这是 demo 的核心看点之一,不能砍。

首页 "我的报告档案" 卡 ──push──► C1 ArchiveListView
                                  │
                                  │ 分类 chip:全部/体检/化验/影像/处方
                                  │ 按 reportDate 年份分组,卡片显示异常 chip
                                  │
                                  └──push──► C2 ReportDetailView
                                              │
                                              ├─ Tab "原图":TabView(.page) 翻页 + 长按保存
                                              ├─ Tab "解读":数字摘要(总/高/低/正常)
                                              │             + AI 整体摘要
                                              │             + 对比上次(同类型上一份 Report diff)
                                              └─ Tab "指标":Indicator 列表,异常优先

C2 底部两个动作:
  ├─ "关联到趋势" ──► 把本报告内未 pinned 的 Indicator 批量 pinned = true,Trends 默认展示
  └─ "重新解读"   ──► CaptureService.reanalyze(report:),重跑 VL 覆盖 summary/indicators

其他进入 C2 的入口:
  • ChatTurn 引用 Pill 点击(referencedReportIDs)
  • 趋势页数据点 tap → 跳到该点来源 Report 的 C2
  • HomeView 时间线点报告类条目

7.2 对比上次("对比上次"=报告对比,已加回)

C2 解读 Tab 底部显示一段 diff 文本,ReportCompareService 计算,不再调 LLM:

  • 找出"同 typeRaw 的上一份 Report"(reportDate < current AND ORDER BY DESC LIMIT 1)
  • 同名 Indicator 配对,数值 diff:Δ 绝对值 + 百分比 + 升/降箭头
  • 标红:跨越参考范围边界(原本正常→偏高,或反过来)
  • 文案模板拼装,不走 LLM,响应即时
  • 若无上一份,该区块隐藏

8. 现有代码状态(2026-05-25)

康康/
├── App/KangkangApp.swift           ✅ SwiftData container 已建
├── RootView.swift              ✅ 3 Tab + RecordSheet 已建
├── Models/Models.swift         ✅ Indicator / Report / DiaryEntry,缺 Asset / ChatTurn
├── DesignSystem/               ✅ Tokens + Components,沿用
└── Features/
    ├── Home/                   ✅ HomeView 静态 UI,数据未接
    ├── Quick/A1-A3             🔧 待合并进 UnifiedCaptureFlow
    ├── Archive/B1-B4           🔧 B1 砍,B2 改用 VisionKit DocumentCamera
    ├── Record/RecordSheet      ✅ 入口选择 UI
    ├── Trends/                 ❌ 只有 placeholder
    └── Me/                     ❌ 只有 placeholder

待建:
├── AI/                         ⚠️ AIRuntime + LLMSession + ModelStore + TokenChunk ✅;VLSession + Prompts/ ❌
├── Debug/DebugAIRunner.swift   ✅ DEBUG-only AI 自检入口
├── Services/                   ❌ CaptureService, AskService, TrendService, ReportCompareService
├── Persistence/FileVault.swift ✅ 原图加密目录管理
├── Security/AppLock.swift      ❌ Face ID 启动锁
├── Features/Ask/               ❌ AskSheet (RAG 问答 UI)
├── Features/Archive/
│   ├── ArchiveListView         ❌ C1 档案列表(分类 chip + 年份分组)
│   └── ReportDetailView        ❌ C2 报告详情(三 Tab:原图/解读/指标 + 对比上次)
├── Features/Capture/
│   └── UnifiedCaptureFlow      ❌ 替代 QuickCaptureFlow,状态机驱动 A1→VL→A2/B3
├── Features/Onboarding/        ❌ 首启动隐私承诺 + 模型下载
└── LiveActivity/               ❌ WidgetExtension target

9. 设计系统约束

  • 不要新增颜色 token。所有颜色走 Tj.Palette.* (sand / paper / ink / brick / leaf / line / text / text3)
  • 不要新增字体大小。走 Font.tjTitle() / tjH2() / tjSerifBody() / 系统 size
  • 圆角走 Tj.Radius.*,卡片走 .tjCard() modifier
  • 按钮走 TjPrimaryButton / TjGhostButton

新加 View 时先看 DesignSystem/Components.swift,有现成的不要复刻。


10. 不能跨越的红线

写代码前必读:

  1. 不引入云服务——任何 SDK 都不行,包括崩溃上报、分析、灰度
  2. 不自己实现密码学——.completeFileProtection 已经够
  3. UI 不直接调 AIRuntime——必须经过 Service
  4. AIRuntime 必须 actor 化——禁止 class + lock
  5. VL/LLM prompt 必须有 few-shot + 失败回退——不能让用户卡在 AI 错误屏
  6. 新功能必须问"清单里有吗"——清单外的功能(用药提醒、多 profile、暗黑模式、iCloud 同步……)默认不做,要做必须先讨论。例外:报告对比(16.1)已加回,见 §7.2
  7. 不要在 6 周里重构现有 Tab/RecordSheet 骨架——增量加东西,不要推倒重来
  8. 报告详情(C2)与归档元信息编辑(B3)是两个 View——B3 是 draft 编辑(写),C2 是 detail 浏览(读),不要合并复用主框架

11. 6 周时间表

周次 必交付
W1 末 / W2 当前 项目结构、MLX 跑通 Qwen3-1.7B、首个 token 在设备吐出
W2-W3 AIRuntime + LLMSession,文字日记 + 基础 RAG 问答(打字机效果)(W2 进行中)
W3-W4 VLSession + 统一拍照流程(单项 + 整份)、Asset / FileVault
W4 末 C1 ArchiveListView(分类 chip + 年份分组,接 @Query)
W4-W5 趋势(Swift Charts + AI 解读)、C2 ReportDetailView(三 Tab + 重新解读)
W5 中 ReportCompareService + C2 解读 Tab "对比上次" 区块
W5 末 Face ID、永久删除、首页时间线接入真数据、Live Activity(真机)
W6 模型管理页、首启动下载流程、UI polish、demo 视频、PPT

P0 新增项(从 C 区视觉稿补回):C1 档案列表、C2 报告详情三 Tab、对比上次、关联到趋势、重新解读

P1(必须做完):Live Activity、分享文字摘要(9.4)、首启动隐私承诺页

P2(余力做):—— 任何 P2/P3 都暂时不做,清单里 11/12/13/14/15/17/18/19 全部 deferred(注意:16.1 报告对比已升 P0)

砍 P1 决策顺序(任何一周延期触发):Live Activity → Onboarding 简化 → 分享摘要 → 模型管理页 polish。绝不动 C1/C2/对比上次——视觉稿都做了,demo PPT 也要展示,这是核心卖点之一。


12. 评委 PPT 卖点排序(写代码时记住为什么这么做)

  1. 影像档案系统(统一 VL 拍照 + 归档) — 核心创意
  2. 100% 本地 + SME2 加速 — 技术亮点
  3. 本地 RAG 长期记忆 — 端侧不可替代性
  4. 隐私三件套(系统级加密 + Face ID + 永久删除) — 信任建立
  5. AI 趋势解读 — 长期价值
  6. Live Activity 实时 tok/s — 现场记忆点

每写一个功能,问自己:这条提升了上面哪一项?如果都没有,就别做。