diff --git a/docs/requirements-specification-v2.1.md b/docs/requirements-specification-v2.1.md new file mode 100644 index 0000000..7746857 --- /dev/null +++ b/docs/requirements-specification-v2.1.md @@ -0,0 +1,1937 @@ +# 错题集应用 - 需求规格说明书 v2.1 +> 生产就绪版本 | 修订日期:2026-02-09 + +## 版本历史 +| 版本 | 日期 | 主要变更 | 备注 | +|------|------|---------|------| +| v1.0 | 2026-02-09 | 初始版本 | 基础功能设计 | +| v2.0 | 2026-02-09 | 核心重构 | 引入学习效率闭环、间隔重复复习、练习模式、结构化错因 | +| v2.1 | 2026-02-09 | 工程化完善 | **数据一致性规则、离线架构清晰化、FSRS替代SM-2、技术栈更新至2026最新、MVP降级路径、交互细节优化** | + +--- + +## 0. v2.1 核心改进说明 🎯 + +### 从 v2.0 到 v2.1 的关键优化 + +**v2.0 的不足**(专家评审发现): +- ⚠️ wrong_count 与 attempts 的一致性未定义 +- ⚠️ 离线能力与 PostgreSQL FTS 存在架构矛盾 +- ⚠️ 多题切割作为 P0 对 4 周 MVP 风险过高 +- ⚠️ SM-2 算法缺少边界条件和防刷机制 +- ⚠️ 学习 KPI 统计口径不够严谨 + +**v2.1 的核心改进**: +- ✅ **数据驱动规则**:wrong_count 完全由 attempts 聚合,避免不一致 +- ✅ **离线架构清晰化**:SQLite FTS5 + Event Sourcing 同步策略 +- ✅ **FSRS 替代 SM-2**:使用 dart-fsrs v2.0.0,更科学的间隔重复 +- ✅ **多题切割拆分**:P0 手动框选(稳定)+ P1 自动切割(优化) +- ✅ **KPI 口径严格化**:cohort 定义、二刷定义、重错定义明确 +- ✅ **技术栈更新**:PP-OCRv5、PP-FormulaNet_plus-M、pg_jieba 等 2026 最新版本 +- ✅ **交互细节优化**:防作弊、防挫败、拼图式调整 +- ✅ **MVP 降级路径**:每个功能明确最低可用标准和降级方案 + +--- + +## 1. 项目概述 + +### 1.1 项目背景 + +(与 v2.0 相同,略) + +### 1.2 项目目标 + +**核心目标**(按优先级排序): +1. 🎯 **提高学习效率**:通过科学复习机制(FSRS),使学生用更少时间掌握更多知识 +2. 📈 **降低重错率**:帮助学生从"反复做错同一道题"变为"一次掌握不再错" +3. ⏱️ **减少录入成本**:拍照即录入,10 秒完成一道题 +4. 🎨 **优化复习体验**:自动推送复习清单,无需主动检索 + +**关键成功指标**(MVP 验收,v2.1 口径严格化): +- **重错率下降**:第 1 周录入的错题 cohort,到第 4 周时 attempts(wrong ≥3 且不同日期) 占比下降 ≥30% +- **7 日复习完成率** ≥60%:按 review_sessions 聚合统计 +- **二刷正确率** ≥70%:首次 wrong 后、下次 review_mode='daily_review' 的 attempt 为 correct 的比例 +- **单题录入时间**中位数 ≤15 秒(快速模式 ≤10 秒) +- **用户 7 日留存率** ≥40% + +### 1.3 目标用户 + +**MVP 阶段(v2.1)**: +- ✅ **初高中学生**(主力用户,单人自用为主) + - 场景:日常作业错题、周测月考错题、期末复习 + - 痛点:题量大、易忘记、不知道怎么复习 + - **设备**:iOS 为主(后续扩展 Android) + +**后续版本**: +- 🔜 **教师**(v2.2):班级错题库、共性错题分析 +- 🔜 **家长**(v2.3):学习报告、辅导建议 + +--- + +## 2. 核心功能设计(v2.1 工程化优化) + +### 2.0 **学习效率闭环架构图** + +``` +┌──────────────────────────────────────────────────────────────┐ +│ 学习效率提升闭环 │ +└──────────────────────────────────────────────────────────────┘ + +Step 1: 错题录入 + ├─ 拍照/批量导入 + ├─ OCR 识别(文本+公式,PP-OCRv5 + PP-FormulaNet_plus-M) + ├─ 【v2.1调整】P0 手动框选切题 / P1 自动切割 ⭐ + ├─ 快速录入模式(仅学科+图片) + ├─ 【v2.1新增】图片指纹(pHash)去重 ⭐ + └─ AI 推荐知识点标签 + ↓ +Step 2: 智能复习调度(FSRS 间隔重复系统)⭐核心 + ├─ 【v2.1升级】使用 FSRS 算法(dart-fsrs v2.0.0) + ├─ 每天自动生成"今日复习清单"(从 review_sessions 生成) + ├─ 推送通知:"今日待复习 10 题" + └─ 优先级排序(临近考试→高频错题→即将遗忘) + ↓ +Step 3: 主动练习模式 ⭐核心 + ├─ 显示题目(隐藏答案/笔记) + ├─ 学生草稿纸做题 + ├─ 【v2.1优化】三按钮反馈 + 防作弊机制 ⭐ + │ - 🟢 会做:停留3秒或输入关键数值 + │ - 🔴 做错:记录 attempt + │ - ⚪ 不会:先显示提示/思路(防挫败) + ├─ 查看答案+之前的错误分析 + └─ 【v2.1规则】记录 Attempt → 自动更新 wrong_count ⭐ + ↓ +Step 4: 错因分析与矫正 ⭐ + ├─ 做错后强制选错因标签(审题/计算/概念...) + ├─ 统计"最常犯的错因 TOP3" + ├─ 针对性训练建议(如审题错→题干关键词圈画训练) + └─ 错因趋势图(本周计算错误5次→上周3次) + ↓ +Step 5: 效果评估与强化 + ├─ 【v2.1规则】FSRS 自动调整间隔 + 掌握判定 + ├─ 【v2.1口径】复习完成率统计(按 review_sessions) + ├─ 【v2.1口径】二刷正确率(间隔≥24小时) + └─ 学习报告:本周掌握5题,重错率下降20% + ↓ +Step 6: 辅助功能 + ├─ 打印错题(用于线下二刷) + ├─ 组卷模考(定时+评分) + ├─ 薄弱知识点分析 + ├─ 【v2.1新增】离线能力(SQLite FTS5 + Event Sourcing) ⭐ + └─ 【v2.1预留】游戏化(积分/勋章/连续打卡) +``` + +--- + +## 2.1 错题录入 + +### 2.1.1 拍照录入(v2.1 多题切割策略调整) +**优先级**: P0 + +**功能描述**: +用户使用相机拍摄错题照片,系统自动识别并录入。 + +**具体要求**: +- 支持实时预览 +- 支持闪光灯控制 +- 自动检测纸张边缘并透视矫正 +- 拍照后可手动调整裁剪范围 + +**【v2.1 重大调整】多题切割策略分阶段实施** ⭐: + +#### P0 方案(MVP 必达,4周内):手动框选 + 智能辅助 +- 拍照后,系统显示照片 +- **自动检测题号位置**(正则匹配 "1."、"2."、"(1)"、"一、" 等),在图上标注虚线提示 +- 用户可: + - 接受系统建议的切割线 + - 手动拖动切割线调整(**拼图式调整**): + - 拖动某个边界时,相邻题目边界自动吸附(类似调整 Excel 行高) + - 避免出现"两题之间的空白地带"被遗漏 + - 添加/删除切割线 +- 批量生成多道错题记录 +- **降级方案**:系统不做任何自动检测,完全手动框选 + +**验收标准(P0)**: +- 单题拍照流程 ≤5 秒 +- 手动框选 2-5 题总时长 ≤30 秒 +- 题号自动检测召回率 ≥60%(作为辅助,不强制) + +#### P1 方案(Week 5-8):自动切割 + 手动修正 +- 使用 **PP-DocLayoutV3**(2026年1月最新版)进行版面分析 +- 结合题号正则检测,自动生成题目边界 +- 预览界面显示自动切割结果(彩色边框),用户可: + - 直接确认(80%准确率场景) + - 手动调整(20%失败场景) +- 批量生成多道错题记录 + +**验收标准(P1)**: +- 自动切割准确率(2-5题/页,正式试卷) ≥80% +- 切割失败时,可降级到 P0 手动模式 + +--- + +### 2.1.2 批量导入 +**优先级**: P0 + +**功能描述**: +从相册批量选择错题照片,系统异步处理。 + +**具体要求**: +- 支持一次选择最多 **20 张**图片(v2.0 已调整,避免移动端 OOM) +- 显示实时处理进度 +- 支持后台处理(切到其他 App 后继续) +- 处理失败可重试单张 + +**验收标准**: +- 批量导入 10 张图片,端到端时间 ≤2 分钟(WiFi 环境) +- 处理进度更新延迟 ≤2 秒 +- 单张失败不影响其他图片继续处理 + +--- + +### 2.1.3 快速录入模式 +**优先级**: P0 + +(与 v2.0 相同,略) + +--- + +### 2.1.4 **【v2.1 新增】图片指纹去重** ⭐ +**优先级**: P1 + +**功能描述**: +在 OCR 文本相似度匹配之前,先用图片指纹快速排除完全重复的照片。 + +**技术方案**: +1. **第一道筛选:pHash(感知哈希)** + - 上传图片后,计算 64-bit pHash 指纹 + - 与已有图片的 pHash 计算海明距离 + - 如果海明距离 ≤5,判定为"相同图片" + - 好处: + - 秒级识别(无需 OCR) + - 无视 OCR 错误 + - 能识别轻微裁剪/压缩的同一张照片 +2. **第二道筛选:OCR 文本相似度**(v2.0 已有) + - 编辑距离或余弦相似度 ≥85% + - 识别"换数字的变式题" + +**验收标准**: +- pHash 计算时间 ≤100ms +- 重复照片召回率 ≥95%(测试集:100 张中 20 张重复) +- 变式题召回率 ≥80%(通过 OCR 文本匹配) + +--- + +## 2.2 图片处理 + +### 2.2.1 OCR 识别(v2.1 技术栈更新) +**优先级**: P0 + +**功能描述**: +识别题目文字和公式,用于检索、去重、自动标签。 + +**【v2.1 技术栈更新】** ⭐: + +**文本 OCR**: +- **PP-OCRv5**(2025年5月发布,最新版) + - 相比 v4 端到端准确率提升 13% + - 单模型支持简体中文、繁体中文、拼音、英文、日文 + - i7 CPU:2.2-3.4秒,T4 GPU:约1秒 + - 模型大小:约 70M + +**公式 OCR**: +- **PP-FormulaNet_plus-M**(推荐配置) + - 中文公式 BLEU:89.76%(远超要求的80%) + - 英文公式 BLEU:91.45% + - GPU 推理时间:约 1 秒 + - 模型大小:592MB + - 输出:LaTeX 源码 + +**版面分析**(P1): +- **PP-DocLayoutV3**(2026年1月发布,用于多题切割) + - 支持非平面文档(倾斜、弯曲) + - 单次前向传播确定逻辑阅读顺序 + +**具体要求**(细化): +- **中文印刷体**:准确率 ≥95%(字级别) +- **数学公式**:准确率 ≥80%(能识别基础公式用于检索,不承诺完美 LaTeX 输出) +- **英文文本**:准确率 ≥93% +- **混合图文**:能区分文字区和图形区 +- **手写文字**:识别率低于印刷体,不作为核心指标 + +**OCR 结果用途**:检索、去重、AI 标签推荐,**不作为严肃排版输出** + +**验收标准**: +- 端到端 OCR 处理时间:WiFi 环境 ≤5 秒,4G 环境 ≤8 秒 +- 测试集(含公式的真实试卷 50 张):中文文本准确率 ≥95% + +--- + +### 2.2.2 手写去除 +**优先级**: P1(与 v2.0 相同) + +**【v2.1 补充】RemoveHandwriting.com API 定价** ⭐: + +| 套餐 | 价格 | Credits | 单价 | 有效期 | +|---|---|---|---|---| +| Starter | $99 | 500 | $0.198/张 | 3个月 | +| Popular | $349 | 5,000 | $0.07/张 | 6个月 | +| Business | $749 | 15,000 | $0.05/张 | 1年 | +| Enterprise | $1,999 | 50,000 | $0.04/张 | 1年 | + +**成本评估**(供决策参考): +- 假设月活 1000 人,每人每月上传 50 张图 = 月处理量 50,000 张 +- 使用 Enterprise 套餐,月成本约 $2,000(约 ¥14,500),单张成本 ¥0.29 +- 对于收费应用是可接受的运营成本;对于免费应用则偏高 + +**推荐策略**: +- MVP(P0):仅使用 OpenCV HSV 方案 + 手动橡皮擦工具,零成本 +- P1 升级:集成 RemoveHandwriting API 作为"高级去手写"功能,按需调用 +- 降级逻辑:API 调用失败时 fallback 到 HSV 方案;HSV 也无法处理时保留原图 + +--- + +## 2.3 **【v2.1 核心升级】FSRS 间隔重复系统** ⭐ +**优先级**: P0(MVP 必须) + +**功能描述**: +基于 FSRS(Free Spaced Repetition Scheduler)科学安排复习,显著提高学习效率。 + +### 2.3.1 为什么从 SM-2 升级到 FSRS + +**FSRS vs SM-2 对比**: + +| 维度 | SM-2(v2.0方案) | FSRS(v2.1方案) | +|---|---|---| +| 发布年份 | 1990 | 2022+ | +| 参数化 | 固定公式 | 21个可学习参数 | +| 个性化 | 无 | 根据用户数据自适应 | +| 预测准确性 | 基准线 | 显著优于 SM-2 和 SM-17 | +| 遗忘率估计 | 无 | 可实时计算记忆保持率 | +| 学术支撑 | 经典但过时 | KDD 2022 论文 | +| 开源生态 | 广泛但简单 | 丰富多语言实现 | +| **Flutter 实现** | 需自行实现 | **dart-fsrs v2.0.0**(官方维护) | + +**关键优势**: +- Anki 已采纳 FSRS 作为内置算法替代 SM-2 +- SuperMemopedia 报道:"SuperMemo dethroned by FSRS" +- FSRS-6 在交叉对比和 log loss 上均优于 SM-17 + +--- + +### 2.3.2 复习调度算法(FSRS v4.5) + +**技术实现**: +- 使用 **dart-fsrs** 包(pub.dev/packages/fsrs) + - 版本:v2.0.0,实现 FSRS v4.5 调度器 + - MIT 许可证,由 Open Spaced Repetition 官方维护 + - 支持 JSON 序列化(Card.toMap/fromMap),便于 SQLite 存储 + - 核心 API:`Scheduler.reviewCard(card, rating)` 返回更新后的 Card 和 ReviewLog + +**数据模型映射**: + +每道题维护以下字段(扩展 WrongQuestion 表): + +| 需求字段 | FSRS Card 字段 | 说明 | +|---|---|---| +| next_review_at | card.due | 下次复习时间(UTC) | +| review_interval | card.scheduledDays | 当前间隔天数 | +| stability | card.stability | 记忆稳定性(FSRS 特有) | +| difficulty | card.difficulty | 难度系数(FSRS 替代 ease_factor) | +| consecutive_correct | 自行维护 | FSRS 内部无此字段,需在 attempts 表中计算 | + +**核心逻辑**: +```dart +import 'package:fsrs/fsrs.dart'; + +// 1. 初始化卡片 +Card createNewCard() { + return Card.newCard(); // FSRS 默认参数 +} + +// 2. 复习时更新 +SchedulingCards reviewCard(Card card, Rating rating) { + final scheduler = Scheduler(); + return scheduler.reviewCard(card, rating); +} + +// Rating 枚举: +// - Rating.again (完全不会) +// - Rating.hard (做错了) +// - Rating.good (会做/做对了) +// - Rating.easy (非常简单,FSRS支持但可选) +``` + +**【v2.1 新增】边界条件与防刷机制** ⭐: + +1. **difficulty 上下限**: + ```dart + // 在 dart-fsrs 中配置 + final params = Parameters( + enable_short_term: false, // 关闭短期记忆模式(更适合长期学习) + ); + ``` + - FSRS 的 difficulty 范围已内置约束,无需手动限制 + +2. **review_interval 上限**: + ```dart + final params = Parameters( + maximum_interval: 90, // 最大间隔 90 天(中学阶段足够) + ); + ``` + +3. **防止同一天刷两次算连续正确**: + ```sql + -- 在 attempts 表查询时过滤 + SELECT * FROM attempts + WHERE question_id = ? + AND result = 'correct' + AND DATE(attempt_at) != DATE(?) -- 排除同一天的记录 + ORDER BY attempt_at DESC + LIMIT 2; + ``` + +4. **"跳过(稍后复习)"不应更新间隔**: + - 只记录 session 状态,不调用 `Scheduler.reviewCard` + - 题目的 next_review_at 保持不变 + +--- + +### 2.3.3 今日复习清单 + +**功能**: +- 每天生成待复习题目(`next_review_at ≤ 今天`) +- 按优先级排序: + 1. 距离考试最近的题目(如果用户设置了"期末考试倒计时") + 2. 高频错题(wrong_count ≥ 3) + 3. 其他到期题目 +- 首页醒目入口:"📚 今日待复习 15题" +- 点击进入复习模式(见 2.4) + +**智能提醒**: +- 推送通知:"今天有 10 道数学错题等你复习" +- 提醒时间可配置(默认晚上 7 点) + +--- + +### 2.3.4 掌握判定(与 FSRS 对齐) + +**【v2.1 调整】结合 FSRS 和业务需求** ⭐: + +**FSRS 原生掌握标准**: +- `card.state == State.Review` 且 `retrievability ≥ 0.9` 且 `card.scheduledDays ≥ 7` + +**业务需求掌握标准**(保留 v2.0 定义): +- ✅ **真正掌握**:连续 2 次答对,且两次间隔 ≥3 天(从 attempts 表计算) + +**最终判定逻辑**(两者结合): +```python +def is_mastered(question): + # 1. 检查 FSRS 状态 + fsrs_mastered = ( + question.card_state == 'Review' and + question.retrievability >= 0.9 and + question.scheduled_days >= 7 + ) + + # 2. 检查业务规则(从 attempts 聚合) + recent_attempts = get_recent_correct_attempts(question.id, limit=2) + business_mastered = ( + len(recent_attempts) >= 2 and + (recent_attempts[0].attempt_at - recent_attempts[1].attempt_at).days >= 3 + ) + + # 3. 两者都满足才标记掌握 + return fsrs_mastered and business_mastered +``` + +**验收标准**: +- 今日复习清单生成时间 <500ms +- 推送通知准时率 ≥95% +- 用户 7 日复习完成率 ≥60% + +--- + +## 2.4 **练习/自测模式(v2.1 交互优化)** ⭐ +**优先级**: P0 + +**功能描述**: +遮挡答案重做题目,记录效果,驱动复习调度。 + +### 2.4.1 快速复习模式(v2.1 防作弊 + 防挫败) + +**流程**: +1. 显示题目图片(默认干净版,可切换原图) +2. **隐藏**答案、解析、笔记 +3. 学生草稿纸做题(或心算) +4. **【v2.1 优化】三按钮反馈 + 防作弊 + 防挫败** ⭐: + + **🟢 会做/做对了(防作弊机制)**: + - **方案 A**(强制停留):点击后按钮变灰,3 秒后才能查看答案 + - **方案 B**(输入关键数值):弹窗要求输入答案的关键数值或公式(选填) + - 例如:数学题输入最终答案数字 + - 英文题输入关键单词 + - 不强制,但增加认知投入 + - **推荐**:MVP 使用方案 A(实现简单),P1 可选方案 B + + **🔴 做错了**: + - 直接记录 attempt(result='wrong') + - 弹出错因选择面板(见 2.5) + + **⚪ 完全不会(防挫败机制)**: + - **不直接显示答案**,而是先显示: + - 💡 **提示/思路**(如果有 AI 分析生成的 Hint) + - 📖 **相关知识点**(链接到知识点卡片) + - 🔗 **类似题**(如果有) + - 学生可选择: + - "看完提示再做" → 返回步骤 3 + - "仍然不会" → 显示完整答案 + 记录 attempt(result='unknown') + - **好处**:提供"脚手架",降低学生的畏难情绪 + +5. 查看答案/解析后,显示: + - 正确答案(`correct_answer` 字段) + - 之前的错误分析(`error_reason`) + - 个人笔记(`my_notes`) +6. 如果答错/不会,强制选择错因标签(见 2.5) +7. **【v2.1 数据驱动规则】记录 Attempt → 自动更新 wrong_count** ⭐(见 2.4.3) +8. 更新 `next_review_at` 和相关 FSRS 字段 +9. 继续下一题 + +**交互优化**: +- 长按题目查看原图 +- 左滑查看上一题的答案(如果已做过) +- 右滑跳过当前题(标记为"稍后复习",不调用 FSRS 更新) + +--- + +### 2.4.2 组卷模考模式 +**优先级**: P1 + +(与 v2.0 相同,略) + +--- + +### 2.4.3 **【v2.1 核心规则】数据一致性保障** ⭐ + +**问题**(v2.0 存在的风险): +- `wrong_count`(累加字段)和 `attempts`(每次记录)可能不一致 +- 场景:合并题、离线多端同步、删除 attempt、补录等 +- 结果:后期一定出现数据不一致 + +**【v2.1 解决方案】wrong_count 完全由 attempts 驱动** ⭐: + +**核心规则**: +> **任何导致题目"对/错/不会"的行为都必须产生 attempt;wrong_count 不再手动更新,而是由 attempts 聚合计算。** + +**实施细节**: + +1. **写入规则**(应用层强制执行): + ```python + # ❌ 禁止直接更新 wrong_count + # UPDATE wrong_questions SET wrong_count = wrong_count + 1 WHERE id = ? + + # ✅ 正确做法:先写 attempt + INSERT INTO attempts (question_id, user_id, result, review_mode, attempt_at) + VALUES (?, ?, 'wrong', 'daily_review', NOW()); + + # ✅ wrong_count 在查询时聚合(或通过触发器/缓存字段维护) + ``` + +2. **读取规则**(两种方案): + + **方案 A**(推荐):展示时实时聚合 + ```sql + SELECT + q.*, + COUNT(CASE WHEN a.result = 'wrong' THEN 1 END) AS wrong_count, + COUNT(CASE WHEN a.result = 'correct' THEN 1 END) AS correct_count + FROM wrong_questions q + LEFT JOIN attempts a ON a.question_id = q.id + WHERE q.user_id = ? + GROUP BY q.id; + ``` + + **方案 B**:保留 `wrong_count` 字段作为缓存,通过数据库触发器维护 + ```sql + -- PostgreSQL 触发器示例 + CREATE OR REPLACE FUNCTION update_wrong_count() + RETURNS TRIGGER AS $$ + BEGIN + IF NEW.result = 'wrong' THEN + UPDATE wrong_questions + SET wrong_count = wrong_count + 1 + WHERE id = NEW.question_id; + END IF; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + CREATE TRIGGER attempt_insert_trigger + AFTER INSERT ON attempts + FOR EACH ROW EXECUTE FUNCTION update_wrong_count(); + ``` + + **MVP 推荐**:使用方案 A(简单,无触发器复杂度) + **后续优化**:如果性能成为瓶颈,引入方案 B(缓存 + 触发器) + +3. **手动"再次错误"按钮** ⭐: + - v2.0 允许用户点击"再次错误"按钮 + - v2.1 保留此功能,但本质是: + ```python + # 写入一条 attempt + INSERT INTO attempts (question_id, user_id, result, review_mode, attempt_at) + VALUES (?, ?, 'wrong', 'manual', NOW()); + ``` + - `review_mode = 'manual'` 标记为手动操作,与 `daily_review` 区分 + +4. **数据修复任务**(运维工具): + ```python + # 定期执行(或手动触发) + def rebuild_wrong_counts(): + for question in get_all_questions(): + correct_count = count_attempts(question.id, result='wrong') + question.wrong_count = correct_count + db.save(question) + ``` + +**验收标准**: +- 新增 attempt 后,wrong_count 在 1 秒内更新(如果使用触发器)或查询时正确聚合 +- 所有"做错"操作必须产生 attempt 记录(代码审查 + 单元测试) +- 提供 `rebuild_wrong_counts` 工具函数 + +--- + +## 2.5 **结构化错因体系** ⭐ +**优先级**: P1(强烈建议 MVP 包含) + +(与 v2.0 相同,略) + +--- + +## 2.6 错题分类(优化) + +(与 v2.0 相同,略) + +--- + +## 2.7 搜索与筛选(v2.1 技术栈更新) + +### 2.7.1 全文搜索(v2.1 PostgreSQL FTS 技术细化) +**优先级**: P0 + +**【v2.1 技术方案细化】** ⭐: + +**在线搜索(PostgreSQL + pg_jieba)**: + +1. **安装扩展**: + ```sql + CREATE EXTENSION pg_jieba; + ``` + +2. **创建中文分词配置**: + ```sql + CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = pg_jieba); + ALTER TEXT SEARCH CONFIGURATION chinese + ADD MAPPING FOR n,v,a,i,e,l WITH simple; + ``` + +3. **创建 GIN 索引**: + ```sql + CREATE INDEX idx_wrong_questions_fts + ON wrong_questions + USING gin (to_tsvector('chinese', ocr_text)); + ``` + +4. **全文搜索查询**: + ```sql + SELECT + id, + ocr_text, + ts_rank(to_tsvector('chinese', ocr_text), query) AS rank + FROM wrong_questions, + to_tsquery('chinese', '二次方程 & 求根') AS query + WHERE to_tsvector('chinese', ocr_text) @@ query + ORDER BY rank DESC; + ``` + +5. **自定义数学/学科词典**: + - 预置学科术语词典(约 2000-5000 词) + - 覆盖初高中数学、物理、化学、英语的高频术语 + - 使用 `pg_jieba` 的自定义词典功能加载 + - 通过 `ts_debug('chinese', '二次函数的顶点坐标')` 验证分词效果 + +**性能预估**: +- PostgreSQL FTS + GIN 索引 +- 10 万级数据量:查询响应 10-50ms +- 单用户场景(数百到数千题):性能远超需求(<500ms) + +**【v2.1 新增】离线搜索架构(SQLite FTS5)** ⭐: + +**问题**(v2.0 矛盾): +- 在线:PostgreSQL FTS +- 离线:文档写"本地 FTS 搜索(不依赖服务端)" +- **矛盾**:数据字典与索引只写了 PG,没有本地 FTS 方案 + +**解决方案**: + +1. **本地数据库**: + - 使用 **SQLite**(通过 sqflite 或 drift)作为"离线事实库" + - 启用 **FTS5 扩展**(SQLite 全文搜索) + ```sql + -- 创建 FTS5 虚拟表 + CREATE VIRTUAL TABLE wrong_questions_fts USING fts5( + id UNINDEXED, + ocr_text, + tokenize='unicode61' -- 基础分词(或 N-gram for CJK) + ); + + -- 插入数据时同步到 FTS 表 + INSERT INTO wrong_questions_fts (id, ocr_text) + SELECT id, ocr_text FROM wrong_questions; + + -- 全文搜索查询 + SELECT * FROM wrong_questions_fts + WHERE wrong_questions_fts MATCH '二次方程'; + ``` + +2. **中文分词处理**: + - SQLite FTS5 默认 `unicode61` tokenizer 对中文支持有限 + - **方案 A**(推荐):使用 N-gram(2-gram 或 3-gram) + ```sql + CREATE VIRTUAL TABLE wrong_questions_fts USING fts5( + ocr_text, + tokenize='trigram' -- 3-gram 分词 + ); + ``` + - **方案 B**:自定义 tokenizer(复杂度高,P2 考虑) + +3. **同步策略**(见 2.10.3) + +**最小可行建议(MVP)**: +- 离线允许"复习与打结果",但只允许写 attempts(append-only) +- 不允许编辑题目元数据(标签/笔记等) +- 这样冲突显著减少 + +--- + +### 2.7.2 相似题去重与合并 +**优先级**: P1 + +**【v2.1 完整方案】两道筛选 + TF-IDF** ⭐: + +1. **第一道筛选:图片指纹(pHash)** → 见 2.1.4 + +2. **第二道筛选:OCR 文本相似度** + - **技术方案**:TF-IDF + 余弦相似度(MVP) + ```python + from sklearn.feature_extraction.text import TfidfVectorizer + from sklearn.metrics.pairwise import cosine_similarity + import jieba + + def detect_duplicate(new_ocr_text, existing_questions): + # 1. 分词 + corpus = [' '.join(jieba.cut(q.ocr_text)) for q in existing_questions] + corpus.append(' '.join(jieba.cut(new_ocr_text))) + + # 2. TF-IDF 向量化 + vectorizer = TfidfVectorizer() + tfidf_matrix = vectorizer.fit_transform(corpus) + + # 3. 计算相似度 + similarities = cosine_similarity(tfidf_matrix[-1], tfidf_matrix[:-1])[0] + + # 4. 找出相似度 ≥ 0.85 的题目 + duplicates = [ + (existing_questions[i], sim) + for i, sim in enumerate(similarities) + if sim >= 0.85 + ] + return duplicates + ``` + + - **性能**:单用户数千条记录,比对时间 <100ms + +3. **合并逻辑**: + - 相似度 >85%,提示:"疑似重复题,是否合并?" + - 用户选择合并: + - ~~`wrong_count += 1`~~(v2.0 做法) + - **v2.1 做法**: + ```python + # 写入一条新的 attempt + INSERT INTO attempts (question_id, result, review_mode) + VALUES (merged_question_id, 'wrong', 'import'); + + # wrong_count 会自动更新(由 attempts 驱动) + ``` + - 保留最新的图片和笔记 + - 用户选择不合并:正常保存为新题 + +**验收标准**: +- 相似度检测时间 ≤1 秒 +- 重复题召回率 ≥80%(测试集:100 道题中 20 道重复) + +--- + +## 2.8 统计与分析(v2.1 KPI 口径严格化) + +### 2.8.1 总体统计 +**优先级**: P1 + +**统计指标**(补充): +- 错题总数 +- 反复错误数(wrong_count ≥ 2) +- **高频重点**(wrong_count ≥ 3)⭐ - 需重点关注 +- 已掌握数量(`is_mastered = TRUE`) +- 今日新增、本周新增 + +**【v2.1 新增指标 + 严格口径】** ⭐: + +1. **📊 复习执行力:7 日复习完成率** + - **口径**: + ```sql + SELECT + SUM(completed_count)::FLOAT / NULLIF(SUM(planned_count), 0) AS completion_rate + FROM review_sessions + WHERE user_id = ? + AND started_at >= NOW() - INTERVAL '7 days'; + ``` + - 统计粒度:按 `review_sessions` 表聚合 + - 目标:≥60% + +2. **📉 重错率变化** + - **问题**(v2.0 定义模糊): + - "第 1 周 vs 第 4 周 wrong_count≥3 占比下降≥30%" + - 受新题导入量、合并策略、使用强度、学科差异影响 + + - **【v2.1 严格口径】使用 cohort 定义** ⭐: + ```sql + -- 定义 cohort:第 1 周录入的错题集合 + WITH week1_cohort AS ( + SELECT id FROM wrong_questions + WHERE user_id = ? + AND created_at BETWEEN '2026-02-01' AND '2026-02-07' + ), + + -- 统计该 cohort 在第 4 周的重错情况 + week4_heavy_wrong AS ( + SELECT q.id + FROM wrong_questions q + JOIN attempts a ON a.question_id = q.id + WHERE q.id IN (SELECT id FROM week1_cohort) + AND a.result = 'wrong' + AND DATE(a.attempt_at) NOT IN ( + -- 排除同一天的多次错误 + SELECT DATE(a2.attempt_at) + FROM attempts a2 + WHERE a2.question_id = a.question_id + AND a2.attempt_at < a.attempt_at + ) + GROUP BY q.id + HAVING COUNT(DISTINCT DATE(a.attempt_at)) >= 3 + ) + + SELECT + (SELECT COUNT(*) FROM week4_heavy_wrong)::FLOAT / + (SELECT COUNT(*) FROM week1_cohort) AS heavy_wrong_rate; + ``` + + - **对比**: + - Week 1:该 cohort 的初始重错率(基准,通常 100%因为都是新错题) + - Week 4:该 cohort 的重错率(attempts wrong ≥3 且发生在不同日期) + - 下降幅度:≥30% + +3. **⏱️ 平均掌握时间** + - **口径**: + ```sql + SELECT PERCENTILE_CONT(0.5) WITHIN GROUP ( + ORDER BY EXTRACT(EPOCH FROM (mastered_at - first_wrong_at)) / 86400 + ) AS median_days_to_master + FROM wrong_questions + WHERE user_id = ? + AND is_mastered = TRUE + AND mastered_at IS NOT NULL; + ``` + - 统计:从 `first_wrong_at` 到 `mastered_at` 的中位数天数 + - 目标:≤14 天 + +4. **🎯 二刷正确率提升** + - **【v2.1 严格定义"二刷"】** ⭐: + - 定义:在首次 wrong 后、下一次 review_mode='daily_review' 且**间隔≥24小时**的 attempt + - 排除:同一天的多次练习(防止"刷数据") + + - **口径**: + ```sql + WITH first_wrong AS ( + SELECT + question_id, + MIN(attempt_at) AS first_wrong_at + FROM attempts + WHERE result = 'wrong' + GROUP BY question_id + ), + second_review AS ( + SELECT + a.question_id, + a.result AS second_result + FROM attempts a + JOIN first_wrong fw ON a.question_id = fw.question_id + WHERE a.review_mode = 'daily_review' + AND a.attempt_at > fw.first_wrong_at + INTERVAL '24 hours' + ORDER BY a.attempt_at + LIMIT 1 PER question_id -- PostgreSQL extension + ) + + SELECT + COUNT(CASE WHEN second_result = 'correct' THEN 1 END)::FLOAT / + NULLIF(COUNT(*), 0) AS second_review_accuracy + FROM second_review; + ``` + - 目标:≥70% + +--- + +### 2.8.2 知识点薄弱分析 +**优先级**: P1(与 v2.0 相同) + +(略) + +--- + +## 2.9 导出与打印(优化) + +(与 v2.0 相同,略) + +--- + +## 2.10 **【v2.1 核心补充】离线使用能力 + 同步策略** ⭐ +**优先级**: P1 + +**功能描述**: +在无网络环境仍可使用核心功能。 + +### 2.10.1 离线功能支持 + +**离线核心功能**(与需求对齐): +- **离线拍照**:图片存入本地文件系统,元数据写入 SQLite,标记 `sync_status = pending` +- **离线浏览**:直接读取 SQLite + 本地图片 +- **离线搜索**:SQLite FTS5 扩展支持全文搜索(见 2.7.1) +- **离线复习**:FSRS Card 数据完全在本地,dart-fsrs 纯 Dart 运行,无网络依赖 +- **离线标记**:直接更新 SQLite 中的掌握状态 + +**不支持离线的功能**: +- OCR 处理、手写去除、AI 推荐(均需后端服务) + +--- + +### 2.10.2 本地-服务端一致性架构 ⭐ + +**【v2.1 明确架构约束】** ⭐: + +``` +┌─────────────────────────────────────────────┐ +│ Flutter App (Client) │ +├─────────────────────────────────────────────┤ +│ Repository Layer (唯一数据源) │ +│ ├─ Local: SQLite (sqflite/drift) │ +│ │ - FTS5 全文索引 │ +│ │ - FSRS Card 数据 │ +│ │ - 图片路径(本地文件系统) │ +│ │ │ +│ └─ Remote: FastAPI Backend │ +│ - PostgreSQL (主库) │ +│ - MinIO (图片存储) │ +│ │ +│ Sync Engine (同步引擎) │ +│ ├─ Outbox Queue (离线操作队列) │ +│ ├─ Conflict Resolution (冲突解决) │ +│ └─ Idempotency (幂等性保证) │ +└─────────────────────────────────────────────┘ +``` + +**核心原则**: +1. **Repository 作为唯一数据源**:Repository 层组合本地数据库和远程 API,对上层透明 +2. **读操作使用 Stream**:先 yield 本地数据(快速响应),再 yield 远程数据(保证最新) +3. **写操作先本地后远程**:先写入 SQLite,再尝试同步到服务器 +4. **同步标记**:在数据模型中添加 `sync_status` 字段(pending/synced/failed) + +--- + +### 2.10.3 离线同步策略(Event Sourcing) ⭐ + +**【v2.1 采纳专家建议:增量日志同步】** ⭐: + +**问题**(v2.0 简单覆盖的风险): +- 用户在离线时复习了 10 道题,更新了 `next_review_at` +- 联网后,服务端可能已经通过推送或其他逻辑更新了这些字段 +- **冲突**:单纯的字段覆盖会丢失数据 + +**解决方案:Event Sourcing 思想** + +不要直接覆盖数据库状态,而是上传一个 **Action Log 列表**,服务端按时间顺序重放这些 Action,计算出最终状态。 + +**实施细节**: + +1. **本地记录操作日志**: + ```dart + // SQLite 中的离线操作表 + CREATE TABLE offline_actions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + action_type TEXT NOT NULL, -- 'review', 'mark_mastered', 'add_note', 等 + entity_type TEXT NOT NULL, -- 'question', 'attempt', 等 + entity_id TEXT NOT NULL, + action_data TEXT, -- JSON 格式的操作数据 + timestamp INTEGER NOT NULL, -- Unix timestamp + sync_status TEXT DEFAULT 'pending' -- 'pending', 'synced', 'failed' + ); + + -- 示例:记录一次复习操作 + INSERT INTO offline_actions (action_type, entity_type, entity_id, action_data, timestamp) + VALUES ( + 'review', + 'question', + 'q123', + '{"result": "correct", "review_mode": "daily_review"}', + 1675123456 + ); + ``` + +2. **联网后上传日志**: + ```dart + Future syncOfflineActions() async { + final actions = await getUnsyncedActions(); + + final response = await api.post('/sync/actions', { + 'actions': actions.map((a) => a.toJson()).toList() + }); + + if (response.success) { + await markActionsAsSynced(actions); + } + } + ``` + +3. **服务端重放日志**: + ```python + @app.post("/sync/actions") + async def sync_actions(actions: List[ActionLog]): + # 按时间戳排序 + actions.sort(key=lambda a: a.timestamp) + + for action in actions: + if action.action_type == 'review': + # 1. 创建 attempt 记录 + attempt = Attempt( + question_id=action.entity_id, + result=action.action_data['result'], + attempt_at=datetime.fromtimestamp(action.timestamp) + ) + db.add(attempt) + + # 2. 更新 FSRS 状态(根据 action_data) + question = db.query(WrongQuestion).get(action.entity_id) + # ... FSRS 更新逻辑 + + elif action.action_type == 'mark_mastered': + # ... + + db.commit() + return {"success": True} + ``` + +4. **幂等性保证**: + - 每个 action 生成唯一 ID(UUID) + - 服务端记录已处理的 action_id + - 重复上传时跳过已处理的 action + +5. **冲突解决策略**: + - **MVP 简单策略**:按时间戳顺序重放,后发生的操作覆盖先发生的 + - **后续优化**:复杂冲突(如两端都修改了笔记)可提示用户手动选择 + +**验收标准**: +- 离线模式下,查看/搜索/复习操作响应时间 ≤1 秒 +- 离线修改队列上线后同步成功率 ≥99%(带指数退避重试) +- 同步冲突提示率 ≤5%(MVP 简单策略) + +--- + +## 2.11 **【v2.1 新增】游戏化/运营埋点(预留)** ⭐ +**优先级**: P2(MVP 不做,但数据模型预留) + +**功能描述**: +通过积分、勋章、连续打卡等游戏化机制,对抗学习枯燥,提升用户留存。 + +**数据模型预留**: + +```sql +-- 用户表扩展 +ALTER TABLE users ADD COLUMN points INTEGER DEFAULT 0; +ALTER TABLE users ADD COLUMN current_streak INTEGER DEFAULT 0; -- 连续打卡天数 +ALTER TABLE users ADD COLUMN longest_streak INTEGER DEFAULT 0; + +-- 勋章表 +CREATE TABLE badges ( + id SERIAL PRIMARY KEY, + name VARCHAR(50) NOT NULL, -- '七日连续打卡' + description TEXT, -- '连续7天完成复习任务' + icon_url TEXT, + condition_type VARCHAR(30), -- 'streak', 'mastered_count', 'review_count' + condition_value INTEGER, -- 7, 100, 500 + points_reward INTEGER DEFAULT 0 +); + +-- 用户勋章关联表 +CREATE TABLE user_badges ( + id SERIAL PRIMARY KEY, + user_id UUID REFERENCES users(id), + badge_id INTEGER REFERENCES badges(id), + earned_at TIMESTAMP DEFAULT NOW(), + UNIQUE(user_id, badge_id) +); +``` + +**积分规则示例**(v2.2 实现): +- 完成一次复习:+5 分 +- 连续打卡 7 天:+50 分 + 勋章 +- 掌握一道题:+10 分 +- 完成今日复习清单:+20 分 + +**好处**: +- Gamification 是对抗学习枯燥的最有效手段 +- 提升用户留存率和活跃度 +- 数据模型预留后,后续迭代成本低 + +**验收标准(P2)**: +- 积分系统上线后,7 日留存率提升 ≥10% +- 连续打卡用户占比 ≥30% + +--- + +## 3. 非功能性需求 + +### 3.1 性能需求(细化) + +| 功能 | 指标 | 测试环境 | +|------|------|---------| +| 拍照流程 | ≤5 秒 | 单题拍摄→保存 | +| OCR 识别(PP-OCRv5) | WiFi ≤5 秒,4G ≤8 秒 | 端到端延迟(含上传) | +| 批量导入(10张) | ≤2 分钟 | WiFi 环境,中等复杂度图片 | +| 搜索响应(PostgreSQL FTS) | ≤500ms | 1000 题库 | +| 离线搜索(SQLite FTS5) | ≤200ms | 本地 1000 题库 | +| 页面加载 | ≤2 秒 | 首页、复习清单页 | +| 复习清单生成(FSRS) | ≤500ms | 50 道待复习题 | +| 图片指纹计算(pHash) | ≤100ms | 单张图片 | + +--- + +### 3.2 可用性需求(补充) + +**录入效率**: +- ❌ v1.0:"拍照→识别→选学科→写错因→保存"5 步 +- ✅ v2.1 快速模式:"拍照→选学科→保存"3 步,单题 ≤10 秒 + +**复习引导**: +- 首页进入复习模式 ≤2 步(点击"今日复习"按钮) +- 不需要用户主动搜索或筛选 + +**错误提示**: +- 清晰友好,避免技术术语 +- 例如:"拍照失败"→"照片太暗啦,试试开闪光灯" + +--- + +### 3.3 兼容性需求 + +(与 v2.0 相同,略) + +--- + +### 3.4 安全性需求(补充) + +**数据隔离**: +- 用户数据严格隔离 +- 数据库行级安全(RLS) + +**离线数据加密**: +- 本地 SQLite 使用 **SQLCipher** 加密 +- 防止手机丢失后数据泄露 + +**隐私保护**: +- 图片仅用于 OCR,不用于训练模型(除非用户明确同意) + +--- + +### 3.5 扩展性需求(补充) + +**多设备同步**: +- 手机拍题→iPad 复习→电脑打印 +- 策略:服务端为准,本地缓存(通过 Event Sourcing 同步) +- 冲突解决:后续版本支持离线修改队列合并 + +**教师/家长功能预留**: +- 数据模型预留 `organization_id`(班级/家庭) +- 权限系统预留 `role` 字段(student/teacher/parent) + +--- + +## 4. 数据字典(v2.1 完整版) + +### 4.1 核心表结构 + +#### users(用户表) +(与 v2.0 相同,略,但预留 `points`、`current_streak`、`longest_streak` 字段) + +--- + +#### wrong_questions(错题表 - v2.1 扩展) +```sql +CREATE TABLE wrong_questions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + subject_id INTEGER REFERENCES subjects(id), + + -- 图片相关 + original_image_url TEXT NOT NULL, + cleaned_image_url TEXT, -- 可能为空(去手写失败或未处理) + image_phash BIGINT, -- 【v2.1新增】pHash 图片指纹 + + -- OCR 解析内容 + ocr_text TEXT, + ocr_formula TEXT, -- LaTeX 格式 + ocr_confidence FLOAT, + + -- 分类维度 + difficulty SMALLINT DEFAULT 3, + source VARCHAR(200), + chapter VARCHAR(200), + question_type VARCHAR(50), + + -- ⭐ 错误追踪(v2.1 规则:wrong_count 由 attempts 驱动) + wrong_count INTEGER DEFAULT 1, -- 【v2.1】可选缓存字段(通过触发器维护)或查询时聚合 + last_wrong_at TIMESTAMP DEFAULT NOW(), + first_wrong_at TIMESTAMP DEFAULT NOW(), + + -- ⭐ FSRS 间隔重复字段(v2.1 升级到 FSRS) + -- 直接存储 FSRS Card 的 JSON(或展开字段) + fsrs_card_json JSONB, -- 【v2.1推荐】存储完整 FSRS Card + -- 或者展开字段(与 dart-fsrs Card 对应): + next_review_at TIMESTAMP, -- card.due + scheduled_days INTEGER DEFAULT 1, -- card.scheduledDays + stability FLOAT, -- card.stability(FSRS 特有) + difficulty FLOAT DEFAULT 5.0, -- card.difficulty(FSRS 替代 ease_factor) + elapsed_days INTEGER DEFAULT 0, -- card.elapsedDays + card_state VARCHAR(20), -- card.state: 'New', 'Learning', 'Review', 'Relearning' + + -- ⭐ 业务掌握判定字段(保留,与 FSRS 结合使用) + consecutive_correct INTEGER DEFAULT 0, -- 自行维护(从 attempts 计算) + is_mastered BOOLEAN DEFAULT FALSE, + mastered_at TIMESTAMP, + + -- 笔记 + error_reason TEXT, + correct_answer TEXT, + my_notes TEXT, + + -- 同步状态(离线使用) + sync_status VARCHAR(20) DEFAULT 'synced', -- 'pending', 'synced', 'failed' + + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- 索引 +CREATE INDEX idx_wrong_questions_user ON wrong_questions(user_id); +CREATE INDEX idx_wrong_questions_next_review ON wrong_questions(next_review_at); +CREATE INDEX idx_wrong_questions_phash ON wrong_questions(image_phash); -- 【v2.1新增】 +CREATE INDEX idx_wrong_questions_sync ON wrong_questions(sync_status); -- 【v2.1新增】 + +-- 全文搜索索引(PostgreSQL) +CREATE INDEX idx_wrong_questions_fts +ON wrong_questions +USING gin (to_tsvector('chinese', ocr_text)); +``` + +**【v2.1 重要说明】wrong_count 维护策略** ⭐: +- **方案 A(推荐 MVP)**:`wrong_count` 在查询时实时聚合,不存储 + ```sql + SELECT + q.*, + COUNT(CASE WHEN a.result = 'wrong' THEN 1 END) AS wrong_count + FROM wrong_questions q + LEFT JOIN attempts a ON a.question_id = q.id + GROUP BY q.id; + ``` + +- **方案 B(性能优化)**:保留 `wrong_count` 字段,通过触发器维护 + ```sql + CREATE OR REPLACE FUNCTION update_wrong_count() + RETURNS TRIGGER AS $$ + BEGIN + IF NEW.result = 'wrong' THEN + UPDATE wrong_questions + SET wrong_count = wrong_count + 1 + WHERE id = NEW.question_id; + END IF; + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + CREATE TRIGGER attempt_insert_trigger + AFTER INSERT ON attempts + FOR EACH ROW EXECUTE FUNCTION update_wrong_count(); + ``` + +--- + +#### attempts(练习记录表 - v2.1 完善) +```sql +CREATE TABLE attempts ( + id SERIAL PRIMARY KEY, + question_id UUID REFERENCES wrong_questions(id) ON DELETE CASCADE, + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + + -- 练习结果 + result VARCHAR(20) NOT NULL, -- 'correct', 'wrong', 'unknown' + response_time INTEGER, -- 响应时间(秒,可选) + + -- ⭐ 错因标签 + error_tags TEXT[], -- PostgreSQL 数组:['计算失误', '审题错误'] + error_note TEXT, -- 补充说明(可选) + + -- 复习模式 + review_mode VARCHAR(20), -- 'daily_review', 'exam_mode', 'manual' + + -- 【v2.1新增】FSRS 反馈等级(用于 FSRS 算法) + fsrs_rating VARCHAR(10), -- 'again', 'hard', 'good', 'easy' + + -- 同步状态(离线使用) + sync_status VARCHAR(20) DEFAULT 'synced', + + attempt_at TIMESTAMP DEFAULT NOW() +); + +-- 索引 +CREATE INDEX idx_attempts_question ON attempts(question_id); +CREATE INDEX idx_attempts_user ON attempts(user_id); +CREATE INDEX idx_attempts_time ON attempts(attempt_at DESC); +CREATE INDEX idx_attempts_result ON attempts(result); +CREATE INDEX idx_attempts_sync ON attempts(sync_status); -- 【v2.1新增】 +``` + +**核心规则(v2.1 强制执行)**: +> 任何导致题目"对/错/不会"的行为都必须产生 attempt;wrong_count 由 attempts 聚合计算。 + +--- + +#### error_tags(错因标签表) +(与 v2.0 相同,略) + +--- + +#### review_sessions(复习会话表) +```sql +CREATE TABLE review_sessions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + + -- 会话信息 + planned_count INTEGER, -- 计划复习题数 + completed_count INTEGER DEFAULT 0, -- 实际完成数 + session_type VARCHAR(20), -- 'daily_review', 'exam_prep' + + started_at TIMESTAMP DEFAULT NOW(), + finished_at TIMESTAMP, + duration_seconds INTEGER -- 总用时(秒) +); + +-- 索引 +CREATE INDEX idx_review_sessions_user ON review_sessions(user_id); +CREATE INDEX idx_review_sessions_started ON review_sessions(started_at DESC); +``` + +**用途**: +- 统计"7 日复习完成率"(见 2.8.1) +- 追踪用户复习行为 + +--- + +#### offline_actions(离线操作日志表 - v2.1 新增)⭐ +```sql +CREATE TABLE offline_actions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + + action_type VARCHAR(30) NOT NULL, -- 'review', 'mark_mastered', 'add_note' + entity_type VARCHAR(30) NOT NULL, -- 'question', 'attempt' + entity_id TEXT NOT NULL, + action_data JSONB, -- 操作的详细数据(JSON 格式) + + timestamp BIGINT NOT NULL, -- Unix timestamp(客户端时间) + sync_status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'synced', 'failed' + + created_at TIMESTAMP DEFAULT NOW() -- 服务端收到时间 +); + +-- 索引 +CREATE INDEX idx_offline_actions_user ON offline_actions(user_id); +CREATE INDEX idx_offline_actions_sync ON offline_actions(sync_status); +CREATE UNIQUE INDEX idx_offline_actions_id ON offline_actions(id); -- 幂等性保证 +``` + +**用途**: +- 实现 Event Sourcing 同步策略(见 2.10.3) +- 保证离线操作不丢失 + +--- + +### 4.2 本地数据库(SQLite)结构(v2.1 新增)⭐ + +**Flutter 本地数据库(sqflite/drift)**: + +```sql +-- 错题表(本地缓存) +CREATE TABLE wrong_questions ( + id TEXT PRIMARY KEY, + user_id TEXT, + subject_id INTEGER, + original_image_path TEXT, -- 本地文件路径 + cleaned_image_path TEXT, + ocr_text TEXT, + -- ... 其他字段与服务端一致 + + fsrs_card_json TEXT, -- FSRS Card JSON 字符串 + + sync_status TEXT DEFAULT 'synced', + last_synced_at INTEGER -- Unix timestamp +); + +-- FTS5 全文索引表 +CREATE VIRTUAL TABLE wrong_questions_fts USING fts5( + id UNINDEXED, + ocr_text, + tokenize='trigram' -- 3-gram 分词(适合中文) +); + +-- 离线操作队列 +CREATE TABLE offline_actions ( + id TEXT PRIMARY KEY, + action_type TEXT NOT NULL, + entity_type TEXT NOT NULL, + entity_id TEXT NOT NULL, + action_data TEXT, -- JSON 字符串 + timestamp INTEGER NOT NULL, + sync_status TEXT DEFAULT 'pending' +); + +-- 其他表(attempts、review_sessions 等)类似 +``` + +--- + +## 5. 用户场景(v2.1 更新) + +### 场景1:学生课后快速录入错题 +(与 v2.0 类似,但多题切割改为手动框选 + 智能辅助) + +1. 小明做完数学作业,发现 3 道题做错了 +2. 打开 App,点击首页"➕拍题"按钮 +3. 连续拍摄 3 张照片(每张包含 1-2 道题) +4. **【v2.1 改进】系统自动检测题号位置并标注虚线提示**: + - 照片 1:系统提示 "1." 和 "2." 两个题号 + - 小明拖动切割线微调(**拼图式调整,相邻边界自动吸附**) + - 确认后生成 2 道错题 +5. **快速模式**:只选择学科"数学",点保存(用时 30 秒) +6. 系统后台: + - 计算 pHash 图片指纹(去重第一道筛选) + - OCR 识别题目文本(PP-OCRv5) + - AI 推荐知识点标签(小明稍后确认) + - 自动设置 `next_review_at = 明天`(FSRS 初始化) +7. **第二天晚上 7 点**,小明收到推送:"今天有 5 道数学错题等你复习" + +--- + +### 场景2:科学复习,避免遗忘(v2.1 防作弊 + 防挫败) +(核心场景,v2.1 交互优化) + +1. 小红打开 App,看到首页红点:"📚 今日待复习 10题" +2. 点击进入复习模式 +3. 系统依次展示 10 道题(按 FSRS 优先级排序): + - 显示题目图片(自动隐藏答案) + - 小红在草稿纸上做题 + - 做完点🟢"会做/做对了" + - **【v2.1 防作弊】按钮灰色,3 秒后才能查看答案**(强制认知投入) + - 查看正确答案和之前的错误分析 + - 继续下一题 +4. 第 5 题做错了: + - 点🔴"做错了" + - 弹出错因选择,勾选"计算失误" + - 系统记录: + ```python + # 1. 写入 attempt + INSERT INTO attempts (question_id, result, error_tags, fsrs_rating) + VALUES ('q5', 'wrong', ['计算失误'], 'again'); + + # 2. 更新 FSRS 状态 + card = load_fsrs_card('q5') + updated_card = scheduler.reviewCard(card, Rating.again) + save_fsrs_card('q5', updated_card) + # next_review_at 自动设置为明天 + ``` +5. 第 8 题完全不会: + - 点⚪"完全不会" + - **【v2.1 防挫败】系统先显示提示**: + - 💡 "这道题考查二次函数的顶点公式:y = a(x-h)² + k" + - 📖 相关知识点:二次函数 + - 小红选择:"看完提示再做" → 重新做题 + - 或者:"仍然不会" → 显示完整答案 + 记录 attempt(result='unknown') +6. 10 题复习完成(用时 8 分钟) +7. 系统显示: + - 今日完成率:10/10 ✅ + - 正确率:70%(7 对 2 错 1 不会) + - 连续打卡:3 天 + - **【v2.1 KPI】本周已掌握 5 道题,重错率下降 20%!** + +--- + +### 场景3:离线模式使用(v2.1 新增场景)⭐ + +1. 小李在地铁上(无网络),打开 App +2. **离线浏览**:查看已录入的 50 道错题(从 SQLite 读取) +3. **离线搜索**:输入"二次函数",SQLite FTS5 返回 8 条结果(<200ms) +4. **离线复习**: + - 点击"今日复习",系统从本地 SQLite 生成清单(10 题) + - 依次做题,点🟢🔴⚪按钮 + - dart-fsrs 在本地运行(纯 Dart,无网络依赖) + - 每次操作写入 `offline_actions` 表: + ```sql + INSERT INTO offline_actions (id, action_type, entity_id, action_data, timestamp) + VALUES ( + 'uuid-123', + 'review', + 'q5', + '{"result": "correct", "fsrs_rating": "good"}', + 1675123456 + ); + ``` +5. **联网后自动同步**: + - connectivity_plus 监听到网络恢复 + - 触发同步引擎 + - 上传 `offline_actions` 到服务端 `/sync/actions` + - 服务端按时间戳重放日志,更新 PostgreSQL + - 同步成功后,清空本地 `offline_actions` 表 +6. 同步完成提示:"已同步 10 条复习记录" + +--- + +## 6. 技术方案(v2.1 完整版) + +### 6.1 技术栈总览(v2.1 最新) + +| 层级 | 技术选型 | 版本/备注 | +|------|---------|----------| +| **前端** | Flutter | 3.x,iOS 优先(后续 Android) | +| **本地数据库** | SQLite(sqflite/drift) | FTS5 扩展 | +| **后端框架** | Python FastAPI | 最新稳定版 | +| **后端数据库** | PostgreSQL | 14+,pg_jieba 扩展 | +| **文本 OCR** | **PP-OCRv5** | 2025年5月发布,准确率提升 13% | +| **公式 OCR** | **PP-FormulaNet_plus-M** | 中文 BLEU 89.76%,~1秒 | +| **版面分析** | **PP-DocLayoutV3**(P1) | 2026年1月发布,支持非平面文档 | +| **手写去除** | OpenCV HSV(P0)+ RemoveHandwriting API(P1) | API 定价见 2.2.2 | +| **间隔重复** | **FSRS via dart-fsrs v2.0.0** | 替代 SM-2,MIT 许可 | +| **全文检索(在线)** | PostgreSQL FTS + **pg_jieba** | GIN 索引,中文分词 | +| **全文检索(离线)** | SQLite **FTS5** | trigram 分词(3-gram) | +| **相似题检测** | pHash(图片指纹)+ TF-IDF + 余弦相似度 | 两道筛选 | +| **离线同步** | Event Sourcing + Outbox Queue | 增量日志同步 | +| **对象存储** | MinIO / 阿里云 OSS | 图片存储 | +| **状态管理** | Riverpod | Flutter 官方推荐 | +| **数据模型** | Freezed | 不可变数据类 | + +--- + +### 6.2 技术方案对比(v1.0 vs v2.0 vs v2.1) + +| 模块 | v1.0 | v2.0 | v2.1 | +|------|------|------|------| +| 全文搜索 | Elasticsearch | PostgreSQL FTS | **在线:PG FTS + pg_jieba;离线:SQLite FTS5** | +| 手写去除 | P0 核心,颜色分离+API | P1 可选,半自动+手动 | 同 v2.0,补充 API 定价 | +| 公式 OCR | PP-FormulaNet | 基础识别,不承诺完美 | **PP-FormulaNet_plus-M**(最新版) | +| 文本 OCR | PP-OCRv4 | PP-OCRv4 | **PP-OCRv5**(2025年5月) | +| 批量导入 | 50 张 | 20 张 | 20 张 | +| 知识点树 | 3 级 | 2 级 | 2 级 | +| 间隔重复 | - | SM-2 简化版 | **FSRS via dart-fsrs v2.0.0** | +| 多题切割 | - | P0 自动切割(80%) | **P0 手动框选 + 智能辅助;P1 自动切割** | +| 数据一致性 | - | wrong_count 手动更新 | **wrong_count 由 attempts 驱动** | +| 离线同步 | - | 简单覆盖 | **Event Sourcing 增量日志同步** | +| 图片去重 | - | OCR 文本相似度 | **pHash 图片指纹 + OCR 文本相似度** | +| 游戏化 | - | - | **数据模型预留(积分/勋章/打卡)** | + +--- + +### 6.3 MVP 排期(4周可交付性 - v2.1 优化)⭐ + +**【v2.1 重大调整】降低 MVP 风险,明确降级路径** + +#### Week 1-2:基础 + 录入(稳定优先) + +**核心任务**: +- ✅ Flutter 项目脚手架 + Riverpod + sqflite 集成 +- ✅ 拍照录入(**P0 手动框选切题 + 题号智能提示**) + - 降级路径:题号检测失败时完全手动 +- ✅ 批量导入(20 张,进度条) +- ✅ OCR 集成(PP-OCRv5 文本 + PP-FormulaNet_plus-M 公式) + - 降级路径:公式识别失败时只返回文本 OCR +- ✅ FastAPI 后端骨架 + PostgreSQL 数据库初始化 +- ✅ 快速录入模式(仅学科+图片) + +**风险点**: +- OCR 服务部署需要 GPU 环境(T4 级别) +- **缓解**:Week 0 提前准备 Docker 镜像 + +**验收标准**: +- 单题录入端到端流程 ≤15 秒 +- 手动框选 2-5 题 ≤30 秒 + +--- + +#### Week 3:核心闭环(FSRS + 练习模式) + +**核心任务**: +- ✅ FSRS 集成(dart-fsrs v2.0.0) + - 初始化 Card + - reviewCard 更新逻辑 + - 边界条件(maximum_interval=90) +- ✅ 今日复习清单生成 +- ✅ 练习/自测模式(快速复习) + - 三按钮反馈(会做/做错/不会) + - **防作弊**:3 秒延迟查看答案 + - **防挫败**:点"不会"先显示提示 +- ✅ Attempt 记录表 + 数据一致性规则(wrong_count 由 attempts 驱动) +- ✅ 推送通知(本地提醒,晚上 7 点) + +**风险点**: +- FSRS 算法理解和集成(初次使用) +- **缓解**:dart-fsrs 文档完善,API 简单 + +**验收标准**: +- 今日复习清单生成 <500ms +- 复习 10 题流程 ≤3 分钟 +- wrong_count 与 attempts 一致(单元测试) + +--- + +#### Week 4:完善 + 测试 + +**核心任务**: +- ✅ 搜索(PostgreSQL FTS + pg_jieba) + - 创建 GIN 索引 + - 中文分词配置 +- ✅ 离线能力(SQLite + FTS5) + - 拍照暂存本地 + - 离线浏览/搜索 + - 离线复习(FSRS 本地运行) + - offline_actions 队列 +- ✅ 同步引擎(Event Sourcing) + - 联网后上传日志 + - 服务端重放 + - 幂等性保证 +- ✅ 统计分析(含学习效果 KPI) + - 7 日复习完成率 + - 重错率变化(cohort 定义) + - 二刷正确率 +- ✅ 端到端测试 + 性能优化 + +**风险点**: +- 离线同步的边界 case(多端冲突) +- **缓解**:MVP 使用简单策略(按时间戳重放),复杂冲突后续优化 + +**验收标准**: +- 离线操作同步成功率 ≥99% +- 端到端测试通过率 ≥95% +- 所有 P0 功能可用 + +--- + +#### 【v2.1 明确】可裁剪项(如果 Week 4 紧张)⭐ + +**可后置到 P1**(不影响提效闭环): +- 自动多题切割(先手动框选) +- 离线全文搜索(先离线浏览+复习,搜索在线) +- 相似题自动合并(先做"提示疑似重复"不自动合并) +- OCR 文本层 PDF(先纯图片 PDF) +- 结构化错因体系(先自由文本) + +**最小可用 MVP(Week 4 必达)**: +- 录入(拍照/相册导入) +- OCR(哪怕只做文字识别) +- 复习调度(FSRS) +- 练习模式(对/错/不会 → attempts) +- 今日清单 + 推送 +- 基础统计(复习完成率、二刷正确率) + +--- + +### 6.4 第二阶段(Week 5-8,P1) + +**Week 5-6:体验优化** +- ✅ 结构化错因体系 +- ✅ 相似题去重(pHash + TF-IDF) +- ✅ AI 知识点推荐 +- ✅ 手写去除(OpenCV HSV + 手动橡皮擦) + +**Week 7-8:高级功能** +- ✅ 组卷模考模式 +- ✅ 知识点薄弱分析(含依赖提示) +- ✅ 图片 PDF 导出(含 OCR 文本层) +- ✅ 用户引导 + 帮助文档 +- ✅ **P1 自动多题切割**(PP-DocLayoutV3) + +--- + +### 6.5 后续版本(P2) + +- 知识图谱驱动的薄弱链路分析 +- 教师/家长角色功能 +- 社交/协作(班级错题库、排行榜) +- AI 生成相似题/变式题 +- 完美 LaTeX 公式输出 +- Elasticsearch(教师场景) +- 游戏化功能(积分/勋章/连续打卡)实施 + +--- + +## 7. 验收标准(v2.1 完整版) + +### 7.1 功能验收 + +| 类别 | 指标 | 目标 | +|------|------|------| +| 核心功能 | P0 功能完成度 | 100% | +| 重要功能 | P1 功能完成度 | ≥80% | +| **文本 OCR**(PP-OCRv5) | 中文印刷体(字级别) | ≥95% | +| **公式 OCR**(PP-FormulaNet_plus-M) | 数学公式(基础) | ≥80% | +| 多题切割(P0 手动) | 手动框选准确率 | 100%(用户主导) | +| 多题切割(P1 自动) | 自动切割准确率(2-5题/页) | ≥80% | +| 手写去除(HSV) | 蓝/红笔去除成功率 | ≥75% | +| **图片去重**(pHash) | 重复照片召回率 | ≥95% | +| **离线同步**(Event Sourcing) | 同步成功率 | ≥99% | + +--- + +### 7.2 性能验收 + +| 指标 | 目标 | +|------|------| +| 单题录入时间中位数 | ≤15 秒(快速模式 ≤10 秒) | +| 手动框选多题 | 2-5 题 ≤30 秒 | +| OCR 端到端延迟(WiFi) | ≤5 秒(PP-OCRv5) | +| 批量导入 10 张(WiFi) | ≤2 分钟 | +| 在线搜索响应时间 | ≤500ms(PostgreSQL FTS) | +| **离线搜索响应时间** | ≤200ms(SQLite FTS5) | +| 复习清单生成时间 | ≤500ms(FSRS) | +| **pHash 计算时间** | ≤100ms | + +--- + +### 7.3 学习效果验收(v2.1 口径严格化)⭐ + +| 指标 | 目标 | 测量方式(v2.1 严格口径) | +|------|------|---------------------------| +| **7 日复习完成率** | ≥60% | `SUM(completed_count) / SUM(planned_count)` from `review_sessions` in 7 days | +| **重错率下降** | 使用 4 周后下降 ≥30% | 第 1 周录入的 cohort,到第 4 周时 `attempts(wrong ≥3 且不同日期)` 占比下降 | +| **平均掌握时间** | ≤14 天 | `PERCENTILE_CONT(0.5)` of `(mastered_at - first_wrong_at)` in days | +| **复习会话时长** | 中位数 ≤15 分钟 | `duration_seconds` from `review_sessions` | +| **二刷正确率** | ≥70% | 首次 wrong 后、下次 `review_mode='daily_review'` 且**间隔≥24小时**的 attempt 为 correct 的比例 | + +**关键定义**(v2.1 明确): +- **Cohort**:第 1 周录入的错题集合(`created_at BETWEEN week1_start AND week1_end`) +- **二刷**:首次 wrong 后、下一次 review_mode='daily_review' 且间隔≥24小时 +- **重错题**:`attempts` 中 result='wrong' 的记录 ≥3 次且发生在不同日期 +- **复习完成率**:从 `review_sessions` 表聚合,而非手动计算 + +--- + +### 7.4 用户体验验收 + +| 指标 | v1.0 目标 | v2.0 目标 | v2.1 目标 | 原因 | +|------|---------|---------|---------|------| +| 可用性测试通过率 | ≥90% | ≥85% | ≥85% | 新功能多,初期可能有磨合 | +| 用户满意度(NPS) | ≥4.5/5.0 | ≥3.8/5.0 | ≥4.0/5.0 | MVP 功能更完整 | +| 7 日留存率 | ≥70% | ≥40% | ≥45% | FSRS + 推送提升留存 | +| 30 日留存率 | - | ≥25% | ≥30% | 游戏化预留提升预期 | + +--- + +## 8. 异常流程处理 ⭐ + +(与 v2.0 相同,略) + +--- + +## 9. 项目约束(调整) + +### 9.1 技术约束 + +- 后端:Python FastAPI +- 前端:Flutter(iOS 优先) +- 数据库:PostgreSQL(MVP 不用 ES) +- OCR:PaddleOCR(PP-OCRv5 + PP-FormulaNet_plus-M) +- 图像处理:OpenCV +- 间隔重复:FSRS via dart-fsrs v2.0.0 + +--- + +### 9.2 时间约束 + +- **MVP 版本**(P0 核心功能):**4 周** + - v2.1 优化后 MVP 范围更聚焦(手动切题 + FSRS + 离线能力) + - 明确降级路径,交付风险更低 +- **完整版本**(P0+P1):**8 周** + +--- + +### 9.3 成本约束 + +- 优先本地部署(降低云服务成本) +- 手写去除 API 可选(付费功能,定价见 2.2.2) +- GPU 服务器按需使用(OCR,最低 T4 级别) + +--- + +## 10. 风险与应对(v2.1 更新) + +| 风险 | 概率 | 影响 | 应对措施(v2.1) | +|------|------|------|-----------------| +| **多题切割准确率不达标** | 低(P0 手动) | 低 | v2.1 改为 P0 手动框选 + 智能辅助;P1 自动切割 | +| FSRS 算法用户不买单 | 低 | 高 | A/B 测试;提供"关闭自动复习"选项 | +| OCR 公式识别质量差 | 中 | 中 | **不承诺完美**;提供手动编辑 | +| 手写去除效果不佳 | 高 | 低 | **P1 可选功能**;提供手动橡皮擦 | +| 学生不坚持使用 | 中 | 高 | 推送提醒 + FSRS 科学调度 + 游戏化(P2) | +| **离线同步冲突** | 中 | 中 | v2.1 采用 Event Sourcing;MVP 简单策略 | +| **数据一致性(wrong_count vs attempts)** | 低 | 高 | v2.1 强制规则:wrong_count 由 attempts 驱动;提供修复工具 | +| **4 周 MVP 交付延期** | 中 | 高 | v2.1 明确降级路径;P0 范围更聚焦 | + +--- + +## 11. 成功案例对标 + +### 11.1 Anki(间隔重复) +- 核心:FSRS 算法(已替代 SM-2),科学复习 +- 借鉴:间隔重复调度、复习完成率统计 +- 差异:我们针对错题场景,Anki 是通用记忆卡片 + +### 11.2 小猿搜题(拍照识题) +- 核心:拍照→OCR→题库匹配 +- 借鉴:拍照流程优化、多题切割(v2.1 手动+自动) +- 差异:我们是错题管理+复习,不是搜题 + +### 11.3 Notion(灵活标签体系) +- 核心:多维度标签、数据库视图 +- 借鉴:知识点标签树、多维筛选 +- 差异:我们更简化,移动端友好 + +--- + +## 12. 总结:v2.1 核心变革 + +### 从 v2.0 到 v2.1 的五大工程化升级: + +1. **数据一致性保障**: + - v2.0:wrong_count 和 attempts 可能不一致 + - v2.1:**wrong_count 完全由 attempts 驱动**,强制规则+修复工具 + +2. **离线架构清晰化**: + - v2.0:在线 PG FTS,离线能力模糊 + - v2.1:**SQLite FTS5(离线)+ Event Sourcing 同步策略** + +3. **FSRS 替代 SM-2**: + - v2.0:SM-2 简化版(1990 年算法) + - v2.1:**FSRS via dart-fsrs v2.0.0**(2022+ 最新,Anki 已采纳) + +4. **多题切割策略优化**: + - v2.0:P0 自动切割(80% 准确率),风险高 + - v2.1:**P0 手动框选+智能辅助;P1 自动切割**(降低 MVP 风险) + +5. **技术栈全面更新**: + - v2.0:PP-OCRv4、SM-2、模糊的离线方案 + - v2.1:**PP-OCRv5、PP-FormulaNet_plus-M、PP-DocLayoutV3、pg_jieba、pHash、Event Sourcing** + +--- + +### 关键设计决策对照表 + +| 决策 | v2.0 | v2.1 | 理由 | +|------|------|------|------| +| 间隔重复算法 | SM-2 简化版 | **FSRS via dart-fsrs** | FSRS 更科学,Anki 已采纳,Flutter 有官方包 | +| 多题切割 | P0 自动切割 | **P0 手动+辅助;P1 自动** | 降低 MVP 风险,手动可 100% 准确 | +| 数据一致性 | wrong_count 手动更新 | **由 attempts 驱动** | 避免不一致,可审计,可修复 | +| 离线搜索 | 未明确 | **SQLite FTS5** | 明确架构,trigram 分词支持中文 | +| 离线同步 | 简单覆盖 | **Event Sourcing** | 增量日志,幂等性,冲突可回溯 | +| 图片去重 | OCR 文本 | **pHash + OCR 文本** | 两道筛选,更快更准 | +| 手写去除 | P1 半自动 | **P1 + API 定价明确** | 补充成本评估,决策更清晰 | +| 游戏化 | - | **数据模型预留** | 后续迭代成本低 | + +--- + +## 附录A:v2.1 vs v2.0 改进对照表 + +| 改进点 | v2.0 状态 | v2.1 改进 | 来源评审 | 体现章节 | +|-------|----------|----------|---------|---------| +| wrong_count 一致性 | 手动更新,可能不一致 | ✅ 由 attempts 驱动,强制规则 | 评审2 | §2.4.3 | +| 离线搜索架构 | 未明确 | ✅ SQLite FTS5 + trigram 分词 | 评审2 | §2.7.1 | +| 离线同步策略 | 简单覆盖 | ✅ Event Sourcing 增量日志同步 | 评审1,2 | §2.10.3 | +| 多题切割策略 | P0 自动(风险高) | ✅ P0 手动+辅助;P1 自动 | 评审1,2 | §2.1.1 | +| SM-2 边界条件 | 无 | ✅ ease_factor/interval 上下限,防刷机制 | 评审2 | §2.3.2 | +| FSRS 替代 SM-2 | SM-2 简化版 | ✅ FSRS via dart-fsrs v2.0.0 | 评审3 | §2.3 | +| KPI 统计口径 | 模糊 | ✅ cohort 定义、二刷定义明确 | 评审2 | §2.8.1 | +| 复习防作弊 | 无 | ✅ 点"会做"停留 3 秒或输入数值 | 评审1 | §2.4.1 | +| 复习防挫败 | 无 | ✅ 点"不会"先显示提示/思路 | 评审1 | §2.4.1 | +| 多题切割交互 | 简单框选 | ✅ 拼图式调整,边界自动吸附 | 评审1 | §2.1.1 | +| 图片指纹去重 | 仅 OCR 文本 | ✅ pHash + OCR 文本两道筛选 | 评审1 | §2.1.4 | +| 手写去除成本 | 未明确 | ✅ RemoveHandwriting API 详细定价 | 评审3 | §2.2.2 | +| 技术栈版本 | PP-OCRv4, SM-2 | ✅ PP-OCRv5, FSRS, pg_jieba, PP-DocLayoutV3 | 评审3 | §6.1 | +| 游戏化预留 | 无 | ✅ 数据模型预留(积分/勋章/打卡) | 评审1 | §2.11 | +| MVP 降级路径 | 未明确 | ✅ 每个功能明确最低可用标准 | 评审2 | §6.3 | + +--- + +## 附录B:专家评审总结 + +### 评审1(S级评价)核心建议 +1. ✅ 多题切割的"拼图式调整"交互 +2. ✅ 复习模式的防作弊(停留3秒)和防挫败(先显示提示) +3. ✅ 图片指纹匹配(pHash)作为第一道筛选 +4. ✅ 离线同步采用 Event Sourcing(增量日志) +5. ✅ 游戏化埋点预留(积分/勋章) + +### 评审2(技术可行性)核心建议 +1. ✅ wrong_count 完全由 attempts 驱动,定义唯一口径 +2. ✅ 离线架构清晰化(SQLite FTS5 + 同步策略) +3. ✅ 多题切割拆分为 P0 手动 + P1 自动 +4. ✅ SM-2 边界条件和防刷机制(后改为 FSRS) +5. ✅ KPI 统计口径严格化(cohort、二刷、重错明确定义) + +### 评审3(技术调研)核心发现 +1. ✅ PP-OCRv5(2025年5月,准确率提升13%) +2. ✅ PP-FormulaNet_plus-M(中文 BLEU 89.76%) +3. ✅ PP-DocLayoutV3(2026年1月,非平面文档支持) +4. ✅ FSRS 替代 SM-2(Anki 已采纳,dart-fsrs v2.0.0 可用) +5. ✅ PostgreSQL + pg_jieba(在线 FTS) +6. ✅ SQLite FTS5(离线 FTS) +7. ✅ RemoveHandwriting.com API 详细定价 + +--- + +**修订人**:产品团队 + AI 专家评审团(Claude Opus 4.6、ChatGPT 5.2 Pro、Gemini 3 Pro)+ 技术可行性调研 +**版本**:v2.1(生产就绪版本) +**日期**:2026-02-09 + +--- + +**关键成果**: +- ✅ 数据一致性有保障(attempts 驱动 wrong_count) +- ✅ 离线架构清晰可实施(SQLite FTS5 + Event Sourcing) +- ✅ MVP 风险可控(手动切题 + 明确降级路径) +- ✅ 技术栈全面更新至 2026 最新(PP-OCRv5、FSRS、pg_jieba 等) +- ✅ 学习 KPI 可验证(cohort、二刷、重错口径明确) +- ✅ 交互细节优化(防作弊、防挫败、拼图式调整) + +**Go Build It!** 这将是一款真正能帮助学生提分、工程质量过硬的产品。🚀