@thanh01.pmt/curriculum-kit 1.4.7 → 1.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +341 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -6
- package/dist/index.d.ts +9 -6
- package/dist/index.mjs +341 -120
- package/dist/index.mjs.map +1 -1
- package/dist/storage/index.cjs +37 -8
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.mjs +37 -8
- package/dist/storage/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +218 -84
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.mjs +218 -84
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +21 -22
package/dist/index.cjs
CHANGED
|
@@ -8417,13 +8417,16 @@ function computeContentHash(content) {
|
|
|
8417
8417
|
return "sha256:" + crypto__default.default.createHash("sha256").update(content.trim(), "utf-8").digest("hex");
|
|
8418
8418
|
}
|
|
8419
8419
|
var STANDARD_SOT_FILES = [
|
|
8420
|
-
"PROJECT_BRIEF.md",
|
|
8421
8420
|
"LEARNER_PROFILE.md",
|
|
8421
|
+
"PROJECT_BRIEF.md",
|
|
8422
|
+
"REFERENCE_PACK.md",
|
|
8423
|
+
"PROJECT_GRAPH.json",
|
|
8424
|
+
"HYBRID_GRAPH.json",
|
|
8425
|
+
"SECTION_LANGUAGE_CONTRACT.md",
|
|
8422
8426
|
"CURRICULUM_FRAMEWORK.md",
|
|
8423
|
-
"PROJECT_STATUS.md",
|
|
8424
8427
|
"CONTENT_STYLE_GUIDE.md",
|
|
8425
8428
|
"ART_DIRECTION.md",
|
|
8426
|
-
"
|
|
8429
|
+
"ALIGNMENT_MATRIX.md"
|
|
8427
8430
|
];
|
|
8428
8431
|
var FileSystemCurriculumAdapter = class {
|
|
8429
8432
|
baseDir;
|
|
@@ -8465,19 +8468,33 @@ var FileSystemCurriculumAdapter = class {
|
|
|
8465
8468
|
async listSotDocuments(projectId) {
|
|
8466
8469
|
const projectDir = this.getProjectDir(projectId);
|
|
8467
8470
|
const sotDir = path3__default.default.join(projectDir, "_sot");
|
|
8468
|
-
|
|
8471
|
+
const discoveredFiles = new Set(STANDARD_SOT_FILES);
|
|
8472
|
+
if (fs2__default.default.existsSync(sotDir)) {
|
|
8473
|
+
try {
|
|
8474
|
+
const diskFiles = fs2__default.default.readdirSync(sotDir);
|
|
8475
|
+
for (const f of diskFiles) {
|
|
8476
|
+
if (f.startsWith(".") || f.endsWith(".review.json")) continue;
|
|
8477
|
+
const fullPath = path3__default.default.join(sotDir, f);
|
|
8478
|
+
if (fs2__default.default.statSync(fullPath).isFile()) {
|
|
8479
|
+
discoveredFiles.add(f);
|
|
8480
|
+
}
|
|
8481
|
+
}
|
|
8482
|
+
} catch {
|
|
8483
|
+
}
|
|
8484
|
+
}
|
|
8485
|
+
return Array.from(discoveredFiles).map((filename) => {
|
|
8469
8486
|
const p = fs2__default.default.existsSync(path3__default.default.join(sotDir, filename)) ? path3__default.default.join(sotDir, filename) : fs2__default.default.existsSync(path3__default.default.join(projectDir, filename)) ? path3__default.default.join(projectDir, filename) : null;
|
|
8470
8487
|
if (p && fs2__default.default.existsSync(p)) {
|
|
8471
8488
|
const stats = fs2__default.default.statSync(p);
|
|
8472
8489
|
return {
|
|
8473
|
-
name: filename.replace(
|
|
8490
|
+
name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
|
|
8474
8491
|
filename,
|
|
8475
8492
|
exists: true,
|
|
8476
8493
|
sizeBytes: stats.size
|
|
8477
8494
|
};
|
|
8478
8495
|
}
|
|
8479
8496
|
return {
|
|
8480
|
-
name: filename.replace(
|
|
8497
|
+
name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
|
|
8481
8498
|
filename,
|
|
8482
8499
|
exists: false,
|
|
8483
8500
|
sizeBytes: 0
|
|
@@ -8723,11 +8740,23 @@ var SupabaseCurriculumAdapter = class {
|
|
|
8723
8740
|
return null;
|
|
8724
8741
|
}
|
|
8725
8742
|
async listSotDocuments(projectId) {
|
|
8743
|
+
const discoveredFiles = new Set(STANDARD_SOT_FILES);
|
|
8744
|
+
try {
|
|
8745
|
+
const { data, error } = await this.client.storage.from(this.bucketName).list(`${projectId}/_sot`);
|
|
8746
|
+
if (!error && data) {
|
|
8747
|
+
for (const item of data) {
|
|
8748
|
+
if (item.name && !item.name.startsWith(".") && !item.name.endsWith(".review.json")) {
|
|
8749
|
+
discoveredFiles.add(item.name);
|
|
8750
|
+
}
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
} catch {
|
|
8754
|
+
}
|
|
8726
8755
|
const results = [];
|
|
8727
|
-
for (const filename of
|
|
8756
|
+
for (const filename of discoveredFiles) {
|
|
8728
8757
|
const content = await this.readSotDocument(projectId, filename);
|
|
8729
8758
|
results.push({
|
|
8730
|
-
name: filename.replace(
|
|
8759
|
+
name: filename.replace(/\.(md|json)$/i, "").replace(/_/g, " "),
|
|
8731
8760
|
filename,
|
|
8732
8761
|
exists: !!content,
|
|
8733
8762
|
content: content || void 0,
|
|
@@ -9104,10 +9133,12 @@ async function auditQualityReport(storage, projectId) {
|
|
|
9104
9133
|
computedScore += Math.round(bloomPassCount / total * 20);
|
|
9105
9134
|
computedScore += Math.round(Math.min(syntaxExecutableCount, total) / total * 20);
|
|
9106
9135
|
computedScore += Math.round(scopeCompletenessCount / total * 20);
|
|
9136
|
+
const sotCompletedCount = status.sotReadiness.filter((s) => s.exists).length;
|
|
9137
|
+
const sotTotalCount = status.sotReadiness.length;
|
|
9107
9138
|
if (sotReady) {
|
|
9108
|
-
details.push(
|
|
9139
|
+
details.push(`\u2705 N\u1EC1n t\u1EA3ng SOT (${sotCompletedCount}/${sotTotalCount} t\xE0i li\u1EC7u) s\u1EB5n s\xE0ng.`);
|
|
9109
9140
|
} else {
|
|
9110
|
-
details.push(
|
|
9141
|
+
details.push(`\u26A0\uFE0F C\u1EA7n ho\xE0n thi\u1EC7n \u0111\u1EA7y \u0111\u1EE7 t\xE0i li\u1EC7u SOT c\u01A1 s\u1EDF (${sotCompletedCount}/${sotTotalCount}).`);
|
|
9111
9142
|
}
|
|
9112
9143
|
details.push(`\u{1F3AF} Ph\u1EA1m vi h\u1ECDc li\u1EC7u theo SOT (Artifact Scope): [${artifactScope.join(", ")}].`);
|
|
9113
9144
|
if (pedagogy5EPass) {
|
|
@@ -9123,7 +9154,7 @@ async function auditQualityReport(storage, projectId) {
|
|
|
9123
9154
|
details.push(`\u2705 \u0110\u1ED9 ph\u1EE7 \u0111\u1EA7y \u0111\u1EE7 c\xE1c h\u1ECDc li\u1EC7u trong Artifact Scope: ${scopeCompletenessCount}/${total} b\xE0i h\u1ECDc.`);
|
|
9124
9155
|
const rawMarkdownReport = `## \u{1F6E1}\uFE0F B\xC1O C\xC1O KI\u1EC2M \u0110\u1ECANH CH\u1EA4T L\u01AF\u1EE2NG (QUALITY AUDIT): \`${projectId.toUpperCase()}\`
|
|
9125
9156
|
- **\u0110i\u1EC3m th\u1EA9m \u0111\u1ECBnh ch\u1EA5t l\u01B0\u1EE3ng:** **${computedScore}/100**
|
|
9126
|
-
- **Tr\u1EA1ng th\xE1i SOT:** ${sotReady ?
|
|
9157
|
+
- **Tr\u1EA1ng th\xE1i SOT:** ${sotReady ? `\u2705 ${sotCompletedCount}/${sotTotalCount} File SOT ho\xE0n t\u1EA5t` : `\u26A0\uFE0F C\u1EA7n b\u1ED5 sung t\xE0i li\u1EC7u SOT thi\u1EBFu (${sotCompletedCount}/${sotTotalCount})`}.
|
|
9127
9158
|
- **Ph\u1EA1m vi h\u1ECDc li\u1EC7u (SOT Artifact Scope):** \`${artifactScope.join(", ")}\`
|
|
9128
9159
|
- **C\u1EA5u tr\xFAc S\u01B0 ph\u1EA1m (5E / EDP):** ${pedagogy5ECount}/${total} b\xE0i h\u1ECDc \u0111\u1EA1t chu\u1EA9n ph\xE2n pha.
|
|
9129
9160
|
- **Thang \u0111o nh\u1EADn th\u1EE9c Bloom:** ${bloomPassCount}/${total} b\xE0i h\u1ECDc \u0111\u1EA1t ma tr\u1EADn m\u1EE5c ti\xEAu.
|
|
@@ -9225,7 +9256,9 @@ function extractScopeSequenceRows(framework) {
|
|
|
9225
9256
|
const trimmed = line.trim();
|
|
9226
9257
|
if (!trimmed.startsWith("|")) continue;
|
|
9227
9258
|
const lower = trimmed.toLowerCase();
|
|
9228
|
-
|
|
9259
|
+
const hasCodeCol = lower.includes("lesson code") || lower.includes("m\xE3 b\xE0i") || lower.includes("m\xE3 b\xE0i h\u1ECDc");
|
|
9260
|
+
const hasObjCol = lower.includes("learning objective") || lower.includes("m\u1EE5c ti\xEAu") || lower.includes("m\u1EE5c ti\xEAu h\u1ECDc t\u1EADp");
|
|
9261
|
+
if (hasCodeCol && hasObjCol) {
|
|
9229
9262
|
headerFound = true;
|
|
9230
9263
|
continue;
|
|
9231
9264
|
}
|
|
@@ -9819,6 +9852,224 @@ function gateModeFor(resolved, type) {
|
|
|
9819
9852
|
return resolved[type] ?? "OFF";
|
|
9820
9853
|
}
|
|
9821
9854
|
|
|
9855
|
+
// src/services/languageDirective.ts
|
|
9856
|
+
function resolveTargetLanguageCode(language) {
|
|
9857
|
+
const raw = String(language || "vi").trim().toLowerCase();
|
|
9858
|
+
if (raw.startsWith("vi")) return "vi";
|
|
9859
|
+
if (raw.startsWith("en")) return "en";
|
|
9860
|
+
return raw || "vi";
|
|
9861
|
+
}
|
|
9862
|
+
function targetLanguageDisplayName(code) {
|
|
9863
|
+
const map = {
|
|
9864
|
+
vi: "Vietnamese (Ti\u1EBFng Vi\u1EC7t)",
|
|
9865
|
+
en: "English (US)"
|
|
9866
|
+
};
|
|
9867
|
+
return map[code] || code;
|
|
9868
|
+
}
|
|
9869
|
+
function buildLanguageDirective(targetLanguage) {
|
|
9870
|
+
const code = resolveTargetLanguageCode(targetLanguage);
|
|
9871
|
+
const name = targetLanguageDisplayName(code);
|
|
9872
|
+
const perLanguageExamples = {
|
|
9873
|
+
vi: `- V\xED d\u1EE5 ti\xEAu \u0111\u1EC1 b\xE0i h\u1ECDc \u0111\xFAng chu\u1EA9n: "B\xE0i 1: Kh\xE1m ph\xE1 m\u1EA1ch \u0111i\u1EC7n c\u01A1 b\u1EA3n", "B\xE0i 2: \u0110i\u1EC1u khi\u1EC3n \u0111\xE8n LED nh\u1EA5p nh\xE1y" \u2014 KH\xD4NG d\xF9ng ti\xEAu \u0111\u1EC1 ti\u1EBFng Anh.`,
|
|
9874
|
+
en: `- Example compliant lesson title: "Lesson 1: Exploring Basic Circuits" \u2014 natural, idiomatic English throughout.`
|
|
9875
|
+
};
|
|
9876
|
+
const exampleLine = perLanguageExamples[code] || "";
|
|
9877
|
+
return `MANDATORY LANGUAGE DIRECTIVE (TARGET OUTPUT LANGUAGE: ${name}):
|
|
9878
|
+
- The TARGET OUTPUT LANGUAGE for this course is ${name}. You MUST author the ENTIRE document in ${name}.
|
|
9879
|
+
- All section titles, table headers, table cells, lesson names, learning objectives, pedagogical narratives, and cognitive analyses MUST be written in natural, fluent, academic ${name}.
|
|
9880
|
+
- Technical identifiers, code keywords (e.g. setup(), loop(), pinMode()), standard protocols (I2C, SPI, UART, GPIO), and component model numbers (ESP32, Arduino Uno) remain in standard technical notation, but all surrounding explanations MUST be in ${name}.
|
|
9881
|
+
- DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
|
|
9882
|
+
${exampleLine}` : ""}`;
|
|
9883
|
+
}
|
|
9884
|
+
function matchesArtifactType(candidate, target) {
|
|
9885
|
+
const c = candidate.trim().toUpperCase();
|
|
9886
|
+
const t = target.trim().toUpperCase();
|
|
9887
|
+
if (c === t) return true;
|
|
9888
|
+
if (c.startsWith("LESSON") && t.startsWith("LESSON")) return true;
|
|
9889
|
+
if ((c === "CODE" || c === "CODE_LAB") && (t === "CODE" || t === "CODE_LAB")) return true;
|
|
9890
|
+
if ((c === "EXPOSITION" || c === "KNOWLEDGE_EXPOSITION") && (t === "EXPOSITION" || t === "KNOWLEDGE_EXPOSITION")) return true;
|
|
9891
|
+
if ((c === "WKS" || c === "WORKSHEET") && (t === "WKS" || t === "WORKSHEET")) return true;
|
|
9892
|
+
if ((c === "EXT" || c === "EXTENSION") && (t === "EXT" || t === "EXTENSION")) return true;
|
|
9893
|
+
return false;
|
|
9894
|
+
}
|
|
9895
|
+
var DEFAULT_VIETNAMESE_SECTION_HEADINGS = {
|
|
9896
|
+
LEARNER_PROFILE: {
|
|
9897
|
+
"Foundational Info": "Th\xF4ng tin N\u1EC1n t\u1EA3ng",
|
|
9898
|
+
"Entry Level": "Tr\xECnh \u0111\u1ED9 \u0110\u1EA7u v\xE0o",
|
|
9899
|
+
"Learning Context": "B\u1ED1i c\u1EA3nh H\u1ECDc t\u1EADp",
|
|
9900
|
+
"Client Objectives": "M\u1EE5c ti\xEAu Kh\xE1ch h\xE0ng & Chu\u1EA9n \u0111\u1EA7u ra"
|
|
9901
|
+
},
|
|
9902
|
+
PROJECT_BRIEF: {
|
|
9903
|
+
"Project Overview": "T\u1ED5ng quan D\u1EF1 \xE1n",
|
|
9904
|
+
"Audience & Duration": "\u0110\u1ED1i t\u01B0\u1EE3ng & Th\u1EDDi l\u01B0\u1EE3ng",
|
|
9905
|
+
"Learning Roadmap": "L\u1ED9 tr\xECnh H\u1ECDc t\u1EADp & C\u1ED9t m\u1ED1c",
|
|
9906
|
+
"Artifact Scope": "Ph\u1EA1m vi S\u1EA3n ph\u1EA9m H\u1ECDc t\u1EADp"
|
|
9907
|
+
},
|
|
9908
|
+
CURRICULUM_FRAMEWORK: {
|
|
9909
|
+
"Course Overview": "T\u1ED5ng quan Kh\xF3a h\u1ECDc",
|
|
9910
|
+
"Global Learning Objectives": "M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n",
|
|
9911
|
+
"Structural Hierarchy (Unit & Module)": "C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)",
|
|
9912
|
+
"Scope & Sequence (Detailed Roadmap)": "Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt"
|
|
9913
|
+
},
|
|
9914
|
+
CONTENT_STYLE_GUIDE: {
|
|
9915
|
+
"Voice & Tone": "Gi\u1ECDng v\u0103n & Phong c\xE1ch",
|
|
9916
|
+
"Standard Terminology (Glossary)": "Thu\u1EADt ng\u1EEF Chu\u1EA9n (B\u1EA3ng thu\u1EADt ng\u1EEF)",
|
|
9917
|
+
"Examples & Context Rules": "Quy t\u1EAFc V\xED d\u1EE5 & Ng\u1EEF c\u1EA3nh",
|
|
9918
|
+
"Lesson Content Standards": "Ti\xEAu chu\u1EA9n N\u1ED9i dung B\xE0i h\u1ECDc"
|
|
9919
|
+
},
|
|
9920
|
+
SECTION_LANGUAGE_CONTRACT: {
|
|
9921
|
+
"Metadata & Scope": "Th\xF4ng tin & Ph\u1EA1m vi H\u1EE3p \u0111\u1ED3ng",
|
|
9922
|
+
"Section Translation Matrix": "Ma tr\u1EADn Ti\xEAu \u0111\u1EC1 \u0110a ng\xF4n ng\u1EEF",
|
|
9923
|
+
"Validation Rules": "Quy t\u1EAFc X\xE1c th\u1EF1c & Kh\xF3a Chu\u1EA9n"
|
|
9924
|
+
},
|
|
9925
|
+
KNOWLEDGE_EXPOSITION: {
|
|
9926
|
+
"Session Scope": "Ph\u1EA1m vi Bu\u1ED5i h\u1ECDc",
|
|
9927
|
+
"Key Terms": "Thu\u1EADt ng\u1EEF Then ch\u1ED1t",
|
|
9928
|
+
"Concept Narratives": "Di\u1EC5n gi\u1EA3i Kh\xE1i ni\u1EC7m",
|
|
9929
|
+
"Worked Micro-Examples": "V\xED d\u1EE5 M\u1EABu Chi ti\u1EBFt",
|
|
9930
|
+
"Common Mistakes": "L\u1ED7i Th\u01B0\u1EDDng g\u1EB7p & Kh\u1EAFc ph\u1EE5c",
|
|
9931
|
+
"Self-Check Questions": "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra"
|
|
9932
|
+
},
|
|
9933
|
+
LESSON: {
|
|
9934
|
+
"A. Lesson Design Plan": "A. K\u1EBF ho\u1EA1ch Thi\u1EBFt k\u1EBF B\xE0i h\u1ECDc",
|
|
9935
|
+
"Learning Objectives & Evidence": "M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp & B\u1EB1ng ch\u1EE9ng N\u0103ng l\u1EF1c",
|
|
9936
|
+
"Activity Sequence": "Chu\u1ED7i Ho\u1EA1t \u0111\u1ED9ng D\u1EA1y & H\u1ECDc",
|
|
9937
|
+
"Resource Map": "B\u1EA3n \u0111\u1ED3 T\xE0i nguy\xEAn & H\u1ECDc li\u1EC7u",
|
|
9938
|
+
"Assessment Map": "B\u1EA3n \u0111\u1ED3 \u0110\xE1nh gi\xE1 N\u0103ng l\u1EF1c",
|
|
9939
|
+
"Artifact Contract": "H\u1EE3p \u0111\u1ED3ng S\u1EA3n ph\u1EA9m \u0110\u1EA7u ra",
|
|
9940
|
+
"B. Lesson Flow": "B. Ti\u1EBFn tr\xECnh Gi\u1EA3ng d\u1EA1y Chi ti\u1EBFt"
|
|
9941
|
+
},
|
|
9942
|
+
ACT: {
|
|
9943
|
+
"Computational Thinking Focus": "Tr\u1ECDng t\xE2m T\u01B0 duy M\xE1y t\xEDnh",
|
|
9944
|
+
"Materials & Setup": "V\u1EADt li\u1EC7u & Chu\u1EA9n b\u1ECB Thi\u1EBFt b\u1ECB",
|
|
9945
|
+
"Constraints & Safety": "R\xE0ng bu\u1ED9c & An to\xE0n Ph\xF2ng th\u1EF1c h\xE0nh",
|
|
9946
|
+
"Active Learning Workflow": "Quy tr\xECnh Ho\u1EA1t \u0111\u1ED9ng Tr\u1EA3i nghi\u1EC7m",
|
|
9947
|
+
"3-Tier Differentiation": "Ph\xE2n h\xF3a H\u1ECDc t\u1EADp 3 T\u1EA7ng",
|
|
9948
|
+
"Reflection": "T\u1ED5ng k\u1EBFt & \u0110\xFAc k\u1EBFt Tr\u1EA3i nghi\u1EC7m"
|
|
9949
|
+
},
|
|
9950
|
+
CODE: {
|
|
9951
|
+
"Technical Overview & Architecture Blueprint": "T\u1ED5ng quan K\u1EF9 thu\u1EADt & B\u1EA3n thi\u1EBFt k\u1EBF Ki\u1EBFn tr\xFAc",
|
|
9952
|
+
"Hardware Pinout & Wiring Configuration Matrix": "S\u01A1 \u0111\u1ED3 Ch\xE2n & Ma tr\u1EADn N\u1ED1i d\xE2y Ph\u1EA7n c\u1EE9ng",
|
|
9953
|
+
"Starter Code Sandbox": "M\xE3 ngu\u1ED3n Kh\u1EDFi \u0111\u1EA7u (Starter Code)",
|
|
9954
|
+
"Verified Reference Solution Code": "M\xE3 ngu\u1ED3n L\u1EDDi gi\u1EA3i Chu\u1EA9n (Reference Solution)",
|
|
9955
|
+
"Automated Test / Verification Script": "K\u1ECBch b\u1EA3n Ki\u1EC3m th\u1EED T\u1EF1 \u0111\u1ED9ng",
|
|
9956
|
+
"Common Syntax & Runtime Pitfalls Matrix": "Ma tr\u1EADn L\u1ED7i C\xFA ph\xE1p & Th\u1EDDi gian ch\u1EA1y Th\u01B0\u1EDDng g\u1EB7p"
|
|
9957
|
+
},
|
|
9958
|
+
CODE_LAB: {
|
|
9959
|
+
"Technical Overview & Architecture Blueprint": "T\u1ED5ng quan K\u1EF9 thu\u1EADt & B\u1EA3n thi\u1EBFt k\u1EBF Ki\u1EBFn tr\xFAc",
|
|
9960
|
+
"Hardware Pinout & Wiring Configuration Matrix": "S\u01A1 \u0111\u1ED3 Ch\xE2n & Ma tr\u1EADn N\u1ED1i d\xE2y Ph\u1EA7n c\u1EE9ng",
|
|
9961
|
+
"Starter Code Sandbox": "M\xE3 ngu\u1ED3n Kh\u1EDFi \u0111\u1EA7u (Starter Code)",
|
|
9962
|
+
"Verified Reference Solution Code": "M\xE3 ngu\u1ED3n L\u1EDDi gi\u1EA3i Chu\u1EA9n (Reference Solution)",
|
|
9963
|
+
"Automated Test / Verification Script": "K\u1ECBch b\u1EA3n Ki\u1EC3m th\u1EED T\u1EF1 \u0111\u1ED9ng",
|
|
9964
|
+
"Common Syntax & Runtime Pitfalls Matrix": "Ma tr\u1EADn L\u1ED7i C\xFA ph\xE1p & Th\u1EDDi gian ch\u1EA1y Th\u01B0\u1EDDng g\u1EB7p"
|
|
9965
|
+
},
|
|
9966
|
+
QUIZ: {
|
|
9967
|
+
"Assessment Objectives": "M\u1EE5c ti\xEAu \u0110\xE1nh gi\xE1",
|
|
9968
|
+
"Multiple-Choice Question Bank": "Ng\xE2n h\xE0ng C\xE2u h\u1ECFi Tr\u1EAFc nghi\u1EC7m",
|
|
9969
|
+
"Code Analysis / Debugging Question": "C\xE2u h\u1ECFi Ph\xE2n t\xEDch M\xE3 ngu\u1ED3n & S\u1EEDa l\u1ED7i",
|
|
9970
|
+
"Competency Rubric": "Thang \u0111o \u0110\xE1nh gi\xE1 N\u0103ng l\u1EF1c (Rubric)"
|
|
9971
|
+
},
|
|
9972
|
+
GUIDE: {
|
|
9973
|
+
"Objectives & Preparation": "M\u1EE5c ti\xEAu & Chu\u1EA9n b\u1ECB Gi\u1EA3ng d\u1EA1y",
|
|
9974
|
+
"Preparation Checklist & Workstation Setup": "Danh m\u1EE5c Ki\u1EC3m tra Chu\u1EA9n b\u1ECB & Tr\u1EA1m Th\u1EF1c h\xE0nh",
|
|
9975
|
+
"Facilitation Script & Timeline": "K\u1ECBch b\u1EA3n \u0110i\u1EC1u ph\u1ED1i & Khung Th\u1EDDi gian",
|
|
9976
|
+
"Common Misconceptions & Diagnostic Remediation Matrix": "Quan ni\u1EC7m Sai l\u1EA7m Th\u01B0\u1EDDng g\u1EB7p & Ma tr\u1EADn Kh\u1EAFc ph\u1EE5c",
|
|
9977
|
+
"Differentiated Support Strategies": "Chi\u1EBFn l\u01B0\u1EE3c H\u1ED7 tr\u1EE3 Ph\xE2n h\xF3a"
|
|
9978
|
+
},
|
|
9979
|
+
HANDOUT: {
|
|
9980
|
+
"Core Concepts": "Kh\xE1i ni\u1EC7m C\u1ED1t l\xF5i",
|
|
9981
|
+
"Core Syntax & Mechanism Cheat Sheet": "B\u1EA3ng Tra c\u1EE9u C\xFA ph\xE1p & C\u01A1 ch\u1EBF Tr\u1ECDng t\xE2m",
|
|
9982
|
+
"Visual Mental Model / Architecture Diagram": "M\xF4 h\xECnh T\u01B0 duy Tr\u1EF1c quan / S\u01A1 \u0111\u1ED3 Ki\u1EBFn tr\xFAc",
|
|
9983
|
+
"Step-by-Step Practical Quick-Start Guide": "H\u01B0\u1EDBng d\u1EABn Th\u1EF1c h\xE0nh T\u1EEBng b\u01B0\u1EDBc",
|
|
9984
|
+
"Self-Check Diagnostic Checklist": "B\u1EA3ng Ki\u1EC3m tra T\u1EF1 ch\u1EA9n \u0111o\xE1n"
|
|
9985
|
+
},
|
|
9986
|
+
WKS: {
|
|
9987
|
+
"Part 1: Knowledge Check": "Ph\u1EA7n 1: Ki\u1EC3m tra Ki\u1EBFn th\u1EE9c",
|
|
9988
|
+
"Part 2: Concept Tracing & Diagram Fill-in": "Ph\u1EA7n 2: L\u1EA7n v\u1EBFt Kh\xE1i ni\u1EC7m & \u0110i\u1EC1n S\u01A1 \u0111\u1ED3",
|
|
9989
|
+
"Part 3: Code Analysis & Bug Hunting Challenge": "Ph\u1EA7n 3: Ph\xE2n t\xEDch M\xE3 & Th\u1EED th\xE1ch S\u0103n l\u1ED7i",
|
|
9990
|
+
"Part 4: Synthesis & Problem-Solving Application": "Ph\u1EA7n 4: T\u1ED5ng h\u1EE3p & \u1EE8ng d\u1EE5ng Gi\u1EA3i quy\u1EBFt V\u1EA5n \u0111\u1EC1",
|
|
9991
|
+
"Part 5: Self-Reflection & Learning Log": "Ph\u1EA7n 5: T\u1EF1 suy ng\u1EABm & Nh\u1EADt k\xFD H\u1ECDc t\u1EADp"
|
|
9992
|
+
},
|
|
9993
|
+
EXT: {
|
|
9994
|
+
"For: Advanced Students / Extra Time": "D\xE0nh cho: H\u1ECDc sinh N\xE2ng cao / Ho\xE0n th\xE0nh S\u1EDBm",
|
|
9995
|
+
"Challenge 1: Mission Briefing & Real-World High-Stakes Scenario": "Th\u1EED th\xE1ch 1: B\u1ED1i c\u1EA3nh Nhi\u1EC7m v\u1EE5 Th\u1EF1c t\u1EBF & K\u1ECBch b\u1EA3n \u1EE8ng d\u1EE5ng",
|
|
9996
|
+
"Challenge 2: Advanced Architectural Constraints & Edge Cases": "Th\u1EED th\xE1ch 2: R\xE0ng bu\u1ED9c Ki\u1EBFn tr\xFAc N\xE2ng cao & Tr\u01B0\u1EDDng h\u1EE3p Bi\xEAn",
|
|
9997
|
+
"Challenge 3: Extension Milestones": "Th\u1EED th\xE1ch 3: C\xE1c M\u1ED1c Nhi\u1EC7m v\u1EE5 N\xE2ng cao",
|
|
9998
|
+
"Metacognitive Deep Dive & Engineering Trade-offs": "Suy ng\u1EABm Si\xEAu nh\u1EADn th\u1EE9c & \u0110\xE1nh \u0111\u1ED5i K\u1EF9 thu\u1EADt"
|
|
9999
|
+
},
|
|
10000
|
+
SLIDE: {
|
|
10001
|
+
"Title & Agenda": "Ti\xEAu \u0111\u1EC1 & M\u1EE5c l\u1EE5c",
|
|
10002
|
+
"Core Concepts": "Kh\xE1i ni\u1EC7m C\u1ED1t l\xF5i",
|
|
10003
|
+
"Guided Demonstration": "H\u01B0\u1EDBng d\u1EABn Th\u1EF1c h\xE0nh & Th\u1ECB ph\u1EA1m",
|
|
10004
|
+
"Hands-on Mission": "Nhi\u1EC7m v\u1EE5 Th\u1EF1c h\xE0nh Tr\u1EA3i nghi\u1EC7m",
|
|
10005
|
+
"Summary & Next Steps": "T\u1ED5ng k\u1EBFt & \u0110\u1ECBnh h\u01B0\u1EDBng Ti\u1EBFp theo"
|
|
10006
|
+
}
|
|
10007
|
+
};
|
|
10008
|
+
function extractSectionHeadingsFromSLC(slcInput, artifactType, targetLanguage = "vi") {
|
|
10009
|
+
const upperType = artifactType.trim().toUpperCase();
|
|
10010
|
+
const getFallback = () => {
|
|
10011
|
+
if (resolveTargetLanguageCode(targetLanguage) === "vi") {
|
|
10012
|
+
for (const [key, mapping] of Object.entries(DEFAULT_VIETNAMESE_SECTION_HEADINGS)) {
|
|
10013
|
+
if (matchesArtifactType(key, upperType)) {
|
|
10014
|
+
return mapping;
|
|
10015
|
+
}
|
|
10016
|
+
}
|
|
10017
|
+
}
|
|
10018
|
+
return {};
|
|
10019
|
+
};
|
|
10020
|
+
if (!slcInput) return getFallback();
|
|
10021
|
+
if (typeof slcInput === "object") {
|
|
10022
|
+
if (slcInput[upperType] && Object.keys(slcInput[upperType]).length > 0) return slcInput[upperType];
|
|
10023
|
+
for (const [key, mapping] of Object.entries(slcInput)) {
|
|
10024
|
+
if (matchesArtifactType(key, upperType) && Object.keys(mapping).length > 0) {
|
|
10025
|
+
return mapping;
|
|
10026
|
+
}
|
|
10027
|
+
}
|
|
10028
|
+
return getFallback();
|
|
10029
|
+
}
|
|
10030
|
+
const slcMarkdown = slcInput;
|
|
10031
|
+
const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
|
|
10032
|
+
for (const block of blocks) {
|
|
10033
|
+
const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
|
|
10034
|
+
if (!headerMatch) continue;
|
|
10035
|
+
const blockType = headerMatch[1].trim().toUpperCase();
|
|
10036
|
+
if (matchesArtifactType(blockType, upperType)) {
|
|
10037
|
+
const mapping = {};
|
|
10038
|
+
const lines = block.split("\n");
|
|
10039
|
+
for (const line of lines) {
|
|
10040
|
+
const trimmed = line.trim();
|
|
10041
|
+
if (trimmed.startsWith("|") && !trimmed.includes("---")) {
|
|
10042
|
+
const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
10043
|
+
if (cells.length >= 2 && !cells[0].toLowerCase().includes("canonical") && !cells[1].toLowerCase().includes("localized")) {
|
|
10044
|
+
mapping[cells[0]] = cells[1];
|
|
10045
|
+
}
|
|
10046
|
+
} else {
|
|
10047
|
+
const bullet = line.match(/^[-*]\s+([^:]+):\s+(.+)$/);
|
|
10048
|
+
if (bullet) {
|
|
10049
|
+
mapping[bullet[1].trim()] = bullet[2].trim();
|
|
10050
|
+
}
|
|
10051
|
+
}
|
|
10052
|
+
}
|
|
10053
|
+
if (Object.keys(mapping).length > 0) return mapping;
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
return getFallback();
|
|
10057
|
+
}
|
|
10058
|
+
function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
|
|
10059
|
+
const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType, targetLanguage);
|
|
10060
|
+
if (Object.keys(headings).length === 0) {
|
|
10061
|
+
return "";
|
|
10062
|
+
}
|
|
10063
|
+
const lines = Object.entries(headings).map(
|
|
10064
|
+
([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
|
|
10065
|
+
);
|
|
10066
|
+
return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
|
|
10067
|
+
- The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
|
|
10068
|
+
- In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
|
|
10069
|
+
${lines.join("\n")}
|
|
10070
|
+
- For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
|
|
10071
|
+
}
|
|
10072
|
+
|
|
9822
10073
|
// src/services/contextBuilder.ts
|
|
9823
10074
|
var DEFAULT_LESSON_PRIORITIES = [
|
|
9824
10075
|
"A. Lesson Design Plan",
|
|
@@ -10108,7 +10359,8 @@ var DEPTH_RULES = {
|
|
|
10108
10359
|
cio: "Write WHAT + WHY + HOW: include the mechanism, step by step, still technology-independent.",
|
|
10109
10360
|
sio: "Write WHAT + WHY + HOW + SPECIFIC: include concrete implementations and how the real project keywords are used."
|
|
10110
10361
|
};
|
|
10111
|
-
function buildSystemPrompt() {
|
|
10362
|
+
function buildSystemPrompt(targetLanguage = "vi") {
|
|
10363
|
+
const langDirective = buildLanguageDirective(targetLanguage);
|
|
10112
10364
|
return [
|
|
10113
10365
|
"You write KNOWLEDGE_EXPOSITION: self-study reading material for a student in a teacherless program.",
|
|
10114
10366
|
"Rules:",
|
|
@@ -10117,12 +10369,22 @@ function buildSystemPrompt() {
|
|
|
10117
10369
|
"3. Respect each concept depth target (ULO/CIO/SIO) \u2014 do not overshoot.",
|
|
10118
10370
|
"4. Student-facing only: no teacher instructions, no classroom management text.",
|
|
10119
10371
|
"5. Output the full markdown document following the provided section skeleton exactly (headers and placeholders preserved).",
|
|
10120
|
-
|
|
10372
|
+
langDirective
|
|
10121
10373
|
].join("\n");
|
|
10122
10374
|
}
|
|
10123
|
-
function buildUserPrompt(session, plan, glossary) {
|
|
10375
|
+
function buildUserPrompt(session, plan, glossary, targetLanguage = "vi", slcMarkdown) {
|
|
10124
10376
|
const depthLines = session.depth_assignments.map((d) => "- " + d.node_id + " [" + d.depth.toUpperCase() + "]: " + DEPTH_RULES[d.depth]).join("\n");
|
|
10125
10377
|
const termLines = glossary.map((g) => "- " + g.term + (g.definition ? " \u2014 " + g.definition : "") + (g.example ? " (example: " + g.example + ")" : "")).join("\n");
|
|
10378
|
+
const langCode = resolveTargetLanguageCode(targetLanguage);
|
|
10379
|
+
const headings = extractSectionHeadingsFromSLC(slcMarkdown, "KNOWLEDGE_EXPOSITION");
|
|
10380
|
+
const hScope = headings["Session Scope"] || (langCode === "vi" ? "Ph\u1EA1m vi Bu\u1ED5i h\u1ECDc" : "Session Scope");
|
|
10381
|
+
const hTerms = headings["Key Terms"] || (langCode === "vi" ? "Thu\u1EADt ng\u1EEF Then ch\u1ED1t" : "Key Terms");
|
|
10382
|
+
const hNarratives = headings["Concept Narratives"] || (langCode === "vi" ? "Di\u1EC5n gi\u1EA3i Kh\xE1i ni\u1EC7m" : "Concept Narratives");
|
|
10383
|
+
const hExamples = headings["Worked Micro-Examples"] || (langCode === "vi" ? "V\xED d\u1EE5 M\u1EABu Chi ti\u1EBFt" : "Worked Micro-Examples");
|
|
10384
|
+
const hMistakes = headings["Common Mistakes"] || (langCode === "vi" ? "L\u1ED7i Th\u01B0\u1EDDng g\u1EB7p & Kh\u1EAFc ph\u1EE5c" : "Common Mistakes");
|
|
10385
|
+
const hQuestions = headings["Self-Check Questions"] || (langCode === "vi" ? "C\xE2u h\u1ECFi T\u1EF1 ki\u1EC3m tra" : "Self-Check Questions");
|
|
10386
|
+
const skeleton = `## ${hScope} / ## ${hTerms} / ## ${hNarratives} / ## ${hExamples} / ## ${hMistakes} / ## ${hQuestions}`;
|
|
10387
|
+
const headingDirective = buildHeadingDirective("KNOWLEDGE_EXPOSITION", slcMarkdown, targetLanguage);
|
|
10126
10388
|
return [
|
|
10127
10389
|
"Session: " + session.id + " \u2014 " + session.title,
|
|
10128
10390
|
"Objective: " + session.prose_objective,
|
|
@@ -10137,9 +10399,10 @@ function buildUserPrompt(session, plan, glossary) {
|
|
|
10137
10399
|
termLines || "(none provided \u2014 write definitions and mark them for glossary sync)",
|
|
10138
10400
|
"",
|
|
10139
10401
|
"Section skeleton to fill (replace ONLY the {{placeholders}}):",
|
|
10140
|
-
|
|
10402
|
+
skeleton,
|
|
10403
|
+
headingDirective ? "\n" + headingDirective : "",
|
|
10141
10404
|
"Plan hash: " + plan.plan_hash
|
|
10142
|
-
].join("\n");
|
|
10405
|
+
].filter(Boolean).join("\n");
|
|
10143
10406
|
}
|
|
10144
10407
|
async function ensureKnowledgeExposition(options) {
|
|
10145
10408
|
const { projectId, lessonCode, plan, glossary, llmFn, storage } = options;
|
|
@@ -10155,11 +10418,18 @@ async function ensureKnowledgeExposition(options) {
|
|
|
10155
10418
|
if (plan.approval.plan_hash !== plan.plan_hash) {
|
|
10156
10419
|
throw new ExpositionApprovalError("Approval hash " + plan.approval.plan_hash + " does not match current plan hash " + plan.plan_hash + " \u2014 re-approve after plan changes");
|
|
10157
10420
|
}
|
|
10158
|
-
const
|
|
10421
|
+
const targetLanguage = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
10422
|
+
const content = (await llmFn(
|
|
10423
|
+
buildSystemPrompt(targetLanguage),
|
|
10424
|
+
buildUserPrompt(session, plan, glossary, targetLanguage, options.slcMarkdown)
|
|
10425
|
+
)).trim();
|
|
10159
10426
|
if (content.length < 200) {
|
|
10160
10427
|
throw new Error("EXPOSITION too short for " + lessonCode + " (" + content.length + " chars) \u2014 refusing to save");
|
|
10161
10428
|
}
|
|
10162
|
-
|
|
10429
|
+
const hasKeyTerms = content.includes("Key Terms") || content.includes("Thu\u1EADt ng\u1EEF");
|
|
10430
|
+
const hasNarratives = content.includes("Concept Narratives") || content.includes("Kh\xE1i ni\u1EC7m") || content.includes("Di\u1EC5n gi\u1EA3i");
|
|
10431
|
+
const hasSelfCheck = content.includes("Self-Check") || content.includes("T\u1EF1 ki\u1EC3m tra") || content.includes("C\xE2u h\u1ECFi");
|
|
10432
|
+
if (!hasKeyTerms || !hasNarratives || !hasSelfCheck) {
|
|
10163
10433
|
throw new Error("EXPOSITION missing required sections for " + lessonCode + " \u2014 refusing to save");
|
|
10164
10434
|
}
|
|
10165
10435
|
const gateMode = gateModeFor(options.gates ?? {}, "KNOWLEDGE_EXPOSITION");
|
|
@@ -10221,16 +10491,20 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
10221
10491
|
const scopedTerms = new Set(plan.glossary_scope.find((g) => g.session_id === lessonCode)?.terms ?? []);
|
|
10222
10492
|
glossary = glossary.filter((g) => scopedTerms.has(g.term));
|
|
10223
10493
|
const { runCurriculumAIInference: runCurriculumAIInference2 } = await Promise.resolve().then(() => (init_streamRunner(), streamRunner_exports));
|
|
10494
|
+
const targetLang = options.targetLanguage || plan.translation?.target_language || "vi";
|
|
10224
10495
|
const result = await ensureKnowledgeExposition({
|
|
10225
10496
|
projectId,
|
|
10226
10497
|
lessonCode,
|
|
10227
10498
|
plan,
|
|
10228
10499
|
glossary,
|
|
10500
|
+
targetLanguage: targetLang,
|
|
10501
|
+
slcMarkdown: options.slcMarkdown,
|
|
10229
10502
|
llmFn: async (systemPrompt, userPrompt) => {
|
|
10503
|
+
const runnerOpts = options.customInference ? { customInference: options.customInference } : {};
|
|
10230
10504
|
const out = await runCurriculumAIInference2(
|
|
10231
10505
|
[{ role: "user", content: userPrompt + "\n\n[SYSTEM RULES]:\n" + systemPrompt }],
|
|
10232
|
-
|
|
10233
|
-
|
|
10506
|
+
`You are generating KNOWLEDGE_EXPOSITION - canonical self-study knowledge for one session in ${targetLang === "vi" ? "VIETNAMESE" : targetLang}. Output the complete markdown document only.`,
|
|
10507
|
+
runnerOpts
|
|
10234
10508
|
);
|
|
10235
10509
|
return out;
|
|
10236
10510
|
},
|
|
@@ -23978,90 +24252,6 @@ function extractStandardRefs(text) {
|
|
|
23978
24252
|
return Array.from(new Set(matches));
|
|
23979
24253
|
}
|
|
23980
24254
|
|
|
23981
|
-
// src/services/languageDirective.ts
|
|
23982
|
-
function resolveTargetLanguageCode(language) {
|
|
23983
|
-
const raw = String(language || "vi").trim().toLowerCase();
|
|
23984
|
-
if (raw.startsWith("vi")) return "vi";
|
|
23985
|
-
if (raw.startsWith("en")) return "en";
|
|
23986
|
-
return raw || "vi";
|
|
23987
|
-
}
|
|
23988
|
-
function targetLanguageDisplayName(code) {
|
|
23989
|
-
const map = {
|
|
23990
|
-
vi: "Vietnamese (Ti\u1EBFng Vi\u1EC7t)",
|
|
23991
|
-
en: "English (US)"
|
|
23992
|
-
};
|
|
23993
|
-
return map[code] || code;
|
|
23994
|
-
}
|
|
23995
|
-
function buildLanguageDirective(targetLanguage) {
|
|
23996
|
-
const code = resolveTargetLanguageCode(targetLanguage);
|
|
23997
|
-
const name = targetLanguageDisplayName(code);
|
|
23998
|
-
const perLanguageExamples = {
|
|
23999
|
-
vi: `- V\xED d\u1EE5 ti\xEAu \u0111\u1EC1 b\xE0i h\u1ECDc \u0111\xFAng chu\u1EA9n: "B\xE0i 1: Kh\xE1m ph\xE1 m\u1EA1ch \u0111i\u1EC7n c\u01A1 b\u1EA3n", "B\xE0i 2: \u0110i\u1EC1u khi\u1EC3n \u0111\xE8n LED nh\u1EA5p nh\xE1y" \u2014 KH\xD4NG d\xF9ng ti\xEAu \u0111\u1EC1 ti\u1EBFng Anh.`,
|
|
24000
|
-
en: `- Example compliant lesson title: "Lesson 1: Exploring Basic Circuits" \u2014 natural, idiomatic English throughout.`
|
|
24001
|
-
};
|
|
24002
|
-
const exampleLine = perLanguageExamples[code] || "";
|
|
24003
|
-
return `MANDATORY LANGUAGE DIRECTIVE (TARGET OUTPUT LANGUAGE: ${name}):
|
|
24004
|
-
- The TARGET OUTPUT LANGUAGE for this course is ${name}. You MUST author the ENTIRE document in ${name}.
|
|
24005
|
-
- All section titles, table headers, table cells, lesson names, learning objectives, pedagogical narratives, and cognitive analyses MUST be written in natural, fluent, academic ${name}.
|
|
24006
|
-
- Technical identifiers, code keywords (e.g. setup(), loop(), pinMode()), standard protocols (I2C, SPI, UART, GPIO), and component model numbers (ESP32, Arduino Uno) remain in standard technical notation, but all surrounding explanations MUST be in ${name}.
|
|
24007
|
-
- DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
|
|
24008
|
-
${exampleLine}` : ""}`;
|
|
24009
|
-
}
|
|
24010
|
-
function extractSectionHeadingsFromSLC(slcInput, artifactType) {
|
|
24011
|
-
if (!slcInput) return {};
|
|
24012
|
-
const upperType = artifactType.trim().toUpperCase();
|
|
24013
|
-
if (typeof slcInput === "object") {
|
|
24014
|
-
if (slcInput[upperType]) return slcInput[upperType];
|
|
24015
|
-
for (const [key, mapping] of Object.entries(slcInput)) {
|
|
24016
|
-
if (key.toUpperCase() === upperType || upperType.startsWith("LESSON") && key.toUpperCase().startsWith("LESSON")) {
|
|
24017
|
-
return mapping;
|
|
24018
|
-
}
|
|
24019
|
-
}
|
|
24020
|
-
return {};
|
|
24021
|
-
}
|
|
24022
|
-
const slcMarkdown = slcInput;
|
|
24023
|
-
const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
|
|
24024
|
-
for (const block of blocks) {
|
|
24025
|
-
const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
|
|
24026
|
-
if (!headerMatch) continue;
|
|
24027
|
-
const blockType = headerMatch[1].trim().toUpperCase();
|
|
24028
|
-
if (blockType === upperType || upperType.startsWith("LESSON") && blockType.startsWith("LESSON")) {
|
|
24029
|
-
const mapping = {};
|
|
24030
|
-
const lines = block.split("\n");
|
|
24031
|
-
for (const line of lines) {
|
|
24032
|
-
const trimmed = line.trim();
|
|
24033
|
-
if (trimmed.startsWith("|") && !trimmed.includes("---")) {
|
|
24034
|
-
const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
24035
|
-
if (cells.length >= 2 && !cells[0].toLowerCase().includes("canonical") && !cells[1].toLowerCase().includes("localized")) {
|
|
24036
|
-
mapping[cells[0]] = cells[1];
|
|
24037
|
-
}
|
|
24038
|
-
} else {
|
|
24039
|
-
const bullet = line.match(/^[-*]\s+([^:]+):\s+(.+)$/);
|
|
24040
|
-
if (bullet) {
|
|
24041
|
-
mapping[bullet[1].trim()] = bullet[2].trim();
|
|
24042
|
-
}
|
|
24043
|
-
}
|
|
24044
|
-
}
|
|
24045
|
-
if (Object.keys(mapping).length > 0) return mapping;
|
|
24046
|
-
}
|
|
24047
|
-
}
|
|
24048
|
-
return {};
|
|
24049
|
-
}
|
|
24050
|
-
function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
|
|
24051
|
-
const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType);
|
|
24052
|
-
if (Object.keys(headings).length === 0) {
|
|
24053
|
-
return "";
|
|
24054
|
-
}
|
|
24055
|
-
const lines = Object.entries(headings).map(
|
|
24056
|
-
([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
|
|
24057
|
-
);
|
|
24058
|
-
return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
|
|
24059
|
-
- The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
|
|
24060
|
-
- In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
|
|
24061
|
-
${lines.join("\n")}
|
|
24062
|
-
- For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
|
|
24063
|
-
}
|
|
24064
|
-
|
|
24065
24255
|
// src/services/lessonProductionService.ts
|
|
24066
24256
|
var __filename2 = typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" && typeof url.fileURLToPath === "function" ? url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))) : "";
|
|
24067
24257
|
var _resolvedDir = __filename2 ? path3__default.default.dirname(__filename2) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
@@ -24475,7 +24665,10 @@ async function produceSingleLesson(storage, projectId, lessonId, options = {}) {
|
|
|
24475
24665
|
try {
|
|
24476
24666
|
const exposition = await ensureExpositionForLesson(storage, projectId, lessonCode, {
|
|
24477
24667
|
gates: resolveGateSettings(options.gateSettings),
|
|
24478
|
-
force: options.force
|
|
24668
|
+
force: options.force,
|
|
24669
|
+
targetLanguage: targetLang,
|
|
24670
|
+
slcMarkdown,
|
|
24671
|
+
customInference: options.customInference
|
|
24479
24672
|
});
|
|
24480
24673
|
if (exposition) {
|
|
24481
24674
|
const expoExcerpt = buildExpositionExcerpt(exposition.rawContent || exposition.context, {
|
|
@@ -25525,6 +25718,22 @@ ${buildHeadingDirective("CODE", slcMarkdown, targetLang)}`;
|
|
|
25525
25718
|
if (artifactScope.includes("EXT") && (!await storage.readArtifact(projectId, extRelPath) || force)) {
|
|
25526
25719
|
onProgress?.("@activity", `Authoring Advanced Extension Challenge: \`EXT_${lessonCode}.md\`...`, { artifactType: "EXT" });
|
|
25527
25720
|
const extDateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
25721
|
+
const isVi = targetLang === "vi";
|
|
25722
|
+
const extHeadings = isVi ? {
|
|
25723
|
+
main: "# NHI\u1EC6M V\u1EE4 N\xC2NG CAO: [TI\xCAU \u0110\u1EC0 B\xC0I H\u1ECCC]",
|
|
25724
|
+
sub: "## D\xE0nh cho: H\u1ECDc sinh N\xE2ng cao / Ho\xE0n th\xE0nh S\u1EDBm",
|
|
25725
|
+
c1: "### Th\u1EED th\xE1ch 1: [T\xEAn th\u1EED th\xE1ch] (\u0110\u1ED9 kh\xF3: \u2B50\u2B50\u2B50) \u2014 B\u1ED1i c\u1EA3nh Nhi\u1EC7m v\u1EE5 Th\u1EF1c t\u1EBF & K\u1ECBch b\u1EA3n \u1EE8ng d\u1EE5ng",
|
|
25726
|
+
c2: "### Th\u1EED th\xE1ch 2: [T\xEAn th\u1EED th\xE1ch] (\u0110\u1ED9 kh\xF3: \u2B50\u2B50\u2B50\u2B50) \u2014 R\xE0ng bu\u1ED9c Ki\u1EBFn tr\xFAc N\xE2ng cao & Tr\u01B0\u1EDDng h\u1EE3p Bi\xEAn",
|
|
25727
|
+
c3: "### Th\u1EED th\xE1ch 3: [T\xEAn th\u1EED th\xE1ch] (\u0110\u1ED9 kh\xF3: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 C\xE1c M\u1ED1c Nhi\u1EC7m v\u1EE5 N\xE2ng cao (C\u1EA5p 1: Kh\u1EDFi \u0111\u1ED9ng \u2192 C\u1EA5p 2: N\xE2ng cao \u2192 C\u1EA5p 3: Chuy\xEAn gia)",
|
|
25728
|
+
meta: "## [REQUIRED] Suy ng\u1EABm Si\xEAu nh\u1EADn th\u1EE9c & \u0110\xE1nh \u0111\u1ED5i K\u1EF9 thu\u1EADt"
|
|
25729
|
+
} : {
|
|
25730
|
+
main: "# EXTENSION TASKS: [LESSON TITLE]",
|
|
25731
|
+
sub: "## For: Advanced Students / Extra Time",
|
|
25732
|
+
c1: "### Challenge 1: [Name] (Difficulty: \u2B50\u2B50\u2B50) \u2014 Mission Briefing & Real-World High-Stakes Scenario",
|
|
25733
|
+
c2: "### Challenge 2: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50) \u2014 Advanced Architectural Constraints & Edge Cases",
|
|
25734
|
+
c3: "### Challenge 3: [Name] (Difficulty: \u2B50\u2B50\u2B50\u2B50\u2B50) \u2014 Extension Milestones (Level 1: Ninja \u2192 Level 2: Guru \u2192 Level 3: Master)",
|
|
25735
|
+
meta: "## [REQUIRED] Metacognitive Deep Dive & Engineering Trade-offs"
|
|
25736
|
+
};
|
|
25528
25737
|
const extPrompt = `You are @activity (Lead STEM Specialist & Differentiated Extension Designer).
|
|
25529
25738
|
Based on Master Lesson Plan \`LESSON_${lessonCode}.md\`, author the advanced extension challenge: \`EXT_${lessonCode}.md\`.
|
|
25530
25739
|
|
|
@@ -25540,16 +25749,21 @@ deliverable: "P3-T8"
|
|
|
25540
25749
|
version: "v1.0"
|
|
25541
25750
|
date: "${extDateStr}"
|
|
25542
25751
|
template_contract: "artifact-template-v1"
|
|
25752
|
+
template_required_sections:
|
|
25753
|
+
- "Challenge 1: Mission Briefing & Real-World High-Stakes Scenario"
|
|
25754
|
+
- "Challenge 2: Advanced Architectural Constraints & Edge Cases"
|
|
25755
|
+
- "Challenge 3: Extension Milestones"
|
|
25756
|
+
- "Metacognitive Deep Dive & Engineering Trade-offs"
|
|
25543
25757
|
---
|
|
25544
25758
|
|
|
25545
|
-
|
|
25546
|
-
|
|
25759
|
+
${extHeadings.main}
|
|
25760
|
+
${extHeadings.sub}
|
|
25547
25761
|
|
|
25548
25762
|
2. Mandatory sections (matching EXT_template.md):
|
|
25549
|
-
-
|
|
25550
|
-
-
|
|
25551
|
-
-
|
|
25552
|
-
-
|
|
25763
|
+
- ${extHeadings.c1}
|
|
25764
|
+
- ${extHeadings.c2}
|
|
25765
|
+
- ${extHeadings.c3}
|
|
25766
|
+
- ${extHeadings.meta}
|
|
25553
25767
|
3. Output 100% clean Markdown directly \u2014 NO code fences around the document.
|
|
25554
25768
|
|
|
25555
25769
|
${languageDirective}
|
|
@@ -27689,6 +27903,7 @@ var pad22 = (n) => String(n).padStart(2, "0");
|
|
|
27689
27903
|
var esc = (s) => s.replace(/\|/g, "/").replace(/\n/g, " ").trim();
|
|
27690
27904
|
var BLOOM_OF_DEPTH = { ulo: "Understand", cio: "Apply", sio: "Create" };
|
|
27691
27905
|
function renderFrameworkFromPlan(plan, meta) {
|
|
27906
|
+
const isVi = meta.targetLanguage === "vi";
|
|
27692
27907
|
const dateStr = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
27693
27908
|
const totalSessions = plan.sessions.length;
|
|
27694
27909
|
const totalMinutes = plan.sessions.reduce(
|
|
@@ -27706,10 +27921,16 @@ function renderFrameworkFromPlan(plan, meta) {
|
|
|
27706
27921
|
const depthTops = [...new Set(s.depth_assignments.map((d) => d.depth))].map((d) => BLOOM_OF_DEPTH[d] ?? "Apply");
|
|
27707
27922
|
const bloom = depthTops[depthTops.length - 1] ?? "Apply";
|
|
27708
27923
|
const keyConcepts = s.new_keywords.length > 0 ? s.new_keywords.join(", ") : s.depth_assignments.map((d) => d.node_id).join(", ");
|
|
27709
|
-
const prereq = s.prerequisite_decisions.length > 0 ? s.prerequisite_decisions.filter((p) => p.decision === "taught_in_prior_lesson").length + " prior-taught, " + s.prerequisite_decisions.filter((p) => p.decision === "recap_in_lesson").length + " recap" : "None";
|
|
27924
|
+
const prereq = s.prerequisite_decisions.length > 0 ? s.prerequisite_decisions.filter((p) => p.decision === "taught_in_prior_lesson").length + " prior-taught, " + s.prerequisite_decisions.filter((p) => p.decision === "recap_in_lesson").length + " recap" : isVi ? "Kh\xF4ng" : "None";
|
|
27710
27925
|
const duration = s.knowledge_minutes + s.practice_minutes + s.overhead_minutes;
|
|
27711
27926
|
return "| " + pad22(i + 1) + " | " + s.id + " | " + esc(s.title) + " | " + s.unit_id + " | " + s.unit_id + "_M01 | " + esc(s.prose_objective) + " | " + esc(keyConcepts) + " | " + esc(s.exit_evidence[0] ?? "") + " | " + esc(prereq) + " | " + bloom + " | " + duration + " |";
|
|
27712
27927
|
}).join("\n");
|
|
27928
|
+
const courseOverviewHeader = isVi ? "## [REQUIRED] T\u1ED5ng quan Kh\xF3a h\u1ECDc" : "## [REQUIRED] Course Overview";
|
|
27929
|
+
const globalObjectivesHeader = isVi ? "## [REQUIRED] M\u1EE5c ti\xEAu H\u1ECDc t\u1EADp To\xE0n di\u1EC7n" : "## [REQUIRED] Global Learning Objectives";
|
|
27930
|
+
const structuralHierarchyHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc Kh\xF3a h\u1ECDc (Unit & Module)" : "## [REQUIRED] Structural Hierarchy (Unit & Module)";
|
|
27931
|
+
const scopeSequenceHeader = isVi ? "## [REQUIRED] Ph\u1EA1m vi & Tr\xECnh t\u1EF1 Chi ti\u1EBFt (Scope & Sequence)" : "## [REQUIRED] Scope & Sequence (Detailed Roadmap)";
|
|
27932
|
+
const standardFormatHeader = isVi ? "## [REQUIRED] C\u1EA5u tr\xFAc B\xE0i h\u1ECDc Chu\u1EA9n" : "## [REQUIRED] Standard Lesson Format";
|
|
27933
|
+
const scopeTableHeader = isVi ? "| # | Lesson Code | Title | Unit | Module | Learning Objective (H\u1ECDc sinh l\xE0m \u0111\u01B0\u1EE3c g\xEC...) | Key Concepts (H\u1ECDc g\xEC) | Hands-on Deliverable (S\u1EA3n ph\u1EA9m bu\u1ED5i h\u1ECDc) | Prerequisites | Bloom | Duration (mins) |" : "| # | Lesson Code | Title | Unit | Module | Learning Objective (Students will be able to...) | Key Concepts (Hoc gi) | Hands-on Deliverable (Lam duoc gi) | Prerequisites | Bloom | Duration (mins) |";
|
|
27713
27934
|
return [
|
|
27714
27935
|
"---",
|
|
27715
27936
|
'id: "CURRICULUM-FRAMEWORK"',
|
|
@@ -27731,7 +27952,7 @@ function renderFrameworkFromPlan(plan, meta) {
|
|
|
27731
27952
|
"",
|
|
27732
27953
|
"_Projection of CURRICULUM_PLAN (hash " + plan.plan_hash + "). Approving this framework approves the per-session SCOPE recorded in the plan._",
|
|
27733
27954
|
"",
|
|
27734
|
-
|
|
27955
|
+
courseOverviewHeader,
|
|
27735
27956
|
"- **Official Course Name:** " + esc(meta.courseName),
|
|
27736
27957
|
meta.shortDescription ? "- **Short Description:** " + esc(meta.shortDescription) : "",
|
|
27737
27958
|
"- **Total Units:** " + plan.units.length,
|
|
@@ -27739,20 +27960,20 @@ function renderFrameworkFromPlan(plan, meta) {
|
|
|
27739
27960
|
"- **Total Duration:** ~" + totalHours + " hours (" + totalSessions + " sessions x " + plan.constraints.session_duration_minutes + " min).",
|
|
27740
27961
|
"- **Entry Level:** " + plan.constraints.entry_level + " (age band " + plan.constraints.age_band[0] + "-" + plan.constraints.age_band[1] + ").",
|
|
27741
27962
|
"",
|
|
27742
|
-
|
|
27963
|
+
globalObjectivesHeader,
|
|
27743
27964
|
...plan.course.objectives.map((o, i) => i + 1 + ". **[" + o.bloom + "]:** " + esc(o.statement)),
|
|
27744
27965
|
"",
|
|
27745
|
-
|
|
27966
|
+
structuralHierarchyHeader,
|
|
27746
27967
|
"| Unit | Module | Lessons | Module Objective | Lesson Codes |",
|
|
27747
27968
|
"|---|---|:---:|---|---|",
|
|
27748
27969
|
hierarchyRows || "| (none) | | | | |",
|
|
27749
27970
|
"",
|
|
27750
|
-
|
|
27751
|
-
|
|
27971
|
+
scopeSequenceHeader,
|
|
27972
|
+
scopeTableHeader,
|
|
27752
27973
|
"|:---:|:---:|---|---|---|---|---|---|---|:---:|:---:|",
|
|
27753
27974
|
scopeRows || "| 01 | U01_M01_L01 | (empty plan) | U01 | U01_M01 | - | - | - | None | Understand | " + plan.constraints.session_duration_minutes + " |",
|
|
27754
27975
|
"",
|
|
27755
|
-
|
|
27976
|
+
standardFormatHeader,
|
|
27756
27977
|
"- **Session duration:** " + plan.constraints.session_duration_minutes + " minutes (overhead " + String(plan.sessions[0]?.overhead_minutes ?? 0) + " min).",
|
|
27757
27978
|
"- **Knowledge/practice split per session:** see plan JSON (knowledge_minutes / practice_minutes).",
|
|
27758
27979
|
"- **Pedagogical flow:** 5E (Engage / Explore / Explain / Elaborate / Evaluate) unless policy overrides.",
|
|
@@ -30205,6 +30426,7 @@ exports.DEFAULT_KX_PRIORITIES = DEFAULT_KX_PRIORITIES;
|
|
|
30205
30426
|
exports.DEFAULT_LESSON_PRIORITIES = DEFAULT_LESSON_PRIORITIES;
|
|
30206
30427
|
exports.DEFAULT_STREAM_IDLE_MS = DEFAULT_STREAM_IDLE_MS;
|
|
30207
30428
|
exports.DEFAULT_STREAM_TOTAL_MS = DEFAULT_STREAM_TOTAL_MS;
|
|
30429
|
+
exports.DEFAULT_VIETNAMESE_SECTION_HEADINGS = DEFAULT_VIETNAMESE_SECTION_HEADINGS;
|
|
30208
30430
|
exports.DependencyEdgeSchema = DependencyEdgeSchema;
|
|
30209
30431
|
exports.DepthAssignmentSchema = DepthAssignmentSchema;
|
|
30210
30432
|
exports.DepthLevelSchema = DepthLevelSchema;
|