@thanh01.pmt/curriculum-kit 1.0.12 → 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 +1336 -1921
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -7
- package/dist/index.d.ts +63 -7
- package/dist/index.mjs +1331 -1898
- 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.mjs
CHANGED
|
@@ -12,7 +12,6 @@ import { createClient } from '@supabase/supabase-js';
|
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
13
|
import { jsonrepair } from 'jsonrepair';
|
|
14
14
|
import { Octokit } from '@octokit/rest';
|
|
15
|
-
import { defineHook, getStepMetadata, RetryableError, sleep } from 'workflow';
|
|
16
15
|
|
|
17
16
|
var __defProp = Object.defineProperty;
|
|
18
17
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -13514,6 +13513,78 @@ var DeterministicPipelineRunner = class {
|
|
|
13514
13513
|
|
|
13515
13514
|
// src/services/prefillService.ts
|
|
13516
13515
|
init_streamRunner();
|
|
13516
|
+
var DEFAULT_STREAM_IDLE_MS = 45e3;
|
|
13517
|
+
var DEFAULT_STREAM_TOTAL_MS = 3e5;
|
|
13518
|
+
var LAYER_TOTAL_BUDGET_MS = {
|
|
13519
|
+
1: 18e4,
|
|
13520
|
+
2: 36e4,
|
|
13521
|
+
3: 36e4
|
|
13522
|
+
};
|
|
13523
|
+
function resolveStreamBudget(layer, opts) {
|
|
13524
|
+
const envIdle = Number(process.env.WIZARD_PREFILL_IDLE_TIMEOUT_MS);
|
|
13525
|
+
const envTotal = Number(process.env.WIZARD_PREFILL_TOTAL_TIMEOUT_MS);
|
|
13526
|
+
const layerTotal = layer !== void 0 ? LAYER_TOTAL_BUDGET_MS[layer] : void 0;
|
|
13527
|
+
return {
|
|
13528
|
+
idleMs: opts?.idleMs ?? (Number.isFinite(envIdle) && envIdle > 0 ? envIdle : DEFAULT_STREAM_IDLE_MS),
|
|
13529
|
+
totalMs: opts?.totalMs ?? (Number.isFinite(envTotal) && envTotal > 0 ? envTotal : layerTotal ?? DEFAULT_STREAM_TOTAL_MS)
|
|
13530
|
+
};
|
|
13531
|
+
}
|
|
13532
|
+
function extractStreamChunk(part) {
|
|
13533
|
+
if (!part || typeof part !== "object") return {};
|
|
13534
|
+
if (part.type === "reasoning-delta" || part.type === "reasoning") {
|
|
13535
|
+
const thought = part.text ?? part.delta ?? part.reasoning ?? "";
|
|
13536
|
+
return thought ? { thought } : {};
|
|
13537
|
+
}
|
|
13538
|
+
if (part.type === "text-delta") {
|
|
13539
|
+
const content = part.text ?? part.delta ?? "";
|
|
13540
|
+
return content ? { content } : {};
|
|
13541
|
+
}
|
|
13542
|
+
if (part.type === "raw") {
|
|
13543
|
+
const raw = part.rawValue;
|
|
13544
|
+
const delta = raw?.choices?.[0]?.delta;
|
|
13545
|
+
const reasoning = delta?.reasoning_content ?? delta?.reasoning ?? raw?.delta?.reasoning_content ?? raw?.delta?.reasoning;
|
|
13546
|
+
if (typeof reasoning === "string" && reasoning) return { thought: reasoning };
|
|
13547
|
+
const text = delta?.content ?? raw?.delta?.content;
|
|
13548
|
+
if (typeof text === "string" && text) return { content: text };
|
|
13549
|
+
return {};
|
|
13550
|
+
}
|
|
13551
|
+
return {};
|
|
13552
|
+
}
|
|
13553
|
+
function createStreamAbortSignal(budget) {
|
|
13554
|
+
const controller = new AbortController();
|
|
13555
|
+
let idleTimer = null;
|
|
13556
|
+
let totalTimer = null;
|
|
13557
|
+
const armIdle = () => {
|
|
13558
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
13559
|
+
if (budget.idleMs > 0) {
|
|
13560
|
+
idleTimer = setTimeout(
|
|
13561
|
+
() => controller.abort(new Error(`Idle timeout: no data for ${Math.round(budget.idleMs / 1e3)}s`)),
|
|
13562
|
+
budget.idleMs
|
|
13563
|
+
);
|
|
13564
|
+
idleTimer?.unref?.();
|
|
13565
|
+
}
|
|
13566
|
+
};
|
|
13567
|
+
armIdle();
|
|
13568
|
+
if (budget.totalMs > 0) {
|
|
13569
|
+
totalTimer = setTimeout(
|
|
13570
|
+
() => controller.abort(new Error(`Total stream timeout after ${Math.round(budget.totalMs / 1e3)}s`)),
|
|
13571
|
+
budget.totalMs
|
|
13572
|
+
);
|
|
13573
|
+
totalTimer?.unref?.();
|
|
13574
|
+
}
|
|
13575
|
+
return {
|
|
13576
|
+
signal: controller.signal,
|
|
13577
|
+
/** Reset the idle window — call on EVERY received stream part. */
|
|
13578
|
+
kick: armIdle,
|
|
13579
|
+
/** Clear both timers once the stream lifecycle is over. */
|
|
13580
|
+
dispose: () => {
|
|
13581
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
13582
|
+
if (totalTimer) clearTimeout(totalTimer);
|
|
13583
|
+
idleTimer = null;
|
|
13584
|
+
totalTimer = null;
|
|
13585
|
+
}
|
|
13586
|
+
};
|
|
13587
|
+
}
|
|
13517
13588
|
function safeParseJson(rawText) {
|
|
13518
13589
|
if (!rawText) return null;
|
|
13519
13590
|
const cleaned = rawText.replace(/```json\s*/gi, "").replace(/```\s*/g, "").trim();
|
|
@@ -13538,7 +13609,7 @@ function safeParseJson(rawText) {
|
|
|
13538
13609
|
return null;
|
|
13539
13610
|
}
|
|
13540
13611
|
}
|
|
13541
|
-
async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider) {
|
|
13612
|
+
async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk, preferredModel, preferredProvider, budget) {
|
|
13542
13613
|
const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
|
|
13543
13614
|
const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
|
|
13544
13615
|
const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
|
|
@@ -13616,8 +13687,13 @@ async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onC
|
|
|
13616
13687
|
const systemInstructions = `${systemPrompt}
|
|
13617
13688
|
|
|
13618
13689
|
THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on the core pedagogical trade-offs in 4-6 concise bullet points (under 120 words). Then output the JSON immediately.`;
|
|
13690
|
+
const resolvedBudget = {
|
|
13691
|
+
idleMs: budget?.idleMs ?? DEFAULT_STREAM_IDLE_MS,
|
|
13692
|
+
totalMs: budget?.totalMs ?? DEFAULT_STREAM_TOTAL_MS
|
|
13693
|
+
};
|
|
13619
13694
|
for (const candidate of candidates) {
|
|
13620
13695
|
const t0 = Date.now();
|
|
13696
|
+
const abort = createStreamAbortSignal(resolvedBudget);
|
|
13621
13697
|
try {
|
|
13622
13698
|
const modelInstance = getAIModel({
|
|
13623
13699
|
provider: candidate.provider,
|
|
@@ -13630,26 +13706,16 @@ THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on
|
|
|
13630
13706
|
prompt: userPrompt,
|
|
13631
13707
|
temperature: 0.2,
|
|
13632
13708
|
includeRawChunks: true,
|
|
13633
|
-
abortSignal:
|
|
13709
|
+
abortSignal: abort.signal
|
|
13634
13710
|
});
|
|
13635
13711
|
let fullContent = "";
|
|
13636
13712
|
for await (const part of streamResult.fullStream) {
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
const reasoning = delta?.reasoning_content || delta?.reasoning;
|
|
13644
|
-
if (reasoning) {
|
|
13645
|
-
onChunk?.(reasoning, "thought");
|
|
13646
|
-
}
|
|
13647
|
-
} else if (part.type === "text-delta") {
|
|
13648
|
-
const textDelta = part.text ?? part.delta ?? "";
|
|
13649
|
-
if (textDelta) {
|
|
13650
|
-
fullContent += textDelta;
|
|
13651
|
-
onChunk?.(textDelta, "content");
|
|
13652
|
-
}
|
|
13713
|
+
abort.kick();
|
|
13714
|
+
const extracted = extractStreamChunk(part);
|
|
13715
|
+
if (extracted.thought) onChunk?.(extracted.thought, "thought");
|
|
13716
|
+
if (extracted.content) {
|
|
13717
|
+
fullContent += extracted.content;
|
|
13718
|
+
onChunk?.(extracted.content, "content");
|
|
13653
13719
|
}
|
|
13654
13720
|
}
|
|
13655
13721
|
if (fullContent.trim()) {
|
|
@@ -13658,6 +13724,8 @@ THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on
|
|
|
13658
13724
|
}
|
|
13659
13725
|
} catch (e) {
|
|
13660
13726
|
console.warn(`[curriculum-kit:VercelAI] Candidate ${candidate.provider} (${candidate.model}) failed in ${Date.now() - t0}ms:`, e?.message || e);
|
|
13727
|
+
} finally {
|
|
13728
|
+
abort.dispose();
|
|
13661
13729
|
}
|
|
13662
13730
|
}
|
|
13663
13731
|
return null;
|
|
@@ -13698,7 +13766,11 @@ Return concise JSON matching:
|
|
|
13698
13766
|
(chunk, type) => {
|
|
13699
13767
|
if (type === "content") rawContent += chunk;
|
|
13700
13768
|
onChunk?.(chunk, type);
|
|
13701
|
-
}
|
|
13769
|
+
},
|
|
13770
|
+
options.model,
|
|
13771
|
+
options.provider,
|
|
13772
|
+
// Research may run live web grounding — generous budget, still idle-guarded.
|
|
13773
|
+
resolveStreamBudget(void 0, { idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs })
|
|
13702
13774
|
);
|
|
13703
13775
|
let parsedResearch = {
|
|
13704
13776
|
groundTruthSummary: `Verified ecosystem standards for ${projectName}`,
|
|
@@ -13900,7 +13972,10 @@ REQUIREMENT: Synthesize ALL parameters above \u2014 every single line in FULL AC
|
|
|
13900
13972
|
onChunk?.(chunk, type);
|
|
13901
13973
|
},
|
|
13902
13974
|
options.model,
|
|
13903
|
-
options.provider
|
|
13975
|
+
options.provider,
|
|
13976
|
+
// RC-W1: layer-aware budget — Layer 2 has the largest output and free-tier
|
|
13977
|
+
// models may think/stream for minutes. Idle window still catches hangs.
|
|
13978
|
+
resolveStreamBudget(targetLayer, { idleMs: options.idleTimeoutMs, totalMs: options.timeoutMs })
|
|
13904
13979
|
);
|
|
13905
13980
|
const parsed = safeParseJson(rawContent);
|
|
13906
13981
|
return {
|
|
@@ -14585,1014 +14660,640 @@ template_contract: "artifact-template-v1"
|
|
|
14585
14660
|
timestamp: dateStr
|
|
14586
14661
|
};
|
|
14587
14662
|
}
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14663
|
+
|
|
14664
|
+
// src/index.ts
|
|
14665
|
+
init_errors();
|
|
14666
|
+
|
|
14667
|
+
// src/evaluators/deterministicStructuralLinter.ts
|
|
14668
|
+
var DeterministicStructuralLinter = class {
|
|
14669
|
+
/**
|
|
14670
|
+
* Validates structural invariants across a complete lesson bundle.
|
|
14671
|
+
*/
|
|
14672
|
+
static lintBundle(input) {
|
|
14673
|
+
const { lesson, quiz, activity, slides, codeLab } = input;
|
|
14674
|
+
const findings = [];
|
|
14675
|
+
const strengths = [];
|
|
14676
|
+
let score = 100;
|
|
14677
|
+
const baseLessonId = lesson.lessonId;
|
|
14678
|
+
const baseLanguage = lesson.language;
|
|
14679
|
+
if (!lesson.title || lesson.title.trim().length === 0) {
|
|
14680
|
+
score -= 20;
|
|
14681
|
+
findings.push({
|
|
14682
|
+
id: "struct_missing_lesson_title",
|
|
14683
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14684
|
+
severity: "CRITICAL",
|
|
14685
|
+
title: "Missing Lesson Title",
|
|
14686
|
+
description: "Lesson plan has an empty or whitespace title.",
|
|
14687
|
+
remediationAdvice: "Provide a non-empty lesson title."
|
|
14688
|
+
});
|
|
14594
14689
|
}
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14601
|
-
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
if (isRateLimited || isTransientError) {
|
|
14605
|
-
const retryAfterSeconds = Math.min(120, Math.pow(2, attempt) * 2);
|
|
14606
|
-
const reason = isRateLimited ? "Rate limit exceeded (429)" : `Transient server error (${status || "network"})`;
|
|
14607
|
-
throw new RetryableError(`[${options.stepName}] ${reason}. Retrying attempt ${attempt + 1}...`, {
|
|
14608
|
-
retryAfter: `${retryAfterSeconds}s`
|
|
14690
|
+
if (!lesson.learningObjectives || lesson.learningObjectives.length === 0) {
|
|
14691
|
+
score -= 25;
|
|
14692
|
+
findings.push({
|
|
14693
|
+
id: "struct_empty_learning_objectives",
|
|
14694
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
14695
|
+
severity: "CRITICAL",
|
|
14696
|
+
title: "Empty Learning Objectives Array",
|
|
14697
|
+
description: "Lesson plan contains 0 learning objectives.",
|
|
14698
|
+
remediationAdvice: "Declare at least 2 structured Learning Objectives in the schema."
|
|
14609
14699
|
});
|
|
14610
14700
|
}
|
|
14611
|
-
|
|
14612
|
-
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
const res = await withRateLimitBackoff({
|
|
14621
|
-
stepName: `generate-master-lesson-${input.milestone.id || "L01"}`,
|
|
14622
|
-
fn: async () => {
|
|
14623
|
-
return generateLessonMasterFlow({
|
|
14624
|
-
milestone: input.milestone,
|
|
14625
|
-
language: input.language,
|
|
14626
|
-
topic: input.topic,
|
|
14627
|
-
targetAudience: input.targetAudience,
|
|
14628
|
-
contextContinuity: input.contextContinuity,
|
|
14629
|
-
standardsContext: input.standardsContext,
|
|
14630
|
-
modelOptions: input.modelOptions
|
|
14701
|
+
if (!lesson.sections || lesson.sections.length === 0) {
|
|
14702
|
+
score -= 25;
|
|
14703
|
+
findings.push({
|
|
14704
|
+
id: "struct_empty_lesson_sections",
|
|
14705
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
14706
|
+
severity: "CRITICAL",
|
|
14707
|
+
title: "Empty Lesson Sections Array",
|
|
14708
|
+
description: "Lesson plan contains no instructional sections.",
|
|
14709
|
+
remediationAdvice: "Provide structured lesson flow sections."
|
|
14631
14710
|
});
|
|
14632
14711
|
}
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
fn: async () => {
|
|
14644
|
-
return auditCurriculumQualityFlow({
|
|
14645
|
-
targetArtifactType: "LESSON",
|
|
14646
|
-
lessonId: input.lessonId,
|
|
14647
|
-
expectedLanguage: input.language,
|
|
14648
|
-
targetObjectives: input.targetObjectives,
|
|
14649
|
-
generatedContentJson: JSON.stringify(input.lesson, null, 2),
|
|
14650
|
-
standardStatements: input.standardStatements,
|
|
14651
|
-
modelOptions: input.modelOptions
|
|
14712
|
+
if (quiz && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
|
|
14713
|
+
score -= 15;
|
|
14714
|
+
findings.push({
|
|
14715
|
+
id: "struct_quiz_id_mismatch",
|
|
14716
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14717
|
+
severity: "MAJOR",
|
|
14718
|
+
title: "Quiz ID Contract Mismatch",
|
|
14719
|
+
description: `Quiz ID "${quiz.quizId}" does not match Lesson ID "${baseLessonId}".`,
|
|
14720
|
+
remediationAdvice: `Sync Quiz quizId to "${baseLessonId}".`,
|
|
14721
|
+
affectedElement: quiz.quizId
|
|
14652
14722
|
});
|
|
14653
14723
|
}
|
|
14654
|
-
|
|
14655
|
-
|
|
14656
|
-
|
|
14657
|
-
|
|
14658
|
-
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
${input.auditReport.criteria.filter((c) => !c.passed).map((c) => `- [${c.name}] ${c.feedback}`).join("\n")}
|
|
14665
|
-
Actionable Repairs:
|
|
14666
|
-
${input.auditReport.actionableRepairPrompts.map((p) => `* ${p}`).join("\n")}
|
|
14667
|
-
Language Adherence Required: "${input.language}"
|
|
14668
|
-
`.trim();
|
|
14669
|
-
return withRateLimitBackoff({
|
|
14670
|
-
stepName: `repair-master-lesson-${input.milestone.id || "L01"}`,
|
|
14671
|
-
fn: async () => {
|
|
14672
|
-
return generateLessonMasterFlow({
|
|
14673
|
-
milestone: input.milestone,
|
|
14674
|
-
language: input.language,
|
|
14675
|
-
topic: input.topic,
|
|
14676
|
-
targetAudience: input.targetAudience,
|
|
14677
|
-
contextContinuity: repairFeedback,
|
|
14678
|
-
standardsContext: input.standardsContext,
|
|
14679
|
-
modelOptions: input.modelOptions
|
|
14724
|
+
if (activity && activity.lessonId !== baseLessonId) {
|
|
14725
|
+
score -= 15;
|
|
14726
|
+
findings.push({
|
|
14727
|
+
id: "struct_act_id_mismatch",
|
|
14728
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14729
|
+
severity: "MAJOR",
|
|
14730
|
+
title: "Activity Lesson ID Contract Mismatch",
|
|
14731
|
+
description: `Activity lessonId "${activity.lessonId}" does not match Lesson ID "${baseLessonId}".`,
|
|
14732
|
+
remediationAdvice: `Sync Activity lessonId to "${baseLessonId}".`,
|
|
14733
|
+
affectedElement: activity.lessonId
|
|
14680
14734
|
});
|
|
14681
14735
|
}
|
|
14682
|
-
|
|
14683
|
-
|
|
14684
|
-
|
|
14685
|
-
|
|
14686
|
-
|
|
14687
|
-
|
|
14688
|
-
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
fn: async () => generateActivityFlow(options)
|
|
14693
|
-
});
|
|
14694
|
-
console.log(` \u2705 [Step: Activity Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
|
|
14695
|
-
return res;
|
|
14696
|
-
}
|
|
14697
|
-
async function generateCodeLabStep(options) {
|
|
14698
|
-
"use step";
|
|
14699
|
-
console.log(` \u23F3 [Step: Code Lab] Generating starter & solution code LAB.md...`);
|
|
14700
|
-
const t0 = Date.now();
|
|
14701
|
-
const res = await withRateLimitBackoff({
|
|
14702
|
-
stepName: `generate-lab-${options.lesson.lessonId}`,
|
|
14703
|
-
fn: async () => generateCodeLabFlow(options)
|
|
14704
|
-
});
|
|
14705
|
-
console.log(` \u2705 [Step: Code Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
|
|
14706
|
-
return res;
|
|
14707
|
-
}
|
|
14708
|
-
async function generateSelfLabStep(options) {
|
|
14709
|
-
"use step";
|
|
14710
|
-
console.log(` \u23F3 [Step: Self-Lab] Generating SELF_LAB.md with Progressive Hints...`);
|
|
14711
|
-
const t0 = Date.now();
|
|
14712
|
-
const res = await withRateLimitBackoff({
|
|
14713
|
-
stepName: `generate-self-lab-${options.milestone.id || "L01"}`,
|
|
14714
|
-
fn: async () => generateSelfLabFlow(options)
|
|
14715
|
-
});
|
|
14716
|
-
console.log(` \u2705 [Step: Self-Lab] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Challenges: 3 tiers Bronze/Silver/Gold)`);
|
|
14717
|
-
return res;
|
|
14718
|
-
}
|
|
14719
|
-
async function generateDiagnosticQuizStep(options) {
|
|
14720
|
-
"use step";
|
|
14721
|
-
console.log(` \u23F3 [Step: Diagnostic Quiz] Generating QUIZ.json Bloom Assessment...`);
|
|
14722
|
-
const t0 = Date.now();
|
|
14723
|
-
const res = await withRateLimitBackoff({
|
|
14724
|
-
stepName: `generate-quiz-${options.milestone.id || "L01"}`,
|
|
14725
|
-
fn: async () => generateDiagnosticQuizFlow(options)
|
|
14726
|
-
});
|
|
14727
|
-
console.log(` \u2705 [Step: Diagnostic Quiz] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Questions: ${res.questions.length})`);
|
|
14728
|
-
return res;
|
|
14729
|
-
}
|
|
14730
|
-
async function generateSlidesStep(options) {
|
|
14731
|
-
"use step";
|
|
14732
|
-
console.log(` \u23F3 [Step: Slides] Generating Marp presentation SLIDE.md...`);
|
|
14733
|
-
const t0 = Date.now();
|
|
14734
|
-
const res = await withRateLimitBackoff({
|
|
14735
|
-
stepName: `generate-slides-${options.lesson.lessonId}`,
|
|
14736
|
-
fn: async () => generateSlidesFlow(options)
|
|
14737
|
-
});
|
|
14738
|
-
console.log(` \u2705 [Step: Slides] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Slides: ${res.slides.length})`);
|
|
14739
|
-
return res;
|
|
14740
|
-
}
|
|
14741
|
-
async function generateHandoutStep(options) {
|
|
14742
|
-
"use step";
|
|
14743
|
-
console.log(` \u23F3 [Step: Handout] Generating 5-Second Rule HANDOUT.md...`);
|
|
14744
|
-
const t0 = Date.now();
|
|
14745
|
-
const res = await withRateLimitBackoff({
|
|
14746
|
-
stepName: `generate-handout-${options.lesson.lessonId}`,
|
|
14747
|
-
fn: async () => generateHandoutFlow(options)
|
|
14748
|
-
});
|
|
14749
|
-
console.log(` \u2705 [Step: Handout] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (Title: "${res.title}")`);
|
|
14750
|
-
return res;
|
|
14751
|
-
}
|
|
14752
|
-
async function generateWorksheetStep(options) {
|
|
14753
|
-
"use step";
|
|
14754
|
-
return withRateLimitBackoff({
|
|
14755
|
-
stepName: `generate-worksheet-${options.lesson.lessonId}`,
|
|
14756
|
-
fn: async () => generateWorksheetFlow(options)
|
|
14757
|
-
});
|
|
14758
|
-
}
|
|
14759
|
-
async function generateTeacherGuideStep(options) {
|
|
14760
|
-
"use step";
|
|
14761
|
-
return withRateLimitBackoff({
|
|
14762
|
-
stepName: `generate-teacher-guide-${options.lesson.lessonId}`,
|
|
14763
|
-
fn: async () => generateTeacherGuideFlow(options)
|
|
14764
|
-
});
|
|
14765
|
-
}
|
|
14766
|
-
async function generateExtensionStep(options) {
|
|
14767
|
-
"use step";
|
|
14768
|
-
return withRateLimitBackoff({
|
|
14769
|
-
stepName: `generate-extension-${options.lesson.lessonId}`,
|
|
14770
|
-
fn: async () => generateExtensionFlow(options)
|
|
14771
|
-
});
|
|
14772
|
-
}
|
|
14773
|
-
|
|
14774
|
-
// src/workflow/steps/satelliteJudgeStep.ts
|
|
14775
|
-
async function judgeSatelliteStep(input) {
|
|
14776
|
-
"use step";
|
|
14777
|
-
const t0 = Date.now();
|
|
14778
|
-
console.log(" \u{1F9D1}\u200D\u2696\uFE0F [Step: Satellite Judge] " + input.artifactType + " (" + input.artifactId + ")...");
|
|
14779
|
-
return withRateLimitBackoff({
|
|
14780
|
-
stepName: "judge-satellite-" + input.artifactType + "-" + input.artifactId,
|
|
14781
|
-
fn: async () => {
|
|
14782
|
-
return auditCurriculumQualityFlow({
|
|
14783
|
-
targetArtifactType: input.artifactType,
|
|
14784
|
-
lessonId: input.artifactId,
|
|
14785
|
-
expectedLanguage: input.language,
|
|
14786
|
-
targetObjectives: input.targetObjectives,
|
|
14787
|
-
generatedContentJson: JSON.stringify(input.artifact),
|
|
14788
|
-
standardStatements: input.standardStatements,
|
|
14789
|
-
modelOptions: input.modelOptions
|
|
14736
|
+
if (slides && slides.lessonId !== baseLessonId) {
|
|
14737
|
+
score -= 15;
|
|
14738
|
+
findings.push({
|
|
14739
|
+
id: "struct_slides_id_mismatch",
|
|
14740
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14741
|
+
severity: "MAJOR",
|
|
14742
|
+
title: "Slide Deck Lesson ID Contract Mismatch",
|
|
14743
|
+
description: `Slide deck lessonId "${slides.lessonId}" does not match Lesson ID "${baseLessonId}".`,
|
|
14744
|
+
remediationAdvice: `Sync Slide deck lessonId to "${baseLessonId}".`,
|
|
14745
|
+
affectedElement: slides.lessonId
|
|
14790
14746
|
});
|
|
14791
14747
|
}
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
14807
|
-
|
|
14808
|
-
|
|
14809
|
-
savedFiles.push(p2);
|
|
14810
|
-
}
|
|
14811
|
-
if (input.codeLab) {
|
|
14812
|
-
const labContent = serializeCodeLabToMarkdown(input.codeLab);
|
|
14813
|
-
const p3 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "LAB.md", labContent, "LAB");
|
|
14814
|
-
savedFiles.push(p3);
|
|
14815
|
-
}
|
|
14816
|
-
if (input.selfLab) {
|
|
14817
|
-
const selfLabMd = serializeSelfLabToMarkdown(input.selfLab);
|
|
14818
|
-
const p4 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "SELF_LAB.md", selfLabMd, "SELF_LAB");
|
|
14819
|
-
savedFiles.push(p4);
|
|
14820
|
-
}
|
|
14821
|
-
if (input.quiz) {
|
|
14822
|
-
const quizMd = serializeDiagnosticQuizToMarkdown(input.quiz);
|
|
14823
|
-
const p5a = await manager.saveArtifact(input.jobId, input.milestoneSlug, "QUIZ.md", quizMd, "QUIZ_MD");
|
|
14824
|
-
const p5b = await manager.saveArtifact(
|
|
14825
|
-
input.jobId,
|
|
14826
|
-
input.milestoneSlug,
|
|
14827
|
-
"QUIZ.json",
|
|
14828
|
-
JSON.stringify(input.quiz, null, 2),
|
|
14829
|
-
"QUIZ_JSON"
|
|
14830
|
-
);
|
|
14831
|
-
savedFiles.push(p5a, p5b);
|
|
14832
|
-
}
|
|
14833
|
-
if (input.slides) {
|
|
14834
|
-
const slidesContent = `---
|
|
14835
|
-
marp: true
|
|
14836
|
-
theme: default
|
|
14837
|
-
paginate: true
|
|
14838
|
-
---
|
|
14839
|
-
|
|
14840
|
-
# ${input.slides.title}
|
|
14841
|
-
|
|
14842
|
-
---
|
|
14843
|
-
|
|
14844
|
-
${input.slides.slides.map((s) => `## Slide ${s.slideNumber}: ${s.title}
|
|
14845
|
-
|
|
14846
|
-
${s.bulletPoints.map((b) => `- ${b}`).join("\n")}${s.codeSnippet ? `
|
|
14847
|
-
|
|
14848
|
-
\`\`\`
|
|
14849
|
-
${s.codeSnippet}
|
|
14850
|
-
\`\`\`` : ""}
|
|
14851
|
-
|
|
14852
|
-
<!-- Presenter Notes: ${s.presenterNotes || ""} -->`).join("\n\n---\n\n")}
|
|
14853
|
-
`.trim();
|
|
14854
|
-
const p6 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "SLIDE.md", slidesContent, "SLIDES");
|
|
14855
|
-
savedFiles.push(p6);
|
|
14856
|
-
}
|
|
14857
|
-
if (input.handout) {
|
|
14858
|
-
const handoutMd = serializeHandoutToMarkdown(input.handout);
|
|
14859
|
-
const p7 = await manager.saveArtifact(input.jobId, input.milestoneSlug, "HANDOUT.md", handoutMd, "HANDOUT");
|
|
14860
|
-
savedFiles.push(p7);
|
|
14861
|
-
}
|
|
14862
|
-
if (input.cheatSheetMarkdown) {
|
|
14863
|
-
const p8 = await manager.saveArtifact(
|
|
14864
|
-
input.jobId,
|
|
14865
|
-
input.milestoneSlug,
|
|
14866
|
-
"CHEAT_SHEET.md",
|
|
14867
|
-
input.cheatSheetMarkdown,
|
|
14868
|
-
"CHEAT_SHEET"
|
|
14869
|
-
);
|
|
14870
|
-
savedFiles.push(p8);
|
|
14871
|
-
}
|
|
14872
|
-
await manager.markMilestoneCompleted(input.jobId);
|
|
14873
|
-
return {
|
|
14874
|
-
milestoneSlug: input.milestoneSlug,
|
|
14875
|
-
savedFiles
|
|
14876
|
-
};
|
|
14877
|
-
}
|
|
14878
|
-
async function publishToGitStep(options) {
|
|
14879
|
-
"use step";
|
|
14880
|
-
return publishToGitHub(options);
|
|
14881
|
-
}
|
|
14882
|
-
async function publishToSupabaseStep(options) {
|
|
14883
|
-
"use step";
|
|
14884
|
-
return publishToSupabase(options);
|
|
14885
|
-
}
|
|
14886
|
-
var approvalPayloadSchema = z.object({
|
|
14887
|
-
approved: z.boolean().describe("Whether the human reviewer approves the generated curriculum/lesson"),
|
|
14888
|
-
reviewerName: z.string().optional().describe("Name or ID of the reviewer"),
|
|
14889
|
-
feedback: z.string().optional().describe("Actionable feedback or required changes if rejected"),
|
|
14890
|
-
timestamp: z.string().optional().describe("ISO timestamp of the approval action")
|
|
14891
|
-
});
|
|
14892
|
-
var lessonApprovalHook = defineHook();
|
|
14893
|
-
function approvalHookToken(projectId, lessonId, artifactType) {
|
|
14894
|
-
return `approval:${projectId}:${lessonId}:${artifactType}`;
|
|
14895
|
-
}
|
|
14896
|
-
|
|
14897
|
-
// src/standards/standardsCoverageGate.ts
|
|
14898
|
-
function resolveStatementRef(ref, packs) {
|
|
14899
|
-
const [head, ...rest] = ref.split(":");
|
|
14900
|
-
const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
|
|
14901
|
-
const statementId = rest.length > 0 ? rest.join(":") : ref;
|
|
14902
|
-
for (const p of candidatePacks) {
|
|
14903
|
-
if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
|
|
14904
|
-
}
|
|
14905
|
-
return null;
|
|
14906
|
-
}
|
|
14907
|
-
function evaluateStandardsCoverage(input) {
|
|
14908
|
-
const rows = [];
|
|
14909
|
-
const aoToLo = /* @__PURE__ */ new Map();
|
|
14910
|
-
for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
|
|
14911
|
-
const loToRefs = /* @__PURE__ */ new Map();
|
|
14912
|
-
for (const lo of input.objectives) {
|
|
14913
|
-
loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
|
|
14914
|
-
}
|
|
14915
|
-
const taughtLOs = /* @__PURE__ */ new Set();
|
|
14916
|
-
for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
|
|
14917
|
-
const assessedLOs = /* @__PURE__ */ new Set();
|
|
14918
|
-
for (const q of input.quizQuestions) {
|
|
14919
|
-
if (q.alignedLO) assessedLOs.add(q.alignedLO);
|
|
14920
|
-
if (q.alignedAO) {
|
|
14921
|
-
const lo = aoToLo.get(q.alignedAO);
|
|
14922
|
-
if (lo) assessedLOs.add(lo);
|
|
14748
|
+
const satellites = [
|
|
14749
|
+
{ type: "QUIZ", lang: quiz?.language },
|
|
14750
|
+
{ type: "ACT", lang: activity?.language },
|
|
14751
|
+
{ type: "SLIDE", lang: slides?.language }
|
|
14752
|
+
];
|
|
14753
|
+
for (const sat of satellites) {
|
|
14754
|
+
if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
|
|
14755
|
+
score -= 25;
|
|
14756
|
+
findings.push({
|
|
14757
|
+
id: `struct_language_mismatch_${sat.type}`,
|
|
14758
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
14759
|
+
severity: "CRITICAL",
|
|
14760
|
+
title: `Language Policy Inconsistency in ${sat.type}`,
|
|
14761
|
+
description: `Master Lesson is in "${baseLanguage}" but ${sat.type} is declared in "${sat.lang}".`,
|
|
14762
|
+
remediationAdvice: `Set ${sat.type} language to "${baseLanguage}".`
|
|
14763
|
+
});
|
|
14764
|
+
}
|
|
14923
14765
|
}
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14766
|
+
if (quiz && quiz.questions) {
|
|
14767
|
+
for (let i = 0; i < quiz.questions.length; i++) {
|
|
14768
|
+
const q = quiz.questions[i];
|
|
14769
|
+
const qId = q.id || `Q${i + 1}`;
|
|
14770
|
+
const options = q.options || [];
|
|
14771
|
+
if (options.length < 4) {
|
|
14772
|
+
score -= 10;
|
|
14773
|
+
findings.push({
|
|
14774
|
+
id: `struct_quiz_option_count_${qId}`,
|
|
14775
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
14776
|
+
severity: "MAJOR",
|
|
14777
|
+
title: `Structural Option Count Error in ${qId}`,
|
|
14778
|
+
description: `Question ${qId} has ${options.length} options (standard schema requires 4).`,
|
|
14779
|
+
remediationAdvice: "Ensure each question has 4 options (A, B, C, D).",
|
|
14780
|
+
affectedElement: qId
|
|
14781
|
+
});
|
|
14782
|
+
}
|
|
14783
|
+
const correctCount = options.filter((o) => o.isCorrect).length;
|
|
14784
|
+
if (correctCount !== 1) {
|
|
14785
|
+
score -= 20;
|
|
14786
|
+
findings.push({
|
|
14787
|
+
id: `struct_quiz_key_count_${qId}`,
|
|
14788
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
14789
|
+
severity: "CRITICAL",
|
|
14790
|
+
title: `Key Assignment Error in ${qId}`,
|
|
14791
|
+
description: `Question ${qId} has ${correctCount} correct options (must be exactly 1).`,
|
|
14792
|
+
remediationAdvice: "Set `isCorrect: true` on exactly 1 option.",
|
|
14793
|
+
affectedElement: qId
|
|
14794
|
+
});
|
|
14795
|
+
}
|
|
14931
14796
|
}
|
|
14932
14797
|
}
|
|
14933
|
-
|
|
14934
|
-
const
|
|
14935
|
-
const
|
|
14936
|
-
const
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
else status = "UNCOVERED";
|
|
14947
|
-
if (status === "UNCOVERED") {
|
|
14948
|
-
if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
|
|
14949
|
-
else {
|
|
14950
|
-
if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
|
|
14951
|
-
if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
|
|
14952
|
-
}
|
|
14953
|
-
}
|
|
14954
|
-
rows.push({
|
|
14955
|
-
packId: pack.manifest.id,
|
|
14956
|
-
statementId: statement.id,
|
|
14957
|
-
statementText: Object.values(statement.texts)[0] ?? "",
|
|
14958
|
-
status,
|
|
14959
|
-
objectives: los,
|
|
14960
|
-
hasActivity,
|
|
14961
|
-
hasAssessment,
|
|
14962
|
-
issues
|
|
14963
|
-
});
|
|
14964
|
-
}
|
|
14965
|
-
}
|
|
14966
|
-
for (const lo of input.objectives) {
|
|
14967
|
-
for (const r of lo.standardRefs ?? []) {
|
|
14968
|
-
if (!resolveStatementRef(r, input.packs)) {
|
|
14969
|
-
rows.push({
|
|
14970
|
-
packId: "(unresolved)",
|
|
14971
|
-
statementId: r,
|
|
14972
|
-
statementText: "",
|
|
14973
|
-
status: "UNCOVERED",
|
|
14974
|
-
objectives: [lo.code],
|
|
14975
|
-
hasActivity: false,
|
|
14976
|
-
hasAssessment: false,
|
|
14977
|
-
issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
|
|
14798
|
+
if (activity?.materialsBOM?.hardware && activity.materialsBOM.hardware.length > 0) {
|
|
14799
|
+
const hwText = activity.materialsBOM.hardware.join(" ").toLowerCase();
|
|
14800
|
+
const hasLED = hwText.includes("led");
|
|
14801
|
+
const hasResistor = hwText.includes("\u0111i\u1EC7n tr\u1EDF") || hwText.includes("resistor") || hwText.includes("220") || hwText.includes("330") || hwText.includes("1k");
|
|
14802
|
+
if (hasLED && !hasResistor) {
|
|
14803
|
+
score -= 20;
|
|
14804
|
+
findings.push({
|
|
14805
|
+
id: "struct_hardware_unsafe_led_no_resistor",
|
|
14806
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
14807
|
+
severity: "CRITICAL",
|
|
14808
|
+
title: "Unsafe Circuit BOM: LED without Current-Limiting Resistor",
|
|
14809
|
+
description: "Hardware BOM includes LED without a 220\u03A9-1k\u03A9 resistor, causing circuit overload.",
|
|
14810
|
+
remediationAdvice: "Add a 220\u03A9 current-limiting resistor to the hardware materials list."
|
|
14978
14811
|
});
|
|
14979
14812
|
}
|
|
14980
14813
|
}
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
14985
|
-
const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
|
|
14986
|
-
const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
|
|
14987
|
-
const lines = [
|
|
14988
|
-
"# Standards Coverage Report",
|
|
14989
|
-
"",
|
|
14990
|
-
`- Verdict: **${verdict}**`,
|
|
14991
|
-
`- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
|
|
14992
|
-
`- Unresolved standardRefs: ${unresolvedCount}`,
|
|
14993
|
-
"",
|
|
14994
|
-
"| Pack | Statement | Status | LOs | Activity | Assessment |",
|
|
14995
|
-
"|---|---|---|---|---|---|",
|
|
14996
|
-
...rows.map(
|
|
14997
|
-
(r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
|
|
14998
|
-
)
|
|
14999
|
-
];
|
|
15000
|
-
const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
|
|
15001
|
-
if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
|
|
15002
|
-
return {
|
|
15003
|
-
verdict,
|
|
15004
|
-
coveragePct,
|
|
15005
|
-
rows,
|
|
15006
|
-
summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
|
|
15007
|
-
rawMarkdownReport: lines.join("\n")
|
|
15008
|
-
};
|
|
15009
|
-
}
|
|
15010
|
-
var StandardsRegistryAdapter = class {
|
|
15011
|
-
client;
|
|
15012
|
-
constructor(config = {}) {
|
|
15013
|
-
if (config.client) {
|
|
15014
|
-
this.client = config.client;
|
|
15015
|
-
return;
|
|
15016
|
-
}
|
|
15017
|
-
const url = config.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || "";
|
|
15018
|
-
const key = config.supabaseServiceRoleKey || process.env.SUPABASE_SERVICE_ROLE_KEY || config.supabaseAnonKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
|
|
15019
|
-
if (!url || !key) {
|
|
15020
|
-
throw new Error("StandardsRegistryAdapter: Supabase URL/key missing (pass client or env).");
|
|
15021
|
-
}
|
|
15022
|
-
this.client = createClient(url, key);
|
|
15023
|
-
}
|
|
15024
|
-
// ─── Intake (write path — service role) ───────────────────────────────────
|
|
15025
|
-
/** Persist a schema-validated pack as a new framework (status=draft). */
|
|
15026
|
-
async importPack(pack, opts) {
|
|
15027
|
-
const parsed = FrameworkPackSchema.safeParse(pack);
|
|
15028
|
-
if (!parsed.success) {
|
|
15029
|
-
throw new Error("importPack: pack failed schema validation: " + parsed.error.issues.map((i) => i.path.join(".") + ": " + i.message).join("; "));
|
|
15030
|
-
}
|
|
15031
|
-
const p = parsed.data;
|
|
15032
|
-
const { data: fw, error: fwErr } = await this.client.from("standards_frameworks").insert({
|
|
15033
|
-
pack_id: p.manifest.id,
|
|
15034
|
-
content_version: p.manifest.contentVersion,
|
|
15035
|
-
name: p.manifest.name,
|
|
15036
|
-
spec_version: p.manifest.specVersion,
|
|
15037
|
-
subject: p.manifest.subject,
|
|
15038
|
-
languages: p.manifest.languages,
|
|
15039
|
-
grade_model: p.manifest.gradeModel,
|
|
15040
|
-
provenance: p.manifest.provenance,
|
|
15041
|
-
trust: p.manifest.trust,
|
|
15042
|
-
status: "draft",
|
|
15043
|
-
organization_code: opts?.organizationCode ?? (p.manifest.trust === "verified" ? null : p.manifest.provenance.importedBy?.replace(/^org:/, "") || null),
|
|
15044
|
-
original_file_path: opts?.originalFilePath ?? null,
|
|
15045
|
-
created_by: opts?.createdBy ?? null
|
|
15046
|
-
}).select("id").single();
|
|
15047
|
-
if (fwErr) throw new Error("importPack: framework insert failed: " + fwErr.message);
|
|
15048
|
-
const frameworkId = fw.id;
|
|
15049
|
-
const statementRows = p.statements.map((s) => ({
|
|
15050
|
-
framework_id: frameworkId,
|
|
15051
|
-
statement_id: s.id,
|
|
15052
|
-
parent_statement_id: s.parentId ?? null,
|
|
15053
|
-
grade_min: s.gradeBand[0],
|
|
15054
|
-
grade_max: s.gradeBand[1],
|
|
15055
|
-
texts: s.texts,
|
|
15056
|
-
classifications: s.classifications ?? [],
|
|
15057
|
-
bloom_hint: s.bloomHint ?? null,
|
|
15058
|
-
keywords: s.keywords ?? [],
|
|
15059
|
-
source_ref: s.sourceRef ?? null,
|
|
15060
|
-
provenance: s.provenance
|
|
15061
|
-
}));
|
|
15062
|
-
const { error: stErr, count: stCount } = await this.client.from("standards_statements").insert(statementRows, { count: "exact" });
|
|
15063
|
-
if (stErr) throw new Error("importPack: statement insert failed: " + stErr.message);
|
|
15064
|
-
let mappingCount = 0;
|
|
15065
|
-
if (p.mappings && p.mappings.length > 0) {
|
|
15066
|
-
const mappingRows = p.mappings.map((m) => ({
|
|
15067
|
-
framework_id: frameworkId,
|
|
15068
|
-
statement_id: m.statementId,
|
|
15069
|
-
target_ref: m.targetRef,
|
|
15070
|
-
kind: m.kind,
|
|
15071
|
-
confidence: m.confidence,
|
|
15072
|
-
provenance: m.provenance
|
|
15073
|
-
}));
|
|
15074
|
-
const { error: mpErr, count: mpCount } = await this.client.from("standards_mappings").insert(mappingRows, { count: "exact" });
|
|
15075
|
-
if (mpErr) throw new Error("importPack: mapping insert failed: " + mpErr.message);
|
|
15076
|
-
mappingCount = mpCount ?? mappingRows.length;
|
|
14814
|
+
score = Math.max(0, Math.min(100, score));
|
|
14815
|
+
const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
|
|
14816
|
+
if (passed) {
|
|
14817
|
+
strengths.push("Deterministic structure, ID contracts, and schema invariants strictly verified.");
|
|
15077
14818
|
}
|
|
15078
|
-
return {
|
|
15079
|
-
|
|
15080
|
-
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
}
|
|
15085
|
-
async deprecatePack(frameworkDbId) {
|
|
15086
|
-
const { error } = await this.client.from("standards_frameworks").update({ status: "deprecated" }).eq("id", frameworkDbId);
|
|
15087
|
-
if (error) throw new Error("deprecatePack failed: " + error.message);
|
|
15088
|
-
}
|
|
15089
|
-
async adoptPack(frameworkDbId, organizationCode, adoptedBy) {
|
|
15090
|
-
const { error } = await this.client.from("standards_org_adoptions").upsert(
|
|
15091
|
-
{ framework_id: frameworkDbId, organization_code: organizationCode, adopted_by: adoptedBy ?? null },
|
|
15092
|
-
{ onConflict: "organization_code,framework_id" }
|
|
15093
|
-
);
|
|
15094
|
-
if (error) throw new Error("adoptPack failed: " + error.message);
|
|
14819
|
+
return {
|
|
14820
|
+
passed,
|
|
14821
|
+
structuralScore: score,
|
|
14822
|
+
findings,
|
|
14823
|
+
strengths
|
|
14824
|
+
};
|
|
15095
14825
|
}
|
|
15096
|
-
|
|
14826
|
+
};
|
|
14827
|
+
|
|
14828
|
+
// src/evaluators/academicAuditor.ts
|
|
14829
|
+
var AcademicAuditor = class {
|
|
15097
14830
|
/**
|
|
15098
|
-
*
|
|
15099
|
-
*
|
|
15100
|
-
*
|
|
14831
|
+
* Evaluates a complete lesson bundle.
|
|
14832
|
+
* Step 1 (Deterministic): Fast structural, contract, schema, and safety linting.
|
|
14833
|
+
* Step 2 (Semantic LLM-as-a-Judge): Deep pedagogical, cognitive (Bloom), and misconception analysis with live frontier LLM.
|
|
15101
14834
|
*/
|
|
15102
|
-
async
|
|
15103
|
-
|
|
15104
|
-
const
|
|
15105
|
-
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
|
|
15110
|
-
return global || own || adopted;
|
|
14835
|
+
static async auditLessonBundle(input) {
|
|
14836
|
+
const { lesson, quiz, activity, slides, codeLab, modelOptions, executeLLMJudge = true } = input;
|
|
14837
|
+
const structuralResult = DeterministicStructuralLinter.lintBundle({
|
|
14838
|
+
lesson,
|
|
14839
|
+
quiz,
|
|
14840
|
+
activity,
|
|
14841
|
+
slides,
|
|
14842
|
+
codeLab
|
|
15111
14843
|
});
|
|
15112
|
-
|
|
15113
|
-
const
|
|
15114
|
-
|
|
15115
|
-
|
|
15116
|
-
|
|
15117
|
-
|
|
15118
|
-
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
|
|
15127
|
-
|
|
15128
|
-
|
|
15129
|
-
parentId: s.parent_statement_id ?? void 0,
|
|
15130
|
-
gradeBand: [s.grade_min, s.grade_max],
|
|
15131
|
-
texts: s.texts,
|
|
15132
|
-
classifications: s.classifications ?? [],
|
|
15133
|
-
bloomHint: s.bloom_hint ?? void 0,
|
|
15134
|
-
keywords: s.keywords ?? [],
|
|
15135
|
-
sourceRef: s.source_ref ?? void 0,
|
|
15136
|
-
provenance: s.provenance ?? { method: "imported" }
|
|
15137
|
-
}));
|
|
15138
|
-
const ov = overridesByFw.get(f.id) ?? [];
|
|
15139
|
-
const retired = new Set(ov.filter((o) => o.kind === "retired").map((o) => o.statement_id + "|" + o.target_ref));
|
|
15140
|
-
const embedded = (mappings ?? []).filter((m) => m.framework_id === f.id).map((m) => ({
|
|
15141
|
-
statementId: m.statement_id,
|
|
15142
|
-
targetRef: m.target_ref,
|
|
15143
|
-
kind: m.kind,
|
|
15144
|
-
confidence: Number(m.confidence),
|
|
15145
|
-
provenance: m.provenance ?? { method: "imported" }
|
|
15146
|
-
}));
|
|
15147
|
-
const overlay = ov.filter((o) => o.kind !== "retired").map((o) => ({
|
|
15148
|
-
statementId: o.statement_id,
|
|
15149
|
-
targetRef: o.target_ref,
|
|
15150
|
-
kind: o.kind,
|
|
15151
|
-
confidence: Number(o.confidence),
|
|
15152
|
-
provenance: { method: "human", reviewedBy: o.created_by ?? "overlay", note: o.note ?? void 0 }
|
|
15153
|
-
}));
|
|
15154
|
-
const bridge = /* @__PURE__ */ new Map();
|
|
15155
|
-
for (const m of embedded) bridge.set(m.statementId + "|" + m.targetRef, m);
|
|
15156
|
-
for (const m of overlay) bridge.set(m.statementId + "|" + m.targetRef, m);
|
|
15157
|
-
const finalMappings = [...bridge.values()].filter((m) => !retired.has(m.statementId + "|" + m.targetRef));
|
|
15158
|
-
const hydrated = FrameworkPackSchema.safeParse({
|
|
15159
|
-
manifest: {
|
|
15160
|
-
id: f.pack_id,
|
|
15161
|
-
name: f.name,
|
|
15162
|
-
specVersion: f.spec_version || "1.0",
|
|
15163
|
-
contentVersion: f.content_version,
|
|
15164
|
-
subject: f.subject,
|
|
15165
|
-
languages: f.languages,
|
|
15166
|
-
gradeModel: f.grade_model,
|
|
15167
|
-
provenance: f.provenance,
|
|
15168
|
-
trust: f.trust
|
|
15169
|
-
},
|
|
15170
|
-
statements: stmts,
|
|
15171
|
-
mappings: finalMappings
|
|
15172
|
-
});
|
|
15173
|
-
if (!hydrated.success) {
|
|
15174
|
-
throw new Error('loadPacksForOrg: hydrated pack "' + f.pack_id + '" failed schema: ' + hydrated.error.issues[0]?.message);
|
|
15175
|
-
}
|
|
15176
|
-
return { ...hydrated.data, dbId: f.id, status: f.status, organizationCode: f.organization_code ?? null };
|
|
15177
|
-
});
|
|
15178
|
-
}
|
|
15179
|
-
};
|
|
15180
|
-
|
|
15181
|
-
// src/workflow/workflows/milestoneWorkflow.ts
|
|
15182
|
-
async function generateMilestoneWorkflow(input) {
|
|
15183
|
-
"use workflow";
|
|
15184
|
-
const language = input.language || "vi";
|
|
15185
|
-
const milestoneId = input.milestone.id || input.milestone.concept_code || "L01";
|
|
15186
|
-
const slugPrefix = /^\d+$/.test(milestoneId) ? `M${milestoneId.padStart(2, "0")}` : milestoneId;
|
|
15187
|
-
const milestoneSlug = `${slugPrefix}_${(input.milestone.name || "milestone").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
15188
|
-
const bundleTier = input.bundleTier || "minimum";
|
|
15189
|
-
const gates = resolveGateSettings(input.gateSettings);
|
|
15190
|
-
const techStack = input.techStack || input.milestone.tech_keywords || ["General Technology"];
|
|
15191
|
-
console.log(" \u{1F6A6} [Gate] LESSON: " + gates.LESSON + " | ACT: " + gates.ACT + " | QUIZ: " + gates.QUIZ);
|
|
15192
|
-
const targetObjectives = (input.milestone.learning_objectives || []).map((lo) => ({
|
|
15193
|
-
code: lo.code,
|
|
15194
|
-
description: lo.description || lo.name || "",
|
|
15195
|
-
bloomLevel: lo.bloom_level || "understand"
|
|
15196
|
-
}));
|
|
15197
|
-
const packs = input.standardsPacks ?? [];
|
|
15198
|
-
const { block: standardsContext, selected: selectedStatements } = buildStandardsContext({
|
|
15199
|
-
packs,
|
|
15200
|
-
gradeBand: input.milestone.grade_band ?? [6, 12],
|
|
15201
|
-
topicText: [input.topic, input.milestone.name, input.milestone.description, ...input.milestone.tech_keywords || []].filter(Boolean).join(" "),
|
|
15202
|
-
conceptCodes: (input.milestone.concept_code || "").split(",").map((c) => c.trim()).filter(Boolean),
|
|
15203
|
-
language
|
|
15204
|
-
});
|
|
15205
|
-
const standardStatements = {};
|
|
15206
|
-
for (const s of selectedStatements) {
|
|
15207
|
-
standardStatements[s.packId + ":" + s.statement.id] = Object.values(s.statement.texts)[0] || "";
|
|
15208
|
-
}
|
|
15209
|
-
if (selectedStatements.length > 0) {
|
|
15210
|
-
console.log(" \u{1F4D0} [Standards] Grounded with " + selectedStatements.length + " verbatim statement(s) from " + new Set(selectedStatements.map((s) => s.packId)).size + " pack(s)");
|
|
15211
|
-
}
|
|
15212
|
-
let lesson = await generateMasterLessonStep({
|
|
15213
|
-
milestone: input.milestone,
|
|
15214
|
-
language,
|
|
15215
|
-
topic: input.topic,
|
|
15216
|
-
targetAudience: input.targetAudience,
|
|
15217
|
-
standardsContext: standardsContext || void 0,
|
|
15218
|
-
modelOptions: input.modelOptions
|
|
15219
|
-
});
|
|
15220
|
-
let auditReport;
|
|
15221
|
-
const lessonGate = gateModeFor(gates, "LESSON");
|
|
15222
|
-
if (lessonGate === "LLM_JUDGE") {
|
|
15223
|
-
auditReport = await judgeMasterLessonStep({
|
|
15224
|
-
lessonId: milestoneId,
|
|
15225
|
-
lesson,
|
|
15226
|
-
language,
|
|
15227
|
-
targetObjectives,
|
|
15228
|
-
standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0,
|
|
15229
|
-
modelOptions: input.modelOptions
|
|
15230
|
-
});
|
|
15231
|
-
if (auditReport.overallVerdict === "FAIL" || auditReport.overallVerdict === "NEEDS_REVISION") {
|
|
15232
|
-
lesson = await repairMasterLessonStep({
|
|
15233
|
-
milestone: input.milestone,
|
|
15234
|
-
language,
|
|
15235
|
-
topic: input.topic,
|
|
15236
|
-
targetAudience: input.targetAudience,
|
|
15237
|
-
auditReport,
|
|
15238
|
-
standardsContext: standardsContext || void 0,
|
|
15239
|
-
modelOptions: input.modelOptions
|
|
15240
|
-
});
|
|
15241
|
-
}
|
|
15242
|
-
} else if (lessonGate === "HITL") {
|
|
15243
|
-
const hookToken = "gate:lesson:" + milestoneId;
|
|
15244
|
-
console.log(" \u23F8 [Gate] LESSON awaiting HUMAN review (HITL) \u2014 hook " + hookToken);
|
|
15245
|
-
const workflowPkg = await import('workflow');
|
|
15246
|
-
const createHook = workflowPkg.createHook;
|
|
15247
|
-
const hook = createHook({ token: hookToken });
|
|
15248
|
-
const review = await hook;
|
|
15249
|
-
console.log(" \u2705 [Gate] Human verdict: " + (review.approved ? "APPROVED" : "REJECTED") + (review.feedback ? " \u2014 " + review.feedback : ""));
|
|
15250
|
-
if (!review.approved) {
|
|
15251
|
-
const repairReport = {
|
|
15252
|
-
overallVerdict: "NEEDS_REVISION",
|
|
15253
|
-
totalScore: 0,
|
|
15254
|
-
languageAdherencePassed: true,
|
|
15255
|
-
criteria: [],
|
|
15256
|
-
actionableRepairPrompts: review.feedback ? [review.feedback] : ["S\u1EEDa theo ph\u1EA3n h\u1ED3i c\u1EE7a ng\u01B0\u1EDDi duy\u1EC7t"]
|
|
15257
|
-
};
|
|
15258
|
-
lesson = await repairMasterLessonStep({
|
|
15259
|
-
milestone: input.milestone,
|
|
15260
|
-
language,
|
|
15261
|
-
topic: input.topic,
|
|
15262
|
-
targetAudience: input.targetAudience,
|
|
15263
|
-
auditReport: repairReport,
|
|
15264
|
-
standardsContext: standardsContext || void 0,
|
|
15265
|
-
modelOptions: input.modelOptions
|
|
15266
|
-
});
|
|
15267
|
-
const hook2 = createHook({ token: hookToken + ":v2" });
|
|
15268
|
-
const review2 = await hook2;
|
|
15269
|
-
console.log(" \u2705 [Gate] Human verdict (v2): " + (review2.approved ? "APPROVED" : "REJECTED"));
|
|
15270
|
-
if (!review2.approved) {
|
|
15271
|
-
console.log(" \u{1F6D1} [Gate] LESSON rejected twice \u2014 satellites & save BLOCKED");
|
|
15272
|
-
return {
|
|
15273
|
-
milestoneId,
|
|
15274
|
-
milestoneSlug,
|
|
15275
|
-
status: "FAILED",
|
|
15276
|
-
gateBlocked: { artifactType: "LESSON", reason: "HITL_REJECTED_V2", feedback: review2.feedback },
|
|
15277
|
-
artifacts: void 0
|
|
15278
|
-
};
|
|
15279
|
-
}
|
|
15280
|
-
}
|
|
15281
|
-
}
|
|
15282
|
-
const [activity, selfLab, quiz, slides, handout] = await Promise.all([
|
|
15283
|
-
generateActivityStep({ lesson, language, modelOptions: input.modelOptions }),
|
|
15284
|
-
generateSelfLabStep({ milestone: input.milestone, language, targetTechStack: techStack, modelOptions: input.modelOptions }),
|
|
15285
|
-
generateDiagnosticQuizStep({ milestone: input.milestone, language, modelOptions: input.modelOptions }),
|
|
15286
|
-
generateSlidesStep({ lesson, language, modelOptions: input.modelOptions }),
|
|
15287
|
-
generateHandoutStep({ lesson, language, modelOptions: input.modelOptions })
|
|
15288
|
-
]);
|
|
15289
|
-
const codeLab = await generateCodeLabStep({
|
|
15290
|
-
lesson,
|
|
15291
|
-
activity,
|
|
15292
|
-
language,
|
|
15293
|
-
targetTechStack: techStack,
|
|
15294
|
-
modelOptions: input.modelOptions
|
|
15295
|
-
});
|
|
15296
|
-
const satelliteReports = {};
|
|
15297
|
-
const satelliteCandidates = [
|
|
15298
|
-
{ type: "ACT", id: "ACT_" + milestoneId, artifact: activity },
|
|
15299
|
-
{ type: "QUIZ", id: "QUIZ_" + milestoneId, artifact: quiz },
|
|
15300
|
-
{ type: "SLIDE", id: "SLIDE_" + milestoneId, artifact: slides },
|
|
15301
|
-
{ type: "HANDOUT", id: "HANDOUT_" + milestoneId, artifact: handout },
|
|
15302
|
-
{ type: "CODE", id: "CODE_" + milestoneId, artifact: codeLab }
|
|
15303
|
-
];
|
|
15304
|
-
const toJudge = satelliteCandidates.filter((c) => c.artifact && gateModeFor(gates, c.type) === "LLM_JUDGE");
|
|
15305
|
-
if (toJudge.length > 0) {
|
|
15306
|
-
const reports = await Promise.all(
|
|
15307
|
-
toJudge.map(
|
|
15308
|
-
(c) => judgeSatelliteStep({
|
|
15309
|
-
artifactType: c.type,
|
|
15310
|
-
artifactId: c.id,
|
|
15311
|
-
artifact: c.artifact,
|
|
15312
|
-
language,
|
|
15313
|
-
targetObjectives,
|
|
15314
|
-
standardStatements: Object.keys(standardStatements).length > 0 ? standardStatements : void 0,
|
|
15315
|
-
modelOptions: input.modelOptions
|
|
15316
|
-
}).catch((err) => {
|
|
15317
|
-
console.warn(" \u26A0 [Gate] satellite judge failed for " + c.type + ": " + err.message);
|
|
15318
|
-
return void 0;
|
|
15319
|
-
})
|
|
15320
|
-
)
|
|
15321
|
-
);
|
|
15322
|
-
for (let i = 0; i < toJudge.length; i++) {
|
|
15323
|
-
const r = reports[i];
|
|
15324
|
-
if (r) satelliteReports[toJudge[i].type] = r;
|
|
15325
|
-
}
|
|
15326
|
-
}
|
|
15327
|
-
let worksheet;
|
|
15328
|
-
let teacherGuide;
|
|
15329
|
-
let extension;
|
|
15330
|
-
if (bundleTier === "full") {
|
|
15331
|
-
[worksheet, teacherGuide, extension] = await Promise.all([
|
|
15332
|
-
generateWorksheetStep({ lesson, language, modelOptions: input.modelOptions }),
|
|
15333
|
-
generateTeacherGuideStep({ lesson, activity, language, modelOptions: input.modelOptions }),
|
|
15334
|
-
generateExtensionStep({ lesson, activity, language, modelOptions: input.modelOptions })
|
|
15335
|
-
]);
|
|
15336
|
-
}
|
|
15337
|
-
let standardsCoverage;
|
|
15338
|
-
if (packs.length > 0) {
|
|
15339
|
-
standardsCoverage = evaluateStandardsCoverage({
|
|
15340
|
-
packs,
|
|
15341
|
-
objectives: (lesson.learningObjectives || []).map((lo) => ({
|
|
15342
|
-
code: lo.code,
|
|
15343
|
-
standardRefs: lo.standardRefs,
|
|
15344
|
-
conceptRefs: lo.conceptRefs
|
|
15345
|
-
})),
|
|
15346
|
-
// NOTE: ActivityLab has no structured LO linkage yet — activity coverage rows are derived
|
|
15347
|
-
// from LO→quiz paths inside the gate. Wire ACT.loRefs in a future schema revision.
|
|
15348
|
-
activities: [],
|
|
15349
|
-
quizQuestions: (quiz?.questions || []).map((q) => ({ alignedLO: q.alignedLO, alignedAO: q.alignedAO })),
|
|
15350
|
-
assessmentObjectives: quiz?.assessmentObjectives?.map((ao) => ({ aoCode: ao.aoCode, alignedLO: ao.alignedLO }))
|
|
15351
|
-
});
|
|
15352
|
-
console.log(" \u{1F4D0} [Standards Coverage] " + standardsCoverage.summary);
|
|
15353
|
-
const enforce = input.enforceStandardsCoverage ?? true;
|
|
15354
|
-
if (enforce && standardsCoverage.verdict === "FAIL") {
|
|
15355
|
-
console.error(" \u26D4 [Standards Coverage] HARD BLOCK \u2014 " + standardsCoverage.summary);
|
|
15356
|
-
console.error(standardsCoverage.rawMarkdownReport.split("\n").filter((l) => l.startsWith("- [")).slice(0, 10).join("\n"));
|
|
15357
|
-
return {
|
|
15358
|
-
milestoneId,
|
|
15359
|
-
milestoneSlug,
|
|
15360
|
-
status: "FAILED",
|
|
15361
|
-
savedResult: { workspaceDir: "", files: [] },
|
|
15362
|
-
judgeReport: auditReport,
|
|
15363
|
-
standardsCoverage,
|
|
15364
|
-
artifacts: { lesson, activity, codeLab, selfLab, quiz, slides, handout, worksheet, teacherGuide, extension }
|
|
15365
|
-
};
|
|
15366
|
-
}
|
|
15367
|
-
}
|
|
15368
|
-
const savedResult = await saveMilestoneToWorkspaceStep({
|
|
15369
|
-
jobId: input.jobId,
|
|
15370
|
-
milestoneSlug,
|
|
15371
|
-
lesson,
|
|
15372
|
-
activity,
|
|
15373
|
-
codeLab,
|
|
15374
|
-
selfLab,
|
|
15375
|
-
quiz,
|
|
15376
|
-
slides,
|
|
15377
|
-
handout,
|
|
15378
|
-
baseWorkspaceDir: input.baseWorkspaceDir
|
|
15379
|
-
});
|
|
15380
|
-
return {
|
|
15381
|
-
milestoneId,
|
|
15382
|
-
milestoneSlug,
|
|
15383
|
-
status: "SUCCESS",
|
|
15384
|
-
savedResult,
|
|
15385
|
-
judgeReport: auditReport,
|
|
15386
|
-
satelliteJudgeReports: Object.keys(satelliteReports).length > 0 ? satelliteReports : void 0,
|
|
15387
|
-
gatesResolved: gates,
|
|
15388
|
-
standardsCoverage,
|
|
15389
|
-
artifacts: {
|
|
15390
|
-
lesson,
|
|
15391
|
-
activity,
|
|
15392
|
-
codeLab,
|
|
15393
|
-
selfLab,
|
|
15394
|
-
quiz,
|
|
15395
|
-
slides,
|
|
15396
|
-
handout,
|
|
15397
|
-
worksheet,
|
|
15398
|
-
teacherGuide,
|
|
15399
|
-
extension
|
|
15400
|
-
}
|
|
15401
|
-
};
|
|
15402
|
-
}
|
|
15403
|
-
async function generateRoadmapWorkflow(input) {
|
|
15404
|
-
"use workflow";
|
|
15405
|
-
const jobId = input.jobId || `job_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
15406
|
-
const language = input.language || input.roadmap.target_language || "vi";
|
|
15407
|
-
const topic = input.roadmap.topic || input.roadmap.goal || "General Curriculum";
|
|
15408
|
-
const courseTitle = input.roadmap.title || `Curriculum: ${topic}`;
|
|
15409
|
-
const batchSize = input.batchSize || 2;
|
|
15410
|
-
const milestones = input.roadmap.milestones || [];
|
|
15411
|
-
const workspaceManager = new LocalWorkspaceManager(input.baseWorkspaceDir);
|
|
15412
|
-
const workspaceDir = await workspaceManager.initJobWorkspace(jobId, {
|
|
15413
|
-
courseTitle,
|
|
15414
|
-
topic,
|
|
15415
|
-
language,
|
|
15416
|
-
totalMilestones: milestones.length
|
|
15417
|
-
});
|
|
15418
|
-
const milestoneOutcomes = [];
|
|
15419
|
-
for (let i = 0; i < milestones.length; i += batchSize) {
|
|
15420
|
-
const batch = milestones.slice(i, i + batchSize);
|
|
15421
|
-
const batchSettled = await Promise.allSettled(
|
|
15422
|
-
batch.map(
|
|
15423
|
-
(milestone) => generateMilestoneWorkflow({
|
|
15424
|
-
jobId,
|
|
15425
|
-
milestone,
|
|
15426
|
-
language,
|
|
15427
|
-
topic,
|
|
15428
|
-
targetAudience: input.roadmap.target_audience,
|
|
15429
|
-
techStack: input.roadmap.tech_stack || milestone.tech_keywords,
|
|
15430
|
-
bundleTier: input.bundleTier,
|
|
15431
|
-
gateSettings: input.gateSettings,
|
|
15432
|
-
baseWorkspaceDir: input.baseWorkspaceDir,
|
|
15433
|
-
modelOptions: input.modelOptions
|
|
15434
|
-
})
|
|
15435
|
-
)
|
|
15436
|
-
);
|
|
15437
|
-
for (const [idx, outcome] of batchSettled.entries()) {
|
|
15438
|
-
const targetMilestone = batch[idx];
|
|
15439
|
-
const mId = targetMilestone.id || targetMilestone.concept_code || `M${i + idx + 1}`;
|
|
15440
|
-
if (outcome.status === "fulfilled") {
|
|
15441
|
-
milestoneOutcomes.push({
|
|
15442
|
-
milestoneId: mId,
|
|
15443
|
-
status: "FULFILLED",
|
|
15444
|
-
result: outcome.value
|
|
14844
|
+
const allFindings = [...structuralResult.findings];
|
|
14845
|
+
const strengths = [...structuralResult.strengths];
|
|
14846
|
+
let semanticScore = null;
|
|
14847
|
+
let semanticVerdict = "PASS";
|
|
14848
|
+
if (executeLLMJudge) {
|
|
14849
|
+
try {
|
|
14850
|
+
const judgeReport = await auditCurriculumQualityFlow({
|
|
14851
|
+
targetArtifactType: "LESSON_BUNDLE",
|
|
14852
|
+
lessonId: lesson.lessonId,
|
|
14853
|
+
expectedLanguage: lesson.language,
|
|
14854
|
+
targetObjectives: (lesson.learningObjectives || []).map((lo) => ({
|
|
14855
|
+
code: lo.code,
|
|
14856
|
+
description: lo.description,
|
|
14857
|
+
bloomLevel: lo.bloomLevel
|
|
14858
|
+
})),
|
|
14859
|
+
generatedContentJson: JSON.stringify({ lesson, quiz, activity, slides, codeLab }),
|
|
14860
|
+
modelOptions
|
|
15445
14861
|
});
|
|
15446
|
-
|
|
15447
|
-
|
|
15448
|
-
|
|
15449
|
-
|
|
15450
|
-
|
|
14862
|
+
semanticScore = judgeReport.totalScore;
|
|
14863
|
+
semanticVerdict = judgeReport.overallVerdict;
|
|
14864
|
+
for (const criterion of judgeReport.criteria) {
|
|
14865
|
+
if (!criterion.passed) {
|
|
14866
|
+
allFindings.push({
|
|
14867
|
+
id: `llm_judge_${criterion.name.toLowerCase().replace(/\s+/g, "_")}`,
|
|
14868
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
14869
|
+
severity: criterion.score < 50 ? "CRITICAL" : "MAJOR",
|
|
14870
|
+
title: `LLM-as-Judge Finding: ${criterion.name}`,
|
|
14871
|
+
description: criterion.feedback,
|
|
14872
|
+
remediationAdvice: judgeReport.actionableRepairPrompts.join("; ") || "Refine prompt context."
|
|
14873
|
+
});
|
|
14874
|
+
} else {
|
|
14875
|
+
strengths.push(`[LLM-Judge] ${criterion.name}: ${criterion.feedback}`);
|
|
14876
|
+
}
|
|
14877
|
+
}
|
|
14878
|
+
} catch (err) {
|
|
14879
|
+
semanticScore = null;
|
|
14880
|
+
semanticVerdict = "FAIL";
|
|
14881
|
+
allFindings.push({
|
|
14882
|
+
id: "llm_judge_connection_error",
|
|
14883
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
14884
|
+
severity: "MINOR",
|
|
14885
|
+
title: "LLM-as-Judge Skipped / Fallback",
|
|
14886
|
+
description: `Semantic inference error: ${err.message || String(err)}`,
|
|
14887
|
+
remediationAdvice: "Check API credentials for LLM-as-Judge."
|
|
15451
14888
|
});
|
|
15452
14889
|
}
|
|
15453
14890
|
}
|
|
15454
|
-
|
|
15455
|
-
|
|
14891
|
+
const criticalCount = allFindings.filter((f) => f.severity === "CRITICAL").length;
|
|
14892
|
+
const overallScore = executeLLMJudge && semanticScore !== null ? Math.round(structuralResult.structuralScore * 0.4 + semanticScore * 0.6) : structuralResult.structuralScore;
|
|
14893
|
+
const passed = structuralResult.passed && criticalCount === 0 && (executeLLMJudge ? semanticVerdict === "PASS" : true);
|
|
14894
|
+
let verdict = "REJECTED";
|
|
14895
|
+
if (overallScore >= 90 && criticalCount === 0) {
|
|
14896
|
+
verdict = "EXEMPLARY";
|
|
14897
|
+
} else if (overallScore >= 75 && criticalCount === 0) {
|
|
14898
|
+
verdict = "ACADEMICALLY_SOUND";
|
|
14899
|
+
} else if (overallScore >= 60) {
|
|
14900
|
+
verdict = "NEEDS_PEDAGOGICAL_REFINEMENT";
|
|
15456
14901
|
}
|
|
14902
|
+
const summary = passed ? `\u2705 Lesson Bundle "${lesson.title}" (${lesson.lessonId}) passes academic & structural verification with score ${overallScore}/100 (${verdict}).` : `\u26A0\uFE0F Lesson Bundle "${lesson.title}" requires revision (${overallScore}/100 - ${verdict}). Found ${allFindings.length} finding(s) with ${criticalCount} critical blocker(s).`;
|
|
14903
|
+
const actionablePromptGuidance = allFindings.map(
|
|
14904
|
+
(f, idx) => `[${f.dimension}] ${idx + 1}. ${f.title}: ${f.remediationAdvice}`
|
|
14905
|
+
);
|
|
14906
|
+
const computeDimScore = (dimFindings2) => {
|
|
14907
|
+
const hasCritical = dimFindings2.some((f) => f.severity === "CRITICAL");
|
|
14908
|
+
const majorCount = dimFindings2.filter((f) => f.severity === "MAJOR").length;
|
|
14909
|
+
const minorCount = dimFindings2.filter((f) => f.severity === "MINOR").length;
|
|
14910
|
+
let dimScore = 100 - (hasCritical ? 40 : 0) - majorCount * 15 - minorCount * 5;
|
|
14911
|
+
dimScore = Math.max(0, Math.min(100, dimScore));
|
|
14912
|
+
return { score: dimScore, passed: dimScore >= 75 && !hasCritical };
|
|
14913
|
+
};
|
|
14914
|
+
const dimFindings = {
|
|
14915
|
+
constructiveAlignment: allFindings.filter((f) => f.dimension === "CONSTRUCTIVE_ALIGNMENT"),
|
|
14916
|
+
bloomProgression: allFindings.filter((f) => f.dimension === "BLOOM_PROGRESSION"),
|
|
14917
|
+
fiveEFidelity: allFindings.filter((f) => f.dimension === "5E_INSTRUCTIONAL_FIDELITY"),
|
|
14918
|
+
misconceptionRigor: allFindings.filter((f) => f.dimension === "MISCONCEPTION_RIGOR"),
|
|
14919
|
+
technicalAuthenticity: allFindings.filter((f) => f.dimension === "TECHNICAL_AUTHENTICITY")
|
|
14920
|
+
};
|
|
14921
|
+
const dimScores = Object.fromEntries(
|
|
14922
|
+
Object.entries(dimFindings).map(([k, v]) => [k, computeDimScore(v)])
|
|
14923
|
+
);
|
|
14924
|
+
return {
|
|
14925
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14926
|
+
targetId: lesson.lessonId,
|
|
14927
|
+
targetType: "BUNDLE",
|
|
14928
|
+
overallScore,
|
|
14929
|
+
passed,
|
|
14930
|
+
verdict,
|
|
14931
|
+
summary,
|
|
14932
|
+
dimensionScores: {
|
|
14933
|
+
constructiveAlignment: {
|
|
14934
|
+
score: dimScores.constructiveAlignment.score,
|
|
14935
|
+
weight: 0.2,
|
|
14936
|
+
passed: dimScores.constructiveAlignment.passed,
|
|
14937
|
+
strengths: dimScores.constructiveAlignment.passed ? ["Constructive alignment verified."] : [],
|
|
14938
|
+
findings: dimFindings.constructiveAlignment
|
|
14939
|
+
},
|
|
14940
|
+
bloomProgression: {
|
|
14941
|
+
score: dimScores.bloomProgression.score,
|
|
14942
|
+
weight: 0.2,
|
|
14943
|
+
passed: dimScores.bloomProgression.passed,
|
|
14944
|
+
strengths: dimScores.bloomProgression.passed ? ["Bloom taxonomy progression verified."] : [],
|
|
14945
|
+
findings: dimFindings.bloomProgression
|
|
14946
|
+
},
|
|
14947
|
+
fiveEFidelity: {
|
|
14948
|
+
score: dimScores.fiveEFidelity.score,
|
|
14949
|
+
weight: 0.15,
|
|
14950
|
+
passed: dimScores.fiveEFidelity.passed,
|
|
14951
|
+
strengths: dimScores.fiveEFidelity.passed ? ["5E instructional fidelity verified."] : [],
|
|
14952
|
+
findings: dimFindings.fiveEFidelity
|
|
14953
|
+
},
|
|
14954
|
+
misconceptionRigor: {
|
|
14955
|
+
score: dimScores.misconceptionRigor.score,
|
|
14956
|
+
weight: 0.2,
|
|
14957
|
+
passed: dimScores.misconceptionRigor.passed,
|
|
14958
|
+
strengths: dimScores.misconceptionRigor.passed ? ["Misconception rigor verified."] : [],
|
|
14959
|
+
findings: dimFindings.misconceptionRigor
|
|
14960
|
+
},
|
|
14961
|
+
technicalAuthenticity: {
|
|
14962
|
+
score: dimScores.technicalAuthenticity.score,
|
|
14963
|
+
weight: 0.15,
|
|
14964
|
+
passed: dimScores.technicalAuthenticity.passed,
|
|
14965
|
+
strengths: dimScores.technicalAuthenticity.passed ? ["Technical authenticity verified."] : [],
|
|
14966
|
+
findings: dimFindings.technicalAuthenticity
|
|
14967
|
+
},
|
|
14968
|
+
crossArtifactZeroDrift: {
|
|
14969
|
+
score: structuralResult.structuralScore,
|
|
14970
|
+
weight: 0.1,
|
|
14971
|
+
passed: structuralResult.passed,
|
|
14972
|
+
strengths: structuralResult.passed ? ["Cross-artifact zero drift verified."] : [],
|
|
14973
|
+
findings: allFindings.filter((f) => f.dimension === "CROSS_ARTIFACT_ZERO_DRIFT")
|
|
14974
|
+
}
|
|
14975
|
+
},
|
|
14976
|
+
criticalFindingsCount: criticalCount,
|
|
14977
|
+
allFindings,
|
|
14978
|
+
actionablePromptGuidance
|
|
14979
|
+
};
|
|
15457
14980
|
}
|
|
15458
|
-
|
|
15459
|
-
const failedMilestones = milestoneOutcomes.filter((m) => m.status === "REJECTED").length;
|
|
15460
|
-
let gitPublishResult = void 0;
|
|
15461
|
-
let supabasePublishResult = void 0;
|
|
15462
|
-
if (input.gitPublishOptions) {
|
|
15463
|
-
gitPublishResult = await publishToGitStep({
|
|
15464
|
-
jobId,
|
|
15465
|
-
localJobDir: workspaceDir,
|
|
15466
|
-
...input.gitPublishOptions
|
|
15467
|
-
});
|
|
15468
|
-
}
|
|
15469
|
-
if (input.supabasePublishOptions) {
|
|
15470
|
-
supabasePublishResult = await publishToSupabaseStep({
|
|
15471
|
-
jobId,
|
|
15472
|
-
courseTitle,
|
|
15473
|
-
topic,
|
|
15474
|
-
language,
|
|
15475
|
-
milestones: milestoneOutcomes.filter((m) => m.status === "FULFILLED" && m.result).map((m) => ({
|
|
15476
|
-
milestoneId: m.milestoneId,
|
|
15477
|
-
milestoneName: m.result.savedResult.milestoneSlug,
|
|
15478
|
-
lessonSlug: m.result.savedResult.milestoneSlug
|
|
15479
|
-
})),
|
|
15480
|
-
...input.supabasePublishOptions
|
|
15481
|
-
});
|
|
15482
|
-
}
|
|
15483
|
-
return {
|
|
15484
|
-
jobId,
|
|
15485
|
-
courseTitle,
|
|
15486
|
-
topic,
|
|
15487
|
-
language,
|
|
15488
|
-
totalMilestones: milestones.length,
|
|
15489
|
-
successfulMilestones,
|
|
15490
|
-
failedMilestones,
|
|
15491
|
-
results: milestoneOutcomes,
|
|
15492
|
-
gitPublishResult,
|
|
15493
|
-
supabasePublishResult,
|
|
15494
|
-
workspaceDir
|
|
15495
|
-
};
|
|
15496
|
-
}
|
|
15497
|
-
|
|
15498
|
-
// src/workflow/workflows/singleArtifactWorkflow.ts
|
|
15499
|
-
async function executeSingleArtifactStep(request) {
|
|
15500
|
-
"use step";
|
|
15501
|
-
const t0 = Date.now();
|
|
15502
|
-
console.log(` \u23F3 [Step: Single Artifact] Generating ${request.artifactType.toUpperCase()} for "${request.topic.slice(0, 35)}"...`);
|
|
15503
|
-
const res = await withRateLimitBackoff({
|
|
15504
|
-
stepName: `generate-single-artifact-${request.artifactType}-${(request.topic || "topic").slice(0, 20)}`,
|
|
15505
|
-
fn: async () => generateSingleArtifact(request)
|
|
15506
|
-
});
|
|
15507
|
-
console.log(` \u2705 [Step: Single Artifact] Done in ${((Date.now() - t0) / 1e3).toFixed(1)}s (File: "${res.filename}")`);
|
|
15508
|
-
return res;
|
|
15509
|
-
}
|
|
15510
|
-
async function generateSingleArtifactWorkflow(request) {
|
|
15511
|
-
"use workflow";
|
|
15512
|
-
return executeSingleArtifactStep(request);
|
|
15513
|
-
}
|
|
15514
|
-
|
|
15515
|
-
// src/index.ts
|
|
15516
|
-
init_errors();
|
|
14981
|
+
};
|
|
15517
14982
|
|
|
15518
|
-
// src/evaluators/
|
|
15519
|
-
var
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
|
|
14983
|
+
// src/evaluators/bloomTaxonomyEvaluator.ts
|
|
14984
|
+
var BLOOM_ACTION_VERBS = {
|
|
14985
|
+
remember: [
|
|
14986
|
+
"list",
|
|
14987
|
+
"define",
|
|
14988
|
+
"recall",
|
|
14989
|
+
"state",
|
|
14990
|
+
"name",
|
|
14991
|
+
"identify",
|
|
14992
|
+
"label",
|
|
14993
|
+
"recognize",
|
|
14994
|
+
"li\u1EC7t k\xEA",
|
|
14995
|
+
"\u0111\u1ECBnh ngh\u0129a",
|
|
14996
|
+
"g\u1ECDi t\xEAn",
|
|
14997
|
+
"nh\u1EADn di\u1EC7n",
|
|
14998
|
+
"ch\u1EC9 ra",
|
|
14999
|
+
"nh\u1EAFc l\u1EA1i",
|
|
15000
|
+
"ghi nh\u1EDB"
|
|
15001
|
+
],
|
|
15002
|
+
understand: [
|
|
15003
|
+
"explain",
|
|
15004
|
+
"describe",
|
|
15005
|
+
"summarize",
|
|
15006
|
+
"classify",
|
|
15007
|
+
"interpret",
|
|
15008
|
+
"predict",
|
|
15009
|
+
"trace",
|
|
15010
|
+
"paraphrase",
|
|
15011
|
+
"gi\u1EA3i th\xEDch",
|
|
15012
|
+
"m\xF4 t\u1EA3",
|
|
15013
|
+
"t\xF3m t\u1EAFt",
|
|
15014
|
+
"ph\xE2n lo\u1EA1i",
|
|
15015
|
+
"di\u1EC5n gi\u1EA3i",
|
|
15016
|
+
"d\u1EF1 \u0111o\xE1n",
|
|
15017
|
+
"l\u1EA7n theo",
|
|
15018
|
+
"hi\u1EC3u"
|
|
15019
|
+
],
|
|
15020
|
+
apply: [
|
|
15021
|
+
"implement",
|
|
15022
|
+
"execute",
|
|
15023
|
+
"calculate",
|
|
15024
|
+
"solve",
|
|
15025
|
+
"construct",
|
|
15026
|
+
"debug",
|
|
15027
|
+
"modify",
|
|
15028
|
+
"build",
|
|
15029
|
+
"\xE1p d\u1EE5ng",
|
|
15030
|
+
"th\u1EF1c thi",
|
|
15031
|
+
"t\xEDnh to\xE1n",
|
|
15032
|
+
"gi\u1EA3i quy\u1EBFt",
|
|
15033
|
+
"x\xE2y d\u1EF1ng",
|
|
15034
|
+
"s\u1EEDa l\u1ED7i",
|
|
15035
|
+
"l\u1EAFp \u0111\u1EB7t",
|
|
15036
|
+
"vi\u1EBFt m\xE3",
|
|
15037
|
+
"l\u1EADp tr\xECnh"
|
|
15038
|
+
],
|
|
15039
|
+
analyze: [
|
|
15040
|
+
"compare",
|
|
15041
|
+
"contrast",
|
|
15042
|
+
"decompose",
|
|
15043
|
+
"differentiate",
|
|
15044
|
+
"troubleshoot",
|
|
15045
|
+
"diagnose",
|
|
15046
|
+
"deconstruct",
|
|
15047
|
+
"so s\xE1nh",
|
|
15048
|
+
"\u0111\u1ED1i chi\u1EBFu",
|
|
15049
|
+
"ph\xE2n t\xEDch",
|
|
15050
|
+
"ph\xE2n r\xE3",
|
|
15051
|
+
"ch\u1EA9n \u0111o\xE1n",
|
|
15052
|
+
"t\xECm nguy\xEAn nh\xE2n g\u1ED1c",
|
|
15053
|
+
"b\xF3c t\xE1ch"
|
|
15054
|
+
],
|
|
15055
|
+
evaluate: [
|
|
15056
|
+
"justify",
|
|
15057
|
+
"critique",
|
|
15058
|
+
"assess",
|
|
15059
|
+
"defend",
|
|
15060
|
+
"argue",
|
|
15061
|
+
"benchmark",
|
|
15062
|
+
"prioritize",
|
|
15063
|
+
"\u0111\xE1nh gi\xE1",
|
|
15064
|
+
"bi\u1EC7n minh",
|
|
15065
|
+
"ph\xEA ph\xE1n",
|
|
15066
|
+
"th\u1EA9m \u0111\u1ECBnh",
|
|
15067
|
+
"b\u1EA3o v\u1EC7 quan \u0111i\u1EC3m",
|
|
15068
|
+
"l\u1EF1a ch\u1ECDn t\u1ED1i \u01B0u"
|
|
15069
|
+
],
|
|
15070
|
+
create: [
|
|
15071
|
+
"design",
|
|
15072
|
+
"synthesize",
|
|
15073
|
+
"architect",
|
|
15074
|
+
"formulate",
|
|
15075
|
+
"invent",
|
|
15076
|
+
"devise",
|
|
15077
|
+
"author",
|
|
15078
|
+
"thi\u1EBFt k\u1EBF",
|
|
15079
|
+
"t\u1ED5ng h\u1EE3p",
|
|
15080
|
+
"s\xE1ng t\u1EA1o",
|
|
15081
|
+
"ki\u1EBFn tr\xFAc",
|
|
15082
|
+
"ph\xE1t minh",
|
|
15083
|
+
"ho\xE0n thi\u1EC7n \u0111\u1ED3 \xE1n"
|
|
15084
|
+
]
|
|
15085
|
+
};
|
|
15086
|
+
var RECALL_PATTERNS = [
|
|
15087
|
+
/^(?:chức năng chính của|định nghĩa của|cú pháp của|lệnh nào là|what is the definition of|which keyword|what does .* stand for)/i,
|
|
15088
|
+
/(?:là gì\?|được gọi là gì\?|có ý nghĩa gì\?)/i
|
|
15089
|
+
];
|
|
15090
|
+
var BloomTaxonomyEvaluator = class {
|
|
15091
|
+
/**
|
|
15092
|
+
* Audits a LessonPlan for Bloom taxonomy fidelity and cognitive scaffolding.
|
|
15093
|
+
*/
|
|
15094
|
+
static evaluateLesson(lesson) {
|
|
15525
15095
|
const findings = [];
|
|
15526
15096
|
const strengths = [];
|
|
15527
15097
|
let score = 100;
|
|
15528
|
-
const
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
15532
|
-
|
|
15533
|
-
|
|
15534
|
-
|
|
15535
|
-
|
|
15536
|
-
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15098
|
+
const los = lesson.learningObjectives || [];
|
|
15099
|
+
if (los.length === 0) {
|
|
15100
|
+
return {
|
|
15101
|
+
score: 0,
|
|
15102
|
+
weight: 0.2,
|
|
15103
|
+
passed: false,
|
|
15104
|
+
strengths: [],
|
|
15105
|
+
findings: [
|
|
15106
|
+
{
|
|
15107
|
+
id: "bloom_missing_los",
|
|
15108
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15109
|
+
severity: "CRITICAL",
|
|
15110
|
+
title: "Missing Learning Objectives",
|
|
15111
|
+
description: "The lesson plan contains zero declared Learning Objectives.",
|
|
15112
|
+
remediationAdvice: "Add 2-4 clearly articulated LOs with Bloom levels and observable success criteria."
|
|
15113
|
+
}
|
|
15114
|
+
]
|
|
15115
|
+
};
|
|
15540
15116
|
}
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
15548
|
-
|
|
15549
|
-
|
|
15550
|
-
|
|
15117
|
+
for (const lo of los) {
|
|
15118
|
+
const declaredLevel = (lo.bloomLevel || "").toLowerCase();
|
|
15119
|
+
const text = `${lo.name || ""} ${lo.description || ""} ${lo.successCriteria || ""}`.toLowerCase();
|
|
15120
|
+
const isRecallPattern = RECALL_PATTERNS.some((p) => p.test(text));
|
|
15121
|
+
if (isRecallPattern && (declaredLevel === "apply" || declaredLevel === "analyze" || declaredLevel === "evaluate")) {
|
|
15122
|
+
score -= 15;
|
|
15123
|
+
findings.push({
|
|
15124
|
+
id: `bloom_inflation_${lo.code}`,
|
|
15125
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15126
|
+
severity: "MAJOR",
|
|
15127
|
+
title: `Bloom Level Inflation in LO "${lo.code}"`,
|
|
15128
|
+
description: `LO is tagged as "${lo.bloomLevel}" but its phrasing reflects low-level Recall ("${lo.description}").`,
|
|
15129
|
+
remediationAdvice: `Rewrite the objective using authentic "${declaredLevel}" active verbs (e.g. build, debug, compare, diagnose) with an explicit application context.`,
|
|
15130
|
+
affectedElement: lo.code
|
|
15131
|
+
});
|
|
15132
|
+
}
|
|
15133
|
+
if (!lo.successCriteria || lo.successCriteria.trim().length < 15) {
|
|
15134
|
+
score -= 10;
|
|
15135
|
+
findings.push({
|
|
15136
|
+
id: `bloom_unmeasurable_criteria_${lo.code}`,
|
|
15137
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15138
|
+
severity: "MINOR",
|
|
15139
|
+
title: `Vague Success Criteria in LO "${lo.code}"`,
|
|
15140
|
+
description: `Success criteria "${lo.successCriteria || ""}" is too brief to provide objective student evidence.`,
|
|
15141
|
+
remediationAdvice: 'Specify concrete, observable evidence (e.g. "LED blinks with 1s period without circuit shorting").',
|
|
15142
|
+
affectedElement: lo.code
|
|
15143
|
+
});
|
|
15144
|
+
} else {
|
|
15145
|
+
strengths.push(`LO "${lo.code}" defines concrete observable student evidence.`);
|
|
15146
|
+
}
|
|
15551
15147
|
}
|
|
15552
|
-
|
|
15553
|
-
|
|
15148
|
+
const diff = lesson.differentiation;
|
|
15149
|
+
if (diff) {
|
|
15150
|
+
if (!diff.bronzeTier || !diff.silverTier || !diff.goldTier) {
|
|
15151
|
+
score -= 15;
|
|
15152
|
+
findings.push({
|
|
15153
|
+
id: "bloom_incomplete_differentiation",
|
|
15154
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15155
|
+
severity: "MAJOR",
|
|
15156
|
+
title: "Incomplete 3-Tier Scaffolding",
|
|
15157
|
+
description: "Differentiation must provide distinct Bronze (Foundation), Silver (Application), and Gold (Extension) challenges.",
|
|
15158
|
+
remediationAdvice: "Define all 3 tiers with increasing cognitive demands (Remember/Understand -> Apply -> Analyze/Create)."
|
|
15159
|
+
});
|
|
15160
|
+
} else {
|
|
15161
|
+
strengths.push("Complete 3-tier scaffolding (Bronze -> Silver -> Gold) present in Lesson Plan.");
|
|
15162
|
+
}
|
|
15163
|
+
} else {
|
|
15164
|
+
score -= 20;
|
|
15554
15165
|
findings.push({
|
|
15555
|
-
id: "
|
|
15556
|
-
dimension: "
|
|
15166
|
+
id: "bloom_missing_differentiation",
|
|
15167
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15557
15168
|
severity: "CRITICAL",
|
|
15558
|
-
title: "
|
|
15559
|
-
description: "Lesson plan
|
|
15560
|
-
remediationAdvice: "Provide
|
|
15169
|
+
title: "Missing Differentiation Scaffolding",
|
|
15170
|
+
description: "Lesson plan lacks differentiation tiers.",
|
|
15171
|
+
remediationAdvice: "Provide tiered practice instructions for varied learner paces."
|
|
15561
15172
|
});
|
|
15562
15173
|
}
|
|
15563
|
-
|
|
15564
|
-
|
|
15174
|
+
score = Math.max(0, Math.min(100, score));
|
|
15175
|
+
return {
|
|
15176
|
+
score,
|
|
15177
|
+
weight: 0.2,
|
|
15178
|
+
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
15179
|
+
strengths,
|
|
15180
|
+
findings
|
|
15181
|
+
};
|
|
15182
|
+
}
|
|
15183
|
+
/**
|
|
15184
|
+
* Audits a Diagnostic Quiz for true cognitive depth matching declared Bloom levels.
|
|
15185
|
+
*/
|
|
15186
|
+
static evaluateQuiz(quiz) {
|
|
15187
|
+
const findings = [];
|
|
15188
|
+
const strengths = [];
|
|
15189
|
+
let score = 100;
|
|
15190
|
+
const questions = quiz.questions || [];
|
|
15191
|
+
if (questions.length < 3) {
|
|
15192
|
+
score -= 30;
|
|
15565
15193
|
findings.push({
|
|
15566
|
-
id: "
|
|
15567
|
-
dimension: "
|
|
15194
|
+
id: "bloom_quiz_too_short",
|
|
15195
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15568
15196
|
severity: "MAJOR",
|
|
15569
|
-
title: "
|
|
15570
|
-
description: `Quiz
|
|
15571
|
-
remediationAdvice:
|
|
15572
|
-
affectedElement: quiz.quizId
|
|
15197
|
+
title: "Insufficient Question Pool",
|
|
15198
|
+
description: `Quiz only contains ${questions.length} questions. Minimum diagnostic threshold is 3 questions.`,
|
|
15199
|
+
remediationAdvice: "Generate at least 3-5 diagnostic questions covering foundational through analytical depth."
|
|
15573
15200
|
});
|
|
15574
15201
|
}
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
|
|
15583
|
-
|
|
15584
|
-
|
|
15585
|
-
|
|
15202
|
+
let understandCount = 0;
|
|
15203
|
+
let applyCount = 0;
|
|
15204
|
+
let analyzeCount = 0;
|
|
15205
|
+
for (let i = 0; i < questions.length; i++) {
|
|
15206
|
+
const q = questions[i];
|
|
15207
|
+
const qId = q.id || `Q${i + 1}`;
|
|
15208
|
+
const declaredBloom = (q.bloomLevel || "").toLowerCase();
|
|
15209
|
+
const stem = q.scenarioOrStem || "";
|
|
15210
|
+
if (declaredBloom === "understand") understandCount++;
|
|
15211
|
+
if (declaredBloom === "apply") applyCount++;
|
|
15212
|
+
if (declaredBloom === "analyze" || declaredBloom === "evaluate") analyzeCount++;
|
|
15213
|
+
const isRecall = RECALL_PATTERNS.some((p) => p.test(stem));
|
|
15214
|
+
if (isRecall && (declaredBloom === "apply" || declaredBloom === "analyze")) {
|
|
15215
|
+
score -= 15;
|
|
15216
|
+
findings.push({
|
|
15217
|
+
id: `bloom_quiz_misclassification_${qId}`,
|
|
15218
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15219
|
+
severity: "MAJOR",
|
|
15220
|
+
title: `Cognitive Level Misclassification in ${qId}`,
|
|
15221
|
+
description: `Question stem "${stem.substring(0, 60)}..." is pure Recall/Definition, but is tagged as "${q.bloomLevel}".`,
|
|
15222
|
+
remediationAdvice: 'Either re-tag as "understand" or transform the stem into an authentic problem-solving scenario requiring code debugging or design decision.',
|
|
15223
|
+
affectedElement: qId
|
|
15224
|
+
});
|
|
15225
|
+
}
|
|
15226
|
+
if ((declaredBloom === "apply" || declaredBloom === "analyze") && !q.codeSnippet && !stem.includes("```") && !stem.includes("m\u1EA1ch")) {
|
|
15227
|
+
score -= 10;
|
|
15228
|
+
findings.push({
|
|
15229
|
+
id: `bloom_quiz_missing_code_context_${qId}`,
|
|
15230
|
+
dimension: "BLOOM_PROGRESSION",
|
|
15231
|
+
severity: "MINOR",
|
|
15232
|
+
title: `Missing Practical Context in High-Bloom Question ${qId}`,
|
|
15233
|
+
description: `Question ${qId} tagged as "${q.bloomLevel}" lacks concrete code or circuit context to evaluate hands-on execution.`,
|
|
15234
|
+
remediationAdvice: "Include an annotated code snippet or circuit state for students to trace, debug, or evaluate.",
|
|
15235
|
+
affectedElement: qId
|
|
15236
|
+
});
|
|
15237
|
+
}
|
|
15586
15238
|
}
|
|
15587
|
-
if (
|
|
15239
|
+
if (questions.length > 0 && (understandCount > 0 || applyCount > 0)) {
|
|
15240
|
+
strengths.push(`Quiz features multi-level cognitive questions (Understand: ${understandCount}, Apply: ${applyCount}, Analyze: ${analyzeCount}).`);
|
|
15241
|
+
}
|
|
15242
|
+
score = Math.max(0, Math.min(100, score));
|
|
15243
|
+
return {
|
|
15244
|
+
score,
|
|
15245
|
+
weight: 0.2,
|
|
15246
|
+
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
15247
|
+
strengths,
|
|
15248
|
+
findings
|
|
15249
|
+
};
|
|
15250
|
+
}
|
|
15251
|
+
};
|
|
15252
|
+
|
|
15253
|
+
// src/evaluators/crossArtifactDriftEvaluator.ts
|
|
15254
|
+
var CrossArtifactDriftEvaluator = class {
|
|
15255
|
+
/**
|
|
15256
|
+
* Audits consistency, metadata synchronization, and zero-drift across all artifacts in a lesson bundle.
|
|
15257
|
+
*/
|
|
15258
|
+
static evaluateBundle(lesson, quiz, activity, slides) {
|
|
15259
|
+
const findings = [];
|
|
15260
|
+
const strengths = [];
|
|
15261
|
+
let score = 100;
|
|
15262
|
+
const baseLessonId = lesson.lessonId;
|
|
15263
|
+
const baseLanguage = lesson.language;
|
|
15264
|
+
if (quiz && quiz.quizId && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
|
|
15588
15265
|
score -= 15;
|
|
15589
15266
|
findings.push({
|
|
15590
|
-
id: "
|
|
15267
|
+
id: "drift_quiz_id_mismatch",
|
|
15591
15268
|
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15592
15269
|
severity: "MAJOR",
|
|
15593
|
-
title: "
|
|
15594
|
-
description: `
|
|
15595
|
-
remediationAdvice: `
|
|
15270
|
+
title: "Quiz ID Mismatch with Master Lesson",
|
|
15271
|
+
description: `Quiz ID "${quiz.quizId}" diverges from master Lesson ID "${baseLessonId}".`,
|
|
15272
|
+
remediationAdvice: `Align Quiz ID to "${baseLessonId}".`,
|
|
15273
|
+
affectedElement: quiz.quizId
|
|
15274
|
+
});
|
|
15275
|
+
}
|
|
15276
|
+
if (activity && activity.lessonId && activity.lessonId !== baseLessonId) {
|
|
15277
|
+
score -= 15;
|
|
15278
|
+
findings.push({
|
|
15279
|
+
id: "drift_act_id_mismatch",
|
|
15280
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15281
|
+
severity: "MAJOR",
|
|
15282
|
+
title: "Activity Lesson ID Mismatch",
|
|
15283
|
+
description: `Activity lessonId "${activity.lessonId}" diverges from master Lesson ID "${baseLessonId}".`,
|
|
15284
|
+
remediationAdvice: `Align Activity lessonId to "${baseLessonId}".`,
|
|
15285
|
+
affectedElement: activity.lessonId
|
|
15286
|
+
});
|
|
15287
|
+
}
|
|
15288
|
+
if (slides && slides.lessonId && slides.lessonId !== baseLessonId) {
|
|
15289
|
+
score -= 15;
|
|
15290
|
+
findings.push({
|
|
15291
|
+
id: "drift_slides_id_mismatch",
|
|
15292
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15293
|
+
severity: "MAJOR",
|
|
15294
|
+
title: "Slide Deck Lesson ID Mismatch",
|
|
15295
|
+
description: `Slide deck lessonId "${slides.lessonId}" diverges from master Lesson ID "${baseLessonId}".`,
|
|
15296
|
+
remediationAdvice: `Align Slide deck lessonId to "${baseLessonId}".`,
|
|
15596
15297
|
affectedElement: slides.lessonId
|
|
15597
15298
|
});
|
|
15598
15299
|
}
|
|
@@ -15605,589 +15306,377 @@ var DeterministicStructuralLinter = class {
|
|
|
15605
15306
|
if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
|
|
15606
15307
|
score -= 25;
|
|
15607
15308
|
findings.push({
|
|
15608
|
-
id: `
|
|
15309
|
+
id: `drift_language_mismatch_${sat.type}`,
|
|
15609
15310
|
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15610
15311
|
severity: "CRITICAL",
|
|
15611
|
-
title: `Language
|
|
15312
|
+
title: `Language Contamination in ${sat.type}`,
|
|
15612
15313
|
description: `Master Lesson is in "${baseLanguage}" but ${sat.type} is declared in "${sat.lang}".`,
|
|
15613
|
-
remediationAdvice: `
|
|
15314
|
+
remediationAdvice: `Regenerate ${sat.type} strictly in "${baseLanguage}".`
|
|
15614
15315
|
});
|
|
15615
15316
|
}
|
|
15616
15317
|
}
|
|
15617
|
-
|
|
15618
|
-
|
|
15619
|
-
|
|
15620
|
-
|
|
15621
|
-
|
|
15622
|
-
|
|
15623
|
-
|
|
15624
|
-
|
|
15625
|
-
|
|
15626
|
-
|
|
15627
|
-
|
|
15628
|
-
|
|
15629
|
-
|
|
15630
|
-
|
|
15631
|
-
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15635
|
-
|
|
15318
|
+
const sections = lesson.sections || [];
|
|
15319
|
+
const totalSectionMins = sections.reduce((sum, s) => sum + (s.durationMinutes || 0), 0);
|
|
15320
|
+
if (totalSectionMins > 0 && (totalSectionMins < 40 || totalSectionMins > 180)) {
|
|
15321
|
+
score -= 10;
|
|
15322
|
+
findings.push({
|
|
15323
|
+
id: "drift_unrealistic_lesson_duration",
|
|
15324
|
+
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
15325
|
+
severity: "MINOR",
|
|
15326
|
+
title: `Unrealistic Lesson Flow Total Duration (${totalSectionMins} mins)`,
|
|
15327
|
+
description: `Sum of 5E section durations is ${totalSectionMins} minutes. Standard K-12/College lessons span 45-120 minutes.`,
|
|
15328
|
+
remediationAdvice: "Adjust individual section timings so total duration matches standard classroom blocks (e.g. 60m or 90m)."
|
|
15329
|
+
});
|
|
15330
|
+
} else if (totalSectionMins > 0) {
|
|
15331
|
+
strengths.push(`Lesson section timings sum up to a realistic classroom block (${totalSectionMins} mins).`);
|
|
15332
|
+
}
|
|
15333
|
+
if (score >= 90) {
|
|
15334
|
+
strengths.push("Zero drift verified: IDs, language policies, and pedagogical contracts are strictly aligned across all artifacts.");
|
|
15335
|
+
}
|
|
15336
|
+
score = Math.max(0, Math.min(100, score));
|
|
15337
|
+
return {
|
|
15338
|
+
score,
|
|
15339
|
+
weight: 0.1,
|
|
15340
|
+
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
15341
|
+
strengths,
|
|
15342
|
+
findings
|
|
15343
|
+
};
|
|
15344
|
+
}
|
|
15345
|
+
};
|
|
15346
|
+
|
|
15347
|
+
// src/evaluators/constructiveAlignmentEvaluator.ts
|
|
15348
|
+
var ConstructiveAlignmentEvaluator = class {
|
|
15349
|
+
/**
|
|
15350
|
+
* Evaluates constructive alignment within a LessonPlan and across its satellite artifacts.
|
|
15351
|
+
*/
|
|
15352
|
+
static evaluateLesson(lesson, quiz, activity) {
|
|
15353
|
+
const findings = [];
|
|
15354
|
+
const strengths = [];
|
|
15355
|
+
let score = 100;
|
|
15356
|
+
const los = lesson.learningObjectives || [];
|
|
15357
|
+
const sections = lesson.sections || [];
|
|
15358
|
+
const exitTicket = lesson.exitTicket;
|
|
15359
|
+
if (los.length === 0) {
|
|
15360
|
+
return {
|
|
15361
|
+
score: 0,
|
|
15362
|
+
weight: 0.2,
|
|
15363
|
+
passed: false,
|
|
15364
|
+
strengths: [],
|
|
15365
|
+
findings: [
|
|
15366
|
+
{
|
|
15367
|
+
id: "align_no_los",
|
|
15368
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15369
|
+
severity: "CRITICAL",
|
|
15370
|
+
title: "No Learning Objectives Defined",
|
|
15371
|
+
description: "Constructive alignment cannot be established without baseline LOs.",
|
|
15372
|
+
remediationAdvice: "Define at least 2 measurable Learning Objectives."
|
|
15373
|
+
}
|
|
15374
|
+
]
|
|
15375
|
+
};
|
|
15376
|
+
}
|
|
15377
|
+
const combinedSectionText = sections.map((s) => `${s.title} ${s.teacherActions} ${s.studentActions} ${(s.analogies || []).join(" ")}`).join(" ").toLowerCase();
|
|
15378
|
+
for (const lo of los) {
|
|
15379
|
+
const loKeywords = (lo.name || lo.description || "").toLowerCase().split(/\s+/).filter((w) => w.length > 4);
|
|
15380
|
+
const hasCoverage = loKeywords.some((k) => combinedSectionText.includes(k));
|
|
15381
|
+
if (!hasCoverage && loKeywords.length > 0) {
|
|
15382
|
+
score -= 15;
|
|
15383
|
+
findings.push({
|
|
15384
|
+
id: `align_uncovered_lo_${lo.code}`,
|
|
15385
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15386
|
+
severity: "MAJOR",
|
|
15387
|
+
title: `Untaught Objective in Lesson Flow: "${lo.code}"`,
|
|
15388
|
+
description: `The LO "${lo.name || lo.code}" is declared in the objectives table but receives minimal coverage in the 5E lesson sections.`,
|
|
15389
|
+
remediationAdvice: `Add explicit Teacher Moves and Student Actions in the Explore/Explain sections addressing "${lo.name}".`,
|
|
15390
|
+
affectedElement: lo.code
|
|
15391
|
+
});
|
|
15392
|
+
}
|
|
15393
|
+
}
|
|
15394
|
+
if (!exitTicket || !exitTicket.questionStem || exitTicket.questionStem.trim().length < 15) {
|
|
15395
|
+
score -= 15;
|
|
15396
|
+
findings.push({
|
|
15397
|
+
id: "align_missing_exit_ticket",
|
|
15398
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15399
|
+
severity: "MAJOR",
|
|
15400
|
+
title: "Missing or Shallow Exit Ticket",
|
|
15401
|
+
description: "The lesson ends without a rigorous formative Exit Ticket to measure LO attainment before class dismissal.",
|
|
15402
|
+
remediationAdvice: "Include an Exit Ticket question matching the primary LO + a metacognitive reflection prompt."
|
|
15403
|
+
});
|
|
15404
|
+
} else {
|
|
15405
|
+
strengths.push("Formative Exit Ticket is properly anchored at the conclusion of the lesson.");
|
|
15406
|
+
}
|
|
15407
|
+
if (quiz) {
|
|
15408
|
+
const quizQuestions = quiz.questions || [];
|
|
15409
|
+
if (quizQuestions.length > 0) {
|
|
15410
|
+
const quizText = quizQuestions.map((q) => `${q.scenarioOrStem} ${(q.options || []).map((o) => o.text).join(" ")}`).join(" ").toLowerCase();
|
|
15411
|
+
const lessonTopicKeywords = (lesson.topic || lesson.title || "").toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
15412
|
+
const hasTopicMatch = lessonTopicKeywords.some((k) => quizText.includes(k));
|
|
15413
|
+
if (!hasTopicMatch && lessonTopicKeywords.length > 0) {
|
|
15636
15414
|
score -= 20;
|
|
15637
15415
|
findings.push({
|
|
15638
|
-
id:
|
|
15639
|
-
dimension: "
|
|
15416
|
+
id: "align_quiz_topic_divergence",
|
|
15417
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15640
15418
|
severity: "CRITICAL",
|
|
15641
|
-
title:
|
|
15642
|
-
description: `
|
|
15643
|
-
remediationAdvice: "
|
|
15644
|
-
affectedElement: qId
|
|
15419
|
+
title: "Quiz Topic Divergence from Lesson",
|
|
15420
|
+
description: `Quiz questions do not reflect the main topic "${lesson.topic}" of the master lesson.`,
|
|
15421
|
+
remediationAdvice: "Re-align quiz questions strictly to the core concepts taught in the lesson."
|
|
15645
15422
|
});
|
|
15423
|
+
} else {
|
|
15424
|
+
strengths.push("Diagnostic Quiz questions tightly reflect the lesson topic and concepts.");
|
|
15646
15425
|
}
|
|
15647
15426
|
}
|
|
15648
15427
|
}
|
|
15649
|
-
if (activity
|
|
15650
|
-
const
|
|
15651
|
-
const
|
|
15652
|
-
const
|
|
15653
|
-
if (
|
|
15654
|
-
score -=
|
|
15428
|
+
if (activity) {
|
|
15429
|
+
const actObj = (activity.objective || "").toLowerCase();
|
|
15430
|
+
const lessonTitleLower = (lesson.title || "").toLowerCase();
|
|
15431
|
+
const hasActAlignment = los.some((lo) => actObj.includes((lo.name || "").toLowerCase()) || actObj.includes((lo.description || "").toLowerCase())) || actObj.includes(lessonTitleLower) || lessonTitleLower.split(/\s+/).some((w) => w.length > 4 && actObj.includes(w));
|
|
15432
|
+
if (!hasActAlignment && activity.objective) {
|
|
15433
|
+
score -= 15;
|
|
15655
15434
|
findings.push({
|
|
15656
|
-
id: "
|
|
15657
|
-
dimension: "
|
|
15658
|
-
severity: "
|
|
15659
|
-
title: "
|
|
15660
|
-
description:
|
|
15661
|
-
remediationAdvice: "
|
|
15435
|
+
id: "align_act_objective_divergence",
|
|
15436
|
+
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15437
|
+
severity: "MAJOR",
|
|
15438
|
+
title: "Activity Objective Disconnected from Lesson Plan",
|
|
15439
|
+
description: `Activity objective "${activity.objective}" does not directly support the lesson LOs.`,
|
|
15440
|
+
remediationAdvice: "Ensure the Activity hands-on lab operationalizes the exact LOs specified in the Master Lesson."
|
|
15662
15441
|
});
|
|
15442
|
+
} else {
|
|
15443
|
+
strengths.push("Activity lab objective directly operationalizes master lesson learning goals.");
|
|
15663
15444
|
}
|
|
15664
15445
|
}
|
|
15665
15446
|
score = Math.max(0, Math.min(100, score));
|
|
15666
|
-
const passed = score >= 80 && !findings.some((f) => f.severity === "CRITICAL");
|
|
15667
|
-
if (passed) {
|
|
15668
|
-
strengths.push("Deterministic structure, ID contracts, and schema invariants strictly verified.");
|
|
15669
|
-
}
|
|
15670
15447
|
return {
|
|
15671
|
-
|
|
15672
|
-
|
|
15673
|
-
findings,
|
|
15674
|
-
strengths
|
|
15448
|
+
score,
|
|
15449
|
+
weight: 0.2,
|
|
15450
|
+
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
15451
|
+
strengths,
|
|
15452
|
+
findings
|
|
15675
15453
|
};
|
|
15676
15454
|
}
|
|
15677
15455
|
};
|
|
15678
15456
|
|
|
15679
|
-
// src/evaluators/
|
|
15680
|
-
var
|
|
15457
|
+
// src/evaluators/fiveEInstructionalEvaluator.ts
|
|
15458
|
+
var FIVE_E_STAGES = [
|
|
15459
|
+
{ key: "engage", label: "Engage (Kh\u1EDFi \u0111\u1ED9ng / M\xF3c neo)", regex: /(?:engage|khởi động|hook|mở đầu|anchor)/i },
|
|
15460
|
+
{ key: "explore", label: "Explore (Kh\xE1m ph\xE1 / Tr\u1EA3i nghi\u1EC7m)", regex: /(?:explore|khám phá|trải nghiệm|thử nghiệm)/i },
|
|
15461
|
+
{ key: "explain", label: "Explain (Gi\u1EA3i th\xEDch / H\xECnh th\xE0nh ki\u1EBFn th\u1EE9c)", regex: /(?:explain|giải thích|khái niệm|kiến thức cốt lõi)/i },
|
|
15462
|
+
{ key: "elaborate", label: "Elaborate (V\u1EADn d\u1EE5ng / M\u1EDF r\u1ED9ng)", regex: /(?:elaborate|vận dụng|luyện tập|thực hành|áp dụng)/i },
|
|
15463
|
+
{ key: "evaluate", label: "Evaluate (\u0110\xE1nh gi\xE1 / T\u1ED5ng k\u1EBFt)", regex: /(?:evaluate|đánh giá|tổng kết|wrap-up|exit ticket)/i }
|
|
15464
|
+
];
|
|
15465
|
+
var FiveEInstructionalEvaluator = class {
|
|
15681
15466
|
/**
|
|
15682
|
-
* Evaluates a
|
|
15683
|
-
* Step 1 (Deterministic): Fast structural, contract, schema, and safety linting.
|
|
15684
|
-
* Step 2 (Semantic LLM-as-a-Judge): Deep pedagogical, cognitive (Bloom), and misconception analysis with live frontier LLM.
|
|
15467
|
+
* Evaluates the pedagogical structure of a Lesson Plan against the 5E Inquiry Model.
|
|
15685
15468
|
*/
|
|
15686
|
-
static
|
|
15687
|
-
const
|
|
15688
|
-
const
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
|
|
15707
|
-
|
|
15708
|
-
|
|
15709
|
-
|
|
15710
|
-
|
|
15711
|
-
|
|
15712
|
-
|
|
15713
|
-
|
|
15714
|
-
|
|
15715
|
-
|
|
15716
|
-
|
|
15717
|
-
|
|
15718
|
-
|
|
15719
|
-
|
|
15720
|
-
severity: criterion.score < 50 ? "CRITICAL" : "MAJOR",
|
|
15721
|
-
title: `LLM-as-Judge Finding: ${criterion.name}`,
|
|
15722
|
-
description: criterion.feedback,
|
|
15723
|
-
remediationAdvice: judgeReport.actionableRepairPrompts.join("; ") || "Refine prompt context."
|
|
15724
|
-
});
|
|
15725
|
-
} else {
|
|
15726
|
-
strengths.push(`[LLM-Judge] ${criterion.name}: ${criterion.feedback}`);
|
|
15727
|
-
}
|
|
15469
|
+
static evaluateLesson(lesson) {
|
|
15470
|
+
const findings = [];
|
|
15471
|
+
const strengths = [];
|
|
15472
|
+
let score = 100;
|
|
15473
|
+
if (!lesson.hookScenario || lesson.hookScenario.trim().length < 30) {
|
|
15474
|
+
score -= 20;
|
|
15475
|
+
findings.push({
|
|
15476
|
+
id: "5e_weak_hook",
|
|
15477
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
15478
|
+
severity: "MAJOR",
|
|
15479
|
+
title: "Shallow or Missing Real-World Hook",
|
|
15480
|
+
description: "The lesson lacks a compelling, high-stakes real-world scenario to trigger inquiry.",
|
|
15481
|
+
remediationAdvice: "Frame the lesson around an authentic engineering or domain problem (e.g. server crash, sensor failure, clinical anomaly)."
|
|
15482
|
+
});
|
|
15483
|
+
} else {
|
|
15484
|
+
strengths.push("Engaging real-world hook scenario sets high-stakes context for inquiry.");
|
|
15485
|
+
}
|
|
15486
|
+
if (!lesson.hookQuestions || lesson.hookQuestions.length < 2) {
|
|
15487
|
+
score -= 10;
|
|
15488
|
+
findings.push({
|
|
15489
|
+
id: "5e_insufficient_hook_questions",
|
|
15490
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
15491
|
+
severity: "MINOR",
|
|
15492
|
+
title: "Insufficient Inquiry Questions in Hook",
|
|
15493
|
+
description: "Need at least 2 open-ended inquiry questions in the Engage phase to activate prior mental models.",
|
|
15494
|
+
remediationAdvice: "Add 2-3 provocative questions challenging common student assumptions."
|
|
15495
|
+
});
|
|
15496
|
+
}
|
|
15497
|
+
const sections = lesson.sections || [];
|
|
15498
|
+
const coveredStages = /* @__PURE__ */ new Set();
|
|
15499
|
+
for (const section of sections) {
|
|
15500
|
+
for (const stage of FIVE_E_STAGES) {
|
|
15501
|
+
if (stage.regex.test(section.title)) {
|
|
15502
|
+
coveredStages.add(stage.key);
|
|
15728
15503
|
}
|
|
15729
|
-
} catch (err) {
|
|
15730
|
-
semanticScore = null;
|
|
15731
|
-
semanticVerdict = "FAIL";
|
|
15732
|
-
allFindings.push({
|
|
15733
|
-
id: "llm_judge_connection_error",
|
|
15734
|
-
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
15735
|
-
severity: "MINOR",
|
|
15736
|
-
title: "LLM-as-Judge Skipped / Fallback",
|
|
15737
|
-
description: `Semantic inference error: ${err.message || String(err)}`,
|
|
15738
|
-
remediationAdvice: "Check API credentials for LLM-as-Judge."
|
|
15739
|
-
});
|
|
15740
15504
|
}
|
|
15741
15505
|
}
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
const
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
|
|
15506
|
+
if (lesson.hookScenario) coveredStages.add("engage");
|
|
15507
|
+
if (lesson.exitTicket) coveredStages.add("evaluate");
|
|
15508
|
+
const missingStages = FIVE_E_STAGES.filter((s) => !coveredStages.has(s.key));
|
|
15509
|
+
if (missingStages.length > 0) {
|
|
15510
|
+
score -= missingStages.length * 10;
|
|
15511
|
+
findings.push({
|
|
15512
|
+
id: "5e_missing_phases",
|
|
15513
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
15514
|
+
severity: missingStages.length > 2 ? "CRITICAL" : "MAJOR",
|
|
15515
|
+
title: `Incomplete 5E Instructional Cycle (Missing ${missingStages.length} phase(s))`,
|
|
15516
|
+
description: `Lesson plan does not clearly demarcate: ${missingStages.map((s) => s.label).join(", ")}.`,
|
|
15517
|
+
remediationAdvice: "Ensure the lesson explicitly walks through Engage -> Explore -> Explain -> Elaborate -> Evaluate."
|
|
15518
|
+
});
|
|
15519
|
+
} else {
|
|
15520
|
+
strengths.push("Full 5E Instructional Cycle (Engage, Explore, Explain, Elaborate, Evaluate) is completely covered.");
|
|
15752
15521
|
}
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
}
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
);
|
|
15522
|
+
let passiveSections = 0;
|
|
15523
|
+
for (let i = 0; i < sections.length; i++) {
|
|
15524
|
+
const s = sections[i];
|
|
15525
|
+
const studentAct = (s.studentActions || "").trim().toLowerCase();
|
|
15526
|
+
if (studentAct.includes("nghe gi\u1EA3ng") || studentAct.includes("ch\xE9p b\xE0i") || studentAct.includes("listen passively") || studentAct.length < 10) {
|
|
15527
|
+
passiveSections++;
|
|
15528
|
+
}
|
|
15529
|
+
}
|
|
15530
|
+
if (passiveSections > 0 && sections.length > 0) {
|
|
15531
|
+
score -= passiveSections * 8;
|
|
15532
|
+
findings.push({
|
|
15533
|
+
id: "5e_passive_student_roles",
|
|
15534
|
+
dimension: "5E_INSTRUCTIONAL_FIDELITY",
|
|
15535
|
+
severity: "MAJOR",
|
|
15536
|
+
title: "Passive Student Roles Detected in Lesson Sections",
|
|
15537
|
+
description: `${passiveSections} section(s) assign passive roles (listening/copying) to students rather than active inquiry, pair-discussion, or hands-on experimentation.`,
|
|
15538
|
+
remediationAdvice: "Transform student actions into active tasks (e.g. Think-Pair-Share, code tracing, hypothesis testing, live bug hunting)."
|
|
15539
|
+
});
|
|
15540
|
+
} else if (sections.length > 0) {
|
|
15541
|
+
strengths.push("Student roles emphasize active learning and hands-on participation throughout.");
|
|
15542
|
+
}
|
|
15543
|
+
score = Math.max(0, Math.min(100, score));
|
|
15775
15544
|
return {
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
verdict,
|
|
15782
|
-
summary,
|
|
15783
|
-
dimensionScores: {
|
|
15784
|
-
constructiveAlignment: {
|
|
15785
|
-
score: dimScores.constructiveAlignment.score,
|
|
15786
|
-
weight: 0.2,
|
|
15787
|
-
passed: dimScores.constructiveAlignment.passed,
|
|
15788
|
-
strengths: dimScores.constructiveAlignment.passed ? ["Constructive alignment verified."] : [],
|
|
15789
|
-
findings: dimFindings.constructiveAlignment
|
|
15790
|
-
},
|
|
15791
|
-
bloomProgression: {
|
|
15792
|
-
score: dimScores.bloomProgression.score,
|
|
15793
|
-
weight: 0.2,
|
|
15794
|
-
passed: dimScores.bloomProgression.passed,
|
|
15795
|
-
strengths: dimScores.bloomProgression.passed ? ["Bloom taxonomy progression verified."] : [],
|
|
15796
|
-
findings: dimFindings.bloomProgression
|
|
15797
|
-
},
|
|
15798
|
-
fiveEFidelity: {
|
|
15799
|
-
score: dimScores.fiveEFidelity.score,
|
|
15800
|
-
weight: 0.15,
|
|
15801
|
-
passed: dimScores.fiveEFidelity.passed,
|
|
15802
|
-
strengths: dimScores.fiveEFidelity.passed ? ["5E instructional fidelity verified."] : [],
|
|
15803
|
-
findings: dimFindings.fiveEFidelity
|
|
15804
|
-
},
|
|
15805
|
-
misconceptionRigor: {
|
|
15806
|
-
score: dimScores.misconceptionRigor.score,
|
|
15807
|
-
weight: 0.2,
|
|
15808
|
-
passed: dimScores.misconceptionRigor.passed,
|
|
15809
|
-
strengths: dimScores.misconceptionRigor.passed ? ["Misconception rigor verified."] : [],
|
|
15810
|
-
findings: dimFindings.misconceptionRigor
|
|
15811
|
-
},
|
|
15812
|
-
technicalAuthenticity: {
|
|
15813
|
-
score: dimScores.technicalAuthenticity.score,
|
|
15814
|
-
weight: 0.15,
|
|
15815
|
-
passed: dimScores.technicalAuthenticity.passed,
|
|
15816
|
-
strengths: dimScores.technicalAuthenticity.passed ? ["Technical authenticity verified."] : [],
|
|
15817
|
-
findings: dimFindings.technicalAuthenticity
|
|
15818
|
-
},
|
|
15819
|
-
crossArtifactZeroDrift: {
|
|
15820
|
-
score: structuralResult.structuralScore,
|
|
15821
|
-
weight: 0.1,
|
|
15822
|
-
passed: structuralResult.passed,
|
|
15823
|
-
strengths: structuralResult.passed ? ["Cross-artifact zero drift verified."] : [],
|
|
15824
|
-
findings: allFindings.filter((f) => f.dimension === "CROSS_ARTIFACT_ZERO_DRIFT")
|
|
15825
|
-
}
|
|
15826
|
-
},
|
|
15827
|
-
criticalFindingsCount: criticalCount,
|
|
15828
|
-
allFindings,
|
|
15829
|
-
actionablePromptGuidance
|
|
15545
|
+
score,
|
|
15546
|
+
weight: 0.15,
|
|
15547
|
+
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
15548
|
+
strengths,
|
|
15549
|
+
findings
|
|
15830
15550
|
};
|
|
15831
15551
|
}
|
|
15832
15552
|
};
|
|
15833
15553
|
|
|
15834
|
-
// src/evaluators/
|
|
15835
|
-
var
|
|
15836
|
-
|
|
15837
|
-
|
|
15838
|
-
|
|
15839
|
-
|
|
15840
|
-
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
"recognize",
|
|
15845
|
-
"li\u1EC7t k\xEA",
|
|
15846
|
-
"\u0111\u1ECBnh ngh\u0129a",
|
|
15847
|
-
"g\u1ECDi t\xEAn",
|
|
15848
|
-
"nh\u1EADn di\u1EC7n",
|
|
15849
|
-
"ch\u1EC9 ra",
|
|
15850
|
-
"nh\u1EAFc l\u1EA1i",
|
|
15851
|
-
"ghi nh\u1EDB"
|
|
15852
|
-
],
|
|
15853
|
-
understand: [
|
|
15854
|
-
"explain",
|
|
15855
|
-
"describe",
|
|
15856
|
-
"summarize",
|
|
15857
|
-
"classify",
|
|
15858
|
-
"interpret",
|
|
15859
|
-
"predict",
|
|
15860
|
-
"trace",
|
|
15861
|
-
"paraphrase",
|
|
15862
|
-
"gi\u1EA3i th\xEDch",
|
|
15863
|
-
"m\xF4 t\u1EA3",
|
|
15864
|
-
"t\xF3m t\u1EAFt",
|
|
15865
|
-
"ph\xE2n lo\u1EA1i",
|
|
15866
|
-
"di\u1EC5n gi\u1EA3i",
|
|
15867
|
-
"d\u1EF1 \u0111o\xE1n",
|
|
15868
|
-
"l\u1EA7n theo",
|
|
15869
|
-
"hi\u1EC3u"
|
|
15870
|
-
],
|
|
15871
|
-
apply: [
|
|
15872
|
-
"implement",
|
|
15873
|
-
"execute",
|
|
15874
|
-
"calculate",
|
|
15875
|
-
"solve",
|
|
15876
|
-
"construct",
|
|
15877
|
-
"debug",
|
|
15878
|
-
"modify",
|
|
15879
|
-
"build",
|
|
15880
|
-
"\xE1p d\u1EE5ng",
|
|
15881
|
-
"th\u1EF1c thi",
|
|
15882
|
-
"t\xEDnh to\xE1n",
|
|
15883
|
-
"gi\u1EA3i quy\u1EBFt",
|
|
15884
|
-
"x\xE2y d\u1EF1ng",
|
|
15885
|
-
"s\u1EEDa l\u1ED7i",
|
|
15886
|
-
"l\u1EAFp \u0111\u1EB7t",
|
|
15887
|
-
"vi\u1EBFt m\xE3",
|
|
15888
|
-
"l\u1EADp tr\xECnh"
|
|
15889
|
-
],
|
|
15890
|
-
analyze: [
|
|
15891
|
-
"compare",
|
|
15892
|
-
"contrast",
|
|
15893
|
-
"decompose",
|
|
15894
|
-
"differentiate",
|
|
15895
|
-
"troubleshoot",
|
|
15896
|
-
"diagnose",
|
|
15897
|
-
"deconstruct",
|
|
15898
|
-
"so s\xE1nh",
|
|
15899
|
-
"\u0111\u1ED1i chi\u1EBFu",
|
|
15900
|
-
"ph\xE2n t\xEDch",
|
|
15901
|
-
"ph\xE2n r\xE3",
|
|
15902
|
-
"ch\u1EA9n \u0111o\xE1n",
|
|
15903
|
-
"t\xECm nguy\xEAn nh\xE2n g\u1ED1c",
|
|
15904
|
-
"b\xF3c t\xE1ch"
|
|
15905
|
-
],
|
|
15906
|
-
evaluate: [
|
|
15907
|
-
"justify",
|
|
15908
|
-
"critique",
|
|
15909
|
-
"assess",
|
|
15910
|
-
"defend",
|
|
15911
|
-
"argue",
|
|
15912
|
-
"benchmark",
|
|
15913
|
-
"prioritize",
|
|
15914
|
-
"\u0111\xE1nh gi\xE1",
|
|
15915
|
-
"bi\u1EC7n minh",
|
|
15916
|
-
"ph\xEA ph\xE1n",
|
|
15917
|
-
"th\u1EA9m \u0111\u1ECBnh",
|
|
15918
|
-
"b\u1EA3o v\u1EC7 quan \u0111i\u1EC3m",
|
|
15919
|
-
"l\u1EF1a ch\u1ECDn t\u1ED1i \u01B0u"
|
|
15920
|
-
],
|
|
15921
|
-
create: [
|
|
15922
|
-
"design",
|
|
15923
|
-
"synthesize",
|
|
15924
|
-
"architect",
|
|
15925
|
-
"formulate",
|
|
15926
|
-
"invent",
|
|
15927
|
-
"devise",
|
|
15928
|
-
"author",
|
|
15929
|
-
"thi\u1EBFt k\u1EBF",
|
|
15930
|
-
"t\u1ED5ng h\u1EE3p",
|
|
15931
|
-
"s\xE1ng t\u1EA1o",
|
|
15932
|
-
"ki\u1EBFn tr\xFAc",
|
|
15933
|
-
"ph\xE1t minh",
|
|
15934
|
-
"ho\xE0n thi\u1EC7n \u0111\u1ED3 \xE1n"
|
|
15935
|
-
]
|
|
15936
|
-
};
|
|
15937
|
-
var RECALL_PATTERNS = [
|
|
15938
|
-
/^(?:chức năng chính của|định nghĩa của|cú pháp của|lệnh nào là|what is the definition of|which keyword|what does .* stand for)/i,
|
|
15939
|
-
/(?:là gì\?|được gọi là gì\?|có ý nghĩa gì\?)/i
|
|
15554
|
+
// src/evaluators/codeHardwareFeasibilityEvaluator.ts
|
|
15555
|
+
var VALID_MERMAID_STARTERS = [
|
|
15556
|
+
"graph",
|
|
15557
|
+
"flowchart",
|
|
15558
|
+
"sequencediagram",
|
|
15559
|
+
"statediagram",
|
|
15560
|
+
"classdiagram",
|
|
15561
|
+
"erdiagram",
|
|
15562
|
+
"gantt",
|
|
15563
|
+
"gitgraph"
|
|
15940
15564
|
];
|
|
15941
|
-
var
|
|
15565
|
+
var CodeHardwareFeasibilityEvaluator = class {
|
|
15942
15566
|
/**
|
|
15943
|
-
* Audits
|
|
15567
|
+
* Audits technical accuracy, code syntax sanity, and hardware circuit safety.
|
|
15944
15568
|
*/
|
|
15945
|
-
static
|
|
15569
|
+
static evaluateTechnicalFeasibility(lesson, codeLab, activity) {
|
|
15946
15570
|
const findings = [];
|
|
15947
15571
|
const strengths = [];
|
|
15948
15572
|
let score = 100;
|
|
15949
|
-
|
|
15950
|
-
|
|
15951
|
-
|
|
15952
|
-
score
|
|
15953
|
-
weight: 0.2,
|
|
15954
|
-
passed: false,
|
|
15955
|
-
strengths: [],
|
|
15956
|
-
findings: [
|
|
15957
|
-
{
|
|
15958
|
-
id: "bloom_missing_los",
|
|
15959
|
-
dimension: "BLOOM_PROGRESSION",
|
|
15960
|
-
severity: "CRITICAL",
|
|
15961
|
-
title: "Missing Learning Objectives",
|
|
15962
|
-
description: "The lesson plan contains zero declared Learning Objectives.",
|
|
15963
|
-
remediationAdvice: "Add 2-4 clearly articulated LOs with Bloom levels and observable success criteria."
|
|
15964
|
-
}
|
|
15965
|
-
]
|
|
15966
|
-
};
|
|
15967
|
-
}
|
|
15968
|
-
for (const lo of los) {
|
|
15969
|
-
const declaredLevel = (lo.bloomLevel || "").toLowerCase();
|
|
15970
|
-
const text = `${lo.name || ""} ${lo.description || ""} ${lo.successCriteria || ""}`.toLowerCase();
|
|
15971
|
-
const isRecallPattern = RECALL_PATTERNS.some((p) => p.test(text));
|
|
15972
|
-
if (isRecallPattern && (declaredLevel === "apply" || declaredLevel === "analyze" || declaredLevel === "evaluate")) {
|
|
15973
|
-
score -= 15;
|
|
15573
|
+
if (lesson?.guidedPractice) {
|
|
15574
|
+
const { codeSnippet, codeLanguage, mermaidDiagram } = lesson.guidedPractice;
|
|
15575
|
+
if (!codeSnippet || codeSnippet.trim().length < 15) {
|
|
15576
|
+
score -= 20;
|
|
15974
15577
|
findings.push({
|
|
15975
|
-
id:
|
|
15976
|
-
dimension: "
|
|
15578
|
+
id: "tech_empty_guided_code",
|
|
15579
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
15977
15580
|
severity: "MAJOR",
|
|
15978
|
-
title:
|
|
15979
|
-
description:
|
|
15980
|
-
remediationAdvice:
|
|
15981
|
-
affectedElement: lo.code
|
|
15581
|
+
title: "Empty or Trivial Guided Practice Code",
|
|
15582
|
+
description: "Guided practice lacks runnable code snippet or domain calculation template.",
|
|
15583
|
+
remediationAdvice: "Provide a complete, runnable code example with explanatory line-by-line comments."
|
|
15982
15584
|
});
|
|
15585
|
+
} else {
|
|
15586
|
+
if (codeLanguage?.toLowerCase() === "arduino" || codeLanguage?.toLowerCase() === "cpp" || codeSnippet.includes("pinMode")) {
|
|
15587
|
+
if (codeSnippet.includes("digitalWrite") && !codeSnippet.includes("pinMode") && !codeSnippet.includes("setup()")) {
|
|
15588
|
+
score -= 15;
|
|
15589
|
+
findings.push({
|
|
15590
|
+
id: "tech_arduino_missing_pinmode",
|
|
15591
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
15592
|
+
severity: "MAJOR",
|
|
15593
|
+
title: "Missing pinMode() Configuration in Arduino Code",
|
|
15594
|
+
description: "Code calls `digitalWrite()` without initializing the pin with `pinMode(pin, OUTPUT)`.",
|
|
15595
|
+
remediationAdvice: "Ensure `setup()` configures pin direction before writing digital states."
|
|
15596
|
+
});
|
|
15597
|
+
}
|
|
15598
|
+
if (codeSnippet.includes("delay(0)") || codeSnippet.includes("delay(-")) {
|
|
15599
|
+
score -= 15;
|
|
15600
|
+
findings.push({
|
|
15601
|
+
id: "tech_arduino_invalid_delay",
|
|
15602
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
15603
|
+
severity: "MAJOR",
|
|
15604
|
+
title: "Invalid delay() Parameter",
|
|
15605
|
+
description: "delay() duration must be a positive integer in milliseconds.",
|
|
15606
|
+
remediationAdvice: "Use realistic delay timings (e.g. 500ms, 1000ms)."
|
|
15607
|
+
});
|
|
15608
|
+
}
|
|
15609
|
+
}
|
|
15610
|
+
strengths.push("Guided practice features runnable code snippet with clear syntax.");
|
|
15983
15611
|
}
|
|
15984
|
-
if (
|
|
15985
|
-
|
|
15612
|
+
if (mermaidDiagram) {
|
|
15613
|
+
const cleanDiagram = mermaidDiagram.trim().toLowerCase();
|
|
15614
|
+
const isValidStarter = VALID_MERMAID_STARTERS.some((starter) => cleanDiagram.startsWith(starter));
|
|
15615
|
+
if (!isValidStarter) {
|
|
15616
|
+
score -= 15;
|
|
15617
|
+
findings.push({
|
|
15618
|
+
id: "tech_invalid_mermaid_syntax",
|
|
15619
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
15620
|
+
severity: "MAJOR",
|
|
15621
|
+
title: "Invalid Mermaid Diagram Syntax",
|
|
15622
|
+
description: `Mermaid diagram does not start with a valid declaration (e.g. "flowchart TD", "graph TD", "sequenceDiagram"). Got: "${mermaidDiagram.substring(0, 30)}..."`,
|
|
15623
|
+
remediationAdvice: "Format Mermaid diagrams starting with `flowchart TD` or `sequenceDiagram`."
|
|
15624
|
+
});
|
|
15625
|
+
} else {
|
|
15626
|
+
strengths.push("Valid Mermaid architectural diagram included.");
|
|
15627
|
+
}
|
|
15628
|
+
}
|
|
15629
|
+
}
|
|
15630
|
+
if (codeLab) {
|
|
15631
|
+
const starter = codeLab.starterCode?.content || "";
|
|
15632
|
+
const solution = codeLab.solutionCode?.content || "";
|
|
15633
|
+
if (starter.length < 20 || solution.length < 20) {
|
|
15634
|
+
score -= 25;
|
|
15986
15635
|
findings.push({
|
|
15987
|
-
id:
|
|
15988
|
-
dimension: "
|
|
15989
|
-
severity: "
|
|
15990
|
-
title:
|
|
15991
|
-
description:
|
|
15992
|
-
remediationAdvice:
|
|
15993
|
-
affectedElement: lo.code
|
|
15636
|
+
id: "tech_codelab_incomplete_codes",
|
|
15637
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
15638
|
+
severity: "CRITICAL",
|
|
15639
|
+
title: "Incomplete CodeLab Starter / Solution Code",
|
|
15640
|
+
description: "CodeLab must provide both scaffolding starter code and complete reference solution code.",
|
|
15641
|
+
remediationAdvice: "Populate `starterCode` with TODO markers and `solutionCode` with tested implementation."
|
|
15994
15642
|
});
|
|
15995
15643
|
} else {
|
|
15996
|
-
strengths.push(
|
|
15644
|
+
strengths.push("CodeLab provides complete starter skeleton and working solution code.");
|
|
15997
15645
|
}
|
|
15998
|
-
|
|
15999
|
-
const diff = lesson.differentiation;
|
|
16000
|
-
if (diff) {
|
|
16001
|
-
if (!diff.bronzeTier || !diff.silverTier || !diff.goldTier) {
|
|
15646
|
+
if (!codeLab.testCases || codeLab.testCases.length === 0) {
|
|
16002
15647
|
score -= 15;
|
|
16003
15648
|
findings.push({
|
|
16004
|
-
id: "
|
|
16005
|
-
dimension: "
|
|
15649
|
+
id: "tech_codelab_missing_testcases",
|
|
15650
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16006
15651
|
severity: "MAJOR",
|
|
16007
|
-
title: "
|
|
16008
|
-
description: "
|
|
16009
|
-
remediationAdvice: "
|
|
15652
|
+
title: "Missing Automated Verification Test Cases",
|
|
15653
|
+
description: "CodeLab lacks test cases for students to self-verify their implementations.",
|
|
15654
|
+
remediationAdvice: "Add at least 2 concrete test cases with input and expected output assertions."
|
|
16010
15655
|
});
|
|
16011
|
-
} else {
|
|
16012
|
-
strengths.push("Complete 3-tier scaffolding (Bronze -> Silver -> Gold) present in Lesson Plan.");
|
|
16013
15656
|
}
|
|
16014
|
-
} else {
|
|
16015
|
-
score -= 20;
|
|
16016
|
-
findings.push({
|
|
16017
|
-
id: "bloom_missing_differentiation",
|
|
16018
|
-
dimension: "BLOOM_PROGRESSION",
|
|
16019
|
-
severity: "CRITICAL",
|
|
16020
|
-
title: "Missing Differentiation Scaffolding",
|
|
16021
|
-
description: "Lesson plan lacks differentiation tiers.",
|
|
16022
|
-
remediationAdvice: "Provide tiered practice instructions for varied learner paces."
|
|
16023
|
-
});
|
|
16024
15657
|
}
|
|
16025
|
-
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
|
|
16030
|
-
|
|
16031
|
-
findings
|
|
16032
|
-
};
|
|
16033
|
-
}
|
|
16034
|
-
/**
|
|
16035
|
-
* Audits a Diagnostic Quiz for true cognitive depth matching declared Bloom levels.
|
|
16036
|
-
*/
|
|
16037
|
-
static evaluateQuiz(quiz) {
|
|
16038
|
-
const findings = [];
|
|
16039
|
-
const strengths = [];
|
|
16040
|
-
let score = 100;
|
|
16041
|
-
const questions = quiz.questions || [];
|
|
16042
|
-
if (questions.length < 3) {
|
|
16043
|
-
score -= 30;
|
|
16044
|
-
findings.push({
|
|
16045
|
-
id: "bloom_quiz_too_short",
|
|
16046
|
-
dimension: "BLOOM_PROGRESSION",
|
|
16047
|
-
severity: "MAJOR",
|
|
16048
|
-
title: "Insufficient Question Pool",
|
|
16049
|
-
description: `Quiz only contains ${questions.length} questions. Minimum diagnostic threshold is 3 questions.`,
|
|
16050
|
-
remediationAdvice: "Generate at least 3-5 diagnostic questions covering foundational through analytical depth."
|
|
16051
|
-
});
|
|
16052
|
-
}
|
|
16053
|
-
let understandCount = 0;
|
|
16054
|
-
let applyCount = 0;
|
|
16055
|
-
let analyzeCount = 0;
|
|
16056
|
-
for (let i = 0; i < questions.length; i++) {
|
|
16057
|
-
const q = questions[i];
|
|
16058
|
-
const qId = q.id || `Q${i + 1}`;
|
|
16059
|
-
const declaredBloom = (q.bloomLevel || "").toLowerCase();
|
|
16060
|
-
const stem = q.scenarioOrStem || "";
|
|
16061
|
-
if (declaredBloom === "understand") understandCount++;
|
|
16062
|
-
if (declaredBloom === "apply") applyCount++;
|
|
16063
|
-
if (declaredBloom === "analyze" || declaredBloom === "evaluate") analyzeCount++;
|
|
16064
|
-
const isRecall = RECALL_PATTERNS.some((p) => p.test(stem));
|
|
16065
|
-
if (isRecall && (declaredBloom === "apply" || declaredBloom === "analyze")) {
|
|
16066
|
-
score -= 15;
|
|
16067
|
-
findings.push({
|
|
16068
|
-
id: `bloom_quiz_misclassification_${qId}`,
|
|
16069
|
-
dimension: "BLOOM_PROGRESSION",
|
|
16070
|
-
severity: "MAJOR",
|
|
16071
|
-
title: `Cognitive Level Misclassification in ${qId}`,
|
|
16072
|
-
description: `Question stem "${stem.substring(0, 60)}..." is pure Recall/Definition, but is tagged as "${q.bloomLevel}".`,
|
|
16073
|
-
remediationAdvice: 'Either re-tag as "understand" or transform the stem into an authentic problem-solving scenario requiring code debugging or design decision.',
|
|
16074
|
-
affectedElement: qId
|
|
16075
|
-
});
|
|
16076
|
-
}
|
|
16077
|
-
if ((declaredBloom === "apply" || declaredBloom === "analyze") && !q.codeSnippet && !stem.includes("```") && !stem.includes("m\u1EA1ch")) {
|
|
16078
|
-
score -= 10;
|
|
16079
|
-
findings.push({
|
|
16080
|
-
id: `bloom_quiz_missing_code_context_${qId}`,
|
|
16081
|
-
dimension: "BLOOM_PROGRESSION",
|
|
16082
|
-
severity: "MINOR",
|
|
16083
|
-
title: `Missing Practical Context in High-Bloom Question ${qId}`,
|
|
16084
|
-
description: `Question ${qId} tagged as "${q.bloomLevel}" lacks concrete code or circuit context to evaluate hands-on execution.`,
|
|
16085
|
-
remediationAdvice: "Include an annotated code snippet or circuit state for students to trace, debug, or evaluate.",
|
|
16086
|
-
affectedElement: qId
|
|
16087
|
-
});
|
|
16088
|
-
}
|
|
16089
|
-
}
|
|
16090
|
-
if (questions.length > 0 && (understandCount > 0 || applyCount > 0)) {
|
|
16091
|
-
strengths.push(`Quiz features multi-level cognitive questions (Understand: ${understandCount}, Apply: ${applyCount}, Analyze: ${analyzeCount}).`);
|
|
16092
|
-
}
|
|
16093
|
-
score = Math.max(0, Math.min(100, score));
|
|
16094
|
-
return {
|
|
16095
|
-
score,
|
|
16096
|
-
weight: 0.2,
|
|
16097
|
-
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
16098
|
-
strengths,
|
|
16099
|
-
findings
|
|
16100
|
-
};
|
|
16101
|
-
}
|
|
16102
|
-
};
|
|
16103
|
-
|
|
16104
|
-
// src/evaluators/crossArtifactDriftEvaluator.ts
|
|
16105
|
-
var CrossArtifactDriftEvaluator = class {
|
|
16106
|
-
/**
|
|
16107
|
-
* Audits consistency, metadata synchronization, and zero-drift across all artifacts in a lesson bundle.
|
|
16108
|
-
*/
|
|
16109
|
-
static evaluateBundle(lesson, quiz, activity, slides) {
|
|
16110
|
-
const findings = [];
|
|
16111
|
-
const strengths = [];
|
|
16112
|
-
let score = 100;
|
|
16113
|
-
const baseLessonId = lesson.lessonId;
|
|
16114
|
-
const baseLanguage = lesson.language;
|
|
16115
|
-
if (quiz && quiz.quizId && quiz.quizId !== baseLessonId && !quiz.quizId.includes(baseLessonId)) {
|
|
16116
|
-
score -= 15;
|
|
16117
|
-
findings.push({
|
|
16118
|
-
id: "drift_quiz_id_mismatch",
|
|
16119
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
16120
|
-
severity: "MAJOR",
|
|
16121
|
-
title: "Quiz ID Mismatch with Master Lesson",
|
|
16122
|
-
description: `Quiz ID "${quiz.quizId}" diverges from master Lesson ID "${baseLessonId}".`,
|
|
16123
|
-
remediationAdvice: `Align Quiz ID to "${baseLessonId}".`,
|
|
16124
|
-
affectedElement: quiz.quizId
|
|
16125
|
-
});
|
|
16126
|
-
}
|
|
16127
|
-
if (activity && activity.lessonId && activity.lessonId !== baseLessonId) {
|
|
16128
|
-
score -= 15;
|
|
16129
|
-
findings.push({
|
|
16130
|
-
id: "drift_act_id_mismatch",
|
|
16131
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
16132
|
-
severity: "MAJOR",
|
|
16133
|
-
title: "Activity Lesson ID Mismatch",
|
|
16134
|
-
description: `Activity lessonId "${activity.lessonId}" diverges from master Lesson ID "${baseLessonId}".`,
|
|
16135
|
-
remediationAdvice: `Align Activity lessonId to "${baseLessonId}".`,
|
|
16136
|
-
affectedElement: activity.lessonId
|
|
16137
|
-
});
|
|
16138
|
-
}
|
|
16139
|
-
if (slides && slides.lessonId && slides.lessonId !== baseLessonId) {
|
|
16140
|
-
score -= 15;
|
|
16141
|
-
findings.push({
|
|
16142
|
-
id: "drift_slides_id_mismatch",
|
|
16143
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
16144
|
-
severity: "MAJOR",
|
|
16145
|
-
title: "Slide Deck Lesson ID Mismatch",
|
|
16146
|
-
description: `Slide deck lessonId "${slides.lessonId}" diverges from master Lesson ID "${baseLessonId}".`,
|
|
16147
|
-
remediationAdvice: `Align Slide deck lessonId to "${baseLessonId}".`,
|
|
16148
|
-
affectedElement: slides.lessonId
|
|
16149
|
-
});
|
|
16150
|
-
}
|
|
16151
|
-
const satellites = [
|
|
16152
|
-
{ type: "QUIZ", lang: quiz?.language },
|
|
16153
|
-
{ type: "ACT", lang: activity?.language },
|
|
16154
|
-
{ type: "SLIDE", lang: slides?.language }
|
|
16155
|
-
];
|
|
16156
|
-
for (const sat of satellites) {
|
|
16157
|
-
if (sat.lang && sat.lang.toLowerCase() !== baseLanguage.toLowerCase()) {
|
|
16158
|
-
score -= 25;
|
|
15658
|
+
if (activity?.materialsBOM?.hardware && activity.materialsBOM.hardware.length > 0) {
|
|
15659
|
+
const hwText = activity.materialsBOM.hardware.join(" ").toLowerCase();
|
|
15660
|
+
const hasLED = hwText.includes("led");
|
|
15661
|
+
const hasResistor = hwText.includes("\u0111i\u1EC7n tr\u1EDF") || hwText.includes("resistor") || hwText.includes("220") || hwText.includes("330") || hwText.includes("1k");
|
|
15662
|
+
if (hasLED && !hasResistor) {
|
|
15663
|
+
score -= 20;
|
|
16159
15664
|
findings.push({
|
|
16160
|
-
id:
|
|
16161
|
-
dimension: "
|
|
15665
|
+
id: "tech_hardware_unsafe_led_no_resistor",
|
|
15666
|
+
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16162
15667
|
severity: "CRITICAL",
|
|
16163
|
-
title:
|
|
16164
|
-
description:
|
|
16165
|
-
remediationAdvice:
|
|
15668
|
+
title: "Dangerous Hardware Circuit: LED without Current-Limiting Resistor",
|
|
15669
|
+
description: "Activity specifies an LED on breadboard/microcontroller without a 220\u03A9-1k\u03A9 current-limiting resistor, which causes electrical overload and burnout.",
|
|
15670
|
+
remediationAdvice: "Add a 220\u03A9 or 330\u03A9 current-limiting resistor to the hardware BOM."
|
|
16166
15671
|
});
|
|
15672
|
+
} else if (hasLED && hasResistor) {
|
|
15673
|
+
strengths.push("Hardware BOM safely pairs LED with current-limiting resistor protection.");
|
|
16167
15674
|
}
|
|
16168
15675
|
}
|
|
16169
|
-
const sections = lesson.sections || [];
|
|
16170
|
-
const totalSectionMins = sections.reduce((sum, s) => sum + (s.durationMinutes || 0), 0);
|
|
16171
|
-
if (totalSectionMins > 0 && (totalSectionMins < 40 || totalSectionMins > 180)) {
|
|
16172
|
-
score -= 10;
|
|
16173
|
-
findings.push({
|
|
16174
|
-
id: "drift_unrealistic_lesson_duration",
|
|
16175
|
-
dimension: "CROSS_ARTIFACT_ZERO_DRIFT",
|
|
16176
|
-
severity: "MINOR",
|
|
16177
|
-
title: `Unrealistic Lesson Flow Total Duration (${totalSectionMins} mins)`,
|
|
16178
|
-
description: `Sum of 5E section durations is ${totalSectionMins} minutes. Standard K-12/College lessons span 45-120 minutes.`,
|
|
16179
|
-
remediationAdvice: "Adjust individual section timings so total duration matches standard classroom blocks (e.g. 60m or 90m)."
|
|
16180
|
-
});
|
|
16181
|
-
} else if (totalSectionMins > 0) {
|
|
16182
|
-
strengths.push(`Lesson section timings sum up to a realistic classroom block (${totalSectionMins} mins).`);
|
|
16183
|
-
}
|
|
16184
|
-
if (score >= 90) {
|
|
16185
|
-
strengths.push("Zero drift verified: IDs, language policies, and pedagogical contracts are strictly aligned across all artifacts.");
|
|
16186
|
-
}
|
|
16187
15676
|
score = Math.max(0, Math.min(100, score));
|
|
16188
15677
|
return {
|
|
16189
15678
|
score,
|
|
16190
|
-
weight: 0.
|
|
15679
|
+
weight: 0.15,
|
|
16191
15680
|
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
16192
15681
|
strengths,
|
|
16193
15682
|
findings
|
|
@@ -16195,19 +15684,22 @@ var CrossArtifactDriftEvaluator = class {
|
|
|
16195
15684
|
}
|
|
16196
15685
|
};
|
|
16197
15686
|
|
|
16198
|
-
// src/evaluators/
|
|
16199
|
-
var
|
|
15687
|
+
// src/evaluators/misconceptionEvaluator.ts
|
|
15688
|
+
var LAZY_DISTRACTOR_PATTERNS = [
|
|
15689
|
+
/^(?:tất cả các đáp án trên đều (?:đúng|sai)|cả a,?\s*b,?\s*c đều (?:đúng|sai)|all of the above|none of the above)[\.\?!]?$/i,
|
|
15690
|
+
/^(?:không có đáp án nào đúng|không có chức năng|không làm gì cả|không ảnh hưởng)[\.\?!]?$/i,
|
|
15691
|
+
/^(?:đáp án khác|other)[\.\?!]?$/i
|
|
15692
|
+
];
|
|
15693
|
+
var MisconceptionEvaluator = class {
|
|
16200
15694
|
/**
|
|
16201
|
-
*
|
|
15695
|
+
* Audits the psychometric and pedagogical rigor of diagnostic questions and their distractors.
|
|
16202
15696
|
*/
|
|
16203
|
-
static
|
|
15697
|
+
static evaluateQuiz(quiz) {
|
|
16204
15698
|
const findings = [];
|
|
16205
15699
|
const strengths = [];
|
|
16206
15700
|
let score = 100;
|
|
16207
|
-
const
|
|
16208
|
-
|
|
16209
|
-
const exitTicket = lesson.exitTicket;
|
|
16210
|
-
if (los.length === 0) {
|
|
15701
|
+
const questions = quiz.questions || [];
|
|
15702
|
+
if (questions.length === 0) {
|
|
16211
15703
|
return {
|
|
16212
15704
|
score: 0,
|
|
16213
15705
|
weight: 0.2,
|
|
@@ -16215,85 +15707,84 @@ var ConstructiveAlignmentEvaluator = class {
|
|
|
16215
15707
|
strengths: [],
|
|
16216
15708
|
findings: [
|
|
16217
15709
|
{
|
|
16218
|
-
id: "
|
|
16219
|
-
dimension: "
|
|
15710
|
+
id: "misconception_no_questions",
|
|
15711
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
16220
15712
|
severity: "CRITICAL",
|
|
16221
|
-
title: "
|
|
16222
|
-
description: "
|
|
16223
|
-
remediationAdvice: "
|
|
15713
|
+
title: "Empty Question Bank",
|
|
15714
|
+
description: "No questions provided in quiz artifact.",
|
|
15715
|
+
remediationAdvice: "Generate diagnostic questions with deliberate misconception traps."
|
|
16224
15716
|
}
|
|
16225
15717
|
]
|
|
16226
15718
|
};
|
|
16227
15719
|
}
|
|
16228
|
-
|
|
16229
|
-
for (
|
|
16230
|
-
const
|
|
16231
|
-
const
|
|
16232
|
-
|
|
15720
|
+
let questionsWithFullExplanations = 0;
|
|
15721
|
+
for (let i = 0; i < questions.length; i++) {
|
|
15722
|
+
const q = questions[i];
|
|
15723
|
+
const qId = q.id || `Q${i + 1}`;
|
|
15724
|
+
const options = q.options || [];
|
|
15725
|
+
if (options.length < 4) {
|
|
16233
15726
|
score -= 15;
|
|
16234
15727
|
findings.push({
|
|
16235
|
-
id: `
|
|
16236
|
-
dimension: "
|
|
15728
|
+
id: `misconception_few_options_${qId}`,
|
|
15729
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
16237
15730
|
severity: "MAJOR",
|
|
16238
|
-
title: `
|
|
16239
|
-
description: `
|
|
16240
|
-
remediationAdvice:
|
|
16241
|
-
affectedElement:
|
|
15731
|
+
title: `Insufficient Distractors in ${qId}`,
|
|
15732
|
+
description: `Question ${qId} has only ${options.length} options. Standard diagnostic rigor requires 4 plausible choices (1 key + 3 diagnostic distractors).`,
|
|
15733
|
+
remediationAdvice: "Provide 4 full options (A, B, C, D) representing distinct cognitive states.",
|
|
15734
|
+
affectedElement: qId
|
|
16242
15735
|
});
|
|
16243
15736
|
}
|
|
16244
|
-
|
|
16245
|
-
|
|
16246
|
-
|
|
16247
|
-
findings.push({
|
|
16248
|
-
id: "align_missing_exit_ticket",
|
|
16249
|
-
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
16250
|
-
severity: "MAJOR",
|
|
16251
|
-
title: "Missing or Shallow Exit Ticket",
|
|
16252
|
-
description: "The lesson ends without a rigorous formative Exit Ticket to measure LO attainment before class dismissal.",
|
|
16253
|
-
remediationAdvice: "Include an Exit Ticket question matching the primary LO + a metacognitive reflection prompt."
|
|
16254
|
-
});
|
|
16255
|
-
} else {
|
|
16256
|
-
strengths.push("Formative Exit Ticket is properly anchored at the conclusion of the lesson.");
|
|
16257
|
-
}
|
|
16258
|
-
if (quiz) {
|
|
16259
|
-
const quizQuestions = quiz.questions || [];
|
|
16260
|
-
if (quizQuestions.length > 0) {
|
|
16261
|
-
const quizText = quizQuestions.map((q) => `${q.scenarioOrStem} ${(q.options || []).map((o) => o.text).join(" ")}`).join(" ").toLowerCase();
|
|
16262
|
-
const lessonTopicKeywords = (lesson.topic || lesson.title || "").toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
16263
|
-
const hasTopicMatch = lessonTopicKeywords.some((k) => quizText.includes(k));
|
|
16264
|
-
if (!hasTopicMatch && lessonTopicKeywords.length > 0) {
|
|
16265
|
-
score -= 20;
|
|
16266
|
-
findings.push({
|
|
16267
|
-
id: "align_quiz_topic_divergence",
|
|
16268
|
-
dimension: "CONSTRUCTIVE_ALIGNMENT",
|
|
16269
|
-
severity: "CRITICAL",
|
|
16270
|
-
title: "Quiz Topic Divergence from Lesson",
|
|
16271
|
-
description: `Quiz questions do not reflect the main topic "${lesson.topic}" of the master lesson.`,
|
|
16272
|
-
remediationAdvice: "Re-align quiz questions strictly to the core concepts taught in the lesson."
|
|
16273
|
-
});
|
|
16274
|
-
} else {
|
|
16275
|
-
strengths.push("Diagnostic Quiz questions tightly reflect the lesson topic and concepts.");
|
|
16276
|
-
}
|
|
16277
|
-
}
|
|
16278
|
-
}
|
|
16279
|
-
if (activity) {
|
|
16280
|
-
const actObj = (activity.objective || "").toLowerCase();
|
|
16281
|
-
const lessonTitleLower = (lesson.title || "").toLowerCase();
|
|
16282
|
-
const hasActAlignment = los.some((lo) => actObj.includes((lo.name || "").toLowerCase()) || actObj.includes((lo.description || "").toLowerCase())) || actObj.includes(lessonTitleLower) || lessonTitleLower.split(/\s+/).some((w) => w.length > 4 && actObj.includes(w));
|
|
16283
|
-
if (!hasActAlignment && activity.objective) {
|
|
16284
|
-
score -= 15;
|
|
15737
|
+
const correctCount = options.filter((o) => o.isCorrect).length;
|
|
15738
|
+
if (correctCount !== 1) {
|
|
15739
|
+
score -= 25;
|
|
16285
15740
|
findings.push({
|
|
16286
|
-
id:
|
|
16287
|
-
dimension: "
|
|
16288
|
-
severity: "
|
|
16289
|
-
title:
|
|
16290
|
-
description: `
|
|
16291
|
-
remediationAdvice: "
|
|
15741
|
+
id: `misconception_invalid_correct_count_${qId}`,
|
|
15742
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
15743
|
+
severity: "CRITICAL",
|
|
15744
|
+
title: `Key Assignment Error in ${qId}`,
|
|
15745
|
+
description: `Question ${qId} has ${correctCount} correct options (must have exactly 1 true answer).`,
|
|
15746
|
+
remediationAdvice: "Set `isCorrect: true` on exactly one option and `isCorrect: false` on all distractors.",
|
|
15747
|
+
affectedElement: qId
|
|
15748
|
+
});
|
|
15749
|
+
}
|
|
15750
|
+
let missingExplanation = false;
|
|
15751
|
+
for (const opt of options) {
|
|
15752
|
+
const text = (opt.text || "").trim();
|
|
15753
|
+
const explanation = (opt.explanation || "").trim();
|
|
15754
|
+
if (LAZY_DISTRACTOR_PATTERNS.some((p) => p.test(text))) {
|
|
15755
|
+
score -= 10;
|
|
15756
|
+
findings.push({
|
|
15757
|
+
id: `misconception_lazy_distractor_${qId}_${opt.id}`,
|
|
15758
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
15759
|
+
severity: "MAJOR",
|
|
15760
|
+
title: `Low-Utility Distractor in ${qId} (${opt.id})`,
|
|
15761
|
+
description: `Option "${text}" is a generic/throwaway distractor ("All/None of the above" or "No effect") that does not diagnose student cognitive models.`,
|
|
15762
|
+
remediationAdvice: "Replace with an authentic student misconception (e.g. inverted logic, missing pullup, off-by-one boundary, unit confusion).",
|
|
15763
|
+
affectedElement: `${qId}.${opt.id}`
|
|
15764
|
+
});
|
|
15765
|
+
}
|
|
15766
|
+
if (!explanation || explanation.length < 20) {
|
|
15767
|
+
missingExplanation = true;
|
|
15768
|
+
}
|
|
15769
|
+
}
|
|
15770
|
+
if (missingExplanation) {
|
|
15771
|
+
score -= 10;
|
|
15772
|
+
findings.push({
|
|
15773
|
+
id: `misconception_shallow_explanation_${qId}`,
|
|
15774
|
+
dimension: "MISCONCEPTION_RIGOR",
|
|
15775
|
+
severity: "MAJOR",
|
|
15776
|
+
title: `Shallow Distractor Explanations in ${qId}`,
|
|
15777
|
+
description: `Question ${qId} lacks detailed pedagogical explanations for all options. Students and teachers cannot diagnose root causes without option-level rationale.`,
|
|
15778
|
+
remediationAdvice: "For EVERY option (A, B, C, D), explicitly write why the choice is correct or what mental misconception it represents.",
|
|
15779
|
+
affectedElement: qId
|
|
16292
15780
|
});
|
|
16293
15781
|
} else {
|
|
16294
|
-
|
|
15782
|
+
questionsWithFullExplanations++;
|
|
16295
15783
|
}
|
|
16296
15784
|
}
|
|
15785
|
+
if (questionsWithFullExplanations === questions.length && questions.length >= 3) {
|
|
15786
|
+
strengths.push("All quiz questions feature rigorous 4-option breakdown with comprehensive misconception analysis.");
|
|
15787
|
+
}
|
|
16297
15788
|
score = Math.max(0, Math.min(100, score));
|
|
16298
15789
|
return {
|
|
16299
15790
|
score,
|
|
@@ -16305,345 +15796,287 @@ var ConstructiveAlignmentEvaluator = class {
|
|
|
16305
15796
|
}
|
|
16306
15797
|
};
|
|
16307
15798
|
|
|
16308
|
-
// src/
|
|
16309
|
-
|
|
16310
|
-
|
|
16311
|
-
|
|
16312
|
-
|
|
16313
|
-
|
|
16314
|
-
|
|
16315
|
-
|
|
16316
|
-
|
|
16317
|
-
|
|
16318
|
-
|
|
16319
|
-
|
|
16320
|
-
|
|
16321
|
-
|
|
16322
|
-
|
|
16323
|
-
|
|
16324
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
|
|
16334
|
-
} else {
|
|
16335
|
-
strengths.push("Engaging real-world hook scenario sets high-stakes context for inquiry.");
|
|
15799
|
+
// src/standards/standardsCoverageGate.ts
|
|
15800
|
+
function resolveStatementRef(ref, packs) {
|
|
15801
|
+
const [head, ...rest] = ref.split(":");
|
|
15802
|
+
const candidatePacks = rest.length > 0 ? packs.filter((p) => p.manifest.id === head) : packs;
|
|
15803
|
+
const statementId = rest.length > 0 ? rest.join(":") : ref;
|
|
15804
|
+
for (const p of candidatePacks) {
|
|
15805
|
+
if (p.statements.some((s) => s.id === statementId)) return { pack: p, statementId };
|
|
15806
|
+
}
|
|
15807
|
+
return null;
|
|
15808
|
+
}
|
|
15809
|
+
function evaluateStandardsCoverage(input) {
|
|
15810
|
+
const rows = [];
|
|
15811
|
+
const aoToLo = /* @__PURE__ */ new Map();
|
|
15812
|
+
for (const ao of input.assessmentObjectives ?? []) aoToLo.set(ao.aoCode, ao.alignedLO);
|
|
15813
|
+
const loToRefs = /* @__PURE__ */ new Map();
|
|
15814
|
+
for (const lo of input.objectives) {
|
|
15815
|
+
loToRefs.set(lo.code, new Set(lo.standardRefs ?? []));
|
|
15816
|
+
}
|
|
15817
|
+
const taughtLOs = /* @__PURE__ */ new Set();
|
|
15818
|
+
for (const act of input.activities ?? []) for (const lo of act.lo) taughtLOs.add(lo);
|
|
15819
|
+
const assessedLOs = /* @__PURE__ */ new Set();
|
|
15820
|
+
for (const q of input.quizQuestions) {
|
|
15821
|
+
if (q.alignedLO) assessedLOs.add(q.alignedLO);
|
|
15822
|
+
if (q.alignedAO) {
|
|
15823
|
+
const lo = aoToLo.get(q.alignedAO);
|
|
15824
|
+
if (lo) assessedLOs.add(lo);
|
|
16336
15825
|
}
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16345
|
-
remediationAdvice: "Add 2-3 provocative questions challenging common student assumptions."
|
|
16346
|
-
});
|
|
15826
|
+
}
|
|
15827
|
+
for (const pack of input.packs) {
|
|
15828
|
+
const mappingByStatement = /* @__PURE__ */ new Map();
|
|
15829
|
+
for (const m of pack.mappings ?? []) {
|
|
15830
|
+
const prev = mappingByStatement.get(m.statementId);
|
|
15831
|
+
if (!prev || prev !== "covers" && m.kind === "covers" || prev === "extends" && m.kind !== "extends") {
|
|
15832
|
+
mappingByStatement.set(m.statementId, m.kind);
|
|
15833
|
+
}
|
|
16347
15834
|
}
|
|
16348
|
-
const
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
15835
|
+
for (const statement of pack.statements) {
|
|
15836
|
+
const refFull = `${pack.manifest.id}:${statement.id}`;
|
|
15837
|
+
const issues = [];
|
|
15838
|
+
const los = input.objectives.filter((lo) => (lo.standardRefs ?? []).some((r) => r === statement.id || r === refFull)).map((lo) => lo.code);
|
|
15839
|
+
const kind = mappingByStatement.get(statement.id);
|
|
15840
|
+
const hasMapping = kind !== void 0;
|
|
15841
|
+
const isComplianceRelevant = kind === "covers";
|
|
15842
|
+
const hasActivity = los.some((lo) => taughtLOs.has(lo));
|
|
15843
|
+
const hasAssessment = los.some((lo) => assessedLOs.has(lo));
|
|
15844
|
+
let status;
|
|
15845
|
+
if (!hasMapping) status = "UNMAPPED";
|
|
15846
|
+
else if (!isComplianceRelevant) status = "PARTIAL";
|
|
15847
|
+
else if (los.length > 0 && hasActivity && hasAssessment) status = "COVERED";
|
|
15848
|
+
else status = "UNCOVERED";
|
|
15849
|
+
if (status === "UNCOVERED") {
|
|
15850
|
+
if (los.length === 0) issues.push("No learning objective anchors this statement (missing standardRefs).");
|
|
15851
|
+
else {
|
|
15852
|
+
if (!hasActivity) issues.push(`Anchored LO(s) ${los.join(", ")} have no activity in lesson flow.`);
|
|
15853
|
+
if (!hasAssessment) issues.push(`Anchored LO(s) ${los.join(", ")} are not measured by any quiz question.`);
|
|
16354
15854
|
}
|
|
16355
15855
|
}
|
|
16356
|
-
|
|
16357
|
-
|
|
16358
|
-
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
16363
|
-
|
|
16364
|
-
|
|
16365
|
-
severity: missingStages.length > 2 ? "CRITICAL" : "MAJOR",
|
|
16366
|
-
title: `Incomplete 5E Instructional Cycle (Missing ${missingStages.length} phase(s))`,
|
|
16367
|
-
description: `Lesson plan does not clearly demarcate: ${missingStages.map((s) => s.label).join(", ")}.`,
|
|
16368
|
-
remediationAdvice: "Ensure the lesson explicitly walks through Engage -> Explore -> Explain -> Elaborate -> Evaluate."
|
|
15856
|
+
rows.push({
|
|
15857
|
+
packId: pack.manifest.id,
|
|
15858
|
+
statementId: statement.id,
|
|
15859
|
+
statementText: Object.values(statement.texts)[0] ?? "",
|
|
15860
|
+
status,
|
|
15861
|
+
objectives: los,
|
|
15862
|
+
hasActivity,
|
|
15863
|
+
hasAssessment,
|
|
15864
|
+
issues
|
|
16369
15865
|
});
|
|
16370
|
-
} else {
|
|
16371
|
-
strengths.push("Full 5E Instructional Cycle (Engage, Explore, Explain, Elaborate, Evaluate) is completely covered.");
|
|
16372
15866
|
}
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
|
|
16378
|
-
|
|
15867
|
+
}
|
|
15868
|
+
for (const lo of input.objectives) {
|
|
15869
|
+
for (const r of lo.standardRefs ?? []) {
|
|
15870
|
+
if (!resolveStatementRef(r, input.packs)) {
|
|
15871
|
+
rows.push({
|
|
15872
|
+
packId: "(unresolved)",
|
|
15873
|
+
statementId: r,
|
|
15874
|
+
statementText: "",
|
|
15875
|
+
status: "UNCOVERED",
|
|
15876
|
+
objectives: [lo.code],
|
|
15877
|
+
hasActivity: false,
|
|
15878
|
+
hasAssessment: false,
|
|
15879
|
+
issues: [`standardRef "${r}" does not exist in any adopted pack \u2014 likely a hallucinated or stale ID`]
|
|
15880
|
+
});
|
|
16379
15881
|
}
|
|
16380
15882
|
}
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
15883
|
+
}
|
|
15884
|
+
const complianceRows = rows.filter((r) => r.status !== "UNMAPPED" && r.status !== "PARTIAL");
|
|
15885
|
+
const covered = complianceRows.filter((r) => r.status === "COVERED").length;
|
|
15886
|
+
const coveragePct = complianceRows.length === 0 ? 100 : Math.round(covered / complianceRows.length * 100);
|
|
15887
|
+
const unresolvedCount = rows.filter((r) => r.packId === "(unresolved)").length;
|
|
15888
|
+
const verdict = unresolvedCount > 0 || coveragePct < 60 ? "FAIL" : coveragePct < 100 ? "WARN" : "PASS";
|
|
15889
|
+
const lines = [
|
|
15890
|
+
"# Standards Coverage Report",
|
|
15891
|
+
"",
|
|
15892
|
+
`- Verdict: **${verdict}**`,
|
|
15893
|
+
`- Compliance coverage: **${coveragePct}%** (${covered}/${complianceRows.length} "covers" statements fully taught + assessed)`,
|
|
15894
|
+
`- Unresolved standardRefs: ${unresolvedCount}`,
|
|
15895
|
+
"",
|
|
15896
|
+
"| Pack | Statement | Status | LOs | Activity | Assessment |",
|
|
15897
|
+
"|---|---|---|---|---|---|",
|
|
15898
|
+
...rows.map(
|
|
15899
|
+
(r) => `|${r.packId}|${r.statementId}|${r.status}|${r.objectives.join(", ") || "\u2014"}|${r.hasActivity ? "\u2713" : "\u2717"}|${r.hasAssessment ? "\u2713" : "\u2717"}|`
|
|
15900
|
+
)
|
|
15901
|
+
];
|
|
15902
|
+
const issueLines = rows.filter((r) => r.issues.length > 0).flatMap((r) => r.issues.map((i) => `- [${r.packId}:${r.statementId}] ${i}`));
|
|
15903
|
+
if (issueLines.length > 0) lines.push("", "## Issues", ...issueLines);
|
|
15904
|
+
return {
|
|
15905
|
+
verdict,
|
|
15906
|
+
coveragePct,
|
|
15907
|
+
rows,
|
|
15908
|
+
summary: `Standards coverage ${coveragePct}% \u2014 verdict ${verdict}`,
|
|
15909
|
+
rawMarkdownReport: lines.join("\n")
|
|
15910
|
+
};
|
|
15911
|
+
}
|
|
15912
|
+
var StandardsRegistryAdapter = class {
|
|
15913
|
+
client;
|
|
15914
|
+
constructor(config = {}) {
|
|
15915
|
+
if (config.client) {
|
|
15916
|
+
this.client = config.client;
|
|
15917
|
+
return;
|
|
16393
15918
|
}
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
findings
|
|
16401
|
-
};
|
|
15919
|
+
const url = config.supabaseUrl || process.env.NEXT_PUBLIC_SUPABASE_URL || process.env.SUPABASE_URL || "";
|
|
15920
|
+
const key = config.supabaseServiceRoleKey || process.env.SUPABASE_SERVICE_ROLE_KEY || config.supabaseAnonKey || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "";
|
|
15921
|
+
if (!url || !key) {
|
|
15922
|
+
throw new Error("StandardsRegistryAdapter: Supabase URL/key missing (pass client or env).");
|
|
15923
|
+
}
|
|
15924
|
+
this.client = createClient(url, key);
|
|
16402
15925
|
}
|
|
16403
|
-
|
|
16404
|
-
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
"sequencediagram",
|
|
16410
|
-
"statediagram",
|
|
16411
|
-
"classdiagram",
|
|
16412
|
-
"erdiagram",
|
|
16413
|
-
"gantt",
|
|
16414
|
-
"gitgraph"
|
|
16415
|
-
];
|
|
16416
|
-
var CodeHardwareFeasibilityEvaluator = class {
|
|
16417
|
-
/**
|
|
16418
|
-
* Audits technical accuracy, code syntax sanity, and hardware circuit safety.
|
|
16419
|
-
*/
|
|
16420
|
-
static evaluateTechnicalFeasibility(lesson, codeLab, activity) {
|
|
16421
|
-
const findings = [];
|
|
16422
|
-
const strengths = [];
|
|
16423
|
-
let score = 100;
|
|
16424
|
-
if (lesson?.guidedPractice) {
|
|
16425
|
-
const { codeSnippet, codeLanguage, mermaidDiagram } = lesson.guidedPractice;
|
|
16426
|
-
if (!codeSnippet || codeSnippet.trim().length < 15) {
|
|
16427
|
-
score -= 20;
|
|
16428
|
-
findings.push({
|
|
16429
|
-
id: "tech_empty_guided_code",
|
|
16430
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16431
|
-
severity: "MAJOR",
|
|
16432
|
-
title: "Empty or Trivial Guided Practice Code",
|
|
16433
|
-
description: "Guided practice lacks runnable code snippet or domain calculation template.",
|
|
16434
|
-
remediationAdvice: "Provide a complete, runnable code example with explanatory line-by-line comments."
|
|
16435
|
-
});
|
|
16436
|
-
} else {
|
|
16437
|
-
if (codeLanguage?.toLowerCase() === "arduino" || codeLanguage?.toLowerCase() === "cpp" || codeSnippet.includes("pinMode")) {
|
|
16438
|
-
if (codeSnippet.includes("digitalWrite") && !codeSnippet.includes("pinMode") && !codeSnippet.includes("setup()")) {
|
|
16439
|
-
score -= 15;
|
|
16440
|
-
findings.push({
|
|
16441
|
-
id: "tech_arduino_missing_pinmode",
|
|
16442
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16443
|
-
severity: "MAJOR",
|
|
16444
|
-
title: "Missing pinMode() Configuration in Arduino Code",
|
|
16445
|
-
description: "Code calls `digitalWrite()` without initializing the pin with `pinMode(pin, OUTPUT)`.",
|
|
16446
|
-
remediationAdvice: "Ensure `setup()` configures pin direction before writing digital states."
|
|
16447
|
-
});
|
|
16448
|
-
}
|
|
16449
|
-
if (codeSnippet.includes("delay(0)") || codeSnippet.includes("delay(-")) {
|
|
16450
|
-
score -= 15;
|
|
16451
|
-
findings.push({
|
|
16452
|
-
id: "tech_arduino_invalid_delay",
|
|
16453
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16454
|
-
severity: "MAJOR",
|
|
16455
|
-
title: "Invalid delay() Parameter",
|
|
16456
|
-
description: "delay() duration must be a positive integer in milliseconds.",
|
|
16457
|
-
remediationAdvice: "Use realistic delay timings (e.g. 500ms, 1000ms)."
|
|
16458
|
-
});
|
|
16459
|
-
}
|
|
16460
|
-
}
|
|
16461
|
-
strengths.push("Guided practice features runnable code snippet with clear syntax.");
|
|
16462
|
-
}
|
|
16463
|
-
if (mermaidDiagram) {
|
|
16464
|
-
const cleanDiagram = mermaidDiagram.trim().toLowerCase();
|
|
16465
|
-
const isValidStarter = VALID_MERMAID_STARTERS.some((starter) => cleanDiagram.startsWith(starter));
|
|
16466
|
-
if (!isValidStarter) {
|
|
16467
|
-
score -= 15;
|
|
16468
|
-
findings.push({
|
|
16469
|
-
id: "tech_invalid_mermaid_syntax",
|
|
16470
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16471
|
-
severity: "MAJOR",
|
|
16472
|
-
title: "Invalid Mermaid Diagram Syntax",
|
|
16473
|
-
description: `Mermaid diagram does not start with a valid declaration (e.g. "flowchart TD", "graph TD", "sequenceDiagram"). Got: "${mermaidDiagram.substring(0, 30)}..."`,
|
|
16474
|
-
remediationAdvice: "Format Mermaid diagrams starting with `flowchart TD` or `sequenceDiagram`."
|
|
16475
|
-
});
|
|
16476
|
-
} else {
|
|
16477
|
-
strengths.push("Valid Mermaid architectural diagram included.");
|
|
16478
|
-
}
|
|
16479
|
-
}
|
|
16480
|
-
}
|
|
16481
|
-
if (codeLab) {
|
|
16482
|
-
const starter = codeLab.starterCode?.content || "";
|
|
16483
|
-
const solution = codeLab.solutionCode?.content || "";
|
|
16484
|
-
if (starter.length < 20 || solution.length < 20) {
|
|
16485
|
-
score -= 25;
|
|
16486
|
-
findings.push({
|
|
16487
|
-
id: "tech_codelab_incomplete_codes",
|
|
16488
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16489
|
-
severity: "CRITICAL",
|
|
16490
|
-
title: "Incomplete CodeLab Starter / Solution Code",
|
|
16491
|
-
description: "CodeLab must provide both scaffolding starter code and complete reference solution code.",
|
|
16492
|
-
remediationAdvice: "Populate `starterCode` with TODO markers and `solutionCode` with tested implementation."
|
|
16493
|
-
});
|
|
16494
|
-
} else {
|
|
16495
|
-
strengths.push("CodeLab provides complete starter skeleton and working solution code.");
|
|
16496
|
-
}
|
|
16497
|
-
if (!codeLab.testCases || codeLab.testCases.length === 0) {
|
|
16498
|
-
score -= 15;
|
|
16499
|
-
findings.push({
|
|
16500
|
-
id: "tech_codelab_missing_testcases",
|
|
16501
|
-
dimension: "TECHNICAL_AUTHENTICITY",
|
|
16502
|
-
severity: "MAJOR",
|
|
16503
|
-
title: "Missing Automated Verification Test Cases",
|
|
16504
|
-
description: "CodeLab lacks test cases for students to self-verify their implementations.",
|
|
16505
|
-
remediationAdvice: "Add at least 2 concrete test cases with input and expected output assertions."
|
|
16506
|
-
});
|
|
16507
|
-
}
|
|
15926
|
+
// ─── Intake (write path — service role) ───────────────────────────────────
|
|
15927
|
+
/** Persist a schema-validated pack as a new framework (status=draft). */
|
|
15928
|
+
async importPack(pack, opts) {
|
|
15929
|
+
const parsed = FrameworkPackSchema.safeParse(pack);
|
|
15930
|
+
if (!parsed.success) {
|
|
15931
|
+
throw new Error("importPack: pack failed schema validation: " + parsed.error.issues.map((i) => i.path.join(".") + ": " + i.message).join("; "));
|
|
16508
15932
|
}
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
|
|
16520
|
-
|
|
16521
|
-
|
|
16522
|
-
|
|
16523
|
-
|
|
16524
|
-
|
|
16525
|
-
|
|
15933
|
+
const p = parsed.data;
|
|
15934
|
+
const { data: fw, error: fwErr } = await this.client.from("standards_frameworks").insert({
|
|
15935
|
+
pack_id: p.manifest.id,
|
|
15936
|
+
content_version: p.manifest.contentVersion,
|
|
15937
|
+
name: p.manifest.name,
|
|
15938
|
+
spec_version: p.manifest.specVersion,
|
|
15939
|
+
subject: p.manifest.subject,
|
|
15940
|
+
languages: p.manifest.languages,
|
|
15941
|
+
grade_model: p.manifest.gradeModel,
|
|
15942
|
+
provenance: p.manifest.provenance,
|
|
15943
|
+
trust: p.manifest.trust,
|
|
15944
|
+
status: "draft",
|
|
15945
|
+
organization_code: opts?.organizationCode ?? (p.manifest.trust === "verified" ? null : p.manifest.provenance.importedBy?.replace(/^org:/, "") || null),
|
|
15946
|
+
original_file_path: opts?.originalFilePath ?? null,
|
|
15947
|
+
created_by: opts?.createdBy ?? null
|
|
15948
|
+
}).select("id").single();
|
|
15949
|
+
if (fwErr) throw new Error("importPack: framework insert failed: " + fwErr.message);
|
|
15950
|
+
const frameworkId = fw.id;
|
|
15951
|
+
const statementRows = p.statements.map((s) => ({
|
|
15952
|
+
framework_id: frameworkId,
|
|
15953
|
+
statement_id: s.id,
|
|
15954
|
+
parent_statement_id: s.parentId ?? null,
|
|
15955
|
+
grade_min: s.gradeBand[0],
|
|
15956
|
+
grade_max: s.gradeBand[1],
|
|
15957
|
+
texts: s.texts,
|
|
15958
|
+
classifications: s.classifications ?? [],
|
|
15959
|
+
bloom_hint: s.bloomHint ?? null,
|
|
15960
|
+
keywords: s.keywords ?? [],
|
|
15961
|
+
source_ref: s.sourceRef ?? null,
|
|
15962
|
+
provenance: s.provenance
|
|
15963
|
+
}));
|
|
15964
|
+
const { error: stErr, count: stCount } = await this.client.from("standards_statements").insert(statementRows, { count: "exact" });
|
|
15965
|
+
if (stErr) throw new Error("importPack: statement insert failed: " + stErr.message);
|
|
15966
|
+
let mappingCount = 0;
|
|
15967
|
+
if (p.mappings && p.mappings.length > 0) {
|
|
15968
|
+
const mappingRows = p.mappings.map((m) => ({
|
|
15969
|
+
framework_id: frameworkId,
|
|
15970
|
+
statement_id: m.statementId,
|
|
15971
|
+
target_ref: m.targetRef,
|
|
15972
|
+
kind: m.kind,
|
|
15973
|
+
confidence: m.confidence,
|
|
15974
|
+
provenance: m.provenance
|
|
15975
|
+
}));
|
|
15976
|
+
const { error: mpErr, count: mpCount } = await this.client.from("standards_mappings").insert(mappingRows, { count: "exact" });
|
|
15977
|
+
if (mpErr) throw new Error("importPack: mapping insert failed: " + mpErr.message);
|
|
15978
|
+
mappingCount = mpCount ?? mappingRows.length;
|
|
16526
15979
|
}
|
|
16527
|
-
|
|
16528
|
-
return {
|
|
16529
|
-
score,
|
|
16530
|
-
weight: 0.15,
|
|
16531
|
-
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
16532
|
-
strengths,
|
|
16533
|
-
findings
|
|
16534
|
-
};
|
|
15980
|
+
return { frameworkDbId: frameworkId, statementsInserted: stCount ?? statementRows.length, mappingsInserted: mappingCount };
|
|
16535
15981
|
}
|
|
16536
|
-
|
|
16537
|
-
|
|
16538
|
-
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
15982
|
+
/** Activate a draft framework (immutable version is now live). */
|
|
15983
|
+
async activatePack(frameworkDbId) {
|
|
15984
|
+
const { error } = await this.client.from("standards_frameworks").update({ status: "active" }).eq("id", frameworkDbId).eq("status", "draft");
|
|
15985
|
+
if (error) throw new Error("activatePack failed: " + error.message);
|
|
15986
|
+
}
|
|
15987
|
+
async deprecatePack(frameworkDbId) {
|
|
15988
|
+
const { error } = await this.client.from("standards_frameworks").update({ status: "deprecated" }).eq("id", frameworkDbId);
|
|
15989
|
+
if (error) throw new Error("deprecatePack failed: " + error.message);
|
|
15990
|
+
}
|
|
15991
|
+
async adoptPack(frameworkDbId, organizationCode, adoptedBy) {
|
|
15992
|
+
const { error } = await this.client.from("standards_org_adoptions").upsert(
|
|
15993
|
+
{ framework_id: frameworkDbId, organization_code: organizationCode, adopted_by: adoptedBy ?? null },
|
|
15994
|
+
{ onConflict: "organization_code,framework_id" }
|
|
15995
|
+
);
|
|
15996
|
+
if (error) throw new Error("adoptPack failed: " + error.message);
|
|
15997
|
+
}
|
|
15998
|
+
// ─── Runtime (read path — generation) ─────────────────────────────────────
|
|
16545
15999
|
/**
|
|
16546
|
-
*
|
|
16000
|
+
* Load all packs usable by an org: platform packs (verified, org IS NULL) +
|
|
16001
|
+
* org packs + org adoptions. Hydrated into the same FrameworkPack shape the
|
|
16002
|
+
* in-memory Phase-1 components consume (injector / coverage gate / judge).
|
|
16547
16003
|
*/
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
const
|
|
16551
|
-
|
|
16552
|
-
const
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
|
|
16560
|
-
|
|
16561
|
-
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
|
|
16565
|
-
|
|
16566
|
-
|
|
16567
|
-
|
|
16568
|
-
|
|
16569
|
-
|
|
16004
|
+
async loadPacksForOrg(organizationCode) {
|
|
16005
|
+
let query = this.client.from("standards_frameworks").select("*, standards_org_adoptions(organization_code)").eq("status", "active");
|
|
16006
|
+
const { data: frameworks, error } = await query;
|
|
16007
|
+
if (error) throw new Error("loadPacksForOrg: " + error.message);
|
|
16008
|
+
const visible = (frameworks ?? []).filter((f) => {
|
|
16009
|
+
const own = f.organization_code && organizationCode && f.organization_code === organizationCode;
|
|
16010
|
+
const global = !f.organization_code;
|
|
16011
|
+
const adopted = organizationCode && (f.standards_org_adoptions ?? []).some((a) => a.organization_code === organizationCode);
|
|
16012
|
+
return global || own || adopted;
|
|
16013
|
+
});
|
|
16014
|
+
if (visible.length === 0) return [];
|
|
16015
|
+
const ids = visible.map((f) => f.id);
|
|
16016
|
+
const [{ data: statements, error: stErr }, { data: mappings, error: mpErr }, { data: overrides, error: ovErr }] = await Promise.all([
|
|
16017
|
+
this.client.from("standards_statements").select("*").in("framework_id", ids),
|
|
16018
|
+
this.client.from("standards_mappings").select("*").in("framework_id", ids),
|
|
16019
|
+
this.client.from("standards_mapping_overrides").select("*").in("framework_id", ids)
|
|
16020
|
+
]);
|
|
16021
|
+
if (stErr) throw new Error("loadPacksForOrg statements: " + stErr.message);
|
|
16022
|
+
const overridesByFw = /* @__PURE__ */ new Map();
|
|
16023
|
+
for (const o of overrides ?? []) {
|
|
16024
|
+
const list = overridesByFw.get(o.framework_id) ?? [];
|
|
16025
|
+
list.push(o);
|
|
16026
|
+
overridesByFw.set(o.framework_id, list);
|
|
16570
16027
|
}
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
|
|
16574
|
-
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
|
|
16578
|
-
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
|
|
16585
|
-
|
|
16586
|
-
|
|
16587
|
-
|
|
16588
|
-
|
|
16589
|
-
|
|
16590
|
-
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
|
|
16594
|
-
|
|
16595
|
-
|
|
16596
|
-
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
}
|
|
16621
|
-
if (missingExplanation) {
|
|
16622
|
-
score -= 10;
|
|
16623
|
-
findings.push({
|
|
16624
|
-
id: `misconception_shallow_explanation_${qId}`,
|
|
16625
|
-
dimension: "MISCONCEPTION_RIGOR",
|
|
16626
|
-
severity: "MAJOR",
|
|
16627
|
-
title: `Shallow Distractor Explanations in ${qId}`,
|
|
16628
|
-
description: `Question ${qId} lacks detailed pedagogical explanations for all options. Students and teachers cannot diagnose root causes without option-level rationale.`,
|
|
16629
|
-
remediationAdvice: "For EVERY option (A, B, C, D), explicitly write why the choice is correct or what mental misconception it represents.",
|
|
16630
|
-
affectedElement: qId
|
|
16631
|
-
});
|
|
16632
|
-
} else {
|
|
16633
|
-
questionsWithFullExplanations++;
|
|
16028
|
+
return visible.map((f) => {
|
|
16029
|
+
const stmts = (statements ?? []).filter((s) => s.framework_id === f.id).map((s) => ({
|
|
16030
|
+
id: s.statement_id,
|
|
16031
|
+
parentId: s.parent_statement_id ?? void 0,
|
|
16032
|
+
gradeBand: [s.grade_min, s.grade_max],
|
|
16033
|
+
texts: s.texts,
|
|
16034
|
+
classifications: s.classifications ?? [],
|
|
16035
|
+
bloomHint: s.bloom_hint ?? void 0,
|
|
16036
|
+
keywords: s.keywords ?? [],
|
|
16037
|
+
sourceRef: s.source_ref ?? void 0,
|
|
16038
|
+
provenance: s.provenance ?? { method: "imported" }
|
|
16039
|
+
}));
|
|
16040
|
+
const ov = overridesByFw.get(f.id) ?? [];
|
|
16041
|
+
const retired = new Set(ov.filter((o) => o.kind === "retired").map((o) => o.statement_id + "|" + o.target_ref));
|
|
16042
|
+
const embedded = (mappings ?? []).filter((m) => m.framework_id === f.id).map((m) => ({
|
|
16043
|
+
statementId: m.statement_id,
|
|
16044
|
+
targetRef: m.target_ref,
|
|
16045
|
+
kind: m.kind,
|
|
16046
|
+
confidence: Number(m.confidence),
|
|
16047
|
+
provenance: m.provenance ?? { method: "imported" }
|
|
16048
|
+
}));
|
|
16049
|
+
const overlay = ov.filter((o) => o.kind !== "retired").map((o) => ({
|
|
16050
|
+
statementId: o.statement_id,
|
|
16051
|
+
targetRef: o.target_ref,
|
|
16052
|
+
kind: o.kind,
|
|
16053
|
+
confidence: Number(o.confidence),
|
|
16054
|
+
provenance: { method: "human", reviewedBy: o.created_by ?? "overlay", note: o.note ?? void 0 }
|
|
16055
|
+
}));
|
|
16056
|
+
const bridge = /* @__PURE__ */ new Map();
|
|
16057
|
+
for (const m of embedded) bridge.set(m.statementId + "|" + m.targetRef, m);
|
|
16058
|
+
for (const m of overlay) bridge.set(m.statementId + "|" + m.targetRef, m);
|
|
16059
|
+
const finalMappings = [...bridge.values()].filter((m) => !retired.has(m.statementId + "|" + m.targetRef));
|
|
16060
|
+
const hydrated = FrameworkPackSchema.safeParse({
|
|
16061
|
+
manifest: {
|
|
16062
|
+
id: f.pack_id,
|
|
16063
|
+
name: f.name,
|
|
16064
|
+
specVersion: f.spec_version || "1.0",
|
|
16065
|
+
contentVersion: f.content_version,
|
|
16066
|
+
subject: f.subject,
|
|
16067
|
+
languages: f.languages,
|
|
16068
|
+
gradeModel: f.grade_model,
|
|
16069
|
+
provenance: f.provenance,
|
|
16070
|
+
trust: f.trust
|
|
16071
|
+
},
|
|
16072
|
+
statements: stmts,
|
|
16073
|
+
mappings: finalMappings
|
|
16074
|
+
});
|
|
16075
|
+
if (!hydrated.success) {
|
|
16076
|
+
throw new Error('loadPacksForOrg: hydrated pack "' + f.pack_id + '" failed schema: ' + hydrated.error.issues[0]?.message);
|
|
16634
16077
|
}
|
|
16635
|
-
|
|
16636
|
-
|
|
16637
|
-
strengths.push("All quiz questions feature rigorous 4-option breakdown with comprehensive misconception analysis.");
|
|
16638
|
-
}
|
|
16639
|
-
score = Math.max(0, Math.min(100, score));
|
|
16640
|
-
return {
|
|
16641
|
-
score,
|
|
16642
|
-
weight: 0.2,
|
|
16643
|
-
passed: score >= 75 && !findings.some((f) => f.severity === "CRITICAL"),
|
|
16644
|
-
strengths,
|
|
16645
|
-
findings
|
|
16646
|
-
};
|
|
16078
|
+
return { ...hydrated.data, dbId: f.id, status: f.status, organizationCode: f.organization_code ?? null };
|
|
16079
|
+
});
|
|
16647
16080
|
}
|
|
16648
16081
|
};
|
|
16649
16082
|
|
|
@@ -17134,6 +16567,6 @@ function renderMediaPlaceholder(entry) {
|
|
|
17134
16567
|
return `> \u{1F5BC}\uFE0F [\u1EA2nh \u0111ang ch\u1EDD: ${entry.name} \u2014 s\u1EBD \u0111\u01B0\u1EE3c t\u1EA1o sau khi duy\u1EC7t media]`;
|
|
17135
16568
|
}
|
|
17136
16569
|
|
|
17137
|
-
export { ACT_TEMPLATE, ARTIFACT_EXECUTION_ORDER, AcademicAuditor, ActiveLearningWorkflowSchema, ActivityLabSchema, ActivitySeqRowSchema, ActivityStepSchema, ArtifactContractRowSchema, AssessmentMapRowSchema, AssessmentObjectiveSchema, BELL_RINGER_TEMPLATE, BLOOM_ACTION_VERBS, BOM_TEMPLATE, BUNDLED_STANDARDS_PACK_IDS, BellRingerDaySchema, BellRingerSchema, BloomHintSchema, BloomLevelSchema, BloomTaxonomyEvaluator, BronzeTierSchema, CERConclusionSchema, CHOICE_BOARD_TEMPLATE, CODE_LAB_TEMPLATE, CURRICULUM_SLASH_COMMANDS, CardTierEnum, ChoiceBoardRubricRowSchema, ChoiceBoardSchema, ChoiceRuleSchema, ChoiceSquareSchema, ClassificationSchema, CodeHardwareFeasibilityEvaluator, CodeLabSchema, CodeSnippetSchema, CompetencyRubricRowSchema, ComputationalThinkingSchema, ConstructiveAlignmentEvaluator, CoreConceptSchema, CourseObjectiveSchema, CoverageReportSchema, CrossArtifactDriftEvaluator, CurriculumArtifactSchema, CurriculumError, CurriculumPlanSchema, CurriculumQualityReportSchema, DEFAULT_ENABLED_PROVIDERS, DEFAULT_GATE_SETTINGS, DependencyEdgeSchema, DepthAssignmentSchema, DepthLevelSchema, DeterministicPipelineRunner, DeterministicStructuralLinter, DiagnosticQuestionSchema, DiagnosticQuizSchema, EDPPhaseEnum, EXIT_TICKET_TEMPLATE, EXPOSITION_REL, EXT_TEMPLATE, EntryLevelSchema, ExitTicketBugHuntSchema, ExitTicketMetacognitionSchema, ExitTicketQuickItemSchema, ExitTicketRecallPromptSchema, ExitTicketSchema, ExperimentalDesignSchema, ExpositionApprovalError, ExtensionChallengeSchema, ExtensionSchema, FileSystemCurriculumAdapter, FiveEFlowPhaseSchema, FiveEInstructionalEvaluator, FiveEPhaseEnum, FrameworkMappingSchema, FrameworkPackManifestSchema, FrameworkPackSchema, FrameworkStatementSchema, GLOSSARY_TEMPLATE, GRAPHIC_ORGANIZER_TEMPLATE, GlossaryQuickRefRowSchema, GlossarySchema, GlossaryTermSchema, GoldTierSchema, GradeBandSchema, GraphicOrganizerItemSchema, GraphicOrganizerSchema, HANDOUT_TEMPLATE, HandoutSchema, HandoutSectionSchema, InstructionSectionSchema, InstructionStepSchema, JudgeCriterionSchema, LESSON_PLAN_TEMPLATE, LLMJudgeEngine, LabTierTaskSchema, LearningObjectiveInputSchema, LearningObjectiveRowSchema, LessonFlowPhaseSchema, LessonPlan5ESchema, LessonPlanEDPSchema, LessonPlanSchema, LessonSectionSchema, LocalWorkspaceManager, MappingKindSchema, MarpSlideSchema, MediaError, MediaLedger, MentorCheatsheetSchema, MilestoneInputSchema, MisconceptionEvaluator, MisconceptionSchema, ModelPolicyResolver, NVIDIA_MODELS, NodeKindSchema, OrganizerTypeEnum, PROJECT_INSTRUCTION_TEMPLATE, PhaseInputSchema, PlanConstraintsSchema, PlannerInputSchema, PlanningGraphSchema, PlanningNodeSchema, PrerequisiteDecisionEntrySchema, PrerequisiteDecisionSchema, ProductGoalSchema, ProgressiveHintsSchema, ProjectGraphSchema, ProjectInstructionSchema, ProjectProfileSchema, QUIZ_TEMPLATE, QualityAuditVerdictSchema, QuizOptionSchema, REVIEW_GAME_TEMPLATE, RUBRIC_TEMPLATE, RecordProvenanceSchema, ResourceMapRowSchema, ReviewGameExportRowSchema, ReviewGameQuestionSchema, ReviewGameSchema, RoadmapInputSchema, RotationStationSchema, RubricCriteriaSchema, RubricSchema, SCIENCE_LAB_TEMPLATE, SELF_LAB_TEMPLATE, SLIDE_LAYOUT_PRESETS, SLIDE_TEMPLATE, STANDARD_SOT_FILES, STATION_ROTATION_TEMPLATE, ScaffoldDecisionEntrySchema, ScaffoldDecisionSchema, ScienceLabSchema, ScoringRubricRowSchema, SelfLabSchema, SelfPacedBundleSchema, SelfPacedChallengeSchema, SessionPlanSchema, SilverTierSchema, SlideDeckSchema, StandardizedTermRowSchema, StandardsRegistryAdapter, StationRecordRowSchema, StationRotationSchema, StationTypeEnum, SupabaseCurriculumAdapter, TASK_CARDS_TEMPLATE, TEACHER_GUIDE_TEMPLATE, TIERED_PRACTICE_TEMPLATE, TaskCardSchema, TaskCardsSchema, TeacherGuideSchema, TeachingScriptPhaseSchema, TierLevelEnum, TierObjectiveSchema, TieredPracticeSchema, TranslationPolicySchema, TroubleshootingEntrySchema, TroubleshootingRowSchema, UnitPlanSchema, UserJourneySchema, WORKSHEET_TEMPLATE, WorksheetItemSchema, WorksheetItemTypeEnum, WorksheetPartSchema, WorksheetSchema, activityTools, analystTools, analyzeProjectCreationIntent,
|
|
16570
|
+
export { ACT_TEMPLATE, ARTIFACT_EXECUTION_ORDER, AcademicAuditor, ActiveLearningWorkflowSchema, ActivityLabSchema, ActivitySeqRowSchema, ActivityStepSchema, ArtifactContractRowSchema, AssessmentMapRowSchema, AssessmentObjectiveSchema, BELL_RINGER_TEMPLATE, BLOOM_ACTION_VERBS, BOM_TEMPLATE, BUNDLED_STANDARDS_PACK_IDS, BellRingerDaySchema, BellRingerSchema, BloomHintSchema, BloomLevelSchema, BloomTaxonomyEvaluator, BronzeTierSchema, CERConclusionSchema, CHOICE_BOARD_TEMPLATE, CODE_LAB_TEMPLATE, CURRICULUM_SLASH_COMMANDS, CardTierEnum, ChoiceBoardRubricRowSchema, ChoiceBoardSchema, ChoiceRuleSchema, ChoiceSquareSchema, ClassificationSchema, CodeHardwareFeasibilityEvaluator, CodeLabSchema, CodeSnippetSchema, CompetencyRubricRowSchema, ComputationalThinkingSchema, ConstructiveAlignmentEvaluator, CoreConceptSchema, CourseObjectiveSchema, CoverageReportSchema, CrossArtifactDriftEvaluator, CurriculumArtifactSchema, CurriculumError, CurriculumPlanSchema, CurriculumQualityReportSchema, DEFAULT_ENABLED_PROVIDERS, DEFAULT_GATE_SETTINGS, DEFAULT_STREAM_IDLE_MS, DEFAULT_STREAM_TOTAL_MS, DependencyEdgeSchema, DepthAssignmentSchema, DepthLevelSchema, DeterministicPipelineRunner, DeterministicStructuralLinter, DiagnosticQuestionSchema, DiagnosticQuizSchema, EDPPhaseEnum, EXIT_TICKET_TEMPLATE, EXPOSITION_REL, EXT_TEMPLATE, EntryLevelSchema, ExitTicketBugHuntSchema, ExitTicketMetacognitionSchema, ExitTicketQuickItemSchema, ExitTicketRecallPromptSchema, ExitTicketSchema, ExperimentalDesignSchema, ExpositionApprovalError, ExtensionChallengeSchema, ExtensionSchema, FileSystemCurriculumAdapter, FiveEFlowPhaseSchema, FiveEInstructionalEvaluator, FiveEPhaseEnum, FrameworkMappingSchema, FrameworkPackManifestSchema, FrameworkPackSchema, FrameworkStatementSchema, GLOSSARY_TEMPLATE, GRAPHIC_ORGANIZER_TEMPLATE, GlossaryQuickRefRowSchema, GlossarySchema, GlossaryTermSchema, GoldTierSchema, GradeBandSchema, GraphicOrganizerItemSchema, GraphicOrganizerSchema, HANDOUT_TEMPLATE, HandoutSchema, HandoutSectionSchema, InstructionSectionSchema, InstructionStepSchema, JudgeCriterionSchema, LAYER_TOTAL_BUDGET_MS, LESSON_PLAN_TEMPLATE, LLMJudgeEngine, LabTierTaskSchema, LearningObjectiveInputSchema, LearningObjectiveRowSchema, LessonFlowPhaseSchema, LessonPlan5ESchema, LessonPlanEDPSchema, LessonPlanSchema, LessonSectionSchema, LocalWorkspaceManager, MappingKindSchema, MarpSlideSchema, MediaError, MediaLedger, MentorCheatsheetSchema, MilestoneInputSchema, MisconceptionEvaluator, MisconceptionSchema, ModelPolicyResolver, NVIDIA_MODELS, NodeKindSchema, OrganizerTypeEnum, PROJECT_INSTRUCTION_TEMPLATE, PhaseInputSchema, PlanConstraintsSchema, PlannerInputSchema, PlanningGraphSchema, PlanningNodeSchema, PrerequisiteDecisionEntrySchema, PrerequisiteDecisionSchema, ProductGoalSchema, ProgressiveHintsSchema, ProjectGraphSchema, ProjectInstructionSchema, ProjectProfileSchema, QUIZ_TEMPLATE, QualityAuditVerdictSchema, QuizOptionSchema, REVIEW_GAME_TEMPLATE, RUBRIC_TEMPLATE, RecordProvenanceSchema, ResourceMapRowSchema, ReviewGameExportRowSchema, ReviewGameQuestionSchema, ReviewGameSchema, RoadmapInputSchema, RotationStationSchema, RubricCriteriaSchema, RubricSchema, SCIENCE_LAB_TEMPLATE, SELF_LAB_TEMPLATE, SLIDE_LAYOUT_PRESETS, SLIDE_TEMPLATE, STANDARD_SOT_FILES, STATION_ROTATION_TEMPLATE, ScaffoldDecisionEntrySchema, ScaffoldDecisionSchema, ScienceLabSchema, ScoringRubricRowSchema, SelfLabSchema, SelfPacedBundleSchema, SelfPacedChallengeSchema, SessionPlanSchema, SilverTierSchema, SlideDeckSchema, StandardizedTermRowSchema, StandardsRegistryAdapter, StationRecordRowSchema, StationRotationSchema, StationTypeEnum, SupabaseCurriculumAdapter, TASK_CARDS_TEMPLATE, TEACHER_GUIDE_TEMPLATE, TIERED_PRACTICE_TEMPLATE, TaskCardSchema, TaskCardsSchema, TeacherGuideSchema, TeachingScriptPhaseSchema, TierLevelEnum, TierObjectiveSchema, TieredPracticeSchema, TranslationPolicySchema, TroubleshootingEntrySchema, TroubleshootingRowSchema, UnitPlanSchema, UserJourneySchema, WORKSHEET_TEMPLATE, WorksheetItemSchema, WorksheetItemTypeEnum, WorksheetPartSchema, WorksheetSchema, activityTools, analystTools, analyzeProjectCreationIntent, assertAcyclic, assessorTools, auditCurriculumQualityFlow, auditQualityReport, buildActivityPrompt, buildCodeLabPrompt, buildCurriculumContext, buildCurriculumPlan, buildDeliveryPackages, buildDiagnosticQuizPrompt, buildExpositionContext, buildExtensionPrompt, buildHandoutPrompt, buildImagePrompt, buildJudgePrompt, buildLessonMasterPrompt, buildProjectInstructionPrompt, buildSelfLabPrompt, buildSessionSliceContext, buildSlidesPrompt, buildStandardStackMarkdown, buildStandardsContext, buildStandardsContextBlock, buildTeacherGuidePrompt, buildWorksheetPrompt, computeContentHash, computePackingSpec, conductPreliminaryResearch, contentTools, convertRoadmapToFoundationSot, createAiInferenceError, createCurriculumStorage, createStreamAbortSignal, curateMediaLedger, designerTools, detectProjectPedagogy, ensureExpositionForLesson, ensureKnowledgeExposition, evaluateStandardsCoverage, executeCurriculumCommand, exportAllProjectQuizzes, expositionCacheKey, extractScopeSequenceRows, extractSessionSlice, extractStreamChunk, extractThoughtAndContent, formatQuizzesToCsv, fulfillMediaLedger, gateModeFor, generateActivityFlow, generateCodeLabFlow, generateDiagnosticQuizFlow, generateEducationalImage, generateExtensionFlow, generateHandoutFlow, generateLessonMasterFlow, generateMilestoneCurriculumBundle, generatePhase1SotArtifacts, generatePhase2SotArtifacts, generateProjectInstruction, generateProjectInstructionFlow, generateRoadmapCurriculum, generateSelfLabFlow, generateSelfPacedBundle, generateSingleArtifact, generateSlidesFlow, generateTeacherGuideFlow, generateWorksheetFlow, getAIModel, getArtifactMetadata, getDesignatedFallbackChain, getDesignatedFallbackConfig, getLessonMetadata, getProjectArtifactScope, getProjectStatusReport, getSlideLayoutPresetById, getSlideLayoutPresetsByCategory, getSmartResumeContext, glossaryTermsForSession, illustratorTools, importRoadmapToProject, ingestProjectGraphIntoProject, initializeProjectInStorage, injectMediaIntoMarkdown, isExpositionFresh, isModelAllowed, isProviderEnabled, isTranslationDue, lintAndSanitizeArtifact, lintCurriculumFramework, lintFrameworkPack, loadCurriculumTemplate, loadSotTemplate, normalizePlanningGraph, packagerTools, parseGateSettings, parseQuizMarkdown, parseRoadmapJsonToProjectPayload, produceBatchLessons, produceSingleLesson, publishToGitHub, publishToSupabase, rankGenCandidates, renderFrameworkFromPlan, renderMediaPlaceholder, researcherTools, resolveGateSettings, resolveStandardsPacks, resolveStreamBudget, resolveTranslationTargets, reviewerTools, runCurriculumAIInference, safeParseJson, searchEducationalImages, searchEducationalVideos, selectStatementsForLesson, serializeActivityToMarkdown, serializeCodeLabToMarkdown, serializeDiagnosticQuizToMarkdown, serializeHandoutToMarkdown, serializeLessonToMarkdown, serializeProjectInstructionToMarkdown, serializeSelfLabToMarkdown, slugifyProjectName, streamCurriculumAIInference, streamCurriculumAIInferenceWithTools, streamDiagnosticQuizFlow, streamLLMWithFallback, streamLayerPrefillWithFallback, streamLessonMasterFlow, streamSelfLabFlow, techSmeTools, topoSort, uploadAssetToBucket, validateCurriculumPlan, validateFrameworkPack, validateMarkdownTables, validateMermaidSyntax, withAutoRepair };
|
|
17138
16571
|
//# sourceMappingURL=index.mjs.map
|
|
17139
16572
|
//# sourceMappingURL=index.mjs.map
|