新增解答题拍照判分:百炼 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
+12 -9
View File
@@ -63,19 +63,22 @@ export async function passedCount(childId: number): Promise<number> {
const norm = (s: unknown) => String(s ?? "").trim().toLowerCase();
// 服务端判分:五种题型 choice/judge/fill/order/match
// 服务端判分:六种题型 choice/judge/fill/order/match/solve(solve 不在此处判,走 AI)
export type AnswerValue = number | string | string[];
// xuchao: 单题客观判分(solve 返回 false,由 AI 判分链路单独处理)
export function gradeOne(q: QuizQ, a: AnswerValue | null | undefined): boolean {
if (q.type === "fill") return (q.answers ?? []).some((x) => norm(x) === norm(a));
if (q.type === "order") return Array.isArray(a) && a.join(" ").trim() === (q.answer as string[]).join(" ").trim();
if (q.type === "match") return Array.isArray(a) && (q.pairs ?? []).every((p, pi) => norm(a[pi]) === norm(p[1]));
if (q.type === "solve") return false;
return Number(a) === (q.answer as number);
}
export function gradeAnswers(questions: QuizQ[], answers: AnswerValue[]) {
let correct = 0;
const detail = questions.map((q, i) => {
const a = answers[i];
let ok = false;
if (q.type === "fill") ok = (q.answers ?? []).some((x) => norm(x) === norm(a));
else if (q.type === "order")
ok = Array.isArray(a) && a.join(" ").trim() === (q.answer as string[]).join(" ").trim();
else if (q.type === "match")
ok = Array.isArray(a) && (q.pairs ?? []).every((p, pi) => norm(a[pi]) === norm(p[1]));
else ok = Number(a) === (q.answer as number);
const ok = gradeOne(q, answers[i]);
if (ok) correct++;
return ok;
});