@thanh01.pmt/curriculum-kit 1.0.22 → 1.2.0
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/dist/ai/index.cjs +57 -2
- package/dist/ai/index.cjs.map +1 -1
- package/dist/ai/index.d.cts +11 -1
- package/dist/ai/index.d.ts +11 -1
- package/dist/ai/index.mjs +56 -3
- package/dist/ai/index.mjs.map +1 -1
- package/dist/{gateSettings-DKXRztJy.d.cts → gateSettings-Buk_MSML.d.cts} +3 -0
- package/dist/{gateSettings-DKXRztJy.d.ts → gateSettings-Buk_MSML.d.ts} +3 -0
- package/dist/index.cjs +728 -198
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -5
- package/dist/index.d.ts +94 -5
- package/dist/index.mjs +713 -199
- package/dist/index.mjs.map +1 -1
- package/dist/media/index.cjs +3 -0
- package/dist/media/index.cjs.map +1 -1
- package/dist/media/index.mjs +3 -0
- package/dist/media/index.mjs.map +1 -1
- package/dist/pipeline/index.cjs +13 -2
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.mjs +13 -2
- package/dist/pipeline/index.mjs.map +1 -1
- package/dist/schemas/index.cjs +13 -2
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +34 -16
- package/dist/schemas/index.d.ts +34 -16
- package/dist/schemas/index.mjs +13 -2
- package/dist/schemas/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +461 -16
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.d.cts +2 -2
- package/dist/workflow/index.d.ts +2 -2
- package/dist/workflow/index.mjs +461 -16
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MilestoneInput, LessonPlan, CurriculumQualityReport, ActivityLab, CodeLab, SelfLab, DiagnosticQuiz, SlideDeck, Handout, Worksheet, TeacherGuide, Extension, ProjectRoadmap } from '../schemas/index.cjs';
|
|
2
2
|
import { M as ModelResolutionOptions } from '../provider-factory-DxzOVEmh.cjs';
|
|
3
3
|
import { G as GitPublishOptions, a as GitPublishResult, S as SupabasePublishOptions, b as SupabasePublishResult } from '../supabasePublisher-C627qXFT.cjs';
|
|
4
|
-
import { S as SingleArtifactRequest, a as SingleArtifactResult } from '../gateSettings-
|
|
5
|
-
export { b as CurriculumArtifactType, C as CurriculumGateMode, D as DEFAULT_GATE_SETTINGS, G as GateSettings, R as ResolvedGateSettings, g as gateModeFor, p as parseGateSettings, r as resolveGateSettings } from '../gateSettings-
|
|
4
|
+
import { S as SingleArtifactRequest, a as SingleArtifactResult } from '../gateSettings-Buk_MSML.cjs';
|
|
5
|
+
export { b as CurriculumArtifactType, C as CurriculumGateMode, D as DEFAULT_GATE_SETTINGS, G as GateSettings, R as ResolvedGateSettings, g as gateModeFor, p as parseGateSettings, r as resolveGateSettings } from '../gateSettings-Buk_MSML.cjs';
|
|
6
6
|
import * as workflow from 'workflow';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { F as FrameworkPack, C as CoverageGateReport } from '../standardsCoverageGate-CsUNzv6C.cjs';
|
package/dist/workflow/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MilestoneInput, LessonPlan, CurriculumQualityReport, ActivityLab, CodeLab, SelfLab, DiagnosticQuiz, SlideDeck, Handout, Worksheet, TeacherGuide, Extension, ProjectRoadmap } from '../schemas/index.js';
|
|
2
2
|
import { M as ModelResolutionOptions } from '../provider-factory-DxzOVEmh.js';
|
|
3
3
|
import { G as GitPublishOptions, a as GitPublishResult, S as SupabasePublishOptions, b as SupabasePublishResult } from '../supabasePublisher-C627qXFT.js';
|
|
4
|
-
import { S as SingleArtifactRequest, a as SingleArtifactResult } from '../gateSettings-
|
|
5
|
-
export { b as CurriculumArtifactType, C as CurriculumGateMode, D as DEFAULT_GATE_SETTINGS, G as GateSettings, R as ResolvedGateSettings, g as gateModeFor, p as parseGateSettings, r as resolveGateSettings } from '../gateSettings-
|
|
4
|
+
import { S as SingleArtifactRequest, a as SingleArtifactResult } from '../gateSettings-Buk_MSML.js';
|
|
5
|
+
export { b as CurriculumArtifactType, C as CurriculumGateMode, D as DEFAULT_GATE_SETTINGS, G as GateSettings, R as ResolvedGateSettings, g as gateModeFor, p as parseGateSettings, r as resolveGateSettings } from '../gateSettings-Buk_MSML.js';
|
|
6
6
|
import * as workflow from 'workflow';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { F as FrameworkPack, C as CoverageGateReport } from '../standardsCoverageGate-CsUNzv6C.js';
|
package/dist/workflow/index.mjs
CHANGED
|
@@ -68,6 +68,8 @@ function getSuggestedActionForCode(code) {
|
|
|
68
68
|
return "N\u1ED9i dung sinh ra thi\u1EBFu c\xE1c ph\u1EA7n b\u1EAFt bu\u1ED9c [REQUIRED]. H\xE3y k\xEDch ho\u1EA1t ki\u1EC3m th\u1EED ho\u1EB7c b\u1EA5m Rerun.";
|
|
69
69
|
case "ERR_STORAGE_IO_FAILED":
|
|
70
70
|
return "L\u1ED7i \u0111\u1ECDc/ghi t\u1EC7p tin tr\xEAn h\u1EC7 th\u1ED1ng l\u01B0u tr\u1EEF. Ki\u1EC3m tra quy\u1EC1n ghi th\u01B0 m\u1EE5c output/projects.";
|
|
71
|
+
case "ERR_CONTEXT_MISSING":
|
|
72
|
+
return "Thi\u1EBFu ngu\u1ED3n tri th\u1EE9c b\u1EAFt bu\u1ED9c (KNOWLEDGE_EXPOSITION ho\u1EB7c CANONICAL LESSON) theo Dependency Routing Matrix. Vui l\xF2ng t\u1EA1o ho\u1EB7c \u0111\u1ED3ng b\u1ED9 t\xE0i li\u1EC7u ngu\u1ED3n tr\u01B0\u1EDBc.";
|
|
71
73
|
default:
|
|
72
74
|
return "B\u1EA5m Rerun \u0111\u1EC3 th\u1EED l\u1EA1i ho\u1EB7c ki\u1EC3m tra nh\u1EADt k\xFD h\u1EC7 th\u1ED1ng.";
|
|
73
75
|
}
|
|
@@ -85,11 +87,23 @@ function isCodeRetryable(code) {
|
|
|
85
87
|
case "ERR_TASK_GATE_BLOCKED":
|
|
86
88
|
case "ERR_METERED_LIMIT_EXCEEDED":
|
|
87
89
|
case "ERR_SCHEMA_CONTRACT_VIOLATION":
|
|
90
|
+
case "ERR_CONTEXT_MISSING":
|
|
88
91
|
return false;
|
|
89
92
|
default:
|
|
90
93
|
return true;
|
|
91
94
|
}
|
|
92
95
|
}
|
|
96
|
+
function createContextMissingError(params) {
|
|
97
|
+
return new CurriculumError({
|
|
98
|
+
errorCode: "ERR_CONTEXT_MISSING",
|
|
99
|
+
artifactType: params.artifactType,
|
|
100
|
+
lessonId: params.lessonId,
|
|
101
|
+
agent: params.agent,
|
|
102
|
+
message: `Required context source "${params.missingSource}" is missing for artifact "${params.artifactType}"`,
|
|
103
|
+
suggestedAction: `Generate or provide the authoritative "${params.missingSource}" source before producing "${params.artifactType}".`,
|
|
104
|
+
retryable: false
|
|
105
|
+
});
|
|
106
|
+
}
|
|
93
107
|
var CurriculumError;
|
|
94
108
|
var init_errors = __esm({
|
|
95
109
|
"src/errors/index.ts"() {
|
|
@@ -2222,9 +2236,20 @@ var SessionPlanSchema = z.object({
|
|
|
2222
2236
|
differentiation: z.object({ bronze: z.string(), silver: z.string(), gold: z.string() })
|
|
2223
2237
|
});
|
|
2224
2238
|
var TranslationPolicySchema = z.object({
|
|
2225
|
-
policy: z.enum(["after_artifact", "end_of_unit", "at_publish"]).default("
|
|
2239
|
+
policy: z.enum(["after_artifact", "end_of_unit", "at_publish", "native"]).default("native"),
|
|
2226
2240
|
target_language: z.string().nullable().default(null)
|
|
2227
|
-
})
|
|
2241
|
+
}).refine(
|
|
2242
|
+
(data) => {
|
|
2243
|
+
if (data.policy === "native") {
|
|
2244
|
+
return typeof data.target_language === "string" && data.target_language.trim().length > 0;
|
|
2245
|
+
}
|
|
2246
|
+
return true;
|
|
2247
|
+
},
|
|
2248
|
+
{
|
|
2249
|
+
message: "When translation policy is 'native', target_language must be a non-empty string",
|
|
2250
|
+
path: ["target_language"]
|
|
2251
|
+
}
|
|
2252
|
+
);
|
|
2228
2253
|
var CoverageReportSchema = z.object({
|
|
2229
2254
|
assigned_node_ids: z.array(z.string()),
|
|
2230
2255
|
unassigned_node_ids: z.array(z.string()),
|
|
@@ -6195,6 +6220,307 @@ var LLMJudgeEngine = class {
|
|
|
6195
6220
|
// src/services/lessonProductionService.ts
|
|
6196
6221
|
init_errors();
|
|
6197
6222
|
|
|
6223
|
+
// src/services/contextBuilder.ts
|
|
6224
|
+
var DEFAULT_LESSON_PRIORITIES = [
|
|
6225
|
+
"A. Lesson Design Plan",
|
|
6226
|
+
"B. Lesson Flow",
|
|
6227
|
+
"Learning Objectives & Evidence",
|
|
6228
|
+
"Activity Sequence"
|
|
6229
|
+
];
|
|
6230
|
+
var DEFAULT_KX_PRIORITIES = [
|
|
6231
|
+
"Key Terms",
|
|
6232
|
+
"Concept Narratives",
|
|
6233
|
+
"Worked Micro-Examples"
|
|
6234
|
+
];
|
|
6235
|
+
function buildSectionAwareExcerpt(markdown, opts = {}) {
|
|
6236
|
+
const budget = opts.budget ?? 12e3;
|
|
6237
|
+
const minContentChars = opts.minContentChars ?? 400;
|
|
6238
|
+
const issues = [];
|
|
6239
|
+
const source = (markdown || "").trim();
|
|
6240
|
+
if (!source) {
|
|
6241
|
+
return {
|
|
6242
|
+
excerpt: "",
|
|
6243
|
+
verified: false,
|
|
6244
|
+
sectionAware: false,
|
|
6245
|
+
includedSections: [],
|
|
6246
|
+
issues: ["empty-source"],
|
|
6247
|
+
tokenEstimate: 0
|
|
6248
|
+
};
|
|
6249
|
+
}
|
|
6250
|
+
const slcMap = normalizeSlcContract(opts.sectionLanguageContract);
|
|
6251
|
+
const sections = parseMarkdownSections(source, slcMap, opts.artifactType);
|
|
6252
|
+
if (sections.length === 0) {
|
|
6253
|
+
issues.push("parse:no-sections");
|
|
6254
|
+
const excerpt2 = truncateByBudget(source, budget);
|
|
6255
|
+
const verified2 = excerpt2.length >= minContentChars;
|
|
6256
|
+
if (excerpt2.length < minContentChars) issues.push("content:insufficient");
|
|
6257
|
+
return {
|
|
6258
|
+
excerpt: excerpt2,
|
|
6259
|
+
verified: verified2,
|
|
6260
|
+
sectionAware: false,
|
|
6261
|
+
includedSections: [],
|
|
6262
|
+
issues,
|
|
6263
|
+
tokenEstimate: Math.round(excerpt2.length / 4)
|
|
6264
|
+
};
|
|
6265
|
+
}
|
|
6266
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
6267
|
+
const unmatched = [];
|
|
6268
|
+
for (const s of sections) {
|
|
6269
|
+
if (s.canonicalKey && !byCanonical.has(s.canonicalKey)) {
|
|
6270
|
+
byCanonical.set(s.canonicalKey, s);
|
|
6271
|
+
} else {
|
|
6272
|
+
unmatched.push(s);
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
const priorities = opts.priorities ?? DEFAULT_LESSON_PRIORITIES;
|
|
6276
|
+
const includedSections = [];
|
|
6277
|
+
const blocks = [];
|
|
6278
|
+
let used = 0;
|
|
6279
|
+
const tryAdd = (heading, body, key) => {
|
|
6280
|
+
const trimmedBody = body.trim();
|
|
6281
|
+
if (!trimmedBody) return false;
|
|
6282
|
+
const block = `## ${heading}
|
|
6283
|
+
|
|
6284
|
+
${trimmedBody}`;
|
|
6285
|
+
if (used + block.length > budget && blocks.length > 0) return false;
|
|
6286
|
+
blocks.push(block);
|
|
6287
|
+
used += block.length;
|
|
6288
|
+
includedSections.push(key);
|
|
6289
|
+
return true;
|
|
6290
|
+
};
|
|
6291
|
+
let sectionAware = false;
|
|
6292
|
+
for (const key of priorities) {
|
|
6293
|
+
const sec = byCanonical.get(key);
|
|
6294
|
+
if (!sec) continue;
|
|
6295
|
+
sectionAware = true;
|
|
6296
|
+
if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
|
|
6297
|
+
}
|
|
6298
|
+
for (const [key, sec] of byCanonical) {
|
|
6299
|
+
if (includedSections.includes(key)) continue;
|
|
6300
|
+
if (used >= budget) break;
|
|
6301
|
+
if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
|
|
6302
|
+
}
|
|
6303
|
+
for (const sec of unmatched) {
|
|
6304
|
+
if (used >= budget) break;
|
|
6305
|
+
if (!tryAdd(sec.cleanTitle, sec.body, sec.cleanTitle)) break;
|
|
6306
|
+
}
|
|
6307
|
+
if (priorities.length > 0 && !priorities.some((k) => includedSections.includes(k))) {
|
|
6308
|
+
issues.push("parse:no-priority-resolution");
|
|
6309
|
+
}
|
|
6310
|
+
let excerpt = blocks.join("\n\n");
|
|
6311
|
+
if (excerpt.length > budget) {
|
|
6312
|
+
excerpt = truncateByBudget(excerpt, budget);
|
|
6313
|
+
}
|
|
6314
|
+
const effectiveMinChars = Math.min(minContentChars, source.length);
|
|
6315
|
+
if (excerpt.length < effectiveMinChars) {
|
|
6316
|
+
issues.push("content:insufficient");
|
|
6317
|
+
}
|
|
6318
|
+
const verified = !issues.includes("parse:no-priority-resolution") && excerpt.length >= effectiveMinChars;
|
|
6319
|
+
return {
|
|
6320
|
+
excerpt,
|
|
6321
|
+
verified,
|
|
6322
|
+
sectionAware: sectionAware || priorities.length === 0 && sections.length > 0,
|
|
6323
|
+
includedSections,
|
|
6324
|
+
issues,
|
|
6325
|
+
tokenEstimate: Math.round(excerpt.length / 4)
|
|
6326
|
+
};
|
|
6327
|
+
}
|
|
6328
|
+
function buildExpositionExcerpt(markdown, opts = {}) {
|
|
6329
|
+
return buildSectionAwareExcerpt(markdown, {
|
|
6330
|
+
...opts,
|
|
6331
|
+
priorities: DEFAULT_KX_PRIORITIES
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
6334
|
+
function buildLessonExcerpt(markdown, opts = {}) {
|
|
6335
|
+
return buildSectionAwareExcerpt(markdown, {
|
|
6336
|
+
...opts,
|
|
6337
|
+
priorities: DEFAULT_LESSON_PRIORITIES
|
|
6338
|
+
});
|
|
6339
|
+
}
|
|
6340
|
+
function normalizeSlcContract(slc) {
|
|
6341
|
+
if (!slc) return void 0;
|
|
6342
|
+
if (typeof slc !== "string") return slc;
|
|
6343
|
+
const out = {};
|
|
6344
|
+
const blocks = slc.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
|
|
6345
|
+
for (const block of blocks) {
|
|
6346
|
+
const header = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
|
|
6347
|
+
if (!header?.[1]) continue;
|
|
6348
|
+
const mapping = {};
|
|
6349
|
+
for (const line of block.split("\n")) {
|
|
6350
|
+
const t = line.trim();
|
|
6351
|
+
if (!t.startsWith("|") || t.includes("---")) continue;
|
|
6352
|
+
const cells = t.split("|").map((c) => c.trim()).filter((_, i, a) => i > 0 && i < a.length - 1);
|
|
6353
|
+
const [c0, c1] = cells;
|
|
6354
|
+
if (c0 && c1 && !c0.toLowerCase().includes("canonical") && !c1.toLowerCase().includes("localized")) {
|
|
6355
|
+
mapping[c0] = c1;
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
if (Object.keys(mapping).length > 0) out[header[1]] = mapping;
|
|
6359
|
+
}
|
|
6360
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
6361
|
+
}
|
|
6362
|
+
function parseMarkdownSections(markdown, slcMap, artifactType) {
|
|
6363
|
+
const sections = [];
|
|
6364
|
+
const lines = markdown.split("\n");
|
|
6365
|
+
let currentHeading = "";
|
|
6366
|
+
let currentBody = [];
|
|
6367
|
+
const reverseMap = /* @__PURE__ */ new Map();
|
|
6368
|
+
if (slcMap) {
|
|
6369
|
+
const maps = artifactType && slcMap[artifactType] ? [slcMap[artifactType]] : Object.values(slcMap);
|
|
6370
|
+
for (const m of maps) {
|
|
6371
|
+
for (const [canonical, localized] of Object.entries(m)) {
|
|
6372
|
+
reverseMap.set(normalizeHeading(localized), canonical);
|
|
6373
|
+
reverseMap.set(normalizeHeading(canonical), canonical);
|
|
6374
|
+
}
|
|
6375
|
+
}
|
|
6376
|
+
}
|
|
6377
|
+
const flush = () => {
|
|
6378
|
+
if (!currentHeading && currentBody.length === 0) return;
|
|
6379
|
+
const cleanTitle = currentHeading.replace(/^#{1,4}\s+/, "").trim();
|
|
6380
|
+
const norm = normalizeHeading(cleanTitle);
|
|
6381
|
+
const canonicalKey = reverseMap.get(norm) || findCanonicalFuzzy(norm);
|
|
6382
|
+
sections.push({
|
|
6383
|
+
rawHeading: currentHeading,
|
|
6384
|
+
cleanTitle,
|
|
6385
|
+
canonicalKey,
|
|
6386
|
+
body: currentBody.join("\n")
|
|
6387
|
+
});
|
|
6388
|
+
currentBody = [];
|
|
6389
|
+
};
|
|
6390
|
+
const hasH2 = lines.some((l) => /^##\s+/.test(l));
|
|
6391
|
+
const headingRegex = hasH2 ? /^##\s+(.*)$/ : /^#{2,3}\s+(.*)$/;
|
|
6392
|
+
for (const line of lines) {
|
|
6393
|
+
const headingMatch = line.match(headingRegex);
|
|
6394
|
+
if (headingMatch) {
|
|
6395
|
+
flush();
|
|
6396
|
+
currentHeading = line.trim();
|
|
6397
|
+
} else {
|
|
6398
|
+
currentBody.push(line);
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
flush();
|
|
6402
|
+
return sections;
|
|
6403
|
+
}
|
|
6404
|
+
function normalizeHeading(str) {
|
|
6405
|
+
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim();
|
|
6406
|
+
}
|
|
6407
|
+
function findCanonicalFuzzy(norm) {
|
|
6408
|
+
if (norm.includes("lesson design plan") || norm.includes("ke hoach thiet ke")) {
|
|
6409
|
+
return "A. Lesson Design Plan";
|
|
6410
|
+
}
|
|
6411
|
+
if (norm.includes("lesson flow") || norm.includes("tien trinh giang day")) {
|
|
6412
|
+
return "B. Lesson Flow";
|
|
6413
|
+
}
|
|
6414
|
+
if (norm.includes("learning objective") || norm.includes("muc tieu bai hoc")) {
|
|
6415
|
+
return "Learning Objectives & Evidence";
|
|
6416
|
+
}
|
|
6417
|
+
if (norm.includes("activity sequence") || norm.includes("chuoi hoat dong")) {
|
|
6418
|
+
return "Activity Sequence";
|
|
6419
|
+
}
|
|
6420
|
+
if (norm.includes("key term") || norm.includes("thuat ngu")) {
|
|
6421
|
+
return "Key Terms";
|
|
6422
|
+
}
|
|
6423
|
+
if (norm.includes("concept narrative") || norm.includes("dien giai khai niem")) {
|
|
6424
|
+
return "Concept Narratives";
|
|
6425
|
+
}
|
|
6426
|
+
if (norm.includes("worked example") || norm.includes("vi du")) {
|
|
6427
|
+
return "Worked Micro-Examples";
|
|
6428
|
+
}
|
|
6429
|
+
return void 0;
|
|
6430
|
+
}
|
|
6431
|
+
function truncateByBudget(source, budget) {
|
|
6432
|
+
if (source.length <= budget) return source;
|
|
6433
|
+
let cut = source.slice(0, budget);
|
|
6434
|
+
const fenceCount = (cut.match(/^```/gm) || []).length;
|
|
6435
|
+
if (fenceCount % 2 === 1) cut += "\n```";
|
|
6436
|
+
return cut;
|
|
6437
|
+
}
|
|
6438
|
+
init_errors();
|
|
6439
|
+
|
|
6440
|
+
// src/services/languageDirective.ts
|
|
6441
|
+
function resolveTargetLanguageCode(language) {
|
|
6442
|
+
const raw = String(language || "vi").trim().toLowerCase();
|
|
6443
|
+
if (raw.startsWith("vi")) return "vi";
|
|
6444
|
+
if (raw.startsWith("en")) return "en";
|
|
6445
|
+
return raw || "vi";
|
|
6446
|
+
}
|
|
6447
|
+
function targetLanguageDisplayName(code) {
|
|
6448
|
+
const map = {
|
|
6449
|
+
vi: "Vietnamese (Ti\u1EBFng Vi\u1EC7t)",
|
|
6450
|
+
en: "English (US)"
|
|
6451
|
+
};
|
|
6452
|
+
return map[code] || code;
|
|
6453
|
+
}
|
|
6454
|
+
function buildLanguageDirective(targetLanguage) {
|
|
6455
|
+
const code = resolveTargetLanguageCode(targetLanguage);
|
|
6456
|
+
const name = targetLanguageDisplayName(code);
|
|
6457
|
+
const perLanguageExamples = {
|
|
6458
|
+
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.`,
|
|
6459
|
+
en: `- Example compliant lesson title: "Lesson 1: Exploring Basic Circuits" \u2014 natural, idiomatic English throughout.`
|
|
6460
|
+
};
|
|
6461
|
+
const exampleLine = perLanguageExamples[code] || "";
|
|
6462
|
+
return `MANDATORY LANGUAGE DIRECTIVE (TARGET OUTPUT LANGUAGE: ${name}):
|
|
6463
|
+
- The TARGET OUTPUT LANGUAGE for this course is ${name}. You MUST author the ENTIRE document in ${name}.
|
|
6464
|
+
- All section titles, table headers, table cells, lesson names, learning objectives, pedagogical narratives, and cognitive analyses MUST be written in natural, fluent, academic ${name}.
|
|
6465
|
+
- 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}.
|
|
6466
|
+
- DO NOT mix in paragraphs, tables, or titles written in any other language. Strict adherence is required.${exampleLine ? `
|
|
6467
|
+
${exampleLine}` : ""}`;
|
|
6468
|
+
}
|
|
6469
|
+
function extractSectionHeadingsFromSLC(slcInput, artifactType) {
|
|
6470
|
+
if (!slcInput) return {};
|
|
6471
|
+
const upperType = artifactType.trim().toUpperCase();
|
|
6472
|
+
if (typeof slcInput === "object") {
|
|
6473
|
+
if (slcInput[upperType]) return slcInput[upperType];
|
|
6474
|
+
for (const [key, mapping] of Object.entries(slcInput)) {
|
|
6475
|
+
if (key.toUpperCase() === upperType || upperType.startsWith("LESSON") && key.toUpperCase().startsWith("LESSON")) {
|
|
6476
|
+
return mapping;
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
return {};
|
|
6480
|
+
}
|
|
6481
|
+
const slcMarkdown = slcInput;
|
|
6482
|
+
const blocks = slcMarkdown.split(/\n(?=#{2,3}\s+[A-Z0-9_]+)/g);
|
|
6483
|
+
for (const block of blocks) {
|
|
6484
|
+
const headerMatch = block.match(/^#{2,3}\s+([A-Z0-9_]+)/);
|
|
6485
|
+
if (!headerMatch) continue;
|
|
6486
|
+
const blockType = headerMatch[1].trim().toUpperCase();
|
|
6487
|
+
if (blockType === upperType || upperType.startsWith("LESSON") && blockType.startsWith("LESSON")) {
|
|
6488
|
+
const mapping = {};
|
|
6489
|
+
const lines = block.split("\n");
|
|
6490
|
+
for (const line of lines) {
|
|
6491
|
+
const trimmed = line.trim();
|
|
6492
|
+
if (trimmed.startsWith("|") && !trimmed.includes("---")) {
|
|
6493
|
+
const cells = trimmed.split("|").map((c) => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
6494
|
+
if (cells.length >= 2 && !cells[0].toLowerCase().includes("canonical") && !cells[1].toLowerCase().includes("localized")) {
|
|
6495
|
+
mapping[cells[0]] = cells[1];
|
|
6496
|
+
}
|
|
6497
|
+
} else {
|
|
6498
|
+
const bullet = line.match(/^[-*]\s+([^:]+):\s+(.+)$/);
|
|
6499
|
+
if (bullet) {
|
|
6500
|
+
mapping[bullet[1].trim()] = bullet[2].trim();
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
if (Object.keys(mapping).length > 0) return mapping;
|
|
6505
|
+
}
|
|
6506
|
+
}
|
|
6507
|
+
return {};
|
|
6508
|
+
}
|
|
6509
|
+
function buildHeadingDirective(artifactType, slcMarkdown, targetLanguage = "vi") {
|
|
6510
|
+
const headings = extractSectionHeadingsFromSLC(slcMarkdown, artifactType);
|
|
6511
|
+
if (Object.keys(headings).length === 0) {
|
|
6512
|
+
return "";
|
|
6513
|
+
}
|
|
6514
|
+
const lines = Object.entries(headings).map(
|
|
6515
|
+
([canonical, localized]) => ` - "${canonical}" -> Use Display Heading: "${localized}"`
|
|
6516
|
+
);
|
|
6517
|
+
return `MANDATORY SECTION HEADINGS FROM SECTION_LANGUAGE_CONTRACT:
|
|
6518
|
+
- The YAML frontmatter \`template_required_sections\` MUST preserve the exact English canonical keys.
|
|
6519
|
+
- In the markdown body, section headers MUST use the EXACT Localized Display Labels below (do NOT translate or paraphrase):
|
|
6520
|
+
${lines.join("\n")}
|
|
6521
|
+
- For example: if canonical section is "Computational Thinking Focus", your heading must be "## [REQUIRED] ${headings["Computational Thinking Focus"] || "Computational Thinking Focus"}".`;
|
|
6522
|
+
}
|
|
6523
|
+
|
|
6198
6524
|
// src/services/lessonProductionService.ts
|
|
6199
6525
|
var __filename$1 = typeof import.meta?.url === "string" ? fileURLToPath(import.meta.url) : "";
|
|
6200
6526
|
var _resolvedDir = __filename$1 ? path4.dirname(__filename$1) : typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
@@ -6224,6 +6550,87 @@ function loadCurriculumTemplate(templateName) {
|
|
|
6224
6550
|
|
|
6225
6551
|
// src/services/singleArtifactService.ts
|
|
6226
6552
|
init_errors();
|
|
6553
|
+
|
|
6554
|
+
// src/services/contextRouting.ts
|
|
6555
|
+
init_errors();
|
|
6556
|
+
var ARTIFACT_DEPENDENCY_ROUTING = {
|
|
6557
|
+
LESSON: {
|
|
6558
|
+
required: ["KNOWLEDGE_EXPOSITION", "SESSION_SLICE", "FRAMEWORK_EXCERPT"],
|
|
6559
|
+
optional: ["STYLE_GUIDE", "REFERENCE_PACK"],
|
|
6560
|
+
rationale: "D\u1EA1y theo ki\u1EBFn th\u1EE9c chu\u1EA9n c\u1EE7a plan, ph\xE2n b\u1ED5 m\u1EE5c ti\xEAu v\xE0 khung ch\u01B0\u01A1ng tr\xECnh"
|
|
6561
|
+
},
|
|
6562
|
+
HANDOUT: {
|
|
6563
|
+
required: ["KNOWLEDGE_EXPOSITION"],
|
|
6564
|
+
optional: ["SESSION_SLICE"],
|
|
6565
|
+
rationale: "T\xE0i li\u1EC7u t\u1EF1 h\u1ECDc \u2014 b\xE1m kh\xE1i ni\u1EC7m chu\u1EA9n, kh\xF4ng c\u1EA7n activity timeline"
|
|
6566
|
+
},
|
|
6567
|
+
EXT: {
|
|
6568
|
+
required: ["KNOWLEDGE_EXPOSITION"],
|
|
6569
|
+
optional: ["SESSION_SLICE"],
|
|
6570
|
+
rationale: "Th\u1EED th\xE1ch m\u1EDF r\u1ED9ng chuy\xEAn s\xE2u b\xE1m kh\xE1i ni\u1EC7m"
|
|
6571
|
+
},
|
|
6572
|
+
CODE: {
|
|
6573
|
+
required: ["KNOWLEDGE_EXPOSITION"],
|
|
6574
|
+
optional: ["LESSON_OBJECTIVES"],
|
|
6575
|
+
rationale: "Code lab th\u1EF1c h\xE0nh b\xE1m kh\xE1i ni\u1EC7m k\u1EF9 thu\u1EADt v\xE0 v\xED d\u1EE5 worked examples"
|
|
6576
|
+
},
|
|
6577
|
+
ACT: {
|
|
6578
|
+
required: ["CANONICAL_LESSON"],
|
|
6579
|
+
optional: ["KNOWLEDGE_EXPOSITION", "SESSION_SLICE"],
|
|
6580
|
+
rationale: "STEM Activity Lab b\u1EAFt bu\u1ED9c ph\u1EA3i kh\u1EDBp 100% Activity Sequence v\xE0 Lesson Flow c\u1EE7a LESSON"
|
|
6581
|
+
},
|
|
6582
|
+
WKS: {
|
|
6583
|
+
required: ["CANONICAL_LESSON"],
|
|
6584
|
+
optional: ["KNOWLEDGE_EXPOSITION"],
|
|
6585
|
+
rationale: "Phi\u1EBFu h\u1ECDc t\u1EADp (Worksheet) luy\u1EC7n t\u1EADp theo c\xE1c ch\u1EB7ng ho\u1EA1t \u0111\u1ED9ng c\u1EE7a gi\xE1o \xE1n"
|
|
6586
|
+
},
|
|
6587
|
+
GUIDE: {
|
|
6588
|
+
required: ["CANONICAL_LESSON"],
|
|
6589
|
+
optional: ["SESSION_SLICE"],
|
|
6590
|
+
rationale: "K\u1ECBch b\u1EA3n s\u01B0 ph\u1EA1m gi\xE1o vi\xEAn (Teacher Guide) b\xE1m s\xE1t timeline t\u1EEBng ph\xFAt c\u1EE7a LESSON"
|
|
6591
|
+
},
|
|
6592
|
+
SLIDE: {
|
|
6593
|
+
required: ["CANONICAL_LESSON"],
|
|
6594
|
+
optional: ["KNOWLEDGE_EXPOSITION"],
|
|
6595
|
+
rationale: "Slide b\xE0i gi\u1EA3ng tr\xECnh chi\u1EBFu theo c\u1EA5u tr\xFAc ph\xE2n ph\u1ED1i n\u1ED9i dung c\u1EE7a gi\xE1o \xE1n"
|
|
6596
|
+
},
|
|
6597
|
+
QUIZ: {
|
|
6598
|
+
required: ["KNOWLEDGE_EXPOSITION", "CANONICAL_LESSON"],
|
|
6599
|
+
optional: ["SESSION_SLICE"],
|
|
6600
|
+
rationale: "Ng\xE2n h\xE0ng c\xE2u h\u1ECFi \u0111\xE1nh gi\xE1 c\u1EA3 ki\u1EBFn th\u1EE9c chu\u1EA9n m\u1EF1c (KX) l\u1EABn m\u1EE5c ti\xEAu b\xE0i d\u1EA1y (LESSON)"
|
|
6601
|
+
}
|
|
6602
|
+
};
|
|
6603
|
+
function validateArtifactDependencies(artifactType, availableSources, opts = {}) {
|
|
6604
|
+
const normType = (artifactType || "").toUpperCase().trim();
|
|
6605
|
+
const rule = ARTIFACT_DEPENDENCY_ROUTING[normType];
|
|
6606
|
+
const failClosed = opts.failClosed ?? true;
|
|
6607
|
+
if (!rule) {
|
|
6608
|
+
return { valid: true, missing: [] };
|
|
6609
|
+
}
|
|
6610
|
+
const missing = [];
|
|
6611
|
+
for (const req of rule.required) {
|
|
6612
|
+
const val = availableSources[req];
|
|
6613
|
+
const isPresent = val !== void 0 && val !== null && (typeof val === "string" ? val.trim().length > 0 : true);
|
|
6614
|
+
if (!isPresent) {
|
|
6615
|
+
missing.push(req);
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
if (missing.length > 0) {
|
|
6619
|
+
const err = createContextMissingError({
|
|
6620
|
+
artifactType: normType,
|
|
6621
|
+
missingSource: missing[0],
|
|
6622
|
+
lessonId: opts.lessonId,
|
|
6623
|
+
agent: opts.agent
|
|
6624
|
+
});
|
|
6625
|
+
if (failClosed) {
|
|
6626
|
+
throw err;
|
|
6627
|
+
}
|
|
6628
|
+
return { valid: false, missing, error: err };
|
|
6629
|
+
}
|
|
6630
|
+
return { valid: true, missing: [] };
|
|
6631
|
+
}
|
|
6632
|
+
|
|
6633
|
+
// src/services/singleArtifactService.ts
|
|
6227
6634
|
function getArtifactMetadata(type, lessonId, pedagogy) {
|
|
6228
6635
|
const id = lessonId || "ON_DEMAND";
|
|
6229
6636
|
switch (type) {
|
|
@@ -6303,9 +6710,24 @@ async function generateSingleArtifact(req) {
|
|
|
6303
6710
|
const templateContent = loadedTemplate || getDefaultFallbackTemplate(artifactType, displayTitle, lessonId || "ON_DEMAND");
|
|
6304
6711
|
const hwStr = hardwarePlatform.join(", ");
|
|
6305
6712
|
const selectedDevices = (config?.devices || (hardwarePlatform[0]?.toLowerCase().includes("paper") ? "paper-only" : "laptop")).toLowerCase();
|
|
6306
|
-
|
|
6307
|
-
const
|
|
6308
|
-
const
|
|
6713
|
+
language.toLowerCase().includes("bilingual") || language.toLowerCase().includes("song");
|
|
6714
|
+
const targetLang = resolveTargetLanguageCode(language);
|
|
6715
|
+
const langDirective = buildLanguageDirective(language);
|
|
6716
|
+
const headingDirective = buildHeadingDirective(
|
|
6717
|
+
artifactType.toUpperCase(),
|
|
6718
|
+
req.sectionLanguageContract,
|
|
6719
|
+
targetLang
|
|
6720
|
+
);
|
|
6721
|
+
const availableSources = {
|
|
6722
|
+
KNOWLEDGE_EXPOSITION: contextSot.exposition || researchContext,
|
|
6723
|
+
CANONICAL_LESSON: contextSot.lessonPlan,
|
|
6724
|
+
FRAMEWORK_EXCERPT: contextSot.framework
|
|
6725
|
+
};
|
|
6726
|
+
validateArtifactDependencies(artifactType.toUpperCase(), availableSources, {
|
|
6727
|
+
lessonId,
|
|
6728
|
+
agent: meta.persona,
|
|
6729
|
+
failClosed: req.failClosedContext ?? false
|
|
6730
|
+
});
|
|
6309
6731
|
const isPaperOnly = selectedDevices === "paper-only" || hwStr.toLowerCase().includes("paper") || hwStr.toLowerCase().includes("print-ready");
|
|
6310
6732
|
const deviceRule = isPaperOnly ? `
|
|
6311
6733
|
- \u{1F4C4} PAPER-ONLY / NO-DEVICE ENVIRONMENT: The learning space has NO computers or screens. All exercises, worksheets, task cards, exit tickets, and code analyses MUST be 100% printable. Provide write-in blank boxes, printed code snippets with line numbers, tracing tables, and physical manipulative instructions.` : selectedDevices === "tablet" || selectedDevices === "smartphone" ? `
|
|
@@ -6326,7 +6748,8 @@ async function generateSingleArtifact(req) {
|
|
|
6326
6748
|
Your task is to author a canonical, publication-grade learning artifact of type "${artifactType.toUpperCase()}".
|
|
6327
6749
|
|
|
6328
6750
|
### CORE OPERATIONAL INVARIANTS:
|
|
6329
|
-
${
|
|
6751
|
+
${langDirective}
|
|
6752
|
+
${headingDirective}
|
|
6330
6753
|
2. NON-MOCK CODE INVARIANT: Any code snippet provided MUST be 100% syntactically valid, idiomatic, and executable without placeholders or fake functions.
|
|
6331
6754
|
3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
|
|
6332
6755
|
4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
|
|
@@ -6335,6 +6758,11 @@ ${langRule}
|
|
|
6335
6758
|
|
|
6336
6759
|
IMAGE PLANNING (media ledger) \u2014 QUOTA GEN: t\u1ED1i \u0111a ${mediaPolicy.maxGenImagesPerArtifact} \u1EA3nh AI cho artifact n\xE0y (\u1EA3nh search kho kh\xF4ng gi\u1EDBi h\u1EA1n).
|
|
6337
6760
|
Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u1ED3 kh\xE1i ni\u1EC7m, step-by-step visual), ch\xE8n placeholder \u0111\xFAng format [IMAGE: slug] (slug ch\u1EEF th\u01B0\u1EDDng-g\u1EA1ch ngang, unique trong b\xE0i, vd img-for-loop-diagram) T\u1EA0I \u0110\xDANG CH\u1ED6 c\u1EA7n \u1EA3nh, tr\xEAn d\xF2ng ri\xEAng. KH\xD4NG t\u1EF1 sinh \u1EA3nh, KH\xD4NG d\xF9ng markdown image \u2014 placeholder s\u1EBD \u0111\u01B0\u1EE3c thay b\u1EB1ng \u1EA3nh th\u1EADt sau khi Media Curator duy\u1EC7t. Ch\u1EC9 \u0111\u1EB7t cho \u1EA3nh TH\u1EF0C S\u1EF0 c\u1EA7n thi\u1EBFt.` : ""}`;
|
|
6761
|
+
const slcContract = req.sectionLanguageContract;
|
|
6762
|
+
const frameworkExcerpt = contextSot.framework ? buildSectionAwareExcerpt(contextSot.framework, { budget: 2500, sectionLanguageContract: slcContract }).excerpt : "";
|
|
6763
|
+
const lessonPlanExcerpt = contextSot.lessonPlan ? buildLessonExcerpt(contextSot.lessonPlan, { budget: 8e3, sectionLanguageContract: slcContract, artifactType: "LESSON" }).excerpt : "";
|
|
6764
|
+
const handoutExcerpt = contextSot.handout ? buildSectionAwareExcerpt(contextSot.handout, { budget: 2e3, sectionLanguageContract: slcContract }).excerpt : "";
|
|
6765
|
+
const expositionExcerpt = contextSot.exposition ? buildExpositionExcerpt(contextSot.exposition, { budget: 6e3, sectionLanguageContract: slcContract }).excerpt : "";
|
|
6338
6766
|
const userPrompt = `### CONTEXT GROUND TRUTH:
|
|
6339
6767
|
- Topic / Concept Domain: "${topic}"
|
|
6340
6768
|
- Lesson ID: "${lessonId || "ON_DEMAND"}"
|
|
@@ -6352,14 +6780,17 @@ ${selectedDevices ? `- Device Mode: "${selectedDevices}"
|
|
|
6352
6780
|
${researchContext ? `### RESEARCH GROUND TRUTH (fact-checked context \u2014 \u01B0u ti\xEAn d\xF9ng, KH\xD4NG b\u1ECBa s\u1ED1 li\u1EC7u):
|
|
6353
6781
|
${researchContext.slice(0, 4e3)}
|
|
6354
6782
|
` : ""}
|
|
6355
|
-
${
|
|
6356
|
-
${
|
|
6783
|
+
${expositionExcerpt ? `### SOT KNOWLEDGE EXPOSITION (CANONICAL KNOWLEDGE \u2014 teach from this, do not contradict):
|
|
6784
|
+
${expositionExcerpt}
|
|
6785
|
+
` : ""}
|
|
6786
|
+
${frameworkExcerpt ? `### SOT CURRICULUM FRAMEWORK:
|
|
6787
|
+
${frameworkExcerpt}
|
|
6357
6788
|
` : ""}
|
|
6358
|
-
${
|
|
6359
|
-
${
|
|
6789
|
+
${lessonPlanExcerpt ? `### SOT CANONICAL LESSON PLAN:
|
|
6790
|
+
${lessonPlanExcerpt}
|
|
6360
6791
|
` : ""}
|
|
6361
|
-
${
|
|
6362
|
-
${
|
|
6792
|
+
${handoutExcerpt ? `### SOT STUDENT HANDOUT:
|
|
6793
|
+
${handoutExcerpt}
|
|
6363
6794
|
` : ""}
|
|
6364
6795
|
|
|
6365
6796
|
### MANDATORY CANONICAL TEMPLATE CONTRACT:
|
|
@@ -6448,9 +6879,16 @@ ${yaml.trim()}
|
|
|
6448
6879
|
const verdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
|
|
6449
6880
|
targetArtifactType: artifactType.toUpperCase(),
|
|
6450
6881
|
lessonId: lessonId || "ON_DEMAND",
|
|
6451
|
-
expectedLanguage:
|
|
6882
|
+
expectedLanguage: targetLang || "en",
|
|
6452
6883
|
targetObjectives: judgeObjectives,
|
|
6453
|
-
generatedContentJson: JSON.stringify({
|
|
6884
|
+
generatedContentJson: JSON.stringify({
|
|
6885
|
+
content: finalContent,
|
|
6886
|
+
artifactType,
|
|
6887
|
+
lessonId: lessonId || "ON_DEMAND",
|
|
6888
|
+
title: displayTitle,
|
|
6889
|
+
canonicalLessonExcerpt: lessonPlanExcerpt || void 0,
|
|
6890
|
+
canonicalExposition: expositionExcerpt || void 0
|
|
6891
|
+
})
|
|
6454
6892
|
});
|
|
6455
6893
|
const passed = Boolean(verdict.passed) && verdict.score >= 85;
|
|
6456
6894
|
if (passed) {
|
|
@@ -6492,9 +6930,16 @@ ${finalContent}` }
|
|
|
6492
6930
|
const reVerdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
|
|
6493
6931
|
targetArtifactType: artifactType.toUpperCase(),
|
|
6494
6932
|
lessonId: lessonId || "ON_DEMAND",
|
|
6495
|
-
expectedLanguage:
|
|
6933
|
+
expectedLanguage: targetLang || "en",
|
|
6496
6934
|
targetObjectives: judgeObjectives,
|
|
6497
|
-
generatedContentJson: JSON.stringify({
|
|
6935
|
+
generatedContentJson: JSON.stringify({
|
|
6936
|
+
content: repaired,
|
|
6937
|
+
artifactType,
|
|
6938
|
+
lessonId: lessonId || "ON_DEMAND",
|
|
6939
|
+
title: displayTitle,
|
|
6940
|
+
canonicalLessonExcerpt: lessonPlanExcerpt || void 0,
|
|
6941
|
+
canonicalExposition: expositionExcerpt || void 0
|
|
6942
|
+
})
|
|
6498
6943
|
});
|
|
6499
6944
|
if (reVerdict.passed && reVerdict.score >= 85) {
|
|
6500
6945
|
return {
|