Download src/video-engine/physics/production/semanticVisualIntent.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 17.8 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/video-engine/physics/production/semanticVisualIntent.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/video-engine/physics/production/semanticVisualIntent.ts
-
curl -L -o semanticVisualIntent.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/video-engine/physics/production/semanticVisualIntent.ts
17.8 kB
| /** | |
| * RESOLVED SEMANTIC VISUAL INTENT | |
| * | |
| * Every teaching scene must resolve to an explicit visual intent at | |
| * package-build time. The runtime renderer consumes the resolved intent | |
| * rather than guessing the topic from title strings each frame. | |
| * | |
| * This replaces vague keyword matching with explicit, validated intents. | |
| */ | |
| // --------------------------------------------------------------------------- | |
| // Canonical chapter IDs from the authoritative manifest | |
| // --------------------------------------------------------------------------- | |
| export const CANONICAL_CHAPTER_IDS = [ | |
| "phy-p1-c1", // Sound Waves | |
| "phy-p1-c2", // Lenses | |
| "phy-p1-c3", // The World of Colours and Vision | |
| "phy-p1-c4", // Magnetic Effect of Electric Current | |
| "phy-p2-c5", // Electric Energy: Consumption and Conservation | |
| "phy-p2-c6", // Electromagnetic Induction in Daily Life | |
| "phy-p2-c7", // Mechanical Advantage in Action | |
| ] as const; | |
| export type CanonicalChapterId = (typeof CANONICAL_CHAPTER_IDS)[number]; | |
| export function isCanonicalChapterId(id: string): id is CanonicalChapterId { | |
| return (CANONICAL_CHAPTER_IDS as readonly string[]).includes(id); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Visual families per chapter — exhaustive enumeration | |
| // --------------------------------------------------------------------------- | |
| export type SoundVisualFamily = | |
| | "oscillation_source" | |
| | "longitudinal_wave" | |
| | "transverse_wave" | |
| | "resonance" | |
| | "echo_reflection" | |
| | "audibility_range" | |
| | "seismic_tsunami" | |
| | "sonar" | |
| | "ultrasonic_application" | |
| | "wave_graph" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type LensVisualFamily = | |
| | "lens_anatomy" | |
| | "convex_concave_comparison" | |
| | "optical_bench" | |
| | "ray_rule_1" | |
| | "ray_rule_2" | |
| | "ray_rule_3" | |
| | "convex_beyond_2f" | |
| | "convex_at_2f" | |
| | "convex_between_f_2f" | |
| | "convex_at_f" | |
| | "convex_inside_f" | |
| | "concave_construction" | |
| | "sign_convention" | |
| | "lens_formula" | |
| | "magnification" | |
| | "power_of_lens" | |
| | "camera_projector" | |
| | "magnifying_glass" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type ColourVisualFamily = | |
| | "prism_dispersion" | |
| | "two_prism_recombination" | |
| | "rainbow_raindrops" | |
| | "rgb_additive_mixing" | |
| | "complementary_colours" | |
| | "coloured_object_illumination" | |
| | "colour_filters" | |
| | "atmospheric_scattering" | |
| | "blue_sky" | |
| | "red_sunset" | |
| | "eye_anatomy" | |
| | "accommodation" | |
| | "near_far_point" | |
| | "myopia" | |
| | "hypermetropia" | |
| | "presbyopia" | |
| | "correcting_lens" | |
| | "lens_power_numerical" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type MagneticVisualFamily = | |
| | "oersted_experiment" | |
| | "straight_conductor" | |
| | "circular_coil" | |
| | "solenoid" | |
| | "electromagnet" | |
| | "electromagnet_application" | |
| | "force_on_conductor" | |
| | "flemings_left_hand" | |
| | "dc_motor" | |
| | "split_ring_commutator" | |
| | "moving_coil_loudspeaker" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type ElectricEnergyVisualFamily = | |
| | "heating_circuit" | |
| | "nichrome_coil" | |
| | "electric_iron" | |
| | "kettle_heater" | |
| | "watt_hour_meter" | |
| | "appliance_star_rating" | |
| | "tod_timeline" | |
| | "household_bill" | |
| | "solar_rooftop" | |
| | "carbon_emission" | |
| | "conservation_transport" | |
| | "power_formula" | |
| | "kwh_numerical" | |
| | "household_energy_calculation" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type InductionVisualFamily = | |
| | "magnet_stationary" | |
| | "magnet_moving_in" | |
| | "magnet_stopped" | |
| | "magnet_moving_away" | |
| | "galvanometer_deflection" | |
| | "ac_waveform" | |
| | "ac_generator" | |
| | "dc_generator" | |
| | "slip_ring" | |
| | "split_ring" | |
| | "generator_energy" | |
| | "generating_station" | |
| | "step_up_transformer" | |
| | "step_down_transformer" | |
| | "transmission_towers" | |
| | "transformer_construction" | |
| | "domestic_wiring" | |
| | "three_pin_plug" | |
| | "fuse_mcb" | |
| | "elcb" | |
| | "appliance_earthing" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type MachineVisualFamily = | |
| | "first_order_lever" | |
| | "second_order_lever" | |
| | "third_order_lever" | |
| | "all_lever_orders" | |
| | "fixed_pulley" | |
| | "movable_pulley" | |
| | "first_pulley_system" | |
| | "second_pulley_system" | |
| | "rope_segment_counting" | |
| | "wheel_and_axle" | |
| | "gear_pair" | |
| | "gear_speed_torque" | |
| | "inclined_plane" | |
| | "wedge" | |
| | "screw_wrapped_plane" | |
| | "screw_jack" | |
| | "no_gain_in_work" | |
| | "real_world_applications" | |
| | "formula_workspace" | |
| | "numerical_workspace" | |
| | "exam_answer" | |
| | "recap" | |
| | "homework" | |
| | "context_image"; | |
| export type VisualFamily = | |
| | SoundVisualFamily | |
| | LensVisualFamily | |
| | ColourVisualFamily | |
| | MagneticVisualFamily | |
| | ElectricEnergyVisualFamily | |
| | InductionVisualFamily | |
| | MachineVisualFamily; | |
| // --------------------------------------------------------------------------- | |
| // Forbidden visual families per chapter — prevent cross-topic misrouting | |
| // --------------------------------------------------------------------------- | |
| export const FORBIDDEN_FAMILIES: Record<CanonicalChapterId, string[]> = { | |
| "phy-p1-c1": ["prism_dispersion", "lens_anatomy", "oersted_experiment", "heating_circuit", "ac_generator", "first_order_lever"], | |
| "phy-p1-c2": ["prism_dispersion", "longitudinal_wave", "oersted_experiment", "heating_circuit", "ac_generator", "first_order_lever"], | |
| "phy-p1-c3": ["longitudinal_wave", "oersted_experiment", "heating_circuit", "ac_generator", "first_order_lever"], | |
| "phy-p1-c4": ["longitudinal_wave", "prism_dispersion", "lens_anatomy", "heating_circuit", "ac_generator", "first_order_lever"], | |
| "phy-p2-c5": ["longitudinal_wave", "prism_dispersion", "lens_anatomy", "oersted_experiment", "ac_generator", "first_order_lever"], | |
| "phy-p2-c6": ["longitudinal_wave", "prism_dispersion", "lens_anatomy", "oersted_experiment", "first_order_lever"], | |
| "phy-p2-c7": ["longitudinal_wave", "prism_dispersion", "lens_anatomy", "oersted_experiment", "heating_circuit", "ac_generator"], | |
| }; | |
| // --------------------------------------------------------------------------- | |
| // Resolved Visual Intent — the output of the build-time router | |
| // --------------------------------------------------------------------------- | |
| export interface ResolvedVisualIntent { | |
| chapterId: CanonicalChapterId; | |
| sceneId: string; | |
| topicKey: string; | |
| visualFamily: VisualFamily; | |
| /** ID from the image-asset manifest, if a generated base image is used. */ | |
| baseAssetId?: string; | |
| /** State variant key for the same apparatus in different states. */ | |
| stateVariant?: string; | |
| /** Deterministic overlay IDs to render on top of the base image. */ | |
| overlayIds: string[]; | |
| /** Visual families that this scene must NOT use. */ | |
| forbiddenVisualFamilies: string[]; | |
| /** Textbook source page for scientific diagrams. */ | |
| sourcePage?: number; | |
| /** Content hash for cache invalidation. */ | |
| contentHash?: string; | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Topic → VisualFamily routing rules (build-time) | |
| // --------------------------------------------------------------------------- | |
| interface RoutingRule { | |
| pattern: RegExp; | |
| family: VisualFamily; | |
| overlays?: string[]; | |
| } | |
| const SOUND_RULES: RoutingRule[] = [ | |
| { pattern: /resonance|forced vibration|natural frequency/i, family: "resonance" }, | |
| { pattern: /longitudinal|compression|rarefaction/i, family: "longitudinal_wave" }, | |
| { pattern: /transverse/i, family: "transverse_wave" }, | |
| { pattern: /echo|reflection|reverberation/i, family: "echo_reflection" }, | |
| { pattern: /audib|ultrasonic|infrasonic|frequency range/i, family: "audibility_range" }, | |
| { pattern: /seismic|tsunami/i, family: "seismic_tsunami" }, | |
| { pattern: /sonar|depth.*sea|submarine/i, family: "sonar" }, | |
| { pattern: /ultrasound.*clean|ultrasonic.*appli|medical.*ultra/i, family: "ultrasonic_application" }, | |
| { pattern: /displacement.*graph|wave.*graph/i, family: "wave_graph" }, | |
| { pattern: /oscillat|vibrat|swing|pendulum|tuning fork/i, family: "oscillation_source" }, | |
| ]; | |
| const LENS_RULES: RoutingRule[] = [ | |
| { pattern: /rule 1|parallel ray/i, family: "ray_rule_1" }, | |
| { pattern: /rule 2|optic centre|optical centre/i, family: "ray_rule_2" }, | |
| { pattern: /rule 3|focus ray/i, family: "ray_rule_3" }, | |
| { pattern: /beyond 2f|outside 2f/i, family: "convex_beyond_2f" }, | |
| { pattern: /at 2f/i, family: "convex_at_2f" }, | |
| { pattern: /between f and 2f/i, family: "convex_between_f_2f" }, | |
| { pattern: /at f$|at the focus/i, family: "convex_at_f" }, | |
| { pattern: /inside focus|between f and o|magnifying/i, family: "convex_inside_f" }, | |
| { pattern: /concave.*construct|image.*concave/i, family: "concave_construction" }, | |
| { pattern: /sign convention/i, family: "sign_convention" }, | |
| { pattern: /lens formula|1\/f/i, family: "lens_formula" }, | |
| { pattern: /magnification|m\s*=\s*v\/u/i, family: "magnification" }, | |
| { pattern: /power.*lens|dioptre/i, family: "power_of_lens" }, | |
| { pattern: /camera|projector/i, family: "camera_projector" }, | |
| { pattern: /convex.*concave|types.*lens|shape.*lens/i, family: "convex_concave_comparison" }, | |
| { pattern: /optical bench|screen.*convex/i, family: "optical_bench" }, | |
| { pattern: /anatomy|parts.*lens/i, family: "lens_anatomy" }, | |
| ]; | |
| const COLOUR_RULES: RoutingRule[] = [ | |
| { pattern: /two.prism|recombination|newton.*experiment/i, family: "two_prism_recombination" }, | |
| { pattern: /prism|dispersion|spectrum/i, family: "prism_dispersion" }, | |
| { pattern: /rainbow/i, family: "rainbow_raindrops" }, | |
| { pattern: /primary.*colour|secondary.*colour|additive.*mix/i, family: "rgb_additive_mixing" }, | |
| { pattern: /complementary/i, family: "complementary_colours" }, | |
| { pattern: /coloured.*object|illuminat.*coloured/i, family: "coloured_object_illumination" }, | |
| { pattern: /filter/i, family: "colour_filters" }, | |
| { pattern: /scatter|blue.*sky|rayleigh/i, family: "atmospheric_scattering" }, | |
| { pattern: /sunset|reddening|red.*sun/i, family: "red_sunset" }, | |
| { pattern: /myopia|short.sight|near.sight/i, family: "myopia" }, | |
| { pattern: /hypermetropia|far.sight|long.sight/i, family: "hypermetropia" }, | |
| { pattern: /presbyopia/i, family: "presbyopia" }, | |
| { pattern: /correction|corrective|correcting.*lens/i, family: "correcting_lens" }, | |
| { pattern: /eye.*anatomy|retina|cornea|ciliary/i, family: "eye_anatomy" }, | |
| { pattern: /accommod/i, family: "accommodation" }, | |
| { pattern: /near point|far point/i, family: "near_far_point" }, | |
| { pattern: /power.*numerical|power.*dioptre.*calc/i, family: "lens_power_numerical" }, | |
| ]; | |
| const MAGNETIC_RULES: RoutingRule[] = [ | |
| { pattern: /oersted/i, family: "oersted_experiment" }, | |
| { pattern: /solenoid/i, family: "solenoid" }, | |
| { pattern: /circular.*coil|circular.*loop/i, family: "circular_coil" }, | |
| { pattern: /straight.*conductor/i, family: "straight_conductor" }, | |
| { pattern: /loudspeaker|speaker/i, family: "moving_coil_loudspeaker" }, | |
| { pattern: /commutator|split.ring.*close/i, family: "split_ring_commutator" }, | |
| { pattern: /motor|force on.*conductor|left.hand/i, family: "dc_motor" }, | |
| { pattern: /fleming/i, family: "flemings_left_hand" }, | |
| { pattern: /electromagnet.*crane|scrap|electromagnet.*appli/i, family: "electromagnet_application" }, | |
| { pattern: /electromagnet/i, family: "electromagnet" }, | |
| ]; | |
| const ENERGY_RULES: RoutingRule[] = [ | |
| { pattern: /nichrome|heating coil/i, family: "nichrome_coil" }, | |
| { pattern: /iron.*cutaway|electric iron/i, family: "electric_iron" }, | |
| { pattern: /kettle|heater/i, family: "kettle_heater" }, | |
| { pattern: /watt.*hour|energy meter/i, family: "watt_hour_meter" }, | |
| { pattern: /star.*rat/i, family: "appliance_star_rating" }, | |
| { pattern: /tod|time of day|peak.*hour/i, family: "tod_timeline" }, | |
| { pattern: /bill|tariff/i, family: "household_bill" }, | |
| { pattern: /solar/i, family: "solar_rooftop" }, | |
| { pattern: /carbon|emission|footprint/i, family: "carbon_emission" }, | |
| { pattern: /conservation|transport|led/i, family: "conservation_transport" }, | |
| { pattern: /kwh.*numerical|household.*calc/i, family: "household_energy_calculation" }, | |
| { pattern: /heat|joule|resistance/i, family: "heating_circuit" }, | |
| { pattern: /power.*formula|P\s*=\s*VI/i, family: "power_formula" }, | |
| ]; | |
| const INDUCTION_RULES: RoutingRule[] = [ | |
| { pattern: /ac generator/i, family: "ac_generator" }, | |
| { pattern: /dc generator/i, family: "dc_generator" }, | |
| { pattern: /slip.ring/i, family: "slip_ring" }, | |
| { pattern: /step.up/i, family: "step_up_transformer" }, | |
| { pattern: /step.down/i, family: "step_down_transformer" }, | |
| { pattern: /transformer.*construct|iron core.*primary/i, family: "transformer_construction" }, | |
| { pattern: /transmission|power.*grid|distribut/i, family: "transmission_towers" }, | |
| { pattern: /generating station|power plant/i, family: "generating_station" }, | |
| { pattern: /domestic.*wir|house.*wir|live.*neutral/i, family: "domestic_wiring" }, | |
| { pattern: /three.pin|plug/i, family: "three_pin_plug" }, | |
| { pattern: /fuse|mcb/i, family: "fuse_mcb" }, | |
| { pattern: /elcb/i, family: "elcb" }, | |
| { pattern: /earth|ground.*fault/i, family: "appliance_earthing" }, | |
| { pattern: /ac wave|alternating.*current.*graph/i, family: "ac_waveform" }, | |
| { pattern: /galvanometer.*deflect/i, family: "galvanometer_deflection" }, | |
| { pattern: /magnet.*moving.*in|moving.*towards/i, family: "magnet_moving_in" }, | |
| { pattern: /magnet.*moving.*away|moving.*out/i, family: "magnet_moving_away" }, | |
| { pattern: /magnet.*stop|no.*relative.*motion/i, family: "magnet_stopped" }, | |
| { pattern: /magnet.*station|no.*change|stationary/i, family: "magnet_stationary" }, | |
| { pattern: /generator.*energy|mechanical.*electrical/i, family: "generator_energy" }, | |
| ]; | |
| const MACHINE_RULES: RoutingRule[] = [ | |
| { pattern: /three.*order|classif.*lever|all.*lever/i, family: "all_lever_orders" }, | |
| { pattern: /first.*order/i, family: "first_order_lever" }, | |
| { pattern: /second.*order/i, family: "second_order_lever" }, | |
| { pattern: /third.*order/i, family: "third_order_lever" }, | |
| { pattern: /block.*tackle|second.*pulley.*system|compound.*pulley/i, family: "second_pulley_system" }, | |
| { pattern: /first.*pulley.*system/i, family: "first_pulley_system" }, | |
| { pattern: /movable.*pulley/i, family: "movable_pulley" }, | |
| { pattern: /fixed.*pulley/i, family: "fixed_pulley" }, | |
| { pattern: /rope.*segment|count.*rope/i, family: "rope_segment_counting" }, | |
| { pattern: /wheel.*axle|bicycle.*axle/i, family: "wheel_and_axle" }, | |
| { pattern: /gear.*speed|gear.*torque|gear.*trade/i, family: "gear_speed_torque" }, | |
| { pattern: /gear/i, family: "gear_pair" }, | |
| { pattern: /inclined.*plane|ramp|loading/i, family: "inclined_plane" }, | |
| { pattern: /wedge/i, family: "wedge" }, | |
| { pattern: /screw.*jack/i, family: "screw_jack" }, | |
| { pattern: /screw|wrapped.*plane/i, family: "screw_wrapped_plane" }, | |
| { pattern: /no.*gain.*work/i, family: "no_gain_in_work" }, | |
| ]; | |
| const CHAPTER_RULES: Record<CanonicalChapterId, RoutingRule[]> = { | |
| "phy-p1-c1": SOUND_RULES, | |
| "phy-p1-c2": LENS_RULES, | |
| "phy-p1-c3": COLOUR_RULES, | |
| "phy-p1-c4": MAGNETIC_RULES, | |
| "phy-p2-c5": ENERGY_RULES, | |
| "phy-p2-c6": INDUCTION_RULES, | |
| "phy-p2-c7": MACHINE_RULES, | |
| }; | |
| // --------------------------------------------------------------------------- | |
| // Build-time router | |
| // --------------------------------------------------------------------------- | |
| /** Shared segment types that don't use chapter-specific visuals. */ | |
| function sharedSegmentFamily(segmentKind: string): VisualFamily | undefined { | |
| switch (segmentKind) { | |
| case "board_answer": return "exam_answer"; | |
| case "practice_sample": return "numerical_workspace"; | |
| case "recap_revision": return "recap"; | |
| case "homework": return "homework"; | |
| default: return undefined; | |
| } | |
| } | |
| /** | |
| * Resolve a scene to its visual family at package-build time. | |
| * The scene text is the concatenation of title + onScreenText + recapNote + formula. | |
| */ | |
| export function resolveVisualFamily( | |
| chapterId: string, | |
| segmentKind: string, | |
| sceneText: string, | |
| hasFormula: boolean, | |
| ): VisualFamily { | |
| // 1. Shared segment types | |
| const shared = sharedSegmentFamily(segmentKind); | |
| if (shared) return shared; | |
| // 2. Formula scenes | |
| if (hasFormula && segmentKind === "visual_board") return "formula_workspace"; | |
| // 3. Chapter-specific rules | |
| if (isCanonicalChapterId(chapterId)) { | |
| const rules = CHAPTER_RULES[chapterId]; | |
| for (const rule of rules) { | |
| if (rule.pattern.test(sceneText)) return rule.family; | |
| } | |
| } | |
| // 4. Fallback to context_image (will be resolved by context asset routing) | |
| return "context_image"; | |
| } | |
| /** | |
| * Validate that a visual family is not forbidden for the chapter. | |
| */ | |
| export function validateVisualIntent(intent: ResolvedVisualIntent): string[] { | |
| const issues: string[] = []; | |
| if (!isCanonicalChapterId(intent.chapterId)) { | |
| issues.push(`Unknown chapterId: ${intent.chapterId}`); | |
| return issues; | |
| } | |
| const forbidden = FORBIDDEN_FAMILIES[intent.chapterId]; | |
| if (forbidden.includes(intent.visualFamily)) { | |
| issues.push( | |
| `Scene ${intent.sceneId}: visual family "${intent.visualFamily}" is forbidden for chapter ${intent.chapterId}` | |
| ); | |
| } | |
| // Cross-chapter asset leak | |
| if (intent.baseAssetId && !intent.baseAssetId.startsWith(intent.chapterId.replace(/-/g, ""))) { | |
| // Allow shared assets | |
| if (!intent.baseAssetId.startsWith("shared-")) { | |
| issues.push( | |
| `Scene ${intent.sceneId}: asset "${intent.baseAssetId}" may belong to another chapter` | |
| ); | |
| } | |
| } | |
| return issues; | |
| } | |