@thanh01.pmt/curriculum-kit 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1312,7 +1312,9 @@ var LessonPlanSchema = zod.z.object({
1312
1312
  name: zod.z.string().optional().default(""),
1313
1313
  description: zod.z.string(),
1314
1314
  bloomLevel: zod.z.string().default("understand"),
1315
- successCriteria: zod.z.string().optional().default("")
1315
+ successCriteria: zod.z.string().optional().default(""),
1316
+ standardRefs: zod.z.array(zod.z.string()).optional(),
1317
+ conceptRefs: zod.z.array(zod.z.string()).optional()
1316
1318
  })).default([]),
1317
1319
  prerequisites: zod.z.string().optional().default(""),
1318
1320
  materialsSummary: zod.z.string().optional().default(""),
@@ -9827,7 +9829,6 @@ var DEFAULT_KX_PRIORITIES = [
9827
9829
  ];
9828
9830
  function buildSectionAwareExcerpt(markdown, opts = {}) {
9829
9831
  const budget = opts.budget ?? 12e3;
9830
- const minContentChars = opts.minContentChars ?? 400;
9831
9832
  const issues = [];
9832
9833
  const source = (markdown || "").trim();
9833
9834
  if (!source) {
@@ -9840,9 +9841,11 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
9840
9841
  tokenEstimate: 0
9841
9842
  };
9842
9843
  }
9844
+ const minContentChars = opts.minContentChars !== void 0 ? opts.minContentChars : Math.min(400, source.length);
9843
9845
  const slcMap = normalizeSlcContract(opts.sectionLanguageContract);
9844
- const sections = parseMarkdownSections(source, slcMap, opts.artifactType);
9845
- const metaBlock = buildMetaBlock(source);
9846
+ const effectiveArtifactType = opts.artifactType || deriveArtifactTypeFromFileName(opts.fileName);
9847
+ const sections = parseMarkdownSections(source, slcMap, effectiveArtifactType);
9848
+ const metaBlock = buildMetaBlock(source, effectiveArtifactType);
9846
9849
  if (sections.length === 0) {
9847
9850
  issues.push("parse:no-sections");
9848
9851
  const excerpt2 = truncateByBudget(source, budget);
@@ -9858,44 +9861,74 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
9858
9861
  };
9859
9862
  }
9860
9863
  const byCanonical = /* @__PURE__ */ new Map();
9864
+ const byCleanTitle = /* @__PURE__ */ new Map();
9861
9865
  const unmatched = [];
9862
9866
  for (const s of sections) {
9863
9867
  if (s.canonicalKey && !byCanonical.has(s.canonicalKey)) {
9864
9868
  byCanonical.set(s.canonicalKey, s);
9865
- } else {
9869
+ }
9870
+ const norm = normalizeHeading(s.cleanTitle);
9871
+ if (!byCleanTitle.has(norm)) {
9872
+ byCleanTitle.set(norm, s);
9873
+ }
9874
+ if (!s.canonicalKey) {
9866
9875
  unmatched.push(s);
9867
9876
  }
9868
9877
  }
9869
9878
  const priorities = opts.priorities ?? DEFAULT_LESSON_PRIORITIES;
9870
9879
  const includedSections = [];
9871
9880
  const blocks = [];
9872
- let used = 0;
9881
+ const initialOverhead = metaBlock ? metaBlock.length + 2 : 0;
9882
+ let used = initialOverhead;
9873
9883
  const tryAdd = (heading, body, key) => {
9874
9884
  const trimmedBody = body.trim();
9875
9885
  if (!trimmedBody) return false;
9876
9886
  const block = `## ${heading}
9877
9887
 
9878
9888
  ${trimmedBody}`;
9879
- if (used + block.length > budget && used > 0) return false;
9880
- blocks.push(block);
9881
- used += block.length;
9882
- includedSections.push(key);
9883
- return true;
9889
+ if (used + block.length <= budget) {
9890
+ blocks.push(block);
9891
+ used += block.length;
9892
+ includedSections.push(key);
9893
+ return true;
9894
+ }
9895
+ if (includedSections.length === 0 && budget >= minContentChars) {
9896
+ const headingOverhead = `## ${heading}
9897
+
9898
+ `.length;
9899
+ const remaining = budget - used;
9900
+ if (remaining > headingOverhead + 50) {
9901
+ const allowedBody = remaining - headingOverhead;
9902
+ const truncated = truncateByBudget(trimmedBody, allowedBody).trim();
9903
+ if (truncated.length > 0) {
9904
+ const partialBlock = `## ${heading}
9905
+
9906
+ ${truncated}`;
9907
+ blocks.push(partialBlock);
9908
+ used += partialBlock.length;
9909
+ includedSections.push(key);
9910
+ issues.push(`section:truncated:${key}`);
9911
+ return true;
9912
+ }
9913
+ }
9914
+ }
9915
+ return false;
9884
9916
  };
9885
9917
  blocks.push(metaBlock);
9886
9918
  let sectionAware = false;
9887
9919
  for (const key of priorities) {
9888
- const sec = byCanonical.get(key);
9920
+ const sec = byCanonical.get(key) || byCleanTitle.get(normalizeHeading(key));
9889
9921
  if (!sec) continue;
9890
9922
  sectionAware = true;
9891
9923
  if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
9892
9924
  }
