@thanh01.pmt/curriculum-kit 1.4.49 → 1.4.51
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/index.cjs +69 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.mjs +69 -17
- package/dist/index.mjs.map +1 -1
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11030,10 +11030,13 @@ function buildUnitGroupings(graph, orderedIds) {
|
|
|
11030
11030
|
}
|
|
11031
11031
|
function computePackingSpec(constraints) {
|
|
11032
11032
|
const overheadMinutes = Math.round(constraints.session_duration_minutes * constraints.overhead_ratio);
|
|
11033
|
+
const upperAge = constraints.age_band?.[1];
|
|
11034
|
+
const maxNew = upperAge !== void 0 && upperAge > 15 ? MAX_NEW_CONCEPTS_PER_SESSION_ADULT : MAX_NEW_CONCEPTS_PER_SESSION_K12;
|
|
11033
11035
|
return {
|
|
11034
11036
|
contentBudget: constraints.session_duration_minutes - overheadMinutes,
|
|
11035
11037
|
overheadMinutes,
|
|
11036
|
-
sessionDurationMinutes: constraints.session_duration_minutes
|
|
11038
|
+
sessionDurationMinutes: constraints.session_duration_minutes,
|
|
11039
|
+
maxNewConceptsPerSession: maxNew
|
|
11037
11040
|
};
|
|
11038
11041
|
}
|
|
11039
11042
|
var filledOf = (s) => s.knowledgeMinutes + s.practiceMinutes;
|
|
@@ -11069,6 +11072,15 @@ function cutBackToCheckpoint(current, nodeById, spec, warnings) {
|
|
|
11069
11072
|
current.nodeIds = current.entries.map((e) => e.id);
|
|
11070
11073
|
return displacedEntries.map((e) => nodeById.get(e.id)).filter((n) => n != null);
|
|
11071
11074
|
}
|
|
11075
|
+
function canCoalesceWithinZpdBudget(curr, next, nodeById, maxNew) {
|
|
11076
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11077
|
+
for (const s of [curr, next]) {
|
|
11078
|
+
for (const nid of s.nodeIds) {
|
|
11079
|
+
for (const c of nodeById.get(nid)?.concept_codes || []) seen.add(c);
|
|
11080
|
+
}
|
|
11081
|
+
}
|
|
11082
|
+
return seen.size <= maxNew;
|
|
11083
|
+
}
|
|
11072
11084
|
function packUnitSessions(group, nodeById, spec, warnings) {
|
|
11073
11085
|
const sessions = [];
|
|
11074
11086
|
const fresh = () => ({ groupId: group.key, nodeIds: [], entries: [], knowledgeMinutes: 0, practiceMinutes: 0, oversized: false });
|
|
@@ -11116,6 +11128,15 @@ function packUnitSessions(group, nodeById, spec, warnings) {
|
|
|
11116
11128
|
continue;
|
|
11117
11129
|
}
|
|
11118
11130
|
const minutes = node.estimated_minutes;
|
|
11131
|
+
if (spec.maxNewConceptsPerSession && spec.maxNewConceptsPerSession > 0 && current.entries.length > 0) {
|
|
11132
|
+
const currentConcepts = new Set(current.nodeIds.flatMap((nid) => nodeById.get(nid)?.concept_codes || []));
|
|
11133
|
+
const incoming = nodeById.get(id)?.concept_codes || [];
|
|
11134
|
+
const wouldAdd = incoming.filter((c) => !currentConcepts.has(c)).length;
|
|
11135
|
+
if (currentConcepts.size + wouldAdd > spec.maxNewConceptsPerSession) {
|
|
11136
|
+
warnings.push(`ZPD pack flush: session ${group.key}#${sessions.length + 1} cut at ${currentConcepts.size} new concepts (budget ${spec.maxNewConceptsPerSession})`);
|
|
11137
|
+
flush();
|
|
11138
|
+
}
|
|
11139
|
+
}
|
|
11119
11140
|
if (current.entries.length > 0 && filled() + minutes > spec.contentBudget) {
|
|
11120
11141
|
const displaced = cutBackToCheckpoint(current, nodeById, spec, warnings);
|
|
11121
11142
|
if (displaced.length > 0) {
|
|
@@ -11274,19 +11295,19 @@ function checkSessionZpd(sessionIndex, nodeIds, nodeById, allSeenConcepts, allSe
|
|
|
11274
11295
|
const knownKeywords = allKeywords.filter((k) => allSeenKeywords.has(k.toLowerCase()) || entryKw.has(k.toLowerCase()));
|
|
11275
11296
|
const issues = [];
|
|
11276
11297
|
let verdict = "OK";
|
|
11277
|
-
if (
|
|
11298
|
+
if (newConcepts.length > MAX_NEW_CONCEPTS_PER_SESSION_K12) {
|
|
11278
11299
|
verdict = "TOO_MANY_NEW";
|
|
11279
|
-
issues.push(`${newConcepts.length} new concepts exceed ZPD limit (max
|
|
11280
|
-
}
|
|
11281
|
-
|
|
11282
|
-
issues.push(
|
|
11300
|
+
issues.push(`${newConcepts.length} new concepts exceed ZPD limit (max ${MAX_NEW_CONCEPTS_PER_SESSION_K12})`);
|
|
11301
|
+
}
|
|
11302
|
+
if (newKeywords.length > 4) {
|
|
11303
|
+
issues.push(`Evidence for judge: ${newKeywords.length} new keywords in session (noisy proxy, not a gate)`);
|
|
11283
11304
|
}
|
|
11284
11305
|
if (sessionIndex > 0) {
|
|
11285
11306
|
if (concepts.length > 1 && knownConcepts.length === 0) {
|
|
11286
|
-
verdict = "NO_ZPD_BRIDGE";
|
|
11307
|
+
if (verdict !== "TOO_MANY_NEW") verdict = "NO_ZPD_BRIDGE";
|
|
11287
11308
|
issues.push("No known concepts from previous sessions to bridge new learning");
|
|
11288
11309
|
} else if (concepts.length === 0 && allKeywords.length > 2 && knownKeywords.length === 0) {
|
|
11289
|
-
verdict = "NO_ZPD_BRIDGE";
|
|
11310
|
+
if (verdict !== "TOO_MANY_NEW") verdict = "NO_ZPD_BRIDGE";
|
|
11290
11311
|
issues.push("No known keywords from previous sessions to bridge new learning");
|
|
11291
11312
|
}
|
|
11292
11313
|
}
|
|
@@ -11495,7 +11516,8 @@ async function buildCurriculumPlan(rawGraph, options) {
|
|
|
11495
11516
|
const currMins = curr.knowledgeMinutes + curr.practiceMinutes;
|
|
11496
11517
|
const nextMins = next.knowledgeMinutes + next.practiceMinutes;
|
|
11497
11518
|
const totalContent = currMins + nextMins;
|
|
11498
|
-
|
|
11519
|
+
const maxNew = spec.maxNewConceptsPerSession ?? Infinity;
|
|
11520
|
+
if ((currMins <= 45 || nextMins <= 45 || totalContent <= spec.contentBudget * 1.5) && canCoalesceWithinZpdBudget(curr, next, nodeById, maxNew)) {
|
|
11499
11521
|
const scale = totalContent > spec.contentBudget ? spec.contentBudget / totalContent : 1;
|
|
11500
11522
|
curr.nodeIds.push(...next.nodeIds);
|
|
11501
11523
|
for (const e of next.entries) {
|
|
@@ -11564,9 +11586,13 @@ async function buildCurriculumPlan(rawGraph, options) {
|
|
|
11564
11586
|
if (options.llmFn) {
|
|
11565
11587
|
const sys = "You write ONE concise learning objective sentence (max 40 words) for a training session. Output the sentence only.";
|
|
11566
11588
|
const usr = "Session: " + lessonCode + " \u2014 " + nodeNames.join("; ") + ". Unit: " + unit.name + ". Course goals: " + (constraints.course_goals.join("; ") || "n/a") + ".";
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11589
|
+
try {
|
|
11590
|
+
const out = (await options.llmFn(sys, usr)).trim();
|
|
11591
|
+
if (out.length < 10) throw new Error("LLM prose objective too short for " + lessonCode + ': "' + out.slice(0, 40) + '"');
|
|
11592
|
+
proseObjective = out;
|
|
11593
|
+
} catch (e) {
|
|
11594
|
+
warnings.push("prose_objective_llm_failed:" + (e instanceof Error ? e.message.slice(0, 80) : String(e).slice(0, 80)));
|
|
11595
|
+
}
|
|
11570
11596
|
}
|
|
11571
11597
|
const ctx = { lessonCode, sessionIndex: i, nodeIds: s.nodeIds, packedMinutes: s.knowledgeMinutes + s.practiceMinutes, contentBudget: spec.contentBudget };
|
|
11572
11598
|
const nodeKeywords = (ids) => [...new Set(ids.flatMap((id) => nodeById.get(id).keywords))];
|
|
@@ -11664,6 +11690,32 @@ ${sessionSummaries}`;
|
|
|
11664
11690
|
warnings.push("differentiation_llm_failed:" + (e instanceof Error ? e.message.slice(0, 80) : String(e).slice(0, 80)));
|
|
11665
11691
|
}
|
|
11666
11692
|
}
|
|
11693
|
+
if (options.llmFn && sessions.length > 0) {
|
|
11694
|
+
try {
|
|
11695
|
+
const eSys = 'You write OBSERVABLE exit evidence for curriculum sessions. Output ONLY a JSON object mapping session-id to an array of 1-3 short strings. Each string must name what a bystander can SEE or VERIFY at the end of the session: a running screen, a passing test, a saved file, correct printed output, a working interaction. NEVER write "Student understands/explains/applies X" \u2014 describe the artifact or observable result instead. Same language as the session objectives.';
|
|
11696
|
+
const eUsr = `Age band: ${constraints.age_band[0]}-${constraints.age_band[1]}. Entry level: ${constraints.entry_level}.
|
|
11697
|
+
Sessions (current exit evidence to improve):
|
|
11698
|
+
${sessions.map((s) => `${s.id}|objective: ${s.prose_objective.slice(0, 100)}|current: ${s.exit_evidence.join(" ; ")}`).join("\n")}`;
|
|
11699
|
+
const raw = (await options.llmFn(eSys, eUsr)).trim();
|
|
11700
|
+
const match = raw.match(/\{[\s\S]*\}/);
|
|
11701
|
+
if (match) {
|
|
11702
|
+
const parsed = JSON.parse(match[0]);
|
|
11703
|
+
let upgraded = 0;
|
|
11704
|
+
for (const s of sessions) {
|
|
11705
|
+
const arr = parsed[s.id];
|
|
11706
|
+
if (Array.isArray(arr) && arr.length > 0 && arr.every((x) => typeof x === "string" && x.trim().length >= 8)) {
|
|
11707
|
+
s.exit_evidence = arr.map((x) => x.trim());
|
|
11708
|
+
upgraded++;
|
|
11709
|
+
}
|
|
11710
|
+
}
|
|
11711
|
+
if (upgraded < sessions.length) warnings.push(`exit_evidence_llm_partial:${upgraded}/${sessions.length}`);
|
|
11712
|
+
} else {
|
|
11713
|
+
warnings.push("exit_evidence_llm_unparseable");
|
|
11714
|
+
}
|
|
11715
|
+
} catch (e) {
|
|
11716
|
+
warnings.push("exit_evidence_llm_failed:" + (e instanceof Error ? e.message.slice(0, 80) : String(e).slice(0, 80)));
|
|
11717
|
+
}
|
|
11718
|
+
}
|
|
11667
11719
|
const glossaryScope = sessions.map((s) => ({
|
|
11668
11720
|
session_id: s.id,
|
|
11669
11721
|
terms: [...new Set(s.node_ids.flatMap((id) => nodeById.get(id).keywords))]
|
|
@@ -29951,14 +30003,14 @@ var DeterministicStructuralLinter = class {
|
|
|
29951
30003
|
let score = 100;
|
|
29952
30004
|
for (const s of plan.sessions) {
|
|
29953
30005
|
if (s.zpd_status?.verdict === "TOO_MANY_NEW") {
|
|
29954
|
-
score -=
|
|
30006
|
+
score -= 25;
|
|
29955
30007
|
findings.push({
|
|
29956
30008
|
id: `PLAN_TOO_MANY_NEW_${s.id}`,
|
|
29957
30009
|
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
29958
|
-
severity: "
|
|
30010
|
+
severity: "CRITICAL",
|
|
29959
30011
|
title: `Cognitive Overload in Session ${s.id}`,
|
|
29960
|
-
description: s.zpd_status.issues.join("; ") || `Session introduces
|
|
29961
|
-
remediationAdvice: `
|
|
30012
|
+
description: s.zpd_status.issues.join("; ") || `Session introduces more new concepts than the ZPD limit allows.`,
|
|
30013
|
+
remediationAdvice: `Split the session: move overflow concepts to a later session (the packer flushes on concept budget automatically).`,
|
|
29962
30014
|
affectedElement: s.id
|
|
29963
30015
|
});
|
|
29964
30016
|
} else if (s.zpd_status?.verdict === "NO_ZPD_BRIDGE") {
|
|
@@ -31985,6 +32037,7 @@ exports.buildStandardsContextBlock = buildStandardsContextBlock;
|
|
|
31985
32037
|
exports.buildTeacherGuidePrompt = buildTeacherGuidePrompt;
|
|
31986
32038
|
exports.buildWalkingSkeleton = buildWalkingSkeleton;
|
|
31987
32039
|
exports.buildWorksheetPrompt = buildWorksheetPrompt;
|
|
32040
|
+
exports.canCoalesceWithinZpdBudget = canCoalesceWithinZpdBudget;
|
|
31988
32041
|
exports.checkSessionZpd = checkSessionZpd;
|
|
31989
32042
|
exports.classifyDepthCandidates = classifyDepthCandidates;
|
|
31990
32043
|
exports.closeTruncatedJson = closeTruncatedJson;
|