@thanh01.pmt/curriculum-kit 1.4.47 → 1.4.49
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/README.md +23 -0
- package/dist/ai/index.cjs +21 -3
- package/dist/ai/index.cjs.map +1 -1
- package/dist/ai/index.mjs +21 -3
- package/dist/ai/index.mjs.map +1 -1
- package/dist/index.cjs +240 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -1
- package/dist/index.d.ts +86 -1
- package/dist/index.mjs +237 -14
- package/dist/index.mjs.map +1 -1
- package/dist/pipeline/index.cjs +21 -3
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.mjs +21 -3
- package/dist/pipeline/index.mjs.map +1 -1
- package/dist/schemas/index.cjs +19 -1
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +140 -0
- package/dist/schemas/index.d.ts +140 -0
- package/dist/schemas/index.mjs +19 -1
- package/dist/schemas/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +22 -3
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.mjs +22 -3
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4019,7 +4019,25 @@ var PlanningNodeSchema = zod.z.object({
|
|
|
4019
4019
|
file: zod.z.string(),
|
|
4020
4020
|
evidence: zod.z.string().default("")
|
|
4021
4021
|
})).default([]),
|
|
4022
|
-
phase_id: zod.z.string().default("")
|
|
4022
|
+
phase_id: zod.z.string().default(""),
|
|
4023
|
+
// P52/T2.1: tangible user-visible outcome projected from the graph step
|
|
4024
|
+
// (checkpoint candidate for session cutting). Empty = not a checkpoint.
|
|
4025
|
+
user_visible_deliverable: zod.z.string().default(""),
|
|
4026
|
+
// P52/T3.1 — depth-reconcile inputs (mirrored from the feed; optional so
|
|
4027
|
+
// raw graphs without variants keep planning unchanged):
|
|
4028
|
+
// • depth_variants: minutes to teach this node at each depth level (ULO ≤
|
|
4029
|
+
// CIO ≤ SIO) — the reconciler's price list for downgrades.
|
|
4030
|
+
// • depth_scaffold_candidates: parallel to scaffold_candidates but acting
|
|
4031
|
+
// on DEPTH (SIO→CIO, CIO→ULO) instead of lesson time.
|
|
4032
|
+
// • is_core: Master-Tree core concept — never downgraded; escalate instead.
|
|
4033
|
+
depth_variants: zod.z.object({ ulo: zod.z.number().int().nonnegative(), cio: zod.z.number().int().nonnegative(), sio: zod.z.number().int().nonnegative() }).optional(),
|
|
4034
|
+
depth_scaffold_candidates: zod.z.array(zod.z.object({
|
|
4035
|
+
from_depth: DepthLevelSchema,
|
|
4036
|
+
to_depth: DepthLevelSchema,
|
|
4037
|
+
minutes_saved: zod.z.number().int().nonnegative(),
|
|
4038
|
+
reason: zod.z.string().default("")
|
|
4039
|
+
})).optional(),
|
|
4040
|
+
is_core: zod.z.boolean().default(false)
|
|
4023
4041
|
});
|
|
4024
4042
|
var DependencyEdgeSchema = zod.z.object({
|
|
4025
4043
|
from: zod.z.string(),
|
|
@@ -4857,9 +4875,9 @@ Evaluate the candidate artifact across these 6 dimensions:
|
|
|
4857
4875
|
- 0 pts: Missing critical sections.
|
|
4858
4876
|
|
|
4859
4877
|
5. **Technical & Conceptual Accuracy (15 pts):**
|
|
4860
|
-
- 15 pts: Code snippets, logic flows, architectural descriptions, and Mermaid diagrams are conceptually sound and valid. If \`canonicalExposition\` or \`[KNOWLEDGE_EXPOSITION]\` is provided, content strictly adheres to canonical concepts without contradicting definitions or examples.
|
|
4878
|
+
- 15 pts: Code snippets, logic flows, architectural descriptions, and Mermaid diagrams are conceptually sound and valid. Every code identifier uses the REAL standard-library/API spelling of the declared stack (grounded in REFERENCE_PACK / canonical LESSON) \u2014 internal consistency with a glossary term name is NOT evidence of correctness. If \`canonicalExposition\` or \`[KNOWLEDGE_EXPOSITION]\` is provided, content strictly adheres to canonical concepts without contradicting definitions or examples.
|
|
4861
4879
|
- 8 pts: Minor syntax inaccuracies or unidiomatic code that does not break core concepts.
|
|
4862
|
-
- 0 pts: Severe technical hallucinations, broken logic, invalid diagrams, or direct contradiction of canonical exposition knowledge.
|
|
4880
|
+
- 0 pts: Severe technical hallucinations, broken logic, invalid diagrams, invented casing/spelling rules for standard-library identifiers, or direct contradiction of canonical exposition knowledge.
|
|
4863
4881
|
|
|
4864
4882
|
6. **Contract Consistency & Zero-Drift (10 pts):**
|
|
4865
4883
|
- 10 pts: Satellite artifact strictly adheres to the scope, tech stack, and objectives of Lesson ${lessonId} without introducing unrelated topics. If \`canonicalLessonExcerpt\` is present in candidate JSON, all satellite phases/activities strictly operationalize the master Lesson Flow and Activity Sequence without inventing divergent timelines.
|
|
@@ -5436,6 +5454,7 @@ var MAX_NEW_CONCEPTS_PER_SESSION_K12 = 2;
|
|
|
5436
5454
|
var MAX_NEW_CONCEPTS_PER_SESSION_ADULT = 3;
|
|
5437
5455
|
var DEFAULT_OVERHEAD_RATIO = 0.15;
|
|
5438
5456
|
var MAX_IN_SESSION_SETUP_MINUTES = 15;
|
|
5457
|
+
var SESSION_CUT_TOLERANCE = 0.15;
|
|
5439
5458
|
var ENVIRONMENT_PROVISIONING_MODELS = [
|
|
5440
5459
|
"PRE_INSTALLED_LAB",
|
|
5441
5460
|
"CLOUD_MANAGED",
|
|
@@ -10189,6 +10208,38 @@ function checkScopeDrift(content, scopedKeywords, tolerance = 0) {
|
|
|
10189
10208
|
evidence: drift
|
|
10190
10209
|
};
|
|
10191
10210
|
}
|
|
10211
|
+
function checkIdentifierCasing(content, refPack) {
|
|
10212
|
+
if (!refPack || !refPack.trim()) return null;
|
|
10213
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
10214
|
+
const remember = (id) => {
|
|
10215
|
+
if (id.length < 2) return;
|
|
10216
|
+
const key = id.toLowerCase().replace(/_/g, "");
|
|
10217
|
+
if (!canonical.has(key)) canonical.set(key, id);
|
|
10218
|
+
};
|
|
10219
|
+
for (const m of refPack.matchAll(/`([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) remember(m[1]);
|
|
10220
|
+
for (const m of refPack.matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) remember(m[1]);
|
|
10221
|
+
for (const m of refPack.matchAll(/`([A-Za-z_][A-Za-z0-9_]*)`/g)) remember(m[1]);
|
|
10222
|
+
if (canonical.size === 0) return null;
|
|
10223
|
+
const RESERVED = /* @__PURE__ */ new Set(["if", "for", "while", "switch", "func", "return", "guard", "catch", "else", "in"]);
|
|
10224
|
+
const violations = [];
|
|
10225
|
+
const fenceRe = /```[a-zA-Z]*\n([\s\S]*?)```/g;
|
|
10226
|
+
for (const fence of content.matchAll(fenceRe)) {
|
|
10227
|
+
for (const m of fence[1].matchAll(/\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g)) {
|
|
10228
|
+
const tok = m[1];
|
|
10229
|
+
if (RESERVED.has(tok.toLowerCase())) continue;
|
|
10230
|
+
const real = canonical.get(tok.toLowerCase().replace(/_/g, ""));
|
|
10231
|
+
if (real && real !== tok && !violations.includes(`${tok} \u2192 ${real}`)) {
|
|
10232
|
+
violations.push(`${tok} \u2192 ${real}`);
|
|
10233
|
+
}
|
|
10234
|
+
}
|
|
10235
|
+
}
|
|
10236
|
+
if (violations.length === 0) return null;
|
|
10237
|
+
return {
|
|
10238
|
+
code: "identifier-casing",
|
|
10239
|
+
detail: `Code d\xF9ng identifier l\u1EC7ch casing so v\u1EDBi REFERENCE_PACK (ngu\u1ED3n s\u1EF1 th\u1EADt k\u1EF9 thu\u1EADt): ${violations.join(", ")}. Identifier trong code PH\u1EA2I copy nguy\xEAn d\u1EA1ng t\u1EEB REFERENCE_PACK \u2014 KH\xD4NG suy ra casing t\u1EEB t\xEAn keyword/concept (ch\xFAng l\xE0 nh\xE3n s\u01B0 ph\u1EA1m, kh\xF4ng ph\u1EA3i API).`,
|
|
10240
|
+
evidence: violations.slice(0, 5)
|
|
10241
|
+
};
|
|
10242
|
+
}
|
|
10192
10243
|
function validateArtifactDeterministic(input) {
|
|
10193
10244
|
const issues = [];
|
|
10194
10245
|
const cjk = scanCjkLeaks(input.content);
|
|
@@ -10196,6 +10247,8 @@ function validateArtifactDeterministic(input) {
|
|
|
10196
10247
|
if (input.refPack) {
|
|
10197
10248
|
const ver = checkVersionGroundTruth(input.content, input.refPack);
|
|
10198
10249
|
if (ver) issues.push(ver);
|
|
10250
|
+
const casing = checkIdentifierCasing(input.content, input.refPack);
|
|
10251
|
+
if (casing) issues.push(casing);
|
|
10199
10252
|
}
|
|
10200
10253
|
if (input.scopedKeywords && input.scopedKeywords.length > 0) {
|
|
10201
10254
|
const scope = checkScopeDrift(input.content, input.scopedKeywords);
|
|
@@ -10307,9 +10360,10 @@ function buildSystemPrompt(targetLanguage = "vi", techStack, hardwarePlatform) {
|
|
|
10307
10360
|
"Rules:",
|
|
10308
10361
|
"1. Cover EXACTLY the scoped keywords and concepts \u2014 nothing beyond the session scope.",
|
|
10309
10362
|
"2. Every Key Term definition must be consistent with the glossary entry provided.",
|
|
10310
|
-
|
|
10311
|
-
"4.
|
|
10312
|
-
"5.
|
|
10363
|
+
'3. IDENTIFIER GROUND TRUTH: glossary/keyword names are pedagogical concept labels \u2014 NEVER derive code identifier casing or spelling from them. In code samples, copy identifiers EXACTLY from the REFERENCE_PACK ground truth (e.g. the standard library function is `print` lowercase; "Print" as a topic label is not an API symbol).',
|
|
10364
|
+
"4. Respect each concept depth target (ULO/CIO/SIO) \u2014 do not overshoot.",
|
|
10365
|
+
"5. Student-facing only: no teacher instructions, no classroom management text.",
|
|
10366
|
+
"6. Output the full markdown document following the provided section skeleton exactly (headers and placeholders preserved).",
|
|
10313
10367
|
domainGuardrails,
|
|
10314
10368
|
langDirective
|
|
10315
10369
|
].join("\n");
|
|
@@ -10547,6 +10601,98 @@ async function ensureExpositionForLesson(storage, projectId, lessonCode, options
|
|
|
10547
10601
|
return { relPath: result.relPath, context: buildExpositionContext(result.content), reused: result.reused, rawContent: result.content };
|
|
10548
10602
|
}
|
|
10549
10603
|
init_errors();
|
|
10604
|
+
|
|
10605
|
+
// src/services/depthReconciler.ts
|
|
10606
|
+
function tierOf(node, isLeaf) {
|
|
10607
|
+
if (node.phase_id !== "" && /__ADV\d{2}$/.test(node.id)) return 1;
|
|
10608
|
+
const depth = node.depth_hint ?? "cio";
|
|
10609
|
+
if (depth === "sio") return isLeaf ? 2 : 3;
|
|
10610
|
+
if (depth === "cio") return isLeaf ? 4 : 5;
|
|
10611
|
+
return -1;
|
|
10612
|
+
}
|
|
10613
|
+
function classifyDepthCandidates(nodes, edges) {
|
|
10614
|
+
const baseConcept = (id) => id.replace(/__(ADV|PREV)\d{2}$/, "");
|
|
10615
|
+
const conceptIds = new Set(nodes.filter((n) => n.kind === "concept").map((n) => baseConcept(n.id)));
|
|
10616
|
+
const hasDependent = /* @__PURE__ */ new Set();
|
|
10617
|
+
for (const e of edges) {
|
|
10618
|
+
if (e.kind !== "knowledge") continue;
|
|
10619
|
+
const from = baseConcept(e.from);
|
|
10620
|
+
const to = baseConcept(e.to);
|
|
10621
|
+
if (conceptIds.has(from) && conceptIds.has(to)) hasDependent.add(from);
|
|
10622
|
+
}
|
|
10623
|
+
const isLeaf = /* @__PURE__ */ new Map();
|
|
10624
|
+
for (const n of nodes) {
|
|
10625
|
+
if (n.kind !== "concept") continue;
|
|
10626
|
+
const base = baseConcept(n.id);
|
|
10627
|
+
if (!isLeaf.has(base)) isLeaf.set(base, !hasDependent.has(base));
|
|
10628
|
+
}
|
|
10629
|
+
return isLeaf;
|
|
10630
|
+
}
|
|
10631
|
+
function reconcilePlanDepth(nodes, edges, spec, budgetMinutes) {
|
|
10632
|
+
const MAX_ROUNDS = 5;
|
|
10633
|
+
const total = () => nodes.reduce((acc, n) => acc + n.estimated_minutes, 0);
|
|
10634
|
+
const isLeaf = classifyDepthCandidates(nodes, edges);
|
|
10635
|
+
const baseConcept = (id) => id.replace(/__(ADV|PREV)\d{2}$/, "");
|
|
10636
|
+
const applied = [];
|
|
10637
|
+
const mutated = /* @__PURE__ */ new Set();
|
|
10638
|
+
let rounds = 0;
|
|
10639
|
+
while (total() > budgetMinutes && rounds < MAX_ROUNDS) {
|
|
10640
|
+
rounds++;
|
|
10641
|
+
let overBy = total() - budgetMinutes;
|
|
10642
|
+
const candidates = [];
|
|
10643
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
10644
|
+
const n = nodes[i];
|
|
10645
|
+
if (n.kind !== "concept") continue;
|
|
10646
|
+
if (n.is_core) continue;
|
|
10647
|
+
if (n.depth_hint == null) continue;
|
|
10648
|
+
const base = baseConcept(n.id);
|
|
10649
|
+
const leaf = isLeaf.get(base) ?? true;
|
|
10650
|
+
const tier = tierOf(n, leaf);
|
|
10651
|
+
if (tier < 0) continue;
|
|
10652
|
+
const depth = n.depth_hint ?? "cio";
|
|
10653
|
+
const toDepth = depth === "sio" ? "cio" : "ulo";
|
|
10654
|
+
const variants = n.depth_variants;
|
|
10655
|
+
const target = variants ? variants[toDepth] : Math.max(5, Math.round(n.estimated_minutes * 0.6));
|
|
10656
|
+
const saved = n.estimated_minutes - target;
|
|
10657
|
+
if (saved <= 0) continue;
|
|
10658
|
+
candidates.push({
|
|
10659
|
+
nodeId: n.id,
|
|
10660
|
+
fromDepth: depth,
|
|
10661
|
+
toDepth,
|
|
10662
|
+
minutesSaved: saved,
|
|
10663
|
+
reason: n.depth_scaffold_candidates?.find((c) => c.to_depth === toDepth)?.reason ?? `depth downgrade ${depth}\u2192${toDepth} (tier ${tier}${leaf ? ", leaf" : ""})`,
|
|
10664
|
+
node: n,
|
|
10665
|
+
tier,
|
|
10666
|
+
index: i
|
|
10667
|
+
});
|
|
10668
|
+
}
|
|
10669
|
+
if (candidates.length === 0) break;
|
|
10670
|
+
candidates.sort((a, b) => a.tier - b.tier || b.minutesSaved - a.minutesSaved);
|
|
10671
|
+
for (const c of candidates) {
|
|
10672
|
+
if (overBy <= 0) break;
|
|
10673
|
+
const n = c.node;
|
|
10674
|
+
const target = n.depth_variants ? n.depth_variants[c.toDepth] : Math.max(5, Math.round(n.estimated_minutes * 0.6));
|
|
10675
|
+
const saved = n.estimated_minutes - target;
|
|
10676
|
+
if (saved <= 0) continue;
|
|
10677
|
+
n.estimated_minutes = target;
|
|
10678
|
+
n.depth_hint = c.toDepth;
|
|
10679
|
+
applied.push({ nodeId: n.id, fromDepth: c.fromDepth, toDepth: c.toDepth, minutesSaved: saved, reason: c.reason });
|
|
10680
|
+
mutated.add(n.id);
|
|
10681
|
+
overBy -= saved;
|
|
10682
|
+
}
|
|
10683
|
+
}
|
|
10684
|
+
const finalTotal = total();
|
|
10685
|
+
const deficitMinutes = Math.max(0, finalTotal - budgetMinutes);
|
|
10686
|
+
return {
|
|
10687
|
+
unchanged: applied.length === 0,
|
|
10688
|
+
applied,
|
|
10689
|
+
mutatedNodeIds: [...mutated],
|
|
10690
|
+
escalate: deficitMinutes > 0,
|
|
10691
|
+
deficitMinutes
|
|
10692
|
+
};
|
|
10693
|
+
}
|
|
10694
|
+
|
|
10695
|
+
// src/services/curriculumPlannerService.ts
|
|
10550
10696
|
var asArr = (v) => Array.isArray(v) ? v : [];
|
|
10551
10697
|
var asStr = (v, dflt = "") => typeof v === "string" ? v : dflt;
|
|
10552
10698
|
var asNum = (v, dflt) => typeof v === "number" && Number.isFinite(v) ? v : dflt;
|
|
@@ -10586,7 +10732,20 @@ function normalizePlanningGraph(raw) {
|
|
|
10586
10732
|
suggest: asStr(c.suggest, "provide_full")
|
|
10587
10733
|
})),
|
|
10588
10734
|
references: asArr(n.references).map((ref) => ({ file: asStr(ref.file), evidence: asStr(ref.evidence) })).filter((ref) => ref.file),
|
|
10589
|
-
phase_id: asStr(n.phase_id)
|
|
10735
|
+
phase_id: asStr(n.phase_id),
|
|
10736
|
+
user_visible_deliverable: asStr(n.user_visible_deliverable),
|
|
10737
|
+
depth_variants: (() => {
|
|
10738
|
+
const dv = n.depth_variants;
|
|
10739
|
+
if (!dv) return void 0;
|
|
10740
|
+
return { ulo: Math.max(0, Math.round(asNum(dv.ulo, 0))), cio: Math.max(0, Math.round(asNum(dv.cio, 0))), sio: Math.max(0, Math.round(asNum(dv.sio, 0))) };
|
|
10741
|
+
})(),
|
|
10742
|
+
depth_scaffold_candidates: asArr(n.depth_scaffold_candidates).map((c) => ({
|
|
10743
|
+
from_depth: asStr(c.from_depth, "sio"),
|
|
10744
|
+
to_depth: asStr(c.to_depth, "cio"),
|
|
10745
|
+
minutes_saved: Math.max(0, Math.round(asNum(c.minutes_saved, 0))),
|
|
10746
|
+
reason: asStr(c.reason)
|
|
10747
|
+
})),
|
|
10748
|
+
is_core: n.is_core === true
|
|
10590
10749
|
});
|
|
10591
10750
|
}
|
|
10592
10751
|
for (const e of asArr(r.dependency_edges)) {
|
|
@@ -10630,7 +10789,9 @@ function normalizePlanningGraph(raw) {
|
|
|
10630
10789
|
completion_level: "standard",
|
|
10631
10790
|
scaffold_candidates: [],
|
|
10632
10791
|
references: [],
|
|
10633
|
-
phase_id: ""
|
|
10792
|
+
phase_id: "",
|
|
10793
|
+
user_visible_deliverable: "",
|
|
10794
|
+
is_core: false
|
|
10634
10795
|
});
|
|
10635
10796
|
for (const p of asStrArr(c.prerequisites)) {
|
|
10636
10797
|
edges.push({ from: p, to: asStr(c.id), kind: "knowledge", reason: "concept prerequisite" });
|
|
@@ -10667,7 +10828,9 @@ function normalizePlanningGraph(raw) {
|
|
|
10667
10828
|
completion_level: "standard",
|
|
10668
10829
|
scaffold_candidates: [],
|
|
10669
10830
|
references: [],
|
|
10670
|
-
phase_id: ""
|
|
10831
|
+
phase_id: "",
|
|
10832
|
+
user_visible_deliverable: "",
|
|
10833
|
+
is_core: false
|
|
10671
10834
|
});
|
|
10672
10835
|
if (prev) edges.push({ from: prev, to: id, kind: "task", reason: "roadmap sequence" });
|
|
10673
10836
|
prev = id;
|
|
@@ -10703,13 +10866,15 @@ function normalizePlanningGraph(raw) {
|
|
|
10703
10866
|
completion_level: "standard",
|
|
10704
10867
|
scaffold_candidates: [],
|
|
10705
10868
|
references: [],
|
|
10706
|
-
phase_id: ""
|
|
10869
|
+
phase_id: "",
|
|
10870
|
+
user_visible_deliverable: "",
|
|
10871
|
+
is_core: false
|
|
10707
10872
|
});
|
|
10708
10873
|
if (prev) edges.push({ from: prev, to: id, kind: "task", reason: "feature build order" });
|
|
10709
10874
|
prev = id;
|
|
10710
10875
|
}
|
|
10711
10876
|
if (stepCount === 0 && fid) {
|
|
10712
|
-
nodes.push({ id: fid, kind: "skill", name: asStr(f.name, fid), concept_codes: [], keywords: [], new_keywords: [], prerequisite_keywords: [], estimated_minutes: 45, bloom_hint: "Apply", depth_hint: null, completion_level: "standard", scaffold_candidates: [], references: [], phase_id: "" });
|
|
10877
|
+
nodes.push({ id: fid, kind: "skill", name: asStr(f.name, fid), concept_codes: [], keywords: [], new_keywords: [], prerequisite_keywords: [], estimated_minutes: 45, bloom_hint: "Apply", depth_hint: null, completion_level: "standard", scaffold_candidates: [], references: [], phase_id: "", user_visible_deliverable: "", is_core: false });
|
|
10713
10878
|
}
|
|
10714
10879
|
}
|
|
10715
10880
|
}
|
|
@@ -10871,6 +11036,39 @@ function computePackingSpec(constraints) {
|
|
|
10871
11036
|
sessionDurationMinutes: constraints.session_duration_minutes
|
|
10872
11037
|
};
|
|
10873
11038
|
}
|
|
11039
|
+
var filledOf = (s) => s.knowledgeMinutes + s.practiceMinutes;
|
|
11040
|
+
function cutBackToCheckpoint(current, nodeById, spec, warnings) {
|
|
11041
|
+
const budget = spec.contentBudget;
|
|
11042
|
+
const minFill = budget * (1 - SESSION_CUT_TOLERANCE);
|
|
11043
|
+
const hasPartAfter = new Array(current.entries.length).fill(false);
|
|
11044
|
+
let seenPart = false;
|
|
11045
|
+
for (let i = current.entries.length - 1; i >= 0; i--) {
|
|
11046
|
+
hasPartAfter[i] = seenPart;
|
|
11047
|
+
if (current.entries[i].totalParts > 1) seenPart = true;
|
|
11048
|
+
}
|
|
11049
|
+
let bestIdx = -1;
|
|
11050
|
+
let cumulative = 0;
|
|
11051
|
+
for (let i = 0; i < current.entries.length; i++) {
|
|
11052
|
+
const e = current.entries[i];
|
|
11053
|
+
cumulative += e.minutes;
|
|
11054
|
+
if (hasPartAfter[i] || e.totalParts > 1) continue;
|
|
11055
|
+
const n = nodeById.get(e.id);
|
|
11056
|
+
if (!n || n.user_visible_deliverable.trim().length === 0) continue;
|
|
11057
|
+
if (cumulative > budget || cumulative < minFill) continue;
|
|
11058
|
+
bestIdx = i;
|
|
11059
|
+
}
|
|
11060
|
+
if (bestIdx < 0) {
|
|
11061
|
+
warnings.push("no-checkpoint-within-tolerance: session cut at " + filledOf(current) + "m without a user-visible deliverable");
|
|
11062
|
+
return [];
|
|
11063
|
+
}
|
|
11064
|
+
const displacedEntries = current.entries.splice(bestIdx + 1);
|
|
11065
|
+
for (const e of displacedEntries) {
|
|
11066
|
+
if (nodeById.get(e.id)?.kind === "concept") current.knowledgeMinutes -= e.minutes;
|
|
11067
|
+
else current.practiceMinutes -= e.minutes;
|
|
11068
|
+
}
|
|
11069
|
+
current.nodeIds = current.entries.map((e) => e.id);
|
|
11070
|
+
return displacedEntries.map((e) => nodeById.get(e.id)).filter((n) => n != null);
|
|
11071
|
+
}
|
|
10874
11072
|
function packUnitSessions(group, nodeById, spec, warnings) {
|
|
10875
11073
|
const sessions = [];
|
|
10876
11074
|
const fresh = () => ({ groupId: group.key, nodeIds: [], entries: [], knowledgeMinutes: 0, practiceMinutes: 0, oversized: false });
|
|
@@ -10880,7 +11078,9 @@ function packUnitSessions(group, nodeById, spec, warnings) {
|
|
|
10880
11078
|
current = fresh();
|
|
10881
11079
|
};
|
|
10882
11080
|
const filled = () => current.knowledgeMinutes + current.practiceMinutes;
|
|
10883
|
-
|
|
11081
|
+
const ids = [...group.orderedIds];
|
|
11082
|
+
while (ids.length > 0) {
|
|
11083
|
+
const id = ids.shift();
|
|
10884
11084
|
const node = nodeById.get(id);
|
|
10885
11085
|
const isKnowledge = node.kind === "concept";
|
|
10886
11086
|
if (node.estimated_minutes > spec.contentBudget) {
|
|
@@ -10916,7 +11116,16 @@ function packUnitSessions(group, nodeById, spec, warnings) {
|
|
|
10916
11116
|
continue;
|
|
10917
11117
|
}
|
|
10918
11118
|
const minutes = node.estimated_minutes;
|
|
10919
|
-
if (current.entries.length > 0 && filled() + minutes > spec.contentBudget)
|
|
11119
|
+
if (current.entries.length > 0 && filled() + minutes > spec.contentBudget) {
|
|
11120
|
+
const displaced = cutBackToCheckpoint(current, nodeById, spec, warnings);
|
|
11121
|
+
if (displaced.length > 0) {
|
|
11122
|
+
ids.unshift(id);
|
|
11123
|
+
for (let di = displaced.length - 1; di >= 0; di--) ids.unshift(displaced[di].id);
|
|
11124
|
+
flush();
|
|
11125
|
+
continue;
|
|
11126
|
+
}
|
|
11127
|
+
flush();
|
|
11128
|
+
}
|
|
10920
11129
|
if (isKnowledge) current.knowledgeMinutes += minutes;
|
|
10921
11130
|
else current.practiceMinutes += minutes;
|
|
10922
11131
|
current.entries.push({ id, minutes, part: 1, totalParts: 1 });
|
|
@@ -11226,6 +11435,19 @@ async function buildCurriculumPlan(rawGraph, options) {
|
|
|
11226
11435
|
const graph = normalizePlanningGraph(rawGraph);
|
|
11227
11436
|
warnings.push(...graph.warnings);
|
|
11228
11437
|
assertAcyclic(graph.nodes, graph.edges);
|
|
11438
|
+
let reconcileSummary = null;
|
|
11439
|
+
if (typeof constraints.total_sessions === "number" && constraints.total_sessions > 0) {
|
|
11440
|
+
const spec0 = computePackingSpec(constraints);
|
|
11441
|
+
const budgetMinutes = constraints.total_sessions * spec0.contentBudget;
|
|
11442
|
+
reconcileSummary = reconcilePlanDepth(graph.nodes, graph.edges, spec0, budgetMinutes);
|
|
11443
|
+
if (!reconcileSummary.unchanged) {
|
|
11444
|
+
const saved = reconcileSummary.applied.reduce((a, x) => a + x.minutesSaved, 0);
|
|
11445
|
+
warnings.push(`depth reconcile: ${reconcileSummary.applied.length} downgrade(s), saved ${saved}m across ${reconcileSummary.mutatedNodeIds.length} node(s)`);
|
|
11446
|
+
}
|
|
11447
|
+
if (reconcileSummary.escalate) {
|
|
11448
|
+
warnings.push(`budget_escalation: deficit ${reconcileSummary.deficitMinutes}m after exhausting non-core depth candidates (budget ${constraints.total_sessions * computePackingSpec(constraints).contentBudget}m) \u2014 human review required`);
|
|
11449
|
+
}
|
|
11450
|
+
}
|
|
11229
11451
|
const orderedIds = topoSort(graph.nodes, graph.edges);
|
|
11230
11452
|
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
11231
11453
|
const spec = computePackingSpec(constraints);
|
|
@@ -27169,6 +27391,7 @@ Your task is to author a canonical, publication-grade learning artifact of type
|
|
|
27169
27391
|
${langDirective}
|
|
27170
27392
|
${headingDirective}
|
|
27171
27393
|
2. NON-MOCK CODE INVARIANT: Any code snippet provided MUST be 100% syntactically valid, idiomatic, and executable without placeholders or fake functions.
|
|
27394
|
+
2b. IDENTIFIER GROUND TRUTH: Never derive identifier casing/spelling from glossary term or concept names (they are pedagogical labels). Copy identifiers EXACTLY from the canonical LESSON and REFERENCE_PACK ground truth present in the context.
|
|
27172
27395
|
3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
|
|
27173
27396
|
4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
|
|
27174
27397
|
5. TARGET AUDIENCE: ${gradeLevel ? `Grade ${gradeLevel}, ` : ""}${targetAge} on ${hwStr}.${deviceRule}${classDynamicsRule}${extraContextRule}
|
|
@@ -31673,6 +31896,7 @@ exports.RotationStationSchema = RotationStationSchema;
|
|
|
31673
31896
|
exports.RubricCriteriaSchema = RubricCriteriaSchema;
|
|
31674
31897
|
exports.RubricSchema = RubricSchema;
|
|
31675
31898
|
exports.SATELLITE_LESSON_PRIORITIES = SATELLITE_LESSON_PRIORITIES;
|
|
31899
|
+
exports.SESSION_CUT_TOLERANCE = SESSION_CUT_TOLERANCE;
|
|
31676
31900
|
exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
|
|
31677
31901
|
exports.STANDARD_REF_REGEX = STANDARD_REF_REGEX;
|
|
31678
31902
|
exports.STANDARD_SOT_FILES = STANDARD_SOT_FILES;
|
|
@@ -31762,6 +31986,7 @@ exports.buildTeacherGuidePrompt = buildTeacherGuidePrompt;
|
|
|
31762
31986
|
exports.buildWalkingSkeleton = buildWalkingSkeleton;
|
|
31763
31987
|
exports.buildWorksheetPrompt = buildWorksheetPrompt;
|
|
31764
31988
|
exports.checkSessionZpd = checkSessionZpd;
|
|
31989
|
+
exports.classifyDepthCandidates = classifyDepthCandidates;
|
|
31765
31990
|
exports.closeTruncatedJson = closeTruncatedJson;
|
|
31766
31991
|
exports.computeConceptSpiralProgression = computeConceptSpiralProgression;
|
|
31767
31992
|
exports.computeContentHash = computeContentHash;
|
|
@@ -31855,6 +32080,7 @@ exports.loadAuthoringTemplate = loadAuthoringTemplate;
|
|
|
31855
32080
|
exports.loadCurriculumTemplate = loadCurriculumTemplate;
|
|
31856
32081
|
exports.loadSotTemplate = loadSotTemplate;
|
|
31857
32082
|
exports.normalizePlanningGraph = normalizePlanningGraph;
|
|
32083
|
+
exports.packUnitSessions = packUnitSessions;
|
|
31858
32084
|
exports.packagerTools = packagerTools;
|
|
31859
32085
|
exports.parseAllSessions = parseAllSessions;
|
|
31860
32086
|
exports.parseGateSettings = parseGateSettings;
|
|
@@ -31865,6 +32091,7 @@ exports.produceSingleLesson = produceSingleLesson;
|
|
|
31865
32091
|
exports.publishToGitHub = publishToGitHub;
|
|
31866
32092
|
exports.publishToSupabase = publishToSupabase;
|
|
31867
32093
|
exports.rankGenCandidates = rankGenCandidates;
|
|
32094
|
+
exports.reconcilePlanDepth = reconcilePlanDepth;
|
|
31868
32095
|
exports.renderAssignedRowsTable = renderAssignedRowsTable;
|
|
31869
32096
|
exports.renderEntitySlotsForType = renderEntitySlotsForType;
|
|
31870
32097
|
exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
|