| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- export const reviewModules = [
- 'api',
- 'database',
- 'api-database',
- 'tasks',
- 'redis',
- 'docker',
- 'linux',
- 'troubleshooting',
- 'distributed',
- ];
- const nonempty = (value) =>
- typeof value === 'string' && value.trim().length > 0;
- const strings = (value, min) =>
- Array.isArray(value) && value.length >= min && value.every(nonempty);
- export function verifyReview(lesson, index, sources) {
- const errors = [];
- const at = `review/${lesson?.id || index + 1}`;
- const fail = (message) => errors.push(`${at}: ${message}`);
- if (
- lesson.kind !== 'review' ||
- lesson.id !== String(index + 1).padStart(2, '0') ||
- lesson.sourceModule !== reviewModules[index]
- )
- fail('invalid review identity or source module');
- for (const key of ['title', 'summary', 'group'])
- if (!nonempty(lesson[key])) fail(`missing ${key}`);
- if (
- !nonempty(lesson.intro?.scenario) ||
- !nonempty(lesson.intro?.outcome) ||
- !strings(lesson.intro?.startingPoint, 2)
- )
- fail('missing self-test preparation');
- for (const key of [
- 'steps',
- 'production',
- 'interview',
- 'demo',
- 'code',
- 'concepts',
- ])
- if (key in lesson)
- fail(`review must not contain teaching-only field ${key}`);
- const source = sources.get(lesson.sourceModule);
- if (!source) fail('missing source lessons');
- for (const [key, count, prefix] of [
- ['questions', 6, 'q'],
- ['challenges', 2, 'c'],
- ]) {
- if (!Array.isArray(lesson[key]) || lesson[key].length !== count) {
- fail(`expected ${count} ${key}`);
- continue;
- }
- for (const [i, item] of lesson[key].entries()) {
- if (item.id !== `${prefix}${i + 1}`) fail(`invalid ${key} ID`);
- if (
- !nonempty(item.title) ||
- !nonempty(item.prompt) ||
- !strings(item.checkpoints, 3) ||
- !nonempty(item.misconception)
- )
- fail(`incomplete ${item.id}`);
- if (
- !Array.isArray(item.revisit) ||
- item.revisit.length < 1 ||
- item.revisit.length > 2
- ) {
- fail(`${item.id} needs one or two return links`);
- continue;
- }
- const seen = new Set();
- for (const link of item.revisit) {
- const original = source?.find((entry) => entry.id === link.lessonId);
- if (
- !original ||
- original.title !== link.label ||
- seen.has(link.lessonId)
- )
- fail(`${item.id}: invalid or duplicate return link ${link.lessonId}`);
- seen.add(link.lessonId);
- }
- }
- }
- if (!Array.isArray(lesson.mastery) || lesson.mastery.length !== 4)
- fail('expected four mastery criteria');
- else
- lesson.mastery.forEach((item, i) => {
- if (
- item.id !== `m${i + 1}` ||
- !nonempty(item.text) ||
- !nonempty(item.evidence) ||
- item.text === item.evidence
- )
- fail(`incomplete mastery evidence ${item.id}`);
- });
- return errors;
- }
- const bullets = (items) => items.map((item) => `- ${item}`).join('\n');
- function links(lesson, item) {
- return item.revisit
- .map((link) => {
- const route =
- lesson.sourceModule === 'api'
- ? `lesson=${link.lessonId}`
- : `module=${lesson.sourceModule}&lesson=${link.lessonId}`;
- return `[${link.lessonId} · ${link.label}](../?${route})`;
- })
- .join(';');
- }
- export function reviewMarkdown(lessons) {
- let result =
- '# 复盘与自测\n\n术核 AI · 后端工程课件\n\n每个单元对应一个已学模块。先独立回答,再看文末参考要点;合上答案后仍能解释原因、推演条件变化,并拿出四项掌握证据,才达到本轮标准。记录反映学员自评,不自动判定文字答案的正确性。\n\n';
- for (const lesson of lessons) {
- result += `## ${lesson.id} ${lesson.title}\n\n${lesson.summary}\n\n### 如何自测\n\n${lesson.intro.scenario}\n\n${bullets(lesson.intro.startingPoint)}\n\n本轮目标:${lesson.intro.outcome}\n\n原模块讲义:[${lesson.sourceModule}-lessons.md](./${lesson.sourceModule}-lessons.md)。离线阅读时按题目下的课号查找。\n\n`;
- for (const [title, items] of [
- ['核心理解问题', lesson.questions],
- ['情境推演', lesson.challenges],
- ]) {
- result += `### ${title}\n\n先写下判断、原因与验证方式,再对照本单元末尾的参考要点。\n\n`;
- for (const item of items)
- result += `#### ${item.id.toUpperCase()} ${item.title}\n\n${item.prompt}\n\n我的回答:\n\n自评:□ 还需要回学 □ 合上要点后能独立回答并举例或推演\n\n需要时回学:${links(lesson, item)}\n\n`;
- }
- result += '### 掌握标准与证据\n\n';
- for (const item of lesson.mastery)
- result += `- [ ] ${item.text}\n\n 证据:${item.evidence}\n\n`;
- result +=
- '### 本轮结果与回学计划\n\n八项问题均能独立回答,且四项证据均已核对,才达到本轮标准。未完成的题按回学链接补课,再闭卷重答;已经完成的题,隔一天换个条件再次推演。\n\n需要回学的题号:\n\n尚缺的证据:\n\n下次复测时准备改变的条件:\n\n### 参考要点与易错点\n\n完成自己的回答后再阅读。表述可以不同,要核对的是推理、边界和验证依据。\n\n';
- for (const item of [...lesson.questions, ...lesson.challenges])
- result += `#### ${item.id.toUpperCase()} ${item.title}\n\n${bullets(item.checkpoints)}\n\n容易误判:${item.misconception}\n\n`;
- result += '---\n\n';
- }
- return result;
- }
|