@thanh01.pmt/curriculum-kit 1.4.1 → 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,6 +9841,7 @@ 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
9846
  const effectiveArtifactType = opts.artifactType || deriveArtifactTypeFromFileName(opts.fileName);
9845
9847
  const sections = parseMarkdownSections(source, slcMap, effectiveArtifactType);
@@ -9859,44 +9861,74 @@ function buildSectionAwareExcerpt(markdown, opts = {}) {
9859
9861
  };
9860
9862
  }
9861
9863
  const byCanonical = /* @__PURE__ */ new Map();
9864
+ const byCleanTitle = /* @__PURE__ */ new Map();
9862
9865
  const unmatched = [];
9863
9866
  for (const s of sections) {
9864
9867
  if (s.canonicalKey && !byCanonical.has(s.canonicalKey)) {
9865
9868
  byCanonical.set(s.canonicalKey, s);
9866
- } else {
9869
+ }
9870
+ const norm = normalizeHeading(s.cleanTitle);
9871
+ if (!byCleanTitle.has(norm)) {
9872
+ byCleanTitle.set(norm, s);
9873
+ }
9874
+ if (!s.canonicalKey) {
9867
9875
  unmatched.push(s);
9868
9876
  }
9869
9877
  }
9870
9878
  const priorities = opts.priorities ?? DEFAULT_LESSON_PRIORITIES;
9871
9879
  const includedSections = [];
9872
9880
  const blocks = [];
9873
- let used = 0;
9881
+ const initialOverhead = metaBlock ? metaBlock.length + 2 : 0;
9882
+ let used = initialOverhead;
9874
9883
  const tryAdd = (heading, body, key) => {
9875
9884
  const trimmedBody = body.trim();
9876
9885
  if (!trimmedBody) return false;
9877
9886
  const block = `## ${heading}
9878
9887
 
9879
9888
  ${trimmedBody}`;
9880
- if (used + block.length > budget && used > 0) return false;
9881
- blocks.push(block);
9882
- used += block.length;
9883
- includedSections.push(key);
9884
- 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;
9885
9916
  };
9886
9917
  blocks.push(metaBlock);
9887
9918
  let sectionAware = false;
9888
9919
  for (const key of priorities) {
9889
- const sec = byCanonical.get(key);
9920
+ const sec = byCanonical.get(key) || byCleanTitle.get(normalizeHeading(key));
9890
9921
  if (!sec) continue;
9891
9922
  sectionAware = true;
9892
9923
  if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
9893
9924
  }
9894
9925
  for (const [key, sec] of byCanonical) {
9895
- if (includedSections.includes(key)) continue;
9926
+ if (includedSections.includes(key) || includedSections.includes(sec.cleanTitle)) continue;
9896
9927
  if (used >= budget) break;
9897
9928
  if (!tryAdd(sec.cleanTitle, sec.body, key)) break;
9898
9929
  }
9899
9930
  for (const sec of unmatched) {
9931
+ if (includedSections.includes(sec.cleanTitle)) continue;
9900
9932
  if (used >= budget) break;
9901
9933
  if (!tryAdd(sec.cleanTitle, sec.body, sec.cleanTitle)) break;
9902
9934
  }