9893
9925
  for (const [key, sec] of byCanonical) {
9894
- if (includedSections.includes(key)) continue;
9926
+ if (includedSections.includes(key) || includedSections.includes(sec.cleanTitle)) continue;
9895
9927
  if (used >= budget) break;
9896
9928
  if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
9897
9929
  }
9898
9930
  for (const sec of unmatched) {
9931
+ if (includedSections.includes(sec.cleanTitle)) continue;
9899
9932
  if (used >= budget) break;
9900
9933
  if (!tryAdd(sec.cleanTitle, sec.body, sec.cleanTitle)) break;
9901
9934
  }
@@ -9903,17 +9936,16 @@ ${trimmedBody}`;
9903
9936
  issues.push("parse:no-canonical-resolution");
9904
9937
  }
9905
9938
  if (priorities.length > 0 && !priorities.some((k) => includedSections.includes(k))) {
9906
- issues.push("parse:no-priority-resolution");
9939
+ issues.push(sections.length === 0 ? "parse:no-sections" : "parse:no-priority-resolution");
9907
9940
  }
9908
9941
  let excerpt = blocks.join("\n\n");
9909
9942
  if (excerpt.length > budget) {
9910
9943
  excerpt = truncateByBudget(excerpt, budget);
9911
9944
  }
9912
- const effectiveMinChars = Math.min(minContentChars, source.length);
9913
- if (excerpt.length - metaBlock.length < effectiveMinChars) {
9945
+ if (excerpt.length - metaBlock.length < minContentChars) {
9914
9946
  issues.push("content:insufficient");
9915
9947
  }
9916
- const verified = !issues.includes("parse:no-priority-resolution") && !issues.includes("parse:no-canonical-resolution") && excerpt.length - metaBlock.length >= effectiveMinChars;
9948
+ const verified = !issues.includes("parse:no-priority-resolution") && !issues.includes("parse:no-canonical-resolution") && excerpt.length - metaBlock.length >= minContentChars;
9917
9949
  return {
9918
9950
  excerpt,
9919
9951
  verified,
@@ -9973,7 +10005,7 @@ function parseMarkdownSections(markdown, slcMap, artifactType) {
9973
10005
  }
9974
10006
  }
9975
10007
  const flush = () => {
9976
- if (!currentHeading && currentBody.length === 0) return;
10008
+ if (!currentHeading) return;
9977
10009
  const cleanTitle = currentHeading.replace(/^#{1,4}\s+/, "").trim();
9978
10010
  const norm = normalizeHeading(cleanTitle);
9979
10011
  const canonicalKey = reverseMap.get(norm) || findCanonicalFuzzy(norm);
@@ -10033,7 +10065,7 @@ function truncateByBudget(source, budget) {
10033
10065
  if (fenceCount % 2 === 1) cut += "\n```";
10034
10066
  return cut;
10035
10067
  }
