import { chaptersWithPhysicsSeeds, getAllLessonSeeds, type LessonSeedContentType, type TuitionLessonSeed, } from "./physicsLessonSeeds"; import { getPhysicsMissionBlueprint, getPhysicsMissionBlueprints, getMissingBlueprintRequirements, type PhysicsMissionBlueprintType, } from "./physicsMissionBlueprints"; import { getPhysicsSourcePack, getPhysicsSourcePacks, type PhysicsChapterSourcePack } from "./physicsSourcePacks"; import { evaluateMagneticEffectCoverageGate } from "./magneticEffectCoverageGate"; /** * Curation status distinguishes *source readiness* from *teachable curation*: * - "curated" — final teachable seeds exist and are registered. * - "curation_ready" — source repaired + evidence gate passed; final seeds MISSING (Phase 6J). * - "draftable" — source strong enough to draft review-only, not gate-passed. * - "blocked" — source too thin; needs manual source repair first. */ export type ChapterCurationStatus = "curated" | "curation_ready" | "draftable" | "blocked"; export type DraftSeedStatus = | "curated" | "draft_needs_review" | "blocked_missing_source" | "blocked_missing_required_sections"; export type PhysicsDraftLessonSeed = { missionId: string; chapterId: string; chapterTitle: string; missionTitle: string; status: DraftSeedStatus; canTeach: boolean; missingFields: string[]; warnings: string[]; seed?: TuitionLessonSeed; }; export type PhysicsSeedCoverage = { chapterId: string; chapterTitle: string; curatedSeedCount: number; missionIds: string[]; missionTypes: LessonSeedContentType[]; }; export type PhysicsDraftSeedPlan = { chapterId: string; chapterTitle: string; status: DraftSeedStatus; sourceConfidence: PhysicsChapterSourcePack["coverageConfidence"] | "missing"; recommendedBlueprints: PhysicsMissionBlueprintType[]; missingSignals: string[]; warnings: string[]; nextAction: string; }; export type PhysicsSeedFactoryReport = { totalPhysicsChapters: number; curatedChapters: string[]; uncuratedChapters: string[]; blockedChapters: Array<{ chapterId: string; reason: string }>; draftableChapters: string[]; /** Chapters whose thin extraction was repaired by a manual evidence override (Phase 6I). */ sourceRepairedChapters: string[]; /** Source-repaired + gate-passed chapters awaiting final seed authoring (not teachable yet). */ curationReadyChapters: string[]; nextRecommendedBatch: string[]; /** Human-readable next action so source repair is never confused with final curation. */ nextRecommendedAction: string; coverageByChapter: Array<{ chapterId: string; chapterTitle: string; sourceConfidence: PhysicsChapterSourcePack["coverageConfidence"]; curatedSeedCount: number; curationStatus: ChapterCurationStatus; sourceRepaired: boolean; finalSeedsPresent: boolean; missingMissionTypes: PhysicsMissionBlueprintType[]; warnings: string[]; }>; }; export type PhysicsSeedFactoryStatus = { officialCuratedChapters: string[]; uncuratedChapters: string[]; draftableChapters: string[]; blockedChapters: Array<{ chapterId: string; reason: string }>; officialSeedCount: number; }; function unique(values: T[]): T[] { return Array.from(new Set(values)); } function curatedSeedsFor(chapterId: string): TuitionLessonSeed[] { return getAllLessonSeeds().filter((seed) => seed.chapterId === chapterId); } function missionTypeFromContentType(contentType: LessonSeedContentType): PhysicsMissionBlueprintType { if (contentType === "concept") return "concept"; if (contentType === "diagram") return "diagram"; if (contentType === "formula") return "formula"; if (contentType === "numerical") return "numerical"; if (contentType === "exam_answer") return "exam_answer"; if (contentType === "revision") return "revision"; if (contentType === "pyq_placeholder") return "pyq_placeholder"; return "concept"; } function recommendedBlueprintsFor(pack: PhysicsChapterSourcePack): PhysicsMissionBlueprintType[] { const signals = pack.extractedSignals; const recommended: PhysicsMissionBlueprintType[] = []; if (signals.definitions.length || signals.keyTerms.length) recommended.push("concept"); if (signals.diagrams.length) recommended.push("diagram"); if (signals.formulas.length) recommended.push("formula"); if (signals.formulas.length && signals.numericalPatterns.length) recommended.push("numerical"); if (signals.possibleQuestions.length) recommended.push("exam_answer"); if (signals.definitions.length || signals.formulas.length || signals.possibleQuestions.length) recommended.push("revision"); return recommended.length ? unique(recommended) : ["concept", "exam_answer", "revision"]; } function missingSignalsFor(pack: PhysicsChapterSourcePack, blueprint: PhysicsMissionBlueprintType): string[] { const signals = pack.extractedSignals; const missing: string[] = []; const requiredSignals = getPhysicsMissionBlueprint(blueprint).requiredSignals; if (requiredSignals.includes("definitions") && signals.definitions.length === 0) missing.push("definitions"); if (requiredSignals.includes("keyTerms") && signals.keyTerms.length === 0) missing.push("key terms"); if (requiredSignals.includes("formulas") && signals.formulas.length === 0) missing.push("formulas"); if (requiredSignals.includes("visualBoard") && signals.diagrams.length === 0) missing.push("visual board frames"); if (requiredSignals.includes("solvedExample") && signals.numericalPatterns.length === 0) missing.push("solved numerical example"); if (requiredSignals.includes("examAnswers") && signals.possibleQuestions.length === 0) missing.push("exam answer questions"); if (requiredSignals.includes("keywords") && signals.keyTerms.length === 0) missing.push("exam keywords"); if (requiredSignals.includes("flashcards") && signals.definitions.length === 0) missing.push("flashcards/revision anchors"); if (requiredSignals.includes("weakAreaTags") && signals.keyTerms.length === 0) missing.push("weak area tags"); if (requiredSignals.includes("curatedPyqEvidence")) missing.push("curated PYQ evidence"); return unique(missing); } function draftMissingFields(pack: PhysicsChapterSourcePack, blueprint: PhysicsMissionBlueprintType): string[] { return unique([ "human-authored 11-step lesson seed", "human review approval", ...missingSignalsFor(pack, blueprint), ...getMissingBlueprintRequirements(blueprint, { sourceRefs: pack.sourceRefs, formulas: pack.extractedSignals.formulas.map((formula) => ({ formula, symbols: [] })), solvedExamples: pack.extractedSignals.numericalPatterns, visualFrames: pack.extractedSignals.diagrams, examAnswers: pack.extractedSignals.possibleQuestions.map(() => ({ keywords: pack.extractedSignals.keyTerms })), flashcards: pack.extractedSignals.definitions, weakAreaTags: pack.extractedSignals.keyTerms, pyqEvidenceStatus: "not_curated", }), ]); } export function getCuratedPhysicsSeedCoverage(): PhysicsSeedCoverage[] { const byChapter = new Map(); for (const seed of getAllLessonSeeds()) { byChapter.set(seed.chapterId, [...(byChapter.get(seed.chapterId) ?? []), seed]); } return Array.from(byChapter.entries()).map(([chapterId, seeds]) => ({ chapterId, chapterTitle: seeds[0]?.chapterTitle ?? chapterId, curatedSeedCount: seeds.length, missionIds: seeds.map((seed) => seed.missionId), missionTypes: unique(seeds.map((seed) => seed.contentType)), })); } export function getUncuratedPhysicsChapters(): PhysicsChapterSourcePack[] { const curated = new Set(chaptersWithPhysicsSeeds()); return getPhysicsSourcePacks().filter((pack) => !curated.has(pack.chapterId)); } export function createPhysicsDraftSeedPlan(chapterId: string): PhysicsDraftSeedPlan { const pack = getPhysicsSourcePack(chapterId); if (!pack) { return { chapterId, chapterTitle: chapterId, status: "blocked_missing_source", sourceConfidence: "missing", recommendedBlueprints: [], missingSignals: ["source pack"], warnings: ["No Physics source pack exists for this chapter."], nextAction: "Locate source metadata before drafting any lesson seed.", }; } if (chaptersWithPhysicsSeeds().includes(chapterId)) { return { chapterId, chapterTitle: pack.chapterTitle, status: "curated", sourceConfidence: pack.coverageConfidence, recommendedBlueprints: [], missingSignals: [], warnings: pack.warnings, nextAction: "Already curated. Do not regenerate from the factory.", }; } const recommendedBlueprints = recommendedBlueprintsFor(pack); const missingSignals = unique(recommendedBlueprints.flatMap((blueprint) => missingSignalsFor(pack, blueprint))); const blocked = pack.coverageConfidence === "low"; return { chapterId, chapterTitle: pack.chapterTitle, status: blocked ? "blocked_missing_required_sections" : "draft_needs_review", sourceConfidence: pack.coverageConfidence, recommendedBlueprints, missingSignals, warnings: pack.warnings, nextAction: blocked ? "Create a human curation TODO before drafting. Extracted signals are too thin for safe seed drafting." : "Draft review-only seeds from the recommended blueprints; keep canTeach=false until human-authored content validates.", }; } export function createPhysicsDraftSeedsForChapter(chapterId: string): PhysicsDraftLessonSeed[] { const pack = getPhysicsSourcePack(chapterId); if (!pack) { return [ { missionId: "TODO", chapterId, chapterTitle: chapterId, missionTitle: "Source pack missing", status: "blocked_missing_source", canTeach: false, missingFields: ["source pack", "sourceRefs"], warnings: ["No Physics source pack exists for this chapter."], }, ]; } const curatedSeeds = curatedSeedsFor(chapterId); if (curatedSeeds.length > 0) { return curatedSeeds.map((seed) => ({ missionId: seed.missionId, chapterId: seed.chapterId, chapterTitle: seed.chapterTitle, missionTitle: seed.missionTitle, status: "curated", canTeach: seed.canTeach, missingFields: [], warnings: seed.qualityFlags.reason ? [seed.qualityFlags.reason] : [], seed, })); } const plan = createPhysicsDraftSeedPlan(chapterId); if (plan.status === "blocked_missing_required_sections") { return [ { missionId: "TODO", chapterId: pack.chapterId, chapterTitle: pack.chapterTitle, missionTitle: `Review source pack for ${pack.chapterTitle}`, status: "blocked_missing_required_sections", canTeach: false, missingFields: unique(["strong source signals", ...plan.missingSignals, "human-authored 11-step lesson seed"]), warnings: plan.warnings, }, ]; } return plan.recommendedBlueprints.map((blueprint, index) => ({ missionId: `D${index + 1}`, chapterId: pack.chapterId, chapterTitle: pack.chapterTitle, missionTitle: `${getPhysicsMissionBlueprint(blueprint).title} - ${pack.chapterTitle}`, status: "draft_needs_review", canTeach: false, missingFields: draftMissingFields(pack, blueprint), warnings: unique([ ...plan.warnings, "Draft output is review-only and is not registered in Tuition Mode.", "No official canTeach=true seed is produced by the factory.", ]), })); } /** * Curation status per chapter. Distinguishes source repair (Phase 6I) from final * teachable curation. A source-repaired chapter that passes its evidence gate is * "curation_ready" — its final seeds are still MISSING and it is NOT teachable. */ function curationStatusFor(pack: PhysicsChapterSourcePack, hasSeeds: boolean): ChapterCurationStatus { if (hasSeeds) return "curated"; if (pack.sourceRepaired) { if (pack.chapterId === "phy-p1-c4") { const gate = evaluateMagneticEffectCoverageGate(); if (gate.status === "curation_ready") return "curation_ready"; if (gate.status === "draftable") return "draftable"; return "blocked"; } return "draftable"; } return pack.coverageConfidence === "low" ? "blocked" : "draftable"; } export function createPhysicsBatchCurationReport(): PhysicsSeedFactoryReport { const packs = getPhysicsSourcePacks(); const coverage = getCuratedPhysicsSeedCoverage(); const curatedByChapter = new Map(coverage.map((item) => [item.chapterId, item])); const curatedChapters = coverage.map((item) => item.chapterId); const uncurated = packs.filter((pack) => !curatedByChapter.has(pack.chapterId)); const statusByChapter = new Map( packs.map((pack) => [pack.chapterId, curationStatusFor(pack, curatedByChapter.has(pack.chapterId))]), ); const blockedChapters = packs .filter((pack) => statusByChapter.get(pack.chapterId) === "blocked") .map((pack) => ({ chapterId: pack.chapterId, reason: pack.warnings[0] ?? "Source too thin to draft." })); const draftableChapters = packs .filter((pack) => statusByChapter.get(pack.chapterId) === "draftable") .map((pack) => pack.chapterId); const curationReadyChapters = packs .filter((pack) => statusByChapter.get(pack.chapterId) === "curation_ready") .map((pack) => pack.chapterId); const sourceRepairedChapters = packs.filter((pack) => pack.sourceRepaired).map((pack) => pack.chapterId); // Priority of the next real work: // 1. curation_ready chapters → author final teaching seeds (Phase 6J). // 2. draftable chapters → draft review-only seeds. // 3. blocked chapters → repair the source first. let nextRecommendedBatch: string[]; let nextRecommendedAction: string; if (curationReadyChapters.length > 0) { nextRecommendedBatch = curationReadyChapters.slice(0, 2); nextRecommendedAction = `Phase 6J: curate final lesson seeds for ${curationReadyChapters.join(", ")} (source repaired, final seeds missing, not teachable yet).`; } else if (draftableChapters.length > 0) { nextRecommendedBatch = draftableChapters.slice(0, 2); nextRecommendedAction = `Draft review-only seeds for ${draftableChapters.slice(0, 2).join(", ")}.`; } else if (blockedChapters.length > 0) { nextRecommendedBatch = blockedChapters.map((chapter) => chapter.chapterId).slice(0, 2); nextRecommendedAction = `Repair the source layer for ${blockedChapters.map((c) => c.chapterId).slice(0, 2).join(", ")} before drafting.`; } else { nextRecommendedBatch = []; nextRecommendedAction = "Physics is complete — all 7 chapters are curated. Next: a Physics QA pass, or begin Chemistry source repair/curation."; } return { totalPhysicsChapters: packs.length, curatedChapters, uncuratedChapters: uncurated.map((pack) => pack.chapterId), blockedChapters, draftableChapters, sourceRepairedChapters, curationReadyChapters, nextRecommendedBatch, nextRecommendedAction, coverageByChapter: packs.map((pack) => { const item = curatedByChapter.get(pack.chapterId); const existingMissionTypes = new Set((item?.missionTypes ?? []).map(missionTypeFromContentType)); const missingMissionTypes = getPhysicsMissionBlueprints() .filter((blueprint) => blueprint.canBeTeachable) .map((blueprint) => blueprint.type) .filter((type) => !existingMissionTypes.has(type)); return { chapterId: pack.chapterId, chapterTitle: pack.chapterTitle, sourceConfidence: pack.coverageConfidence, curatedSeedCount: item?.curatedSeedCount ?? 0, curationStatus: statusByChapter.get(pack.chapterId) ?? "blocked", sourceRepaired: Boolean(pack.sourceRepaired), finalSeedsPresent: (item?.curatedSeedCount ?? 0) > 0, missingMissionTypes, warnings: pack.warnings, }; }), }; } export function getPhysicsSeedFactoryStatus(): PhysicsSeedFactoryStatus { const report = createPhysicsBatchCurationReport(); return { officialCuratedChapters: report.curatedChapters, uncuratedChapters: report.uncuratedChapters, draftableChapters: report.draftableChapters, blockedChapters: report.blockedChapters, // Day 20 — the seed pool now includes the first Chemistry draft; this // factory reports PHYSICS curation status, so count Physics seeds only. officialSeedCount: getAllLessonSeeds().filter((seed) => seed.subject === "Physics").length, }; }