@thanh01.pmt/curriculum-kit 1.0.13 → 1.0.14
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/gateSettings-DKXRztJy.d.cts +119 -0
- package/dist/gateSettings-DKXRztJy.d.ts +119 -0
- package/dist/index.cjs +511 -1178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.mjs +512 -1155
- package/dist/index.mjs.map +1 -1
- package/dist/standards/index.d.cts +2 -2
- package/dist/standards/index.d.ts +2 -2
- package/dist/{standardsCoverageGate-BWAkXY76.d.cts → standardsCoverageGate-CsUNzv6C.d.cts} +1 -1
- package/dist/{standardsCoverageGate-BWAkXY76.d.ts → standardsCoverageGate-CsUNzv6C.d.ts} +1 -1
- package/dist/workflow/index.d.cts +298 -7
- package/dist/workflow/index.d.ts +298 -7
- package/package.json +1 -1
- package/dist/index-B8lOdFuz.d.ts +0 -414
- package/dist/index-DhxvfPoW.d.cts +0 -414
package/dist/index.cjs
CHANGED
|
@@ -14,7 +14,6 @@ var supabaseJs = require('@supabase/supabase-js');
|
|
|
14
14
|
var url = require('url');
|
|
15
15
|
var jsonrepair = require('jsonrepair');
|
|
16
16
|
var rest = require('@octokit/rest');
|
|
17
|
-
var workflow = require('workflow');
|
|
18
17
|
|
|
19
18
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
20
19
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -14672,1159 +14671,233 @@ template_contract: "artifact-template-v1"
|
|
|
14672
14671
|
timestamp: dateStr
|
|
14673
14672
|
};
|
|
14674
14673
|
}
|
|
14675
|
-
async function withRateLimitBackoff(options) {
|
|
14676
|
-
let attempt = 1;
|
|
14677
|
-
try {
|
|
14678
|
-
const meta = workflow.getStepMetadata();
|
|
14679
|
-
if (meta && typeof meta.attempt === "number") {
|
|
14680
|
-
attempt = meta.attempt;
|
|
14681
|
-
}
|
|
14682
|
-
} catch {
|
|
14683
|
-
}
|
|
14684
|
-
try {
|
|
14685
|
-
return await options.fn();
|
|
14686
|
-
} catch (error) {
|
|
14687
|
-
const errorMessage = error?.message || String(error);
|
|
14688
|
-
const status = error?.status || error?.statusCode || error?.response?.status;
|
|
14689
|
-
const isRateLimited = status === 429 || errorMessage.includes("429") || errorMessage.toLowerCase().includes("rate limit") || errorMessage.toLowerCase().includes("resource exhausted") || errorMessage.toLowerCase().includes("quota exceeded");
|
|
14690
|
-
const isTransientError = status === 500 || status === 502 || status === 503 || status === 504 || errorMessage.includes("fetch failed") || errorMessage.includes("ECONNRESET") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("socket hang up");
|
|
14691
|
-
if (isRateLimited || isTransientError) {
|
|
14692
|
-
const retryAfterSeconds = Math.min(120, Math.pow(2, attempt) * 2);
|
|
14693
|
-
const reason = isRateLimited ? "Rate limit exceeded (429)" : `Transient server error (${status || "network"})`;
|
|
14694
|
-
throw new workflow.RetryableError(`[${options.stepName}] ${reason}. Retrying attempt ${attempt + 1}...`, {
|
|
14695
|
-
retryAfter: `${retryAfterSeconds}s`
|
|
14696
|
-
});
|
|
14697
|
-
}
|
|
14698
|
-
throw error;
|
|
14699
|
-
}
|
|
14700
|
-
}
|
|
14701
14674
|
|
|
14702
|
-
// src/
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14675
|
+
// src/index.ts
|
|
14676
|
+
init_errors();
|
|
14677
|
+
|
|
14678
|
+
// src/evaluators/deterministicStructuralLinter.ts
|
|
14679
|
+
var DeterministicStructuralLinter = class {
|
|
14680
|
+
/**
|
|
14681
|
+
* Validates structural invariants across a complete lesson bundle.
|
|
14682
|
+
*/
|
|
14683
|
+
static lintBundle(input) {
|
|
14684
|
+
const { lesson, quiz, activity, slides, codeLab } = input;
|
|
14685
|
+
const findings = [];
|
|
14686
|
+
const strengths = [];
|
|
14687
|
+
let score = 100;
|
|
14688
|
+
const baseLessonId = lesson.lessonId;
|
|
14689
|
+
const baseLanguage = lesson.language;
|
|
14690
|
+
if (!lesson.title || lesson.title.trim().length === 0) {
|
|
14691
|
+
score -= 20;
|
|
14692
|
+
findings.push({
|
|
14693
|
+
id: "struct_missing_lesson_title",
|
|
14694
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14695
|
+
severity: "CRITICAL",
|
|
14696
|
+
title: "Missing Lesson Title",
|
|
14697
|
+
description: "Lesson plan has an empty or whitespace title.",
|
|
14698
|
+
remediationAdvice: "Provide a non-empty lesson title."
|
|
14718
14699
|
});
|
|
14719
14700
|
}
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
stepName: `judge-master-lesson-${input.lessonId}`,
|
|
14730
|
-
fn: async () => {
|
|
14731
|
-
return auditCurriculumQualityFlow({
|
|
14732
|
-
targetArtifactType: "LESSON",
|
|
14733
|
-
lessonId: input.lessonId,
|
|
14734
|
-
expectedLanguage: input.language,
|
|
14735
|
-
targetObjectives: input.targetObjectives,
|
|
14736
|
-
generatedContentJson: JSON.stringify(input.lesson, null, 2),
|
|
14737
|
-
standardStatements: input.standardStatements,
|
|
14738
|
-
modelOptions: input.modelOptions
|
|
14701
|
+
if (!lesson.learningObjectives || lesson.learningObjectives.length === 0) {
|
|
14702
|
+
score -= 25;
|
|
14703
|
+
findings.push({
|
|
14704
|
+
id: "struct_empty_learning_objectives",
|
|
14705
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
14706
|
+
severity: "CRITICAL",
|
|
14707
|
+
title: "Empty Learning Objectives Array",
|
|
14708
|
+
description: "Lesson plan contains 0 learning objectives.",
|
|
14709
|
+
remediationAdvice: "Declare at least 2 structured Learning Objectives in the schema."
|
|
14739
14710
|
});
|
|
14740
14711
|
}
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
Detected Issues:
|
|
14751
|
-
${input.auditReport.criteria.filter((c) => !c.passed).map((c) => `- [${c.name}] ${c.feedback}`).join("\n")}
|
|
14752
|
-
Actionable Repairs:
|
|
14753
|
-
${input.auditReport.actionableRepairPrompts.map((p) => `* ${p}`).join("\n")}
|
|
14754
|
-
Language Adherence Required: "${input.language}"
|
|
14755
|
-
`.trim();
|
|
14756
|
-
return withRateLimitBackoff({
|
|
14757
|
-
stepName: `repair-master-lesson-${input.milestone.id || "L01"}`,
|
|
14758
|
-
fn: async () => {
|
|
14759
|
-
return generateLessonMasterFlow({
|
|
14760
|
-
milestone: input.milestone,
|
|
14761
|
-
language: input.language,
|
|
14762
|
-
topic: input.topic,
|
|
14763
|
-
targetAudience: input.targetAudience,
|
|
14764
|
-
contextContinuity: repairFeedback,
|
|
14765
|
-
standardsContext: input.standardsContext,
|
|
14766
|
-
modelOptions: input.modelOptions
|
|
14712
|
+
if (!lesson.sections || lesson.sections.length === 0) {
|
|
14713
|
+
score -= 25;
|
|
14714
|
+
findings.push({
|
|
14715
|
+
id: "struct_empty_lesson_sections",
|
|
14716
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
14717
|
+
severity: "CRITICAL",
|
|
14718
|
+
title: "Empty Lesson Sections Array",
|
|
14719
|
+
description: "Lesson plan contains no instructional sections.",
|
|
14720
|
+
remediationAdvice: "Provide structured lesson flow sections."
|
|
14767
14721
|
});
|
|
14768
14722
|
}
|
|
14769
|
-
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
fn: async () => generateActivityFlow(options)
|
|
14780
|
-
});
|
|
14781
|
-
console.log(` \u2705 [Step: Activity Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
|
|
14782
|
-
return res;
|
|
14783
|
-
}
|
|
14784
|
-
async function generateCodeLabStep(options) {
|
|
14785
|
-
"use step";
|
|
14786
|
-
console.log(` \u23F3 [Step: Code Lab] Generating starter & solution code LAB.md...`);
|
|
14787
|
-
const t0 = Date.now();
|
|
14788
|
-
const res = await withRateLimitBackoff({
|
|
14789
|
-
stepName: `generate-lab-${options.lesson.lessonId}`,
|
|
14790
|
-
fn: async () => generateCodeLabFlow(options)
|
|
14791
|
-
});
|
|
14792
|
-
console.log(` \u2705 [Step: Code Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
|
|
14793
|
-
return res;
|
|
14794
|
-
}
|
|
14795
|
-
async function generateSelfLabStep(options) {
|
|
14796
|
-
"use step";
|
|
14797
|
-
console.log(` \u23F3 [Step: Self-Lab] Generating SELF_LAB.md with Progressive Hints...`);
|
|
14798
|
-
const t0 = Date.now();
|
|
14799
|
-
const res = await withRateLimitBackoff({
|
|
14800
|
-
stepName: `generate-self-lab-${options.milestone.id || "L01"}`,
|
|
14801
|
-
fn: async () => generateSelfLabFlow(options)
|
|
14802
|
-
});
|
|
14803
|
-
console.log(` \u2705 [Step: Self-Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Challenges: 3 tiers Bronze/Silver/Gold)`);
|
|
14804
|
-
return res;
|
|
14805
|
-
}
|
|
14806
|
-
async function generateDiagnosticQuizStep(options) {
|
|
14807
|
-
"use step";
|
|
14808
|
-
console.log(` \u23F3 [Step: Diagnostic Quiz] Generating QUIZ.json Bloom Assessment...`);
|
|
14809
|
-
const t0 = Date.now();
|
|
14810
|
-
const res = await withRateLimitBackoff({
|
|
14811
|
-
stepName: `generate-quiz-${options.milestone.id || "L01"}`,
|
|
14812
|
-
fn: async () => generateDiagnosticQuizFlow(options)
|
|
14813
|
-
});
|
|
14814
|
-
console.log(` \u2705 [Step: Diagnostic Quiz] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Questions: ${res.questions.length})`);
|
|
14815
|
-
return res;
|
|
14816
|
-
}
|
|
14817
|
-
async function generateSlidesStep(options) {
|
|
14818
|
-
"use step";
|
|
14819
|
-
console.log(` \u23F3 [Step: Slides] Generating Marp presentation SLIDE.md...`);
|
|
14820
|
-
const t0 = Date.now();
|
|
14821
|
-
const res = await withRateLimitBackoff({
|
|
14822
|
-
stepName: `generate-slides-${options.lesson.lessonId}`,
|
|
14823
|
-
fn: async () => generateSlidesFlow(options)
|
|
14824
|
-
});
|
|
14825
|
-
console.log(` \u2705 [Step: Slides] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Slides: ${res.slides.length})`);
|
|
14826
|
-
return res;
|
|
14827
|
-
}
|
|
14828
|
-
async function generateHandoutStep(options) {
|
|
14829
|
-
"use step";
|
|
14830
|
-
console.log(` \u23F3 [Step: Handout] Generating 5-Second Rule HANDOUT.md...`);
|
|
14831
|
-
const t0 = Date.now();
|
|
14832
|
-
const res = await withRateLimitBackoff({
|
|
14833
|
-
stepName: `generate-handout-${options.lesson.lessonId}`,
|
|
14834
|
-
fn: async () => generateHandoutFlow(options)
|
|
14835
|
-
});
|
|
14836
|
-
console.log(` \u2705 [Step: Handout] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
|
|
14837
|
-
return res;
|
|
14838
|
-
}
|
|
14839
|
-
async function generateWorksheetStep(options) {
|
|
14840
|
-
"use step";
|
|
14841
|
-
return withRateLimitBackoff({
|
|
14842
|
-
stepName: `generate-worksheet-${options.lesson.lessonId}`,
|
|
14843
|
-
fn: async () => generateWorksheetFlow(options)
|
|
14844
|
-
});
|
|
14845
|
-
}
|
|
14846
|
-
async function generateTeacherGuideStep(options) {
|
|
14847
|
-
"use step";
|
|
14848
|
-
return withRateLimitBackoff({
|
|
14849
|
-
stepName: `generate-teacher-guide-${options.lesson.lessonId}`,
|
|
14850
|
-
fn: async () => generateTeacherGuideFlow(options)
|
|
14851
|
-
});
|
|
14852
|
-
}
|
|
14853
|
-
async function generateExtensionStep(options) {
|
|
14854
|
-
"use step";
|
|
14855
|
-
return withRateLimitBackoff({
|
|
14856
|
-
stepName: `generate-extension-${options.lesson.lessonId}`,
|
|
14857
|
-
fn: async () => generateExtensionFlow(options)
|
|
14858
|
-
});
|
|
14859
|
-
}
|
|
14860
|
-
|
|
14861
|
-
// src/workflow/steps/satelliteJudgeStep.ts
|
|
14862
|
-
async function judgeSatelliteStep(input) {
|
|
14863
|
-
"use step";
|
|
14864
|
-
const t0 = Date.now();
|
|
14865
|
-
console.log(" \u{1F9D1}\u200D\u2696\uFE0F [Step: Satellite Judge] " + input.artifactType + " (" + input.artifactId + ")...");
|
|
14866
|
-
return withRateLimitBackoff({
|
|
14867
|
-
stepName: "judge-satellite-" + input.artifactType + "-" + input.artifactId,
|
|
14868
|
-
fn: async () => {
|
|
14869
|
-
return auditCurriculumQualityFlow({
|
|
14870
|
-
targetArtifactType: input.artifactType,
|
|
14871
|
-
lessonId: input.artifactId,
|
|
14872
|
-
expectedLanguage: input.language,
|
|
14873
|
-
targetObjectives: input.targetObjectives,
|
|
14874
|
-
generatedContentJson: JSON.stringify(input.artifact),
|
|
14875
|
-
standardStatements: input.standardStatements,
|
|
14876
|
-
modelOptions: input.modelOptions
|
|
14723
|
+
if (quiz && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
|
|
14724
|
+
score -= 15;
|
|
14725
|
+
findings.push({
|
|
14726
|
+
id: "struct_quiz_id_mismatch",
|
|
14727
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14728
|
+
severity: "MAJOR",
|
|
14729
|
+
title: "Quiz ID Contract Mismatch",
|
|
14730
|
+
description: `Quiz ID "${quiz.quizId}" does not match Lesson ID "${baseLessonId}".`,
|
|
14731
|
+
remediationAdvice: `Sync Quiz quizId to "${baseLessonId}".`,
|
|
14732
|
+
affectedElement: quiz.quizId
|
|
14877
14733
|
});
|
|
14878
14734
|
}
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
const lessonMd = serializeLessonToMarkdown(input.lesson);
|
|
14891
|
-
const p1 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "LESSON_5E.md", lessonMd, "LESSON");
|
|
14892
|
-
savedFiles.push(p1);
|
|
14893
|
-
if (input.activity) {
|
|
14894
|
-
const actMd = serializeActivityToMarkdown(input.activity);
|
|
14895
|
-
const p2 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "ACT.md", actMd, "ACT");
|
|
14896
|
-
savedFiles.push(p2);
|
|
14897
|
-
}
|
|
14898
|
-
if (input.codeLab) {
|
|
14899
|
-
const labContent = serializeCodeLabToMarkdown(input.codeLab);
|
|
14900
|
-
const p3 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "LAB.md", labContent, "LAB");
|
|
14901
|
-
savedFiles.push(p3);
|
|
14902
|
-
}
|
|
14903
|
-
if (input.selfLab) {
|
|
14904
|
-
const selfLabMd = serializeSelfLabToMarkdown(input.selfLab);
|
|
14905
|
-
const p4 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "SELF_LAB.md", selfLabMd, "SELF_LAB");
|
|
14906
|
-
savedFiles.push(p4);
|
|
14907
|
-
}
|
|
14908
|
-
if (input.quiz) {
|
|
14909
|
-
const quizMd = serializeDiagnosticQuizToMarkdown(input.quiz);
|
|
14910
|
-
const p5a = await manager.saveArtifact(input.jobId, input.milestoneSlug, "QUIZ.md", quizMd, "QUIZ_MD");
|
|
14911
|
-
const p5b = await manager.saveArtifact(
|
|
14912
|
-
input.jobId,
|
|
14913
|
-
input.milestoneSlug,
|
|
14914
|
-
"QUIZ.json",
|
|
14915
|
-
JSON.stringify(input.quiz, null, 2),
|
|
14916
|
-
"QUIZ_JSON"
|
|
14917
|
-
);
|
|
14918
|
-
savedFiles.push(p5a, p5b);
|
|
14919
|
-
}
|
|
14920
|
-
if (input.slides) {
|
|
14921
|
-
const slidesContent = `---
|
|
14922
|
-
marp: true
|
|
14923
|
-
theme: default
|
|
14924
|
-
paginate: true
|
|
14925
|
-
---
|
|
14926
|
-
|
|
14927
|
-
# ${input.slides.title}
|
|
14928
|
-
|
|
14929
|
-
---
|
|
14930
|
-
|
|
14931
|
-
${input.slides.slides.map((s) => `## Slide ${s.slideNumber}: ${s.title}
|
|
14932
|
-
|
|
14933
|
-
${s.bulletPoints.map((b) => `- ${b}`).join("\n")}${s.codeSnippet ? `
|
|
14934
|
-
|
|
14935
|
-
\`\`\`
|
|
14936
|
-
${s.codeSnippet}
|
|
14937
|
-
\`\`\`` : ""}
|
|
14938
|
-
|
|
14939
|
-
<!-- Presenter Notes: ${s.presenterNotes || ""} -->`).join("\n\n---\n\n")}
|
|
14940
|
-
`.trim();
|
|
14941
|
-
const p6 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "SLIDE.md", slidesContent, "SLIDES");
|
|
14942
|
-
savedFiles.push(p6);
|
|
14943
|
-
}
|
|
14944
|
-
if (input.handout) {
|
|
14945
|
-
const handoutMd = serializeHandoutToMarkdown(input.handout);
|
|
14946
|
-
const p7 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "HANDOUT.md", handoutMd, "HANDOUT");
|
|
14947
|
-
savedFiles.push(p7);
|
|
14948
|
-
}
|
|
14949
|
-
if (input.cheatSheetMarkdown) {
|
|
14950
|
-
const p8 = await manager.saveArtifact(
|
|
14951
|
-
input.jobId,
|
|
14952
|
-
input.milestoneSlug,
|
|
14953
|
-
"CHEAT_SHEET.md",
|
|
14954
|
-
input.cheatSheetMarkdown,
|
|
14955
|
-
"CHEAT_SHEET"
|
|
14956
|
-
);
|
|
14957
|
-
savedFiles.push(p8);
|
|
14958
|
-
}
|
|
14959
|
-
await manager.markMilestoneCompleted(input.jobId);
|
|
14960
|
-
return {
|
|
14961
|
-
milestoneSlug: input.milestoneSlug,
|
|
14962
|
-
savedFiles
|
|
14963
|
-
};
|
|
14964
|
-
}
|
|
14965
|
-
async function publishToGitStep(options) {
|
|
14966
|
-
"use step";
|
|
14967
|
-
return publishToGitHub(options);
|
|
14968
|
-
}
|
|
14969
|
-
async function publishToSupabaseStep(options) {
|
|
14970
|
-
"use step";
|
|
14971
|
-
return publishToSupabase(options);
|
|
14972
|
-
}
|
|
14973
|
-
var approvalPayloadSchema = zod.z.object({
|
|
14974
|
-
approved: zod.z.boolean().describe("Whether the human reviewer approves the generated curriculum/lesson"),
|
|
14975
|
-
reviewerName: zod.z.string().optional().describe("Name or ID of the reviewer"),
|
|
14976
|
-
feedback: zod.z.string().optional().describe("Actionable feedback or required changes if rejected"),
|
|
14977
|
-
timestamp: zod.z.string().optional().describe("ISO timestamp of the approval action")
|
|
14978
|
-
});
|
|
14979
|
-
var lessonApprovalHook = workflow.defineHook();
|
|
14980
|
-
function approvalHookToken(projectId, lessonId, artifactType) {
|
|
14981
|
-
return `approval:${projectId}:${lessonId}:${artifactType}`;
|
|
14982
|
-
}
|
|
14983
|
-
|
|
14984
|
-
// src/standards/standardsCoverageGate.ts
|
|
14985
|
-
function resolveStatementRef(ref, packs) {
|
|
14986
|
-
const [head, ...rest] = ref.split(":");
|
|
14987
|
-
const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
|
|
14988
|
-
const statementId = rest.length > 0 ? rest.join(":") : ref;
|
|
14989
|
-
for (const p of candidatePacks) {
|
|
14990
|
-
if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
|
|
14991
|
-
}
|
|
14992
|
-
return null;
|
|
14993
|
-
}
|
|
14994
|
-
function evaluateStandardsCoverage(input) {
|
|
14995
|
-
const rows = [];
|
|
14996
|
-
const aoToLo = /* @__PURE__ */ new Map();
|
|
14997
|
-
for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
|
|
14998
|
-
const loToRefs = /* @__PURE__ */ new Map();
|
|
14999
|
-
for (const lo of input.objectives) {
|
|
15000
|
-
loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
|
|
15001
|
-
}
|
|
15002
|
-
const taughtLOs = /* @__PURE__ */ new Set();
|
|
15003
|
-
for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
|
|
15004
|
-
const assessedLOs = /* @__PURE__ */ new Set();
|
|
15005
|
-
for (const q of input.quizQuestions) {
|
|
15006
|
-
if (q.alignedLO) assessedLOs.add(q.alignedLO);
|
|
15007
|
-
if (q.alignedAO) {
|
|
15008
|
-
const lo = aoToLo.get(q.alignedAO);
|
|
15009
|
-
if (lo) assessedLOs.add(lo);
|
|
15010
|
-
}
|
|
15011
|
-
}
|
|
15012
|
-
for (const pack of input.packs) {
|
|
15013
|
-
const mappingByStatement = /* @__PURE__ */ new Map();
|
|
15014
|
-
for (const m of pack.mappings ?? []) {
|
|
15015
|
-
const prev = mappingByStatement.get(m.statementId);
|
|
15016
|
-
if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
|
|
15017
|
-
mappingByStatement.set(m.statementId, m.kind);
|
|
15018
|
-
}
|
|
14735
|
+
if (activity && activity.lessonId !== baseLessonId) {
|
|
14736
|
+
score -= 15;
|
|
14737
|
+
findings.push({
|
|
14738
|
+
id: "struct_act_id_mismatch",
|
|
14739
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14740
|
+
severity: "MAJOR",
|
|
14741
|
+
title: "Activity Lesson ID Contract Mismatch",
|
|
14742
|
+
description: `Activity lessonId "${activity.lessonId}" does not match Lesson ID "${baseLessonId}".`,
|
|
14743
|
+
remediationAdvice: `Sync Activity lessonId to "${baseLessonId}".`,
|
|
14744
|
+
affectedElement: activity.lessonId
|
|
14745
|
+
});
|
|
15019
14746
|
}
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
if (!hasMapping) status = "UNMAPPED";
|
|
15031
|
-
else if (!isComplianceRelevant) status = "PARTIAL";
|
|
15032
|
-
else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
|
|
15033
|
-
else status = "UNCOVERED";
|
|
15034
|
-
if (status === "UNCOVERED") {
|
|
15035
|
-
if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
|
|
15036
|
-
else {
|
|
15037
|
-
if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
|
|
15038
|
-
if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
|
|
15039
|
-
}
|
|
15040
|
-
}
|
|
15041
|
-
rows.push({
|
|
15042
|
-
packId: pack.manifest.id,
|
|
15043
|
-
statementId: statement.id,
|
|
15044
|
-
statementText: Object.values(statement.texts)[0] ?? "",
|
|
15045
|
-
status,
|
|
15046
|
-
objectives: los,
|
|
15047
|
-
hasActivity,
|
|
15048
|
-
hasAssessment,
|
|
15049
|
-
issues
|
|
14747
|
+
if (slides && slides.lessonId !== baseLessonId) {
|
|
14748
|
+
score -= 15;
|
|
14749
|
+
findings.push({
|
|
14750
|
+
id: "struct_slides_id_mismatch",
|
|
14751
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14752
|
+
severity: "MAJOR",
|
|
14753
|
+
title: "Slide Deck Lesson ID Contract Mismatch",
|
|
14754
|
+
description: `Slide deck lessonId "${slides.lessonId}" does not match Lesson ID "${baseLessonId}".`,
|
|
14755
|
+
remediationAdvice: `Sync Slide deck lessonId to "${baseLessonId}".`,
|
|
14756
|
+
affectedElement: slides.lessonId
|
|
15050
14757
|
});
|
|
15051
14758
|
}
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
14759
|
+
const satellites = [
|
|
14760
|
+
{ type: "QUIZ", lang: quiz?.language },
|
|
14761
|
+
{ type: "ACT", lang: activity?.language },
|
|
14762
|
+
{ type: "SLIDE", lang: slides?.language }
|
|
14763
|
+
];
|
|
14764
|
+
for (const sat of satellites) {
|
|
14765
|
+
if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
|
|
14766
|
+
score -= 25;
|
|
14767
|
+
findings.push({
|
|
14768
|
+
id: `struct_language_mismatch_${sat.type}`,
|
|
14769
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14770
|
+
severity: "CRITICAL",
|
|
14771
|
+
title: `Language Policy Inconsistency in ${sat.type}`,
|
|
14772
|
+
description: `Master Lesson is in "${baseLanguage}" but ${sat.type} is declared in "${sat.lang}".`,
|
|
14773
|
+
remediationAdvice: `Set ${sat.type} language to "${baseLanguage}".`
|
|
15065
14774
|
});
|
|
15066
14775
|
}
|
|
15067
14776
|
}
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
|
|
15087
|
-
|
|
15088
|
-
|
|
15089
|
-
|
|
15090
|
-
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
}
|
|
15097
|
-
|
|
15098
|
-
|
|
15099
|
-
constructor(config = {}) {
|
|
15100
|
-
if (config.client) {
|
|
15101
|
-
this.client = config.client;
|
|
15102
|
-
return;
|
|
15103
|
-
}
|
|
15104
|
-
const url = config.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || "";
|
|
15105
|
-
const key = config.supabaseServiceRoleKey || process.env.SUPABASE_SERVICE_ROLE_KEY || config.supabaseAnonKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
|
|
15106
|
-
if (!url || !key) {
|
|
15107
|
-
throw new Error("StandardsRegistryAdapter: Supabase URL/key missing (pass client or env).");
|
|
14777
|
+
if (quiz && quiz.questions) {
|
|
14778
|
+
for (let i = 0; i < quiz.questions.length; i++) {
|
|
14779
|
+
const q = quiz.questions[i];
|
|
14780
|
+
const qId = q.id || `Q${i + 1}`;
|
|
14781
|
+
const options = q.options || [];
|
|
14782
|
+
if (options.length < 4) {
|
|
14783
|
+
score -= 10;
|
|
14784
|
+
findings.push({
|
|
14785
|
+
id: `struct_quiz_option_count_${qId}`,
|
|
14786
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
14787
|
+
severity: "MAJOR",
|
|
14788
|
+
title: `Structural Option Count Error in ${qId}`,
|
|
14789
|
+
description: `Question ${qId} has ${options.length} options (standard schema requires 4).`,
|
|
14790
|
+
remediationAdvice: "Ensure each question has 4 options (A, B, C, D).",
|
|
14791
|
+
affectedElement: qId
|
|
14792
|
+
});
|
|
14793
|
+
}
|
|
14794
|
+
const correctCount = options.filter((o) => o.isCorrect).length;
|
|
14795
|
+
if (correctCount !== 1) {
|
|
14796
|
+
score -= 20;
|
|
14797
|
+
findings.push({
|
|
14798
|
+
id: `struct_quiz_key_count_${qId}`,
|
|
14799
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
14800
|
+
severity: "CRITICAL",
|
|
14801
|
+
title: `Key Assignment Error in ${qId}`,
|
|
14802
|
+
description: `Question ${qId} has ${correctCount} correct options (must be exactly 1).`,
|
|
14803
|
+
remediationAdvice: "Set `isCorrect: true` on exactly 1 option.",
|
|
14804
|
+
affectedElement: qId
|
|
14805
|
+
});
|
|
14806
|
+
}
|
|
14807
|
+
}
|
|
15108
14808
|
}
|
|
15109
|
-
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
|
|
15114
|
-
|
|
15115
|
-
|
|
15116
|
-
|
|
14809
|
+
if (activity?.materialsBOM?.hardware && activity.materialsBOM.hardware.length > 0) {
|
|
14810
|
+
const hwText = activity.materialsBOM.hardware.join(" ").toLowerCase();
|
|
14811
|
+
const hasLED = hwText.includes("led");
|
|
14812
|
+
const hasResistor = hwText.includes("\u0111i\u1EC7n tr\u1EDF") || hwText.includes("resistor") || hwText.includes("220") || hwText.includes("330") || hwText.includes("1k");
|
|
14813
|
+
if (hasLED && !hasResistor) {
|
|
14814
|
+
score -= 20;
|
|
14815
|
+
findings.push({
|
|
14816
|
+
id: "struct_hardware_unsafe_led_no_resistor",
|
|
14817
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
14818
|
+
severity: "CRITICAL",
|
|
14819
|
+
title: "Unsafe Circuit BOM: LED without Current-Limiting Resistor",
|
|
14820
|
+
description: "Hardware BOM includes LED without a 220\u03A9-1k\u03A9 resistor, causing circuit overload.",
|
|
14821
|
+
remediationAdvice: "Add a 220\u03A9 current-limiting resistor to the hardware materials list."
|
|
14822
|
+
});
|
|
14823
|
+
}
|
|
15117
14824
|
}
|
|
15118
|
-
|
|
15119
|
-
const
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
name: p.manifest.name,
|
|
15123
|
-
spec_version: p.manifest.specVersion,
|
|
15124
|
-
subject: p.manifest.subject,
|
|
15125
|
-
languages: p.manifest.languages,
|
|
15126
|
-
grade_model: p.manifest.gradeModel,
|
|
15127
|
-
provenance: p.manifest.provenance,
|
|
15128
|
-
trust: p.manifest.trust,
|
|
15129
|
-
status: "draft",
|
|
15130
|
-
organization_code: opts?.organizationCode ?? (p.manifest.trust === "verified" ? null : p.manifest.provenance.importedBy?.replace(/^org:/, "") || null),
|
|
15131
|
-
original_file_path: opts?.originalFilePath ?? null,
|
|
15132
|
-
created_by: opts?.createdBy ?? null
|
|
15133
|
-
}).select("id").single();
|
|
15134
|
-
if (fwErr) throw new Error("importPack: framework insert failed: " + fwErr.message);
|
|
15135
|
-
const frameworkId = fw.id;
|
|
15136
|
-
const statementRows = p.statements.map((s) => ({
|
|
15137
|
-
framework_id: frameworkId,
|
|
15138
|
-
statement_id: s.id,
|
|
15139
|
-
parent_statement_id: s.parentId ?? null,
|
|
15140
|
-
grade_min: s.gradeBand[0],
|
|
15141
|
-
grade_max: s.gradeBand[1],
|
|
15142
|
-
texts: s.texts,
|
|
15143
|
-
classifications: s.classifications ?? [],
|
|
15144
|
-
bloom_hint: s.bloomHint ?? null,
|
|
15145
|
-
keywords: s.keywords ?? [],
|
|
15146
|
-
source_ref: s.sourceRef ?? null,
|
|
15147
|
-
provenance: s.provenance
|
|
15148
|
-
}));
|
|
15149
|
-
const { error: stErr, count: stCount } = await this.client.from("standards_statements").insert(statementRows, { count: "exact" });
|
|
15150
|
-
if (stErr) throw new Error("importPack: statement insert failed: " + stErr.message);
|
|
15151
|
-
let mappingCount = 0;
|
|
15152
|
-
if (p.mappings && p.mappings.length > 0) {
|
|
15153
|
-
const mappingRows = p.mappings.map((m) => ({
|
|
15154
|
-
framework_id: frameworkId,
|
|
15155
|
-
statement_id: m.statementId,
|
|
15156
|
-
target_ref: m.targetRef,
|
|
15157
|
-
kind: m.kind,
|
|
15158
|
-
confidence: m.confidence,
|
|
15159
|
-
provenance: m.provenance
|
|
15160
|
-
}));
|
|
15161
|
-
const { error: mpErr, count: mpCount } = await this.client.from("standards_mappings").insert(mappingRows, { count: "exact" });
|
|
15162
|
-
if (mpErr) throw new Error("importPack: mapping insert failed: " + mpErr.message);
|
|
15163
|
-
mappingCount = mpCount ?? mappingRows.length;
|
|
14825
|
+
score = Math.max(0, Math.min(100, score));
|
|
14826
|
+
const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
|
|
14827
|
+
if (passed) {
|
|
14828
|
+
strengths.push("Deterministic structure, ID contracts, and schema invariants strictly verified.");
|
|
15164
14829
|
}
|
|
15165
|
-
return {
|
|
15166
|
-
|
|
15167
|
-
|
|
15168
|
-
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
}
|
|
15172
|
-
async deprecatePack(frameworkDbId) {
|
|
15173
|
-
const { error } = await this.client.from("standards_frameworks").update({ status: "deprecated" }).eq("id", frameworkDbId);
|
|
15174
|
-
if (error) throw new Error("deprecatePack failed: " + error.message);
|
|
15175
|
-
}
|
|
15176
|
-
async adoptPack(frameworkDbId, organizationCode, adoptedBy) {
|
|
15177
|
-
const { error } = await this.client.from("standards_org_adoptions").upsert(
|
|
15178
|
-
{ framework_id: frameworkDbId, organization_code: organizationCode, adopted_by: adoptedBy ?? null },
|
|
15179
|
-
{ onConflict: "organization_code,framework_id" }
|
|
15180
|
-
);
|
|
15181
|
-
if (error) throw new Error("adoptPack failed: " + error.message);
|
|
14830
|
+
return {
|
|
14831
|
+
passed,
|
|
14832
|
+
structuralScore: score,
|
|
14833
|
+
findings,
|
|
14834
|
+
strengths
|
|
14835
|
+
};
|
|
15182
14836
|
}
|
|
15183
|
-
|
|
14837
|
+
};
|
|
14838
|
+
|
|
14839
|
+
// src/evaluators/academicAuditor.ts
|
|
14840
|
+
var AcademicAuditor = class {
|
|
15184
14841
|
/**
|
|
15185
|
-
*
|
|
15186
|
-
*
|
|
15187
|
-
*
|
|
14842
|
+
* Evaluates a complete lesson bundle.
|
|
14843
|
+
* Step 1 (Deterministic): Fast structural, contract, schema, and safety linting.
|
|
14844
|
+
* Step 2 (Semantic LLM-as-a-Judge): Deep pedagogical, cognitive (Bloom), and misconception analysis with live frontier LLM.
|
|
15188
14845
|
*/
|
|
15189
|
-
async
|
|
15190
|
-
|
|
15191
|
-
const
|
|
15192
|
-
|
|
15193
|
-
|
|
15194
|
-
|
|
15195
|
-
|
|
15196
|
-
|
|
15197
|
-
return global || own || adopted;
|
|
14846
|
+
static async auditLessonBundle(input) {
|
|
14847
|
+
const { lesson, quiz, activity, slides, codeLab, modelOptions, executeLLMJudge = true } = input;
|
|
14848
|
+
const structuralResult = DeterministicStructuralLinter.lintBundle({
|
|
14849
|
+
lesson,
|
|
14850
|
+
quiz,
|
|
14851
|
+
activity,
|
|
14852
|
+
slides,
|
|
14853
|
+
codeLab
|
|
15198
14854
|
});
|
|
15199
|
-
|
|
15200
|
-
const
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
|
|
15207
|
-
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15231
|
-
|
|
15232
|
-
|
|
15233
|
-
})
|
|
15234
|
-
|
|
15235
|
-
|
|
15236
|
-
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15243
|
-
|
|
15244
|
-
|
|
15245
|
-
const hydrated = FrameworkPackSchema.safeParse({
|
|
15246
|
-
manifest: {
|
|
15247
|
-
id: f.pack_id,
|
|
15248
|
-
name: f.name,
|
|
15249
|
-
specVersion: f.spec_version || "1.0",
|
|
15250
|
-
contentVersion: f.content_version,
|
|
15251
|
-
subject: f.subject,
|
|
15252
|
-
languages: f.languages,
|
|
15253
|
-
gradeModel: f.grade_model,
|
|
15254
|
-
provenance: f.provenance,
|
|
15255
|
-
trust: f.trust
|
|
15256
|
-
},
|
|
15257
|
-
statements: stmts,
|
|
15258
|
-
mappings: finalMappings
|
|
15259
|
-
});
|
|
15260
|
-
if (!hydrated.success) {
|
|
15261
|
-
throw new Error('loadPacksForOrg: hydrated pack "' + f.pack_id + '" failed schema: ' + hydrated.error.issues[0]?.message);
|
|
15262
|
-
}
|
|
15263
|
-
return { ...hydrated.data, dbId: f.id, status: f.status, organizationCode: f.organization_code ?? null };
|
|
15264
|
-
});
|
|
15265
|
-
}
|
|
15266
|
-
};
|
|
15267
|
-
|
|
15268
|
-
// src/workflow/workflows/milestoneWorkflow.ts
|
|
15269
|
-
async function generateMilestoneWorkflow(input) {
|
|
15270
|
-
"use workflow";
|
|
15271
|
-
const language = input.language || "vi";
|
|
15272
|
-
const milestoneId = input.milestone.id || input.milestone.concept_code || "L01";
|
|
15273
|
-
const slugPrefix = /^\d+$/.test(milestoneId) ? `M${milestoneId.padStart(2, "0")}` : milestoneId;
|
|
15274
|
-
const milestoneSlug = `${slugPrefix}_${(input.milestone.name || "milestone").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
15275
|
-
const bundleTier = input.bundleTier || "minimum";
|
|
15276
|
-
const gates = resolveGateSettings(input.gateSettings);
|
|
15277
|
-
const techStack = input.techStack || input.milestone.tech_keywords || ["General Technology"];
|
|
15278
|
-
console.log(" \u{1F6A6} [Gate] LESSON: " + gates.LESSON + " | ACT: " + gates.ACT + " | QUIZ: " + gates.QUIZ);
|
|
15279
|
-
const targetObjectives = (input.milestone.learning_objectives || []).map((lo) => ({
|
|
15280
|
-
code: lo.code,
|
|
15281
|
-
description: lo.description || lo.name || "",
|
|
15282
|
-
bloomLevel: lo.bloom_level || "understand"
|
|
15283
|
-
}));
|
|
15284
|
-
const packs = input.standardsPacks ?? [];
|
|
15285
|
-
const { block: standardsContext, selected: selectedStatements } = buildStandardsContext({
|
|
15286
|
-
packs,
|
|
15287
|
-
gradeBand: input.milestone.grade_band ?? [6, 12],
|
|
15288
|
-
topicText: [input.topic, input.milestone.name, input.milestone.description, ...input.milestone.tech_keywords || []].filter(Boolean).join(" "),
|
|
15289
|
-
conceptCodes: (input.milestone.concept_code || "").split(",").map((c) => c.trim()).filter(Boolean),
|
|
15290
|
-
language
|
|
15291
|
-
});
|
|
15292
|
-
const standardStatements = {};
|
|
15293
|
-
for (const s of selectedStatements) {
|
|
15294
|
-
standardStatements[s.packId + ":" + s.statement.id] = Object.values(s.statement.texts)[0] || "";
|
|
15295
|
-
}
|
|
15296
|
-
if (selectedStatements.length > 0) {
|
|
15297
|
-
console.log(" \u{1F4D0} [Standards] Grounded with " + selectedStatements.length + " verbatim statement(s) from " + new Set(selectedStatements.map((s) => s.packId)).size + " pack(s)");
|
|
15298
|
-
}
|
|
15299
|
-
let lesson = await generateMasterLessonStep({
|
|
15300
|
-
milestone: input.milestone,
|
|
15301
|
-
language,
|
|
15302
|
-
topic: input.topic,
|
|
15303
|
-
targetAudience: input.targetAudience,
|
|
15304
|
-
standardsContext: standardsContext || void 0,
|
|
15305
|
-
modelOptions: input.modelOptions
|
|
15306
|
-
});
|
|
15307
|
-
let auditReport;
|
|
15308
|
-
const lessonGate = gateModeFor(gates, "LESSON");
|
|
15309
|
-
if (lessonGate === "LLM_JUDGE") {
|
|
15310
|
-
auditReport = await judgeMasterLessonStep({
|
|
15311
|
-
lessonId: milestoneId,
|
|
15312
|
-
lesson,
|
|
15313
|
-
language,
|
|
15314
|
-
targetObjectives,
|
|
15315
|
-
standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0,
|
|
15316
|
-
modelOptions: input.modelOptions
|
|
15317
|
-
});
|
|
15318
|
-
if (auditReport.overallVerdict === "FAIL" || auditReport.overallVerdict === "NEEDS_REVISION") {
|
|
15319
|
-
lesson = await repairMasterLessonStep({
|
|
15320
|
-
milestone: input.milestone,
|
|
15321
|
-
language,
|
|
15322
|
-
topic: input.topic,
|
|
15323
|
-
targetAudience: input.targetAudience,
|
|
15324
|
-
auditReport,
|
|
15325
|
-
standardsContext: standardsContext || void 0,
|
|
15326
|
-
modelOptions: input.modelOptions
|
|
15327
|
-
});
|
|
15328
|
-
}
|
|
15329
|
-
} else if (lessonGate === "HITL") {
|
|
15330
|
-
const hookToken = "gate:lesson:" + milestoneId;
|
|
15331
|
-
console.log(" \u23F8 [Gate] LESSON awaiting HUMAN review (HITL) \u2014 hook " + hookToken);
|
|
15332
|
-
const workflowPkg = await import('workflow');
|
|
15333
|
-
const createHook = workflowPkg.createHook;
|
|
15334
|
-
const hook = createHook({ token: hookToken });
|
|
15335
|
-
const review = await hook;
|
|
15336
|
-
console.log(" \u2705 [Gate] Human verdict: " + (review.approved ? "APPROVED" : "REJECTED") + (review.feedback ? " \u2014 " + review.feedback : ""));
|
|
15337
|
-
if (!review.approved) {
|
|
15338
|
-
const repairReport = {
|
|
15339
|
-
overallVerdict: "NEEDS_REVISION",
|
|
15340
|
-
totalScore: 0,
|
|
15341
|
-
languageAdherencePassed: true,
|
|
15342
|
-
criteria: [],
|
|
15343
|
-
actionableRepairPrompts: review.feedback ? [review.feedback] : ["S\u1EEDa theo ph\u1EA3n h\u1ED3i c\u1EE7a ng\u01B0\u1EDDi duy\u1EC7t"]
|
|
15344
|
-
};
|
|
15345
|
-
lesson = await repairMasterLessonStep({
|
|
15346
|
-
milestone: input.milestone,
|
|
15347
|
-
language,
|
|
15348
|
-
topic: input.topic,
|
|
15349
|
-
targetAudience: input.targetAudience,
|
|
15350
|
-
auditReport: repairReport,
|
|
15351
|
-
standardsContext: standardsContext || void 0,
|
|
15352
|
-
modelOptions: input.modelOptions
|
|
15353
|
-
});
|
|
15354
|
-
const hook2 = createHook({ token: hookToken + ":v2" });
|
|
15355
|
-
const review2 = await hook2;
|
|
15356
|
-
console.log(" \u2705 [Gate] Human verdict (v2): " + (review2.approved ? "APPROVED" : "REJECTED"));
|
|
15357
|
-
if (!review2.approved) {
|
|
15358
|
-
console.log(" \u{1F6D1} [Gate] LESSON rejected twice \u2014 satellites & save BLOCKED");
|
|
15359
|
-
return {
|
|
15360
|
-
milestoneId,
|
|
15361
|
-
milestoneSlug,
|
|
15362
|
-
status: "FAILED",
|
|
15363
|
-
gateBlocked: { artifactType: "LESSON", reason: "HITL_REJECTED_V2", feedback: review2.feedback },
|
|
15364
|
-
artifacts: void 0
|
|
15365
|
-
};
|
|
15366
|
-
}
|
|
15367
|
-
}
|
|
15368
|
-
}
|
|
15369
|
-
const [activity, selfLab, quiz, slides, handout] = await Promise.all([
|
|
15370
|
-
generateActivityStep({ lesson, language, modelOptions: input.modelOptions }),
|
|
15371
|
-
generateSelfLabStep({ milestone: input.milestone, language, targetTechStack: techStack, modelOptions: input.modelOptions }),
|
|
15372
|
-
generateDiagnosticQuizStep({ milestone: input.milestone, language, modelOptions: input.modelOptions }),
|
|
15373
|
-
generateSlidesStep({ lesson, language, modelOptions: input.modelOptions }),
|
|
15374
|
-
generateHandoutStep({ lesson, language, modelOptions: input.modelOptions })
|
|
15375
|
-
]);
|
|
15376
|
-
const codeLab = await generateCodeLabStep({
|
|
15377
|
-
lesson,
|
|
15378
|
-
activity,
|
|
15379
|
-
language,
|
|
15380
|
-
targetTechStack: techStack,
|
|
15381
|
-
modelOptions: input.modelOptions
|
|
15382
|
-
});
|
|
15383
|
-
const satelliteReports = {};
|
|
15384
|
-
const satelliteCandidates = [
|
|
15385
|
-
{ type: "ACT", id: "ACT_" + milestoneId, artifact: activity },
|
|
15386
|
-
{ type: "QUIZ", id: "QUIZ_" + milestoneId, artifact: quiz },
|
|
15387
|
-
{ type: "SLIDE", id: "SLIDE_" + milestoneId, artifact: slides },
|
|
15388
|
-
{ type: "HANDOUT", id: "HANDOUT_" + milestoneId, artifact: handout },
|
|
15389
|
-
{ type: "CODE", id: "CODE_" + milestoneId, artifact: codeLab }
|
|
15390
|
-
];
|
|
15391
|
-
const toJudge = satelliteCandidates.filter((c) => c.artifact && gateModeFor(gates, c.type) === "LLM_JUDGE");
|
|
15392
|
-
if (toJudge.length > 0) {
|
|
15393
|
-
const reports = await Promise.all(
|
|
15394
|
-
toJudge.map(
|
|
15395
|
-
(c) => judgeSatelliteStep({
|
|
15396
|
-
artifactType: c.type,
|
|
15397
|
-
artifactId: c.id,
|
|
15398
|
-
artifact: c.artifact,
|
|
15399
|
-
language,
|
|
15400
|
-
targetObjectives,
|
|
15401
|
-
standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0,
|
|
15402
|
-
modelOptions: input.modelOptions
|
|
15403
|
-
}).catch((err) => {
|
|
15404
|
-
console.warn(" \u26A0 [Gate] satellite judge failed for " + c.type + ": " + err.message);
|
|
15405
|
-
return void 0;
|
|
15406
|
-
})
|
|
15407
|
-
)
|
|
15408
|
-
);
|
|
15409
|
-
for (let i = 0; i < toJudge.length; i++) {
|
|
15410
|
-
const r = reports[i];
|
|
15411
|
-
if (r) satelliteReports[toJudge[i].type] = r;
|
|
15412
|
-
}
|
|
15413
|
-
}
|
|
15414
|
-
let worksheet;
|
|
15415
|
-
let teacherGuide;
|
|
15416
|
-
let extension;
|
|
15417
|
-
if (bundleTier === "full") {
|
|
15418
|
-
[worksheet, teacherGuide, extension] = await Promise.all([
|
|
15419
|
-
generateWorksheetStep({ lesson, language, modelOptions: input.modelOptions }),
|
|
15420
|
-
generateTeacherGuideStep({ lesson, activity, language, modelOptions: input.modelOptions }),
|
|
15421
|
-
generateExtensionStep({ lesson, activity, language, modelOptions: input.modelOptions })
|
|
15422
|
-
]);
|
|
15423
|
-
}
|
|
15424
|
-
let standardsCoverage;
|
|
15425
|
-
if (packs.length > 0) {
|
|
15426
|
-
standardsCoverage = evaluateStandardsCoverage({
|
|
15427
|
-
packs,
|
|
15428
|
-
objectives: (lesson.learningObjectives || []).map((lo) => ({
|
|
15429
|
-
code: lo.code,
|
|
15430
|
-
standardRefs: lo.standardRefs,
|
|
15431
|
-
conceptRefs: lo.conceptRefs
|
|
15432
|
-
})),
|
|
15433
|
-
// NOTE: ActivityLab has no structured LO linkage yet — activity coverage rows are derived
|
|
15434
|
-
// from LO→quiz paths inside the gate. Wire ACT.loRefs in a future schema revision.
|
|
15435
|
-
activities: [],
|
|
15436
|
-
quizQuestions: (quiz?.questions || []).map((q) => ({ alignedLO: q.alignedLO, alignedAO: q.alignedAO })),
|
|
15437
|
-
assessmentObjectives: quiz?.assessmentObjectives?.map((ao) => ({ aoCode: ao.aoCode, alignedLO: ao.alignedLO }))
|
|
15438
|
-
});
|
|
15439
|
-
console.log(" \u{1F4D0} [Standards Coverage] " + standardsCoverage.summary);
|
|
15440
|
-
const enforce = input.enforceStandardsCoverage ?? true;
|
|
15441
|
-
if (enforce && standardsCoverage.verdict === "FAIL") {
|
|
15442
|
-
console.error(" \u26D4 [Standards Coverage] HARD BLOCK \u2014 " + standardsCoverage.summary);
|
|
15443
|
-
console.error(standardsCoverage.rawMarkdownReport.split("\n").filter((l) => l.startsWith("- [")).slice(0, 10).join("\n"));
|
|
15444
|
-
return {
|
|
15445
|
-
milestoneId,
|
|
15446
|
-
milestoneSlug,
|
|
15447
|
-
status: "FAILED",
|
|
15448
|
-
savedResult: { workspaceDir: "", files: [] },
|
|
15449
|
-
judgeReport: auditReport,
|
|
15450
|
-
standardsCoverage,
|
|
15451
|
-
artifacts: { lesson, activity, codeLab, selfLab, quiz, slides, handout, worksheet, teacherGuide, extension }
|
|
15452
|
-
};
|
|
15453
|
-
}
|
|
15454
|
-
}
|
|
15455
|
-
const savedResult = await saveMilestoneToWorkspaceStep({
|
|
15456
|
-
jobId: input.jobId,
|
|
15457
|
-
milestoneSlug,
|
|
15458
|
-
lesson,
|
|
15459
|
-
activity,
|
|
15460
|
-
codeLab,
|
|
15461
|
-
selfLab,
|
|
15462
|
-
quiz,
|
|
15463
|
-
slides,
|
|
15464
|
-
handout,
|
|
15465
|
-
baseWorkspaceDir: input.baseWorkspaceDir
|
|
15466
|
-
});
|
|
15467
|
-
return {
|
|
15468
|
-
milestoneId,
|
|
15469
|
-
milestoneSlug,
|
|
15470
|
-
status: "SUCCESS",
|
|
15471
|
-
savedResult,
|
|
15472
|
-
judgeReport: auditReport,
|
|
15473
|
-
satelliteJudgeReports: Object.keys(satelliteReports).length > 0 ? satelliteReports : void 0,
|
|
15474
|
-
gatesResolved: gates,
|
|
15475
|
-
standardsCoverage,
|
|
15476
|
-
artifacts: {
|
|
15477
|
-
lesson,
|
|
15478
|
-
activity,
|
|
15479
|
-
codeLab,
|
|
15480
|
-
selfLab,
|
|
15481
|
-
quiz,
|
|
15482
|
-
slides,
|
|
15483
|
-
handout,
|
|
15484
|
-
worksheet,
|
|
15485
|
-
teacherGuide,
|
|
15486
|
-
extension
|
|
15487
|
-
}
|
|
15488
|
-
};
|
|
15489
|
-
}
|
|
15490
|
-
async function generateRoadmapWorkflow(input) {
|
|
15491
|
-
"use workflow";
|
|
15492
|
-
const jobId = input.jobId || `job_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
15493
|
-
const language = input.language || input.roadmap.target_language || "vi";
|
|
15494
|
-
const topic = input.roadmap.topic || input.roadmap.goal || "General Curriculum";
|
|
15495
|
-
const courseTitle = input.roadmap.title || `Curriculum: ${topic}`;
|
|
15496
|
-
const batchSize = input.batchSize || 2;
|
|
15497
|
-
const milestones = input.roadmap.milestones || [];
|
|
15498
|
-
const workspaceManager = new LocalWorkspaceManager(input.baseWorkspaceDir);
|
|
15499
|
-
const workspaceDir = await workspaceManager.initJobWorkspace(jobId, {
|
|
15500
|
-
courseTitle,
|
|
15501
|
-
topic,
|
|
15502
|
-
language,
|
|
15503
|
-
totalMilestones: milestones.length
|
|
15504
|
-
});
|
|
15505
|
-
const milestoneOutcomes = [];
|
|
15506
|
-
for (let i = 0; i < milestones.length; i += batchSize) {
|
|
15507
|
-
const batch = milestones.slice(i, i + batchSize);
|
|
15508
|
-
const batchSettled = await Promise.allSettled(
|
|
15509
|
-
batch.map(
|
|
15510
|
-
(milestone) => generateMilestoneWorkflow({
|
|
15511
|
-
jobId,
|
|
15512
|
-
milestone,
|
|
15513
|
-
language,
|
|
15514
|
-
topic,
|
|
15515
|
-
targetAudience: input.roadmap.target_audience,
|
|
15516
|
-
techStack: input.roadmap.tech_stack || milestone.tech_keywords,
|
|
15517
|
-
bundleTier: input.bundleTier,
|
|
15518
|
-
gateSettings: input.gateSettings,
|
|
15519
|
-
baseWorkspaceDir: input.baseWorkspaceDir,
|
|
15520
|
-
modelOptions: input.modelOptions
|
|
15521
|
-
})
|
|
15522
|
-
)
|
|
15523
|
-
);
|
|
15524
|
-
for (const [idx, outcome] of batchSettled.entries()) {
|
|
15525
|
-
const targetMilestone = batch[idx];
|
|
15526
|
-
const mId = targetMilestone.id || targetMilestone.concept_code || `M${i + idx + 1}`;
|
|
15527
|
-
if (outcome.status === "fulfilled") {
|
|
15528
|
-
milestoneOutcomes.push({
|
|
15529
|
-
milestoneId: mId,
|
|
15530
|
-
status: "FULFILLED",
|
|
15531
|
-
result: outcome.value
|
|
15532
|
-
});
|
|
15533
|
-
} else {
|
|
15534
|
-
milestoneOutcomes.push({
|
|
15535
|
-
milestoneId: mId,
|
|
15536
|
-
status: "REJECTED",
|
|
15537
|
-
error: outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason)
|
|
15538
|
-
});
|
|
15539
|
-
}
|
|
15540
|
-
}
|
|
15541
|
-
if (i + batchSize < milestones.length) {
|
|
15542
|
-
await workflow.sleep("2s");
|
|
15543
|
-
}
|
|
15544
|
-
}
|
|
15545
|
-
const successfulMilestones = milestoneOutcomes.filter((m) => m.status === "FULFILLED").length;
|
|
15546
|
-
const failedMilestones = milestoneOutcomes.filter((m) => m.status === "REJECTED").length;
|
|
15547
|
-
let gitPublishResult = void 0;
|
|
15548
|
-
let supabasePublishResult = void 0;
|
|
15549
|
-
if (input.gitPublishOptions) {
|
|
15550
|
-
gitPublishResult = await publishToGitStep({
|
|
15551
|
-
jobId,
|
|
15552
|
-
localJobDir: workspaceDir,
|
|
15553
|
-
...input.gitPublishOptions
|
|
15554
|
-
});
|
|
15555
|
-
}
|
|
15556
|
-
if (input.supabasePublishOptions) {
|
|
15557
|
-
supabasePublishResult = await publishToSupabaseStep({
|
|
15558
|
-
jobId,
|
|
15559
|
-
courseTitle,
|
|
15560
|
-
topic,
|
|
15561
|
-
language,
|
|
15562
|
-
milestones: milestoneOutcomes.filter((m) => m.status === "FULFILLED" && m.result).map((m) => ({
|
|
15563
|
-
milestoneId: m.milestoneId,
|
|
15564
|
-
milestoneName: m.result.savedResult.milestoneSlug,
|
|
15565
|
-
lessonSlug: m.result.savedResult.milestoneSlug
|
|
15566
|
-
})),
|
|
15567
|
-
...input.supabasePublishOptions
|
|
15568
|
-
});
|
|
15569
|
-
}
|
|
15570
|
-
return {
|
|
15571
|
-
jobId,
|
|
15572
|
-
courseTitle,
|
|
15573
|
-
topic,
|
|
15574
|
-
language,
|
|
15575
|
-
totalMilestones: milestones.length,
|
|
15576
|
-
successfulMilestones,
|
|
15577
|
-
failedMilestones,
|
|
15578
|
-
results: milestoneOutcomes,
|
|
15579
|
-
gitPublishResult,
|
|
15580
|
-
supabasePublishResult,
|
|
15581
|
-
workspaceDir
|
|
15582
|
-
};
|
|
15583
|
-
}
|
|
15584
|
-
|
|
15585
|
-
// src/workflow/workflows/singleArtifactWorkflow.ts
|
|
15586
|
-
async function executeSingleArtifactStep(request) {
|
|
15587
|
-
"use step";
|
|
15588
|
-
const t0 = Date.now();
|
|
15589
|
-
console.log(` \u23F3 [Step: Single Artifact] Generating ${request.artifactType.toUpperCase()} for "${request.topic.slice(0, 35)}"...`);
|
|
15590
|
-
const res = await withRateLimitBackoff({
|
|
15591
|
-
stepName: `generate-single-artifact-${request.artifactType}-${(request.topic || "topic").slice(0, 20)}`,
|
|
15592
|
-
fn: async () => generateSingleArtifact(request)
|
|
15593
|
-
});
|
|
15594
|
-
console.log(` \u2705 [Step: Single Artifact] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (File: "${res.filename}")`);
|
|
15595
|
-
return res;
|
|
15596
|
-
}
|
|
15597
|
-
async function generateSingleArtifactWorkflow(request) {
|
|
15598
|
-
"use workflow";
|
|
15599
|
-
return executeSingleArtifactStep(request);
|
|
15600
|
-
}
|
|
15601
|
-
|
|
15602
|
-
// src/index.ts
|
|
15603
|
-
init_errors();
|
|
15604
|
-
|
|
15605
|
-
// src/evaluators/deterministicStructuralLinter.ts
|
|
15606
|
-
var DeterministicStructuralLinter = class {
|
|
15607
|
-
/**
|
|
15608
|
-
* Validates structural invariants across a complete lesson bundle.
|
|
15609
|
-
*/
|
|
15610
|
-
static lintBundle(input) {
|
|
15611
|
-
const { lesson, quiz, activity, slides, codeLab } = input;
|
|
15612
|
-
const findings = [];
|
|
15613
|
-
const strengths = [];
|
|
15614
|
-
let score = 100;
|
|
15615
|
-
const baseLessonId = lesson.lessonId;
|
|
15616
|
-
const baseLanguage = lesson.language;
|
|
15617
|
-
if (!lesson.title || lesson.title.trim().length === 0) {
|
|
15618
|
-
score -= 20;
|
|
15619
|
-
findings.push({
|
|
15620
|
-
id: "struct_missing_lesson_title",
|
|
15621
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15622
|
-
severity: "CRITICAL",
|
|
15623
|
-
title: "Missing Lesson Title",
|
|
15624
|
-
description: "Lesson plan has an empty or whitespace title.",
|
|
15625
|
-
remediationAdvice: "Provide a non-empty lesson title."
|
|
15626
|
-
});
|
|
15627
|
-
}
|
|
15628
|
-
if (!lesson.learningObjectives || lesson.learningObjectives.length === 0) {
|
|
15629
|
-
score -= 25;
|
|
15630
|
-
findings.push({
|
|
15631
|
-
id: "struct_empty_learning_objectives",
|
|
15632
|
-
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15633
|
-
severity: "CRITICAL",
|
|
15634
|
-
title: "Empty Learning Objectives Array",
|
|
15635
|
-
description: "Lesson plan contains 0 learning objectives.",
|
|
15636
|
-
remediationAdvice: "Declare at least 2 structured Learning Objectives in the schema."
|
|
15637
|
-
});
|
|
15638
|
-
}
|
|
15639
|
-
if (!lesson.sections || lesson.sections.length === 0) {
|
|
15640
|
-
score -= 25;
|
|
15641
|
-
findings.push({
|
|
15642
|
-
id: "struct_empty_lesson_sections",
|
|
15643
|
-
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
15644
|
-
severity: "CRITICAL",
|
|
15645
|
-
title: "Empty Lesson Sections Array",
|
|
15646
|
-
description: "Lesson plan contains no instructional sections.",
|
|
15647
|
-
remediationAdvice: "Provide structured lesson flow sections."
|
|
15648
|
-
});
|
|
15649
|
-
}
|
|
15650
|
-
if (quiz && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
|
|
15651
|
-
score -= 15;
|
|
15652
|
-
findings.push({
|
|
15653
|
-
id: "struct_quiz_id_mismatch",
|
|
15654
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15655
|
-
severity: "MAJOR",
|
|
15656
|
-
title: "Quiz ID Contract Mismatch",
|
|
15657
|
-
description: `Quiz ID "${quiz.quizId}" does not match Lesson ID "${baseLessonId}".`,
|
|
15658
|
-
remediationAdvice: `Sync Quiz quizId to "${baseLessonId}".`,
|
|
15659
|
-
affectedElement: quiz.quizId
|
|
15660
|
-
});
|
|
15661
|
-
}
|
|
15662
|
-
if (activity && activity.lessonId !== baseLessonId) {
|
|
15663
|
-
score -= 15;
|
|
15664
|
-
findings.push({
|
|
15665
|
-
id: "struct_act_id_mismatch",
|
|
15666
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15667
|
-
severity: "MAJOR",
|
|
15668
|
-
title: "Activity Lesson ID Contract Mismatch",
|
|
15669
|
-
description: `Activity lessonId "${activity.lessonId}" does not match Lesson ID "${baseLessonId}".`,
|
|
15670
|
-
remediationAdvice: `Sync Activity lessonId to "${baseLessonId}".`,
|
|
15671
|
-
affectedElement: activity.lessonId
|
|
15672
|
-
});
|
|
15673
|
-
}
|
|
15674
|
-
if (slides && slides.lessonId !== baseLessonId) {
|
|
15675
|
-
score -= 15;
|
|
15676
|
-
findings.push({
|
|
15677
|
-
id: "struct_slides_id_mismatch",
|
|
15678
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15679
|
-
severity: "MAJOR",
|
|
15680
|
-
title: "Slide Deck Lesson ID Contract Mismatch",
|
|
15681
|
-
description: `Slide deck lessonId "${slides.lessonId}" does not match Lesson ID "${baseLessonId}".`,
|
|
15682
|
-
remediationAdvice: `Sync Slide deck lessonId to "${baseLessonId}".`,
|
|
15683
|
-
affectedElement: slides.lessonId
|
|
15684
|
-
});
|
|
15685
|
-
}
|
|
15686
|
-
const satellites = [
|
|
15687
|
-
{ type: "QUIZ", lang: quiz?.language },
|
|
15688
|
-
{ type: "ACT", lang: activity?.language },
|
|
15689
|
-
{ type: "SLIDE", lang: slides?.language }
|
|
15690
|
-
];
|
|
15691
|
-
for (const sat of satellites) {
|
|
15692
|
-
if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
|
|
15693
|
-
score -= 25;
|
|
15694
|
-
findings.push({
|
|
15695
|
-
id: `struct_language_mismatch_${sat.type}`,
|
|
15696
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15697
|
-
severity: "CRITICAL",
|
|
15698
|
-
title: `Language Policy Inconsistency in ${sat.type}`,
|
|
15699
|
-
description: `Master Lesson is in "${baseLanguage}" but ${sat.type} is declared in "${sat.lang}".`,
|
|
15700
|
-
remediationAdvice: `Set ${sat.type} language to "${baseLanguage}".`
|
|
15701
|
-
});
|
|
15702
|
-
}
|
|
15703
|
-
}
|
|
15704
|
-
if (quiz && quiz.questions) {
|
|
15705
|
-
for (let i = 0; i < quiz.questions.length; i++) {
|
|
15706
|
-
const q = quiz.questions[i];
|
|
15707
|
-
const qId = q.id || `Q${i + 1}`;
|
|
15708
|
-
const options = q.options || [];
|
|
15709
|
-
if (options.length < 4) {
|
|
15710
|
-
score -= 10;
|
|
15711
|
-
findings.push({
|
|
15712
|
-
id: `struct_quiz_option_count_${qId}`,
|
|
15713
|
-
dimension: "MISCONCEPTION_RIGOR",
|
|
15714
|
-
severity: "MAJOR",
|
|
15715
|
-
title: `Structural Option Count Error in ${qId}`,
|
|
15716
|
-
description: `Question ${qId} has ${options.length} options (standard schema requires 4).`,
|
|
15717
|
-
remediationAdvice: "Ensure each question has 4 options (A, B, C, D).",
|
|
15718
|
-
affectedElement: qId
|
|
15719
|
-
});
|
|
15720
|
-
}
|
|
15721
|
-
const correctCount = options.filter((o) => o.isCorrect).length;
|
|
15722
|
-
if (correctCount !== 1) {
|
|
15723
|
-
score -= 20;
|
|
15724
|
-
findings.push({
|
|
15725
|
-
id: `struct_quiz_key_count_${qId}`,
|
|
15726
|
-
dimension: "MISCONCEPTION_RIGOR",
|
|
15727
|
-
severity: "CRITICAL",
|
|
15728
|
-
title: `Key Assignment Error in ${qId}`,
|
|
15729
|
-
description: `Question ${qId} has ${correctCount} correct options (must be exactly 1).`,
|
|
15730
|
-
remediationAdvice: "Set `isCorrect: true` on exactly 1 option.",
|
|
15731
|
-
affectedElement: qId
|
|
15732
|
-
});
|
|
15733
|
-
}
|
|
15734
|
-
}
|
|
15735
|
-
}
|
|
15736
|
-
if (activity?.materialsBOM?.hardware && activity.materialsBOM.hardware.length > 0) {
|
|
15737
|
-
const hwText = activity.materialsBOM.hardware.join(" ").toLowerCase();
|
|
15738
|
-
const hasLED = hwText.includes("led");
|
|
15739
|
-
const hasResistor = hwText.includes("\u0111i\u1EC7n tr\u1EDF") || hwText.includes("resistor") || hwText.includes("220") || hwText.includes("330") || hwText.includes("1k");
|
|
15740
|
-
if (hasLED && !hasResistor) {
|
|
15741
|
-
score -= 20;
|
|
15742
|
-
findings.push({
|
|
15743
|
-
id: "struct_hardware_unsafe_led_no_resistor",
|
|
15744
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
15745
|
-
severity: "CRITICAL",
|
|
15746
|
-
title: "Unsafe Circuit BOM: LED without Current-Limiting Resistor",
|
|
15747
|
-
description: "Hardware BOM includes LED without a 220\u03A9-1k\u03A9 resistor, causing circuit overload.",
|
|
15748
|
-
remediationAdvice: "Add a 220\u03A9 current-limiting resistor to the hardware materials list."
|
|
15749
|
-
});
|
|
15750
|
-
}
|
|
15751
|
-
}
|
|
15752
|
-
score = Math.max(0, Math.min(100, score));
|
|
15753
|
-
const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
|
|
15754
|
-
if (passed) {
|
|
15755
|
-
strengths.push("Deterministic structure, ID contracts, and schema invariants strictly verified.");
|
|
15756
|
-
}
|
|
15757
|
-
return {
|
|
15758
|
-
passed,
|
|
15759
|
-
structuralScore: score,
|
|
15760
|
-
findings,
|
|
15761
|
-
strengths
|
|
15762
|
-
};
|
|
15763
|
-
}
|
|
15764
|
-
};
|
|
15765
|
-
|
|
15766
|
-
// src/evaluators/academicAuditor.ts
|
|
15767
|
-
var AcademicAuditor = class {
|
|
15768
|
-
/**
|
|
15769
|
-
* Evaluates a complete lesson bundle.
|
|
15770
|
-
* Step 1 (Deterministic): Fast structural, contract, schema, and safety linting.
|
|
15771
|
-
* Step 2 (Semantic LLM-as-a-Judge): Deep pedagogical, cognitive (Bloom), and misconception analysis with live frontier LLM.
|
|
15772
|
-
*/
|
|
15773
|
-
static async auditLessonBundle(input) {
|
|
15774
|
-
const { lesson, quiz, activity, slides, codeLab, modelOptions, executeLLMJudge = true } = input;
|
|
15775
|
-
const structuralResult = DeterministicStructuralLinter.lintBundle({
|
|
15776
|
-
lesson,
|
|
15777
|
-
quiz,
|
|
15778
|
-
activity,
|
|
15779
|
-
slides,
|
|
15780
|
-
codeLab
|
|
15781
|
-
});
|
|
15782
|
-
const allFindings = [...structuralResult.findings];
|
|
15783
|
-
const strengths = [...structuralResult.strengths];
|
|
15784
|
-
let semanticScore = null;
|
|
15785
|
-
let semanticVerdict = "PASS";
|
|
15786
|
-
if (executeLLMJudge) {
|
|
15787
|
-
try {
|
|
15788
|
-
const judgeReport = await auditCurriculumQualityFlow({
|
|
15789
|
-
targetArtifactType: "LESSON_BUNDLE",
|
|
15790
|
-
lessonId: lesson.lessonId,
|
|
15791
|
-
expectedLanguage: lesson.language,
|
|
15792
|
-
targetObjectives: (lesson.learningObjectives || []).map((lo) => ({
|
|
15793
|
-
code: lo.code,
|
|
15794
|
-
description: lo.description,
|
|
15795
|
-
bloomLevel: lo.bloomLevel
|
|
15796
|
-
})),
|
|
15797
|
-
generatedContentJson: JSON.stringify({ lesson, quiz, activity, slides, codeLab }),
|
|
15798
|
-
modelOptions
|
|
15799
|
-
});
|
|
15800
|
-
semanticScore = judgeReport.totalScore;
|
|
15801
|
-
semanticVerdict = judgeReport.overallVerdict;
|
|
15802
|
-
for (const criterion of judgeReport.criteria) {
|
|
15803
|
-
if (!criterion.passed) {
|
|
15804
|
-
allFindings.push({
|
|
15805
|
-
id: `llm_judge_${criterion.name.toLowerCase().replace(/\s+/g, "_")}`,
|
|
15806
|
-
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15807
|
-
severity: criterion.score < 50 ? "CRITICAL" : "MAJOR",
|
|
15808
|
-
title: `LLM-as-Judge Finding: ${criterion.name}`,
|
|
15809
|
-
description: criterion.feedback,
|
|
15810
|
-
remediationAdvice: judgeReport.actionableRepairPrompts.join("; ") || "Refine prompt context."
|
|
15811
|
-
});
|
|
15812
|
-
} else {
|
|
15813
|
-
strengths.push(`[LLM-Judge] ${criterion.name}: ${criterion.feedback}`);
|
|
15814
|
-
}
|
|
15815
|
-
}
|
|
15816
|
-
} catch (err) {
|
|
15817
|
-
semanticScore = null;
|
|
15818
|
-
semanticVerdict = "FAIL";
|
|
15819
|
-
allFindings.push({
|
|
15820
|
-
id: "llm_judge_connection_error",
|
|
15821
|
-
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15822
|
-
severity: "MINOR",
|
|
15823
|
-
title: "LLM-as-Judge Skipped / Fallback",
|
|
15824
|
-
description: `Semantic inference error: ${err.message || String(err)}`,
|
|
15825
|
-
remediationAdvice: "Check API credentials for LLM-as-Judge."
|
|
15826
|
-
});
|
|
15827
|
-
}
|
|
14855
|
+
const allFindings = [...structuralResult.findings];
|
|
14856
|
+
const strengths = [...structuralResult.strengths];
|
|
14857
|
+
let semanticScore = null;
|
|
14858
|
+
let semanticVerdict = "PASS";
|
|
14859
|
+
if (executeLLMJudge) {
|
|
14860
|
+
try {
|
|
14861
|
+
const judgeReport = await auditCurriculumQualityFlow({
|
|
14862
|
+
targetArtifactType: "LESSON_BUNDLE",
|
|
14863
|
+
lessonId: lesson.lessonId,
|
|
14864
|
+
expectedLanguage: lesson.language,
|
|
14865
|
+
targetObjectives: (lesson.learningObjectives || []).map((lo) => ({
|
|
14866
|
+
code: lo.code,
|
|
14867
|
+
description: lo.description,
|
|
14868
|
+
bloomLevel: lo.bloomLevel
|
|
14869
|
+
})),
|
|
14870
|
+
generatedContentJson: JSON.stringify({ lesson, quiz, activity, slides, codeLab }),
|
|
14871
|
+
modelOptions
|
|
14872
|
+
});
|
|
14873
|
+
semanticScore = judgeReport.totalScore;
|
|
14874
|
+
semanticVerdict = judgeReport.overallVerdict;
|
|
14875
|
+
for (const criterion of judgeReport.criteria) {
|
|
14876
|
+
if (!criterion.passed) {
|
|
14877
|
+
allFindings.push({
|
|
14878
|
+
id: `llm_judge_${criterion.name.toLowerCase().replace(/\s+/g, "_")}`,
|
|
14879
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
14880
|
+
severity: criterion.score < 50 ? "CRITICAL" : "MAJOR",
|
|
14881
|
+
title: `LLM-as-Judge Finding: ${criterion.name}`,
|
|
14882
|
+
description: criterion.feedback,
|
|
14883
|
+
remediationAdvice: judgeReport.actionableRepairPrompts.join("; ") || "Refine prompt context."
|
|
14884
|
+
});
|
|
14885
|
+
} else {
|
|
14886
|
+
strengths.push(`[LLM-Judge] ${criterion.name}: ${criterion.feedback}`);
|
|
14887
|
+
}
|
|
14888
|
+
}
|
|
14889
|
+
} catch (err) {
|
|
14890
|
+
semanticScore = null;
|
|
14891
|
+
semanticVerdict = "FAIL";
|
|
14892
|
+
allFindings.push({
|
|
14893
|
+
id: "llm_judge_connection_error",
|
|
14894
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
14895
|
+
severity: "MINOR",
|
|
14896
|
+
title: "LLM-as-Judge Skipped / Fallback",
|
|
14897
|
+
description: `Semantic inference error: ${err.message || String(err)}`,
|
|
14898
|
+
remediationAdvice: "Check API credentials for LLM-as-Judge."
|
|
14899
|
+
});
|
|
14900
|
+
}
|
|
15828
14901
|
}
|
|
15829
14902
|
const criticalCount = allFindings.filter((f) => f.severity === "CRITICAL").length;
|
|
15830
14903
|
const overallScore = executeLLMJudge && semanticScore !== null ? Math.round(structuralResult.structuralScore * 0.4 + semanticScore * 0.6) : structuralResult.structuralScore;
|
|
@@ -16705,32 +15778,316 @@ var MisconceptionEvaluator = class {
|
|
|
16705
15778
|
missingExplanation = true;
|
|
16706
15779
|
}
|
|
16707
15780
|
}
|
|
16708
|
-
if (missingExplanation) {
|
|
16709
|
-
score -= 10;
|
|
16710
|
-
findings.push({
|
|
16711
|
-
id: `misconception_shallow_explanation_${qId}`,
|
|
16712
|
-
dimension: "MISCONCEPTION_RIGOR",
|
|
16713
|
-
severity: "MAJOR",
|
|
16714
|
-
title: `Shallow Distractor Explanations in ${qId}`,
|
|
16715
|
-
description: `Question ${qId} lacks detailed pedagogical explanations for all options. Students and teachers cannot diagnose root causes without option-level rationale.`,
|
|
16716
|
-
remediationAdvice: "For EVERY option (A, B, C, D), explicitly write why the choice is correct or what mental misconception it represents.",
|
|
16717
|
-
affectedElement: qId
|
|
15781
|
+
if (missingExplanation) {
|
|
15782
|
+
score -= 10;
|
|
15783
|
+
findings.push({
|
|
15784
|
+
id: `misconception_shallow_explanation_${qId}`,
|
|
15785
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
15786
|
+
severity: "MAJOR",
|
|
15787
|
+
title: `Shallow Distractor Explanations in ${qId}`,
|
|
15788
|
+
description: `Question ${qId} lacks detailed pedagogical explanations for all options. Students and teachers cannot diagnose root causes without option-level rationale.`,
|
|
15789
|
+
remediationAdvice: "For EVERY option (A, B, C, D), explicitly write why the choice is correct or what mental misconception it represents.",
|
|
15790
|
+
affectedElement: qId
|
|
15791
|
+
});
|
|
15792
|
+
} else {
|
|
15793
|
+
questionsWithFullExplanations++;
|
|
15794
|
+
}
|
|
15795
|
+
}
|
|
15796
|
+
if (questionsWithFullExplanations === questions.length && questions.length >= 3) {
|
|
15797
|
+
strengths.push("All quiz questions feature rigorous 4-option breakdown with comprehensive misconception analysis.");
|
|
15798
|
+
}
|
|
15799
|
+
score = Math.max(0, Math.min(100, score));
|
|
15800
|
+
return {
|
|
15801
|
+
score,
|
|
15802
|
+
weight: 0.2,
|
|
15803
|
+
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
15804
|
+
strengths,
|
|
15805
|
+
findings
|
|
15806
|
+
};
|
|
15807
|
+
}
|
|
15808
|
+
};
|
|
15809
|
+
|
|
15810
|
+
// src/standards/standardsCoverageGate.ts
|
|
15811
|
+
function resolveStatementRef(ref, packs) {
|
|
15812
|
+
const [head, ...rest] = ref.split(":");
|
|
15813
|
+
const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
|
|
15814
|
+
const statementId = rest.length > 0 ? rest.join(":") : ref;
|
|
15815
|
+
for (const p of candidatePacks) {
|
|
15816
|
+
if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
|
|
15817
|
+
}
|
|
15818
|
+
return null;
|
|
15819
|
+
}
|
|
15820
|
+
function evaluateStandardsCoverage(input) {
|
|
15821
|
+
const rows = [];
|
|
15822
|
+
const aoToLo = /* @__PURE__ */ new Map();
|
|
15823
|
+
for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
|
|
15824
|
+
const loToRefs = /* @__PURE__ */ new Map();
|
|
15825
|
+
for (const lo of input.objectives) {
|
|
15826
|
+
loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
|
|
15827
|
+
}
|
|
15828
|
+
const taughtLOs = /* @__PURE__ */ new Set();
|
|
15829
|
+
for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
|
|
15830
|
+
const assessedLOs = /* @__PURE__ */ new Set();
|
|
15831
|
+
for (const q of input.quizQuestions) {
|
|
15832
|
+
if (q.alignedLO) assessedLOs.add(q.alignedLO);
|
|
15833
|
+
if (q.alignedAO) {
|
|
15834
|
+
const lo = aoToLo.get(q.alignedAO);
|
|
15835
|
+
if (lo) assessedLOs.add(lo);
|
|
15836
|
+
}
|
|
15837
|
+
}
|
|
15838
|
+
for (const pack of input.packs) {
|
|
15839
|
+
const mappingByStatement = /* @__PURE__ */ new Map();
|
|
15840
|
+
for (const m of pack.mappings ?? []) {
|
|
15841
|
+
const prev = mappingByStatement.get(m.statementId);
|
|
15842
|
+
if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
|
|
15843
|
+
mappingByStatement.set(m.statementId, m.kind);
|
|
15844
|
+
}
|
|
15845
|
+
}
|
|
15846
|
+
for (const statement of pack.statements) {
|
|
15847
|
+
const refFull = `${pack.manifest.id}:${statement.id}`;
|
|
15848
|
+
const issues = [];
|
|
15849
|
+
const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
|
|
15850
|
+
const kind = mappingByStatement.get(statement.id);
|
|
15851
|
+
const hasMapping = kind !== void 0;
|
|
15852
|
+
const isComplianceRelevant = kind === "covers";
|
|
15853
|
+
const hasActivity = los.some((lo) => taughtLOs.has(lo));
|
|
15854
|
+
const hasAssessment = los.some((lo) => assessedLOs.has(lo));
|
|
15855
|
+
let status;
|
|
15856
|
+
if (!hasMapping) status = "UNMAPPED";
|
|
15857
|
+
else if (!isComplianceRelevant) status = "PARTIAL";
|
|
15858
|
+
else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
|
|
15859
|
+
else status = "UNCOVERED";
|
|
15860
|
+
if (status === "UNCOVERED") {
|
|
15861
|
+
if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
|
|
15862
|
+
else {
|
|
15863
|
+
if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
|
|
15864
|
+
if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
|
|
15865
|
+
}
|
|
15866
|
+
}
|
|
15867
|
+
rows.push({
|
|
15868
|
+
packId: pack.manifest.id,
|
|
15869
|
+
statementId: statement.id,
|
|
15870
|
+
statementText: Object.values(statement.texts)[0] ?? "",
|
|
15871
|
+
status,
|
|
15872
|
+
objectives: los,
|
|
15873
|
+
hasActivity,
|
|
15874
|
+
hasAssessment,
|
|
15875
|
+
issues
|
|
15876
|
+
});
|
|
15877
|
+
}
|
|
15878
|
+
}
|
|
15879
|
+
for (const lo of input.objectives) {
|
|
15880
|
+
for (const r of lo.standardRefs ?? []) {
|
|
15881
|
+
if (!resolveStatementRef(r, input.packs)) {
|
|
15882
|
+
rows.push({
|
|
15883
|
+
packId: "(unresolved)",
|
|
15884
|
+
statementId: r,
|
|
15885
|
+
statementText: "",
|
|
15886
|
+
status: "UNCOVERED",
|
|
15887
|
+
objectives: [lo.code],
|
|
15888
|
+
hasActivity: false,
|
|
15889
|
+
hasAssessment: false,
|
|
15890
|
+
issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
|
|
16718
15891
|
});
|
|
16719
|
-
} else {
|
|
16720
|
-
questionsWithFullExplanations++;
|
|
16721
15892
|
}
|
|
16722
15893
|
}
|
|
16723
|
-
|
|
16724
|
-
|
|
15894
|
+
}
|
|
15895
|
+
const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
|
|
15896
|
+
const covered = complianceRows.filter((r) => r.status === "COVERED").length;
|
|
15897
|
+
const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
|
|
15898
|
+
const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
|
|
15899
|
+
const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
|
|
15900
|
+
const lines = [
|
|
15901
|
+
"# Standards Coverage Report",
|
|
15902
|
+
"",
|
|
15903
|
+
`- Verdict: **${verdict}**`,
|
|
15904
|
+
`- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
|
|
15905
|
+
`- Unresolved standardRefs: ${unresolvedCount}`,
|
|
15906
|
+
"",
|
|
15907
|
+
"| Pack | Statement | Status | LOs | Activity | Assessment |",
|
|
15908
|
+
"|---|---|---|---|---|---|",
|
|
15909
|
+
...rows.map(
|
|
15910
|
+
(r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
|
|
15911
|
+
)
|
|
15912
|
+
];
|
|
15913
|
+
const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
|
|
15914
|
+
if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
|
|
15915
|
+
return {
|
|
15916
|
+
verdict,
|
|
15917
|
+
coveragePct,
|
|
15918
|
+
rows,
|
|
15919
|
+
summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
|
|
15920
|
+
rawMarkdownReport: lines.join("\n")
|
|
15921
|
+
};
|
|
15922
|
+
}
|
|
15923
|
+
var StandardsRegistryAdapter = class {
|
|
15924
|
+
client;
|
|
15925
|
+
constructor(config = {}) {
|
|
15926
|
+
if (config.client) {
|
|
15927
|
+
this.client = config.client;
|
|
15928
|
+
return;
|
|
16725
15929
|
}
|
|
16726
|
-
|
|
16727
|
-
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
15930
|
+
const url = config.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || "";
|
|
15931
|
+
const key = config.supabaseServiceRoleKey || process.env.SUPABASE_SERVICE_ROLE_KEY || config.supabaseAnonKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
|
|
15932
|
+
if (!url || !key) {
|
|
15933
|
+
throw new Error("StandardsRegistryAdapter: Supabase URL/key missing (pass client or env).");
|
|
15934
|
+
}
|
|
15935
|
+
this.client = supabaseJs.createClient(url, key);
|
|
15936
|
+
}
|
|
15937
|
+
// ─── Intake (write path — service role) ───────────────────────────────────
|
|
15938
|
+
/** Persist a schema-validated pack as a new framework (status=draft). */
|
|
15939
|
+
async importPack(pack, opts) {
|
|
15940
|
+
const parsed = FrameworkPackSchema.safeParse(pack);
|
|
15941
|
+
if (!parsed.success) {
|
|
15942
|
+
throw new Error("importPack: pack failed schema validation: " + parsed.error.issues.map((i) => i.path.join(".") + ": " + i.message).join("; "));
|
|
15943
|
+
}
|
|
15944
|
+
const p = parsed.data;
|
|
15945
|
+
const { data: fw, error: fwErr } = await this.client.from("standards_frameworks").insert({
|
|
15946
|
+
pack_id: p.manifest.id,
|
|
15947
|
+
content_version: p.manifest.contentVersion,
|
|
15948
|
+
name: p.manifest.name,
|
|
15949
|
+
spec_version: p.manifest.specVersion,
|
|
15950
|
+
subject: p.manifest.subject,
|
|
15951
|
+
languages: p.manifest.languages,
|
|
15952
|
+
grade_model: p.manifest.gradeModel,
|
|
15953
|
+
provenance: p.manifest.provenance,
|
|
15954
|
+
trust: p.manifest.trust,
|
|
15955
|
+
status: "draft",
|
|
15956
|
+
organization_code: opts?.organizationCode ?? (p.manifest.trust === "verified" ? null : p.manifest.provenance.importedBy?.replace(/^org:/, "") || null),
|
|
15957
|
+
original_file_path: opts?.originalFilePath ?? null,
|
|
15958
|
+
created_by: opts?.createdBy ?? null
|
|
15959
|
+
}).select("id").single();
|
|
15960
|
+
if (fwErr) throw new Error("importPack: framework insert failed: " + fwErr.message);
|
|
15961
|
+
const frameworkId = fw.id;
|
|
15962
|
+
const statementRows = p.statements.map((s) => ({
|
|
15963
|
+
framework_id: frameworkId,
|
|
15964
|
+
statement_id: s.id,
|
|
15965
|
+
parent_statement_id: s.parentId ?? null,
|
|
15966
|
+
grade_min: s.gradeBand[0],
|
|
15967
|
+
grade_max: s.gradeBand[1],
|
|
15968
|
+
texts: s.texts,
|
|
15969
|
+
classifications: s.classifications ?? [],
|
|
15970
|
+
bloom_hint: s.bloomHint ?? null,
|
|
15971
|
+
keywords: s.keywords ?? [],
|
|
15972
|
+
source_ref: s.sourceRef ?? null,
|
|
15973
|
+
provenance: s.provenance
|
|
15974
|
+
}));
|
|
15975
|
+
const { error: stErr, count: stCount } = await this.client.from("standards_statements").insert(statementRows, { count: "exact" });
|
|
15976
|
+
if (stErr) throw new Error("importPack: statement insert failed: " + stErr.message);
|
|
15977
|
+
let mappingCount = 0;
|
|
15978
|
+
if (p.mappings && p.mappings.length > 0) {
|
|
15979
|
+
const mappingRows = p.mappings.map((m) => ({
|
|
15980
|
+
framework_id: frameworkId,
|
|
15981
|
+
statement_id: m.statementId,
|
|
15982
|
+
target_ref: m.targetRef,
|
|
15983
|
+
kind: m.kind,
|
|
15984
|
+
confidence: m.confidence,
|
|
15985
|
+
provenance: m.provenance
|
|
15986
|
+
}));
|
|
15987
|
+
const { error: mpErr, count: mpCount } = await this.client.from("standards_mappings").insert(mappingRows, { count: "exact" });
|
|
15988
|
+
if (mpErr) throw new Error("importPack: mapping insert failed: " + mpErr.message);
|
|
15989
|
+
mappingCount = mpCount ?? mappingRows.length;
|
|
15990
|
+
}
|
|
15991
|
+
return { frameworkDbId: frameworkId, statementsInserted: stCount ?? statementRows.length, mappingsInserted: mappingCount };
|
|
15992
|
+
}
|
|
15993
|
+
/** Activate a draft framework (immutable version is now live). */
|
|
15994
|
+
async activatePack(frameworkDbId) {
|
|
15995
|
+
const { error } = await this.client.from("standards_frameworks").update({ status: "active" }).eq("id", frameworkDbId).eq("status", "draft");
|
|
15996
|
+
if (error) throw new Error("activatePack failed: " + error.message);
|
|
15997
|
+
}
|
|
15998
|
+
async deprecatePack(frameworkDbId) {
|
|
15999
|
+
const { error } = await this.client.from("standards_frameworks").update({ status: "deprecated" }).eq("id", frameworkDbId);
|
|
16000
|
+
if (error) throw new Error("deprecatePack failed: " + error.message);
|
|
16001
|
+
}
|
|
16002
|
+
async adoptPack(frameworkDbId, organizationCode, adoptedBy) {
|
|
16003
|
+
const { error } = await this.client.from("standards_org_adoptions").upsert(
|
|
16004
|
+
{ framework_id: frameworkDbId, organization_code: organizationCode, adopted_by: adoptedBy ?? null },
|
|
16005
|
+
{ onConflict: "organization_code,framework_id" }
|
|
16006
|
+
);
|
|
16007
|
+
if (error) throw new Error("adoptPack failed: " + error.message);
|
|
16008
|
+
}
|
|
16009
|
+
// ─── Runtime (read path — generation) ─────────────────────────────────────
|
|
16010
|
+
/**
|
|
16011
|
+
* Load all packs usable by an org: platform packs (verified, org IS NULL) +
|
|
16012
|
+
* org packs + org adoptions. Hydrated into the same FrameworkPack shape the
|
|
16013
|
+
* in-memory Phase-1 components consume (injector / coverage gate / judge).
|
|
16014
|
+
*/
|
|
16015
|
+
async loadPacksForOrg(organizationCode) {
|
|
16016
|
+
let query = this.client.from("standards_frameworks").select("*, standards_org_adoptions(organization_code)").eq("status", "active");
|
|
16017
|
+
const { data: frameworks, error } = await query;
|
|
16018
|
+
if (error) throw new Error("loadPacksForOrg: " + error.message);
|
|
16019
|
+
const visible = (frameworks ?? []).filter((f) => {
|
|
16020
|
+
const own = f.organization_code && organizationCode && f.organization_code === organizationCode;
|
|
16021
|
+
const global = !f.organization_code;
|
|
16022
|
+
const adopted = organizationCode && (f.standards_org_adoptions ?? []).some((a) => a.organization_code === organizationCode);
|
|
16023
|
+
return global || own || adopted;
|
|
16024
|
+
});
|
|
16025
|
+
if (visible.length === 0) return [];
|
|
16026
|
+
const ids = visible.map((f) => f.id);
|
|
16027
|
+
const [{ data: statements, error: stErr }, { data: mappings, error: mpErr }, { data: overrides, error: ovErr }] = await Promise.all([
|
|
16028
|
+
this.client.from("standards_statements").select("*").in("framework_id", ids),
|
|
16029
|
+
this.client.from("standards_mappings").select("*").in("framework_id", ids),
|
|
16030
|
+
this.client.from("standards_mapping_overrides").select("*").in("framework_id", ids)
|
|
16031
|
+
]);
|
|
16032
|
+
if (stErr) throw new Error("loadPacksForOrg statements: " + stErr.message);
|
|
16033
|
+
const overridesByFw = /* @__PURE__ */ new Map();
|
|
16034
|
+
for (const o of overrides ?? []) {
|
|
16035
|
+
const list = overridesByFw.get(o.framework_id) ?? [];
|
|
16036
|
+
list.push(o);
|
|
16037
|
+
overridesByFw.set(o.framework_id, list);
|
|
16038
|
+
}
|
|
16039
|
+
return visible.map((f) => {
|
|
16040
|
+
const stmts = (statements ?? []).filter((s) => s.framework_id === f.id).map((s) => ({
|
|
16041
|
+
id: s.statement_id,
|
|
16042
|
+
parentId: s.parent_statement_id ?? void 0,
|
|
16043
|
+
gradeBand: [s.grade_min, s.grade_max],
|
|
16044
|
+
texts: s.texts,
|
|
16045
|
+
classifications: s.classifications ?? [],
|
|
16046
|
+
bloomHint: s.bloom_hint ?? void 0,
|
|
16047
|
+
keywords: s.keywords ?? [],
|
|
16048
|
+
sourceRef: s.source_ref ?? void 0,
|
|
16049
|
+
provenance: s.provenance ?? { method: "imported" }
|
|
16050
|
+
}));
|
|
16051
|
+
const ov = overridesByFw.get(f.id) ?? [];
|
|
16052
|
+
const retired = new Set(ov.filter((o) => o.kind === "retired").map((o) => o.statement_id + "|" + o.target_ref));
|
|
16053
|
+
const embedded = (mappings ?? []).filter((m) => m.framework_id === f.id).map((m) => ({
|
|
16054
|
+
statementId: m.statement_id,
|
|
16055
|
+
targetRef: m.target_ref,
|
|
16056
|
+
kind: m.kind,
|
|
16057
|
+
confidence: Number(m.confidence),
|
|
16058
|
+
provenance: m.provenance ?? { method: "imported" }
|
|
16059
|
+
}));
|
|
16060
|
+
const overlay = ov.filter((o) => o.kind !== "retired").map((o) => ({
|
|
16061
|
+
statementId: o.statement_id,
|
|
16062
|
+
targetRef: o.target_ref,
|
|
16063
|
+
kind: o.kind,
|
|
16064
|
+
confidence: Number(o.confidence),
|
|
16065
|
+
provenance: { method: "human", reviewedBy: o.created_by ?? "overlay", note: o.note ?? void 0 }
|
|
16066
|
+
}));
|
|
16067
|
+
const bridge = /* @__PURE__ */ new Map();
|
|
16068
|
+
for (const m of embedded) bridge.set(m.statementId + "|" + m.targetRef, m);
|
|
16069
|
+
for (const m of overlay) bridge.set(m.statementId + "|" + m.targetRef, m);
|
|
16070
|
+
const finalMappings = [...bridge.values()].filter((m) => !retired.has(m.statementId + "|" + m.targetRef));
|
|
16071
|
+
const hydrated = FrameworkPackSchema.safeParse({
|
|
16072
|
+
manifest: {
|
|
16073
|
+
id: f.pack_id,
|
|
16074
|
+
name: f.name,
|
|
16075
|
+
specVersion: f.spec_version || "1.0",
|
|
16076
|
+
contentVersion: f.content_version,
|
|
16077
|
+
subject: f.subject,
|
|
16078
|
+
languages: f.languages,
|
|
16079
|
+
gradeModel: f.grade_model,
|
|
16080
|
+
provenance: f.provenance,
|
|
16081
|
+
trust: f.trust
|
|
16082
|
+
},
|
|
16083
|
+
statements: stmts,
|
|
16084
|
+
mappings: finalMappings
|
|
16085
|
+
});
|
|
16086
|
+
if (!hydrated.success) {
|
|
16087
|
+
throw new Error('loadPacksForOrg: hydrated pack "' + f.pack_id + '" failed schema: ' + hydrated.error.issues[0]?.message);
|
|
16088
|
+
}
|
|
16089
|
+
return { ...hydrated.data, dbId: f.id, status: f.status, organizationCode: f.organization_code ?? null };
|
|
16090
|
+
});
|
|
16734
16091
|
}
|
|
16735
16092
|
};
|
|
16736
16093
|
|
|
@@ -17408,8 +16765,6 @@ exports.WorksheetSchema = WorksheetSchema;
|
|
|
17408
16765
|
exports.activityTools = activityTools;
|
|
17409
16766
|
exports.analystTools = analystTools;
|
|
17410
16767
|
exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
|
|
17411
|
-
exports.approvalHookToken = approvalHookToken;
|
|
17412
|
-
exports.approvalPayloadSchema = approvalPayloadSchema;
|
|
17413
16768
|
exports.assertAcyclic = assertAcyclic;
|
|
17414
16769
|
exports.assessorTools = assessorTools;
|
|
17415
16770
|
exports.auditCurriculumQualityFlow = auditCurriculumQualityFlow;
|
|
@@ -17450,7 +16805,6 @@ exports.ensureExpositionForLesson = ensureExpositionForLesson;
|
|
|
17450
16805
|
exports.ensureKnowledgeExposition = ensureKnowledgeExposition;
|
|
17451
16806
|
exports.evaluateStandardsCoverage = evaluateStandardsCoverage;
|
|
17452
16807
|
exports.executeCurriculumCommand = executeCurriculumCommand;
|
|
17453
|
-
exports.executeSingleArtifactStep = executeSingleArtifactStep;
|
|
17454
16808
|
exports.exportAllProjectQuizzes = exportAllProjectQuizzes;
|
|
17455
16809
|
exports.expositionCacheKey = expositionCacheKey;
|
|
17456
16810
|
exports.extractScopeSequenceRows = extractScopeSequenceRows;
|
|
@@ -17461,37 +16815,24 @@ exports.formatQuizzesToCsv = formatQuizzesToCsv;
|
|
|
17461
16815
|
exports.fulfillMediaLedger = fulfillMediaLedger;
|
|
17462
16816
|
exports.gateModeFor = gateModeFor;
|
|
17463
16817
|
exports.generateActivityFlow = generateActivityFlow;
|
|
17464
|
-
exports.generateActivityStep = generateActivityStep;
|
|
17465
16818
|
exports.generateCodeLabFlow = generateCodeLabFlow;
|
|
17466
|
-
exports.generateCodeLabStep = generateCodeLabStep;
|
|
17467
16819
|
exports.generateDiagnosticQuizFlow = generateDiagnosticQuizFlow;
|
|
17468
|
-
exports.generateDiagnosticQuizStep = generateDiagnosticQuizStep;
|
|
17469
16820
|
exports.generateEducationalImage = generateEducationalImage;
|
|
17470
16821
|
exports.generateExtensionFlow = generateExtensionFlow;
|
|
17471
|
-
exports.generateExtensionStep = generateExtensionStep;
|
|
17472
16822
|
exports.generateHandoutFlow = generateHandoutFlow;
|
|
17473
|
-
exports.generateHandoutStep = generateHandoutStep;
|
|
17474
16823
|
exports.generateLessonMasterFlow = generateLessonMasterFlow;
|
|
17475
|
-
exports.generateMasterLessonStep = generateMasterLessonStep;
|
|
17476
16824
|
exports.generateMilestoneCurriculumBundle = generateMilestoneCurriculumBundle;
|
|
17477
|
-
exports.generateMilestoneWorkflow = generateMilestoneWorkflow;
|
|
17478
16825
|
exports.generatePhase1SotArtifacts = generatePhase1SotArtifacts;
|
|
17479
16826
|
exports.generatePhase2SotArtifacts = generatePhase2SotArtifacts;
|
|
17480
16827
|
exports.generateProjectInstruction = generateProjectInstruction;
|
|
17481
16828
|
exports.generateProjectInstructionFlow = generateProjectInstructionFlow;
|
|
17482
16829
|
exports.generateRoadmapCurriculum = generateRoadmapCurriculum;
|
|
17483
|
-
exports.generateRoadmapWorkflow = generateRoadmapWorkflow;
|
|
17484
16830
|
exports.generateSelfLabFlow = generateSelfLabFlow;
|
|
17485
|
-
exports.generateSelfLabStep = generateSelfLabStep;
|
|
17486
16831
|
exports.generateSelfPacedBundle = generateSelfPacedBundle;
|
|
17487
16832
|
exports.generateSingleArtifact = generateSingleArtifact;
|
|
17488
|
-
exports.generateSingleArtifactWorkflow = generateSingleArtifactWorkflow;
|
|
17489
16833
|
exports.generateSlidesFlow = generateSlidesFlow;
|
|
17490
|
-
exports.generateSlidesStep = generateSlidesStep;
|
|
17491
16834
|
exports.generateTeacherGuideFlow = generateTeacherGuideFlow;
|
|
17492
|
-
exports.generateTeacherGuideStep = generateTeacherGuideStep;
|
|
17493
16835
|
exports.generateWorksheetFlow = generateWorksheetFlow;
|
|
17494
|
-
exports.generateWorksheetStep = generateWorksheetStep;
|
|
17495
16836
|
exports.getAIModel = getAIModel;
|
|
17496
16837
|
exports.getArtifactMetadata = getArtifactMetadata;
|
|
17497
16838
|
exports.getDesignatedFallbackChain = getDesignatedFallbackChain;
|
|
@@ -17512,9 +16853,6 @@ exports.isExpositionFresh = isExpositionFresh;
|
|
|
17512
16853
|
exports.isModelAllowed = isModelAllowed;
|
|
17513
16854
|
exports.isProviderEnabled = isProviderEnabled;
|
|
17514
16855
|
exports.isTranslationDue = isTranslationDue;
|
|
17515
|
-
exports.judgeMasterLessonStep = judgeMasterLessonStep;
|
|
17516
|
-
exports.judgeSatelliteStep = judgeSatelliteStep;
|
|
17517
|
-
exports.lessonApprovalHook = lessonApprovalHook;
|
|
17518
16856
|
exports.lintAndSanitizeArtifact = lintAndSanitizeArtifact;
|
|
17519
16857
|
exports.lintCurriculumFramework = lintCurriculumFramework;
|
|
17520
16858
|
exports.lintFrameworkPack = lintFrameworkPack;
|
|
@@ -17528,13 +16866,10 @@ exports.parseRoadmapJsonToProjectPayload = parseRoadmapJsonToProjectPayload;
|
|
|
17528
16866
|
exports.produceBatchLessons = produceBatchLessons;
|
|
17529
16867
|
exports.produceSingleLesson = produceSingleLesson;
|
|
17530
16868
|
exports.publishToGitHub = publishToGitHub;
|
|
17531
|
-
exports.publishToGitStep = publishToGitStep;
|
|
17532
16869
|
exports.publishToSupabase = publishToSupabase;
|
|
17533
|
-
exports.publishToSupabaseStep = publishToSupabaseStep;
|
|
17534
16870
|
exports.rankGenCandidates = rankGenCandidates;
|
|
17535
16871
|
exports.renderFrameworkFromPlan = renderFrameworkFromPlan;
|
|
17536
16872
|
exports.renderMediaPlaceholder = renderMediaPlaceholder;
|
|
17537
|
-
exports.repairMasterLessonStep = repairMasterLessonStep;
|
|
17538
16873
|
exports.researcherTools = researcherTools;
|
|
17539
16874
|
exports.resolveGateSettings = resolveGateSettings;
|
|
17540
16875
|
exports.resolveStandardsPacks = resolveStandardsPacks;
|
|
@@ -17543,7 +16878,6 @@ exports.resolveTranslationTargets = resolveTranslationTargets;
|
|
|
17543
16878
|
exports.reviewerTools = reviewerTools;
|
|
17544
16879
|
exports.runCurriculumAIInference = runCurriculumAIInference;
|
|
17545
16880
|
exports.safeParseJson = safeParseJson;
|
|
17546
|
-
exports.saveMilestoneToWorkspaceStep = saveMilestoneToWorkspaceStep;
|
|
17547
16881
|
exports.searchEducationalImages = searchEducationalImages;
|
|
17548
16882
|
exports.searchEducationalVideos = searchEducationalVideos;
|
|
17549
16883
|
exports.selectStatementsForLesson = selectStatementsForLesson;
|
|
@@ -17570,6 +16904,5 @@ exports.validateFrameworkPack = validateFrameworkPack;
|
|
|
17570
16904
|
exports.validateMarkdownTables = validateMarkdownTables;
|
|
17571
16905
|
exports.validateMermaidSyntax = validateMermaidSyntax;
|
|
17572
16906
|
exports.withAutoRepair = withAutoRepair;
|
|
17573
|
-
exports.withRateLimitBackoff = withRateLimitBackoff;
|
|
17574
16907
|
//# sourceMappingURL=index.cjs.map
|
|
17575
16908
|
//# sourceMappingURL=index.cjs.map
|