@thanh01.pmt/curriculum-kit 1.4.52 → 1.4.53
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 +60 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.mjs +60 -16
- 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.d.cts
CHANGED
|
@@ -1145,6 +1145,10 @@ interface PackedSession {
|
|
|
1145
1145
|
knowledgeMinutes: number;
|
|
1146
1146
|
practiceMinutes: number;
|
|
1147
1147
|
oversized: boolean;
|
|
1148
|
+
/** Distinct NEW concepts counted against the ZPD budget at flush time —
|
|
1149
|
+
* product sessions legitimately restate already-taught concepts, so the
|
|
1150
|
+
* load that matters is what is unseen, not the concept_codes union. */
|
|
1151
|
+
cognitiveNew: number;
|
|
1148
1152
|
}
|
|
1149
1153
|
/**
|
|
1150
1154
|
* Coalesce eligibility (2026-09-18 regen4 audit): a merge must never break
|
|
@@ -1181,7 +1185,7 @@ declare function computeConceptSpiralProgression(sessions: Array<{
|
|
|
1181
1185
|
* - NO_ZPD_BRIDGE: Session index > 0 with >1 new concepts and 0 known concepts from prior sessions
|
|
1182
1186
|
* Self-healing: If NO_ZPD_BRIDGE, injects a recap anchor into prerequisite decisions.
|
|
1183
1187
|
*/
|
|
1184
|
-
declare function checkSessionZpd(sessionIndex: number, nodeIds: string[], nodeById: Map<string, PlanningNode>, allSeenConcepts: Set<string>, allSeenKeywords: Set<string>, entryKw: Set<string>, warnings: string[]): {
|
|
1188
|
+
declare function checkSessionZpd(sessionIndex: number, nodeIds: string[], nodeById: Map<string, PlanningNode>, allSeenConcepts: Set<string>, allSeenKeywords: Set<string>, entryKw: Set<string>, warnings: string[], conceptPrereqMap?: Map<string, string[]>): {
|
|
1185
1189
|
status: SessionZpdStatus;
|
|
1186
1190
|
bridgeRepair?: {
|
|
1187
1191
|
node_id: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1145,6 +1145,10 @@ interface PackedSession {
|
|
|
1145
1145
|
knowledgeMinutes: number;
|
|
1146
1146
|
practiceMinutes: number;
|
|
1147
1147
|
oversized: boolean;
|
|
1148
|
+
/** Distinct NEW concepts counted against the ZPD budget at flush time —
|
|
1149
|
+
* product sessions legitimately restate already-taught concepts, so the
|
|
1150
|
+
* load that matters is what is unseen, not the concept_codes union. */
|
|
1151
|
+
cognitiveNew: number;
|
|
1148
1152
|
}
|
|
1149
1153
|
/**
|
|
1150
1154
|
* Coalesce eligibility (2026-09-18 regen4 audit): a merge must never break
|
|
@@ -1181,7 +1185,7 @@ declare function computeConceptSpiralProgression(sessions: Array<{
|
|
|
1181
1185
|
* - NO_ZPD_BRIDGE: Session index > 0 with >1 new concepts and 0 known concepts from prior sessions
|
|
1182
1186
|
* Self-healing: If NO_ZPD_BRIDGE, injects a recap anchor into prerequisite decisions.
|
|
1183
1187
|
*/
|
|
1184
|
-
declare function checkSessionZpd(sessionIndex: number, nodeIds: string[], nodeById: Map<string, PlanningNode>, allSeenConcepts: Set<string>, allSeenKeywords: Set<string>, entryKw: Set<string>, warnings: string[]): {
|
|
1188
|
+
declare function checkSessionZpd(sessionIndex: number, nodeIds: string[], nodeById: Map<string, PlanningNode>, allSeenConcepts: Set<string>, allSeenKeywords: Set<string>, entryKw: Set<string>, warnings: string[], conceptPrereqMap?: Map<string, string[]>): {
|
|
1185
1189
|
status: SessionZpdStatus;
|
|
1186
1190
|
bridgeRepair?: {
|
|
1187
1191
|
node_id: string;
|
package/dist/index.mjs
CHANGED
|
@@ -11793,10 +11793,16 @@ function canCoalesceWithinZpdBudget(curr, next, nodeById, maxNew) {
|
|
|
11793
11793
|
}
|
|
11794
11794
|
function packUnitSessions(group, nodeById, spec, warnings) {
|
|
11795
11795
|
const sessions = [];
|
|
11796
|
-
const fresh = () => ({ groupId: group.key, nodeIds: [], entries: [], knowledgeMinutes: 0, practiceMinutes: 0, oversized: false });
|
|
11796
|
+
const fresh = () => ({ groupId: group.key, nodeIds: [], entries: [], knowledgeMinutes: 0, practiceMinutes: 0, oversized: false, cognitiveNew: 0 });
|
|
11797
11797
|
let current = fresh();
|
|
11798
|
+
const seenConcepts = /* @__PURE__ */ new Set();
|
|
11798
11799
|
const flush = () => {
|
|
11799
|
-
if (current.entries.length > 0)
|
|
11800
|
+
if (current.entries.length > 0) {
|
|
11801
|
+
for (const nid of current.nodeIds) {
|
|
11802
|
+
for (const c of nodeById.get(nid)?.concept_codes || []) seenConcepts.add(c);
|
|
11803
|
+
}
|
|
11804
|
+
sessions.push(current);
|
|
11805
|
+
}
|
|
11800
11806
|
current = fresh();
|
|
11801
11807
|
};
|
|
11802
11808
|
const filled = () => current.knowledgeMinutes + current.practiceMinutes;
|
|
@@ -11838,14 +11844,11 @@ function packUnitSessions(group, nodeById, spec, warnings) {
|
|
|
11838
11844
|
continue;
|
|
11839
11845
|
}
|
|
11840
11846
|
const minutes = node.estimated_minutes;
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
warnings.push(`ZPD pack flush: session ${group.key}#${sessions.length + 1} cut at ${currentConcepts.size} new concepts (budget ${spec.maxNewConceptsPerSession})`);
|
|
11847
|
-
flush();
|
|
11848
|
-
}
|
|
11847
|
+
const localConcepts = new Set(current.nodeIds.flatMap((nid) => nodeById.get(nid)?.concept_codes || []));
|
|
11848
|
+
const nodeNew = node.concept_codes.filter((c) => !seenConcepts.has(c) && !localConcepts.has(c)).length;
|
|
11849
|
+
if (spec.maxNewConceptsPerSession && spec.maxNewConceptsPerSession > 0 && current.entries.length > 0 && current.cognitiveNew + nodeNew > spec.maxNewConceptsPerSession) {
|
|
11850
|
+
warnings.push(`ZPD pack flush: session ${group.key}#${sessions.length + 1} cut at ${current.cognitiveNew} new concepts (budget ${spec.maxNewConceptsPerSession})`);
|
|
11851
|
+
flush();
|
|
11849
11852
|
}
|
|
11850
11853
|
if (current.entries.length > 0 && filled() + minutes > spec.contentBudget) {
|
|
11851
11854
|
const displaced = cutBackToCheckpoint(current, nodeById, spec, warnings);
|
|
@@ -11857,11 +11860,33 @@ function packUnitSessions(group, nodeById, spec, warnings) {
|
|
|
11857
11860
|
}
|
|
11858
11861
|
flush();
|
|
11859
11862
|
}
|
|
11860
|
-
if (isKnowledge)
|
|
11861
|
-
|
|
11863
|
+
if (isKnowledge) {
|
|
11864
|
+
current.knowledgeMinutes += minutes;
|
|
11865
|
+
current.cognitiveNew += nodeNew;
|
|
11866
|
+
} else {
|
|
11867
|
+
current.practiceMinutes += minutes;
|
|
11868
|
+
current.cognitiveNew += nodeNew;
|
|
11869
|
+
}
|
|
11862
11870
|
current.entries.push({ id, minutes, part: 1, totalParts: 1 });
|
|
11863
11871
|
current.nodeIds.push(id);
|
|
11864
|
-
if (filled() >= spec.contentBudget && current.entries.length >= MAX_SESSION_NODES)
|
|
11872
|
+
if (filled() >= spec.contentBudget && current.entries.length >= MAX_SESSION_NODES) {
|
|
11873
|
+
flush();
|
|
11874
|
+
} else if (!isKnowledge && current.entries.length < MAX_SESSION_NODES) {
|
|
11875
|
+
while (ids.length > 0 && current.entries.length < MAX_SESSION_NODES) {
|
|
11876
|
+
const nxtId = ids[0];
|
|
11877
|
+
const nxt = nodeById.get(nxtId);
|
|
11878
|
+
if (!nxt || nxt.kind !== "concept" || nxt.estimated_minutes > spec.contentBudget) break;
|
|
11879
|
+
const nxtLocal = new Set(current.nodeIds.flatMap((nid) => nodeById.get(nid)?.concept_codes || []));
|
|
11880
|
+
const nxtNew = nxt.concept_codes.filter((c) => !seenConcepts.has(c) && !nxtLocal.has(c)).length;
|
|
11881
|
+
if (spec.maxNewConceptsPerSession && spec.maxNewConceptsPerSession > 0 && current.cognitiveNew + nxtNew > spec.maxNewConceptsPerSession) break;
|
|
11882
|
+
if (filled() + nxt.estimated_minutes > spec.contentBudget) break;
|
|
11883
|
+
ids.shift();
|
|
11884
|
+
current.knowledgeMinutes += nxt.estimated_minutes;
|
|
11885
|
+
current.cognitiveNew += nxtNew;
|
|
11886
|
+
current.entries.push({ id: nxtId, minutes: nxt.estimated_minutes, part: 1, totalParts: 1 });
|
|
11887
|
+
current.nodeIds.push(nxtId);
|
|
11888
|
+
}
|
|
11889
|
+
}
|
|
11865
11890
|
}
|
|
11866
11891
|
flush();
|
|
11867
11892
|
return sessions;
|
|
@@ -11996,7 +12021,7 @@ function computeConceptSpiralProgression(sessions, nodeById) {
|
|
|
11996
12021
|
}
|
|
11997
12022
|
return encounters;
|
|
11998
12023
|
}
|
|
11999
|
-
function checkSessionZpd(sessionIndex, nodeIds, nodeById, allSeenConcepts, allSeenKeywords, entryKw, warnings) {
|
|
12024
|
+
function checkSessionZpd(sessionIndex, nodeIds, nodeById, allSeenConcepts, allSeenKeywords, entryKw, warnings, conceptPrereqMap) {
|
|
12000
12025
|
const concepts = [...new Set(nodeIds.flatMap((id) => nodeById.get(id)?.concept_codes || []))];
|
|
12001
12026
|
const newConcepts = concepts.filter((c) => !allSeenConcepts.has(c));
|
|
12002
12027
|
const knownConcepts = concepts.filter((c) => allSeenConcepts.has(c));
|
|
@@ -12015,7 +12040,15 @@ function checkSessionZpd(sessionIndex, nodeIds, nodeById, allSeenConcepts, allSe
|
|
|
12015
12040
|
if (sessionIndex > 0) {
|
|
12016
12041
|
if (concepts.length > 1 && knownConcepts.length === 0) {
|
|
12017
12042
|
if (verdict !== "TOO_MANY_NEW") verdict = "NO_ZPD_BRIDGE";
|
|
12018
|
-
|
|
12043
|
+
const seenList = [...allSeenConcepts];
|
|
12044
|
+
const prevConcept = seenList[seenList.length - 1];
|
|
12045
|
+
const bridgedByPrereq = prevConcept !== void 0 && concepts.some((c) => (conceptPrereqMap?.get(c) || []).includes(prevConcept));
|
|
12046
|
+
if (bridgedByPrereq) {
|
|
12047
|
+
verdict = verdict === "TOO_MANY_NEW" ? verdict : "OK";
|
|
12048
|
+
issues.push(`Bridge satisfied via prerequisite: prior session ended with recap of ${prevConcept}, a declared prerequisite of this session's concepts`);
|
|
12049
|
+
} else {
|
|
12050
|
+
issues.push("No known concepts from previous sessions to bridge new learning");
|
|
12051
|
+
}
|
|
12019
12052
|
} else if (concepts.length === 0 && allKeywords.length > 2 && knownKeywords.length === 0) {
|
|
12020
12053
|
if (verdict !== "TOO_MANY_NEW") verdict = "NO_ZPD_BRIDGE";
|
|
12021
12054
|
issues.push("No known keywords from previous sessions to bridge new learning");
|
|
@@ -12272,6 +12305,17 @@ async function buildCurriculumPlan(rawGraph, options) {
|
|
|
12272
12305
|
const allSeenConcepts = /* @__PURE__ */ new Set();
|
|
12273
12306
|
const allSeenKeywords = /* @__PURE__ */ new Set();
|
|
12274
12307
|
const conceptEncounterMap = /* @__PURE__ */ new Map();
|
|
12308
|
+
const conceptPrereqMap = /* @__PURE__ */ new Map();
|
|
12309
|
+
for (const e of graph.edges) {
|
|
12310
|
+
for (const to of nodeById.get(e.to)?.concept_codes || []) {
|
|
12311
|
+
for (const from of nodeById.get(e.from)?.concept_codes || []) {
|
|
12312
|
+
if (to === from) continue;
|
|
12313
|
+
const list = conceptPrereqMap.get(to) || [];
|
|
12314
|
+
if (!list.includes(from)) list.push(from);
|
|
12315
|
+
conceptPrereqMap.set(to, list);
|
|
12316
|
+
}
|
|
12317
|
+
}
|
|
12318
|
+
}
|
|
12275
12319
|
for (let i = 0; i < packed.length; i++) {
|
|
12276
12320
|
const s = packed[i];
|
|
12277
12321
|
for (const id of s.nodeIds) {
|
|
@@ -12325,7 +12369,7 @@ async function buildCurriculumPlan(rawGraph, options) {
|
|
|
12325
12369
|
const curCount = (conceptEncounterMap.get(cCode) || 0) + 1;
|
|
12326
12370
|
conceptEncounterMap.set(cCode, curCount);
|
|
12327
12371
|
}
|
|
12328
|
-
const zpdResult = checkSessionZpd(i, s.nodeIds, nodeById, allSeenConcepts, allSeenKeywords, entryKw, warnings);
|
|
12372
|
+
const zpdResult = checkSessionZpd(i, s.nodeIds, nodeById, allSeenConcepts, allSeenKeywords, entryKw, warnings, conceptPrereqMap);
|
|
12329
12373
|
const prereqDecisions = decidePrerequisites(ctx, nodeById, graph.edges, sessionIndexByNode, sessionOrderByIndex, constraints);
|
|
12330
12374
|
if (zpdResult.bridgeRepair) {
|
|
12331
12375
|
prereqDecisions.unshift(zpdResult.bridgeRepair);
|