import { existsSync } from "node:fs"; import { mkdir, writeFile } from "node:fs/promises"; import { join } from "node:path"; import chapterManifest from "../../data/curriculum/kerala-sslc/physics/chapter-manifest.json"; import { buildCaptionCues, buildNarrationSegments } from "../../src/video-engine/physics/audioTiming"; import { soundWavesLessonContract, soundWavesVisualBeatGraph, } from "../../src/video-engine/physics/data/soundWavesContinuousLesson"; import { validateLessonContract } from "../../src/video-engine/physics/validation"; const outputDir = join( process.cwd(), "data", "curriculum", "kerala-sslc", "physics", "generated", soundWavesLessonContract.chapterId, soundWavesLessonContract.lessonId, ); async function writeJson(name: string, value: unknown) { await writeFile(join(outputDir, name), `${JSON.stringify(value, null, 2)}\n`, "utf8"); } async function main() { await mkdir(outputDir, { recursive: true }); const issues = validateLessonContract(soundWavesLessonContract, soundWavesVisualBeatGraph); const errors = issues.filter((issue) => issue.severity === "error"); if (errors.length) throw new Error(`Lesson contract failed validation: ${JSON.stringify(errors)}`); await writeJson("lesson-contract.json", soundWavesLessonContract); await writeJson("visual-beat-graph.json", soundWavesVisualBeatGraph); await writeJson("narration-segments.json", buildNarrationSegments(soundWavesVisualBeatGraph)); await writeJson("captions.json", buildCaptionCues(soundWavesVisualBeatGraph)); await writeJson("coverage-report.json", { lessonId: soundWavesLessonContract.lessonId, chapterId: soundWavesLessonContract.chapterId, coveredEntries: soundWavesLessonContract.sourceCoverage, beatCount: soundWavesVisualBeatGraph.beats.length, coveredBeatCount: new Set(soundWavesLessonContract.sourceCoverage.flatMap((entry) => entry.visualBeatIds)).size, completeForLesson: true, completeForChapter: false, note: "This report covers the implemented wave-motion lesson, not the entire Sound Waves chapter.", }); await writeJson("qa-report.json", { lessonId: soundWavesLessonContract.lessonId, passed: issues.every((issue) => issue.severity !== "error"), issues, gates: { sourceReferences: true, questionRevealOrder: true, continuousTimeline: true, captionChunking: true, measuredAudioTiming: false, productionAudio: false, fullChapterCoverage: false, }, }); const curriculumRoot = join(process.cwd(), "data", "curriculum", "kerala-sslc", "physics"); const beatsById = new Map(soundWavesVisualBeatGraph.beats.map((beat) => [beat.beatId, beat])); const coverageEntries = soundWavesLessonContract.sourceCoverage.map((entry) => { const beat = beatsById.get(entry.visualBeatIds[0]); return { sourceId: soundWavesLessonContract.teachingSequence[0]?.sourceReferences[0]?.sourceId, pageNumber: entry.pageNumber, blockId: entry.blockId, chapterId: soundWavesLessonContract.chapterId, lessonId: soundWavesLessonContract.lessonId, narrationSegmentIds: entry.narrationBeatIds, visualBeatIds: entry.visualBeatIds, renderedTimestamp: beat ? { startMs: beat.estimatedStartMs, endMs: beat.estimatedEndMs } : null, }; }); await writeFile( join(curriculumRoot, "textbook-coverage.json"), `${JSON.stringify({ schemaVersion: 1, generatorVersion: "docdoe-physics-video-v1", entries: coverageEntries, summary: { implementedLessonCount: 1, completeChapterCount: 0, note: "Coverage is complete for the implemented wave-motion lesson only.", }, }, null, 2)}\n`, "utf8", ); const chapters = chapterManifest.chapters.map((chapter) => { const isSoundWaves = chapter.chapterId === soundWavesLessonContract.chapterId; const representativePreviewPath = "outputs/video-engine/continuous-sound-waves/sound-waves-continuous-preview.mp4"; const representativePreviewReady = isSoundWaves && existsSync(join(process.cwd(), representativePreviewPath)); return { chapterId: chapter.chapterId, chapterNumber: chapter.chapterNumber, title: chapter.title, sourcePages: chapter.sourcePages, sourceIngested: true, status: isSoundWaves ? "representative_lesson_ready" : "source_ingested", lessonIds: isSoundWaves ? [soundWavesLessonContract.lessonId] : [], lowResolutionPreviewReady: representativePreviewReady, representativePreviewPath: representativePreviewReady ? representativePreviewPath : null, productionReady: false, chapterCoverageComplete: false, blockingGates: isSoundWaves ? ["measured_audio_timing", "production_audio", "full_chapter_coverage"] : ["lesson_compilation", "visual_primitive_validation", "representative_preview"], }; }); await writeFile( join(curriculumRoot, "all-chapter-generation-manifest.json"), `${JSON.stringify({ schemaVersion: 1, board: chapterManifest.board, classLevel: chapterManifest.classLevel, subject: chapterManifest.subject, chapters, rollout: { currentStage: "A", nextGate: "Attach measured narration audio and validate the Sound Waves preview before Stage B.", }, }, null, 2)}\n`, "utf8", ); console.log(JSON.stringify({ outputDir, lessonFiles: 6, curriculumFiles: 2, issues }, null, 2)); } void main();