@@ -9904,17 +9936,16 @@ ${trimmedBody}`;
9904
9936
  issues.push("parse:no-canonical-resolution");
9905
9937
  }
9906
9938
  if (priorities.length > 0 && !priorities.some((k) => includedSections.includes(k))) {
9907
- issues.push("parse:no-priority-resolution");
9939
+ issues.push(sections.length === 0 ? "parse:no-sections" : "parse:no-priority-resolution");
9908
9940
  }
9909
9941
  let excerpt = blocks.join("\n\n");
9910
9942
  if (excerpt.length > budget) {
9911
9943
  excerpt = truncateByBudget(excerpt, budget);
9912
9944
  }
9913
- const effectiveMinChars = Math.min(minContentChars, source.length);
9914
- if (excerpt.length - metaBlock.length < effectiveMinChars) {
9945
+ if (excerpt.length - metaBlock.length < minContentChars) {
9915
9946
  issues.push("content:insufficient");
9916
9947
  }
9917
- 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;
9918
9949
  return {
9919
9950
  excerpt,
9920
9951
  verified,
@@ -9974,7 +10005,7 @@ function parseMarkdownSections(markdown, slcMap, artifactType) {
9974
10005
  }
9975
10006
  }
9976
10007
  const flush = () => {
9977
- if (!currentHeading && currentBody.length === 0) return;
10008
+ if (!currentHeading) return;
9978
10009
  const cleanTitle = currentHeading.replace(/^#{1,4}\s+/, "").trim();
9979
10010
  const norm = normalizeHeading(cleanTitle);
9980
10011
  const canonicalKey = reverseMap.get(norm) || findCanonicalFuzzy(norm);
@@ -10957,6 +10988,120 @@ function buildStandardsContext(input) {
10957
10988
  const selected = selectStatementsForLesson(input);
10958
10989
  return { block: buildStandardsContextBlock(selected, { language: input.language }), selected };
10959
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
+ }
10960
11105
  var BloomHintSchema = zod.z.object({
10961
11106
  process: zod.z.enum(["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"]).describe("Cognitive process axis"),
10962
11107
  knowledge: zod.z.enum(["FACTUAL", "CONCEPTUAL", "PROCEDURAL", "METACOGNITIVE"]).optional().describe("Knowledge dimension axis (optional \u2014 most source standards do not tag it)")
@@ -11392,9 +11537,170 @@ var csta_k12_2017_default = {
11392
11537
  ]
11393
11538
  };
11394
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
+
11395
11700
  // src/standards/bundledPacks.ts
11396
11701
  var BUNDLED_PACK_RECORDS = {
11397
- "csta-k12-2017": csta_k12_2017_default
11702
+ "csta-k12-2017": csta_k12_2017_default,
11703
+ "acm-ieee-cs2023": acm_ieee_cs2023_default
11398
11704
  };
11399
11705
  var BUNDLED_STANDARDS_PACK_IDS = Object.keys(BUNDLED_PACK_RECORDS).sort();
11400
11706
  var parsedPackCache = /* @__PURE__ */ new Map();
@@ -11421,6 +11727,36 @@ function resolveStandardsPacks(packIds) {
11421
11727
  }
11422
11728
  return packs;
11423
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
+ }
11424
11760
 
11425
11761
  // src/services/languageDirective.ts
11426
11762
  function resolveTargetLanguageCode(language) {
@@ -11732,7 +12068,7 @@ type: "LESSON_EDP"
11732
12068
  - **Estimated Duration:** 90 minutes
11733
12069
  - **Project Objective:** [Complete engineering system or functional deliverable students build]
11734
12070
  - **Materials & Equipment:** [Detailed hardware, software libraries, and workstation tools]
11735
- - **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")
11736
12072
 
11737
12073
  ### 2. Activity Sequence
11738
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.
@@ -11782,7 +12118,7 @@ type: "LESSON_GENERAL"
11782
12118
  - **Estimated Duration:** 90 minutes
11783
12119
  - **Materials & Equipment:** [Tools, IDE, workstations, resources]
11784
12120
  - **Keywords / Core Concepts:** [Core domain terminology]
11785
- - **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")
11786
12122
 
11787
12123
  ### 2. Activity Sequence
11788
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.
@@ -11830,7 +12166,7 @@ type: "LESSON_5E"
11830
12166
  - **Estimated Duration:** 90 minutes
11831
12167
  - **Materials & Equipment:** [Tools, IDE, hardware/software specifications]
11832
12168
  - **Keywords / Core Concepts:** [Core domain terminology]
11833
- - **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")
11834
12170
 
11835
12171
  ### 2. Activity Sequence
11836
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.
@@ -13050,13 +13386,52 @@ ${buildHeadingDirective("EXT", slcMarkdown, targetLang)}`;
13050
13386
  });
13051
13387
  }
13052
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
+ }
13053
13427
  return {
13054
13428
  lessonId: lessonCode,
13055
13429
  pedagogy,
13056
13430
  producedArtifacts,
13057
13431
  pausedForReview: false,
13058
13432
  contextInjection: lessonInjection && lessonInjection.verified ? expositionInjection || lessonInjection : lessonInjection || expositionInjection,
13059
- promptChars: commonContext.length
13433
+ promptChars: commonContext.length,
13434
+ standardsCoverage
13060
13435
  };
13061
13436
  }
13062
13437
  function parseLessonObjectives(lessonContent, concept, bloomLevel) {
@@ -13078,6 +13453,65 @@ function parseLessonObjectives(lessonContent, concept, bloomLevel) {
13078
13453
  }
13079
13454
  return objectives.slice(0, 6);
13080
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
+ }
13081
13515
  async function judgeSatelliteArtifact(ctx) {
13082
13516
  const { storage, projectId, lessonCode, sat, content } = ctx;
13083
13517
  try {
@@ -16802,120 +17236,6 @@ var MisconceptionEvaluator = class {
16802
17236
  };
16803
17237
  }
16804
17238
  };
