新增解答题拍照判分:百炼 Qwen-VL 按评分点逐点判分,无 API Key 时演示判分回退;混合卷按真实分值加权计分;判分照片与评分点进错题本;补齐历史迁移缺口(0005 模板生效方式/0006 五张缺失表+grammar_progress point_id)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jacobxu666
2026-08-17 16:24:23 +08:00
co-authored by Claude Opus 4.8
parent 4033abc21d
commit f80a931ef0
21 changed files with 1377 additions and 45 deletions
+73
View File
@@ -0,0 +1,73 @@
# 解答题拍照判分方案(设计决策记录)
> 2026-08-06 用户授权:全部按推荐方案落地,无需逐项确认。本文档记录已确认的全部决策。
## 一、目标
考试板块支持**数学解答题**(计算/解方程/应用题):孩子在纸上写完整解答过程,手机拍照上传,AI 视觉模型按评分点部分给分。首期试点一张**初一入学考(小升初分班考)卷**,现有 5 张月考卷不动。
## 二、已确认决策(逐条)
| # | 决策点 | 结论 |
| --- | --- | --- |
| 1 | 题型范围 | 困难档:解答题**过程判分**(不是只判最终答案) |
| 2 | 作答输入 | **纸上手写 + 拍照**(不做屏幕手写、不做分步结构化输入) |
| 3 | AI 模型 | **阿里云百炼 Qwen-VL**(qwen-vl-max),用户 Tokenplan 计费;经 OpenAI 兼容端点调用 |
| 4 | 答题流程 | **混合模式**:客观题(选择/判断/填空)App 内作答;**解答题逐题拍照**,不做整页拍 |
| 5 | 判分规则 | **部分给分 + 评分点(rubric)锚定**:AI 逐点评判,只能返回离散分数,不给自由打分权 |
| 6 | 题目配图 | 原卷裁剪为主、SVG 兜底,存 `public/exam/<卷id>/`;几何题判分时**原图+孩子照片一起喂模型**。本期入学卷无图题 |
| 7 | 内容生产 | **零人工**:抓卷/转录 → **双模型背靠背独立验算,不一致即丢弃**;本期因网络策略无法直抓教育站,内容改为**本地 Obsidian 数学素材 + 经典分班考题型按真实卷格式组卷**,已在试卷 summary 标注 |
| 8 | 计分 | 分值跟随原卷;总分 = 客观题 + 解答题部分得分;**达标线保持 80%**;exam_record 存每题照片 URL + 评分点命中明细 |
| 9 | 错题本 | v1 **只存不重做**:存题目 + 当时照片 + AI 评判 + 参考解答;保留手动"已掌握";重做重判放二期 |
| 10 | 质量控制 | AI 返回状态:`ok / blank(空白) / blurry(模糊) / printed(印刷体拒判) / unrelated(无关内容)`;非手写拒判;**每人每天限 30 次判分**;客户端压缩照片 ≤2MB |
| 11 | 时效 | 同步等待 + "AI 判分中"动画;失败自动重试 1 次,再失败允许单题重拍重交,不阻塞其它题 |
| 12 | 范围 | 解答题只进考试板块;课时练习/周测暂不加 |
## 三、数据模型
### QuizQ 扩展(`src/lib/quiz-parse.ts`)
```ts
{
type: "solve",
stem: string, // 题干
figure?: string, // 配图路径(可选,本期不用)
points: number, // 本题满分(分值跟随原卷)
reference: string, // 完整参考解答(判后展示)
rubric: { point: string; score: number }[], // 评分点,合计 = points
explain: string // 讲解
}
```
### 新表 `ai_grade_log`(迁移 0004)
判分审计 + 限频依据:child_user_id、paper_id、question_index、oss_key、status、earned、points、verdict(JSON:逐点命中+点评)、mock(是否演示判分)、created_at。
### exam_record.answers
解答题条目:`{ type:"solve", qIndex, ossKey, earned, hits:[{point,score,hit,reason}] }`;客观题保持原样。**成绩以服务端 ai_grade_log 为准,不信任客户端上报的分数**(防篡改)。
## 四、判分链路
```
孩子拍照 → 客户端压缩(≤2MB,canvas) → 服务端签发 OSS 直传 URL → PUT OSS
→ gradeSolve action:校验会话/限频(30/天) → 调百炼 Qwen-VL
prompt = 题干 + (原图) + 参考解答 + 评分点 + 孩子照片
→ 结构化 JSON:{status, hits:[{hit,reason}], earned, comment}
→ 写 ai_grade_log → 返回前端即时显示逐点得分
→ 全卷提交 takeExam:客观题即时判 + 解答题取 ai_grade_log 最近一次 ok 判定
→ 总分百分制,≥80 达标,错题(含照片)进 wrong_book,达标联动数学打卡
```
- **无 DASHSCOPE_API_KEY 时进入演示判分(mock)**:返回确定性模拟判定并标记 `mock=true`,供本地开发/测试;生产必须在服务器 .env 配置真 key。
- OSS 未配置时(本地开发)走 `/api/dev-photo` 本地落盘回退,不影响生产。
## 五、内容来源说明(本期入学卷)
本 session 网络策略拦截了对教育网站(学科网/组卷网/瑞文/51test 等)的直接抓取,无法获取原始真实卷页面。本期入学卷改为:**按真实分班考的卷面结构(填空+选择+计算+应用,满分 100)** 组卷,题目取自本地 Obsidian 数学素材库的经典题型(鸡兔同笼、工程、分数百分数、行程等,均带验算)。`sourceUrl` 置空并在 `summary` 注明。待网络恢复后可用同流程替换为真实卷转录。
## 六、上线前待办(需用户)
1. 百炼控制台开通/确认 Tokenplan 覆盖 `qwen-vl-max`,取 API Key;
2. 服务器 `/opt/services/superjump/.env` 增加 `DASHSCOPE_API_KEY=...`,重建容器;
3. 跑 `npm run db:seed-exam` 灌入新卷(容器重建时自动 migrate)。