@thanh01.pmt/domain-kit 0.1.0 → 0.2.1

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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{chunk-GEUDHZE7.mjs → chunk-23TQPTHG.mjs} +173 -4
  3. package/dist/chunk-23TQPTHG.mjs.map +1 -0
  4. package/dist/chunk-7XGWW6CB.mjs +3 -0
  5. package/dist/{chunk-F4RZNBOE.mjs.map → chunk-7XGWW6CB.mjs.map} +1 -1
  6. package/dist/{chunk-VFART56O.mjs → chunk-BL5NBKPH.mjs} +12 -8
  7. package/dist/chunk-BL5NBKPH.mjs.map +1 -0
  8. package/dist/{chunk-3VVPSRAM.mjs → chunk-CRN6D4HG.mjs} +1056 -92
  9. package/dist/chunk-CRN6D4HG.mjs.map +1 -0
  10. package/dist/{chunk-3H2HX7PR.mjs → chunk-HUDFN4IX.mjs} +58 -8
  11. package/dist/chunk-HUDFN4IX.mjs.map +1 -0
  12. package/dist/chunk-IJZUV7N3.mjs +3 -0
  13. package/dist/chunk-IJZUV7N3.mjs.map +1 -0
  14. package/dist/chunk-R57WTNXW.mjs +3 -0
  15. package/dist/chunk-R57WTNXW.mjs.map +1 -0
  16. package/dist/chunk-T4Y2DE2W.mjs +187 -0
  17. package/dist/chunk-T4Y2DE2W.mjs.map +1 -0
  18. package/dist/{chunk-PM42MMDJ.mjs → chunk-WHBAGJWN.mjs} +48 -3
  19. package/dist/chunk-WHBAGJWN.mjs.map +1 -0
  20. package/dist/chunk-ZKLT27T4.mjs +3 -0
  21. package/dist/chunk-ZKLT27T4.mjs.map +1 -0
  22. package/dist/concepts/index.d.cts +2 -2
  23. package/dist/concepts/index.d.ts +2 -2
  24. package/dist/cpp-CZUPqJt9.d.cts +98 -0
  25. package/dist/cpp-CZUPqJt9.d.ts +98 -0
  26. package/dist/curriculumFeedEmitter-E5DAJYUZ.mjs +4 -0
  27. package/dist/curriculumFeedEmitter-E5DAJYUZ.mjs.map +1 -0
  28. package/dist/detector/index.cjs +10 -6
  29. package/dist/detector/index.cjs.map +1 -1
  30. package/dist/detector/index.d.cts +4 -19
  31. package/dist/detector/index.d.ts +4 -19
  32. package/dist/detector/index.mjs +2 -1
  33. package/dist/domainProfileDetector-DHb1T0zB.d.ts +22 -0
  34. package/dist/domainProfileDetector-bzugdQmS.d.cts +22 -0
  35. package/dist/extractors/index.d.cts +3 -3
  36. package/dist/extractors/index.d.ts +3 -3
  37. package/dist/feed/index.cjs +56 -6
  38. package/dist/feed/index.cjs.map +1 -1
  39. package/dist/feed/index.mjs +2 -1
  40. package/dist/graph/index.cjs +370 -66
  41. package/dist/graph/index.cjs.map +1 -1
  42. package/dist/graph/index.d.cts +7 -67
  43. package/dist/graph/index.d.ts +7 -67
  44. package/dist/graph/index.mjs +3 -2
  45. package/dist/hybridGraphPipeline-DVZYLCAQ.d.ts +159 -0
  46. package/dist/hybridGraphPipeline-DmWRKYXf.d.cts +159 -0
  47. package/dist/index.cjs +1303 -700
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +9 -7
  50. package/dist/index.d.ts +9 -7
  51. package/dist/index.mjs +10 -7
  52. package/dist/{keywordExtractor-zPAz2isq.d.cts → keywordExtractor-BxlGRMHC.d.cts} +1 -1
  53. package/dist/{keywordExtractor-CKaXqSku.d.ts → keywordExtractor-DU5XRN8-.d.ts} +1 -1
  54. package/dist/parsers/index.cjs +185 -0
  55. package/dist/parsers/index.cjs.map +1 -1
  56. package/dist/parsers/index.d.cts +2 -98
  57. package/dist/parsers/index.d.ts +2 -98
  58. package/dist/parsers/index.mjs +2 -1
  59. package/dist/pipeline/index.cjs +3142 -8
  60. package/dist/pipeline/index.cjs.map +1 -1
  61. package/dist/pipeline/index.d.cts +87 -4
  62. package/dist/pipeline/index.d.ts +87 -4
  63. package/dist/pipeline/index.mjs +6 -2
  64. package/dist/schemas/index.cjs +51 -0
  65. package/dist/schemas/index.cjs.map +1 -1
  66. package/dist/schemas/index.d.cts +222 -3
  67. package/dist/schemas/index.d.ts +222 -3
  68. package/dist/schemas/index.mjs +1 -1
  69. package/dist/syllabusParser-MSX1fEzW.d.cts +40 -0
  70. package/dist/syllabusParser-MSX1fEzW.d.ts +40 -0
  71. package/package.json +9 -10
  72. package/dist/chunk-3H2HX7PR.mjs.map +0 -1
  73. package/dist/chunk-3VVPSRAM.mjs.map +0 -1
  74. package/dist/chunk-F4RZNBOE.mjs +0 -3
  75. package/dist/chunk-GEUDHZE7.mjs.map +0 -1
  76. package/dist/chunk-MOKNFLUD.mjs +0 -851
  77. package/dist/chunk-MOKNFLUD.mjs.map +0 -1
  78. package/dist/chunk-PM42MMDJ.mjs.map +0 -1
  79. package/dist/chunk-VFART56O.mjs.map +0 -1
  80. package/dist/graphVerifier-DsTP9uAN.d.ts +0 -37
  81. package/dist/graphVerifier-QjgDAJce.d.cts +0 -37
