新增数学思维自学板块与考试板块(9模块154课+初一上5套真实试卷客观题)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3473ecbf2b
commit
bc53e2b2a5
@@ -0,0 +1,97 @@
|
||||
// xuchao: 校验数学内容 JSON——外层可解析、每课有 md、quiz 块内层可解析且题目字段齐全
|
||||
// 用法:node scripts/check-math-content.mjs <file.json> [more.json ...](不传则扫 src/content/math/*.json)
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
const TYPE_OK = new Set(["choice", "fill", "judge", "order", "match"]);
|
||||
|
||||
function checkQuizBlock(raw, where, errs) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
errs.push(`${where}: quiz 块 JSON 解析失败 — ${e.message}`);
|
||||
return;
|
||||
}
|
||||
const qs = parsed.questions;
|
||||
if (!Array.isArray(qs) || qs.length === 0) {
|
||||
errs.push(`${where}: quiz 块没有 questions 数组`);
|
||||
return;
|
||||
}
|
||||
if (qs.length < 8) errs.push(`${where}: 题目只有 ${qs.length} 题(建议 ≥8)`);
|
||||
qs.forEach((q, i) => {
|
||||
const tag = `${where} 第${i + 1}题`;
|
||||
if (!TYPE_OK.has(q.type)) errs.push(`${tag}: 非法 type=${q.type}`);
|
||||
if (!q.stem || typeof q.stem !== "string") errs.push(`${tag}: 缺 stem`);
|
||||
if (!q.explain) errs.push(`${tag}: 缺 explain`);
|
||||
if (q.type === "choice") {
|
||||
if (!Array.isArray(q.options) || q.options.length < 2) errs.push(`${tag}: choice 选项不足`);
|
||||
if (typeof q.answer !== "number" || q.answer < 0 || q.answer >= (q.options?.length ?? 0))
|
||||
errs.push(`${tag}: choice answer 越界`);
|
||||
} else if (q.type === "judge") {
|
||||
if (q.answer !== 0 && q.answer !== 1) errs.push(`${tag}: judge answer 必须是 0 或 1`);
|
||||
} else if (q.type === "fill") {
|
||||
if (!Array.isArray(q.answers) || q.answers.length === 0) errs.push(`${tag}: fill 缺 answers 数组`);
|
||||
} else if (q.type === "order") {
|
||||
if (!Array.isArray(q.words) || !Array.isArray(q.answer)) errs.push(`${tag}: order 缺 words/answer`);
|
||||
} else if (q.type === "match") {
|
||||
if (!Array.isArray(q.pairs) || q.pairs.length === 0) errs.push(`${tag}: match 缺 pairs`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function checkFile(file) {
|
||||
const errs = [];
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
} catch (e) {
|
||||
console.log(`❌ ${file}: 外层 JSON 解析失败 — ${e.message}`);
|
||||
return 1;
|
||||
}
|
||||
if (!data.module || !data.moduleName || !Array.isArray(data.points)) {
|
||||
console.log(`❌ ${file}: 缺 module / moduleName / points`);
|
||||
return 1;
|
||||
}
|
||||
const ids = new Set();
|
||||
let lessons = 0;
|
||||
data.points.forEach((p, idx) => {
|
||||
const tag = `${file} points[${idx}](${p.id ?? "?"})`;
|
||||
if (!p.id) errs.push(`${tag}: 缺 id`);
|
||||
else if (ids.has(p.id)) errs.push(`${tag}: id 重复`);
|
||||
else ids.add(p.id);
|
||||
if (!p.title) errs.push(`${tag}: 缺 title`);
|
||||
if (!p.md || typeof p.md !== "string") {
|
||||
errs.push(`${tag}: 缺 md`);
|
||||
return;
|
||||
}
|
||||
lessons++;
|
||||
const quizRe = /```quiz\s*\n([\s\S]*?)```/g;
|
||||
let m, count = 0;
|
||||
while ((m = quizRe.exec(p.md))) {
|
||||
count++;
|
||||
checkQuizBlock(m[1], `${tag} quiz#${count}`, errs);
|
||||
}
|
||||
if (count === 0) errs.push(`${tag}: md 中没有 quiz 块`);
|
||||
});
|
||||
if (errs.length) {
|
||||
console.log(`❌ ${file}: ${lessons} 课,${errs.length} 处问题`);
|
||||
errs.forEach((e) => console.log(" - " + e));
|
||||
return 1;
|
||||
}
|
||||
console.log(`✅ ${file}: ${data.module} / ${lessons} 课,全部通过`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const files = args.length
|
||||
? args
|
||||
: fs
|
||||
.readdirSync(path.join(process.cwd(), "src/content/math"))
|
||||
.filter((f) => f.endsWith(".json"))
|
||||
.map((f) => path.join(process.cwd(), "src/content/math", f));
|
||||
|
||||
let bad = 0;
|
||||
for (const f of files) bad += checkFile(f);
|
||||
console.log(bad ? `\n${bad} 个文件有问题` : "\n全部文件校验通过");
|
||||
process.exit(bad ? 1 : 0);
|
||||
Reference in New Issue
Block a user