File size: 5,506 Bytes
65d68f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 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();
|