@@ -25,8 +25,8 @@ function resolveProviderChain(config) {
25
25
  }
26
26
  push("legacy", process.env.LLM_BASE_URL || process.env.OPENAI_BASE_URL || "", process.env.LLM_API_KEY || process.env.OPENAI_API_KEY || "", process.env.LLM_MODEL || process.env.LLM_TIER_FAST || "");
27
27
  push("dashscope", process.env.DASHSCOPE_BASE_URL || process.env.ALIBABA_BASE_URL || "", process.env.DASHSCOPE_API_KEY || process.env.ALIBABA_API_KEY || "", process.env.DASHSCOPE_MODEL || process.env.DEFAULT_AI_MODEL || "");
28
- push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b");
29
- push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b");
28
+ push("nvidia", "https://integrate.api.nvidia.com/v1", process.env.NVIDIA_API_KEY || "", process.env.NVIDIA_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
29
+ push("openrouter", "https://openrouter.ai/api/v1", process.env.OPENROUTER_API_KEY || "", process.env.OPENROUTER_MODEL || "nvidia/nemotron-3-ultra-550b-a55b:free");
30
30
  if (process.env.OPENROUTER_MODEL) {
31
31
  const or = chain.find((c) => c.label === "openrouter");
32
32
  if (or) {
@@ -968,6 +968,345 @@ var ULO_CODE_RE = /^ULO-[A-Z][A-Z0-9_]*-\d{2}$/;
968
968
  var CIO_CODE_RE = /^CIO-[A-Z][A-Z0-9_]*-\d{2}-[A-Z][A-Z0-9_]*$/;
969
969
  var SIO_CODE_RE = /^SIO-[A-Z0-9]+-[A-Z][A-Z0-9_]*-\d{2}$/;
970
970
 
971
+ // src/parsers/syllabusParser.ts
972
+ var STOP_WORDS = /* @__PURE__ */ new Set([
973
+ "and",
974
+ "the",
975
+ "for",
976
+ "with",
977
+ "from",
978
+ "that",
979
+ "this",
980
+ "into",
981
+ "when",
982
+ "while",
983
+ "using",
984
+ "their",
985
+ "your",
986
+ "each",
987
+ "how",
988
+ "why",
989
+ "what",
990
+ "between",
991
+ "through",
992
+ "about",
993
+ "trong",
994
+ "nhung",
995
+ "cac",
996
+ "cua",
997
+ "cho",
998
+ "voi",
999
+ "duoc",
1000
+ "khong",
1001
+ "theo",
1002
+ "tren",
1003
+ "duoi",
1004
+ "va",
1005
+ "hoac",
1006
+ "thi",
1007
+ "la",
1008
+ "nhu"
1009
+ ]);
1010
+ function extractTokens(text) {
1011
+ const words = text.toLowerCase().replace(/[^a-z0-9à-ỹ\s-]/gi, " ").split(/\s+/).filter((w) => w.length >= 3 && !STOP_WORDS.has(w));
1012
+ return [...new Set(words)];
1013
+ }
1014
+ function extractGradeBand(text) {
1015
+ const gbMatch = text.match(/(?:grade\s*band|độ\s*tuổi|lớp)\s*[:=]?\s*(?:lớp\s*)?(\d{1,2})\s*(?:-|đến|to)\s*(?:lớp\s*)?(\d{1,2})/i);
1016
+ if (gbMatch && gbMatch[1] && gbMatch[2]) {
1017
+ const min = parseInt(gbMatch[1], 10);
1018
+ const max = parseInt(gbMatch[2], 10);
1019
+ return [Math.min(min, max), Math.max(min, max)];
1020
+ }
1021
+ return void 0;
1022
+ }
1023
+ function extractDomain(text) {
1024
+ const lower = text.toLowerCase();
1025
+ if (lower.includes("computer science") || lower.includes("l\u1EADp tr\xECnh") || lower.includes("algorithm") || lower.includes("software")) return "software";
1026
+ if (lower.includes("to\xE1n") || lower.includes("geometry") || lower.includes("algebra") || lower.includes("h\xECnh h\u1ECDc") || lower.includes("mathematics")) return "math";
1027
+ if (lower.includes("v\u1EADt l\xFD") || lower.includes("h\xF3a h\u1ECDc") || lower.includes("sinh h\u1ECDc") || lower.includes("physics") || lower.includes("chemistry") || lower.includes("biology") || lower.includes("science")) return "science";
1028
+ if (lower.includes("ti\u1EBFng") || lower.includes("ng\u1EEF v\u0103n") || lower.includes("grammar") || lower.includes("language") || lower.includes("writing") || lower.includes("literature")) return "language";
1029
+ return void 0;
1030
+ }
1031
+ function parseSyllabus(rawText) {
1032
+ const lines = rawText.split(/\r?\n/);
1033
+ let subjectTitle = "";
1034
+ let subjectDesc = "";
1035
+ const units = [];
1036
+ const allTopics = [];
1037
+ const globalKeywordsSet = /* @__PURE__ */ new Set();
1038
+ let currentUnit = null;
1039
+ let currentTopic = null;
1040
+ let unitIndex = 0;
1041
+ let topicIndex = 0;
1042
+ for (let i = 0; i < lines.length; i++) {
1043
+ const line = lines[i].trim();
1044
+ const lineNum = i + 1;
1045
+ if (!line) continue;
1046
+ if (line.startsWith("# ") && !subjectTitle) {
1047
+ subjectTitle = line.replace(/^#\s+/, "").trim();
1048
+ continue;
1049
+ }
1050
+ if (!currentUnit) {
1051
+ if (line.toLowerCase().startsWith("m\xF4n h\u1ECDc:") || line.toLowerCase().startsWith("subject:")) {
1052
+ const val = line.split(":")[1]?.trim() || "";
1053
+ if (val && !subjectTitle) subjectTitle = val;
1054
+ }
1055
+ if (line.toLowerCase().startsWith("m\xF4 t\u1EA3:") || line.toLowerCase().startsWith("description:")) {
1056
+ subjectDesc = line.split(":")[1]?.trim() || "";
1057
+ }
1058
+ }
1059
+ const isUnitHeading = /^##\s+(?:chương|module|unit|phần|chủ đề)\s*(\d+|[ivx]+)?[:.-]?\s*(.*)/i.test(line) || /^##\s+([^#].*)/.test(line);
1060
+ if (isUnitHeading) {
1061
+ if (currentTopic) {
1062
+ currentTopic.rawLineEnd = lineNum - 1;
1063
+ currentTopic = null;
1064
+ }
1065
+ unitIndex += 1;
1066
+ const titleMatch = line.replace(/^##\s+/, "").trim();
1067
+ currentUnit = {
1068
+ id: `U${String(unitIndex).padStart(2, "0")}`,
1069
+ title: titleMatch,
1070
+ order: unitIndex,
1071
+ topics: []
1072
+ };
1073
+ units.push(currentUnit);
1074
+ continue;
1075
+ }
1076
+ const isTopicItem = /^(?:-\s*\*\*(?:bài|topic|lesson|chủ đề)\s*(\d+(?:\.\d+)?|\w+)?[:.-]?\s*(.*?)\*\*)/i.exec(line) || /^###\s+(?:bài|topic|lesson)?\s*(\d+(?:\.\d+)?|\w+)?[:.-]?\s*(.*)/i.exec(line) || /^-\s+\*\*(.*?)\*\*/.exec(line) || /^(\d+)\.\s+(?:(?:\*\*)?(?:bài|topic|lesson)?\s*(\d+(?:\.\d+)?|\w+)?[:.-]?\s*(.*?)(?:\*\*)?)$/i.exec(line);
1077
+ if (isTopicItem) {
1078
+ if (currentTopic) {
1079
+ currentTopic.rawLineEnd = lineNum - 1;
1080
+ }
1081
+ if (!currentUnit) {
1082
+ unitIndex += 1;
1083
+ currentUnit = {
1084
+ id: `U${String(unitIndex).padStart(2, "0")}`,
1085
+ title: subjectTitle || "General",
1086
+ order: unitIndex,
1087
+ topics: []
1088
+ };
1089
+ units.push(currentUnit);
1090
+ }
1091
+ topicIndex += 1;
1092
+ const rawName = (isTopicItem[3] || isTopicItem[2] || isTopicItem[1] || `Topic ${topicIndex}`).trim();
1093
+ currentTopic = {
1094
+ id: `T${String(topicIndex).padStart(2, "0")}`,
1095
+ title: rawName,
1096
+ unitId: currentUnit.id,
1097
+ unitTitle: currentUnit.title,
1098
+ description: "",
1099
+ learningObjectives: [],
1100
+ keywords: [],
1101
+ rawLineStart: lineNum,
1102
+ rawLineEnd: lineNum
1103
+ };
1104
+ currentUnit.topics.push(currentTopic);
1105
+ allTopics.push(currentTopic);
1106
+ continue;
1107
+ }
1108
+ if (currentTopic) {
1109
+ currentTopic.rawLineEnd = lineNum;
1110
+ const stripped = line.replace(/^[-*•\s]+/, "").trim();
1111
+ if (stripped.toLowerCase().startsWith("m\u1EE5c ti\xEAu:") || stripped.toLowerCase().startsWith("objectives:")) {
1112
+ const content = stripped.replace(/^(?:mục tiêu|objectives):\s*/i, "").trim();
1113
+ if (content) currentTopic.learningObjectives.push(content);
1114
+ continue;
1115
+ }
1116
+ if (stripped.toLowerCase().startsWith("t\u1EEB kh\xF3a:") || stripped.toLowerCase().startsWith("keywords:")) {
1117
+ const rawKw = stripped.replace(/^(?:từ khóa|keywords):\s*/i, "").trim();
1118
+ const tokens2 = rawKw.split(/[,;]+/).map((k) => k.trim()).filter(Boolean);
1119
+ for (const tok of tokens2) {
1120
+ currentTopic.keywords.push(tok);
1121
+ globalKeywordsSet.add(tok);
1122
+ }
1123
+ continue;
1124
+ }
1125
+ if (currentTopic.description) {
1126
+ currentTopic.description += " " + stripped;
1127
+ } else {
1128
+ currentTopic.description = stripped;
1129
+ }
1130
+ for (const tok of extractTokens(stripped)) {
1131
+ if (tok.length >= 4) globalKeywordsSet.add(tok);
1132
+ }
1133
+ }
1134
+ }
1135
+ if (currentTopic) {
1136
+ currentTopic.rawLineEnd = lines.length;
1137
+ }
1138
+ if (!subjectTitle && units.length > 0) {
1139
+ subjectTitle = units[0]?.title || "Untitled Subject";
1140
+ }
1141
+ return {
1142
+ subject: {
1143
+ title: subjectTitle || "Untitled Subject",
1144
+ domain: extractDomain(rawText),
1145
+ gradeBand: extractGradeBand(rawText),
1146
+ description: subjectDesc
1147
+ },
1148
+ units,
1149
+ allTopics,
1150
+ extractedKeywords: [...globalKeywordsSet],
1151
+ rawText
1152
+ };
1153
+ }
1154
+
1155
+ // src/graph/knowledgeGraphVerifier.ts
1156
+ function detectPrerequisiteCycles(concepts) {
1157
+ const cycles = [];
1158
+ const visited = /* @__PURE__ */ new Set();
1159
+ const inStack = /* @__PURE__ */ new Set();
1160
+ const path = [];
1161
+ const prereqMap = /* @__PURE__ */ new Map();
1162
+ for (const c of concepts) {
1163
+ prereqMap.set(c.id, c.prerequisites || []);
1164
+ }
1165
+ function dfs(conceptId) {
1166
+ if (inStack.has(conceptId)) {
1167
+ const cycleStart = path.indexOf(conceptId);
1168
+ if (cycleStart >= 0) {
1169
+ cycles.push([...path.slice(cycleStart), conceptId]);
1170
+ }
1171
+ return;
1172
+ }
1173
+ if (visited.has(conceptId)) return;
1174
+ visited.add(conceptId);
1175
+ inStack.add(conceptId);
1176
+ path.push(conceptId);
1177
+ for (const prereq of prereqMap.get(conceptId) || []) {
1178
+ dfs(prereq);
1179
+ }
1180
+ path.pop();
1181
+ inStack.delete(conceptId);
1182
+ }
1183
+ for (const c of concepts) {
1184
+ if (!visited.has(c.id)) {
1185
+ dfs(c.id);
1186
+ }
1187
+ }
1188
+ return cycles;
1189
+ }
1190
+ function breakPrerequisiteCycles(concepts, cycles) {
1191
+ const removed = [];
1192
+ const fixed = concepts.map((c) => ({ ...c, prerequisites: [...c.prerequisites || []] }));
1193
+ for (const cycle of cycles) {
1194
+ if (cycle.length >= 2) {
1195
+ const breaker = cycle[cycle.length - 2];
1196
+ const target = cycle[cycle.length - 1];
1197
+ for (const concept of fixed) {
1198
+ if (concept.id === breaker && target) {
1199
+ concept.prerequisites = concept.prerequisites.filter((p) => p !== target);
1200
+ removed.push({ from: breaker, to: target });
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ return { fixed, removed };
1206
+ }
1207
+ function auditSyllabusCoverage(concepts, syllabus) {
1208
+ if (!syllabus || syllabus.allTopics.length === 0) {
1209
+ return { coverageRatio: 1, totalTopics: 0, coveredTopics: 0, uncovered: [] };
1210
+ }
1211
+ const topics = syllabus.allTopics;
1212
+ const coveredSet = /* @__PURE__ */ new Set();
1213
+ for (const topic of topics) {
1214
+ const topicKeywords = /* @__PURE__ */ new Set([
1215
+ ...topic.title.toLowerCase().split(/\s+/),
1216
+ ...(topic.keywords || []).map((k) => k.toLowerCase())
1217
+ ]);
1218
+ for (const concept of concepts) {
1219
+ concept.name.toLowerCase();
1220
+ (concept.description || "").toLowerCase();
1221
+ (concept.keywords || []).map((k) => k.toLowerCase());
1222
+ if (concept.id.toLowerCase() === topic.id.toLowerCase()) {
1223
+ coveredSet.add(topic.id);
1224
+ break;
1225
+ }
1226
+ const refs = concept.references || [];
1227
+ const hasTopicRef = refs.some(
1228
+ (r) => r.id === topic.id || r.title && r.title.toLowerCase() === topic.title.toLowerCase()
1229
+ );
1230
+ if (hasTopicRef) {
1231
+ coveredSet.add(topic.id);
1232
+ break;
1233
+ }
1234
+ const conceptKeywords = (concept.keywords || []).map((k) => k.toLowerCase());
1235
+ const hasOverlap = conceptKeywords.some((k) => topicKeywords.has(k)) || concept.name.toLowerCase().includes(topic.title.toLowerCase()) || topic.title.toLowerCase().includes(concept.name.toLowerCase());
1236
+ if (hasOverlap) {
1237
+ coveredSet.add(topic.id);
1238
+ break;
1239
+ }
1240
+ }
1241
+ }
1242
+ const totalTopics = topics.length;
1243
+ const coveredTopics = coveredSet.size;
1244
+ const coverageRatio = totalTopics > 0 ? coveredTopics / totalTopics : 1;
1245
+ const uncovered = topics.filter((t) => !coveredSet.has(t.id)).map((t) => `${t.id}: ${t.title}`);
1246
+ return { coverageRatio, totalTopics, coveredTopics, uncovered };
1247
+ }
1248
+ function verifyKnowledgeGraph(kg, syllabusOrOptions) {
1249
+ const syllabus = syllabusOrOptions && "parsedSyllabus" in syllabusOrOptions ? syllabusOrOptions.parsedSyllabus : syllabusOrOptions;
1250
+ const warnings = [];
1251
+ let concepts = [...kg.concepts || []];
1252
+ const conceptIds = new Set(concepts.map((c) => c.id));
1253
+ for (const c of concepts) {
1254
+ if ((c.prerequisites || []).includes(c.id)) {
1255
+ c.prerequisites = (c.prerequisites || []).filter((p) => p !== c.id);
1256
+ warnings.push(`Self-loop removed on concept ${c.id}`);
1257
+ }
1258
+ }
1259
+ const danglingPrerequisites = [];
1260
+ for (const c of concepts) {
1261
+ const validPrereqs = [];
1262
+ for (const p of c.prerequisites || []) {
1263
+ if (!conceptIds.has(p)) {
1264
+ danglingPrerequisites.push({ conceptId: c.id, missingPrereqId: p });
1265
+ warnings.push(`Dangling prerequisite removed: ${c.id} -> ${p}`);
1266
+ } else {
1267
+ validPrereqs.push(p);
1268
+ }
1269
+ }
1270
+ c.prerequisites = validPrereqs;
1271
+ }
1272
+ const initialCycles = detectPrerequisiteCycles(concepts);
1273
+ let removedEdges = [];
1274
+ if (initialCycles.length > 0) {
1275
+ const broken = breakPrerequisiteCycles(concepts, initialCycles);
1276
+ concepts = broken.fixed;
1277
+ removedEdges = broken.removed;
1278
+ for (const r of removedEdges) {
1279
+ warnings.push(`Cycle broken: removed edge ${r.from} -> ${r.to}`);
1280
+ }
1281
+ }
1282
+ const coverage = auditSyllabusCoverage(concepts, syllabus);
1283
+ if (coverage.coverageRatio < 1) {
1284
+ warnings.push(`Syllabus coverage ${(coverage.coverageRatio * 100).toFixed(1)}% (${coverage.coveredTopics}/${coverage.totalTopics} topics). Uncovered: ${coverage.uncovered.join(", ")}`);
1285
+ }
1286
+ for (const c of concepts) {
1287
+ if (!c.ulo) warnings.push(`Concept ${c.id} missing ULO (WHAT + WHY)`);
1288
+ if (!c.cio) warnings.push(`Concept ${c.id} missing CIO (HOW mechanism)`);
1289
+ if (!c.sio || c.sio.length === 0) warnings.push(`Concept ${c.id} missing SIO implementation items`);
1290
+ }
1291
+ const valid = initialCycles.length === 0 && danglingPrerequisites.length === 0 && coverage.coverageRatio >= 0.8;
1292
+ const verifiedGraph = {
1293
+ ...kg,
1294
+ concepts
1295
+ };
1296
+ const report = {
1297
+ valid,
1298
+ cycles: initialCycles,
1299
+ removedEdges,
1300
+ danglingPrerequisites,
1301
+ coverageRatio: coverage.coverageRatio,
1302
+ totalSyllabusTopics: coverage.totalTopics,
1303
+ coveredSyllabusTopics: coverage.coveredTopics,
1304
+ uncoveredTopics: coverage.uncovered,
1305
+ warnings
1306
+ };
1307
+ return { verifiedGraph, report };
1308
+ }
1309
+
971
1310
  // src/graph/loExemplars.ts
972
1311
  var ULO_EXEMPLARS = [
973
1312
  // Calibrated against production learning_objectives (UNIVERSAL rows, neutral)
@@ -1565,58 +1904,6 @@ async function standardizeConcepts(opts) {
1565
1904
  }
1566
1905
 
1567
1906
  // src/graph/knowledgeGraphPipeline.ts
1568
- function detectCycles(concepts) {
1569
- const cycles = [];
1570
- const visited = /* @__PURE__ */ new Set();
1571
- const inStack = /* @__PURE__ */ new Set();
1572
- const path = [];
1573
- function dfs(conceptId, prereqMap2) {
1574
- if (inStack.has(conceptId)) {
1575
- const cycleStart = path.indexOf(conceptId);
1576
- if (cycleStart >= 0) {
1577
- cycles.push([...path.slice(cycleStart), conceptId]);
1578
- }
1579
- return;
1580
- }
1581
- if (visited.has(conceptId)) return;
1582
- visited.add(conceptId);
1583
- inStack.add(conceptId);
1584
- path.push(conceptId);
1585
- const prereqs = prereqMap2.get(conceptId) || [];
1586
- for (const prereq of prereqs) {
1587
- dfs(prereq, prereqMap2);
1588
- }
1589
- path.pop();
1590
- inStack.delete(conceptId);
1591
- }
1592
- const prereqMap = /* @__PURE__ */ new Map();
1593
- for (const concept of concepts) {
1594
- prereqMap.set(concept.id, concept.prerequisites || []);
1595
- }
1596
- for (const concept of concepts) {
1597
- if (!visited.has(concept.id)) {
1598
- dfs(concept.id, prereqMap);
1599
- }
1600
- }
1601
- return cycles;
1602
- }
1603
- function breakCycles(concepts, cycles) {
1604
- const removed = [];
1605
- const fixed = concepts.map((c) => ({ ...c, prerequisites: [...c.prerequisites || []] }));
1606
- for (const cycle of cycles) {
1607
- if (cycle.length >= 2) {
1608
- const breaker = cycle[cycle.length - 2];
1609
- const target = cycle[cycle.length - 1];
1610
- for (const concept of fixed) {
1611
- if (concept.id === breaker) {
1612
- concept.prerequisites = concept.prerequisites.filter((p) => p !== target);
1613
- removed.push({ from: breaker, to: target });
1614
- }
1615
- }
1616
- }
1617
- }
1618
- return { fixed, removed };
1619
- }
1620
1907
  function topologicalSort(concepts) {
1621
1908
  const inDegree = /* @__PURE__ */ new Map();
1622
1909
  const adjacency = /* @__PURE__ */ new Map();
@@ -1657,6 +1944,11 @@ async function generateKnowledgeGraph(options) {
1657
1944
  onProgress?.(step, msg);
1658
1945
  };
1659
1946
  log("STEP_1", "Decomposing subject into concepts...");
1947
+ let parsedSyllabus = options.parsedSyllabus;
1948
+ if (!parsedSyllabus && syllabusText) {
1949
+ parsedSyllabus = parseSyllabus(syllabusText);
1950
+ log("STEP_1", `Parsed syllabus: ${parsedSyllabus.units.length} units, ${parsedSyllabus.allTopics.length} topics`);
1951
+ }
1660
1952
  const client = createLlmClient(llmConfig);
1661
1953
  const maxSyllabusChars = parseInt(process.env.KG_MAX_SYLLABUS_CHARS || "16000", 10);
1662
1954
  let truncatedSyllabus = "";
@@ -1668,10 +1960,13 @@ async function generateKnowledgeGraph(options) {
1668
1960
  truncatedSyllabus = syllabusText;
1669
1961
  }
1670
1962
  }
1963
+ const structuredTopicsGuide = parsedSyllabus && parsedSyllabus.allTopics.length > 0 ? `
1964
+ STRUCTURED SYLLABUS TOPICS (Every topic MUST map to at least one concept to ensure 100% syllabus coverage):
1965
+ ` + parsedSyllabus.allTopics.map((t) => `- [${t.unitTitle}] ${t.id}: ${t.title} (Keywords: ${t.keywords.join(", ")})`).join("\n") + "\n" : "";
1671
1966
  const systemPrompt = "You are an expert curriculum designer. Decompose the given subject into a structured knowledge graph with concepts, prerequisites, and problem types.\n\nCRITICAL RULES:\n1. Each concept MUST have the three depth layers, authored against this CONTRACT:\n - ulo (WHAT + WHY): bound to the CONCEPT's intrinsic NATURE. It explains what the\n concept is and why it exists \u2014 the problem it solves in principle. It must be\n TECHNOLOGY-AGNOSTIC: never name a concrete library, API, function, file format,\n tool or language feature. Typical assessment level: Remember / Understand.\n - cio (HOW): the MECHANISM \u2014 how the concept works and solves that problem, step\n by step. Still bound to the concept's nature: more concrete than the ULO, but\n NOT tied to one specific technology (no concrete API names). CIO capability is\n assessed INDIRECTLY, through completing contextual work, not in isolation.\n - sio (IMPLEMENTATION): an array of 2-4 items. EVERY item MUST reference concrete\n KEYWORDS/technologies (a real API, function, module, file, or named pattern) and\n describe a completable, context-specific implementation task. Application,\n Analysis and Creation skills are assessed INDIRECTLY through completing these\n items. There is NO fixed Bloom ceiling for a layer \u2014 but concreteness MUST\n strictly increase: SIO more technology-bound than CIO, CIO more concrete than ULO.\n - prerequisites: concept IDs that must be learned first (MUST NOT create cycles)\n - problem_types: 2-4 problem types with bloom_level and difficulty. Remember and\n Understand problem types test the ULO/CIO directly; Apply-and-above types are\n proxies for SIO completion in context.\n - techniques: methods, formulas, tools\n2. Prerequisite chains MUST be acyclic (no concept can depend on itself or create a loop)\n3. Concepts should be ordered by prerequisite dependency (topological sort)\n4. Problem types should range from Remember to Apply (not all at same level)\n5. Group concepts into categories (chapters/sections)\n6. ALL text in ENGLISH (even if input is Vietnamese)\n7. NEVER invent concepts not present in the syllabus\n8. Return JSON matching the KnowledgeGraphSchema";
1672
1967
  const userPrompt = `Subject: ${subject}
1673
1968
  ${description ? `Description: ${description}` : ""}
1674
- ${truncatedSyllabus ? `Syllabus:
1969
+ ${structuredTopicsGuide}${truncatedSyllabus ? `Syllabus:
1675
1970
  ${truncatedSyllabus}` : ""}
1676
1971
  ${gradeBand ? `Grade band: ${gradeBand[0]}-${gradeBand[1]}` : ""}
1677
1972
  ${domain ? `Domain: ${domain}` : ""}
@@ -1816,13 +2111,13 @@ Return JSON:
1816
2111
  log("STEP_2_5", "Standardized: " + std.decisions.filter((d) => d.decision === "standardized").length + ", proposed new: " + std.decisions.filter((d) => d.decision === "proposed_new").length);
1817
2112
  }
1818
2113
  log("STEP_3", "Checking for prerequisite cycles...");
1819
- const cycles = detectCycles(concepts);
2114
+ const cycles = detectPrerequisiteCycles(concepts);
1820
2115
  if (cycles.length > 0) {
1821
2116
  log("STEP_3", `Found ${cycles.length} cycle(s) in prerequisite chain \u2014 breaking cycles`);
1822
2117
  for (const cycle of cycles) {
1823
2118
  warnings.push(`Cycle detected: ${cycle.join(" \u2192 ")} \u2014 breaking at weakest edge`);
1824
2119
  }
1825
- const { fixed, removed } = breakCycles(concepts, cycles);
2120
+ const { fixed, removed } = breakPrerequisiteCycles(concepts, cycles);
1826
2121
  for (let i = 0; i < concepts.length; i++) {
1827
2122
  const fixedConcept = fixed.find((f) => f.id === concepts[i].id);
1828
2123
  if (fixedConcept) {
@@ -1873,16 +2168,21 @@ Return JSON:
1873
2168
  }
1874
2169
  }
1875
2170
  log("STEP_2", `Validated ${concepts.length} concepts, ${categories.length} categories, ${learningPath.length} learning path steps`);
2171
+ const rawGraph = {
2172
+ schema_version: 1,
2173
+ type: "knowledge_graph",
2174
+ subject: result.subject,
2175
+ concepts,
2176
+ categories,
2177
+ learning_path: learningPath
2178
+ };
2179
+ log("STEP_6", "Running Knowledge Graph verification & reverse coverage audit...");
2180
+ const { verifiedGraph, report } = verifyKnowledgeGraph(rawGraph, parsedSyllabus);
2181
+ warnings.push(...report.warnings);
1876
2182
  return {
1877
- knowledgeGraph: {
1878
- schema_version: 1,
1879
- type: "knowledge_graph",
1880
- subject: result.subject,
1881
- concepts,
1882
- categories,
1883
- learning_path: learningPath
1884
- },
1885
- warnings
2183
+ knowledgeGraph: verifiedGraph,
2184
+ warnings,
2185
+ verificationReport: report
1886
2186
  };
1887
2187
  }
1888
2188
 
@@ -2148,9 +2448,12 @@ exports.SIO_CODE_RE = SIO_CODE_RE;
2148
2448
  exports.ULO_CODE_RE = ULO_CODE_RE;
2149
2449
  exports.assignConceptCodes = assignConceptCodes;
2150
2450
  exports.auditDepthLayers = auditDepthLayers;
2451
+ exports.auditSyllabusCoverage = auditSyllabusCoverage;
2452
+ exports.breakPrerequisiteCycles = breakPrerequisiteCycles;
2151
2453
  exports.cioActionSlug = cioActionSlug;
2152
2454
  exports.cioCode = cioCode;
2153
2455
  exports.conceptCodeFromName = conceptCodeFromName;
2456
+ exports.detectPrerequisiteCycles = detectPrerequisiteCycles;
2154
2457
  exports.escalateAndMapConcepts = escalateAndMapConcepts;
2155
2458
  exports.extractFeatureSteps = extractFeatureSteps;
2156
2459
  exports.extractFeatureStepsBatched = extractFeatureStepsBatched;
@@ -2162,6 +2465,7 @@ exports.sioCode = sioCode;
2162
2465
  exports.standardStatement = standardStatement;
2163
2466
  exports.techTagFor = techTagFor;
2164
2467
  exports.uloCode = uloCode;
2468
+ exports.verifyKnowledgeGraph = verifyKnowledgeGraph;
2165
2469
  exports.verifyProjectGraph = verifyProjectGraph;
2166
2470
  //# sourceMappingURL=index.cjs.map
2167
2471
  //# sourceMappingURL=index.cjs.map