@thanh01.pmt/domain-kit 0.5.0 → 0.6.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.
- package/README.md +682 -168
- package/dist/assembly/index.d.cts +3 -2
- package/dist/assembly/index.d.ts +3 -2
- package/dist/{chunk-TYXSJUQU.mjs → chunk-4UCXYRM3.mjs} +208 -310
- package/dist/chunk-4UCXYRM3.mjs.map +1 -0
- package/dist/chunk-7A2SG466.mjs +325 -0
- package/dist/chunk-7A2SG466.mjs.map +1 -0
- package/dist/{chunk-E4GCCWJO.mjs → chunk-CGNXGVHV.mjs} +37 -4
- package/dist/chunk-CGNXGVHV.mjs.map +1 -0
- package/dist/{chunk-DRVOV5ZD.mjs → chunk-DSEJLTUO.mjs} +2 -2
- package/dist/chunk-DSEJLTUO.mjs.map +1 -0
- package/dist/{chunk-VCPIWJ7R.mjs → chunk-H4W3YJLK.mjs} +29 -4
- package/dist/chunk-H4W3YJLK.mjs.map +1 -0
- package/dist/chunk-IBVXXVGE.mjs +14 -0
- package/dist/chunk-IBVXXVGE.mjs.map +1 -0
- package/dist/{chunk-G7HLFMD2.mjs → chunk-KHCZYLKM.mjs} +68 -33
- package/dist/chunk-KHCZYLKM.mjs.map +1 -0
- package/dist/chunk-OYSZQVPY.mjs +10 -0
- package/dist/chunk-OYSZQVPY.mjs.map +1 -0
- package/dist/{chunk-BL5NBKPH.mjs → chunk-WD3P5V3B.mjs} +19 -8
- package/dist/chunk-WD3P5V3B.mjs.map +1 -0
- package/dist/{chunk-TWKIVUTQ.mjs → chunk-WKTH3JPA.mjs} +197 -36
- package/dist/chunk-WKTH3JPA.mjs.map +1 -0
- package/dist/{conceptEscalator-ltRLtPhf.d.cts → conceptEscalator-BmMJD8C5.d.cts} +10 -1
- package/dist/{conceptEscalator-YfMKIKCZ.d.ts → conceptEscalator-DMsehscs.d.ts} +10 -1
- package/dist/concepts/index.d.cts +4 -3
- package/dist/concepts/index.d.ts +4 -3
- package/dist/{cpp-CZUPqJt9.d.cts → cpp-vlDl6pCL.d.cts} +4 -4
- package/dist/{cpp-CZUPqJt9.d.ts → cpp-vlDl6pCL.d.ts} +4 -4
- package/dist/{curriculumFeedSchema-DUFwFyr2.d.ts → curriculumFeedSchema-DK-TUAJZ.d.cts} +164 -9
- package/dist/{curriculumFeedSchema-DUFwFyr2.d.cts → curriculumFeedSchema-DK-TUAJZ.d.ts} +164 -9
- package/dist/detector/index.cjs +22 -6
- package/dist/detector/index.cjs.map +1 -1
- package/dist/detector/index.d.cts +2 -2
- package/dist/detector/index.d.ts +2 -2
- package/dist/detector/index.mjs +2 -1
- package/dist/{domainProfileDetector-DHb1T0zB.d.ts → domainProfileDetector-6pjgL-6_.d.ts} +1 -1
- package/dist/{domainProfileDetector-bzugdQmS.d.cts → domainProfileDetector-BsADsuRV.d.cts} +1 -1
- package/dist/{domainProfileSchema-CwT3Ffsw.d.ts → domainProfileSchema-779FGk14.d.cts} +38 -1
- package/dist/{domainProfileSchema-CwT3Ffsw.d.cts → domainProfileSchema-779FGk14.d.ts} +38 -1
- package/dist/extractors/index.d.cts +3 -3
- package/dist/extractors/index.d.ts +3 -3
- package/dist/feed/index.cjs +229 -33
- package/dist/feed/index.cjs.map +1 -1
- package/dist/feed/index.d.cts +32 -2
- package/dist/feed/index.d.ts +32 -2
- package/dist/feed/index.mjs +2 -2
- package/dist/graph/index.cjs +308 -86
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +20 -8
- package/dist/graph/index.d.ts +20 -8
- package/dist/graph/index.mjs +4 -1
- package/dist/{hybridGraphPipeline-dT-bio-3.d.cts → hybridGraphPipeline-B4mamLim.d.cts} +6 -2
- package/dist/{hybridGraphPipeline-DXQDbBEj.d.ts → hybridGraphPipeline-D2_1GUva.d.ts} +6 -2
- package/dist/{hybridGraphSchema-nrUeSwpU.d.ts → hybridGraphSchema-C37qxdTM.d.cts} +16 -3
- package/dist/{hybridGraphSchema-nrUeSwpU.d.cts → hybridGraphSchema-C37qxdTM.d.ts} +16 -3
- package/dist/index.cjs +3948 -3464
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -10
- package/dist/index.d.ts +11 -10
- package/dist/index.mjs +14 -11
- package/dist/{keywordExtractor-BxlGRMHC.d.cts → keywordExtractor-1YAFntsn.d.cts} +1 -1
- package/dist/{keywordExtractor-DU5XRN8-.d.ts → keywordExtractor-DWw8z_AF.d.ts} +1 -1
- package/dist/parsers/index.cjs.map +1 -1
- package/dist/parsers/index.d.cts +1 -1
- package/dist/parsers/index.d.ts +1 -1
- package/dist/parsers/index.mjs +1 -1
- package/dist/pipeline/index.cjs +2436 -1994
- package/dist/pipeline/index.cjs.map +1 -1
- package/dist/pipeline/index.d.cts +19 -10
- package/dist/pipeline/index.d.ts +19 -10
- package/dist/pipeline/index.mjs +11 -8
- package/dist/schemas/index.cjs +63 -2
- package/dist/schemas/index.cjs.map +1 -1
- package/dist/schemas/index.d.cts +3 -3
- package/dist/schemas/index.d.ts +3 -3
- package/dist/schemas/index.mjs +2 -2
- package/dist/utils/llmClient.cjs +344 -0
- package/dist/utils/llmClient.cjs.map +1 -0
- package/dist/utils/llmClient.d.cts +118 -0
- package/dist/utils/llmClient.d.ts +118 -0
- package/dist/utils/llmClient.mjs +4 -0
- package/dist/utils/llmClient.mjs.map +1 -0
- package/dist/utils/llmTransportResult.cjs +16 -0
- package/dist/utils/llmTransportResult.cjs.map +1 -0
- package/dist/utils/llmTransportResult.d.cts +36 -0
- package/dist/utils/llmTransportResult.d.ts +36 -0
- package/dist/utils/llmTransportResult.mjs +3 -0
- package/dist/utils/llmTransportResult.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-BL5NBKPH.mjs.map +0 -1
- package/dist/chunk-DRVOV5ZD.mjs.map +0 -1
- package/dist/chunk-E4GCCWJO.mjs.map +0 -1
- package/dist/chunk-G7HLFMD2.mjs.map +0 -1
- package/dist/chunk-TWKIVUTQ.mjs.map +0 -1
- package/dist/chunk-TYXSJUQU.mjs.map +0 -1
- package/dist/chunk-VCPIWJ7R.mjs.map +0 -1
- package/dist/curriculumFeedEmitter-T7NUBLAM.mjs +0 -4
- package/dist/curriculumFeedEmitter-T7NUBLAM.mjs.map +0 -1
- package/dist/llmClient-ysPhLjcH.d.cts +0 -16
- package/dist/llmClient-ysPhLjcH.d.ts +0 -16
|
@@ -1,18 +1,82 @@
|
|
|
1
|
-
import { validateCurriculumFeed } from './chunk-
|
|
1
|
+
import { validateCurriculumFeed } from './chunk-CGNXGVHV.mjs';
|
|
2
2
|
|
|
3
3
|
// src/feed/curriculumFeedEmitter.ts
|
|
4
|
+
var DEPTH_MINUTE_FRACTIONS = {
|
|
5
|
+
/** Awareness-only pass before the anchor teach. */
|
|
6
|
+
preview: 0.25,
|
|
7
|
+
ulo: 0.35,
|
|
8
|
+
cio: 0.65,
|
|
9
|
+
sio: 1,
|
|
10
|
+
/** An advanced revisit re-teaches the concept, not the whole thing again. */
|
|
11
|
+
advancedRevisit: 0.5,
|
|
12
|
+
/** Minutes saved when an advanced revisit drops from SIO practice to a CIO recap. */
|
|
13
|
+
advancedRecapSave: 0.3,
|
|
14
|
+
/** Floor so a tiny concept still gets a teachable awareness pass. */
|
|
15
|
+
floor: 5,
|
|
16
|
+
/** Floor for an advanced revisit — it is a real teach, not an awareness pass. */
|
|
17
|
+
advancedFloor: 15
|
|
18
|
+
};
|
|
19
|
+
var DEFAULT_STEP_MINUTES = 15;
|
|
4
20
|
var BLOOM_RANK = { Remember: 0, Understand: 1, Apply: 2, Analyze: 3, Evaluate: 4, Create: 5 };
|
|
5
21
|
var pad2 = (n) => String(n).padStart(2, "0");
|
|
22
|
+
var steplessStepIds = [];
|
|
23
|
+
var takeSteplessStepIds = () => steplessStepIds.splice(0, steplessStepIds.length);
|
|
6
24
|
var arr = (v) => Array.isArray(v) ? v : [];
|
|
7
25
|
var str = (v, d = "") => typeof v === "string" ? v : d;
|
|
8
26
|
var num = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
9
27
|
var strs = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
10
28
|
function stepMinutes(step) {
|
|
11
29
|
const effort = step.effort ?? {};
|
|
12
|
-
return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes,
|
|
30
|
+
return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, DEFAULT_STEP_MINUTES))));
|
|
13
31
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
32
|
+
function diagnosticsOf(opts) {
|
|
33
|
+
const list = opts.diagnostics ?? (opts.diagnostics = []);
|
|
34
|
+
for (const message of opts.warnings ?? []) {
|
|
35
|
+
if (!list.some((d) => d.code === "upstream_warning" && d.message === message)) {
|
|
36
|
+
list.push({ code: "upstream_warning", severity: "warning", message, node_ids: [] });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return list;
|
|
40
|
+
}
|
|
41
|
+
function pushDiagnostic(opts, code, severity, message, nodeIds = []) {
|
|
42
|
+
diagnosticsOf(opts).push({ code, severity, message, node_ids: nodeIds });
|
|
43
|
+
}
|
|
44
|
+
function splitPrerequisites(concept, known) {
|
|
45
|
+
const internal = [];
|
|
46
|
+
const external = [];
|
|
47
|
+
for (const p of strs(concept.prerequisites)) {
|
|
48
|
+
(known.has(p) ? internal : external).push(p);
|
|
49
|
+
}
|
|
50
|
+
return { internal, external };
|
|
51
|
+
}
|
|
52
|
+
function noteExternalPrerequisites(opts, byNode) {
|
|
53
|
+
if (byNode.size === 0) return;
|
|
54
|
+
const entries = [];
|
|
55
|
+
for (const [nodeId, prereqs] of byNode) {
|
|
56
|
+
for (const prereqId of prereqs) entries.push({ nodeId, prereqId });
|
|
57
|
+
}
|
|
58
|
+
entries.sort((a, b) => a.nodeId === b.nodeId ? a.prereqId.localeCompare(b.prereqId) : a.nodeId.localeCompare(b.nodeId));
|
|
59
|
+
pushDiagnostic(
|
|
60
|
+
opts,
|
|
61
|
+
"external_prerequisite",
|
|
62
|
+
"info",
|
|
63
|
+
"Prerequisite(s) outside this analysis \u2014 the learner must already know them: " + entries.map((e) => e.nodeId + " \u2190 " + e.prereqId).join(", "),
|
|
64
|
+
entries.map((e) => e.nodeId)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function buildSource(opts, graphType) {
|
|
68
|
+
const diagnostics = diagnosticsOf(opts);
|
|
69
|
+
return {
|
|
70
|
+
graph_type: graphType,
|
|
71
|
+
// Derived view — 'info' diagnostics are deliberately not surfaced as warnings.
|
|
72
|
+
warnings: diagnostics.filter((d) => d.severity !== "info").map((d) => d.message),
|
|
73
|
+
hallucination_count: opts.hallucinationCount ?? 0,
|
|
74
|
+
diagnostics,
|
|
75
|
+
provenance: opts.provenance
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function keywordKey(k) {
|
|
79
|
+
return k.normalize("NFC").toLowerCase();
|
|
16
80
|
}
|
|
17
81
|
function newKeywordsInOrder(nodes) {
|
|
18
82
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -21,9 +85,9 @@ function newKeywordsInOrder(nodes) {
|
|
|
21
85
|
n.keywords.new = [];
|
|
22
86
|
n.keywords.prerequisite = [];
|
|
23
87
|
for (const k of all) {
|
|
24
|
-
if (seen.has(k)) n.keywords.prerequisite.push(k);
|
|
88
|
+
if (seen.has(keywordKey(k))) n.keywords.prerequisite.push(k);
|
|
25
89
|
else n.keywords.new.push(k);
|
|
26
|
-
seen.add(k);
|
|
90
|
+
seen.add(keywordKey(k));
|
|
27
91
|
}
|
|
28
92
|
}
|
|
29
93
|
}
|
|
@@ -47,6 +111,7 @@ function feedNodeFromStep(step, featureId) {
|
|
|
47
111
|
// sessions at user-visible checkpoints instead of raw minute counts.
|
|
48
112
|
user_visible_deliverable: str(step.outcome?.user_visible),
|
|
49
113
|
is_core: false,
|
|
114
|
+
external_prerequisites: [],
|
|
50
115
|
phase_id: "",
|
|
51
116
|
introduce_aspect: null
|
|
52
117
|
};
|
|
@@ -66,9 +131,9 @@ function deriveConceptDepthHint(concept) {
|
|
|
66
131
|
return "ulo";
|
|
67
132
|
}
|
|
68
133
|
function deriveDepthInputs(minutes, depth) {
|
|
69
|
-
const ulo = Math.max(
|
|
70
|
-
const cio = Math.max(ulo +
|
|
71
|
-
const sio = Math.max(cio +
|
|
134
|
+
const ulo = Math.max(DEPTH_MINUTE_FRACTIONS.floor, Math.round(minutes * DEPTH_MINUTE_FRACTIONS.ulo));
|
|
135
|
+
const cio = Math.max(ulo + DEPTH_MINUTE_FRACTIONS.floor, Math.round(minutes * DEPTH_MINUTE_FRACTIONS.cio));
|
|
136
|
+
const sio = Math.max(cio + DEPTH_MINUTE_FRACTIONS.floor, minutes);
|
|
72
137
|
const variants = { ulo, cio, sio };
|
|
73
138
|
const candidates = [];
|
|
74
139
|
if (depth === "sio") {
|
|
@@ -111,7 +176,11 @@ function feedNodeFromConcept(concept) {
|
|
|
111
176
|
user_visible_deliverable: "",
|
|
112
177
|
// concepts are taught, not shipped — checkpoints live on product steps
|
|
113
178
|
...deriveDepthInputs(Math.max(0, Math.round(num(concept.estimated_minutes, 15))), deriveConceptDepthHint(concept)),
|
|
114
|
-
|
|
179
|
+
// Knowledge-Tree concepts are the spine of the course — the reconciler must
|
|
180
|
+
// escalate them, never downgrade them. The emitter hardcoded `false`, so the
|
|
181
|
+
// flag the schema documents never reached the planner for any concept.
|
|
182
|
+
is_core: concept.is_core === true || str(concept.standardization) === "standardized",
|
|
183
|
+
external_prerequisites: [],
|
|
115
184
|
depth_context: {
|
|
116
185
|
ulo: str(concept.ulo),
|
|
117
186
|
cio: str(concept.cio),
|
|
@@ -127,7 +196,10 @@ function addFeatureGroup(f, fid, nodes, edges, groupings) {
|
|
|
127
196
|
let prev = "";
|
|
128
197
|
for (let i = 0; i < steps.length; i++) {
|
|
129
198
|
const stepNode = feedNodeFromStep(steps[i], fid);
|
|
130
|
-
if (!stepNode.id)
|
|
199
|
+
if (!stepNode.id) {
|
|
200
|
+
steplessStepIds.push(fid + "#" + i);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
131
203
|
stepIds.push(stepNode.id);
|
|
132
204
|
nodes.push(stepNode);
|
|
133
205
|
if (prev) edges.push({ from: prev, to: stepNode.id, kind: "task", reason: "feature build order" });
|
|
@@ -150,18 +222,26 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
150
222
|
const features = arr(pg.features);
|
|
151
223
|
const conceptRecs = arr(kg.concepts);
|
|
152
224
|
const conceptById = new Map(conceptRecs.map((c) => [str(c.id), c]));
|
|
225
|
+
const knownConceptIds = new Set(conceptById.keys());
|
|
153
226
|
if (rawPhases.length === 0) {
|
|
227
|
+
const externalByNode2 = /* @__PURE__ */ new Map();
|
|
154
228
|
for (const c of conceptRecs) {
|
|
155
229
|
const node = feedNodeFromConcept(c);
|
|
156
230
|
nodes.push(node);
|
|
157
|
-
|
|
231
|
+
const { internal, external } = splitPrerequisites(c, knownConceptIds);
|
|
232
|
+
if (external.length > 0) {
|
|
233
|
+
node.external_prerequisites = external;
|
|
234
|
+
externalByNode2.set(node.id, external);
|
|
235
|
+
}
|
|
236
|
+
for (const p of internal) edges.push({ from: p, to: node.id, kind: "knowledge", reason: "concept prerequisite" });
|
|
158
237
|
}
|
|
238
|
+
noteExternalPrerequisites(opts, externalByNode2);
|
|
159
239
|
features.forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad2(fi + 1)), nodes, edges, groupings));
|
|
160
240
|
linkHybridKnowledgeEdges(hybrid, nodes, edges);
|
|
161
241
|
newKeywordsInOrder(nodes);
|
|
162
242
|
return validateCurriculumFeed({
|
|
163
243
|
schema_version: 2,
|
|
164
|
-
source:
|
|
244
|
+
source: buildSource(opts, "hybrid_graph"),
|
|
165
245
|
learning_nodes: nodes,
|
|
166
246
|
dependency_edges: edges,
|
|
167
247
|
suggested_groupings: groupings,
|
|
@@ -178,6 +258,8 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
178
258
|
const emitCtx = [];
|
|
179
259
|
const emittedConcepts = /* @__PURE__ */ new Set();
|
|
180
260
|
const previewsEmitted = /* @__PURE__ */ new Set();
|
|
261
|
+
const previewNodeIdByConcept = /* @__PURE__ */ new Map();
|
|
262
|
+
const externalByNode = /* @__PURE__ */ new Map();
|
|
181
263
|
const linkList = arr(hybrid.links);
|
|
182
264
|
for (const rp of rawPhases) {
|
|
183
265
|
const pid = str(rp.id);
|
|
@@ -217,7 +299,7 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
217
299
|
prevNode.name = str(c.name, cid) + " (preview)";
|
|
218
300
|
prevNode.phase_id = pid;
|
|
219
301
|
prevNode.introduce_aspect = "preview";
|
|
220
|
-
prevNode.estimated_minutes = Math.max(
|
|
302
|
+
prevNode.estimated_minutes = Math.max(DEPTH_MINUTE_FRACTIONS.floor, Math.round(prevNode.estimated_minutes * DEPTH_MINUTE_FRACTIONS.preview));
|
|
221
303
|
prevNode.depth_hint = "ulo";
|
|
222
304
|
prevNode.bloom_hint = "Understand";
|
|
223
305
|
prevNode.concept_codes = [str(c.code) || cid];
|
|
@@ -227,6 +309,7 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
227
309
|
prevNode.depth_scaffold_candidates = [];
|
|
228
310
|
nodes.push(prevNode);
|
|
229
311
|
conceptOrder.push(prevNode.id);
|
|
312
|
+
previewNodeIdByConcept.set(cid, prevNode.id);
|
|
230
313
|
continue;
|
|
231
314
|
}
|
|
232
315
|
if (!emittedConcepts.has(cid)) {
|
|
@@ -235,13 +318,22 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
235
318
|
node.introduce_aspect = "new";
|
|
236
319
|
nodes.push(node);
|
|
237
320
|
for (const p of strs(c.prerequisites)) {
|
|
238
|
-
if (!
|
|
239
|
-
|
|
321
|
+
if (!conceptById.has(p)) {
|
|
322
|
+
if (!externalByNode.has(cid)) externalByNode.set(cid, []);
|
|
323
|
+
externalByNode.get(cid).push(p);
|
|
324
|
+
node.external_prerequisites.push(p);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const previewOfPrereq = previewNodeIdByConcept.get(p);
|
|
328
|
+
if (!emittedConcepts.has(p) && !previewOfPrereq) {
|
|
329
|
+
throw new Error("Concept " + cid + " (phase " + pid + ") has prerequisite " + p + " that is neither introduced nor previewed in an EARLIER phase - fail-closed");
|
|
240
330
|
}
|
|
241
|
-
edges.push({ from: p, to: cid, kind: "knowledge", reason: "concept prerequisite" });
|
|
331
|
+
edges.push({ from: emittedConcepts.has(p) ? p : previewOfPrereq, to: cid, kind: "knowledge", reason: "concept prerequisite" });
|
|
242
332
|
}
|
|
243
333
|
emittedConcepts.add(cid);
|
|
244
334
|
conceptOrder.push(cid);
|
|
335
|
+
} else if (str(intro.aspect, "new") === "new") {
|
|
336
|
+
throw new Error("Phase " + pid + " introduces " + cid + " as new but it was already introduced in an earlier phase - fail-closed");
|
|
245
337
|
} else {
|
|
246
338
|
const revisitId = cid + "__ADV" + pad2(order);
|
|
247
339
|
const node = feedNodeFromConcept(c);
|
|
@@ -249,8 +341,16 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
249
341
|
node.name = str(c.name, cid) + " (advanced)";
|
|
250
342
|
node.phase_id = pid;
|
|
251
343
|
node.introduce_aspect = "advanced";
|
|
252
|
-
node.
|
|
253
|
-
|
|
344
|
+
node.estimated_minutes = Math.max(
|
|
345
|
+
DEPTH_MINUTE_FRACTIONS.advancedFloor,
|
|
346
|
+
Math.round(node.estimated_minutes * DEPTH_MINUTE_FRACTIONS.advancedRevisit)
|
|
347
|
+
);
|
|
348
|
+
node.depth_scaffold_candidates = [{
|
|
349
|
+
from_depth: "sio",
|
|
350
|
+
to_depth: "cio",
|
|
351
|
+
minutes_saved: Math.round(node.estimated_minutes * DEPTH_MINUTE_FRACTIONS.advancedRecapSave),
|
|
352
|
+
reason: "advanced revisit may drop to CIO recap without breaking first-teach chain"
|
|
353
|
+
}];
|
|
254
354
|
node.concept_codes = [str(c.code) || cid];
|
|
255
355
|
edges.push({ from: cid, to: revisitId, kind: "knowledge", reason: "advanced revisit of " + cid + " at phase " + pid });
|
|
256
356
|
nodes.push(node);
|
|
@@ -260,6 +360,7 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
260
360
|
const featureIds = strs(rp.feature_ids).filter((fid) => featureOrder.has(fid)).sort((a, b) => featureOrder.get(a) - featureOrder.get(b));
|
|
261
361
|
emitCtx.push({ id: pid, order, name: str(rp.name, pid), productCompletion: str(rp.product_completion), conceptOrder, featureIds });
|
|
262
362
|
}
|
|
363
|
+
noteExternalPrerequisites(opts, externalByNode);
|
|
263
364
|
for (const l of linkList) {
|
|
264
365
|
const cid = str(l.concept_id);
|
|
265
366
|
if (cid && conceptById.has(cid) && !emittedConcepts.has(cid)) {
|
|
@@ -271,6 +372,16 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
271
372
|
if (missingFeatures.length > 0) {
|
|
272
373
|
throw new Error("Features not covered by any phase: " + missingFeatures.join(", ") + " - fail-closed");
|
|
273
374
|
}
|
|
375
|
+
const orphans = Array.from(conceptById.keys()).filter((cid) => !emittedConcepts.has(cid));
|
|
376
|
+
if (orphans.length > 0) {
|
|
377
|
+
pushDiagnostic(
|
|
378
|
+
opts,
|
|
379
|
+
"orphan_concept",
|
|
380
|
+
"error",
|
|
381
|
+
"Knowledge graph has " + orphans.length + " concept(s) no phase introduces and no link consumes \u2014 they will not be taught: " + orphans.join(", "),
|
|
382
|
+
orphans
|
|
383
|
+
);
|
|
384
|
+
}
|
|
274
385
|
const phaseNodeIds = [];
|
|
275
386
|
for (const ctx of emitCtx) {
|
|
276
387
|
const ids = [...ctx.conceptOrder];
|
|
@@ -278,13 +389,26 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
278
389
|
const f = features.find((x) => str(x.id) === fid);
|
|
279
390
|
const stepIds = addFeatureGroup(f, fid, nodes, edges, groupings);
|
|
280
391
|
for (const sid of stepIds) {
|
|
281
|
-
const node = nodes.find((
|
|
392
|
+
const node = nodes.find((x) => x.id === sid);
|
|
282
393
|
node.phase_id = ctx.id;
|
|
283
394
|
}
|
|
284
395
|
ids.push(...stepIds);
|
|
285
396
|
}
|
|
397
|
+
if (ids.length === 0) {
|
|
398
|
+
throw new Error("Phase " + ctx.id + " contains no teaching nodes (no concepts introduced and no feature steps) - drop it or give it work - fail-closed");
|
|
399
|
+
}
|
|
286
400
|
phaseNodeIds.push(ids);
|
|
287
401
|
}
|
|
402
|
+
const idlessInHybrid = takeSteplessStepIds();
|
|
403
|
+
if (idlessInHybrid.length > 0) {
|
|
404
|
+
pushDiagnostic(
|
|
405
|
+
opts,
|
|
406
|
+
"step_without_id",
|
|
407
|
+
"warning",
|
|
408
|
+
"Step(s) carrying no id cannot become planning nodes and were dropped (feature#index): " + idlessInHybrid.join(", "),
|
|
409
|
+
idlessInHybrid
|
|
410
|
+
);
|
|
411
|
+
}
|
|
288
412
|
const placed = new Set(phaseNodeIds.flat());
|
|
289
413
|
const orderedNodes = [];
|
|
290
414
|
for (const ids of phaseNodeIds) {
|
|
@@ -315,7 +439,13 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
315
439
|
const anchorPhaseIdx = anchorNode ? emitCtx.findIndex((c2) => c2.id === anchorNode.phase_id) : -1;
|
|
316
440
|
const stepPhaseIdx = stepNode ? emitCtx.findIndex((c2) => c2.id === stepNode.phase_id) : -1;
|
|
317
441
|
if (emitCtx.length > 0 && anchorPhaseIdx > stepPhaseIdx) {
|
|
318
|
-
|
|
442
|
+
pushDiagnostic(
|
|
443
|
+
opts,
|
|
444
|
+
"hybrid_link_drift",
|
|
445
|
+
"warning",
|
|
446
|
+
"hybrid link drift: concept " + cid + " is fully taught in a later phase than consumer " + firstStep + " (a preview covers the dependency)",
|
|
447
|
+
[cid, firstStep]
|
|
448
|
+
);
|
|
319
449
|
continue;
|
|
320
450
|
}
|
|
321
451
|
pushEdge(cid, firstStep, str(l.relationship, "hybrid link") + " [" + depth + "]");
|
|
@@ -329,12 +459,12 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
329
459
|
const feedOrder = new Map(nodes.map((n, i) => [n.id, i]));
|
|
330
460
|
const introducer = /* @__PURE__ */ new Map();
|
|
331
461
|
for (const n of nodes) for (const k of n.keywords?.new ?? []) {
|
|
332
|
-
const key = k
|
|
462
|
+
const key = keywordKey(k);
|
|
333
463
|
if (!introducer.has(key)) introducer.set(key, n.id);
|
|
334
464
|
}
|
|
335
465
|
for (const n of nodes) {
|
|
336
466
|
for (const k of n.keywords?.prerequisite ?? []) {
|
|
337
|
-
const src = introducer.get(k
|
|
467
|
+
const src = introducer.get(keywordKey(k));
|
|
338
468
|
if (!src || src === n.id) continue;
|
|
339
469
|
const srcNode = nodes.find((x) => x.id === src);
|
|
340
470
|
const phaseOf = (id) => {
|
|
@@ -345,9 +475,12 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
345
475
|
const fromPhase = phaseOf(src);
|
|
346
476
|
const toPhase = phaseOf(n.id);
|
|
347
477
|
if (conceptOvertaken || emitCtx.length > 0 && fromPhase > toPhase) {
|
|
348
|
-
|
|
478
|
+
pushDiagnostic(
|
|
349
479
|
opts,
|
|
350
|
-
|
|
480
|
+
conceptOvertaken ? "concept_overtaken_by_step" : "keyword_order_drift",
|
|
481
|
+
"warning",
|
|
482
|
+
"keyword-order drift: introducer " + src + " sits in a later phase than consumer " + n.id + ' (keyword "' + k + '") \u2014 host should move the introducer earlier',
|
|
483
|
+
[src, n.id]
|
|
351
484
|
);
|
|
352
485
|
continue;
|
|
353
486
|
}
|
|
@@ -363,7 +496,7 @@ function emitFromHybrid(hybrid, opts) {
|
|
|
363
496
|
}));
|
|
364
497
|
return validateCurriculumFeed({
|
|
365
498
|
schema_version: 2,
|
|
366
|
-
source:
|
|
499
|
+
source: buildSource(opts, "hybrid_graph"),
|
|
367
500
|
learning_nodes: nodes,
|
|
368
501
|
dependency_edges: edges,
|
|
369
502
|
suggested_groupings: groupings,
|
|
@@ -391,11 +524,17 @@ function emitFromKnowledge(kg, opts) {
|
|
|
391
524
|
const edges = [];
|
|
392
525
|
const groupings = [];
|
|
393
526
|
const conceptIds = new Set(arr(kg.concepts).map((c) => str(c.id)));
|
|
527
|
+
const externalByNode = /* @__PURE__ */ new Map();
|
|
394
528
|
for (const c of arr(kg.concepts)) {
|
|
395
529
|
const node = feedNodeFromConcept(c);
|
|
396
530
|
nodes.push(node);
|
|
397
|
-
|
|
398
|
-
|
|
531
|
+
const { internal, external } = splitPrerequisites(c, conceptIds);
|
|
532
|
+
if (external.length > 0) {
|
|
533
|
+
node.external_prerequisites = external;
|
|
534
|
+
externalByNode.set(node.id, external);
|
|
535
|
+
}
|
|
536
|
+
for (const p of internal) {
|
|
537
|
+
if (p !== node.id) {
|
|
399
538
|
edges.push({
|
|
400
539
|
from: p,
|
|
401
540
|
to: node.id,
|
|
@@ -405,6 +544,7 @@ function emitFromKnowledge(kg, opts) {
|
|
|
405
544
|
}
|
|
406
545
|
}
|
|
407
546
|
}
|
|
547
|
+
noteExternalPrerequisites(opts, externalByNode);
|
|
408
548
|
if (arr(kg.learning_path).length > 0) {
|
|
409
549
|
const orderMap = /* @__PURE__ */ new Map();
|
|
410
550
|
arr(kg.learning_path).forEach((s, idx) => {
|
|
@@ -429,7 +569,7 @@ function emitFromKnowledge(kg, opts) {
|
|
|
429
569
|
newKeywordsInOrder(nodes);
|
|
430
570
|
return validateCurriculumFeed({
|
|
431
571
|
schema_version: 2,
|
|
432
|
-
source:
|
|
572
|
+
source: buildSource(opts, "knowledge_graph"),
|
|
433
573
|
learning_nodes: nodes,
|
|
434
574
|
dependency_edges: edges,
|
|
435
575
|
suggested_groupings: groupings,
|
|
@@ -440,17 +580,38 @@ function emitFromProject(pg, opts) {
|
|
|
440
580
|
const nodes = [];
|
|
441
581
|
const edges = [];
|
|
442
582
|
const groupings = [];
|
|
443
|
-
arr(pg.features)
|
|
444
|
-
|
|
583
|
+
const allFeatures = arr(pg.features);
|
|
584
|
+
allFeatures.forEach((f, fi) => addFeatureGroup(f, str(f.id, "F" + pad2(fi + 1)), nodes, edges, groupings));
|
|
585
|
+
const stepless = allFeatures.filter((f) => arr(f.steps).length === 0).map((f) => str(f.id)).filter(Boolean);
|
|
586
|
+
if (stepless.length > 0) {
|
|
587
|
+
pushDiagnostic(
|
|
588
|
+
opts,
|
|
589
|
+
"feature_without_steps",
|
|
590
|
+
"warning",
|
|
591
|
+
"Feature(s) declared but carrying no steps \u2014 they will be absent from the feed: " + stepless.join(", "),
|
|
592
|
+
stepless
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
const idless = takeSteplessStepIds();
|
|
596
|
+
if (idless.length > 0) {
|
|
597
|
+
pushDiagnostic(
|
|
598
|
+
opts,
|
|
599
|
+
"step_without_id",
|
|
600
|
+
"warning",
|
|
601
|
+
"Step(s) carrying no id cannot become planning nodes and were dropped (feature#index): " + idless.join(", "),
|
|
602
|
+
idless
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
if (nodes.length === 0 && allFeatures.length > 0) {
|
|
445
606
|
const hasTasks = arr(pg.implementation ?? {}).length > 0 || arr(pg.implementation?.tasks).length > 0;
|
|
446
607
|
throw new Error(
|
|
447
|
-
"Project graph has " +
|
|
608
|
+
"Project graph has " + allFeatures.length + " features but NO features[].steps - the curriculum feed cannot emit planning nodes." + (hasTasks ? " This looks like a legacy knowledge-tree graph (implementation.tasks). Regenerate it with the domain-kit v3 pipeline (runProjectGraphPipeline / hybridGraphPipeline), then re-run." : " Every feature must carry at least one step with id/name/keywords/effort for curriculum planning.")
|
|
448
609
|
);
|
|
449
610
|
}
|
|
450
611
|
newKeywordsInOrder(nodes);
|
|
451
612
|
return validateCurriculumFeed({
|
|
452
613
|
schema_version: 2,
|
|
453
|
-
source:
|
|
614
|
+
source: buildSource(opts, "project_graph"),
|
|
454
615
|
learning_nodes: nodes,
|
|
455
616
|
dependency_edges: edges,
|
|
456
617
|
suggested_groupings: groupings,
|
|
@@ -469,6 +630,6 @@ function emitCurriculumFeed(graph, opts = {}) {
|
|
|
469
630
|
return emitFromProject(g, opts);
|
|
470
631
|
}
|
|
471
632
|
|
|
472
|
-
export { emitCurriculumFeed };
|
|
473
|
-
//# sourceMappingURL=chunk-
|
|
474
|
-
//# sourceMappingURL=chunk-
|
|
633
|
+
export { DEFAULT_STEP_MINUTES, DEPTH_MINUTE_FRACTIONS, emitCurriculumFeed };
|
|
634
|
+
//# sourceMappingURL=chunk-WKTH3JPA.mjs.map
|
|
635
|
+
//# sourceMappingURL=chunk-WKTH3JPA.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/feed/curriculumFeedEmitter.ts"],"names":["externalByNode"],"mappings":";;;AAgCO,IAAM,sBAAA,GAAyB;AAAA;AAAA,EAEpC,OAAA,EAAS,IAAA;AAAA,EACT,GAAA,EAAK,IAAA;AAAA,EACL,GAAA,EAAK,IAAA;AAAA,EACL,GAAA,EAAK,CAAA;AAAA;AAAA,EAEL,eAAA,EAAiB,GAAA;AAAA;AAAA,EAEjB,iBAAA,EAAmB,GAAA;AAAA;AAAA,EAEnB,KAAA,EAAO,CAAA;AAAA;AAAA,EAEP,aAAA,EAAe;AACjB;AAOO,IAAM,oBAAA,GAAuB;AAEpC,IAAM,UAAA,GAAqC,EAAE,QAAA,EAAU,CAAA,EAAG,UAAA,EAAY,CAAA,EAAG,KAAA,EAAO,CAAA,EAAG,OAAA,EAAS,CAAA,EAAG,QAAA,EAAU,CAAA,EAAG,QAAQ,CAAA,EAAE;AACtH,IAAM,IAAA,GAAO,CAAC,CAAA,KAAsB,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAA;AAG7D,IAAI,kBAA4B,EAAC;AACjC,IAAM,sBAAsB,MAAgB,eAAA,CAAgB,MAAA,CAAO,CAAA,EAAG,gBAAgB,MAAM,CAAA;AAG5F,IAAM,GAAA,GAAM,CAAC,CAAA,KAAuB,KAAA,CAAM,QAAQ,CAAC,CAAA,GAAK,IAAc,EAAC;AACvE,IAAM,GAAA,GAAM,CAAC,CAAA,EAAY,CAAA,GAAI,OAAgB,OAAO,CAAA,KAAM,WAAW,CAAA,GAAI,CAAA;AACzE,IAAM,GAAA,GAAM,CAAC,CAAA,EAAY,CAAA,KAAuB,OAAO,CAAA,KAAM,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,CAAC,CAAA,GAAI,CAAA,GAAI,CAAA;AAClG,IAAM,IAAA,GAAO,CAAC,CAAA,KAA0B,KAAA,CAAM,QAAQ,CAAC,CAAA,GAAI,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAmB,OAAO,CAAA,KAAM,QAAQ,IAAI,EAAC;AAElH,SAAS,YAAY,IAAA,EAAmB;AACtC,EAAA,MAAM,MAAA,GAAU,IAAA,CAAK,MAAA,IAAU,EAAC;AAChC,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,MAAM,GAAA,CAAI,MAAA,CAAO,iBAAA,EAAmB,GAAA,CAAI,IAAA,CAAK,iBAAA,EAAmB,oBAAoB,CAAC,CAAC,CAAC,CAAA;AACjH;AAEA,SAAS,cAAc,IAAA,EAAyC;AAC9D,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,KAAgB,IAAA,CAAK,cAAc,EAAC,CAAA;AAGtD,EAAA,KAAA,MAAW,OAAA,IAAW,IAAA,CAAK,QAAA,IAAY,EAAC,EAAG;AACzC,IAAA,IAAI,CAAC,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,KAAS,kBAAA,IAAsB,CAAA,CAAE,OAAA,KAAY,OAAO,CAAA,EAAG;AAC7E,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,kBAAA,EAAoB,QAAA,EAAU,WAAW,OAAA,EAAS,QAAA,EAAU,EAAC,EAAG,CAAA;AAAA,IACpF;AAAA,EACF;AACA,EAAA,OAAO,IAAA;AACT;AAGA,SAAS,eACP,IAAA,EACA,IAAA,EACA,UACA,OAAA,EACA,OAAA,GAAoB,EAAC,EACf;AACN,EAAA,aAAA,CAAc,IAAI,EAAE,IAAA,CAAK,EAAE,MAAM,QAAA,EAAU,OAAA,EAAS,QAAA,EAAU,OAAA,EAAS,CAAA;AACzE;AAiBA,SAAS,kBAAA,CAAmB,SAAc,KAAA,EAAgE;AACxG,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,MAAM,WAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,OAAA,CAAQ,aAAa,CAAA,EAAG;AAC3C,IAAA,CAAC,MAAM,GAAA,CAAI,CAAC,IAAI,QAAA,GAAW,QAAA,EAAU,KAAK,CAAC,CAAA;AAAA,EAC7C;AACA,EAAA,OAAO,EAAE,UAAU,QAAA,EAAS;AAC9B;AAGA,SAAS,yBAAA,CACP,MACA,MAAA,EACM;AACN,EAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACvB,EAAA,MAAM,UAA4B,EAAC;AACnC,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,OAAO,CAAA,IAAK,MAAA,EAAQ;AACtC,IAAA,KAAA,MAAW,YAAY,OAAA,EAAS,OAAA,CAAQ,KAAK,EAAE,MAAA,EAAQ,UAAU,CAAA;AAAA,EACnE;AACA,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA,EAAG,MAAO,CAAA,CAAE,MAAA,KAAW,EAAE,MAAA,GAAS,CAAA,CAAE,SAAS,aAAA,CAAc,CAAA,CAAE,QAAQ,CAAA,GAAI,CAAA,CAAE,OAAO,aAAA,CAAc,CAAA,CAAE,MAAM,CAAE,CAAA;AACxH,EAAA,cAAA;AAAA,IACE,IAAA;AAAA,IAAM,uBAAA;AAAA,IAAyB,MAAA;AAAA,IAC/B,mFAAA,GACE,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,MAAA,GAAS,UAAA,GAAQ,CAAA,CAAE,QAAQ,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAAA,IAC7D,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,MAAM;AAAA,GAC7B;AACF;AAEA,SAAS,WAAA,CAAY,MAAuB,SAAA,EAAmD;AAC7F,EAAA,MAAM,WAAA,GAAc,cAAc,IAAI,CAAA;AACtC,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,SAAA;AAAA;AAAA,IAEZ,QAAA,EAAU,WAAA,CAAY,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,QAAA,KAAa,MAAM,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,OAAO,CAAA;AAAA,IAC/E,mBAAA,EAAqB,KAAK,kBAAA,IAAsB,CAAA;AAAA,IAChD,WAAA;AAAA,IACA,YAAY,IAAA,CAAK;AAAA,GACnB;AACF;AAOA,SAAS,WAAW,CAAA,EAAmB;AACrC,EAAA,OAAO,CAAA,CAAE,SAAA,CAAU,KAAK,CAAA,CAAE,WAAA,EAAY;AACxC;AAEA,SAAS,mBAAmB,KAAA,EAAyB;AAEnD,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,MAAM,GAAA,GAAM,EAAE,QAAA,CAAS,GAAA;AACvB,IAAA,CAAA,CAAE,QAAA,CAAS,MAAM,EAAC;AAClB,IAAA,CAAA,CAAE,QAAA,CAAS,eAAe,EAAC;AAC3B,IAAA,KAAA,MAAW,KAAK,GAAA,EAAK;AACnB,MAAA,IAAI,IAAA,CAAK,GAAA,CAAI,UAAA,CAAW,CAAC,CAAC,GAAG,CAAA,CAAE,QAAA,CAAS,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA;AAAA,WACtD,CAAA,CAAE,QAAA,CAAS,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA;AAC1B,MAAA,IAAA,CAAK,GAAA,CAAI,UAAA,CAAW,CAAC,CAAC,CAAA;AAAA,IACxB;AAAA,EACF;AACF;AAEA,SAAS,gBAAA,CAAiB,MAAW,SAAA,EAA6B;AAChE,EAAA,MAAM,EAAA,GAAK,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA;AACtB,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA,EAAM,cAAA;AAAA,IACN,IAAA,EAAM,GAAA,CAAI,IAAA,CAAK,IAAA,EAAM,EAAE,CAAA;AAAA,IACvB,aAAA,EAAe,IAAA,CAAK,IAAA,CAAK,aAAa,CAAA;AAAA,IACtC,QAAA,EAAU,EAAE,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,QAAQ,CAAA,EAAG,GAAA,EAAK,EAAC,EAAG,YAAA,EAAc,EAAC,EAAE;AAAA,IAChE,iBAAA,EAAmB,YAAY,IAAI,CAAA;AAAA,IACnC,UAAA,EAAY,OAAA;AAAA,IACZ,UAAA,EAAY,IAAA;AAAA,IACZ,gBAAA,EAAkB,UAAA;AAAA,IAClB,WAAA,EAAa,EAAE,mBAAA,EAAqB,IAAI,kBAAA,EAAoB,WAAA,CAAY,IAAI,CAAA,EAAE;AAAA,IAC9E,UAAA,EAAY,IAAI,IAAA,CAAK,KAAK,EAAE,GAAA,CAAI,CAAC,OAAO,EAAE,IAAA,EAAM,IAAI,CAAC,CAAA,EAAG,UAAU,WAAA,EAAY,CAAE,EAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA;AAAA,IACtG,UAAA,EAAY,SAAA;AAAA,IACZ,WAAA,EAAa,IAAA;AAAA;AAAA;AAAA,IAGb,wBAAA,EAA0B,GAAA,CAAK,IAAA,CAAK,OAAA,EAA6B,YAAY,CAAA;AAAA,IAC7E,OAAA,EAAS,KAAA;AAAA,IACT,wBAAwB,EAAC;AAAA,IACzB,QAAA,EAAU,EAAA;AAAA,IACV,gBAAA,EAAkB;AAAA,GACpB;AACF;AAEA,SAAS,uBAAuB,OAAA,EAAsC;AACpE,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,OAAA,CAAQ,UAAU,CAAA;AACvC,EAAA,IAAI,aAAa,KAAA,IAAS,QAAA,KAAa,KAAA,IAAS,QAAA,KAAa,OAAO,OAAO,QAAA;AAE3E,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAC3B,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,aAAa,CAAA;AAC1C,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,aAAa,CAAA;AACrC,EAAA,MAAM,YAAA,GAAe,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA,KAAM;AACnC,IAAA,MAAM,CAAA,GAAI,GAAA,CAAI,CAAA,CAAE,WAAW,CAAA;AAC3B,IAAA,OAAO,MAAM,OAAA,IAAW,CAAA,KAAM,SAAA,IAAa,CAAA,KAAM,cAAc,CAAA,KAAM,QAAA;AAAA,EACvE,CAAC,CAAA;AAED,EAAA,IAAI,GAAA,CAAI,MAAA,IAAU,CAAA,IAAK,YAAA,EAAc,OAAO,KAAA;AAC5C,EAAA,IAAI,IAAI,OAAA,CAAQ,GAAG,KAAK,OAAA,CAAQ,MAAA,IAAU,GAAG,OAAO,KAAA;AACpD,EAAA,OAAO,KAAA;AACT;AAQA,SAAS,iBAAA,CAAkB,SAAiB,KAAA,EAA+F;AACzI,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,sBAAA,CAAuB,KAAA,EAAO,KAAK,KAAA,CAAM,OAAA,GAAU,sBAAA,CAAuB,GAAG,CAAC,CAAA;AACnG,EAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,GAAA,GAAM,sBAAA,CAAuB,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,sBAAA,CAAuB,GAAG,CAAC,CAAA;AACzG,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,GAAA,GAAM,sBAAA,CAAuB,OAAO,OAAO,CAAA;AAChE,EAAA,MAAM,QAAA,GAAW,EAAE,GAAA,EAAK,GAAA,EAAK,GAAA,EAAI;AACjC,EAAA,MAAM,aAAiE,EAAC;AACxE,EAAA,IAAI,UAAU,KAAA,EAAO;AACnB,IAAA,UAAA,CAAW,IAAA,CAAK,EAAE,UAAA,EAAY,KAAA,EAAO,QAAA,EAAU,KAAA,EAAO,aAAA,EAAe,GAAA,GAAM,GAAA,EAAK,MAAA,EAAQ,6FAAA,EAA0F,CAAA;AAAA,EACpL;AACA,EAAA,IAAI,KAAA,KAAU,KAAA,IAAS,KAAA,KAAU,KAAA,EAAO;AACtC,IAAA,UAAA,CAAW,IAAA,CAAK,EAAE,UAAA,EAAY,KAAA,EAAO,UAAU,KAAA,EAAO,aAAA,EAAA,CAAgB,KAAA,KAAU,KAAA,GAAQ,GAAA,GAAM,GAAA,IAAO,GAAA,EAAK,MAAA,EAAQ,gGAA2F,CAAA;AAAA,EAC/M;AACA,EAAA,OAAO,EAAE,cAAA,EAAgB,QAAA,EAAU,yBAAA,EAA2B,UAAA,EAAW;AAC3E;AAEA,SAAS,oBAAoB,OAAA,EAAwB;AACnD,EAAA,MAAM,EAAA,GAAK,GAAA,CAAI,OAAA,CAAQ,EAAE,CAAA;AACzB,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,OAAA,CAAQ,aAAa,CAAA,CAClC,GAAA,CAAI,CAAC,CAAA,KAAM,GAAA,CAAI,CAAA,CAAE,WAAA,EAAa,YAAY,CAAC,CAAA,CAC3C,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,IAAK,UAAU,CAAA,CAC7B,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,UAAA,CAAW,CAAC,CAAA,GAAI,UAAA,CAAW,CAAC,CAAC,CAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,CAAI,MAAA,GAAS,CAAC,CAAA,IAAK,YAAA;AAExC,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,UAAU,CAAA;AACtC,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,MAAA,GAAS,CAAA,GAChC,OAAA,CAAQ,IAAI,CAAC,CAAA,MAAO,EAAE,IAAA,EAAM,GAAA,CAAI,CAAA,CAAE,MAAM,aAAa,CAAA,EAAG,QAAA,EAAU,GAAA,CAAI,CAAA,CAAE,QAAA,EAAU,IAAI,OAAA,CAAQ,IAAI,CAAC,CAAA,EAAE,CAAE,CAAA,CAAE,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,GAC7H,CAAC,EAAE,IAAA,EAAM,aAAA,EAAe,QAAA,EAAU,GAAA,CAAI,OAAA,CAAQ,QAAA,GAAW,wBAAwB,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAC,CAAA,CAAA,GAAK,GAAA,CAAI,QAAQ,IAAI,CAAA,EAAG,EAAE,CAAA,EAAG,CAAA;AAEvI,EAAA,MAAM,mBAAmB,GAAA,CAAI,OAAA,CAAQ,aAAa,CAAA,CAAE,GAAA,CAAI,CAAC,EAAA,MAAQ;AAAA,IAC/D,IAAA,EAAM,GAAA,CAAI,EAAA,CAAG,IAAA,EAAM,cAAc,CAAA;AAAA,IACjC,MAAA,EAAQ,GAAA,CAAI,EAAA,CAAG,WAAA,EAAa,0BAA0B,CAAA;AAAA,IACtD,aAAA,EAAe,GAAA,CAAI,EAAA,CAAG,iBAAA,EAAmB,EAAE,CAAA;AAAA,IAC3C,OAAA,EAAS;AAAA,GACX,CAAE,CAAA;AAEF,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA,EAAM,SAAA;AAAA,IACN,IAAA,EAAM,GAAA,CAAI,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAA;AAAA,IAC1B,aAAA,EAAe,CAAC,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,IAAK,EAAE,CAAA,CAAE,MAAA,CAAO,OAAO,CAAA;AAAA,IACvD,QAAA,EAAU,EAAE,GAAA,EAAK,IAAA,CAAK,OAAA,CAAQ,QAAQ,CAAA,EAAG,GAAA,EAAK,EAAC,EAAG,YAAA,EAAc,EAAC,EAAE;AAAA,IACnE,iBAAA,EAAmB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAC,CAAC,CAAA;AAAA,IAC7E,UAAA,EAAY,QAAA;AAAA,IACZ,UAAA,EAAY,uBAAuB,OAAO,CAAA;AAAA,IAC1C,gBAAA,EAAkB,UAAA;AAAA,IAClB,WAAA,EAAa;AAAA,MACX,mBAAA,EAAqB,gBAAA;AAAA,MACrB,kBAAA,EAAoB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAC,CAAC;AAAA,KAChF;AAAA,IACA,UAAA;AAAA,IACA,UAAA,EAAY,IAAA;AAAA,IACZ,WAAA,EAAa,EAAA;AAAA,IACb,wBAAA,EAA0B,EAAA;AAAA;AAAA,IAC1B,GAAG,iBAAA,CAAkB,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,KAAK,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,iBAAA,EAAmB,EAAE,CAAC,CAAC,CAAA,EAAG,sBAAA,CAAuB,OAAO,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA,IAIjH,SAAS,OAAA,CAAQ,OAAA,KAAY,QAAQ,GAAA,CAAI,OAAA,CAAQ,eAAe,CAAA,KAAM,cAAA;AAAA,IACtE,wBAAwB,EAAC;AAAA,IACzB,aAAA,EAAe;AAAA,MACb,GAAA,EAAK,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAAA,MACpB,GAAA,EAAK,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA;AAAA,MACpB,GAAA,EAAK,IAAA,CAAK,OAAA,CAAQ,GAAG;AAAA,KACvB;AAAA,IACA,QAAA,EAAU,EAAA;AAAA,IACV,gBAAA,EAAkB;AAAA,GACpB;AACF;AAEA,SAAS,eAAA,CACP,CAAA,EAAQ,GAAA,EAAa,KAAA,EAAmB,OAAmB,SAAA,EACjD;AACV,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,CAAA,CAAE,KAAK,CAAA;AACzB,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,IAAI,IAAA,GAAO,EAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,KAAA,CAAM,CAAC,GAAG,GAAG,CAAA;AAI/C,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,eAAA,CAAgB,IAAA,CAAK,GAAA,GAAM,GAAA,GAAM,CAAC,CAAA;AAClC,MAAA;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,IAAA,CAAK,SAAS,EAAE,CAAA;AACxB,IAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AACnB,IAAA,IAAI,IAAA,EAAM,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,IAAA,EAAM,EAAA,EAAI,QAAA,CAAS,EAAA,EAAI,IAAA,EAAM,MAAA,EAAQ,MAAA,EAAQ,uBAAuB,CAAA;AACjG,IAAA,IAAA,GAAO,QAAA,CAAS,EAAA;AAAA,EAClB;AACA,EAAA,KAAA,MAAW,GAAA,IAAO,IAAA,CAAK,CAAA,CAAE,UAAU,CAAA,EAAG;AACpC,IAAA,MAAM,WAAW,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,eAAe,GAAG,CAAA;AACvD,IAAA,IAAI,YAAY,OAAA,CAAQ,CAAC,GAAG,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,QAAA,CAAS,IAAI,EAAA,EAAI,OAAA,CAAQ,CAAC,CAAA,EAAG,IAAA,EAAM,QAAQ,MAAA,EAAQ,qBAAA,GAAwB,KAAK,CAAA;AAAA,EACjI;AACA,EAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG,SAAA,CAAU,KAAK,EAAE,EAAA,EAAI,IAAA,GAAO,GAAA,EAAK,IAAA,EAAM,GAAA,CAAI,EAAE,IAAA,EAAM,GAAG,CAAA,EAAG,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,IAAI,CAAA,CAAE,WAAA,EAAa,EAAE,CAAA,EAAG,CAAA;AACvI,EAAA,OAAO,OAAA;AACT;AAoBA,SAAS,cAAA,CAAe,QAAa,IAAA,EAAuC;AAC1E,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,YAA4B,EAAC;AACnC,EAAA,MAAM,EAAA,GAAM,MAAA,CAAO,aAAA,IAAiB,EAAC;AACrC,EAAA,MAAM,EAAA,GAAM,MAAA,CAAO,eAAA,IAAmB,EAAC;AACvC,EAAA,MAAM,SAAA,GAAY,GAAA,CAAI,MAAA,CAAO,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AAE5D,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,EAAA,CAAG,QAAQ,CAAA;AAChC,EAAA,MAAM,WAAA,GAAc,GAAA,CAAI,EAAA,CAAG,QAAQ,CAAA;AACnC,EAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAI,WAAA,CAAY,IAAI,CAAC,CAAA,KAAM,CAAC,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,EAAG,CAAC,CAAC,CAAC,CAAA;AAClE,EAAA,MAAM,eAAA,GAAkB,IAAI,GAAA,CAAI,WAAA,CAAY,MAAM,CAAA;AAGlD,EAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,IAAA,MAAMA,eAAAA,uBAAqB,GAAA,EAAsB;AACjD,IAAA,KAAA,MAAW,KAAK,WAAA,EAAa;AAC3B,MAAA,MAAM,IAAA,GAAO,oBAAoB,CAAC,CAAA;AAClC,MAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,MAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAS,GAAI,kBAAA,CAAmB,GAAG,eAAe,CAAA;AACpE,MAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,QAAA,IAAA,CAAK,sBAAA,GAAyB,QAAA;AAC9B,QAAAA,eAAAA,CAAe,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI,QAAQ,CAAA;AAAA,MACtC;AACA,MAAA,KAAA,MAAW,CAAA,IAAK,QAAA,EAAU,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,CAAA,EAAG,EAAA,EAAI,IAAA,CAAK,EAAA,EAAI,IAAA,EAAM,WAAA,EAAa,MAAA,EAAQ,wBAAwB,CAAA;AAAA,IAClH;AACA,IAAA,yBAAA,CAA0B,MAAMA,eAAc,CAAA;AAC9C,IAAA,QAAA,CAAS,QAAQ,CAAC,CAAA,EAAG,OAAO,eAAA,CAAgB,CAAA,EAAG,IAAI,CAAA,CAAE,EAAA,EAAI,GAAA,GAAM,IAAA,CAAK,KAAK,CAAC,CAAC,GAAG,KAAA,EAAO,KAAA,EAAO,SAAS,CAAC,CAAA;AACtG,IAAA,wBAAA,CAAyB,MAAA,EAAQ,KAAA,EAAO,KAAW,CAAA;AACnD,IAAA,kBAAA,CAAmB,KAAK,CAAA;AACxB,IAAA,OAAO,sBAAA,CAAuB;AAAA,MAC5B,cAAA,EAAgB,CAAA;AAAA,MAChB,MAAA,EAAQ,WAAA,CAAY,IAAA,EAAM,cAAc,CAAA;AAAA,MACxC,cAAA,EAAgB,KAAA;AAAA,MAChB,gBAAA,EAAkB,KAAA;AAAA,MAClB,mBAAA,EAAqB,SAAA;AAAA,MACrB,QAAQ;AAAC,KACV,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,YAAA,uBAAmB,GAAA,EAAoB;AAC7C,EAAA,QAAA,CAAS,QAAQ,CAAC,CAAA,EAAG,EAAA,KAAO,YAAA,CAAa,IAAI,GAAA,CAAI,CAAA,CAAE,EAAA,EAAI,GAAA,GAAM,KAAK,EAAA,GAAK,CAAC,CAAC,CAAA,EAAG,EAAE,CAAC,CAAA;AAC/E,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAsB;AACnD,EAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACxB,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA;AACpB,IAAA,gBAAA,CAAiB,IAAI,GAAA,EAAK,GAAA,CAAI,CAAA,CAAE,KAAK,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,GAAA,CAAI,EAAE,EAAE,CAAC,CAAA,CAAE,MAAA,CAAO,OAAO,CAAC,CAAA;AAAA,EAC9E;AAEA,EAAA,MAAM,UAA0B,EAAC;AACjC,EAAA,MAAM,eAAA,uBAAsB,GAAA,EAAY;AACxC,EAAA,MAAM,eAAA,uBAAsB,GAAA,EAAY;AAExC,EAAA,MAAM,sBAAA,uBAA6B,GAAA,EAAoB;AAEvD,EAAA,MAAM,cAAA,uBAAqB,GAAA,EAAsB;AACjD,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,MAAA,CAAO,KAAK,CAAA;AAEjC,EAAA,KAAA,MAAW,MAAM,SAAA,EAAW;AAC1B,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,EAAA,CAAG,EAAE,CAAA;AACrB,IAAA,MAAM,QAAQ,GAAA,CAAI,EAAA,CAAG,KAAA,EAAO,OAAA,CAAQ,SAAS,CAAC,CAAA;AAC9C,IAAA,MAAM,eAAyB,EAAC;AAChC,IAAA,MAAM,MAAA,GAAS,GAAA,CAAI,EAAA,CAAG,UAAU,CAAA;AAGhC,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,IAAI,CAAA,CAAE,MAAA,EAAQ,KAAK,CAAA,KAAM,KAAK,CAAA;AAChE,IAAA,MAAM,QAAA,GAAW,OAAO,MAAA,CAAO,CAAC,MAAM,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,KAAM,UAAU,CAAA;AAClE,IAAA,MAAM,QAAA,GAAW,OAAO,MAAA,CAAO,CAAC,MAAM,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,KAAM,SAAS,CAAA;AACjE,IAAA,KAAA,MAAW,KAAA,IAAS,CAAC,GAAG,IAAA,EAAM,GAAG,QAAA,EAAU,GAAG,QAAQ,CAAA,EAAG;AACvD,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,KAAA,CAAM,UAAU,CAAA;AAChC,MAAA,MAAM,CAAA,GAAI,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA;AAC7B,MAAA,IAAI,CAAC,CAAA,EAAG;AACN,QAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,8BAAA,GAAiC,MAAM,gBAAgB,CAAA;AAAA,MAC1F;AACA,MAAA,IAAI,GAAA,CAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,CAAA,KAAM,cAAc,CAAC,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AACxE,QAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,6BAAA,GAAgC,MAAM,uCAAuC,CAAA;AAAA,MAChH;AACA,MAAA,IAAI,GAAA,CAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,MAAM,SAAA,EAAW;AAC1C,QAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,UAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,oBAAA,GAAuB,MAAM,oFAAoF,CAAA;AAAA,QACpJ;AACA,QAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5B,UAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,gCAAA,GAAmC,MAAM,gBAAgB,CAAA;AAAA,QAC5F;AACA,QAAA,MAAM,MAAA,GAAS,UAAU,SAAA,CAAU,CAAC,QAAQ,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,KAAM,GAAG,CAAA;AAC/D,QAAA,MAAM,SAAA,GAAY,UAAU,SAAA,CAAU,CAAC,QAAQ,GAAA,CAAI,GAAA,CAAI,UAAU,CAAA,CAAE,IAAA,CAAK,CAAC,OAAO,GAAA,CAAI,EAAA,CAAG,UAAU,CAAA,KAAM,GAAA,IAAO,GAAA,CAAI,GAAG,MAAA,EAAQ,KAAK,CAAA,KAAM,SAAS,CAAC,CAAA;AAClJ,QAAA,IAAI,YAAY,CAAA,EAAG;AACjB,UAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,oBAAA,GAAuB,MAAM,4DAA4D,CAAA;AAAA,QAC5H;AACA,QAAA,IAAI,UAAU,SAAA,EAAW;AACvB,UAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,oBAAA,GAAuB,MAAM,kEAAkE,CAAA;AAAA,QAClI;AACA,QAAA,eAAA,CAAgB,IAAI,GAAG,CAAA;AAOvB,QAAA,MAAM,QAAA,GAAW,oBAAoB,CAAC,CAAA;AACtC,QAAA,QAAA,CAAS,EAAA,GAAK,GAAA,GAAM,QAAA,GAAW,IAAA,CAAK,KAAK,CAAA;AACzC,QAAA,QAAA,CAAS,IAAA,GAAO,GAAA,CAAI,CAAA,CAAE,IAAA,EAAM,GAAG,CAAA,GAAI,YAAA;AACnC,QAAA,QAAA,CAAS,QAAA,GAAW,GAAA;AACpB,QAAA,QAAA,CAAS,gBAAA,GAAmB,SAAA;AAC5B,QAAA,QAAA,CAAS,iBAAA,GAAoB,IAAA,CAAK,GAAA,CAAI,sBAAA,CAAuB,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,iBAAA,GAAoB,sBAAA,CAAuB,OAAO,CAAC,CAAA;AAC3I,QAAA,QAAA,CAAS,UAAA,GAAa,KAAA;AACtB,QAAA,QAAA,CAAS,UAAA,GAAa,YAAA;AACtB,QAAA,QAAA,CAAS,gBAAgB,CAAC,GAAA,CAAI,CAAA,CAAE,IAAI,KAAK,GAAG,CAAA;AAC5C,QAAA,QAAA,CAAS,QAAA,GAAW,EAAE,GAAA,EAAK,IAAA,CAAK,CAAA,CAAE,QAAQ,CAAA,EAAG,GAAA,EAAK,EAAC,EAAG,YAAA,EAAc,EAAC,EAAE;AACvE,QAAA,QAAA,CAAS,cAAc,EAAE,mBAAA,EAAqB,EAAC,EAAG,oBAAoB,CAAA,EAAE;AACxE,QAAA,QAAA,CAAS,cAAA,GAAiB,EAAE,GAAA,EAAK,QAAA,CAAS,iBAAA,EAAmB,KAAK,QAAA,CAAS,iBAAA,EAAmB,GAAA,EAAK,QAAA,CAAS,iBAAA,EAAkB;AAC9H,QAAA,QAAA,CAAS,4BAA4B,EAAC;AACtC,QAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AACnB,QAAA,YAAA,CAAa,IAAA,CAAK,SAAS,EAAE,CAAA;AAC7B,QAAA,sBAAA,CAAuB,GAAA,CAAI,GAAA,EAAK,QAAA,CAAS,EAAE,CAAA;AAC3C,QAAA;AAAA,MACF;AACA,MAAA,IAAI,CAAC,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC7B,QAAA,MAAM,IAAA,GAAO,oBAAoB,CAAC,CAAA;AAClC,QAAA,IAAA,CAAK,QAAA,GAAW,GAAA;AAChB,QAAA,IAAA,CAAK,gBAAA,GAAmB,KAAA;AACxB,QAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,QAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,CAAA,CAAE,aAAa,CAAA,EAAG;AAKrC,UAAA,IAAI,CAAC,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,EAAG;AACvB,YAAA,IAAI,CAAC,eAAe,GAAA,CAAI,GAAG,GAAG,cAAA,CAAe,GAAA,CAAI,GAAA,EAAK,EAAE,CAAA;AACxD,YAAA,cAAA,CAAe,GAAA,CAAI,GAAG,CAAA,CAAG,IAAA,CAAK,CAAC,CAAA;AAC/B,YAAA,IAAA,CAAK,sBAAA,CAAuB,KAAK,CAAC,CAAA;AAClC,YAAA;AAAA,UACF;AAQA,UAAA,MAAM,eAAA,GAAkB,sBAAA,CAAuB,GAAA,CAAI,CAAC,CAAA;AACpD,UAAA,IAAI,CAAC,eAAA,CAAgB,GAAA,CAAI,CAAC,CAAA,IAAK,CAAC,eAAA,EAAiB;AAC/C,YAAA,MAAM,IAAI,MAAM,UAAA,GAAa,GAAA,GAAM,aAAa,GAAA,GAAM,qBAAA,GAAwB,IAAI,6EAA6E,CAAA;AAAA,UACjK;AACA,UAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,eAAA,CAAgB,IAAI,CAAC,CAAA,GAAI,CAAA,GAAI,eAAA,EAAkB,IAAI,GAAA,EAAK,IAAA,EAAM,WAAA,EAAa,MAAA,EAAQ,wBAAwB,CAAA;AAAA,QAChI;AACA,QAAA,eAAA,CAAgB,IAAI,GAAG,CAAA;AACvB,QAAA,YAAA,CAAa,KAAK,GAAG,CAAA;AAAA,MACvB,WAAW,GAAA,CAAI,KAAA,CAAM,MAAA,EAAQ,KAAK,MAAM,KAAA,EAAO;AAG7C,QAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,GAAM,cAAA,GAAiB,MAAM,yEAAyE,CAAA;AAAA,MACnI,CAAA,MAAO;AAIL,QAAA,MAAM,SAAA,GAAY,GAAA,GAAM,OAAA,GAAU,IAAA,CAAK,KAAK,CAAA;AAC5C,QAAA,MAAM,IAAA,GAAO,oBAAoB,CAAC,CAAA;AAClC,QAAA,IAAA,CAAK,EAAA,GAAK,SAAA;AACV,QAAA,IAAA,CAAK,IAAA,GAAO,GAAA,CAAI,CAAA,CAAE,IAAA,EAAM,GAAG,CAAA,GAAI,aAAA;AAC/B,QAAA,IAAA,CAAK,QAAA,GAAW,GAAA;AAChB,QAAA,IAAA,CAAK,gBAAA,GAAmB,UAAA;AAIxB,QAAA,IAAA,CAAK,oBAAoB,IAAA,CAAK,GAAA;AAAA,UAC5B,sBAAA,CAAuB,aAAA;AAAA,UACvB,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,iBAAA,GAAoB,uBAAuB,eAAe;AAAA,SAC5E;AACA,QAAA,IAAA,CAAK,4BAA4B,CAAC;AAAA,UAChC,UAAA,EAAY,KAAA;AAAA,UAAO,QAAA,EAAU,KAAA;AAAA,UAC7B,eAAe,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,iBAAA,GAAoB,uBAAuB,iBAAiB,CAAA;AAAA,UAC3F,MAAA,EAAQ;AAAA,SACT,CAAA;AACD,QAAA,IAAA,CAAK,gBAAgB,CAAC,GAAA,CAAI,CAAA,CAAE,IAAI,KAAK,GAAG,CAAA;AACxC,QAAA,KAAA,CAAM,IAAA,CAAK,EAAE,IAAA,EAAM,GAAA,EAAK,EAAA,EAAI,SAAA,EAAW,IAAA,EAAM,WAAA,EAAa,MAAA,EAAQ,sBAAA,GAAyB,GAAA,GAAM,YAAA,GAAe,KAAK,CAAA;AACrH,QAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,QAAA,YAAA,CAAa,KAAK,SAAS,CAAA;AAAA,MAC7B;AAAA,IACF;AACA,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,EAAA,CAAG,WAAW,CAAA,CACnC,OAAO,CAAC,GAAA,KAAQ,YAAA,CAAa,GAAA,CAAI,GAAG,CAAC,EACrC,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,YAAA,CAAa,GAAA,CAAI,CAAC,CAAA,GAAK,YAAA,CAAa,GAAA,CAAI,CAAC,CAAE,CAAA;AAC7D,IAAA,OAAA,CAAQ,KAAK,EAAE,EAAA,EAAI,KAAK,KAAA,EAAO,IAAA,EAAM,IAAI,EAAA,CAAG,IAAA,EAAM,GAAG,CAAA,EAAG,mBAAmB,GAAA,CAAI,EAAA,CAAG,kBAAkB,CAAA,EAAG,YAAA,EAAc,YAAY,CAAA;AAAA,EACnI;AAEA,EAAA,yBAAA,CAA0B,MAAM,cAAc,CAAA;AAE9C,EAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACxB,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAC5B,IAAA,IAAI,GAAA,IAAO,YAAY,GAAA,CAAI,GAAG,KAAK,CAAC,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAA,EAAG;AAC5D,MAAA,MAAM,IAAI,KAAA,CAAM,UAAA,GAAa,GAAA,GAAM,uEAAuE,CAAA;AAAA,IAC5G;AAAA,EACF;AAEA,EAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,CAAC,CAAA;AACjE,EAAA,MAAM,kBAAkB,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,KAAM,GAAA,CAAI,EAAE,EAAE,CAAC,CAAA,CAAE,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,IAAO,CAAC,YAAA,CAAa,GAAA,CAAI,GAAG,CAAC,CAAA;AACpG,EAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC9B,IAAA,MAAM,IAAI,KAAA,CAAM,qCAAA,GAAwC,gBAAgB,IAAA,CAAK,IAAI,IAAI,gBAAgB,CAAA;AAAA,EACvG;AAIA,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC,GAAA,KAAQ,CAAC,eAAA,CAAgB,GAAA,CAAI,GAAG,CAAC,CAAA;AACxF,EAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,IAAA,cAAA;AAAA,MAAe,IAAA;AAAA,MAAM,gBAAA;AAAA,MAAkB,OAAA;AAAA,MACrC,yBAAyB,OAAA,CAAQ,MAAA,GAAS,uFAAA,GACxC,OAAA,CAAQ,KAAK,IAAI,CAAA;AAAA,MACnB;AAAA,KACF;AAAA,EACF;AAGA,EAAA,MAAM,eAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,OAAO,OAAA,EAAS;AACzB,IAAA,MAAM,GAAA,GAAgB,CAAC,GAAG,GAAA,CAAI,YAAY,CAAA;AAC1C,IAAA,KAAA,MAAW,GAAA,IAAO,IAAI,UAAA,EAAY;AAChC,MAAA,MAAM,CAAA,GAAI,SAAS,IAAA,CAAK,CAAC,MAAM,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,KAAM,GAAG,CAAA;AAChD,MAAA,MAAM,UAAU,eAAA,CAAgB,CAAA,EAAG,GAAA,EAAK,KAAA,EAAO,OAAO,SAAS,CAAA;AAC/D,MAAA,KAAA,MAAW,OAAO,OAAA,EAAS;AACzB,QAAA,MAAM,OAAO,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,GAAG,CAAA;AAC3C,QAAA,IAAA,CAAK,WAAW,GAAA,CAAI,EAAA;AAAA,MACtB;AACA,MAAA,GAAA,CAAI,IAAA,CAAK,GAAG,OAAO,CAAA;AAAA,IACrB;AACA,IAAA,IAAI,GAAA,CAAI,WAAW,CAAA,EAAG;AAIpB,MAAA,MAAM,IAAI,KAAA,CAAM,QAAA,GAAW,GAAA,CAAI,KAAK,mHAAmH,CAAA;AAAA,IACzJ;AACA,IAAA,YAAA,CAAa,KAAK,GAAG,CAAA;AAAA,EACvB;AACA,EAAA,MAAM,iBAAiB,mBAAA,EAAoB;AAC3C,EAAA,IAAI,cAAA,CAAe,SAAS,CAAA,EAAG;AAC7B,IAAA,cAAA;AAAA,MAAe,IAAA;AAAA,MAAM,iBAAA;AAAA,MAAmB,SAAA;AAAA,MACtC,wFAAA,GAA2F,cAAA,CAAe,IAAA,CAAK,IAAI,CAAA;AAAA,MACnH;AAAA,KACF;AAAA,EACF;AASA,EAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAY,YAAA,CAAa,MAAM,CAAA;AAClD,EAAA,MAAM,eAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,OAAO,YAAA,EAAc;AAC9B,IAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AACpB,MAAA,MAAM,IAAI,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AACvC,MAAA,IAAI,CAAA,EAAG,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA;AAAA,IAC5B;AAAA,EACF;AACA,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,IAAI,CAAC,MAAA,CAAO,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,EAAG,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA;AACjE,EAAA,KAAA,CAAM,MAAA,GAAS,CAAA;AACf,EAAA,KAAA,CAAM,IAAA,CAAK,GAAG,YAAY,CAAA;AAI1B,EAAA,MAAM,QAAA,GAAW,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,IAAA,GAAO,GAAA,GAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AAC9D,EAAA,MAAM,QAAA,GAAW,CAAC,IAAA,EAAc,EAAA,EAAY,MAAA,KAAmB;AAC7D,IAAA,MAAM,GAAA,GAAM,OAAO,GAAA,GAAM,EAAA;AACzB,IAAA,IAAI,QAAA,CAAS,GAAA,CAAI,GAAG,CAAA,EAAG;AACvB,IAAA,QAAA,CAAS,IAAI,GAAG,CAAA;AAChB,IAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,IAAI,IAAA,EAAM,WAAA,EAAa,QAAQ,CAAA;AAAA,EACpD,CAAA;AACA,EAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACxB,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAC5B,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAC5B,IAAA,IAAI,CAAC,WAAA,CAAY,GAAA,CAAI,GAAG,CAAA,EAAG;AAC3B,IAAA,MAAM,aAAa,gBAAA,CAAiB,GAAA,CAAI,GAAG,CAAA,IAAK,IAAI,CAAC,CAAA;AACrD,IAAA,IAAI,CAAC,SAAA,EAAW;AAChB,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,KAAK,CAAA;AAKhC,IAAA,MAAM,UAAA,GAAa,KAAA,CAAM,IAAA,CAAK,CAAC,EAAA,KAAO,EAAA,CAAG,IAAA,KAAS,SAAA,IAAa,EAAA,CAAG,WAAA,KAAgB,GAAA,IAAO,EAAA,CAAG,qBAAqB,KAAK,CAAA;AACtH,IAAA,MAAM,WAAW,KAAA,CAAM,IAAA,CAAK,CAAC,EAAA,KAAO,EAAA,CAAG,OAAO,SAAS,CAAA;AACvD,IAAA,MAAM,cAAA,GAAiB,UAAA,GAAa,OAAA,CAAQ,SAAA,CAAU,CAAC,OAAO,EAAA,CAAG,EAAA,KAAO,UAAA,CAAW,QAAQ,CAAA,GAAI,EAAA;AAC/F,IAAA,MAAM,YAAA,GAAe,QAAA,GAAW,OAAA,CAAQ,SAAA,CAAU,CAAC,OAAO,EAAA,CAAG,EAAA,KAAO,QAAA,CAAS,QAAQ,CAAA,GAAI,EAAA;AACzF,IAAA,IAAI,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,cAAA,GAAiB,YAAA,EAAc;AACvD,MAAA,cAAA;AAAA,QAAe,IAAA;AAAA,QAAM,mBAAA;AAAA,QAAqB,SAAA;AAAA,QACxC,6BAAA,GAAgC,GAAA,GAAM,kDAAA,GAAqD,SAAA,GAAY,oCAAA;AAAA,QACvG,CAAC,KAAK,SAAS;AAAA,OAAC;AAClB,MAAA;AAAA,IACF;AACA,IAAA,QAAA,CAAS,GAAA,EAAK,WAAW,GAAA,CAAI,CAAA,CAAE,cAAc,aAAa,CAAA,GAAI,IAAA,GAAO,KAAA,GAAQ,GAAG,CAAA;AAIhF,IAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,MAAA,IAAI,CAAA,CAAE,IAAA,KAAS,SAAA,IAAa,CAAA,CAAE,gBAAgB,GAAA,IAAO,CAAA,CAAE,gBAAA,KAAqB,UAAA,IAAc,CAAA,CAAE,QAAA,KAAa,GAAA,CAAI,CAAA,CAAE,QAAQ,CAAA,EAAG;AACxH,QAAA,QAAA,CAAS,CAAA,CAAE,EAAA,EAAI,SAAA,EAAW,wBAAA,GAA2B,QAAQ,GAAG,CAAA;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AAEA,EAAA,kBAAA,CAAmB,KAAK,CAAA;AAQxB,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAC,CAAC,CAAC,CAAA;AACxD,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAoB;AAC3C,EAAA,KAAA,MAAW,CAAA,IAAK,OAAO,KAAA,MAAW,CAAA,IAAK,EAAE,QAAA,EAAU,GAAA,IAAO,EAAC,EAAG;AAC5D,IAAA,MAAM,GAAA,GAAM,WAAW,CAAC,CAAA;AACxB,IAAA,IAAI,CAAC,WAAW,GAAA,CAAI,GAAG,GAAG,UAAA,CAAW,GAAA,CAAI,GAAA,EAAK,CAAA,CAAE,EAAE,CAAA;AAAA,EACpD;AACA,EAAA,KAAA,MAAW,KAAK,KAAA,EAAO;AACrB,IAAA,KAAA,MAAW,CAAA,IAAK,CAAA,CAAE,QAAA,EAAU,YAAA,IAAgB,EAAC,EAAG;AAC9C,MAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,UAAA,CAAW,CAAC,CAAC,CAAA;AACxC,MAAA,IAAI,CAAC,GAAA,IAAO,GAAA,KAAQ,CAAA,CAAE,EAAA,EAAI;AAC1B,MAAA,MAAM,UAAU,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,GAAG,CAAA;AAS9C,MAAA,MAAM,OAAA,GAAU,CAAC,EAAA,KAAuB;AACtC,QAAA,MAAM,KAAK,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AACxC,QAAA,OAAO,EAAA,GAAK,QAAQ,SAAA,CAAU,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,EAAA,CAAG,QAAQ,CAAA,GAAI,EAAA;AAAA,MAC/D,CAAA;AACA,MAAA,MAAM,gBAAA,GAAmB,CAAA,CAAE,IAAA,KAAS,SAAA,IAAa,SAAS,IAAA,KAAS,cAAA;AACnE,MAAA,MAAM,SAAA,GAAY,QAAQ,GAAG,CAAA;AAC7B,MAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,CAAA,CAAE,EAAE,CAAA;AAC5B,MAAA,IAAI,gBAAA,IAAqB,OAAA,CAAQ,MAAA,GAAS,CAAA,IAAK,YAAY,OAAA,EAAU;AACnE,QAAA,cAAA;AAAA,UAAe,IAAA;AAAA,UACb,mBAAmB,2BAAA,GAA8B,qBAAA;AAAA,UACjD,SAAA;AAAA,UACA,qCAAqC,GAAA,GAAM,uCAAA,GAA0C,CAAA,CAAE,EAAA,GACvF,gBAAgB,CAAA,GAAI,mDAAA;AAAA,UACpB,CAAC,GAAA,EAAK,CAAA,CAAE,EAAE;AAAA,SACZ;AACA,QAAA;AAAA,MACF;AACA,MAAA,IAAI,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA,GAAK,UAAU,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,WAAa,GAAA,EAAK,CAAA,CAAE,EAAA,EAAI,wBAAA,GAA2B,IAAI,GAAG,CAAA;AAAA,IACxG;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,CAAC,KAAK,CAAA,MAAO;AAAA,IACtC,IAAI,GAAA,CAAI,EAAA;AAAA,IACR,OAAO,GAAA,CAAI,KAAA;AAAA,IACX,MAAM,GAAA,CAAI,IAAA;AAAA,IACV,kBAAA,EAAoB,GAAA,CAAI,iBAAA,IAAqB,QAAA,IAAY,IAAI,CAAA,CAAA,GAAK,cAAA;AAAA,IAClE,QAAA,EAAU,aAAa,CAAC;AAAA,GAC1B,CAAE,CAAA;AAEF,EAAA,OAAO,sBAAA,CAAuB;AAAA,IAC5B,cAAA,EAAgB,CAAA;AAAA,IAChB,MAAA,EAAQ,WAAA,CAAY,IAAA,EAAM,cAAc,CAAA;AAAA,IACxC,cAAA,EAAgB,KAAA;AAAA,IAChB,gBAAA,EAAkB,KAAA;AAAA,IAClB,mBAAA,EAAqB,SAAA;AAAA,IACrB;AAAA,GACD,CAAA;AACH;AAGA,SAAS,wBAAA,CAAyB,MAAA,EAAa,KAAA,EAAmB,KAAA,EAAmB,YAAA,EAA6B;AAChH,EAAA,MAAM,aAAa,IAAI,GAAA,CAAI,MAAM,MAAA,CAAO,CAAC,MAAM,CAAA,CAAE,IAAA,KAAS,SAAS,CAAA,CAAE,IAAI,CAAC,CAAA,KAAM,EAAE,WAAA,IAAe,CAAA,CAAE,EAAE,CAAC,CAAA;AACtG,EAAA,KAAA,MAAW,CAAA,IAAK,GAAA,CAAI,MAAA,CAAO,KAAK,CAAA,EAAG;AACjC,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAClC,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAClC,IAAA,IAAI,CAAC,UAAA,CAAW,GAAA,CAAI,SAAS,CAAA,EAAG;AAChC,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,CAAA,CAAE,KAAA,EAAO,KAAK,CAAA;AAChC,IAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,eAAe,SAAS,CAAA;AAC9D,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,KAAA,CAAM,KAAK,EAAE,IAAA,EAAM,WAAW,EAAA,EAAI,SAAA,CAAU,IAAI,IAAA,EAAM,WAAA,EAAa,MAAA,EAAQ,GAAA,CAAI,EAAE,YAAA,EAAc,aAAa,IAAI,IAAA,GAAO,KAAA,GAAQ,KAAK,CAAA;AACpI,MAAA,IAAoB,CAAC,SAAA,CAAU,UAAA,EAAY;AACzC,QAAA,SAAA,CAAU,UAAA,GAAc,CAAC,KAAA,EAAO,KAAA,EAAO,KAAK,CAAA,CAAY,QAAA,CAAS,KAAc,CAAA,GAAK,KAAA,GAAmC,KAAA;AAAA,MACzH;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBAAA,CAAkB,IAAS,IAAA,EAAuC;AACzE,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,YAA4B,EAAC;AACnC,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,GAAA,CAAI,GAAG,QAAQ,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,KAAM,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAC,CAAA;AACjE,EAAA,MAAM,cAAA,uBAAqB,GAAA,EAAsB;AACjD,EAAA,KAAA,MAAW,CAAA,IAAK,GAAA,CAAI,EAAA,CAAG,QAAQ,CAAA,EAAG;AAChC,IAAA,MAAM,IAAA,GAAO,oBAAoB,CAAC,CAAA;AAClC,IAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AACf,IAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAS,GAAI,kBAAA,CAAmB,GAAG,UAAU,CAAA;AAC/D,IAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,MAAA,IAAA,CAAK,sBAAA,GAAyB,QAAA;AAC9B,MAAA,cAAA,CAAe,GAAA,CAAI,IAAA,CAAK,EAAA,EAAI,QAAQ,CAAA;AAAA,IACtC;AACA,IAAA,KAAA,MAAW,KAAK,QAAA,EAAU;AACxB,MAAA,IAAI,CAAA,KAAM,KAAK,EAAA,EAAI;AACjB,QAAA,KAAA,CAAM,IAAA,CAAK;AAAA,UACT,IAAA,EAAM,CAAA;AAAA,UACN,IAAI,IAAA,CAAK,EAAA;AAAA,UACT,IAAA,EAAM,WAAA;AAAA,UACN,MAAA,EAAQ,CAAA,sBAAA,EAAyB,CAAC,CAAA,2BAAA,EAA8B,KAAK,EAAE,CAAA;AAAA,SACxE,CAAA;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,EAAA,yBAAA,CAA0B,MAAM,cAAc,CAAA;AAG9C,EAAA,IAAI,GAAA,CAAI,EAAA,CAAG,aAAa,CAAA,CAAE,SAAS,CAAA,EAAG;AACpC,IAAA,MAAM,QAAA,uBAAe,GAAA,EAAoB;AACzC,IAAA,GAAA,CAAI,GAAG,aAAa,CAAA,CAAE,OAAA,CAAQ,CAAC,GAAG,GAAA,KAAQ;AACxC,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,CAAA,CAAE,UAAU,CAAA;AAC5B,MAAA,IAAI,GAAA,WAAc,GAAA,CAAI,GAAA,EAAK,IAAI,CAAA,CAAE,KAAA,EAAO,GAAA,GAAM,CAAC,CAAC,CAAA;AAAA,IAClD,CAAC,CAAA;AACD,IAAA,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAA,CAAO,SAAS,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA,IAAK,QAAQ,QAAA,CAAS,GAAA,CAAI,CAAA,CAAE,EAAE,KAAK,GAAA,CAAI,CAAA;AAAA,EAChF;AAEA,EAAA,IAAI,EAAA,GAAK,CAAA;AACT,EAAA,KAAA,MAAW,GAAA,IAAO,GAAA,CAAI,EAAA,CAAG,UAAU,CAAA,EAAG;AACpC,IAAA,EAAA,IAAM,CAAA;AACN,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,GAAA,CAAI,WAAW,EAAE,MAAA,CAAO,CAAC,EAAA,KAAO,KAAA,CAAM,KAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAC,CAAA;AAC/E,IAAA,IAAI,GAAA,CAAI,SAAS,CAAA,EAAG;AAClB,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,EAAA,EAAI,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,QAAQ,EAAE,CAAA;AAAA,QAC1B,IAAA,EAAM,GAAA,CAAI,GAAA,CAAI,IAAA,EAAM,cAAc,EAAE,CAAA;AAAA,QACpC,QAAA,EAAU,GAAA;AAAA,QACV,SAAA,EAAW,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,EAAE;AAAA,OACnC,CAAA;AAAA,IACH;AAAA,EACF;AACA,EAAA,kBAAA,CAAmB,KAAK,CAAA;AACxB,EAAA,OAAO,sBAAA,CAAuB;AAAA,IAC5B,cAAA,EAAgB,CAAA;AAAA,IAChB,MAAA,EAAQ,WAAA,CAAY,IAAA,EAAM,iBAAiB,CAAA;AAAA,IAC3C,cAAA,EAAgB,KAAA;AAAA,IAChB,gBAAA,EAAkB,KAAA;AAAA,IAClB,mBAAA,EAAqB,SAAA;AAAA,IACrB,QAAQ;AAAC,GACV,CAAA;AACH;AAEA,SAAS,eAAA,CAAgB,IAAS,IAAA,EAAuC;AACvE,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,YAA4B,EAAC;AACnC,EAAA,MAAM,WAAA,GAAc,GAAA,CAAI,EAAA,CAAG,QAAQ,CAAA;AACnC,EAAA,WAAA,CAAY,QAAQ,CAAC,CAAA,EAAG,OAAO,eAAA,CAAgB,CAAA,EAAG,IAAI,CAAA,CAAE,EAAA,EAAI,GAAA,GAAM,IAAA,CAAK,KAAK,CAAC,CAAC,GAAG,KAAA,EAAO,KAAA,EAAO,SAAS,CAAC,CAAA;AAIzG,EAAA,MAAM,QAAA,GAAW,YACd,MAAA,CAAO,CAAC,MAAM,GAAA,CAAI,CAAA,CAAE,KAAK,CAAA,CAAE,MAAA,KAAW,CAAC,CAAA,CACvC,GAAA,CAAI,CAAC,CAAA,KAAM,GAAA,CAAI,EAAE,EAAE,CAAC,CAAA,CACpB,MAAA,CAAO,OAAO,CAAA;AACjB,EAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,IAAA,cAAA;AAAA,MAAe,IAAA;AAAA,MAAM,uBAAA;AAAA,MAAyB,SAAA;AAAA,MAC5C,sFAAA,GAAoF,QAAA,CAAS,IAAA,CAAK,IAAI,CAAA;AAAA,MACtG;AAAA,KACF;AAAA,EACF;AACA,EAAA,MAAM,SAAS,mBAAA,EAAoB;AACnC,EAAA,IAAI,MAAA,CAAO,SAAS,CAAA,EAAG;AACrB,IAAA,cAAA;AAAA,MAAe,IAAA;AAAA,MAAM,iBAAA;AAAA,MAAmB,SAAA;AAAA,MACtC,wFAAA,GAA2F,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA;AAAA,MAC3G;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,WAAA,CAAY,SAAS,CAAA,EAAG;AAChD,IAAA,MAAM,QAAA,GAAW,GAAA,CAAK,EAAA,CAAG,cAAA,IAAkB,EAAU,CAAA,CAAE,MAAA,GAAS,CAAA,IAAK,GAAA,CAAK,EAAA,CAAG,cAAA,EAAoC,KAAK,EAAE,MAAA,GAAS,CAAA;AACjI,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,oBAAA,GAAuB,WAAA,CAAY,MAAA,GAAS,qFAAA,IAC3C,WACG,oLAAA,GACA,mGAAA;AAAA,KACN;AAAA,EACF;AACA,EAAA,kBAAA,CAAmB,KAAK,CAAA;AACxB,EAAA,OAAO,sBAAA,CAAuB;AAAA,IAC5B,cAAA,EAAgB,CAAA;AAAA,IAChB,MAAA,EAAQ,WAAA,CAAY,IAAA,EAAM,eAAe,CAAA;AAAA,IACzC,cAAA,EAAgB,KAAA;AAAA,IAChB,gBAAA,EAAkB,KAAA;AAAA,IAClB,mBAAA,EAAqB,SAAA;AAAA,IACrB,QAAQ;AAAC,GACV,CAAA;AACH;AAGO,SAAS,kBAAA,CACd,KAAA,EACA,IAAA,GAAwB,EAAC,EACT;AAChB,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,IAAI,GAAA,CAAI,CAAA,CAAE,cAAc,CAAA,CAAE,SAAS,CAAA,EAAG;AACpC,IAAA,OAAO,uBAAuB,CAAC,CAAA;AAAA,EACjC;AACA,EAAA,IAAI,CAAA,CAAE,aAAA,IAAiB,CAAA,CAAE,eAAA,IAAmB,IAAI,CAAA,CAAE,KAAK,CAAA,CAAE,MAAA,GAAS,KAAK,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA,CAAE,SAAS,CAAA,EAAG;AAC/F,IAAA,OAAO,cAAA,CAAe,GAAG,IAAI,CAAA;AAAA,EAC/B;AACA,EAAA,IAAI,GAAA,CAAI,CAAA,CAAE,QAAQ,CAAA,CAAE,SAAS,CAAA,IAAK,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA,KAAM,iBAAA,EAAmB,OAAO,iBAAA,CAAkB,GAAG,IAAI,CAAA;AACrG,EAAA,OAAO,eAAA,CAAgB,GAAG,IAAI,CAAA;AAChC","file":"chunk-WKTH3JPA.mjs","sourcesContent":["import {\n validateCurriculumFeed, type CurriculumFeed, type FeedNode, type FeedEdge, type FeedGrouping,\n type FeedDiagnostic, type FeedProvenance,\n} from '../schemas/curriculumFeedSchema';\n\n/**\n * CurriculumFeed emitter - pure transformation from a VERIFIED domain graph\n * to the planner-ready feed. No LLM calls: hints derive from existing\n * annotations only. Validates its own output (fail-closed).\n *\n * Phase-driven emission (hybrid graphs with development phases): the teaching\n * sequence IS the phase sequence. Concepts are emitted at the phase that\n * introduces them (aspect new) or revisits them at higher depth (aspect\n * advanced) - never as a trailing knowledge appendix. Concept minutes stay\n * UNMERGED: no prerequisite-minute inflation (the planner schedules the\n * revisit, not the total).\n */\n\nexport interface EmitFeedOptions {\n hallucinationCount?: number;\n /** Upstream (graph pipeline) warnings — imported as 'upstream_warning' diagnostics. */\n warnings?: string[];\n diagnostics?: FeedDiagnostic[];\n provenance?: FeedProvenance;\n}\n\n/**\n * Minute fractions per depth level. ONE table: the preview node, the depth_variants\n * price list and the advanced-revisit downgrade candidate all read from here. They\n * used to be 25% / 35% / 65% / 100% / 30% hardcoded in three places, which meant\n * \"what does a ULO pass cost?\" had three answers.\n */\nexport const DEPTH_MINUTE_FRACTIONS = {\n /** Awareness-only pass before the anchor teach. */\n preview: 0.25,\n ulo: 0.35,\n cio: 0.65,\n sio: 1.0,\n /** An advanced revisit re-teaches the concept, not the whole thing again. */\n advancedRevisit: 0.5,\n /** Minutes saved when an advanced revisit drops from SIO practice to a CIO recap. */\n advancedRecapSave: 0.3,\n /** Floor so a tiny concept still gets a teachable awareness pass. */\n floor: 5,\n /** Floor for an advanced revisit — it is a real teach, not an awareness pass. */\n advancedFloor: 15,\n} as const;\n\n/**\n * Fallback when a step declares no `effort.estimated_minutes`. The emitter and the\n * hybrid totals used to disagree (45 vs 15) on the same missing field, so the feed\n * and the planning total priced the same step differently.\n */\nexport const DEFAULT_STEP_MINUTES = 15;\n\nconst BLOOM_RANK: Record<string, number> = { Remember: 0, Understand: 1, Apply: 2, Analyze: 3, Evaluate: 4, Create: 5 };\nconst pad2 = (n: number): string => String(n).padStart(2, '0');\n\n/** Steps dropped during emission because they carried no id — reported per run. */\nlet steplessStepIds: string[] = [];\nconst takeSteplessStepIds = (): string[] => steplessStepIds.splice(0, steplessStepIds.length);\n\ntype Rec = Record<string, unknown>;\nconst arr = (v: unknown): Rec[] => (Array.isArray(v) ? (v as Rec[]) : []);\nconst str = (v: unknown, d = ''): string => (typeof v === 'string' ? v : d);\nconst num = (v: unknown, d: number): number => (typeof v === 'number' && Number.isFinite(v) ? v : d);\nconst strs = (v: unknown): string[] => (Array.isArray(v) ? v.filter((x): x is string => typeof x === 'string') : []);\n\nfunction stepMinutes(step: Rec): number {\n const effort = (step.effort ?? {}) as Rec;\n return Math.max(0, Math.round(num(effort.estimated_minutes, num(step.estimated_minutes, DEFAULT_STEP_MINUTES))));\n}\n\nfunction diagnosticsOf(opts: EmitFeedOptions): FeedDiagnostic[] {\n const list = opts.diagnostics ?? (opts.diagnostics = []);\n // Upstream warnings enter the SAME list once, so `source.diagnostics` is the one\n // record and `source.warnings` can stay a derived text view for old consumers.\n for (const message of opts.warnings ?? []) {\n if (!list.some((d) => d.code === 'upstream_warning' && d.message === message)) {\n list.push({ code: 'upstream_warning', severity: 'warning', message, node_ids: [] });\n }\n }\n return list;\n}\n\n/** The single writer for emission findings. Every code is greppable. */\nfunction pushDiagnostic(\n opts: EmitFeedOptions,\n code: string,\n severity: FeedDiagnostic['severity'],\n message: string,\n nodeIds: string[] = [],\n): void {\n diagnosticsOf(opts).push({ code, severity, message, node_ids: nodeIds });\n}\n\n/**\n * Prerequisites that no node in this feed provides.\n *\n * The three emission paths used to disagree about the same input: the knowledge\n * path silently dropped them, while BOTH hybrid paths hard-failed — the no-phase\n * path built a dangling edge (\"edge C_OUTSIDE -> C2 references unknown node\") and\n * the phase path threw an *ordering* error blaming a concept that is not in the\n * analysis at all. Either way the course lost its entry requirements: the single\n * most useful piece of information for someone deciding whether to enrol.\n */\ninterface ExternalPrereq {\n nodeId: string;\n prereqId: string;\n}\n\nfunction splitPrerequisites(concept: Rec, known: Set<string>): { internal: string[]; external: string[] } {\n const internal: string[] = [];\n const external: string[] = [];\n for (const p of strs(concept.prerequisites)) {\n (known.has(p) ? internal : external).push(p);\n }\n return { internal, external };\n}\n\n/** Fills `external_prerequisites` on each node and reports the set once. */\nfunction noteExternalPrerequisites(\n opts: EmitFeedOptions,\n byNode: Map<string, string[]>,\n): void {\n if (byNode.size === 0) return;\n const entries: ExternalPrereq[] = [];\n for (const [nodeId, prereqs] of byNode) {\n for (const prereqId of prereqs) entries.push({ nodeId, prereqId });\n }\n entries.sort((a, b) => (a.nodeId === b.nodeId ? a.prereqId.localeCompare(b.prereqId) : a.nodeId.localeCompare(b.nodeId)));\n pushDiagnostic(\n opts, 'external_prerequisite', 'info',\n 'Prerequisite(s) outside this analysis — the learner must already know them: '\n + entries.map((e) => e.nodeId + ' ← ' + e.prereqId).join(', '),\n entries.map((e) => e.nodeId),\n );\n}\n\nfunction buildSource(opts: EmitFeedOptions, graphType: CurriculumFeed['source']['graph_type']) {\n const diagnostics = diagnosticsOf(opts);\n return {\n graph_type: graphType,\n // Derived view — 'info' diagnostics are deliberately not surfaced as warnings.\n warnings: diagnostics.filter((d) => d.severity !== 'info').map((d) => d.message),\n hallucination_count: opts.hallucinationCount ?? 0,\n diagnostics,\n provenance: opts.provenance,\n };\n}\n\n/** Comparison key for a keyword: NFC + case-folded, so `SwiftUI` and `swiftui`\n * are the same keyword. The edge materializer already lowercased before looking\n * up an introducer — the ledger it was compared against did not, so a case\n * difference made the ledger report two new keywords while the edge pass saw\n * one introducer. For Vietnamese this also collapses NFD/NFC spellings. */\nfunction keywordKey(k: string): string {\n return k.normalize('NFC').toLowerCase();\n}\n\nfunction newKeywordsInOrder(nodes: FeedNode[]): void {\n // First appearance in emission order becomes 'new'; everything seen before is 'prerequisite'.\n const seen = new Set<string>();\n for (const n of nodes) {\n const all = n.keywords.all;\n n.keywords.new = [];\n n.keywords.prerequisite = [];\n for (const k of all) {\n if (seen.has(keywordKey(k))) n.keywords.prerequisite.push(k);\n else n.keywords.new.push(k);\n seen.add(keywordKey(k));\n }\n }\n}\n\nfunction feedNodeFromStep(step: Rec, featureId: string): FeedNode {\n const id = str(step.id);\n return {\n id,\n kind: 'product_step',\n name: str(step.name, id),\n concept_codes: strs(step.concept_codes),\n keywords: { all: strs(step.keywords), new: [], prerequisite: [] },\n estimated_minutes: stepMinutes(step),\n bloom_hint: 'Apply',\n depth_hint: null,\n completion_level: 'standard',\n scaffolding: { scaffold_candidates: [], core_value_minutes: stepMinutes(step) },\n references: arr(step.files).map((f) => ({ file: str(f), evidence: 'step file' })).filter((f) => f.file),\n feature_id: featureId,\n concept_ref: null,\n // P52/T2.1: carry the step's tangible outcome so the planner can cut\n // sessions at user-visible checkpoints instead of raw minute counts.\n user_visible_deliverable: str((step.outcome as Rec | undefined)?.user_visible),\n is_core: false,\n external_prerequisites: [],\n phase_id: '',\n introduce_aspect: null,\n };\n}\n\nfunction deriveConceptDepthHint(concept: Rec): FeedNode['depth_hint'] {\n const explicit = str(concept.depth_hint);\n if (explicit === 'ulo' || explicit === 'cio' || explicit === 'sio') return explicit;\n\n const sio = arr(concept.sio);\n const prereqs = strs(concept.prerequisites);\n const pts = arr(concept.problem_types);\n const hasHighBloom = pts.some((p) => {\n const b = str(p.bloom_level);\n return b === 'Apply' || b === 'Analyze' || b === 'Evaluate' || b === 'Create';\n });\n\n if (sio.length >= 2 || hasHighBloom) return 'sio';\n if (str(concept.cio) || prereqs.length >= 1) return 'cio';\n return 'ulo';\n}\n\n/**\n * P52/T3.1: minute estimates per depth level for one concept occurrence.\n * ULO (know-it-exists) is a fraction of the full teach; SIO (implementation)\n * equals the feed's estimated minutes. Only DOWNWARD candidates are offered\n * and the ULO floor is never crossed (planner filters is_core itself).\n */\nfunction deriveDepthInputs(minutes: number, depth: FeedNode['depth_hint']): Pick<FeedNode, 'depth_variants' | 'depth_scaffold_candidates'> {\n const ulo = Math.max(DEPTH_MINUTE_FRACTIONS.floor, Math.round(minutes * DEPTH_MINUTE_FRACTIONS.ulo));\n const cio = Math.max(ulo + DEPTH_MINUTE_FRACTIONS.floor, Math.round(minutes * DEPTH_MINUTE_FRACTIONS.cio));\n const sio = Math.max(cio + DEPTH_MINUTE_FRACTIONS.floor, minutes);\n const variants = { ulo, cio, sio };\n const candidates: NonNullable<FeedNode['depth_scaffold_candidates']> = [];\n if (depth === 'sio') {\n candidates.push({ from_depth: 'sio', to_depth: 'cio', minutes_saved: sio - cio, reason: 'SIO implementation practice deferred — CIO mechanism suffices for downstream consumers' });\n }\n if (depth === 'cio' || depth === 'sio') {\n candidates.push({ from_depth: depth, to_depth: 'ulo', minutes_saved: (depth === 'sio' ? sio : cio) - ulo, reason: 'Mechanism deferred — ULO awareness suffices (know-it-exists); never below the ULO floor' });\n }\n return { depth_variants: variants, depth_scaffold_candidates: candidates };\n}\n\nfunction feedNodeFromConcept(concept: Rec): FeedNode {\n const id = str(concept.id);\n const pts = arr(concept.problem_types)\n .map((p) => str(p.bloom_level, 'Understand'))\n .filter((b) => b in BLOOM_RANK)\n .sort((a, b) => BLOOM_RANK[a] - BLOOM_RANK[b]);\n const topBloom = pts[pts.length - 1] ?? 'Understand';\n\n const rawRefs = arr(concept.references);\n const references = rawRefs.length > 0\n ? rawRefs.map((r) => ({ file: str(r.file, 'syllabus.md'), evidence: str(r.evidence, str(concept.name)) })).filter((r) => r.file)\n : [{ file: 'syllabus.md', evidence: str(concept.category ? `Topic under category ${str(concept.category)}` : str(concept.name), id) }];\n\n const problemScaffolds = arr(concept.problem_types).map((pt) => ({\n item: str(pt.name, 'Problem type'),\n reason: str(pt.description, 'Formative practice check'),\n minutes_saved: num(pt.estimated_minutes, 10),\n suggest: 'provide_partial' as const,\n }));\n\n return {\n id,\n kind: 'concept',\n name: str(concept.name, id),\n concept_codes: [str(concept.code) || id].filter(Boolean),\n keywords: { all: strs(concept.keywords), new: [], prerequisite: [] },\n estimated_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 15))),\n bloom_hint: topBloom as FeedNode['bloom_hint'],\n depth_hint: deriveConceptDepthHint(concept),\n completion_level: 'standard',\n scaffolding: {\n scaffold_candidates: problemScaffolds,\n core_value_minutes: Math.max(0, Math.round(num(concept.estimated_minutes, 15))),\n },\n references,\n feature_id: null,\n concept_ref: id,\n user_visible_deliverable: '', // concepts are taught, not shipped — checkpoints live on product steps\n ...deriveDepthInputs(Math.max(0, Math.round(num(concept.estimated_minutes, 15))), deriveConceptDepthHint(concept)),\n // Knowledge-Tree concepts are the spine of the course — the reconciler must\n // escalate them, never downgrade them. The emitter hardcoded `false`, so the\n // flag the schema documents never reached the planner for any concept.\n is_core: concept.is_core === true || str(concept.standardization) === 'standardized',\n external_prerequisites: [],\n depth_context: {\n ulo: str(concept.ulo),\n cio: str(concept.cio),\n sio: strs(concept.sio),\n },\n phase_id: '',\n introduce_aspect: null,\n };\n}\n\nfunction addFeatureGroup(\n f: Rec, fid: string, nodes: FeedNode[], edges: FeedEdge[], groupings: FeedGrouping[],\n): string[] {\n const steps = arr(f.steps);\n const stepIds: string[] = [];\n let prev = '';\n for (let i = 0; i < steps.length; i++) {\n const stepNode = feedNodeFromStep(steps[i], fid);\n // A step with no id cannot become a node. Silently skipping it made a feature\n // with one such step look \"covered\" (steps.length > 0) while half of it — or\n // all of it — never reached the feed.\n if (!stepNode.id) {\n steplessStepIds.push(fid + '#' + i);\n continue;\n }\n stepIds.push(stepNode.id);\n nodes.push(stepNode);\n if (prev) edges.push({ from: prev, to: stepNode.id, kind: 'task', reason: 'feature build order' });\n prev = stepNode.id;\n }\n for (const dep of strs(f.depends_on)) {\n const depFirst = nodes.find((n) => n.feature_id === dep);\n if (depFirst && stepIds[0]) edges.push({ from: depFirst.id, to: stepIds[0], kind: 'task', reason: 'feature depends_on ' + dep });\n }\n if (stepIds.length > 0) groupings.push({ id: 'G-' + fid, name: str(f.name, fid), node_ids: stepIds, rationale: str(f.description, '') });\n return stepIds;\n}\n\ninterface EmitPhaseCtx {\n id: string;\n order: number;\n name: string;\n productCompletion: string;\n conceptOrder: string[]; // concept node ids in teaching order (new first, then advanced)\n featureIds: string[];\n}\n\n/**\n * Phase-driven hybrid emission:\n * - emit concepts per phase (aspect new first, then advanced) BEFORE the\n * phase's features; knowledge edges concept -> first step of consumer\n * features carry the JIT relation\n * - feature order inside a phase follows the project graph order\n * - phases and their node_ids go into feed.phases (teaching sequence)\n * - concept minutes stay UNMERGED (no prereq-minute accumulation)\n */\nfunction emitFromHybrid(hybrid: Rec, opts: EmitFeedOptions): CurriculumFeed {\n const nodes: FeedNode[] = [];\n const edges: FeedEdge[] = [];\n const groupings: FeedGrouping[] = [];\n const pg = (hybrid.project_graph ?? {}) as Rec;\n const kg = (hybrid.knowledge_graph ?? {}) as Rec;\n const rawPhases = arr(hybrid.phases).filter((p) => str(p.id));\n\n const features = arr(pg.features);\n const conceptRecs = arr(kg.concepts);\n const conceptById = new Map(conceptRecs.map((c) => [str(c.id), c]));\n const knownConceptIds = new Set(conceptById.keys());\n\n // --- no phases -> legacy path (concepts first, then features) -----------\n if (rawPhases.length === 0) {\n const externalByNode = new Map<string, string[]>();\n for (const c of conceptRecs) {\n const node = feedNodeFromConcept(c);\n nodes.push(node);\n const { internal, external } = splitPrerequisites(c, knownConceptIds);\n if (external.length > 0) {\n node.external_prerequisites = external;\n externalByNode.set(node.id, external);\n }\n for (const p of internal) edges.push({ from: p, to: node.id, kind: 'knowledge', reason: 'concept prerequisite' });\n }\n noteExternalPrerequisites(opts, externalByNode);\n features.forEach((f, fi) => addFeatureGroup(f, str(f.id, 'F' + pad2(fi + 1)), nodes, edges, groupings));\n linkHybridKnowledgeEdges(hybrid, nodes, edges, true);\n newKeywordsInOrder(nodes);\n return validateCurriculumFeed({\n schema_version: 2,\n source: buildSource(opts, 'hybrid_graph'),\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases: [],\n });\n }\n\n // --- phase-driven emission ----------------------------------------------\n const featureOrder = new Map<string, number>();\n features.forEach((f, fi) => featureOrder.set(str(f.id, 'F' + pad2(fi + 1)), fi));\n const stepIdsByFeature = new Map<string, string[]>();\n for (const f of features) {\n const fid = str(f.id);\n stepIdsByFeature.set(fid, arr(f.steps).map((s) => str(s.id)).filter(Boolean));\n }\n\n const emitCtx: EmitPhaseCtx[] = [];\n const emittedConcepts = new Set<string>();\n const previewsEmitted = new Set<string>();\n /** concept id → the preview NODE id already emitted for it (always an earlier phase). */\n const previewNodeIdByConcept = new Map<string, string>();\n /** concept id → prerequisite ids that are not concepts in this analysis. */\n const externalByNode = new Map<string, string[]>();\n const linkList = arr(hybrid.links);\n\n for (const rp of rawPhases) {\n const pid = str(rp.id);\n const order = num(rp.order, emitCtx.length + 1);\n const conceptOrder: string[] = [];\n const intros = arr(rp.introduces);\n // new introductions first, then advanced revisits, then previews\n // (awareness passes — never the introduction itself)\n const news = intros.filter((i) => str(i.aspect, 'new') === 'new');\n const advanced = intros.filter((i) => str(i.aspect) === 'advanced');\n const previews = intros.filter((i) => str(i.aspect) === 'preview');\n for (const intro of [...news, ...advanced, ...previews]) {\n const cid = str(intro.concept_id);\n const c = conceptById.get(cid);\n if (!c) {\n throw new Error('Phase ' + pid + ' introduces unknown concept ' + cid + ' - fail-closed');\n }\n if (str(intro.aspect, 'new') === 'advanced' && !emittedConcepts.has(cid)) {\n throw new Error('Phase ' + pid + ' advanced-revisits concept ' + cid + ' that was never emitted - fail-closed');\n }\n if (str(intro.aspect, 'new') === 'preview') {\n if (emittedConcepts.has(cid)) {\n throw new Error('Phase ' + pid + ' previews concept ' + cid + ' that was already introduced - preview must precede the anchor teach - fail-closed');\n }\n if (previewsEmitted.has(cid)) {\n throw new Error('Phase ' + pid + ' duplicate preview of concept ' + cid + ' - fail-closed');\n }\n const pidIdx = rawPhases.findIndex((rp2) => str(rp2.id) === pid);\n const anchorIdx = rawPhases.findIndex((rp2) => arr(rp2.introduces).some((i2) => str(i2.concept_id) === cid && str(i2.aspect, 'new') !== 'preview'));\n if (anchorIdx < 0) {\n throw new Error('Phase ' + pid + ' previews concept ' + cid + ' that is never fully introduced in any phase - fail-closed');\n }\n if (pidIdx >= anchorIdx) {\n throw new Error('Phase ' + pid + ' previews concept ' + cid + ' whose anchor introduction is not in a LATER phase - fail-closed');\n }\n previewsEmitted.add(cid);\n // ULO awareness node: \"know it exists\". Keeps the concept vocabulary\n // (keywords.all) so the emitter's in-order pass makes the PREVIEW the\n // legitimate introducer of those words — a product step must never be\n // the introducer (the overtake guard's whole point). Depth pinned to\n // ulo, bloom to Understand, ~25% minutes, no prerequisite edges, no\n // scaffolding (nothing to practice yet).\n const prevNode = feedNodeFromConcept(c);\n prevNode.id = cid + '__PREV' + pad2(order);\n prevNode.name = str(c.name, cid) + ' (preview)';\n prevNode.phase_id = pid;\n prevNode.introduce_aspect = 'preview';\n prevNode.estimated_minutes = Math.max(DEPTH_MINUTE_FRACTIONS.floor, Math.round(prevNode.estimated_minutes * DEPTH_MINUTE_FRACTIONS.preview));\n prevNode.depth_hint = 'ulo';\n prevNode.bloom_hint = 'Understand';\n prevNode.concept_codes = [str(c.code) || cid];\n prevNode.keywords = { all: strs(c.keywords), new: [], prerequisite: [] };\n prevNode.scaffolding = { scaffold_candidates: [], core_value_minutes: 0 };\n prevNode.depth_variants = { ulo: prevNode.estimated_minutes, cio: prevNode.estimated_minutes, sio: prevNode.estimated_minutes };\n prevNode.depth_scaffold_candidates = []; // nothing to downgrade — it IS the floor\n nodes.push(prevNode);\n conceptOrder.push(prevNode.id);\n previewNodeIdByConcept.set(cid, prevNode.id);\n continue;\n }\n if (!emittedConcepts.has(cid)) {\n const node = feedNodeFromConcept(c);\n node.phase_id = pid;\n node.introduce_aspect = 'new';\n nodes.push(node);\n for (const p of strs(c.prerequisites)) {\n // A prerequisite that is not a concept in THIS analysis is an entry\n // requirement, not an ordering problem. The old code blamed ordering for\n // it (\"neither introduced nor previewed in an EARLIER phase\") and threw,\n // which made any knowledge graph referencing prior knowledge un-emittable.\n if (!conceptById.has(p)) {\n if (!externalByNode.has(cid)) externalByNode.set(cid, []);\n externalByNode.get(cid)!.push(p);\n node.external_prerequisites.push(p);\n continue;\n }\n // A preview node in an EARLIER phase DOES satisfy a prerequisite — that\n // is precisely what it exists for (\"know it exists\" before the full\n // teach, so the dependent concept has the vocabulary). Requiring the\n // full anchor first made preview unusable for exactly the concepts that\n // need it most. A preview in the SAME phase does not count: within a\n // phase the sequence is new → advanced → preview, so it would arrive\n // after the dependent concept.\n const previewOfPrereq = previewNodeIdByConcept.get(p);\n if (!emittedConcepts.has(p) && !previewOfPrereq) {\n throw new Error('Concept ' + cid + ' (phase ' + pid + ') has prerequisite ' + p + ' that is neither introduced nor previewed in an EARLIER phase - fail-closed');\n }\n edges.push({ from: emittedConcepts.has(p) ? p : previewOfPrereq!, to: cid, kind: 'knowledge', reason: 'concept prerequisite' });\n }\n emittedConcepts.add(cid);\n conceptOrder.push(cid);\n } else if (str(intro.aspect, 'new') === 'new') {\n // auditPhasePlan rejects a duplicate `new`; the emitter is the last gate and\n // used to accept it silently, emitting no node for the repeat.\n throw new Error('Phase ' + pid + ' introduces ' + cid + ' as new but it was already introduced in an earlier phase - fail-closed');\n } else {\n // advanced revisit: re-emit as a NEW node with a revisit id so the\n // planner can schedule the higher-depth pass without double-counting\n // the first introduction\n const revisitId = cid + '__ADV' + pad2(order);\n const node = feedNodeFromConcept(c);\n node.id = revisitId;\n node.name = str(c.name, cid) + ' (advanced)';\n node.phase_id = pid;\n node.introduce_aspect = 'advanced';\n // Reduce FIRST, then price the downgrade against the minutes the node\n // actually carries. It used to be computed on the pre-reduction value, so\n // `minutes_saved` referred to a number stored nowhere.\n node.estimated_minutes = Math.max(\n DEPTH_MINUTE_FRACTIONS.advancedFloor,\n Math.round(node.estimated_minutes * DEPTH_MINUTE_FRACTIONS.advancedRevisit),\n );\n node.depth_scaffold_candidates = [{\n from_depth: 'sio', to_depth: 'cio',\n minutes_saved: Math.round(node.estimated_minutes * DEPTH_MINUTE_FRACTIONS.advancedRecapSave),\n reason: 'advanced revisit may drop to CIO recap without breaking first-teach chain',\n }];\n node.concept_codes = [str(c.code) || cid];\n edges.push({ from: cid, to: revisitId, kind: 'knowledge', reason: 'advanced revisit of ' + cid + ' at phase ' + pid });\n nodes.push(node);\n conceptOrder.push(revisitId);\n }\n }\n const featureIds = strs(rp.feature_ids)\n .filter((fid) => featureOrder.has(fid))\n .sort((a, b) => featureOrder.get(a)! - featureOrder.get(b)!);\n emitCtx.push({ id: pid, order, name: str(rp.name, pid), productCompletion: str(rp.product_completion), conceptOrder, featureIds });\n }\n\n noteExternalPrerequisites(opts, externalByNode);\n // Fail-closed: every concept consumed by a link must be emitted somewhere\n for (const l of linkList) {\n const cid = str(l.concept_id);\n if (cid && conceptById.has(cid) && !emittedConcepts.has(cid)) {\n throw new Error('Concept ' + cid + ' is consumed by links but never introduced in any phase - fail-closed');\n }\n }\n // Fail-closed: every feature must sit in exactly one emitted phase\n const seenFeatures = new Set(emitCtx.flatMap((e) => e.featureIds));\n const missingFeatures = features.map((f) => str(f.id)).filter((fid) => fid && !seenFeatures.has(fid));\n if (missingFeatures.length > 0) {\n throw new Error('Features not covered by any phase: ' + missingFeatures.join(', ') + ' - fail-closed');\n }\n // Coverage the other way round: a concept the generator emitted but no phase\n // introduces is a hole in the course. The fail-closed check above only catches\n // concepts that HAVE a link — an unlinked concept used to vanish silently.\n const orphans = Array.from(conceptById.keys()).filter((cid) => !emittedConcepts.has(cid));\n if (orphans.length > 0) {\n pushDiagnostic(opts, 'orphan_concept', 'error',\n 'Knowledge graph has ' + orphans.length + ' concept(s) no phase introduces and no link consumes — they will not be taught: '\n + orphans.join(', '),\n orphans,\n );\n }\n\n // Emit feature steps phase by phase (teaching sequence)\n const phaseNodeIds: string[][] = [];\n for (const ctx of emitCtx) {\n const ids: string[] = [...ctx.conceptOrder];\n for (const fid of ctx.featureIds) {\n const f = features.find((x) => str(x.id) === fid)!;\n const stepIds = addFeatureGroup(f, fid, nodes, edges, groupings);\n for (const sid of stepIds) {\n const node = nodes.find((x) => x.id === sid)!;\n node.phase_id = ctx.id;\n }\n ids.push(...stepIds);\n }\n if (ids.length === 0) {\n // FeedPhaseSchema requires at least one node; a phase that introduces and\n // owns nothing is not a teaching unit — surface it instead of letting Zod\n // throw \"Array must contain at least 1 element(s)\" with no phase name.\n throw new Error('Phase ' + ctx.id + ' contains no teaching nodes (no concepts introduced and no feature steps) - drop it or give it work - fail-closed');\n }\n phaseNodeIds.push(ids);\n }\n const idlessInHybrid = takeSteplessStepIds();\n if (idlessInHybrid.length > 0) {\n pushDiagnostic(opts, 'step_without_id', 'warning',\n 'Step(s) carrying no id cannot become planning nodes and were dropped (feature#index): ' + idlessInHybrid.join(', '),\n idlessInHybrid,\n );\n }\n\n // Teaching-order emission (2026-09-17 incident): the nodes array MUST\n // follow the phase sequence. Downstream consumers (planner unit grouping,\n // keyword new/prerequisite labeling, keyword-edge materialization) treat\n // array order as teaching order; emitting \"all concepts first, steps\n // after\" diverged from the phase order and manufactured backwards\n // prerequisite edges across phases (e.g. C8 in PH3 labeled before a PH2\n // step that consumes its keyword) — a self-contradictory feed.\n const placed = new Set<string>(phaseNodeIds.flat());\n const orderedNodes: FeedNode[] = [];\n for (const ids of phaseNodeIds) {\n for (const id of ids) {\n const n = nodes.find((x) => x.id === id);\n if (n) orderedNodes.push(n);\n }\n }\n for (const n of nodes) if (!placed.has(n.id)) orderedNodes.push(n);\n nodes.length = 0;\n nodes.push(...orderedNodes);\n\n // Knowledge edges: concept -> first step of each consumer feature (JIT).\n // Deduped by (from,to): several links may target the same feature.\n const edgeSeen = new Set(edges.map((e) => e.from + '>' + e.to));\n const pushEdge = (from: string, to: string, reason: string) => {\n const key = from + '>' + to;\n if (edgeSeen.has(key)) return;\n edgeSeen.add(key);\n edges.push({ from, to, kind: 'knowledge', reason });\n };\n for (const l of linkList) {\n const cid = str(l.concept_id);\n const fid = str(l.feature_id);\n if (!conceptById.has(cid)) continue;\n const firstStep = (stepIdsByFeature.get(fid) || [])[0];\n if (!firstStep) continue;\n const depth = str(l.depth, 'cio');\n // Preview-aware guard: when the concept's anchor teach sits in a LATER\n // phase than the consumer step, the consumer is served by its __PREV\n // awareness node — the anchor edge would cross phases backwards. Never\n // manufacture it; record the drift instead.\n const anchorNode = nodes.find((n2) => n2.kind === 'concept' && n2.concept_ref === cid && n2.introduce_aspect === 'new');\n const stepNode = nodes.find((n2) => n2.id === firstStep);\n const anchorPhaseIdx = anchorNode ? emitCtx.findIndex((c2) => c2.id === anchorNode.phase_id) : -1;\n const stepPhaseIdx = stepNode ? emitCtx.findIndex((c2) => c2.id === stepNode.phase_id) : -1;\n if (emitCtx.length > 0 && anchorPhaseIdx > stepPhaseIdx) {\n pushDiagnostic(opts, 'hybrid_link_drift', 'warning',\n 'hybrid link drift: concept ' + cid + ' is fully taught in a later phase than consumer ' + firstStep + ' (a preview covers the dependency)',\n [cid, firstStep]);\n continue;\n }\n pushEdge(cid, firstStep, str(l.relationship, 'hybrid link') + ' [' + depth + ']');\n // Advanced revisits feed only features of the revisit's OWN phase: a PH2\n // revisit must never become a prerequisite of a PH1 feature (teaching\n // order is phase-ordered).\n for (const n of nodes) {\n if (n.kind === 'concept' && n.concept_ref === cid && n.introduce_aspect === 'advanced' && n.phase_id === str(l.phase_id)) {\n pushEdge(n.id, firstStep, 'advanced application [' + depth + ']');\n }\n }\n }\n\n newKeywordsInOrder(nodes);\n\n // Deterministic keyword-prerequisite materialization: when a node declares a\n // keyword as PREREQUISITE, some other node must INTRODUCE it (new) EARLIER in\n // feed order. Missing edge = planner can schedule the consumer first while the\n // audit flags ordering it cannot fix. Edge kind 'keyword' — the planner\n // honors it for topological order; 'mastered_before_course' entries are exempt\n // (entry-level keywords need no introducer).\n const feedOrder = new Map(nodes.map((n, i) => [n.id, i]));\n const introducer = new Map<string, string>();\n for (const n of nodes) for (const k of n.keywords?.new ?? []) {\n const key = keywordKey(k);\n if (!introducer.has(key)) introducer.set(key, n.id);\n }\n for (const n of nodes) {\n for (const k of n.keywords?.prerequisite ?? []) {\n const src = introducer.get(keywordKey(k));\n if (!src || src === n.id) continue;\n const srcNode = nodes.find((x) => x.id === src);\n // Phase-aware guard: when phases define teaching order, a keyword edge\n // must never cross phases backwards, and a CONCEPT must never be\n // \"introduced\" by a product STEP that happens to run earlier (the\n // graph anchored the concept downstream of its consumer — teaching\n // order would then declare the step as the origin of foundational\n // knowledge). Do NOT manufacture such edges: record the drift for the\n // host to repair (move the introducer earlier) and let\n // validateCurriculumFeed's teaching-order invariant be the hard gate.\n const phaseOf = (id: string): number => {\n const n2 = nodes.find((x) => x.id === id);\n return n2 ? emitCtx.findIndex((c) => c.id === n2.phase_id) : -1;\n };\n const conceptOvertaken = n.kind === 'concept' && srcNode?.kind === 'product_step';\n const fromPhase = phaseOf(src);\n const toPhase = phaseOf(n.id);\n if (conceptOvertaken || (emitCtx.length > 0 && fromPhase > toPhase)) {\n pushDiagnostic(opts,\n conceptOvertaken ? 'concept_overtaken_by_step' : 'keyword_order_drift',\n 'warning',\n 'keyword-order drift: introducer ' + src + ' sits in a later phase than consumer ' + n.id +\n ' (keyword \"' + k + '\") — host should move the introducer earlier',\n [src, n.id],\n );\n continue;\n }\n if (feedOrder.get(src)! < feedOrder.get(n.id)!) pushEdge(src, n.id, 'keyword prerequisite [' + k + ']');\n }\n }\n\n const phases = emitCtx.map((ctx, i) => ({\n id: ctx.id,\n order: ctx.order,\n name: ctx.name,\n product_completion: ctx.productCompletion || 'Phase ' + (i + 1) + ' deliverable',\n node_ids: phaseNodeIds[i],\n }));\n\n return validateCurriculumFeed({\n schema_version: 2,\n source: buildSource(opts, 'hybrid_graph'),\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases,\n });\n}\n\n/** Legacy helper: knowledge edges from hybrid links (used by the no-phase path). */\nfunction linkHybridKnowledgeEdges(hybrid: Rec, nodes: FeedNode[], edges: FeedEdge[], setStepHints: boolean): void {\n const conceptIds = new Set(nodes.filter((n) => n.kind === 'concept').map((n) => n.concept_ref || n.id));\n for (const l of arr(hybrid.links)) {\n const conceptId = str(l.concept_id);\n const featureId = str(l.feature_id);\n if (!conceptIds.has(conceptId)) continue;\n const depth = str(l.depth, 'cio');\n const firstStep = nodes.find((n) => n.feature_id === featureId);\n if (firstStep) {\n edges.push({ from: conceptId, to: firstStep.id, kind: 'knowledge', reason: str(l.relationship, 'hybrid link') + ' [' + depth + ']' });\n if (setStepHints && !firstStep.depth_hint) {\n firstStep.depth_hint = (['ulo', 'cio', 'sio'] as const).includes(depth as 'ulo') ? (depth as FeedNode['depth_hint']) : 'cio';\n }\n }\n }\n}\n\nfunction emitFromKnowledge(kg: Rec, opts: EmitFeedOptions): CurriculumFeed {\n const nodes: FeedNode[] = [];\n const edges: FeedEdge[] = [];\n const groupings: FeedGrouping[] = [];\n const conceptIds = new Set(arr(kg.concepts).map((c) => str(c.id)));\n const externalByNode = new Map<string, string[]>();\n for (const c of arr(kg.concepts)) {\n const node = feedNodeFromConcept(c);\n nodes.push(node);\n const { internal, external } = splitPrerequisites(c, conceptIds);\n if (external.length > 0) {\n node.external_prerequisites = external;\n externalByNode.set(node.id, external);\n }\n for (const p of internal) {\n if (p !== node.id) {\n edges.push({\n from: p,\n to: node.id,\n kind: 'knowledge',\n reason: `concept prerequisite: ${p} must be understood before ${node.id}`,\n });\n }\n }\n }\n noteExternalPrerequisites(opts, externalByNode);\n\n // Sort nodes by learning_path order if present\n if (arr(kg.learning_path).length > 0) {\n const orderMap = new Map<string, number>();\n arr(kg.learning_path).forEach((s, idx) => {\n const cid = str(s.concept_id);\n if (cid) orderMap.set(cid, num(s.order, idx + 1));\n });\n nodes.sort((a, b) => (orderMap.get(a.id) ?? 999) - (orderMap.get(b.id) ?? 999));\n }\n\n let ci = 0;\n for (const cat of arr(kg.categories)) {\n ci += 1;\n const ids = strs(cat.concept_ids).filter((id) => nodes.some((n) => n.id === id));\n if (ids.length > 0) {\n groupings.push({\n id: str(cat.id, 'cat' + ci),\n name: str(cat.name, 'Category ' + ci),\n node_ids: ids,\n rationale: str(cat.description, ''),\n });\n }\n }\n newKeywordsInOrder(nodes);\n return validateCurriculumFeed({\n schema_version: 2,\n source: buildSource(opts, 'knowledge_graph'),\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases: [],\n });\n}\n\nfunction emitFromProject(pg: Rec, opts: EmitFeedOptions): CurriculumFeed {\n const nodes: FeedNode[] = [];\n const edges: FeedEdge[] = [];\n const groupings: FeedGrouping[] = [];\n const allFeatures = arr(pg.features);\n allFeatures.forEach((f, fi) => addFeatureGroup(f, str(f.id, 'F' + pad2(fi + 1)), nodes, edges, groupings));\n // A feature whose steps vanished (STEP_4_C2 failed for it) produces no node and\n // used to disappear with no trace. Surface it — the host decides whether a\n // partially-built project is acceptable.\n const stepless = allFeatures\n .filter((f) => arr(f.steps).length === 0)\n .map((f) => str(f.id))\n .filter(Boolean);\n if (stepless.length > 0) {\n pushDiagnostic(opts, 'feature_without_steps', 'warning',\n 'Feature(s) declared but carrying no steps — they will be absent from the feed: ' + stepless.join(', '),\n stepless,\n );\n }\n const idless = takeSteplessStepIds();\n if (idless.length > 0) {\n pushDiagnostic(opts, 'step_without_id', 'warning',\n 'Step(s) carrying no id cannot become planning nodes and were dropped (feature#index): ' + idless.join(', '),\n idless,\n );\n }\n if (nodes.length === 0 && allFeatures.length > 0) {\n const hasTasks = arr((pg.implementation ?? {}) as Rec).length > 0 || arr((pg.implementation as Rec | undefined)?.tasks).length > 0;\n throw new Error(\n 'Project graph has ' + allFeatures.length + ' features but NO features[].steps - the curriculum feed cannot emit planning nodes.' +\n (hasTasks\n ? ' This looks like a legacy knowledge-tree graph (implementation.tasks). Regenerate it with the domain-kit v3 pipeline (runProjectGraphPipeline / hybridGraphPipeline), then re-run.'\n : ' Every feature must carry at least one step with id/name/keywords/effort for curriculum planning.'),\n );\n }\n newKeywordsInOrder(nodes);\n return validateCurriculumFeed({\n schema_version: 2,\n source: buildSource(opts, 'project_graph'),\n learning_nodes: nodes,\n dependency_edges: edges,\n suggested_groupings: groupings,\n phases: [],\n });\n}\n\n/** Emit a CurriculumFeed from whichever graph shape is provided (already verified upstream). */\nexport function emitCurriculumFeed(\n graph: unknown,\n opts: EmitFeedOptions = {},\n): CurriculumFeed {\n const g = graph as Rec;\n if (arr(g.learning_nodes).length > 0) {\n return validateCurriculumFeed(g); // already a feed - pass-through validation\n }\n if (g.project_graph || g.knowledge_graph || arr(g.links).length > 0 || arr(g.phases).length > 0) {\n return emitFromHybrid(g, opts);\n }\n if (arr(g.concepts).length > 0 || str(g.type) === 'knowledge_graph') return emitFromKnowledge(g, opts);\n return emitFromProject(g, opts);\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LlmClientConfig, LlmClient } from './utils/llmClient.cjs';
|
|
2
2
|
import { u as ProjectGraph } from './projectGraphSchema-DnD7orZV.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -19,6 +19,12 @@ interface ConceptMapping {
|
|
|
19
19
|
keyword: string;
|
|
20
20
|
depth: 'ulo' | 'cio' | 'sio';
|
|
21
21
|
depth_rationale: string;
|
|
22
|
+
/**
|
|
23
|
+
* Who decided `depth`. The regex fallback (comment → ulo, import → sio) has no
|
|
24
|
+
* pedagogical basis — without this marker a degraded run is indistinguishable
|
|
25
|
+
* from a considered one, because both write the same field.
|
|
26
|
+
*/
|
|
27
|
+
depth_source: 'llm' | 'heuristic' | 'override';
|
|
22
28
|
evidence_files: string[];
|
|
23
29
|
}
|
|
24
30
|
interface EscalateOptions {
|
|
@@ -31,6 +37,9 @@ interface EscalateOptions {
|
|
|
31
37
|
depth?: string;
|
|
32
38
|
}>;
|
|
33
39
|
llmConfig?: LlmClientConfig;
|
|
40
|
+
/** Pre-built client from the calling pipeline — reused so every step of one run
|
|
41
|
+
* lands in ONE callLog and the feed can report real provenance. */
|
|
42
|
+
sharedClient?: LlmClient;
|
|
34
43
|
}
|
|
35
44
|
/**
|
|
36
45
|
* Escalate keywords/api_usage to neutral concepts with ULO/CIO/SIO depth and join evidence files.
|