Download src/video-engine/physics/validation.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 4.17 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/video-engine/physics/validation.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/video-engine/physics/validation.ts
-
curl -L -o validation.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/video-engine/physics/validation.ts
4.17 kB
| import type { LessonContract, VisualAction, VisualBeatGraph } from "./contracts"; | |
| export type EngineValidationIssue = { | |
| severity: "error" | "warning"; | |
| code: string; | |
| message: string; | |
| beatId?: string; | |
| }; | |
| const IMPLEMENTED_ACTIONS = new Set<VisualAction["type"]>([ | |
| "add_object", | |
| "remove_object", | |
| "move_object", | |
| "draw_arrow", | |
| "write_text", | |
| "write_equation", | |
| "reveal_label", | |
| "highlight", | |
| "dim", | |
| "focus", | |
| "plot_graph", | |
| "animate_particles", | |
| "trace_wave", | |
| "show_measurement", | |
| "question", | |
| "thinking_pause", | |
| "hint", | |
| "answer_reveal", | |
| "clear_region", | |
| "mascot_reaction", | |
| ]); | |
| export function validateVisualBeatGraph(graph: VisualBeatGraph): EngineValidationIssue[] { | |
| const issues: EngineValidationIssue[] = []; | |
| let previousEnd = 0; | |
| const questions = new Map<string, { beatId: string; order: number }>(); | |
| const answers = new Map<string, { beatId: string; order: number }>(); | |
| graph.beats.forEach((beat, index) => { | |
| if (beat.order !== index + 1) { | |
| issues.push({ severity: "error", code: "BEAT_ORDER", message: "Beat order must be contiguous.", beatId: beat.beatId }); | |
| } | |
| if (beat.estimatedStartMs !== previousEnd) { | |
| issues.push({ severity: "error", code: "TIMELINE_GAP", message: "Beat timeline must be contiguous.", beatId: beat.beatId }); | |
| } | |
| if (beat.estimatedEndMs <= beat.estimatedStartMs) { | |
| issues.push({ severity: "error", code: "INVALID_DURATION", message: "Beat duration must be positive.", beatId: beat.beatId }); | |
| } | |
| if (!beat.sourceReferences.length) { | |
| issues.push({ severity: "error", code: "UNGROUNDED_BEAT", message: "Every beat requires a textbook source.", beatId: beat.beatId }); | |
| } | |
| for (const ref of beat.sourceReferences) { | |
| if (!ref.sourceId || !ref.chapterId || !ref.pageNumber || ref.excerptHash.length < 16) { | |
| issues.push({ severity: "error", code: "INVALID_SOURCE_REF", message: "Source reference is incomplete.", beatId: beat.beatId }); | |
| } | |
| } | |
| for (const action of beat.actions) { | |
| if (!IMPLEMENTED_ACTIONS.has(action.type)) { | |
| issues.push({ severity: "error", code: "UNSUPPORTED_ACTION", message: `Action ${action.type} is not implemented.`, beatId: beat.beatId }); | |
| } | |
| if (action.type === "question") questions.set(action.objectId, { beatId: beat.beatId, order: beat.order }); | |
| if (action.type === "answer_reveal") answers.set(action.objectId, { beatId: beat.beatId, order: beat.order }); | |
| } | |
| previousEnd = beat.estimatedEndMs; | |
| }); | |
| if (previousEnd !== graph.durationMs) { | |
| issues.push({ severity: "error", code: "DURATION_MISMATCH", message: "Graph duration must equal the final beat end." }); | |
| } | |
| for (const [objectId, question] of questions) { | |
| const answer = answers.get(objectId); | |
| if (!answer || answer.order <= question.order) { | |
| issues.push({ severity: "error", code: "ANSWER_REVEAL_ORDER", message: `Question ${objectId} must be answered in a later beat.`, beatId: question.beatId }); | |
| } | |
| } | |
| return issues; | |
| } | |
| export function validateLessonContract(contract: LessonContract, graph: VisualBeatGraph): EngineValidationIssue[] { | |
| const issues = validateVisualBeatGraph(graph); | |
| if (contract.lessonId !== graph.lessonId) { | |
| issues.push({ severity: "error", code: "LESSON_ID_MISMATCH", message: "Lesson and graph IDs differ." }); | |
| } | |
| if (!contract.objectives.length || !contract.recap.length) { | |
| issues.push({ severity: "error", code: "INCOMPLETE_TEACHING_CONTRACT", message: "Objectives and recap are required." }); | |
| } | |
| if (contract.teachingSequence.length !== graph.beats.length) { | |
| issues.push({ severity: "error", code: "BEAT_COUNT_MISMATCH", message: "Teaching sequence and visual graph differ." }); | |
| } | |
| const covered = new Set(contract.sourceCoverage.flatMap((entry) => entry.narrationBeatIds)); | |
| for (const beat of graph.beats) { | |
| if (!covered.has(beat.beatId)) { | |
| issues.push({ severity: "warning", code: "COVERAGE_GAP", message: "Beat is not listed in the coverage matrix.", beatId: beat.beatId }); | |
| } | |
| } | |
| return issues; | |
| } | |
| export function implementedVisualActions() { | |
| return [...IMPLEMENTED_ACTIONS].sort(); | |
| } | |