Download src/lib/tuition/longFormClassRuntime.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 15.1 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/tuition/longFormClassRuntime.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/tuition/longFormClassRuntime.ts
-
curl -L -o longFormClassRuntime.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/tuition/longFormClassRuntime.ts
15.1 kB
| import type { TuitionLessonSeed } from "@/lib/teaching/physicsLessonSeeds"; | |
| /** | |
| * Phase 6L β long-form class runtime. | |
| * | |
| * Transforms a curated TuitionLessonSeed into a rich 30-50 minute class | |
| * runtime for the Tuition UI. Fully deterministic: no AI calls, no invented | |
| * content. Every field below is derived from seed fields that already passed | |
| * validation (source-backed, honest). Where the seed genuinely lacks data, | |
| * the runtime records a "needs curation" warning instead of faking text. | |
| */ | |
| export type LongFormTeachSegment = { | |
| title: string; | |
| durationMinutes: number; | |
| teacherScript: string[]; | |
| boardPoints: string[]; | |
| examples: string[]; | |
| malayalamSupport?: string; | |
| manglishSupport?: string; | |
| checkpointQuestion?: string; | |
| }; | |
| export type LongFormBoardFrame = { | |
| title: string; | |
| durationMinutes: number; | |
| boardDrawingSteps: string[]; | |
| diagramDescription: string; | |
| labels: string[]; | |
| teacherNarration: string[]; | |
| studentShouldNotice: string[]; | |
| }; | |
| export type LongFormMethodCard = { | |
| title: string; | |
| method: string; | |
| whenToUse: string; | |
| mistakeAvoided: string; | |
| example: string; | |
| }; | |
| export type LongFormExamPractice = { | |
| marks: number; | |
| question: string; | |
| answerStructure: string[]; | |
| keywords: string[]; | |
| commonMissingPoints: string[]; | |
| improvedAnswer: string; | |
| }; | |
| export type LongFormPracticeItem = { | |
| type: "short" | "numerical" | "diagram" | "reasoning"; | |
| question: string; | |
| hint: string; | |
| expectedAnswer: string[]; | |
| rubric: string[]; | |
| }; | |
| export type LongFormClassRuntime = { | |
| missionId: string; | |
| estimatedMinutes: number; | |
| sessionPlan: { | |
| title: string; | |
| learningGoals: string[]; | |
| prerequisites: string[]; | |
| chapterPosition: string; | |
| examRelevance: string[]; | |
| classFlow: string[]; | |
| byTheEnd: string[]; | |
| timing: Array<{ block: string; minutes: number }>; | |
| }; | |
| teachSegments: LongFormTeachSegment[]; | |
| visualBoardFrames: LongFormBoardFrame[]; | |
| tricksAndMethods: LongFormMethodCard[]; | |
| detailedNotes: { | |
| definitions: string[]; | |
| formulas: string[]; | |
| examKeywords: string[]; | |
| commonMistakes: string[]; | |
| boardAnswerPoints: string[]; | |
| copyToNotebook: string[]; | |
| malayalamQuickHelp?: string[]; | |
| manglishQuickHelp?: string[]; | |
| }; | |
| examPractice: LongFormExamPractice[]; | |
| practiceSet: LongFormPracticeItem[]; | |
| revisionPack: { | |
| flashcards: Array<{ front: string; back: string; requiredKeywords?: string[] }>; | |
| onePageRevision: string[]; | |
| lastMinuteMistakes: string[]; | |
| }; | |
| /** Deterministic teacher narration β placeholder for future audio/video generation. */ | |
| narration: { | |
| lessonMediaPlaceholder: string; | |
| teacherScript: string[]; | |
| }; | |
| /** "Needs curation" gaps β shown honestly, never filled with fake text. */ | |
| warnings: string[]; | |
| }; | |
| const NEEDS_CURATION = "This section needs curation for this mission."; | |
| function unique(values: string[]): string[] { | |
| return Array.from(new Set(values.map((v) => v.trim()).filter(Boolean))); | |
| } | |
| /** Deterministic integer split of `total` into `parts` chunks (β₯1 each). */ | |
| function splitMinutes(total: number, parts: number): number[] { | |
| if (parts <= 0) return []; | |
| const base = Math.max(1, Math.floor(total / parts)); | |
| const result = Array.from({ length: parts }, () => base); | |
| let remainder = Math.max(0, total - base * parts); | |
| for (let i = 0; remainder > 0 && i < parts; i += 1, remainder -= 1) result[i] += 1; | |
| return result; | |
| } | |
| function timingPlan(estimatedMinutes: number): Array<{ block: string; minutes: number }> { | |
| // Deterministic proportional split of the whole class. | |
| const teach = Math.max(10, Math.round(estimatedMinutes * 0.3)); | |
| const board = Math.max(8, Math.round(estimatedMinutes * 0.2)); | |
| const methods = Math.max(4, Math.round(estimatedMinutes * 0.1)); | |
| const notes = Math.max(5, Math.round(estimatedMinutes * 0.12)); | |
| const exam = Math.max(6, Math.round(estimatedMinutes * 0.14)); | |
| const plan = 3; | |
| const used = plan + teach + board + methods + notes + exam; | |
| const practice = Math.max(4, estimatedMinutes - used); | |
| return [ | |
| { block: "Session Plan", minutes: plan }, | |
| { block: "Teach", minutes: teach }, | |
| { block: "Visual Board", minutes: board }, | |
| { block: "DocDoe Methods", minutes: methods }, | |
| { block: "Key Notes", minutes: notes }, | |
| { block: "Exam Answers", minutes: exam }, | |
| { block: "Practice + Correction + Quiz + Revision", minutes: practice }, | |
| ]; | |
| } | |
| function buildTeachSegments(seed: TuitionLessonSeed, warnings: string[]): LongFormTeachSegment[] { | |
| const blocks = seed.teach.explanationBlocks; | |
| if (blocks.length === 0) { | |
| warnings.push(`Teach segments: ${NEEDS_CURATION}`); | |
| return []; | |
| } | |
| const minutes = splitMinutes(Math.max(10, Math.round(seed.estimatedMinutes * 0.3)), blocks.length); | |
| const checkpoints = seed.quiz.map((q) => q.question); | |
| return blocks.map((block, index) => { | |
| const boardPoints = unique([ | |
| ...block.keyTerms, | |
| ...seed.keyNotes.filter((note) => block.keyTerms.some((term) => note.toLowerCase().includes(term.toLowerCase().slice(0, 6)))), | |
| ]); | |
| return { | |
| title: block.heading, | |
| durationMinutes: minutes[index] ?? 1, | |
| teacherScript: [ | |
| index === 0 ? seed.teach.teacherIntro : "", | |
| block.explanation, | |
| ].filter(Boolean), | |
| boardPoints: boardPoints.length > 0 ? boardPoints : block.keyTerms, | |
| examples: index === blocks.length - 1 && seed.teach.analogy ? [seed.teach.analogy] : [], | |
| malayalamSupport: index === 0 ? seed.teach.malayalamSupport : undefined, | |
| manglishSupport: index === 0 ? seed.teach.manglishSupport : undefined, | |
| checkpointQuestion: checkpoints[index], | |
| }; | |
| }); | |
| } | |
| function buildBoardFrames(seed: TuitionLessonSeed, warnings: string[]): LongFormBoardFrame[] { | |
| const frames = seed.visualBoard.frames; | |
| if (frames.length === 0) { | |
| warnings.push(`Visual board: ${NEEDS_CURATION}`); | |
| return []; | |
| } | |
| const minutes = splitMinutes(Math.max(8, Math.round(seed.estimatedMinutes * 0.2)), frames.length); | |
| return frames.map((frame, index) => ({ | |
| title: frame.title, | |
| durationMinutes: minutes[index] ?? 1, | |
| boardDrawingSteps: [frame.teacherAction, ...frame.boardText], | |
| diagramDescription: frame.diagramDescription ?? "", | |
| labels: unique(frame.boardText.flatMap((line) => line.split(/[β=:;,]/).map((part) => part.trim()).filter((part) => part.length > 2 && part.length <= 40))).slice(0, 6), | |
| teacherNarration: [frame.teacherAction, frame.studentFocus], | |
| studentShouldNotice: [frame.studentFocus], | |
| })); | |
| } | |
| function buildMethods(seed: TuitionLessonSeed): LongFormMethodCard[] { | |
| const methods: LongFormMethodCard[] = []; | |
| const mistakes = unique(seed.examAnswers.flatMap((a) => a.commonMistakes)); | |
| const keywords = unique(seed.examAnswers.flatMap((a) => a.keywords)); | |
| // 1) The seed's own DocDoe trick β always first. | |
| methods.push({ | |
| title: seed.docDoeTrick.title, | |
| method: seed.docDoeTrick.trick, | |
| whenToUse: "Use while learning the concept and again during last-minute revision.", | |
| mistakeAvoided: mistakes[0] ?? "Forgetting the core idea under exam pressure.", | |
| example: seed.docDoeTrick.whyItWorks, | |
| }); | |
| // 2) Formula/rule selection method (only when the seed has formulas). | |
| if (seed.formulas.length > 0) { | |
| const formula = seed.formulas[0]; | |
| methods.push({ | |
| title: "Formula selection method", | |
| method: `Write the formula first: ${formula.formula}. ${formula.commonUseCase}`, | |
| whenToUse: "At the start of every numerical or formula-based answer.", | |
| mistakeAvoided: formula.numericalPattern ?? "Substituting values before writing the formula loses the method mark.", | |
| example: formula.symbols.map((s) => `${s.symbol} = ${s.meaning}${s.unit ? ` (${s.unit})` : ""}`).join("; "), | |
| }); | |
| } | |
| // 3) Exam keyword method (from the seed's exam answers). | |
| if (keywords.length > 0) { | |
| methods.push({ | |
| title: "Exam keyword method", | |
| method: `Before writing, list the keywords: ${keywords.slice(0, 5).join(", ")}. Tick each one as you use it.`, | |
| whenToUse: "In every written answer β keywords carry the marks.", | |
| mistakeAvoided: "Writing the right idea but missing the exact keyword the examiner looks for.", | |
| example: seed.examAnswers[0]?.boardStyleRewrite ?? seed.examAnswers[0]?.answerPoints[0] ?? "", | |
| }); | |
| } | |
| // 4) Mistake-avoidance method (from common mistakes). | |
| if (mistakes.length > 0) { | |
| methods.push({ | |
| title: "Mistake-avoidance method", | |
| method: `Check your answer against the known traps: ${mistakes.slice(0, 3).join("; ")}.`, | |
| whenToUse: "After writing, before moving on β a 20-second self-check.", | |
| mistakeAvoided: mistakes[0], | |
| example: seed.correctionRubric.commonMissingParts.join("; ") || mistakes[0], | |
| }); | |
| } | |
| // 5) Board-drawing memory method (from the visual board). | |
| if (seed.visualBoard.frames.length > 0) { | |
| const first = seed.visualBoard.frames[0]; | |
| methods.push({ | |
| title: "Board memory method", | |
| method: `Redraw the board from memory: ${seed.visualBoard.frames.map((f) => f.title).join(" β ")}.`, | |
| whenToUse: "During revision β drawing recalls more than re-reading.", | |
| mistakeAvoided: "Recognising the diagram in the book but failing to reproduce it in the exam.", | |
| example: first.teacherAction, | |
| }); | |
| } | |
| return methods.slice(0, 5); | |
| } | |
| function buildDetailedNotes(seed: TuitionLessonSeed): LongFormClassRuntime["detailedNotes"] { | |
| const keywords = unique(seed.examAnswers.flatMap((a) => a.keywords)); | |
| const mistakes = unique([ | |
| ...seed.examAnswers.flatMap((a) => a.commonMistakes), | |
| ...seed.correctionRubric.commonMissingParts, | |
| ]); | |
| const boardAnswers = unique(seed.examAnswers.map((a) => a.boardStyleRewrite)); | |
| return { | |
| definitions: seed.keyNotes, | |
| formulas: seed.formulas.map( | |
| (f) => `${f.formula} β ${f.meaning} [${f.symbols.map((s) => `${s.symbol}=${s.meaning}${s.unit ? ` (${s.unit})` : ""}`).join(", ")}]`, | |
| ), | |
| examKeywords: keywords.length > 0 ? keywords : seed.tags, | |
| commonMistakes: mistakes, | |
| boardAnswerPoints: boardAnswers, | |
| copyToNotebook: unique([...seed.keyNotes, ...seed.revisionSave.revisionNotes]), | |
| malayalamQuickHelp: seed.teach.malayalamSupport ? [seed.teach.malayalamSupport] : undefined, | |
| manglishQuickHelp: seed.teach.manglishSupport ? [seed.teach.manglishSupport] : undefined, | |
| }; | |
| } | |
| function buildExamPractice(seed: TuitionLessonSeed): LongFormExamPractice[] { | |
| return [...seed.examAnswers] | |
| .sort((a, b) => a.marks - b.marks) | |
| .map((answer) => ({ | |
| marks: answer.marks, | |
| question: answer.question, | |
| answerStructure: answer.answerPoints, | |
| keywords: answer.keywords, | |
| commonMissingPoints: answer.commonMistakes, | |
| improvedAnswer: answer.boardStyleRewrite, | |
| })); | |
| } | |
| function practiceTypeFor(quizType: TuitionLessonSeed["quiz"][number]["type"]): LongFormPracticeItem["type"] { | |
| if (quizType === "numerical") return "numerical"; | |
| if (quizType === "diagram") return "diagram"; | |
| if (quizType === "mcq") return "short"; | |
| return "reasoning"; | |
| } | |
| function buildPracticeSet(seed: TuitionLessonSeed): LongFormPracticeItem[] { | |
| const rubric = seed.correctionRubric.scoringPoints.map( | |
| (point) => `${point.point} (${point.marks} mark${point.marks === 1 ? "" : "s"}; keywords: ${point.requiredKeywords.join(", ")})`, | |
| ); | |
| const main: LongFormPracticeItem = { | |
| type: "reasoning", | |
| question: seed.yourTurn.prompt, | |
| hint: seed.yourTurn.hint, | |
| expectedAnswer: seed.yourTurn.expectedAnswerPoints, | |
| rubric, | |
| }; | |
| const fromQuiz: LongFormPracticeItem[] = seed.quiz.map((q) => ({ | |
| type: practiceTypeFor(q.type), | |
| question: q.question, | |
| hint: q.explanation, | |
| expectedAnswer: [q.answer], | |
| rubric: [], | |
| })); | |
| return [main, ...fromQuiz]; | |
| } | |
| function buildNarration(seed: TuitionLessonSeed, segments: LongFormTeachSegment[], frames: LongFormBoardFrame[]): LongFormClassRuntime["narration"] { | |
| return { | |
| lessonMediaPlaceholder: | |
| "Video renderer coming later; this is today's lesson plan outline from the curated class content.", | |
| teacherScript: [ | |
| `Today's class: ${seed.missionTitle}. ${seed.goal}`, | |
| ...segments.flatMap((segment) => segment.teacherScript), | |
| ...frames.flatMap((frame) => frame.teacherNarration), | |
| `Recap: ${seed.recap.summary.join(" ")}`, | |
| ].filter(Boolean), | |
| }; | |
| } | |
| /** Build the long-form runtime from a curated lesson seed. Deterministic. */ | |
| export function buildLongFormClassRuntime(seed: TuitionLessonSeed): LongFormClassRuntime { | |
| const warnings: string[] = []; | |
| const teachSegments = buildTeachSegments(seed, warnings); | |
| const visualBoardFrames = buildBoardFrames(seed, warnings); | |
| const marksCovered = unique(seed.examAnswers.map((a) => `${a.marks}-mark`)); | |
| if (seed.revisionSave.flashcards.length === 0) warnings.push(`Flashcards: ${NEEDS_CURATION}`); | |
| if (seed.examAnswers.length === 0) warnings.push(`Exam practice: ${NEEDS_CURATION}`); | |
| return { | |
| missionId: seed.missionId, | |
| estimatedMinutes: seed.estimatedMinutes, | |
| sessionPlan: { | |
| title: `Today's Session Plan β ${seed.missionTitle}`, | |
| learningGoals: [seed.goal, ...seed.teach.explanationBlocks.map((block) => `Understand: ${block.heading}`)], | |
| prerequisites: seed.recall.expectedPoints, | |
| chapterPosition: `${seed.chapterTitle} β mission ${seed.missionId.replace(/^M/, "")} (${seed.contentType.replace(/_/g, " ")})`, | |
| examRelevance: marksCovered.length > 0 ? [`Board answer practice: ${marksCovered.join(", ")} questions`, ...unique(seed.examAnswers.map((a) => a.question)).slice(0, 3)] : [], | |
| classFlow: [ | |
| "Session Plan β what today covers", | |
| `Teach β ${seed.teach.explanationBlocks.map((block) => block.heading).join(", ")}`, | |
| `Visual Board β ${seed.visualBoard.frames.length} board frame${seed.visualBoard.frames.length === 1 ? "" : "s"}`, | |
| "DocDoe Methods β tricks and self-checks", | |
| "Key Notes β notebook notes", | |
| "Exam Answers β board-style writing", | |
| "Your Turn + Correction + Quiz", | |
| "Recap + Save Revision", | |
| ], | |
| byTheEnd: [ | |
| seed.goal, | |
| ...seed.recap.mustRemember.map((item) => `Remember: ${item}`), | |
| ...(seed.examAnswers[0] ? [`Write a ${seed.examAnswers[0].marks}-mark board answer with the right keywords`] : []), | |
| ], | |
| timing: timingPlan(seed.estimatedMinutes), | |
| }, | |
| teachSegments, | |
| visualBoardFrames, | |
| tricksAndMethods: buildMethods(seed), | |
| detailedNotes: buildDetailedNotes(seed), | |
| examPractice: buildExamPractice(seed), | |
| practiceSet: buildPracticeSet(seed), | |
| revisionPack: { | |
| flashcards: seed.revisionSave.flashcards, | |
| onePageRevision: unique([...seed.recap.summary, ...seed.recap.mustRemember, ...seed.revisionSave.revisionNotes]), | |
| lastMinuteMistakes: unique([ | |
| ...seed.examAnswers.flatMap((a) => a.commonMistakes), | |
| ...seed.correctionRubric.commonMissingParts, | |
| ]), | |
| }, | |
| narration: buildNarration(seed, teachSegments, visualBoardFrames), | |
| warnings, | |
| }; | |
| } | |