docs(arch): 旧架构合并 — 30项决策落地,5份文档升级至v0.4.0
- 总体架构:新增打印/图像预处理/双飞轮/三环境部署 - 技术选型:调整决策理由(Coze沙盒自动化测试),新增Sharp+PDFKit - 数据模型:新增code/role/question_type+print_tasks+audit_logs,ID+code并存 - 模块设计:新增Image/Print模块,推荐两阶段匹配(关键词粗筛→AI精排) - PRD:目标用户扩展为学生+家长,新增PDF打印,年级聚焦小初,图像预处理流程 - ADR-010:题库抽象层Adapter Pattern Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -70,3 +70,45 @@
|
||||
- 新增 `sync-template.sh` 实现自动同步
|
||||
- 新增 `TEMPLATE.yaml` + `init.sh` 实现一键初始化
|
||||
- AI 项目框架从此可复用,token 节省 95%+
|
||||
|
||||
## ADR-009: 人机协同数据质量闭环
|
||||
|
||||
- 日期: 2026-05-26
|
||||
- 状态: 已采纳
|
||||
- 决策: 不依赖 AI 一次识别准确。AI 识别结果作为"草稿"入库,经用户确认/修正后才进入分析和推荐管道。所有修正记录保留为 P02 训练数据。
|
||||
- 理由:
|
||||
- 手写体 OCR 准确率无法保证 100%(尤其中小学生潦草字迹),错误数据直接进入分析会污染薄弱点诊断和练习推荐
|
||||
- 传统方案(调高 AI 准确率)成本极高且天花板低。人机协同方案将"用户修正"从成本转化为资产
|
||||
- 每一次用户修正 = 一条免费的标注数据,是训练自有模型的核心资源
|
||||
- 关键设计:
|
||||
- verification_status 状态机: raw → reviewed → corrected(+ stale 兜底)
|
||||
- 分字段置信度: 每个 AI 字段独立评分,低置信度高亮
|
||||
- 数据质量门控: AnalysisReport 和 Recommendation 仅使用 reviewed+ 数据
|
||||
- CorrectionLog: AI 值 vs 用户修正值的完整记录
|
||||
- 交互设计: 置信度绿/黄/红三级 UI,批量确认降低摩擦
|
||||
- 影响:
|
||||
- error_items 表新增 verification_status + ai_confidence 列
|
||||
- 新增 correction_logs 表
|
||||
- 分析/推荐查询需加 verification_status 过滤
|
||||
- P02 阶段训练数据来源从"外部标注"变为"内部修正记录"
|
||||
|
||||
## ADR-010: 题库抽象层设计 —— Adapter Pattern 多源统一接入
|
||||
|
||||
- 日期: 2026-05-26
|
||||
- 状态: 已采纳
|
||||
- 决策: 采用 Adapter Pattern(适配器模式)实现多题库源的统一接入。自有题库(PDF 录入)和第三方题库(作业帮 API)通过 `QuestionBankAdapter` 接口统一路由,调用方无感知。
|
||||
- 理由:
|
||||
- 旧架构仅自有题库,新架构决定了双题库源(自有 PDF + 作业帮 API),且未来可能有更多来源
|
||||
- 如果直接在主业务逻辑中写 `if (source === 'zuoyebang')` 分支判断,每加一个题库源就要改业务代码
|
||||
- Adapter Pattern 将"题库源差异"封装在适配器内部,业务逻辑只依赖 `QuestionBankAdapter` 接口
|
||||
- 架构已明确: 决策 #1(双题库源)要求"架构层抽象适配"
|
||||
- 关键设计:
|
||||
- 接口定义: `QuestionBankAdapter { source, search(params), getById(id), healthCheck() }`
|
||||
- 适配器工厂: `AdapterFactory` 按 `source` 字段路由到对应适配器实例
|
||||
- 搜索策略: 并行查询所有适配器 → 合并去重 → 自有题库优先排序
|
||||
- 新增题库源: 只需实现 `QuestionBankAdapter` 接口 + 注册到工厂,零业务代码改动
|
||||
- 影响:
|
||||
- `modules/question-bank/adapters/` 目录结构: `base-adapter.ts`, `self-built.adapter.ts`, `zuoyebang.adapter.ts`, `adapter.factory.ts`
|
||||
- `questions` 表 `source` 字段 = 适配器路由 key(`self_built` | `zuoyebang` | 未来扩展)
|
||||
- `external_id` 字段存储第三方题库的原始 ID,自有题库此字段为空
|
||||
- 健康检查: 每个适配器实现 `healthCheck()`,用于监控外部 API 可用性
|
||||
|
||||
@@ -0,0 +1,472 @@
|
||||
# ErrLens 产品需求文档
|
||||
|
||||
> 版本: v0.4.0 | 状态: 已锁定 | 作者: Arch AI | 最后更新: 2026-05-26(旧架构合并)
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品定位
|
||||
|
||||
### 1.1 一句话描述
|
||||
|
||||
ErrLens 是一款面向小学初中学生的 **AI 错题本**,帮助学生或家长拍照录入错题、自动归类分析、输出 PDF 练习、获得针对性练习推荐。
|
||||
|
||||
### 1.2 核心价值
|
||||
|
||||
传统错题本需要学生手动抄题、自己分类、凭感觉复习。ErrLens 将这些步骤交给 AI:
|
||||
|
||||
- **录入**: 拍照即可,AI 自动识别题目、答案、学科
|
||||
- **分析**: AI 诊断错误原因(知识点欠缺/粗心/审题偏差),汇总薄弱点
|
||||
- **推荐**: 基于错题模式,从题库中匹配同类题目,针对性巩固
|
||||
|
||||
### 1.3 核心飞轮(人机协同版)
|
||||
|
||||
AI 识别不可能 100% 准确(尤其手写体),飞轮不能建立在"AI 完美识别"的假设上。正确模型是:
|
||||
|
||||
```
|
||||
拍照录入错题
|
||||
→ AI 识别 + 置信度评估(识别、分类、知识点标注各带分数)
|
||||
→ 高置信度字段自动填充,低置信度字段高亮提示用户确认
|
||||
→ 用户修正/确认后入库(人工校验过的数据才是"干净数据")
|
||||
→ 干净数据 → AI 分析错误原因 → 识别薄弱知识点
|
||||
→ 推荐同类练习 → 更多错题数据 → 修正记录积累
|
||||
→ 反馈训练(P02 阶段:用"AI 识别 vs 用户修正"的 delta 微调模型)
|
||||
```
|
||||
|
||||
**关键认知**:
|
||||
- AI 识别结果不是"答案",是"草稿"——用户确认前不进入分析管道
|
||||
- 用户每一次修正都是**免费的标注数据**,是训练 P02 自有模型的核心资产
|
||||
- 飞轮飞起来靠的不是 AI 一开始有多准,而是用户修正成本有多低
|
||||
|
||||
### 1.4 竞品差异化
|
||||
|
||||
| 维度 | 传统错题本 App | ErrLens |
|
||||
|------|---------------|---------|
|
||||
| 录入方式 | 手动输入/拍照 OCR | AI 识别 + 置信度标注 + 用户修正 |
|
||||
| 分析深度 | 按学科/章节归类 | 知识点粒度 + 错误原因诊断 |
|
||||
| 推荐逻辑 | 随机/按章节 | 基于错题模式的个性化推荐 |
|
||||
| 数据飞轮 | 无 | 用户修正数据反哺模型训练 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 目标用户
|
||||
|
||||
### 2.1 核心用户画像
|
||||
|
||||
| 画像 | 描述 | 核心需求 |
|
||||
|------|------|----------|
|
||||
| **小初高学生** | 10-18 岁,有日常作业和考试,可自己操作也可家长代操作 | 快速录入、自动整理、考前针对性复习 |
|
||||
| **家长** | 关注孩子学习状况,代孩子拍照整理、打印练习题 | 查看分析报告、了解进步轨迹、错题打印 |
|
||||
| **老师(扩展)** | 管理班级,了解全班错题分布 | 班级错题统计、教学重点调整 |
|
||||
|
||||
### 2.2 MVP 阶段聚焦
|
||||
|
||||
- **P0**: 小初高学生 + 家长(均可操作,交互低门槛年轻化)
|
||||
- **P1**: 学习报告、练习推荐
|
||||
- **Phase 3+**: 老师端
|
||||
|
||||
---
|
||||
|
||||
## 3. 功能需求
|
||||
|
||||
### 3.1 MVP 功能清单 (Phase 2)
|
||||
|
||||
| 模块 | 功能 | 优先级 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| **错题录入** | 拍照录入 | P0 | 拍照 → 图像预处理 → AI 识别题目+答案+学科 |
|
||||
| | 手动录入 | P0 | 文字输入兜底方案 |
|
||||
| | 批量导入 | P2 | 从试卷照片一次识别多道题 |
|
||||
| **错题管理** | 错题列表 | P0 | 按时间/学科/知识点筛选 |
|
||||
| | 错题详情 | P0 | 题目、错误答案、正确答案、分析 |
|
||||
| | 错题编辑 | P1 | 修正 AI 识别结果 |
|
||||
| **AI 分析** | 错误原因诊断 | P0 | 标注错误类型(知识点/粗心/审题) |
|
||||
| | 薄弱点汇总 | P0 | 按知识点统计薄弱程度 |
|
||||
| | 学习报告 | P1 | 周/月度错题趋势报告 |
|
||||
| **练习推荐** | 同类题推荐 | P1 | 关键词+Jaccard 粗筛 → AI 精排 |
|
||||
| | 智能组卷 | P2 | 自动生成针对性练习卷 |
|
||||
| **错题打印** | PDF 输出 | P0 | 选题 → 生成 PDF → 下载(24h 有效) |
|
||||
| **用户系统** | 注册/登录 | P0 | 微信授权登录 |
|
||||
| | 个人资料 | P1 | 年级(小初)、学科设置 |
|
||||
| | 学科管理 | P1 | 添加/切换学科 |
|
||||
|
||||
### 3.2 功能详情
|
||||
|
||||
#### 3.2.1 拍照录入(P0 核心流程)
|
||||
|
||||
```
|
||||
拍照 → 裁剪/确认 → 图像预处理 → AI 识别 → 置信度评估 → 确认/修正 → 保存
|
||||
│
|
||||
├─ 透视校正(手动框4角)
|
||||
├─ CLAHE 增强(光照归一化)
|
||||
└─ 笔迹去除(红/蓝笔HSV自动去除)
|
||||
↓
|
||||
提取: 题目文本、学科、知识点、错误答案
|
||||
每个字段附带 confidence: 0.0-1.0
|
||||
↓
|
||||
高置信(>0.9): 绿色标记,自动采纳
|
||||
中置信(0.7-0.9): 黄色标记,建议检查
|
||||
低置信(<0.7): 红色标记,提示手动确认
|
||||
↓
|
||||
用户修正 → 修正字段记录到 correction_log
|
||||
原始 AI 结果 + 用户修正值 一并入库
|
||||
```
|
||||
|
||||
**置信度分字段展示**:
|
||||
- OCR 文本置信度(手写体通常更低)
|
||||
- 学科分类置信度
|
||||
- 知识点标注置信度
|
||||
- 错误类型诊断置信度
|
||||
|
||||
每个字段独立展示置信度,用户可逐个修正。低置信度字段高亮提示,不阻塞整体流程(用户可跳过修正,但数据标记为 unverified)。
|
||||
|
||||
**交互要点**:
|
||||
- 拍照后展示识别结果,低置信度字段红色高亮
|
||||
- 点击任意 AI 识别结果可进入编辑态修正
|
||||
- 识别中展示 loading 动画(预期 3-5 秒)
|
||||
- 支持从相册选择已有截图/照片
|
||||
- "正确答案"字段独立,可留空(学生可能还不知道正确答案)
|
||||
|
||||
**AI 能力需求**:
|
||||
- OCR 文字识别(手写体 + 印刷体)
|
||||
- 学科分类(数学/语文/英语/物理/化学/生物/地理/历史/政治)
|
||||
- 知识点标注(如"二次函数"、"定语从句")
|
||||
- 题目结构提取(题干/选项/答案区)
|
||||
|
||||
#### 3.2.2 错题列表(P0)
|
||||
|
||||
**列表视图**:
|
||||
- 默认按时间倒序
|
||||
- 筛选器:学科、知识点、时间范围、错误类型
|
||||
- 每项展示:题目缩略图、学科标签、知识点标签、录入日期
|
||||
- 搜索:按题目关键词搜索
|
||||
|
||||
**分组视图**:
|
||||
- 按知识点分组(展示每个知识点的错题数量和掌握度)
|
||||
- 按学科分组
|
||||
|
||||
#### 3.2.3 AI 分析(P0)
|
||||
|
||||
**单题分析**:
|
||||
- 错误类型分类:知识点欠缺 / 粗心失误 / 审题偏差 / 概念混淆
|
||||
- 关联知识点(可能有多个)
|
||||
- 难度评估:基础 / 中等 / 拔高
|
||||
|
||||
**汇总分析**:
|
||||
- 薄弱知识点排序(按错误频率和严重程度)
|
||||
- 错误类型分布饼图
|
||||
- 学科间对比
|
||||
|
||||
#### 3.2.4 错题打印/PDF 输出(P0)
|
||||
|
||||
**打印流程**:
|
||||
```
|
||||
选题(错题列表中勾选)→ 生成 PDF → 预览 → 下载 → 自行打印
|
||||
```
|
||||
|
||||
**PDF 排版优先级**:
|
||||
1. 结构化内容(题库匹配的题目,文字+公式清晰排版)
|
||||
2. 增强图片(经 CLAHE+笔迹去除处理后的图片)
|
||||
3. 原始图片(无匹配时兜底)
|
||||
|
||||
**交互要点**:
|
||||
- 错题列表页支持多选模式(勾选要打印的题目)
|
||||
- PDF 生成中展示进度(预计 5-10 秒)
|
||||
- 生成后预览页支持手势缩放
|
||||
- 下载链接 24 小时有效,过期自动清理
|
||||
- 支持分享到微信(发送 PDF 文件给家长/老师)
|
||||
|
||||
#### 3.2.5 练习推荐(P1)
|
||||
|
||||
**推荐逻辑**:
|
||||
1. 基于薄弱知识点权重排序
|
||||
2. 匹配题库中同知识点、同难度题目
|
||||
3. 优先推荐"高频易错"题型
|
||||
4. 已掌握的题目降低推荐权重
|
||||
|
||||
**推荐展示**:
|
||||
- 每日推荐: 3-5 道针对性练习
|
||||
- 自定义练习: 选择知识点 + 数量,一键生成
|
||||
|
||||
### 3.3 数据质量与人机协同修正
|
||||
|
||||
#### 3.3.1 核心设计原则
|
||||
|
||||
**用户每次修正都是免费的标注数据。** 这是 ErrLens 相对于纯 AI 方案的核心壁垒。
|
||||
|
||||
- AI 识别是"草稿",不是"答案"
|
||||
- 用户确认前的数据不进入分析和推荐管道
|
||||
- 修正记录(AI 输出 vs 用户修正)是 P02 自研模型的核心训练数据
|
||||
|
||||
#### 3.3.2 数据校验状态机
|
||||
|
||||
```
|
||||
AI 识别完成
|
||||
│
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ raw │ 原始 AI 识别结果,未确认
|
||||
│ 进入错题列表 │ 仅对用户可见,不参与分析
|
||||
│ 不可用于推荐 │
|
||||
└───────┬────────┘
|
||||
│ 用户查看/修正
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ reviewed │ 用户已确认/修正
|
||||
│ 参与薄弱点分析 │ 所有字段经人工校验
|
||||
│ 参与练习推荐 │
|
||||
└───────┬────────┘
|
||||
│ 用户再次修正
|
||||
▼
|
||||
┌────────────────┐
|
||||
│ corrected │ 用户二次修正(覆盖上次)
|
||||
│ 分析/推荐更新 │
|
||||
└────────────────┘
|
||||
```
|
||||
|
||||
**关键规则**:
|
||||
- `raw` 状态的错题仅用户自己可见,不计入 AnalysisReport
|
||||
- 从 `raw` → `reviewed` 的最小操作:用户至少查看一次并点击"确认"按钮(批量确认支持)
|
||||
- 从 `reviewed` → `corrected`:用户主动编辑了 AI 识别的字段
|
||||
- 30 天后仍为 `raw` 的错题标记为 `stale`,系统定期提醒用户确认
|
||||
|
||||
#### 3.3.3 置信度分字段评估
|
||||
|
||||
AI 对每个字段独立输出置信度 [0.0-1.0]:
|
||||
|
||||
| 字段 | 影响因素 | 典型置信度 |
|
||||
|------|----------|-----------|
|
||||
| 题目文本 (question_text) | 手写体 vs 印刷体、图片清晰度 | 印刷体 0.9+,工整手写 0.7-0.9,潦草手写 0.5-0.7 |
|
||||
| 学科分类 (subject_id) | 题目文本中的关键词密度 | 数学符号明显的题 0.9+,文科主观题 0.6-0.8 |
|
||||
| 知识点标注 (knowledge_points) | 知识树覆盖度、题干关键词匹配 | 主流知识点 0.8+,冷门知识点 0.5-0.7 |
|
||||
| 错误类型 (error_type) | 错误答案与正确答案的对比清晰度 | 有正确答案时 0.8+,无正确答案时 0.5-0.7 |
|
||||
| 正确答案 (correct_answer) | AI 是否能看到标答区域 | 扫描的试卷答案区 0.9+,纯题目照片 0.5-0.7 |
|
||||
|
||||
#### 3.3.4 修正记录 (Correction Log)
|
||||
|
||||
每次用户修正,记录原始 AI 值和用户修正值:
|
||||
|
||||
```json
|
||||
{
|
||||
"error_item_id": "uuid",
|
||||
"field": "knowledge_points",
|
||||
"ai_value": [1021], // AI 识别:顶点坐标
|
||||
"user_value": [1022], // 用户修正:图像性质
|
||||
"ai_confidence": 0.72,
|
||||
"corrected_at": "2026-05-26T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**用途**:
|
||||
1. **即时**: 修正后立即更新错题数据,参与分析和推荐
|
||||
2. **分析**: 统计各字段修正率,识别 AI 薄弱环节(哪个学科/知识点修正率最高)
|
||||
3. **训练 (P02)**: AI 识别 vs 用户修正的 delta 是微调自有模型的核心训练数据
|
||||
|
||||
#### 3.3.5 交互策略
|
||||
|
||||
**确认界面分级提示**:
|
||||
|
||||
| 置信度区间 | 视觉标记 | 交互行为 |
|
||||
|-----------|---------|---------|
|
||||
| > 0.9 | 绿色边框 | 默认展示,无需操作 |
|
||||
| 0.7 - 0.9 | 黄色边框 + 虚线 | 建议点击查看 |
|
||||
| < 0.7 | 红色边框 + 闪烁 | 弹提示建议手动修正 |
|
||||
| — | 无 AI 结果 | 空白输入框,用户手动填写 |
|
||||
|
||||
**批量确认**: 多道题一起拍照后,在列表页支持批量"一键确认",降低确认摩擦。
|
||||
|
||||
**修正激励**: 未来(Phase 3)可引入修正积分/成就,鼓励学生认真修正(干净数据=更好的推荐=对自己有用)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 非功能需求
|
||||
|
||||
### 4.1 性能
|
||||
|
||||
| 指标 | 目标值 |
|
||||
|------|--------|
|
||||
| 小程序首屏加载 | < 2s |
|
||||
| AI 识别响应 | < 5s(拍照→结果展示) |
|
||||
| 列表滚动 | 60fps,虚拟列表 |
|
||||
| 图片上传 | < 3s(压缩后 < 500KB) |
|
||||
| API 响应 | P95 < 200ms |
|
||||
|
||||
### 4.2 安全
|
||||
|
||||
- 微信授权登录,不存储密码
|
||||
- 用户错题数据隔离,API 层鉴权
|
||||
- 图片上传签名 URL,防恶意上传
|
||||
- 敏感信息(姓名、学校)传输加密
|
||||
|
||||
### 4.3 兼容性
|
||||
|
||||
- 微信小程序基础库 ≥ 3.0
|
||||
- iOS 14+ / Android 8+
|
||||
- 屏幕适配:375-428px 逻辑宽度
|
||||
|
||||
### 4.4 可用性
|
||||
|
||||
- 核心流程(拍照→保存)不超过 3 步
|
||||
- 关键操作有明确反馈(loading/成功/失败)
|
||||
- 网络异常时有缓存兜底和重试提示
|
||||
- 首次使用有引导(不强制)
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据模型(高层)
|
||||
|
||||
### 5.1 核心实体
|
||||
|
||||
```
|
||||
User (用户)
|
||||
- id, nickname, avatar, grade, created_at
|
||||
|
||||
Subject (学科)
|
||||
- id, name, icon
|
||||
|
||||
KnowledgePoint (知识点)
|
||||
- id, name, subject_id, parent_id (树形结构)
|
||||
|
||||
ErrorItem (错题)
|
||||
- id, user_id, subject_id
|
||||
- image_url (原始图片)
|
||||
- question_text (题目文本,AI 提取)
|
||||
- wrong_answer (错误答案)
|
||||
- correct_answer (正确答案,可选)
|
||||
- knowledge_points[] (关联知识点)
|
||||
- error_type (错误类型)
|
||||
- difficulty (难度)
|
||||
- verification_status: raw | reviewed | corrected | stale
|
||||
- ai_confidence: JSONB(各字段的 AI 置信度)
|
||||
- note (学生备注)
|
||||
- created_at, updated_at
|
||||
|
||||
CorrectionLog (修正记录) [P02 训练数据]
|
||||
- id, error_item_id, field_name
|
||||
- ai_value (AI 原始值)
|
||||
- user_value (用户修正值)
|
||||
- ai_confidence (该字段置信度)
|
||||
- corrected_at
|
||||
|
||||
AnalysisReport (分析报告)
|
||||
- id, user_id
|
||||
- period (week/month)
|
||||
- weak_points[] (薄弱知识点+权重)
|
||||
- error_type_distribution
|
||||
- trend (进步/退步/持平)
|
||||
- generated_at
|
||||
|
||||
PracticeRecommendation (练习推荐)
|
||||
- id, user_id
|
||||
- knowledge_points[]
|
||||
- questions[] (推荐的题目)
|
||||
- generated_at
|
||||
|
||||
PrintTask (打印任务)
|
||||
- id, user_id
|
||||
- error_item_ids[]
|
||||
- output_mode: pdf
|
||||
- file_url, expires_at
|
||||
- created_at
|
||||
```
|
||||
|
||||
### 5.2 MVP 阶段简化
|
||||
|
||||
- KnowledgePoint 使用预设知识树(非 AI 自动生成)
|
||||
- 题库推荐初期使用外部题库 API,后续自建
|
||||
- 分析报告先做单题分析,汇总报告 P1
|
||||
- **CorrectionLog MVP 即入库,但 P02 阶段才用于训练**
|
||||
- **AnalysisReport 仅统计 verification_status != raw 的错题**
|
||||
|
||||
---
|
||||
|
||||
## 6. 用户旅程
|
||||
|
||||
### 6.1 核心旅程:首次使用
|
||||
|
||||
```
|
||||
1. 打开小程序 → 微信授权登录
|
||||
2. 设置年级 + 学科(初中二年级 + 数学)
|
||||
3. 进入首页 → 看到空状态:「还没有错题,拍一张吧」
|
||||
4. 点击拍照按钮 → 拍摄一道错题 → AI 识别中(3-5s)
|
||||
5. 确认识别结果 → 「分析完成!这道题是【二次函数-顶点坐标】没掌握」
|
||||
6. 回到首页 → 错题列表出现第一条记录
|
||||
7. 底部「薄弱点」tab → 看到第一个薄弱知识点标记
|
||||
```
|
||||
|
||||
### 6.2 核心旅程:日常使用
|
||||
|
||||
```
|
||||
1. 每天做完作业 → 打开 ErrLens
|
||||
2. 拍照录入 2-3 道错题
|
||||
3. 查看 AI 分析:「今天主要是计算粗心」
|
||||
4. 周末 → 收到推送:「本周薄弱点是【全等三角形判定】,已生成针对性练习」
|
||||
5. 完成推荐练习 → 错题本记录更新
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 项目分期
|
||||
|
||||
| 阶段 | 范围 | 交付物 |
|
||||
|------|------|--------|
|
||||
| Phase 1 (当前) | 基础搭建 | 协作框架、脚手架、信息架构 |
|
||||
| Phase 2 (MVP) | 核心闭环 | 拍照录入 + 错题管理 + AI 分析 + 用户系统 |
|
||||
| Phase 3 | 功能完善 | 练习推荐 + 学习报告 + 家长端 + P02/P03 启动 |
|
||||
| Phase 4 | 打磨发布 | 性能优化 + 安全审计 + 上线准备 |
|
||||
|
||||
---
|
||||
|
||||
## 8. MVP 验收标准 (Phase 2)
|
||||
|
||||
- [ ] 用户可以微信授权登录
|
||||
- [ ] 用户拍照后 5 秒内看到识别结果(含置信度标注)
|
||||
- [ ] 图像预处理管线完整运行(透视校正+增强+笔迹去除)
|
||||
- [ ] 低置信度字段红色高亮,用户可逐字段修正
|
||||
- [ ] 用户可以批量确认多道错题
|
||||
- [ ] 用户可以在错题列表中按学科、校验状态筛选
|
||||
- [ ] AI 可以诊断错误原因并关联知识点
|
||||
- [ ] 用户可以查看薄弱点汇总(仅统计 reviewed 及以上状态)
|
||||
- [ ] 用户可以勾选错题生成 PDF 并下载(24h 有效)
|
||||
- [ ] PDF 排版清晰度满足打印标准(300DPI)
|
||||
- [ ] 核心流程有完整的 loading/成功/失败反馈
|
||||
- [ ] 置信度 UI 分级显示(绿/黄/红)正确运行
|
||||
- [ ] `raw` 状态的错题不参与分析和推荐
|
||||
|
||||
---
|
||||
|
||||
## 9. 风险与假设
|
||||
|
||||
### 9.1 关键假设
|
||||
|
||||
1. AI OCR 对手写体的识别率能达到可用水平(>70% 即满足 MVP,用户修正兜底)
|
||||
2. 用户愿意在拍照后花 10-20 秒确认/修正 AI 识别结果
|
||||
3. 学生/家长愿意每天花 2-3 分钟录入错题
|
||||
4. 微信小程序审核能通过教育类目
|
||||
5. 题库资源可获取(合作或爬取)
|
||||
|
||||
### 9.2 主要风险
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|------|------|----------|
|
||||
| AI 识别准确率不足 | 用户不信任,放弃使用 | **架构层面:置信度分层 + 用户修正闭环,AI 定位为"草稿"而非"答案";MVP 优先优化印刷体 OCR** |
|
||||
| 用户修正率低(不确认 raw 数据) | 飞轮无数据可用 | 批量确认降低摩擦;定期提醒 stale 数据;确认操作设计为一次点击 |
|
||||
| 修正数据质量差(用户胡乱修正) | 训练数据被污染 | 修正记录对比 AI 值,异常修正(如学科从数学改体育)标记审查 |
|
||||
| 图像预处理管线复杂度高 | 开发周期延长 | 模块化降级策略:任何模块失败不阻塞整体流程;Coze 沙盒中重新调优参数 |
|
||||
| 题库内容不足 | 推荐功能无法上线 | 先接入第三方题库 API |
|
||||
| 增长缓慢 | 飞轮无法启动 | 考虑学校/班级团购模式 |
|
||||
| 小程序审核受限 | 无法发布 | 优先微信小程序,H5 兜底 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 待决策事项
|
||||
|
||||
以下事项需要人类确认后锁定:
|
||||
|
||||
- [x] 题库来源:**两者都要**。自有本地题库(PDF 手动录入)提供差异化内容,作业帮 API 冷启动兜底。架构需支持多题库源抽象层。
|
||||
- [x] AI 能力来源:**分层使用**。Coze SDK 做测试/验证,其他 AI 做 Coding 实现,Claude 做架构设计。各角色可交叉协作,不僵化绑定。
|
||||
- [x] 用户体系:**MVP 仅微信小程序登录**。增长模式:已有用户/老师/机构通过扫码分享注册,形成用户群的**树状结构**(邀请链)。架构需支持邀请关系建模。
|
||||
- [x] 商业化方向:**基础免费 + 会员**。MVP 阶段全功能免费(积累用户),会员功能在 Phase 3 设计。架构预留能力开关。
|
||||
- [x] 首发学科:**数学 + 英语**。两个学科同时首发,知识树需覆盖数学和英语两套知识体系。
|
||||
|
||||
---
|
||||
|
||||
*PRD 状态: 草案 → 已锁定(v0.3.0,5 项决策已确认)*
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
| 文件 | 说明 | 状态 |
|
||||
|------|------|------|
|
||||
| PRD.md | 产品需求文档(主文档) | 待编写 |
|
||||
| PRD.md | 产品需求文档(主文档) | ✅ 草案完成,待人类审阅 |
|
||||
|
||||
## 编写时机
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
| 文件 | 说明 | 状态 |
|
||||
|------|------|------|
|
||||
| 总体架构.md | 系统总体架构设计 | 待编写 |
|
||||
| 技术选型.md | 技术选型评估 | 待编写 |
|
||||
| 模块设计.md | 主要模块设计 | 待编写 |
|
||||
| 数据模型.md | 数据库 Schema 设计 | 待编写 |
|
||||
| 总体架构.md | 系统总体架构设计 | ✅ 完成 |
|
||||
| 技术选型.md | 技术选型评估 | ✅ 完成 |
|
||||
| 模块设计.md | 主要模块设计 | ✅ 完成 |
|
||||
| 数据模型.md | 数据库 Schema 设计 | ✅ 完成 |
|
||||
|
||||
## 编写时机
|
||||
|
||||
PRD 完成,进入架构设计阶段后开始编写。
|
||||
架构设计文档已完成,随 PRD v0.1.0 同步交付。
|
||||
|
||||
## 相关链接
|
||||
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# 总体架构
|
||||
|
||||
> 版本: v0.4.0 | 作者: Arch AI | 基于 PRD v0.4.0 + 旧架构合并
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统全景
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ 用户层 │
|
||||
│ 学生 │ 家长 │ 老师 │
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ P01 小程序 │ │ P03 Web 后台 │ │ 未来: 家长端 │
|
||||
│ (Taro+React) │ │ (Next.js) │ │ 公众号/小程序 │
|
||||
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
|
||||
│ │ │
|
||||
└────────────────────┼────────────────────┘
|
||||
│ HTTPS
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ API 网关层 │
|
||||
│ NestJS (P01/server) │
|
||||
│ Auth │ Rate Limit │ Validation │
|
||||
└─────────────────┬───────────────────┘
|
||||
│
|
||||
┌─────────────────────┼──────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐
|
||||
│ 核心服务层 │ │ AI 分析服务 │ │ 文件存储 │
|
||||
│ 用户/错题/题库 │ │ 错误诊断/推荐 │ │ 图片/CDN │
|
||||
└────────┬────────┘ └────────┬────────┘ └──────────┬──────────┘
|
||||
│ │ │
|
||||
│ ┌────────┴────────┐ │
|
||||
│ │ P02 训练引擎 │ │
|
||||
│ │ (Python/PyTorch)│ │
|
||||
│ │ Phase 2 启动 │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ PostgreSQL │ │ S3 兼容存储 │
|
||||
│ (主数据库) │ │ (图片/文件) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
## 2. 架构层级
|
||||
|
||||
| 层 | 技术 | 职责 |
|
||||
|----|------|------|
|
||||
| 展示层 | Taro 4 + React 18 / Next.js | UI 渲染、用户交互 |
|
||||
| 网关层 | NestJS 中间件 | 鉴权、限流、参数校验 |
|
||||
| 服务层 | NestJS Service | 业务逻辑编排 |
|
||||
| AI 层 | Coze SDK → P02 PyTorch | OCR、错误诊断、推荐 |
|
||||
| 数据层 | PostgreSQL + Drizzle ORM | 持久化存储 |
|
||||
| 存储层 | S3 兼容存储 | 图片/文件存储 |
|
||||
|
||||
## 3. 数据流
|
||||
|
||||
### 3.1 拍照录入(P0 热路径 — 含修正闭环)
|
||||
|
||||
```
|
||||
[小程序] 拍照/选图
|
||||
│
|
||||
▼
|
||||
[Gateway] 鉴权 + 限流
|
||||
│
|
||||
▼
|
||||
[File Storage] 上传原图 → 返回 URL
|
||||
│
|
||||
▼
|
||||
[Image Pipeline] 图像预处理(提升 OCR 识别率)
|
||||
├─ 透视校正(用户手动框 4 角)
|
||||
├─ 增强处理(CLAHE + Gamma + 对比度增强)
|
||||
└─ 笔迹去除(红/蓝笔 HSV 自动去除,黑笔可选手动圈选)
|
||||
│
|
||||
▼
|
||||
[AI Service] 增强后图片 URL → Coze SDK
|
||||
├─ OCR 提取题目文本 (confidence: 0.5-0.95)
|
||||
├─ 学科分类 (confidence: 0.7-0.95)
|
||||
├─ 知识点标注 (confidence: 0.5-0.9)
|
||||
└─ 错误类型诊断 (confidence: 0.5-0.9)
|
||||
│
|
||||
▼
|
||||
[Core Service]
|
||||
├─ 创建 ErrorItem (verification_status = "raw")
|
||||
├─ 保存 ai_confidence JSONB
|
||||
└─ 返回识别结果 + 置信度 → 低置信度字段标记
|
||||
│
|
||||
▼
|
||||
[小程序] 展示识别结果(绿/黄/红分级)
|
||||
├─ 用户逐字段修正低置信度字段
|
||||
├─ 每修正一个字段 → 记录 CorrectionLog
|
||||
└─ 用户点击"确认"
|
||||
│
|
||||
▼
|
||||
[Core Service]
|
||||
├─ PATCH ErrorItem (verification_status = "reviewed")
|
||||
├─ 修正的字段更新为用户修正值
|
||||
└─ AI 原始值保留在 CorrectionLog
|
||||
│
|
||||
▼
|
||||
[Analysis Pipeline] reviewed 状态的错题进入分析管道
|
||||
```
|
||||
|
||||
### 3.2 练习推荐(P1)
|
||||
|
||||
```
|
||||
[小程序] 请求推荐
|
||||
│
|
||||
▼
|
||||
[Recommendation Service]
|
||||
├─ 查询用户薄弱点 (AnalysisReport)
|
||||
│ └─ 仅统计 verification_status != "raw" 的错题
|
||||
├─ 粗筛:关键词 + Jaccard 相似度 → 候选集
|
||||
├─ 精排:AI 语义匹配(候选集不足时)
|
||||
└─ 排序 + 去重 → 返回推荐列表
|
||||
```
|
||||
|
||||
### 3.3 错题打印/PDF 输出(P0)
|
||||
|
||||
```
|
||||
[小程序] 在错题列表中选择
|
||||
│
|
||||
▼
|
||||
[Print Service]
|
||||
├─ 获取错题数据(结构化内容优先,无匹配时降级为增强图片)
|
||||
├─ PDFKit 排版生成(A4/300DPI)
|
||||
└─ 上传到 S3 → 返回临时下载链接(24h 过期)
|
||||
│
|
||||
▼
|
||||
[小程序] 预览 → 下载 PDF → 自行打印
|
||||
```
|
||||
|
||||
## 4. 关键设计决策
|
||||
|
||||
### 4.1 单体后端 → 未来拆分
|
||||
|
||||
MVP 阶段使用单一 NestJS 后端服务。Phase 3 按业务域拆分为微服务(用户服务、错题服务、推荐服务)。
|
||||
|
||||
**原因**: MVP 阶段团队 1 人,单体架构开发效率最高,NestJS 模块化设计天然支持后续拆分。
|
||||
|
||||
### 4.2 AI 能力分层
|
||||
|
||||
```
|
||||
Phase 2: Coze SDK(快速上线,无需自训模型)
|
||||
↓
|
||||
Phase 3: P02 自训模型(针对错题领域微调,降低 API 成本)
|
||||
```
|
||||
|
||||
**原因**: MVP 验证产品价值,用现成 AI 服务。自研模型在产品方向确认后投入。
|
||||
|
||||
### 4.3 题库策略
|
||||
|
||||
MVP 阶段接入第三方题库 API(如作业帮开放平台),Phase 3 评估是否自建题库。
|
||||
|
||||
### 4.4 数据质量:人机协同修正闭环
|
||||
|
||||
**核心问题**: AI 识别(尤其手写体 OCR)不可能 100% 准确,错误数据进入分析管道会污染飞轮。
|
||||
|
||||
**架构对策**:
|
||||
|
||||
| 层面 | 机制 | 说明 |
|
||||
|------|------|------|
|
||||
| 识别时 | 分字段置信度 | AI 对每个字段独立评分,低置信度高亮提示 |
|
||||
| 入库时 | verification_status | `raw`(未确认) → `reviewed`(已确认) → `corrected`(已修正) |
|
||||
| 分析时 | 数据质量门控 | AnalysisReport 仅统计 `reviewed` 及以上状态的错题 |
|
||||
| 修正时 | CorrectionLog | 记录 AI 值 vs 用户修正值,P02 阶段用于微调模型 |
|
||||
| 交互时 | 低摩擦确认 | 批量确认、置信度分级 UI 降低修正成本 |
|
||||
|
||||
**关键原则**: AI 是草稿,用户是编辑。用户每一次修正都是免费的标注数据。
|
||||
|
||||
### 4.5 数据飞轮——双通道采集
|
||||
|
||||
ErrLens 有两条互补的数据飞轮,共同为 P02 自研模型提供训练数据:
|
||||
|
||||
| 通道 | 数据来源 | 收集内容 | P02 用途 |
|
||||
|------|---------|---------|----------|
|
||||
| **文本侧** | CorrectionLog | AI 识别值 vs 用户修正值(知识点、学科、错误类型) | 微调 OCR/分类/诊断模型 |
|
||||
| **图像侧** | 用户修正操作 | 透视校正关键点、笔迹分割标记、题目匹配反馈 | 训练图像预处理模型(透视校正、笔迹去除) |
|
||||
|
||||
两条飞轮共享同一个核心假设:产品设计得越好用,用户修正越自然,训练数据越丰富,模型越强——正向循环。
|
||||
|
||||
## 5. 部署架构(目标态)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 微信小程序平台 │
|
||||
│ (代码包 < 2MB,分包加载) │
|
||||
└──────────────────┬───────────────────────┘
|
||||
│ HTTPS
|
||||
▼
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Nginx (HTTPS) │
|
||||
│ 负载均衡 + 静态资源 │
|
||||
└──────────────────┬───────────────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ NestJS │ │ NestJS │ │ NestJS │
|
||||
│ 实例 1 │ │ 实例 2 │ │ 实例 3 │
|
||||
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
||||
│ │ │
|
||||
└────────────┼────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│PostgreSQL│ │ Redis │ │ S3/MinIO │
|
||||
│ 主库 │ │ 缓存 │ │ 图片存储 │
|
||||
└──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
MVP 阶段用单实例部署,Nginx + 1x NestJS + 1x PostgreSQL + 1x MinIO 即可。
|
||||
|
||||
**三环境规划**(适配 Coze 沙盒自动化测试):
|
||||
|
||||
| 环境 | 用途 | Coze 沙盒 |
|
||||
|------|------|----------|
|
||||
| dev | 本地开发 + AI 快速迭代 | Coze 沙盒自动运行单元/集成测试 |
|
||||
| test | 灰度验证 + 真机测试 | Coze 沙盒运行完整回归测试 |
|
||||
| prod | 生产环境 | 仅监控,不跑自动测试 |
|
||||
|
||||
灰度发布策略: test 环境验证通过 → prod 先切换 10% 流量 → 监控无异常 → 全量切换。
|
||||
|
||||
## 6. MVP 范围与边界
|
||||
|
||||
| 模块 | MVP (Phase 2) | Phase 3+ |
|
||||
|------|---------------|----------|
|
||||
| 小程序端 | 错题录入、列表、详情、分析、PDF 输出 | 练习推荐、报告、家长端 |
|
||||
| 后端 API | REST API、鉴权、文件上传、图像预处理、打印 | GraphQL、微服务拆分 |
|
||||
| AI 能力 | Coze SDK(OCR + 分类)+ 关键词粗筛 | P02 自研模型 + AI 精排 |
|
||||
| 图像处理 | 透视校正 + CLAHE + 笔迹去除(红/蓝) | 黑笔自动去除、模型训练 |
|
||||
| 数据库 | PostgreSQL 单库 | 读写分离、缓存层 |
|
||||
| 部署 | 三环境(dev/test/prod)+ Coze 沙盒 | 容器化、CI/CD |
|
||||
|
||||
---
|
||||
|
||||
*关联: PRD.md → 模块设计.md → 数据模型.md*
|
||||
@@ -0,0 +1,93 @@
|
||||
# 技术选型
|
||||
|
||||
> 版本: v0.4.0 | 作者: Arch AI | 基于 PRD v0.4.0 + 旧架构合并
|
||||
|
||||
---
|
||||
|
||||
## 1. 技术栈总览
|
||||
|
||||
| 层 | 选型 | 版本 | 选型理由 |
|
||||
|----|------|------|----------|
|
||||
| 小程序框架 | Taro | 4.1.x | 跨端能力(微信/抖音/H5),React 生态 |
|
||||
| UI 框架 | React | 18.x | Hooks 生态成熟,社区资源丰富 |
|
||||
| UI 组件库 | shadcn/ui (Taro 适配) | — | 可定制、无样式锁定、复制即用 |
|
||||
| 样式方案 | Tailwind CSS | 4.x | 原子化 CSS,与 shadcn/ui 深度集成 |
|
||||
| 状态管理 | Zustand | 5.x | 轻量、无 Provider、TS 友好 |
|
||||
| 后端框架 | NestJS | 10.x | 模块化、IoC、企业级 Node.js 框架 |
|
||||
| ORM | Drizzle ORM | 0.45.x | Type-safe、轻量、SQL-like API |
|
||||
| 数据库 | PostgreSQL | 15+ | 关系型、JSON 支持、全文搜索 |
|
||||
| AI 能力 | Coze SDK | — | 现成 OCR + NLP 能力,无需自训 |
|
||||
| 文件存储 | S3 兼容 (MinIO/Supabase) | — | 图片上传,标准化接口 |
|
||||
| 包管理 | pnpm | 9.x | monorepo 原生支持、磁盘效率高 |
|
||||
| 图像处理 | Sharp | 0.33.x | Node.js 高性能图像处理,CLAHE/旋转/缩放 |
|
||||
| PDF 生成 | PDFKit | 0.15.x | Node.js PDF 生成,A4/300DPI 排版 |
|
||||
| 测试 | Jest | 29.x | 生态最大、React 测试工具链成熟 |
|
||||
| 语言 | TypeScript | 5.x | 全栈类型安全 |
|
||||
|
||||
## 2. 关键选型决策
|
||||
|
||||
### 2.1 为什么是 Taro 而不是 uni-app?
|
||||
|
||||
| 维度 | Taro | uni-app |
|
||||
|------|------|---------|
|
||||
| React 支持 | 一等公民 | Vue 为主 |
|
||||
| shadcn/ui | 社区已有 Taro 适配 | 无 |
|
||||
| 跨端编译 | 编译时转译,性能好 | 运行时适配,有开销 |
|
||||
| 社区 | 京东维护,活跃 | DCloud 维护 |
|
||||
|
||||
**决策**: Taro。项目已用 React + shadcn/ui 体系,Taro 天然匹配。最终仅发布微信小程序,但 Taro 保留了未来迁移 H5/原生 APP 的灵活性(后端 API 不变,只换前端壳)。
|
||||
|
||||
### 2.2 为什么是 NestJS 而不是 Express/Fastify?
|
||||
|
||||
| 维度 | NestJS | Express |
|
||||
|------|--------|---------|
|
||||
| 架构约束 | 模块化 + 依赖注入 | 无约束,需自建 |
|
||||
| TypeScript | 一等公民 | 需额外配置 |
|
||||
| 测试友好 | 内置 TestingModule | 需自建 |
|
||||
| 微服务支持 | 内置 Transport 层 | 需自建 |
|
||||
|
||||
**决策**: NestJS。自建后端是刚性需求——Coze 沙盒自动化测试需要完整的后端环境(长连接、GPU 调用、流式响应),微信云开发(云函数 10s 超时)无法满足。同时 NestJS 模块化设计降低长期拆分微服务的迁移成本。
|
||||
|
||||
### 2.3 为什么是 Drizzle ORM 而不是 Prisma?
|
||||
|
||||
| 维度 | Drizzle | Prisma |
|
||||
|------|---------|--------|
|
||||
| Bundle 大小 | < 10KB | ~500KB+ (engine) |
|
||||
| SQL 控制 | SQL-like API,接近原生 | 抽象层厚 |
|
||||
| 边缘运行时 | 支持 | 需额外配置 |
|
||||
| 迁移工具 | 内置 | 内置 |
|
||||
|
||||
**决策**: Drizzle。小程序场景对包体积敏感,Drizzle 更轻。且项目初期 SQL 灵活性重要。
|
||||
|
||||
### 2.4 AI 方案: Coze SDK vs 直接调用 API
|
||||
|
||||
| 维度 | Coze SDK | 直接 Claude/GPT API |
|
||||
|------|----------|---------------------|
|
||||
| 开发量 | 低(工作流已封装) | 高(需自建 prompt chain) |
|
||||
| OCR 能力 | 内置 | 需额外组件 |
|
||||
| 成本 | 按调用次数 | 按 token |
|
||||
| 可定制性 | 低 | 高 |
|
||||
|
||||
**决策**: MVP 用 Coze SDK 快速上线,Phase 3 评估自研模型(P02)。
|
||||
|
||||
## 3. 暂不引入的技术
|
||||
|
||||
| 技术 | 原因 | 引入时机 |
|
||||
|------|------|----------|
|
||||
| Redis | MVP 无高频读写,PostgreSQL 足够 | Phase 3(推荐/排行榜缓存) |
|
||||
| Docker/K8s | 单实例部署,先跑起来 | Phase 3(CI/CD 配套) |
|
||||
| GraphQL | REST 足够,前端查询模式简单 | Phase 3(复杂查询场景) |
|
||||
| Elasticsearch | PostgreSQL 全文搜索够用 | Phase 3(题库搜索量大时) |
|
||||
| Kafka/RabbitMQ | 无异步解耦需求 | Phase 3(微服务拆分后) |
|
||||
|
||||
## 4. 技术债务记录
|
||||
|
||||
| 债务 | 说明 | 偿还计划 |
|
||||
|------|------|----------|
|
||||
| P01 项目代码是"代码检测"模板 | 需要替换为错题本业务代码 | Phase 2 开发时自然替换 |
|
||||
| 无 CI/CD | 手动测试部署 | Phase 3 引入 GitHub Actions |
|
||||
| 无监控 | 无 APM/日志收集 | Phase 3 引入 Sentry + 阿里云日志 |
|
||||
|
||||
---
|
||||
|
||||
*关联: 总体架构.md → 模块设计.md*
|
||||
@@ -0,0 +1,425 @@
|
||||
# 数据模型
|
||||
|
||||
> 版本: v0.4.0 | 作者: Arch AI | 基于 PRD v0.4.0 + 旧架构合并
|
||||
|
||||
---
|
||||
|
||||
## 1. 实体关系图 (ER)
|
||||
|
||||
```
|
||||
User ──< UserRelation (邀请树: inviter → invitee)
|
||||
│
|
||||
User ──< ErrorItem >── Subject
|
||||
│ │
|
||||
│ ├──< CorrectionLog (AI 值 vs 用户修正)
|
||||
│ └── KnowledgePoint (多对多)
|
||||
│
|
||||
└──< AnalysisReport
|
||||
|
||||
ErrorItem >──< KnowledgePoint (error_knowledge_points)
|
||||
PracticeRecommendation >──< KnowledgePoint
|
||||
└──< Question (题库题目, 多对多)
|
||||
|
||||
Question ──< KnowledgePoint (question_knowledge_points)
|
||||
Question ── Subject
|
||||
```
|
||||
|
||||
## 2. 表定义
|
||||
|
||||
### 2.1 users
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK, DEFAULT gen_random_uuid() | 用户 ID |
|
||||
| wx_openid | VARCHAR(128) | UNIQUE, NOT NULL | 微信 OpenID |
|
||||
| nickname | VARCHAR(64) | | 微信昵称 |
|
||||
| avatar_url | VARCHAR(512) | | 头像 URL |
|
||||
| grade | VARCHAR(16) | | 年级,如"初中二年级" |
|
||||
| role | VARCHAR(16) | NOT NULL, DEFAULT 'student' | 角色: student/parent/teacher/admin/super_admin (Phase 3 启用后台角色) |
|
||||
| invitation_code | VARCHAR(16) | UNIQUE | 个人邀请码(6 位字母数字,注册时生成) |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
### 2.2 user_relations
|
||||
|
||||
用户邀请树结构。记录邀请链,支持树状用户群。
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | |
|
||||
| inviter_id | UUID | FK → users.id, NOT NULL | 邀请人 |
|
||||
| invitee_id | UUID | FK → users.id, UNIQUE, NOT NULL | 被邀请人(一个用户只能被一个人邀请) |
|
||||
| relation_type | VARCHAR(16) | NOT NULL, DEFAULT 'student' | student/parent/colleague |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
**索引**: `(inviter_id)` — 查询某用户邀请的所有人;`(invitee_id)` UNIQUE — 确保一对一邀请链
|
||||
|
||||
**树查询**: 通过递归 CTE 查询某用户下的完整子树(老师查看全班学生、机构查看所有老师)
|
||||
|
||||
```sql
|
||||
-- 查询邀请人的一级下线
|
||||
SELECT * FROM user_relations WHERE inviter_id = $1;
|
||||
|
||||
-- 递归查询完整子树(所有下级)
|
||||
WITH RECURSIVE tree AS (
|
||||
SELECT invitee_id, inviter_id, 1 AS depth FROM user_relations WHERE inviter_id = $1
|
||||
UNION ALL
|
||||
SELECT ur.invitee_id, ur.inviter_id, t.depth + 1
|
||||
FROM user_relations ur JOIN tree t ON ur.inviter_id = t.invitee_id
|
||||
WHERE t.depth < 10
|
||||
) SELECT * FROM tree;
|
||||
```
|
||||
|
||||
**典型结构**:
|
||||
```
|
||||
机构负责人 (invitation_code: ABC123)
|
||||
├── 老师A (受邀)
|
||||
│ ├── 学生1 (受邀)
|
||||
│ └── 学生2 (受邀)
|
||||
└── 老师B (受邀)
|
||||
├── 学生3 (受邀)
|
||||
└── 学生4 (受邀)
|
||||
```
|
||||
|
||||
### 2.3 subjects
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | SERIAL | PK | 学科 ID |
|
||||
| name | VARCHAR(32) | UNIQUE, NOT NULL | 数学/英语/语文/... |
|
||||
| icon | VARCHAR(32) | | 图标标识 |
|
||||
| sort_order | INT | DEFAULT 0 | 排序 |
|
||||
|
||||
**预置数据**: 数学、英语(首发)、语文、物理、化学、生物、地理、历史、政治(后续扩展)
|
||||
|
||||
### 2.4 knowledge_points
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | SERIAL | PK | 知识点 ID(内部关联用) |
|
||||
| code | VARCHAR(32) | UNIQUE | 业务编码,如 `G5-MATH-0201`(跨环境稳定,API 对外暴露) |
|
||||
| name | VARCHAR(128) | NOT NULL | 如"二次函数顶点式" |
|
||||
| subject_id | INT | FK → subjects.id, NOT NULL | 所属学科 |
|
||||
| parent_id | INT | FK → knowledge_points.id | 父级知识点(树形结构) |
|
||||
| level | SMALLINT | NOT NULL, DEFAULT 1 | 层级深度 |
|
||||
| sort_order | INT | DEFAULT 0 | 同级排序 |
|
||||
|
||||
**索引**: `(subject_id, parent_id)`, `(name)` GIN trigram(模糊搜索)
|
||||
|
||||
**示例数据(数学 + 英语双学科首发)**:
|
||||
|
||||
**编码规则**: `{Grade}-{Subject}-{Category}{Detail}`,如 `G5-MATH-0201` = 五年级·数学·02 大类·01 知识点。ID 用于内部关联,code 跨环境稳定,API 对外暴露。
|
||||
|
||||
```
|
||||
数学 (id=1)
|
||||
├── 代数 (id=10, code=G5-MATH-0100, parent=NULL)
|
||||
│ ├── 一次函数 (id=101, code=G8-MATH-0101, parent=10)
|
||||
│ │ ├── 斜率与截距 (id=1011, code=G8-MATH-0101-1, parent=101)
|
||||
│ │ └── 一次函数应用 (id=1012, code=G8-MATH-0101-2, parent=101)
|
||||
│ └── 二次函数 (id=102, code=G9-MATH-0102, parent=10)
|
||||
│ ├── 顶点坐标 (id=1021, code=G9-MATH-0102-1, parent=102)
|
||||
│ └── 图像性质 (id=1022, code=G9-MATH-0102-2, parent=102)
|
||||
└── 几何 (id=20, code=G5-MATH-0200, parent=NULL)
|
||||
├── 三角形 (id=201, code=G7-MATH-0201, parent=20)
|
||||
└── 圆 (id=202, code=G9-MATH-0202, parent=20)
|
||||
|
||||
英语 (id=2)
|
||||
├── 语法 (id=200, code=G7-ENG-0100, parent=NULL)
|
||||
│ ├── 时态 (id=2001, code=G7-ENG-0101, parent=200)
|
||||
│ │ ├── 一般现在时 (id=20011, code=G7-ENG-0101-1, parent=2001)
|
||||
│ │ └── 现在完成时 (id=20012, code=G8-ENG-0101-2, parent=2001)
|
||||
│ ├── 从句 (id=2002, code=G8-ENG-0102, parent=200)
|
||||
│ │ ├── 定语从句 (id=20021, code=G9-ENG-0102-1, parent=2002)
|
||||
│ │ └── 状语从句 (id=20022, code=G8-ENG-0102-2, parent=2002)
|
||||
│ └── 被动语态 (id=2003, code=G8-ENG-0103, parent=200)
|
||||
├── 词汇 (id=300, code=G7-ENG-0200, parent=NULL)
|
||||
│ ├── 词义辨析 (id=3001, code=G7-ENG-0201, parent=300)
|
||||
│ └── 固定搭配 (id=3002, code=G8-ENG-0202, parent=300)
|
||||
└── 阅读 (id=400, code=G7-ENG-0300, parent=NULL)
|
||||
├── 主旨大意 (id=4001, code=G7-ENG-0301, parent=400)
|
||||
└── 细节理解 (id=4002, code=G7-ENG-0302, parent=400)
|
||||
```
|
||||
|
||||
### 2.6 error_items
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | 错题 ID |
|
||||
| user_id | UUID | FK → users.id, NOT NULL | 所属用户 |
|
||||
| subject_id | INT | FK → subjects.id | 学科 |
|
||||
| image_url | VARCHAR(512) | NOT NULL | 原始图片 URL |
|
||||
| thumbnail_url | VARCHAR(512) | | 缩略图 URL |
|
||||
| question_text | TEXT | | AI 提取的题目文本 |
|
||||
| wrong_answer | TEXT | | 错误答案 |
|
||||
| correct_answer | TEXT | | 正确答案(可选) |
|
||||
| error_type | VARCHAR(32) | | 错误类型 |
|
||||
| difficulty | VARCHAR(8) | | 难度: basic/medium/advanced |
|
||||
| verification_status | VARCHAR(16) | NOT NULL, DEFAULT 'raw' | raw/reviewed/corrected/stale |
|
||||
| ai_confidence | JSONB | | AI 各字段置信度 |
|
||||
| note | TEXT | | 学生备注 |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
| updated_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
**索引**: `(user_id, created_at DESC)`, `(user_id, subject_id)`, `(user_id, error_type)`, `(user_id, verification_status)`
|
||||
|
||||
**verification_status 枚举**:
|
||||
- `raw` — AI 原始结果,用户尚未确认,不计入分析
|
||||
- `reviewed` — 用户已确认(一键确认或查看后确认)
|
||||
- `corrected` — 用户修正了至少一个 AI 字段
|
||||
- `stale` — 30 天未确认,系统标记,可恢复为 raw
|
||||
|
||||
**ai_confidence JSONB 结构**:
|
||||
```json
|
||||
{
|
||||
"question_text": 0.92,
|
||||
"subject_id": 0.88,
|
||||
"knowledge_points": { "1021": 0.95, "1022": 0.73 },
|
||||
"error_type": 0.81,
|
||||
"correct_answer": 0.55
|
||||
}
|
||||
```
|
||||
|
||||
**error_type 枚举**:
|
||||
- `knowledge_gap` — 知识点欠缺
|
||||
- `careless` — 粗心失误
|
||||
- `misread` — 审题偏差
|
||||
- `concept_confusion` — 概念混淆
|
||||
|
||||
### 2.7 error_knowledge_points
|
||||
|
||||
错题与知识点的多对多关联表。
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | SERIAL | PK | |
|
||||
| error_item_id | UUID | FK → error_items.id, NOT NULL | 错题 |
|
||||
| knowledge_point_id | INT | FK → knowledge_points.id, NOT NULL | 知识点 |
|
||||
| relevance | SMALLINT | DEFAULT 100 | 关联度 (0-100),主关联=100 |
|
||||
|
||||
**唯一约束**: `(error_item_id, knowledge_point_id)`
|
||||
|
||||
### 2.8 correction_logs
|
||||
|
||||
用户修正 AI 识别结果的记录。P02 阶段用于微调自有模型。
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | |
|
||||
| error_item_id | UUID | FK → error_items.id, NOT NULL | 所属错题 |
|
||||
| field_name | VARCHAR(32) | NOT NULL | 修正的字段名 |
|
||||
| ai_value | JSONB | NOT NULL | AI 原始值 |
|
||||
| user_value | JSONB | NOT NULL | 用户修正值 |
|
||||
| ai_confidence | REAL | NOT NULL | 该字段 AI 置信度 |
|
||||
| corrected_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | 修正时间 |
|
||||
|
||||
**索引**: `(error_item_id)`, `(field_name)`(P02 阶段按字段统计 AI 薄弱项)
|
||||
|
||||
**示例数据**:
|
||||
```json
|
||||
{
|
||||
"error_item_id": "uuid",
|
||||
"field_name": "knowledge_points",
|
||||
"ai_value": [1021],
|
||||
"user_value": [1022],
|
||||
"ai_confidence": 0.72,
|
||||
"corrected_at": "2026-05-26T10:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.9 analysis_reports
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | 报告 ID |
|
||||
| user_id | UUID | FK → users.id, NOT NULL | |
|
||||
| period_start | DATE | NOT NULL | 报告周期开始 |
|
||||
| period_end | DATE | NOT NULL | 报告周期结束 |
|
||||
| weak_points | JSONB | NOT NULL | 薄弱点数据 |
|
||||
| error_type_distribution | JSONB | NOT NULL | 错误类型分布 |
|
||||
| trend | VARCHAR(8) | | up/flat/down(与上周期对比) |
|
||||
| generated_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
**weak_points JSONB 结构**:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"knowledge_point_id": 1021,
|
||||
"name": "二次函数顶点坐标",
|
||||
"error_count": 5,
|
||||
"weight": 0.85,
|
||||
"trend": "up"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**error_type_distribution JSONB 结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"knowledge_gap": 12,
|
||||
"careless": 5,
|
||||
"misread": 3,
|
||||
"concept_confusion": 2
|
||||
}
|
||||
```
|
||||
|
||||
**注意**: AnalysisReport 仅统计 `verification_status != 'raw'` 的错题,确保分析基于用户确认过的数据。
|
||||
|
||||
### 2.10 question_bank(题库抽象层)
|
||||
|
||||
支持多题库源的统一抽象。自有题库(PDF 录入)和第三方题库(作业帮 API)通过统一接口接入。
|
||||
|
||||
**2.10.1 questions(题库题目)**
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | 题目 ID |
|
||||
| source | VARCHAR(16) | NOT NULL | 来源: self_built / zuoyebang / future_source |
|
||||
| external_id | VARCHAR(128) | | 外部题库的原始 ID(自建为空) |
|
||||
| subject_id | INT | FK → subjects.id, NOT NULL | 所属学科 |
|
||||
| question_type | VARCHAR(16) | NOT NULL, DEFAULT 'choice' | 题型: choice/fill/calculation/word_problem/geometry/composite |
|
||||
| question_text | TEXT | NOT NULL | 题目文本 |
|
||||
| options | JSONB | | 选项(如 ABCD) |
|
||||
| answer | TEXT | NOT NULL | 正确答案 |
|
||||
| analysis | TEXT | | 解析 |
|
||||
| difficulty | SMALLINT | DEFAULT 3 | 难度 1-5(1 基础 → 5 综合创新) |
|
||||
| cognitive_level | SMALLINT | | 认知层次 1-6(布鲁姆: 记忆/理解/应用/分析/评价/创造,预留) |
|
||||
| grade | VARCHAR(16) | | 适用年级 |
|
||||
| variation_params | JSONB | | 变式参数(数字替换、条件变换,预留,Phase 3 启用) |
|
||||
| status | VARCHAR(16) | DEFAULT 'active' | active/inactive |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
**索引**: `(subject_id, knowledge_point_id)`, `(source)`, `(grade)`
|
||||
|
||||
**2.10.2 question_knowledge_points**
|
||||
|
||||
题目与知识点的多对多关联(同 error_knowledge_points 模式)。
|
||||
|
||||
**2.10.3 pdf_import_tasks(PDF 导入任务)**
|
||||
|
||||
自有题库 PDF 导入的异步任务管理。
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | 任务 ID |
|
||||
| uploaded_by | UUID | FK → users.id, NOT NULL | 上传者 |
|
||||
| file_url | VARCHAR(512) | NOT NULL | PDF 文件 URL |
|
||||
| subject_id | INT | FK → subjects.id | 目标学科 |
|
||||
| status | VARCHAR(16) | NOT NULL, DEFAULT 'pending' | pending/parsing/ai_extracting/review/complete/failed |
|
||||
| parsed_count | INT | DEFAULT 0 | 解析出的题目数 |
|
||||
| imported_count | INT | DEFAULT 0 | 成功导入的题目数 |
|
||||
| error_log | JSONB | | 错误信息 |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
**PDF 导入管线**:
|
||||
```
|
||||
PDF 上传 → OCR 解析 → AI 结构化提取(题目/选项/答案/知识点)
|
||||
→ 人工审核(校验解析结果) → 入库(source=self_built)
|
||||
```
|
||||
|
||||
### 2.11 print_tasks(打印/PDF 输出任务)[P0]
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | 任务 ID |
|
||||
| user_id | UUID | FK → users.id, NOT NULL | 创建者 |
|
||||
| error_item_ids | UUID[] | NOT NULL | 选中的错题 ID 列表 |
|
||||
| output_mode | VARCHAR(8) | NOT NULL, DEFAULT 'pdf' | pdf / image |
|
||||
| status | VARCHAR(16) | NOT NULL, DEFAULT 'pending' | pending/generating/complete/expired/failed |
|
||||
| file_url | VARCHAR(512) | | 生成的 PDF/图片下载链接 |
|
||||
| expires_at | TIMESTAMPTZ | | 下载链接过期时间(24h) |
|
||||
| created_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
**清晰度优先级**: 结构化内容(题库匹配)> 增强图片(经图像预处理)> 原始图片
|
||||
|
||||
### 2.12 audit_logs(操作审计,预留 Phase 3)
|
||||
|
||||
Phase 3 引入完整数据主权方案时建表。字段预留: id, user_id, action, resource_type, resource_id, detail (JSONB), ip_address, created_at。
|
||||
|
||||
### 2.13 practice_recommendations (P1)
|
||||
|
||||
| 列 | 类型 | 约束 | 说明 |
|
||||
|----|------|------|------|
|
||||
| id | UUID | PK | 推荐记录 ID |
|
||||
| user_id | UUID | FK → users.id, NOT NULL | |
|
||||
| knowledge_point_ids | INT[] | NOT NULL | 目标知识点 |
|
||||
| question_refs | JSONB | NOT NULL | 推荐题目引用 |
|
||||
| completed | BOOLEAN | DEFAULT false | 是否完成 |
|
||||
| score | SMALLINT | | 得分 |
|
||||
| generated_at | TIMESTAMPTZ | NOT NULL, DEFAULT NOW() | |
|
||||
|
||||
## 3. Drizzle Schema 示例
|
||||
|
||||
```typescript
|
||||
// src/server/src/db/schema.ts
|
||||
|
||||
import { pgTable, uuid, varchar, text, integer, smallint, date, jsonb, timestamp, boolean, uniqueIndex, index } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
wxOpenid: varchar('wx_openid', { length: 128 }).unique().notNull(),
|
||||
nickname: varchar('nickname', { length: 64 }),
|
||||
avatarUrl: varchar('avatar_url', { length: 512 }),
|
||||
grade: varchar('grade', { length: 16 }),
|
||||
role: varchar('role', { length: 16 }).default('student').notNull(),
|
||||
invitationCode: varchar('invitation_code', { length: 16 }).unique(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
});
|
||||
|
||||
export const errorItems = pgTable('error_items', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
userId: uuid('user_id').references(() => users.id).notNull(),
|
||||
subjectId: integer('subject_id').references(() => subjects.id),
|
||||
imageUrl: varchar('image_url', { length: 512 }).notNull(),
|
||||
thumbnailUrl: varchar('thumbnail_url', { length: 512 }),
|
||||
questionText: text('question_text'),
|
||||
wrongAnswer: text('wrong_answer'),
|
||||
correctAnswer: text('correct_answer'),
|
||||
errorType: varchar('error_type', { length: 32 }),
|
||||
difficulty: varchar('difficulty', { length: 8 }),
|
||||
verificationStatus: varchar('verification_status', { length: 16 }).default('raw').notNull(),
|
||||
aiConfidence: jsonb('ai_confidence'),
|
||||
note: text('note'),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
userIdCreatedIdx: index('idx_error_items_user_created').on(table.userId, table.createdAt.desc()),
|
||||
userIdSubjectIdx: index('idx_error_items_user_subject').on(table.userId, table.subjectId),
|
||||
userIdStatusIdx: index('idx_error_items_user_status').on(table.userId, table.verificationStatus),
|
||||
}));
|
||||
|
||||
export const correctionLogs = pgTable('correction_logs', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
errorItemId: uuid('error_item_id').references(() => errorItems.id).notNull(),
|
||||
fieldName: varchar('field_name', { length: 32 }).notNull(),
|
||||
aiValue: jsonb('ai_value').notNull(),
|
||||
userValue: jsonb('user_value').notNull(),
|
||||
aiConfidence: real('ai_confidence').notNull(),
|
||||
correctedAt: timestamp('corrected_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
}, (table) => ({
|
||||
errorItemIdIdx: index('idx_correction_logs_error_item').on(table.errorItemId),
|
||||
fieldNameIdx: index('idx_correction_logs_field').on(table.fieldName),
|
||||
}));
|
||||
```
|
||||
|
||||
## 4. 数据量预估
|
||||
|
||||
| 表 | MVP 年末预估 | 增长速度 |
|
||||
|----|-------------|----------|
|
||||
| users | 10K | 线性(含邀请裂变) |
|
||||
| user_relations | ~10K | 每用户 1 条邀请关系 |
|
||||
| error_items | 500K | 每用户日均 2-3 道 |
|
||||
| knowledge_points | ~5K (预置) | 版本更新追加 |
|
||||
| analysis_reports | 40K | 每用户每周 1 份 |
|
||||
| error_knowledge_points | 1M | 每错题 1-3 条关联 |
|
||||
| correction_logs | ~200K | 每错题平均修正 0.5-1 个字段 |
|
||||
| questions | 50K+ | 自有 PDF 导入 + 作业帮 API 同步 |
|
||||
| question_knowledge_points | 100K | 每题 1-3 条关联 |
|
||||
|
||||
MVP 单表最大 500K 行,PostgreSQL 单实例完全可承载,无需分库分表。
|
||||
|
||||
---
|
||||
|
||||
*关联: 模块设计.md → 总体架构.md*
|
||||
@@ -0,0 +1,403 @@
|
||||
# 模块设计
|
||||
|
||||
> 版本: v0.4.0 | 作者: Arch AI | 基于 PRD v0.4.0 + 旧架构合并
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块总览
|
||||
|
||||
```
|
||||
ErrLens
|
||||
├── P01_errlens_app (小程序 + 后端)
|
||||
│ ├── 小程序端 (Taro + React)
|
||||
│ │ ├── pages/auth # 登录/注册
|
||||
│ │ ├── pages/home # 首页(错题概览)
|
||||
│ │ ├── pages/capture # 拍照录入
|
||||
│ │ ├── pages/error-detail # 错题详情+分析
|
||||
│ │ ├── pages/error-list # 错题列表
|
||||
│ │ ├── pages/weak-points # 薄弱点分析
|
||||
│ │ ├── pages/practice # 练习推荐 (P1)
|
||||
│ │ ├── pages/print # PDF 输出 (P0)
|
||||
│ │ └── pages/profile # 个人中心
|
||||
│ │
|
||||
│ └── 后端 (NestJS)
|
||||
│ ├── modules/auth # 鉴权模块
|
||||
│ ├── modules/user # 用户模块(含邀请链)
|
||||
│ ├── modules/invitation # 邀请模块
|
||||
│ ├── modules/error-item # 错题模块
|
||||
│ ├── modules/ai # AI 分析模块
|
||||
│ ├── modules/image # 图像预处理模块
|
||||
│ ├── modules/subject # 学科模块
|
||||
│ ├── modules/question-bank # 题库模块
|
||||
│ ├── modules/recommend # 推荐模块 (P1)
|
||||
│ ├── modules/print # 打印/PDF 输出模块 (P0)
|
||||
│ └── modules/upload # 文件上传
|
||||
│
|
||||
├── P02_errlens_training (Phase 3)
|
||||
│ └── AI 模型训练管线
|
||||
│
|
||||
└── P03_errlens_web (Phase 3)
|
||||
└── Web 管理后台
|
||||
```
|
||||
|
||||
## 2. 小程序页面路由
|
||||
|
||||
| 路由 | 页面 | 说明 | MVP |
|
||||
|------|------|------|-----|
|
||||
| `/pages/index/index` | 首页 | 错题统计概览 + 快捷入口 | ✅ |
|
||||
| `/pages/auth/login` | 登录 | 微信授权登录 | ✅ |
|
||||
| `/pages/capture/index` | 拍照录入 | 拍照 → AI 识别 → 确认 | ✅ |
|
||||
| `/pages/error-list/index` | 错题列表 | 筛选 + 搜索 | ✅ |
|
||||
| `/pages/error-detail/index` | 错题详情 | 题目 + AI 分析 | ✅ |
|
||||
| `/pages/weak-points/index` | 薄弱点 | 知识点薄弱项汇总 | ✅ |
|
||||
| `/pages/practice/index` | 练习推荐 | 智能组题 | P1 |
|
||||
| `/pages/practice/result` | 练习结果 | 对错反馈 | P1 |
|
||||
| `/pages/print/index` | 错题选择 | 选题→生成 PDF | ✅ |
|
||||
| `/pages/print/preview` | 打印预览 | PDF 预览+下载 | ✅ |
|
||||
| `/pages/profile/index` | 个人中心 | 资料 + 设置 | ✅ |
|
||||
|
||||
## 3. 后端模块设计
|
||||
|
||||
### 3.1 Auth 模块
|
||||
|
||||
```
|
||||
modules/auth/
|
||||
├── auth.module.ts
|
||||
├── auth.controller.ts # POST /auth/login (微信 code → JWT)
|
||||
├── auth.service.ts # 微信 code2session + JWT 签发
|
||||
├── auth.guard.ts # JWT 鉴权守卫
|
||||
└── dto/
|
||||
└── login.dto.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /auth/login | 微信 code 换取 JWT token |
|
||||
| POST | /auth/refresh | 刷新 token |
|
||||
|
||||
### 3.2 User 模块
|
||||
|
||||
```
|
||||
modules/user/
|
||||
├── user.module.ts
|
||||
├── user.controller.ts # GET/PATCH /user/profile
|
||||
├── user.service.ts
|
||||
├── user.entity.ts # Drizzle schema
|
||||
└── dto/
|
||||
└── update-profile.dto.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /user/profile | 获取个人信息 |
|
||||
| PATCH | /user/profile | 更新年级、学科 |
|
||||
| GET | /user/invitation-code | 获取个人邀请码和二维码 |
|
||||
| GET | /user/tree | 获取邀请树(直接下级列表) |
|
||||
|
||||
### 3.3 Invitation 模块
|
||||
|
||||
```
|
||||
modules/invitation/
|
||||
├── invitation.module.ts
|
||||
├── invitation.controller.ts # POST /invitation/register
|
||||
├── invitation.service.ts # 邀请码生成 + 邀请链管理
|
||||
├── user-relation.entity.ts # Drizzle schema
|
||||
└── dto/
|
||||
├── register-by-invite.dto.ts
|
||||
└── tree-query.dto.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /invitation/register | 通过邀请码注册(携带 inviter_code) |
|
||||
| GET | /invitation/tree | 获取当前用户的邀请树(一级+递归) |
|
||||
| GET | /invitation/tree/:userId | 获取指定用户的邀请统计(子节点数、活跃用户数) |
|
||||
|
||||
**邀请码生成规则**:
|
||||
- 用户注册时自动生成 6 位字母数字邀请码
|
||||
- 格式: `[A-Z0-9]{6}`,排除易混淆字符 (0/O, 1/I/L)
|
||||
- 唯一约束,冲突时重试生成
|
||||
|
||||
**注册流程**:
|
||||
```
|
||||
1. 老用户 A 分享小程序 → 携带邀请码参数 (pages/auth/login?invite=ABC123)
|
||||
2. 新用户 B 打开 → 微信授权登录
|
||||
3. 后端创建 B 用户 → 生成 B 的邀请码
|
||||
4. 创建 user_relation (inviter=A, invitee=B)
|
||||
5. B 进入首页 → 关系建立完成
|
||||
```
|
||||
|
||||
### 3.4 ErrorItem 模块(核心)
|
||||
|
||||
```
|
||||
modules/error-item/
|
||||
├── error-item.module.ts
|
||||
├── error-item.controller.ts # CRUD /error-items
|
||||
├── error-item.service.ts # 错题业务逻辑 + 校验状态管理
|
||||
├── error-item.entity.ts # Drizzle schema
|
||||
├── correction-log.service.ts # 修正记录管理
|
||||
└── dto/
|
||||
├── create-error.dto.ts
|
||||
├── update-error.dto.ts
|
||||
├── query-error.dto.ts # 筛选参数(含 verification_status)
|
||||
└── batch-confirm.dto.ts # 批量确认
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /error-items | 创建错题(AI 识别结果,状态=raw) |
|
||||
| GET | /error-items | 列表查询(分页+筛选,含 verification_status 筛选) |
|
||||
| GET | /error-items/:id | 错题详情(含 AI 分析 + 置信度) |
|
||||
| PATCH | /error-items/:id | 修正错题信息(字段修正时记录 CorrectionLog) |
|
||||
| POST | /error-items/batch-confirm | 批量确认(raw → reviewed) |
|
||||
| DELETE | /error-items/:id | 删除错题 |
|
||||
|
||||
**校验状态机**:
|
||||
```
|
||||
raw → (用户查看+确认) → reviewed → (用户修正字段) → corrected
|
||||
↓
|
||||
stale (30天未确认,系统标记,可恢复为 raw)
|
||||
```
|
||||
|
||||
**AnalysisReport 数据过滤**: 查询时加 `WHERE verification_status != 'raw'`,确保分析只使用已确认数据。
|
||||
|
||||
### 3.5 AI 模块
|
||||
|
||||
```
|
||||
modules/ai/
|
||||
├── ai.module.ts
|
||||
├── ai.controller.ts # POST /ai/analyze
|
||||
├── ai.service.ts # Coze SDK 调用封装
|
||||
├── strategies/
|
||||
│ ├── ocr.strategy.ts # OCR 识别策略
|
||||
│ ├── classify.strategy.ts # 学科/知识点分类策略
|
||||
│ └── diagnose.strategy.ts # 错误原因诊断策略
|
||||
└── dto/
|
||||
├── analyze-request.dto.ts
|
||||
└── analyze-response.dto.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /ai/analyze | 分析错题图片 → 返回结构化结果 |
|
||||
| GET | /ai/report | 获取薄弱点分析报告 |
|
||||
|
||||
**AI 分析流程**:
|
||||
|
||||
```
|
||||
图片 URL → OCR 文字提取 ──→ question_text (confidence: 0.5-0.95)
|
||||
├─ 学科分类 ──────────→ subject_id (confidence: 0.7-0.95)
|
||||
├─ 知识点标注 ────────→ knowledge_points[] (confidence 各 0.5-0.9)
|
||||
├─ 题目结构提取 ──────→ wrong_answer/correct_answer (confidence 各 0.5-0.9)
|
||||
└─ 错误原因诊断 ──────→ error_type + diagnosis (confidence: 0.5-0.9)
|
||||
→ 每个字段附带 confidence 分数
|
||||
→ 低置信(<0.7)字段标记 requires_review=true,前端红色高亮
|
||||
→ 汇总返回 { result, confidences, requires_review_fields[] }
|
||||
```
|
||||
|
||||
### 3.6 Image 模块(图像预处理)
|
||||
|
||||
```
|
||||
modules/image/
|
||||
├── image.module.ts
|
||||
├── image.service.ts # 图像预处理编排
|
||||
├── pipelines/
|
||||
│ ├── perspective.service.ts # 透视校正(手动框 4 角)
|
||||
│ ├── enhance.service.ts # CLAHE + Gamma + 对比度增强
|
||||
│ └── pen-removal.service.ts # 笔迹去除(红/蓝 HSV 自动,黑笔手动圈选)
|
||||
├── sharp.config.ts # Sharp 图像处理参数配置
|
||||
└── dto/
|
||||
└── preprocess.dto.ts
|
||||
```
|
||||
|
||||
**处理管线**:
|
||||
```
|
||||
原始图片 → 透视校正 → CLAHE 增强 → 笔迹去除 → 输出增强图片 URL
|
||||
↓ ↓ ↓ ↓
|
||||
原图保留 手动框角 光照归一化 红/蓝自动去除
|
||||
黑笔可选手动圈选
|
||||
```
|
||||
|
||||
**降级策略**: 任何模块失败不阻塞整体流程。校正失败 → 用原图;增强失败 → 跳过增强;笔迹去除失败 → 保留原图。始终保证图片能进入 AI 识别。
|
||||
|
||||
**Spike 验证结论**(来自旧架构,Coze 沙盒中重新调优):
|
||||
- 自动透视校正不可用(整页裁成碎片),以手动框 4 角为主方案
|
||||
- CLAHE 增强 10/10 达到清晰度标准
|
||||
- 红笔批改可靠去除、蓝笔可用、黑笔无法自动去除(需用户圈选)
|
||||
|
||||
### 3.8 Subject 模块
|
||||
|
||||
```
|
||||
modules/subject/
|
||||
├── subject.module.ts
|
||||
├── subject.controller.ts # GET /subjects
|
||||
├── subject.service.ts
|
||||
└── subject.entity.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /subjects | 获取学科列表 |
|
||||
| GET | /subjects/:id/knowledge-points | 获取学科下知识点树 |
|
||||
|
||||
### 3.7 Print 模块(PDF 输出)[P0]
|
||||
|
||||
```
|
||||
modules/print/
|
||||
├── print.module.ts
|
||||
├── print.controller.ts # POST /print/generate, GET /print/download/:id
|
||||
├── print.service.ts # PDF 生成 + S3 存储 + 过期清理
|
||||
├── pdf-layout.service.ts # PDFKit A4/300DPI 排版
|
||||
└── dto/
|
||||
├── generate-print.dto.ts
|
||||
└── print-task.dto.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /print/generate | 提交错题 ID 列表,生成 PDF 任务 |
|
||||
| GET | /print/task/:id | 查询生成进度 |
|
||||
| GET | /print/download/:id | 下载 PDF 文件(24h 有效) |
|
||||
|
||||
**排版优先级**: 题库匹配的结构化内容 > 经图像预处理的增强图片 > 原始图片
|
||||
|
||||
**清理策略**: 定时任务每天清理 expires_at < NOW() 的临时文件。
|
||||
|
||||
### 3.9 Upload 模块
|
||||
|
||||
```
|
||||
modules/upload/
|
||||
├── upload.module.ts
|
||||
├── upload.controller.ts # POST /upload/image
|
||||
├── upload.service.ts # S3 上传 + 缩略图
|
||||
└── upload.config.ts # S3/MinIO 配置
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | /upload/image | 上传图片 → 返回 URL |
|
||||
|
||||
### 3.10 Recommend 模块 (P1)
|
||||
|
||||
```
|
||||
modules/recommend/
|
||||
├── recommend.module.ts
|
||||
├── recommend.controller.ts # GET /recommend
|
||||
├── recommend.service.ts # 推荐编排
|
||||
└── strategies/
|
||||
├── coarse-recall.strategy.ts # 粗筛:关键词搜索 + Jaccard 相似度(本地,免费)
|
||||
├── ai-rerank.strategy.ts # 精排:AI 语义匹配(候选集不足时启用,付费)
|
||||
├── weak-point.strategy.ts # 薄弱点权重排序
|
||||
└── similar-difficulty.strategy.ts # 难度匹配
|
||||
```
|
||||
|
||||
**两阶段推荐策略**:
|
||||
1. **粗筛**(关键词 + Jaccard 相似度)→ 快速召回候选集,零成本、低延迟
|
||||
2. **精排**(AI 语义匹配)→ 候选集为空或不满足阈值时启用,语义理解泛化
|
||||
3. 排序: 薄弱点权重 > 难度匹配度 > 去重(已做过的不推)
|
||||
|
||||
### 3.11 QuestionBank 模块(题库抽象层)
|
||||
|
||||
```
|
||||
modules/question-bank/
|
||||
├── question-bank.module.ts
|
||||
├── question-bank.controller.ts # CRUD /questions
|
||||
├── question-bank.service.ts # 多源题库路由
|
||||
├── adapters/
|
||||
│ ├── base-adapter.ts # 题库适配器接口
|
||||
│ ├── self-built.adapter.ts # 自有题库适配器
|
||||
│ ├── zuoyebang.adapter.ts # 作业帮 API 适配器
|
||||
│ └── adapter.factory.ts # 按 source 路由到对应适配器
|
||||
├── pdf-import/
|
||||
│ ├── pdf-parser.service.ts # PDF → 文本
|
||||
│ ├── ai-extractor.service.ts # AI 结构化提取题目
|
||||
│ └── import-review.service.ts # 人工审核流程
|
||||
└── dto/
|
||||
├── create-question.dto.ts
|
||||
├── query-question.dto.ts
|
||||
└── pdf-import.dto.ts
|
||||
```
|
||||
|
||||
**API**:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | /questions | 题目列表查询(分页+筛选) |
|
||||
| GET | /questions/:id | 题目详情 |
|
||||
| POST | /questions | 手动创建题目(自有题库录入) |
|
||||
| POST | /questions/search | 跨源搜索(自有 + 作业帮,结果合并) |
|
||||
| POST | /questions/pdf-import | 上传 PDF 启动导入任务 |
|
||||
| GET | /questions/pdf-import/:taskId | 查询 PDF 导入进度 |
|
||||
|
||||
**适配器接口**:
|
||||
```typescript
|
||||
interface QuestionBankAdapter {
|
||||
source: string;
|
||||
search(params: SearchParams): Promise<Question[]>;
|
||||
getById(id: string): Promise<Question>;
|
||||
healthCheck(): Promise<boolean>;
|
||||
}
|
||||
```
|
||||
|
||||
**题库路由策略**: 请求时并行查询自有题库 + 作业帮 API,合并去重后返回。自有题库优先排序。
|
||||
|
||||
## 4. 前端状态管理 (Zustand)
|
||||
|
||||
```
|
||||
stores/
|
||||
├── auth.store.ts # 登录态、token、用户信息
|
||||
├── error-item.store.ts # 错题列表、筛选条件、分页
|
||||
├── capture.store.ts # 拍照流程状态机
|
||||
└── ai-analysis.store.ts # AI 分析结果缓存
|
||||
```
|
||||
|
||||
**capture 状态机**:
|
||||
|
||||
```
|
||||
IDLE → CAMERA → PREVIEW → UPLOADING → ANALYZING → REVIEW → SAVING → DONE
|
||||
↓ ↓ ↓
|
||||
ERROR ERROR ERROR
|
||||
```
|
||||
|
||||
## 5. MVP 开发顺序
|
||||
|
||||
```
|
||||
Phase 2a (Week 1-2): 基础设施
|
||||
- 数据库 Schema + 迁移
|
||||
- Auth 模块(微信登录)
|
||||
- User 模块
|
||||
- Image 模块(图像预处理管线)
|
||||
|
||||
Phase 2b (Week 3-4): 核心闭环
|
||||
- Upload 模块
|
||||
- AI 模块(Coze SDK 集成)
|
||||
- ErrorItem CRUD
|
||||
- 拍照录入页 + 错题列表页
|
||||
|
||||
Phase 2c (Week 5-6): 分析+打印+打磨
|
||||
- 错题详情 + AI 分析展示
|
||||
- 薄弱点汇总
|
||||
- Print 模块(PDF 输出)
|
||||
- Subject 模块 + 筛选
|
||||
- 交互打磨、异常处理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*关联: 总体架构.md → 技术选型.md → 数据模型.md*
|
||||
Reference in New Issue
Block a user