16805
-
16806
- // src/standards/standardsCoverageGate.ts
16807
- function resolveStatementRef(ref, packs) {
16808
- const [head, ...rest] = ref.split(":");
16809
- const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
16810
- const statementId = rest.length > 0 ? rest.join(":") : ref;
16811
- for (const p of candidatePacks) {
16812
- if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
16813
- }
16814
- return null;
16815
- }
16816
- function evaluateStandardsCoverage(input) {
16817
- const rows = [];
16818
- const aoToLo = /* @__PURE__ */ new Map();
16819
- for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
16820
- const loToRefs = /* @__PURE__ */ new Map();
16821
- for (const lo of input.objectives) {
16822
- loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
16823
- }
16824
- const taughtLOs = /* @__PURE__ */ new Set();
16825
- for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
16826
- const assessedLOs = /* @__PURE__ */ new Set();
16827
- for (const q of input.quizQuestions) {
16828
- if (q.alignedLO) assessedLOs.add(q.alignedLO);
16829
- if (q.alignedAO) {
16830
- const lo = aoToLo.get(q.alignedAO);
16831
- if (lo) assessedLOs.add(lo);
16832
- }
16833
- }
16834
- for (const pack of input.packs) {
16835
- const mappingByStatement = /* @__PURE__ */ new Map();
16836
- for (const m of pack.mappings ?? []) {
16837
- const prev = mappingByStatement.get(m.statementId);
16838
- if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
16839
- mappingByStatement.set(m.statementId, m.kind);
16840
- }
16841
- }
16842
- for (const statement of pack.statements) {
16843
- const refFull = `${pack.manifest.id}:${statement.id}`;
16844
- const issues = [];
16845
- const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
16846
- const kind = mappingByStatement.get(statement.id);
16847
- const hasMapping = kind !== void 0;
16848
- const isComplianceRelevant = kind === "covers";
16849
- const hasActivity = los.some((lo) => taughtLOs.has(lo));
16850
- const hasAssessment = los.some((lo) => assessedLOs.has(lo));
16851
- let status;
16852
- if (!hasMapping) status = "UNMAPPED";
16853
- else if (!isComplianceRelevant) status = "PARTIAL";
16854
- else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
16855
- else status = "UNCOVERED";
16856
- if (status === "UNCOVERED") {
16857
- if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
16858
- else {
16859
- if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
16860
- if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
16861
- }
16862
- }
16863
- rows.push({
16864
- packId: pack.manifest.id,
16865
- statementId: statement.id,
16866
- statementText: Object.values(statement.texts)[0] ?? "",
16867
- status,
16868
- objectives: los,
16869
- hasActivity,
16870
- hasAssessment,
16871
- issues
16872
- });
16873
- }
16874
- }
16875
- for (const lo of input.objectives) {
16876
- for (const r of lo.standardRefs ?? []) {
16877
- if (!resolveStatementRef(r, input.packs)) {
16878
- rows.push({
16879
- packId: "(unresolved)",
16880
- statementId: r,
16881
- statementText: "",
16882
- status: "UNCOVERED",
16883
- objectives: [lo.code],
16884
- hasActivity: false,
16885
- hasAssessment: false,
16886
- issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
16887
- });
16888
- }
16889
- }
16890
- }
16891
- const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
16892
- const covered = complianceRows.filter((r) => r.status === "COVERED").length;
16893
- const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
16894
- const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
16895
- const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
16896
- const lines = [
16897
- "# Standards Coverage Report",
16898
- "",
16899
- `- Verdict: **${verdict}**`,
16900
- `- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
16901
- `- Unresolved standardRefs: ${unresolvedCount}`,
16902
- "",
16903
- "| Pack | Statement | Status | LOs | Activity | Assessment |",
16904
- "|---|---|---|---|---|---|",
16905
- ...rows.map(
16906
- (r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
16907
- )
16908
- ];
16909
- const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
16910
- if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
16911
- return {
16912
- verdict,
16913
- coveragePct,
16914
- rows,
16915
- summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
16916
- rawMarkdownReport: lines.join("\n")
16917
- };
16918
- }
16919
17239
  var StandardsRegistryAdapter = class {
16920
17240
  client;
16921
17241
  constructor(config = {}) {
@@ -17728,6 +18048,7 @@ exports.SELF_LAB_TEMPLATE = SELF_LAB_TEMPLATE;
17728
18048
  exports.SLIDE_CANONICAL_METHODOLOGY = SLIDE_CANONICAL_METHODOLOGY;
17729
18049
  exports.SLIDE_LAYOUT_PRESETS = SLIDE_LAYOUT_PRESETS;
17730
18050
  exports.SLIDE_TEMPLATE = SLIDE_TEMPLATE;
18051
+ exports.STANDARD_REF_REGEX = STANDARD_REF_REGEX;
17731
18052
  exports.STANDARD_SOT_FILES = STANDARD_SOT_FILES;
17732
18053
  exports.STATION_ROTATION_TEMPLATE = STATION_ROTATION_TEMPLATE;
17733
18054
  exports.ScaffoldDecisionEntrySchema = ScaffoldDecisionEntrySchema;
@@ -17826,8 +18147,10 @@ exports.expositionCacheKey = expositionCacheKey;
17826
18147
  exports.extractScopeSequenceRows = extractScopeSequenceRows;
17827
18148
  exports.extractSectionHeadingsFromSLC = extractSectionHeadingsFromSLC;
17828
18149
  exports.extractSessionSlice = extractSessionSlice;
18150
+ exports.extractStandardRefs = extractStandardRefs;
17829
18151
  exports.extractStreamChunk = extractStreamChunk;
17830
18152
  exports.extractThoughtAndContent = extractThoughtAndContent;
18153
+ exports.findStandardStatement = findStandardStatement;
17831
18154
  exports.formatQuizzesToCsv = formatQuizzesToCsv;
17832
18155
  exports.fulfillMediaLedger = fulfillMediaLedger;
17833
18156
  exports.gateModeFor = gateModeFor;