10036
- function buildMetaBlock(source) {
10068
+ function buildMetaBlock(source, artifactType) {
10037
10069
  const fm = source.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
10038
10070
  const pick = (key) => {
10039
10071
  if (!fm) return "";
@@ -10042,9 +10074,17 @@ function buildMetaBlock(source) {
10042
10074
  };
10043
10075
  const id = pick("id");
10044
10076
  const title = pick("title");
10045
- const type = pick("type");
10077
+ const type = pick("type") || artifactType || "";
10046
10078
  return `<!-- SOURCE: ${type || "ARTIFACT"} | ${id || "unknown"}${title ? ` \u2014 ${title}` : ""} (section-aware excerpt; canonical knowledge, do not contradict) -->`;
10047
10079
  }
10080
+ function deriveArtifactTypeFromFileName(fileName) {
10081
+ if (!fileName) return void 0;
10082
+ const base = fileName.replace(/\.md$/i, "");
10083
+ const multiWord = base.match(/^(KNOWLEDGE_EXPOSITION|SECTION_LANGUAGE_CONTRACT|CONTENT_STYLE_GUIDE|CURRICULUM_FRAMEWORK|LEARNER_PROFILE|PROJECT_BRIEF|REFERENCE_PACK|CURRICULUM_PLAN)/i);
10084
+ if (multiWord) return multiWord[1].toUpperCase();
10085
+ const token = base.match(/^([A-Z][A-Z0-9_]*?)(?=_|$)/);
10086
+ return token ? token[1] : void 0;
10087
+ }
10048
10088
 
10049
10089
  // src/services/knowledgeExpositionService.ts
10050
10090
  var EXPOSITION_REL = (lessonCode) => "_sot/expositions/" + lessonCode + ".md";
@@ -10948,6 +10988,120 @@ function buildStandardsContext(input) {
10948
10988
  const selected = selectStatementsForLesson(input);
10949
10989
  return { block: buildStandardsContextBlock(selected, { language: input.language }), selected };
10950
10990
  }
10991
+
10992
+ // src/standards/standardsCoverageGate.ts
10993
+ function resolveStatementRef(ref, packs) {
10994
+ const [head, ...rest] = ref.split(":");
10995
+ const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
10996
+ const statementId = rest.length > 0 ? rest.join(":") : ref;
10997
+ for (const p of candidatePacks) {
10998
+ if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
10999
+ }
11000
+ return null;
11001
+ }
11002
+ function evaluateStandardsCoverage(input) {
11003
+ const rows = [];
11004
+ const aoToLo = /* @__PURE__ */ new Map();
11005
+ for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
11006
+ const loToRefs = /* @__PURE__ */ new Map();
11007
+ for (const lo of input.objectives) {
11008
+ loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
11009
+ }
11010
+ const taughtLOs = /* @__PURE__ */ new Set();
11011
+ for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
11012
+ const assessedLOs = /* @__PURE__ */ new Set();
11013
+ for (const q of input.quizQuestions) {
11014
+ if (q.alignedLO) assessedLOs.add(q.alignedLO);
11015
+ if (q.alignedAO) {
11016
+ const lo = aoToLo.get(q.alignedAO);
11017
+ if (lo) assessedLOs.add(lo);
11018
+ }
11019
+ }
11020
+ for (const pack of input.packs) {
11021
+ const mappingByStatement = /* @__PURE__ */ new Map();
11022
+ for (const m of pack.mappings ?? []) {
11023
+ const prev = mappingByStatement.get(m.statementId);
11024
+ if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
11025
+ mappingByStatement.set(m.statementId, m.kind);
11026
+ }
11027
+ }
11028
+ for (const statement of pack.statements) {
11029
+ const refFull = `${pack.manifest.id}:${statement.id}`;
11030
+ const issues = [];
11031
+ const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
11032
+ const kind = mappingByStatement.get(statement.id);
11033
+ const hasMapping = kind !== void 0;
11034
+ const isComplianceRelevant = kind === "covers";
11035
+ const hasActivity = los.some((lo) => taughtLOs.has(lo));
11036
+ const hasAssessment = los.some((lo) => assessedLOs.has(lo));
11037
+ let status;
11038
+ if (!hasMapping) status = "UNMAPPED";
11039
+ else if (!isComplianceRelevant) status = "PARTIAL";
11040
+ else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
11041
+ else status = "UNCOVERED";
11042
+ if (status === "UNCOVERED") {
11043
+ if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
11044
+ else {
11045
+ if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
11046
+ if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
11047
+ }
11048
+ }
11049
+ rows.push({
11050
+ packId: pack.manifest.id,
11051
+ statementId: statement.id,
11052
+ statementText: Object.values(statement.texts)[0] ?? "",
11053
+ status,
11054
+ objectives: los,
11055
+ hasActivity,
11056
+ hasAssessment,
11057
+ issues
11058
+ });
11059
+ }
11060
+ }
11061
+ for (const lo of input.objectives) {
11062
+ for (const r of lo.standardRefs ?? []) {
11063
+ if (!resolveStatementRef(r, input.packs)) {
11064
+ rows.push({
11065
+ packId: "(unresolved)",
11066
+ statementId: r,
11067
+ statementText: "",
11068
+ status: "UNCOVERED",
11069
+ objectives: [lo.code],
11070
+ hasActivity: false,
11071
+ hasAssessment: false,
11072
+ issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
11073
+ });
11074
+ }
11075
+ }
11076
+ }
11077
+ const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
11078
+ const covered = complianceRows.filter((r) => r.status === "COVERED").length;
11079
+ const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
11080
+ const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
11081
+ const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
11082
+ const lines = [
11083
+ "# Standards Coverage Report",
11084
+ "",
11085
+ `- Verdict: **${verdict}**`,
11086
+ `- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
11087
+ `- Unresolved standardRefs: ${unresolvedCount}`,
11088
+ "",
11089
+ "| Pack | Statement | Status | LOs | Activity | Assessment |",
11090
+ "|---|---|---|---|---|---|",
11091
+ ...rows.map(
11092
+ (r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
11093
+ )
11094
+ ];
11095
+ const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
11096
+ if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
11097
+ return {
11098
+ verdict,
11099
+ coveragePct,
11100
+ rows,
11101
+ summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
11102
+ rawMarkdownReport: lines.join("\n")
11103
+ };
11104
+ }
10951
11105
  var BloomHintSchema = zod.z.object({
10952
11106
  process: zod.z.enum(["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"]).describe("Cognitive process axis"),
10953
11107
  knowledge: zod.z.enum(["FACTUAL", "CONCEPTUAL", "PROCEDURAL", "METACOGNITIVE"]).optional().describe("Knowledge dimension axis (optional \u2014 most source standards do not tag it)")
@@ -11383,9 +11537,170 @@ var csta_k12_2017_default = {
11383
11537
  ]
11384
11538
  };
11385
11539
 
11540
+ // src/standards/data/acm-ieee-cs2023.json
11541
+ var acm_ieee_cs2023_default = {
11542
+ manifest: {
11543
+ id: "acm-ieee-cs2023",
11544
+ name: "ACM/IEEE-CS/AAAI Computer Science Curricula 2023 (CS2023)",
11545
+ specVersion: "1.0",
11546
+ contentVersion: "2023.1",
11547
+ subject: "computing",
11548
+ languages: ["en"],
11549
+ gradeModel: {
11550
+ type: "grades",
11551
+ range: [9, 16]
11552
+ },
11553
+ provenance: {
11554
+ sourceRef: "ACM/IEEE-CS/AAAI Computer Science Curricula 2023 - cs2023.org",
11555
+ sourceUrl: "https://cs2023.org",
11556
+ license: "ACM / IEEE Computer Society (cited verbatim for curriculum alignment)",
11557
+ importedBy: "platform",
11558
+ importedAt: "2026-09-11T00:00:00.000Z"
11559
+ },
11560
+ trust: "verified"
11561
+ },
11562
+ statements: [
11563
+ {
11564
+ id: "CS2023-SDF-01",
11565
+ gradeBand: [9, 14],
11566
+ texts: {
11567
+ en: "Design, implement, test, and debug programs using basic computation, standard conditional and iterative structures, and functions."
11568
+ },
11569
+ classifications: [
11570
+ { axis: "Knowledge Area", value: "Software Development Fundamentals (SDF)" },
11571
+ { axis: "Core Tier", value: "Tier-1 Core" }
11572
+ ],
11573
+ bloomHint: { process: "Apply" },
11574
+ keywords: ["algorithms", "control structures", "functions", "variables", "debugging", "programming", "implementation"],
11575
+ sourceRef: "CS2023, Software Development Fundamentals, Program Construction",
11576
+ provenance: { method: "source_official" }
11577
+ },
11578
+ {
11579
+ id: "CS2023-SDF-02",
11580
+ gradeBand: [9, 14],
11581
+ texts: {
11582
+ en: "Apply fundamental data structures (arrays, lists, stacks, queues, hash maps) to represent collections of data and solve algorithmic problems."
11583
+ },
11584
+ classifications: [
11585
+ { axis: "Knowledge Area", value: "Software Development Fundamentals (SDF)" },
11586
+ { axis: "Core Tier", value: "Tier-1 Core" }
11587
+ ],
11588
+ bloomHint: { process: "Apply" },
11589
+ keywords: ["data structures", "arrays", "lists", "hash maps", "collections", "stacks", "queues"],
11590
+ sourceRef: "CS2023, Software Development Fundamentals, Fundamental Data Structures",
11591
+ provenance: { method: "source_official" }
11592
+ },
11593
+ {
11594
+ id: "CS2023-AL-01",
11595
+ gradeBand: [10, 16],
11596
+ texts: {
11597
+ en: "Analyze and compare the time and space complexity of fundamental algorithms using asymptotic notation (Big-O)."
11598
+ },
11599
+ classifications: [
11600
+ { axis: "Knowledge Area", value: "Algorithms and Complexity (AL)" },
11601
+ { axis: "Core Tier", value: "Tier-1 Core" }
11602
+ ],
11603
+ bloomHint: { process: "Analyze" },
11604
+ keywords: ["complexity", "big-o", "efficiency", "sorting", "searching", "asymptotic", "runtime"],
11605
+ sourceRef: "CS2023, Algorithms and Complexity, Basic Analysis",
11606
+ provenance: { method: "source_official" }
11607
+ },
11608
+ {
11609
+ id: "CS2023-SE-01",
11610
+ gradeBand: [9, 16],
11611
+ texts: {
11612
+ en: "Decompose complex problems into modular software components applying principles of abstraction, encapsulation, and separation of concerns."
11613
+ },
11614
+ classifications: [
11615
+ { axis: "Knowledge Area", value: "Software Engineering (SE)" },
11616
+ { axis: "Core Tier", value: "Tier-1 Core" }
11617
+ ],
11618
+ bloomHint: { process: "Create" },
11619
+ keywords: ["modularity", "abstraction", "software design", "refactoring", "encapsulation", "architecture"],
11620
+ sourceRef: "CS2023, Software Engineering, Software Design and Architecture",
11621
+ provenance: { method: "source_official" }
11622
+ },
11623
+ {
11624
+ id: "CS2023-SEC-01",
11625
+ gradeBand: [9, 16],
11626
+ texts: {
11627
+ en: "Incorporate defensive programming and fundamental cybersecurity principles (least privilege, input validation, secure data handling) into software development."
11628
+ },
11629
+ classifications: [
11630
+ { axis: "Knowledge Area", value: "Security (SEC)" },
11631
+ { axis: "Core Tier", value: "Tier-1 Core" }
11632
+ ],
11633
+ bloomHint: { process: "Apply" },
11634
+ keywords: ["security", "defensive programming", "input validation", "cybersecurity", "vulnerabilities"],
11635
+ sourceRef: "CS2023, Security, Secure Software Development",
11636
+ provenance: { method: "source_official" }
11637
+ },
11638
+ {
11639
+ id: "CS2023-AI-01",
11640
+ gradeBand: [10, 16],
11641
+ texts: {
11642
+ en: "Formulate machine learning tasks, prepare training and evaluation datasets, and evaluate model performance using standard metrics."
11643
+ },
11644
+ classifications: [
11645
+ { axis: "Knowledge Area", value: "Artificial Intelligence (AI)" },
11646
+ { axis: "Core Tier", value: "Tier-2 Core" }
11647
+ ],
11648
+ bloomHint: { process: "Evaluate" },
11649
+ keywords: ["artificial intelligence", "machine learning", "dataset", "evaluation", "model", "accuracy"],
11650
+ sourceRef: "CS2023, Artificial Intelligence, Machine Learning Fundamentals",
11651
+ provenance: { method: "source_official" }
11652
+ }
11653
+ ],
11654
+ mappings: [
11655
+ {
11656
+ statementId: "CS2023-SDF-01",
11657
+ targetRef: "ontora:PROGRAM_CONSTRUCTION",
11658
+ kind: "covers",
11659
+ confidence: 0.95,
11660
+ provenance: { method: "source_official" }
11661
+ },
11662
+ {
11663
+ statementId: "CS2023-SDF-02",
11664
+ targetRef: "ontora:DATA_STRUCTURES",
11665
+ kind: "covers",
11666
+ confidence: 0.95,
11667
+ provenance: { method: "source_official" }
11668
+ },
11669
+ {
11670
+ statementId: "CS2023-AL-01",
11671
+ targetRef: "ontora:ALGORITHM_COMPLEXITY",
11672
+ kind: "covers",
11673
+ confidence: 0.95,
11674
+ provenance: { method: "source_official" }
11675
+ },
11676
+ {
11677
+ statementId: "CS2023-SE-01",
11678
+ targetRef: "ontora:SOFTWARE_ARCHITECTURE",
11679
+ kind: "covers",
11680
+ confidence: 0.95,
11681
+ provenance: { method: "source_official" }
11682
+ },
11683
+ {
11684
+ statementId: "CS2023-SEC-01",
11685
+ targetRef: "ontora:DEFENSIVE_PROGRAMMING",
11686
+ kind: "covers",
11687
+ confidence: 0.95,
11688
+ provenance: { method: "source_official" }
11689
+ },
11690
+ {
11691
+ statementId: "CS2023-AI-01",
11692
+ targetRef: "ontora:MACHINE_LEARNING",
11693
+ kind: "covers",
11694
+ confidence: 0.95,
11695
+ provenance: { method: "source_official" }
11696
+ }
11697
+ ]
11698
+ };
11699
+
11386
11700
  // src/standards/bundledPacks.ts
11387
11701
  var BUNDLED_PACK_RECORDS = {
11388
- "csta-k12-2017": csta_k12_2017_default
11702
+ "csta-k12-2017": csta_k12_2017_default,
11703
+ "acm-ieee-cs2023": acm_ieee_cs2023_default
11389
11704
  };
11390
11705
  var BUNDLED_STANDARDS_PACK_IDS = Object.keys(BUNDLED_PACK_RECORDS).sort();
11391
11706
  var parsedPackCache = /* @__PURE__ */ new Map();
@@ -11412,6 +11727,36 @@ function resolveStandardsPacks(packIds) {
11412
11727
  }
11413
11728
  return packs;
11414
11729
  }
11730
+ function findStandardStatement(statementIdOrRef) {
11731
+ if (!statementIdOrRef) return null;
11732
+ const [packPrefix, ...rest] = statementIdOrRef.split(":");
11733
+ const targetId = rest.length > 0 ? rest.join(":") : statementIdOrRef;
11734
+ const packs = resolveStandardsPacks(BUNDLED_STANDARDS_PACK_IDS);
11735
+ for (const pack of packs) {
11736
+ if (rest.length > 0 && pack.manifest.id !== packPrefix) continue;
11737
+ const stmt = pack.statements.find((s) => s.id.toLowerCase() === targetId.toLowerCase());
11738
+ if (stmt) {
11739
+ const description = stmt.texts["en"] || Object.values(stmt.texts)[0] || "";
11740
+ const category = stmt.classifications && stmt.classifications.length > 0 ? stmt.classifications.map((c) => c.value).join(", ") : void 0;
11741
+ const gradeBand = Array.isArray(stmt.gradeBand) ? stmt.gradeBand[0] === stmt.gradeBand[1] ? String(stmt.gradeBand[0]) : `${stmt.gradeBand[0]}-${stmt.gradeBand[1]}` : void 0;
11742
+ return {
11743
+ packId: pack.manifest.id,
11744
+ packTitle: pack.manifest.name,
11745
+ statementId: stmt.id,
11746
+ description,
11747
+ category,
11748
+ gradeBand
11749
+ };
11750
+ }
11751
+ }
11752
+ return null;
11753
+ }
11754
+ var STANDARD_REF_REGEX = /\b(?:(?:csta-k12-2017|acm-ieee-cs2023):)?(?:[1-3][A-B]?-[A-Z]{2}-\d{2}|CS2023-[A-Z0-9_-]+)\b/gi;
11755
+ function extractStandardRefs(text) {
11756
+ if (!text) return [];
11757
+ const matches = Array.from(text.matchAll(STANDARD_REF_REGEX)).map((m) => m[0]);
11758
+ return Array.from(new Set(matches));
11759
+ }
11415
11760
 
11416
11761
  // src/services/languageDirective.ts
11417
11762
  function resolveTargetLanguageCode(language) {
@@ -11723,7 +12068,7 @@ type: "LESSON_EDP"
11723
12068
  - **Estimated Duration:** 90 minutes
11724
12069
  - **Project Objective:** [Complete engineering system or functional deliverable students build]
11725
12070
  - **Materials & Equipment:** [Detailed hardware, software libraries, and workstation tools]
11726
- - **Learning Objectives Table:** (LO1, LO2 mapped with EDP Bloom level, Student Evidence, and Success Criteria)
12071
+ - **Learning Objectives Table:** (LO1, LO2 mapped with EDP Bloom level, Student Evidence, Success Criteria, and matching Standard ID from [STANDARDS STATEMENTS] if present, e.g. "CSTA: 1B-AP-10" or "CS2023-SDF-01")
11727
12072
 
11728
12073
  ### 2. Activity Sequence
11729
12074
  > Activity Contract table with columns: Seq, EDP Phase (Ask, Imagine, Plan, Create, Test & Improve, Share & Evaluate), Activity Type, Actor, Purpose, Student Action, Teacher Move, Output/Evidence, LO, Time, Artifact Contract.
@@ -11773,7 +12118,7 @@ type: "LESSON_GENERAL"
11773
12118
  - **Estimated Duration:** 90 minutes
11774
12119
  - **Materials & Equipment:** [Tools, IDE, workstations, resources]
11775
12120
  - **Keywords / Core Concepts:** [Core domain terminology]
11776
- - **Learning Objectives Table:** (LO1, LO2 with Bloom action verbs, Student Evidence, Success Criteria)
12121
+ - **Learning Objectives Table:** (LO1, LO2 with Bloom action verbs, Student Evidence, Success Criteria, and matching Standard ID from [STANDARDS STATEMENTS] if present, e.g. "CSTA: 1B-AP-10" or "CS2023-SDF-01")
11777
12122
 
11778
12123
  ### 2. Activity Sequence
11779
12124
  > Activity Contract table with columns: Seq, Lesson Stage (Warm-up, Core Concepts, Guided Practice, Independent Practice, Wrap-up), Activity Type, Actor, Purpose, Student Action, Teacher Move, Output/Evidence, LO, Time, Artifact Contract.
@@ -11821,7 +12166,7 @@ type: "LESSON_5E"
11821
12166
  - **Estimated Duration:** 90 minutes
11822
12167
  - **Materials & Equipment:** [Tools, IDE, hardware/software specifications]
11823
12168
  - **Keywords / Core Concepts:** [Core domain terminology]
11824
- - **Learning Objectives Table:** (LO1, LO2 with Bloom action verbs, Student Evidence, Success Criteria)
12169
+ - **Learning Objectives Table:** (LO1, LO2 with Bloom action verbs, Student Evidence, Success Criteria, and matching Standard ID from [STANDARDS STATEMENTS] if present, e.g. "CSTA: 1B-AP-10" or "CS2023-SDF-01")
11825
12170
 
11826
12171
  ### 2. Activity Sequence
11827
12172
  > Activity Contract table with columns: Seq, 5E Phase (Engage, Explore, Explain, Elaborate, Evaluate), Activity Type, Actor, Purpose, Student Action, Teacher Move, Output/Evidence, LO, Time, Artifact Contract.
@@ -13041,13 +13386,52 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
13041
13386
  });
13042
13387
  }
13043
13388
  }
13389
+ let standardsCoverage;
13390
+ if (packs.length > 0 && lessonContent) {
13391
+ try {
13392
+ const objectives = parseLessonObjectivesWithStandards(lessonContent, concept, bloomLevel, packs);
13393
+ const activities = parseActivitySequenceLo(lessonContent);
13394
+ const quizContent = await storage.readArtifact(projectId, quizRelPath).catch(() => null);
13395
+ const quizQuestions = quizContent ? parseQuizQuestionsLo(quizContent) : [];
13396
+ const selected = selectStatementsForLesson({
13397
+ packs,
13398
+ gradeBand: options.gradeBand ?? [6, 12],
13399
+ topicText: `${lessonTitle} ${concept}`
13400
+ });
13401
+ const targetStatementIds = new Set(selected.map((s) => s.statement.id.toLowerCase()));
13402
+ for (const lo of objectives) {
13403
+ for (const r of lo.standardRefs || []) {
13404
+ targetStatementIds.add(r.replace(/^[^:]+:/, "").toLowerCase());
13405
+ }
13406
+ }
13407
+ const scopedPacks = packs.map((p) => ({
13408
+ ...p,
13409
+ statements: p.statements.filter((s) => targetStatementIds.has(s.id.toLowerCase()))
13410
+ })).filter((p) => p.statements.length > 0);
13411
+ if (scopedPacks.length > 0) {
13412
+ standardsCoverage = evaluateStandardsCoverage({
13413
+ packs: scopedPacks,
13414
+ objectives,
13415
+ activities,
13416
+ quizQuestions
13417
+ });
13418
+ onProgress?.(
13419
+ "@reviewer",
13420
+ `\u{1F4CA} Standards Coverage Gate: ${standardsCoverage.verdict} (${standardsCoverage.coveragePct}% coverage, ${standardsCoverage.rows.filter((r) => r.status === "COVERED").length}/${standardsCoverage.rows.length} statements covered)`
13421
+ );
13422
+ }
13423
+ } catch (covErr) {
13424
+ console.warn("[lessonProductionService] Failed to evaluate standards coverage:", covErr?.message || covErr);
13425
+ }
13426
+ }
13044
13427
  return {
13045
13428
  lessonId: lessonCode,
13046
13429
  pedagogy,
13047
13430
  producedArtifacts,
13048
13431
  pausedForReview: false,
13049
13432
  contextInjection: lessonInjection && lessonInjection.verified ? expositionInjection || lessonInjection : lessonInjection || expositionInjection,
13050
- promptChars: commonContext.length
13433
+ promptChars: commonContext.length,
13434
+ standardsCoverage
13051
13435
  };
13052
13436
  }
13053
13437
  function parseLessonObjectives(lessonContent, concept, bloomLevel) {
@@ -13069,6 +13453,65 @@ function parseLessonObjectives(lessonContent, concept, bloomLevel) {
13069
13453
  }
13070
13454
  return objectives.slice(0, 6);
13071
13455
  }
13456
+ function parseLessonObjectivesWithStandards(lessonContent, concept, bloomLevel, _packs) {
13457
+ const baseObjectives = parseLessonObjectives(lessonContent, concept, bloomLevel);
13458
+ const lines = lessonContent.split("\n");
13459
+ return baseObjectives.map((lo) => {
13460
+ const refs = /* @__PURE__ */ new Set();
13461
+ for (const line of lines) {
13462
+ if (line.includes(lo.code)) {
13463
+ const found = extractStandardRefs(line);
13464
+ for (const r of found) refs.add(r);
13465
+ }
13466
+ }
13467
+ return {
13468
+ ...lo,
13469
+ standardRefs: refs.size > 0 ? Array.from(refs) : void 0
13470
+ };
13471
+ });
13472
+ }
13473
+ function parseActivitySequenceLo(lessonContent) {
13474
+ const activities = [];
13475
+ const lines = lessonContent.split("\n");
13476
+ let currentSeq = 1;
13477
+ for (const line of lines) {
13478
+ const matchActivity = line.match(/(?:Hoạt động|Activity|Phase|Giai đoạn)\s*(\d+)/i);
13479
+ const loMatches = Array.from(line.matchAll(/\b(LO\d+)\b/gi)).map((m) => m[1].toUpperCase());
13480
+ if (matchActivity && loMatches.length > 0) {
13481
+ activities.push({
13482
+ seq: parseInt(matchActivity[1], 10),
13483
+ lo: Array.from(new Set(loMatches))
13484
+ });
13485
+ } else if (loMatches.length > 0 && line.trim().startsWith("|")) {
13486
+ activities.push({
13487
+ seq: currentSeq++,
13488
+ lo: Array.from(new Set(loMatches))
13489
+ });
13490
+ }
13491
+ }
13492
+ if (activities.length === 0) {
13493
+ const allLos = Array.from(lessonContent.matchAll(/\b(LO\d+)\b/gi)).map((m) => m[1].toUpperCase());
13494
+ if (allLos.length > 0) {
13495
+ activities.push({ seq: 1, lo: Array.from(new Set(allLos)) });
13496
+ }
13497
+ }
13498
+ return activities;
13499
+ }
13500
+ function parseQuizQuestionsLo(quizContent) {
13501
+ const questions = [];
13502
+ const lines = quizContent.split("\n");
13503
+ for (const line of lines) {
13504
+ const loMatch = line.match(/\b(LO\d+)\b/i);
13505
+ const aoMatch = line.match(/\b(AO\d+)\b/i);
13506
+ if (loMatch || aoMatch) {
13507
+ questions.push({
13508
+ alignedLO: loMatch ? loMatch[1].toUpperCase() : void 0,
13509
+ alignedAO: aoMatch ? aoMatch[1].toUpperCase() : void 0
13510
+ });
13511
+ }
13512
+ }
13513
+ return questions;
13514
+ }
13072
13515
  async function judgeSatelliteArtifact(ctx) {
13073
13516
  const { storage, projectId, lessonCode, sat, content } = ctx;
13074
13517
  try {
@@ -16793,120 +17236,6 @@ var MisconceptionEvaluator = class {
16793
17236
  };
16794
17237
  }
16795
17238
  };
16796
-
16797
- // src/standards/standardsCoverageGate.ts
16798
- function resolveStatementRef(ref, packs) {
16799
- const [head, ...rest] = ref.split(":");
16800
- const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
16801
- const statementId = rest.length > 0 ? rest.join(":") : ref;
16802
- for (const p of candidatePacks) {
16803
- if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
16804
- }
16805
- return null;
16806
- }
16807
- function evaluateStandardsCoverage(input) {
16808
- const rows = [];
16809
- const aoToLo = /* @__PURE__ */ new Map();
16810
- for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
16811
- const loToRefs = /* @__PURE__ */ new Map();
16812
- for (const lo of input.objectives) {
16813
- loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
16814
- }
16815
- const taughtLOs = /* @__PURE__ */ new Set();
16816
- for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
16817
- const assessedLOs = /* @__PURE__ */ new Set();
16818
- for (const q of input.quizQuestions) {
16819
- if (q.alignedLO) assessedLOs.add(q.alignedLO);
16820
- if (q.alignedAO) {
16821
- const lo = aoToLo.get(q.alignedAO);
16822
- if (lo) assessedLOs.add(lo);
16823
- }
16824
- }
16825
- for (const pack of input.packs) {
16826
- const mappingByStatement = /* @__PURE__ */ new Map();
16827
- for (const m of pack.mappings ?? []) {
16828
- const prev = mappingByStatement.get(m.statementId);
16829
- if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
16830
- mappingByStatement.set(m.statementId, m.kind);
16831
- }
16832
- }
16833
- for (const statement of pack.statements) {
16834
- const refFull = `${pack.manifest.id}:${statement.id}`;
16835
- const issues = [];
16836
- const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
16837
- const kind = mappingByStatement.get(statement.id);
16838
- const hasMapping = kind !== void 0;
16839
- const isComplianceRelevant = kind === "covers";
16840
- const hasActivity = los.some((lo) => taughtLOs.has(lo));
16841
- const hasAssessment = los.some((lo) => assessedLOs.has(lo));
16842
- let status;
16843
- if (!hasMapping) status = "UNMAPPED";
16844
- else if (!isComplianceRelevant) status = "PARTIAL";
16845
- else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
16846
- else status = "UNCOVERED";
16847
- if (status === "UNCOVERED") {
16848
- if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
16849
- else {
16850
- if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
16851
- if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
16852
- }
16853
- }
16854
- rows.push({
16855
- packId: pack.manifest.id,
16856
- statementId: statement.id,
16857
- statementText: Object.values(statement.texts)[0] ?? "",
16858
- status,
16859
- objectives: los,
16860
- hasActivity,
16861
- hasAssessment,
16862
- issues
16863
- });
16864
- }
16865
- }
16866
- for (const lo of input.objectives) {
16867
- for (const r of lo.standardRefs ?? []) {
16868
- if (!resolveStatementRef(r, input.packs)) {
16869
- rows.push({
16870
- packId: "(unresolved)",
16871
- statementId: r,
16872
- statementText: "",
16873
- status: "UNCOVERED",
16874
- objectives: [lo.code],
16875
- hasActivity: false,
16876
- hasAssessment: false,
16877
- issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
16878
- });
16879
- }
16880
- }
16881
- }
16882
- const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
16883
- const covered = complianceRows.filter((r) => r.status === "COVERED").length;
16884
- const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
16885
- const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
16886
- const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
16887
- const lines = [
16888
- "# Standards Coverage Report",
16889
- "",
16890
- `- Verdict: **${verdict}**`,
16891
- `- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
16892
- `- Unresolved standardRefs: ${unresolvedCount}`,
16893
- "",
16894
- "| Pack | Statement | Status | LOs | Activity | Assessment |",
16895
- "|---|---|---|---|---|---|",
16896
- ...rows.map(
16897
- (r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
16898
- )
16899
- ];
16900
- const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
16901
- if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
16902
- return {
16903
- verdict,
16904
- coveragePct,
16905
- rows,
16906
- summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
16907
- rawMarkdownReport: lines.join("\n")
16908
- };
16909
- }
16910
17239
  var StandardsRegistryAdapter = class {
16911
17240
  client;
16912
17241
  constructor(config = {}) {
@@ -17719,6 +18048,7 @@ exports.SELF_LAB_TEMPLATE = SELF_LAB_TEMPLATE;
17719
18048
  exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
17720
18049
  exports.SLIDE_LAYOUT_PRESETS = SLIDE_LAYOUT_PRESETS;
17721
18050
  exports.SLIDE_TEMPLATE = SLIDE_TEMPLATE;
18051
+ exports.STANDARD_REF_REGEX = STANDARD_REF_REGEX;
17722
18052
  exports.STANDARD_SOT_FILES = STANDARD_SOT_FILES;
17723
18053
  exports.STATION_ROTATION_TEMPLATE = STATION_ROTATION_TEMPLATE;
17724
18054
  exports.ScaffoldDecisionEntrySchema = ScaffoldDecisionEntrySchema;
@@ -17817,8 +18147,10 @@ exports.expositionCacheKey = expositionCacheKey;
17817
18147
  exports.extractScopeSequenceRows = extractScopeSequenceRows;
17818
18148
  exports.extractSectionHeadingsFromSLC = extractSectionHeadingsFromSLC;
17819
18149
  exports.extractSessionSlice = extractSessionSlice;
18150
+ exports.extractStandardRefs = extractStandardRefs;
17820
18151
  exports.extractStreamChunk = extractStreamChunk;
17821
18152
  exports.extractThoughtAndContent = extractThoughtAndContent;
18153
+ exports.findStandardStatement = findStandardStatement;
17822
18154
  exports.formatQuizzesToCsv = formatQuizzesToCsv;
17823
18155
  exports.fulfillMediaLedger = fulfillMediaLedger;
17824
18156
  exports.gateModeFor = gateModeFor;