File size: 9,479 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import {
  getChapters,
  getDefinitions,
  getFormulas,
  getNumericalPatterns,
  getQuestions,
  getVisualLessonFrames,
  type ChapterSummary,
} from "@/lib/knowledge-base/sslc-physics";
import type { LessonSeedSourceRef } from "./physicsLessonSeeds";
import { getMagneticEffectEvidencePack } from "./source-overrides/magneticEffectEvidence";

export type PhysicsChapterSourcePack = {
  subject: "Physics";
  chapterId: string;
  chapterTitle: string;
  sourceBook: string;
  pageRange?: string;
  sourceRefs: LessonSeedSourceRef[];
  extractedSignals: {
    formulas: string[];
    definitions: string[];
    possibleQuestions: string[];
    keyTerms: string[];
    diagrams: string[];
    numericalPatterns: string[];
  };
  coverageConfidence: "high" | "medium" | "low";
  warnings: string[];
  /** True when the extracted signals were repaired from a manual source-evidence override (Phase 6I). */
  sourceRepaired?: boolean;
};

/**
 * Manual source-evidence overrides (Phase 6I). These do NOT create lesson seeds
 * and do NOT make a chapter teachable — they only repair thin auto-extraction so
 * the source pack reflects the real (source-referenced) chapter scope.
 */
type SourceEvidenceOverride = {
  definitions: string[];
  keyTerms: string[];
  diagrams: string[];
  possibleQuestions: string[];
};

function magneticEffectOverride(): SourceEvidenceOverride {
  const pack = getMagneticEffectEvidencePack();
  return {
    definitions: pack.definitions.map((d) => `${d.term}: ${d.definition}`),
    keyTerms: pack.keyTerms,
    diagrams: pack.diagrams.map((d) => `${d.title}: ${d.visualDescription}`),
    possibleQuestions: pack.questions.map((q) => q.question),
  };
}

function evidenceOverrideFor(chapterId: string): SourceEvidenceOverride | null {
  if (chapterId === "phy-p1-c4") return magneticEffectOverride();
  return null;
}

const SOURCE_BOOK = "Kerala SCERT Physics, Standard X (2025)";
const MAX_SIGNAL_LENGTH = 180;
const MAX_SIGNALS_PER_KIND = 20;

function asRecord(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" ? (value as Record<string, unknown>) : {};
}

function readString(value: unknown): string {
  return typeof value === "string" ? value.trim() : "";
}

function getChapterTitle(chapter: ChapterSummary): string {
  const maybeTitle = chapter as ChapterSummary & { title?: string };
  return chapter.chapter_title?.trim() || maybeTitle.title?.trim() || chapter.chapter_id;
}

function compactText(value: string): string {
  return value.replace(/\s+/g, " ").trim().slice(0, MAX_SIGNAL_LENGTH);
}

function unique(values: string[]): string[] {
  return Array.from(new Set(values.map(compactText).filter(Boolean))).slice(0, MAX_SIGNALS_PER_KIND);
}

function firstText(record: Record<string, unknown>, keys: string[]): string {
  for (const key of keys) {
    const value = readString(record[key]);
    if (value) return value;
  }
  return "";
}

function sourcePageRange(chapter: ChapterSummary): string | undefined {
  const printedStart = Number(chapter.printed_start_page);
  const printedEnd = Number(chapter.printed_end_page);
  if (Number.isFinite(printedStart) && printedStart > 0) {
    return Number.isFinite(printedEnd) && printedEnd >= printedStart
      ? `${printedStart}-${printedEnd}`
      : String(printedStart);
  }

  const pdfStart = Number(chapter.pdf_start_page);
  const pdfEnd = Number(chapter.pdf_end_page);
  if (Number.isFinite(pdfStart) && pdfStart > 0) {
    return Number.isFinite(pdfEnd) && pdfEnd >= pdfStart ? `${pdfStart}-${pdfEnd}` : String(pdfStart);
  }

  return undefined;
}

function buildSourceRef(chapter: ChapterSummary): LessonSeedSourceRef {
  const pageRange = sourcePageRange(chapter);
  const chapterTitle = getChapterTitle(chapter);
  return {
    label: pageRange
      ? `Kerala SSLC Physics - ${chapterTitle} (pp. ${pageRange})`
      : `Kerala SSLC Physics - ${chapterTitle}`,
    sourceBook: SOURCE_BOOK,
    chapterTitle,
    pageRange,
    url: null,
    confidence: chapter.needs_review ? "needs_review" : "source_backed",
  };
}

function formulaSignals(chapterId: string): string[] {
  return unique(
    (getFormulas(chapterId) ?? []).map((item) => {
      const record = asRecord(item);
      const formula = firstText(record, ["formula", "formula_used", "equation", "expression"]);
      const name = firstText(record, ["name", "title", "formula_name", "pattern_name"]);
      return formula && name ? `${name}: ${formula}` : formula || name;
    }),
  );
}

function definitionSignals(chapterId: string): string[] {
  return unique(
    getDefinitions(chapterId).map((item) => {
      const term = item.term?.trim() ?? "";
      const definition = item.definition?.trim() ?? "";
      return definition ? `${term}: ${definition}` : term;
    }),
  );
}

function questionSignals(chapterId: string): string[] {
  return unique(getQuestions(chapterId).map((item) => item.question));
}

function termSignals(chapterId: string): string[] {
  const fromDefinitions = getDefinitions(chapterId).map((item) => item.term);
  const fromQuestions = getQuestions(chapterId).flatMap((item) => item.keywords ?? []);
  const fromFormulas = (getFormulas(chapterId) ?? []).map((item) => firstText(asRecord(item), ["name", "title"]));
  return unique([...fromDefinitions, ...fromQuestions, ...fromFormulas]);
}

function numericalSignals(chapterId: string): string[] {
  return unique(
    (getNumericalPatterns(chapterId) ?? []).map((item) => {
      const record = asRecord(item);
      const pattern = firstText(record, ["pattern_name", "title", "name"]);
      const formula = firstText(record, ["formula_used", "formula", "equation"]);
      return pattern && formula ? `${pattern}: ${formula}` : pattern || formula;
    }),
  );
}

function diagramSignals(chapterId: string): string[] {
  return unique(
    (getVisualLessonFrames(chapterId) ?? []).flatMap((item) => {
      const record = asRecord(item);
      const title = firstText(record, ["title", "frame_title", "name"]);
      const idea = firstText(record, ["diagramDescription", "description", "teacherAction", "visual_idea"]);
      return [title, idea];
    }),
  );
}

function buildWarnings(signals: PhysicsChapterSourcePack["extractedSignals"], sourceRefs: LessonSeedSourceRef[]): string[] {
  const warnings: string[] = [];
  if (sourceRefs.length === 0) warnings.push("Missing chapter source reference.");
  if (signals.definitions.length === 0) warnings.push("No extracted definitions; concept missions need human curation.");
  if (signals.formulas.length === 0) warnings.push("No extracted formulas; formula/numerical missions need review.");
  if (signals.possibleQuestions.length < 10) warnings.push("Thin possible-question coverage.");
  if (signals.diagrams.length === 0) warnings.push("No extracted visual-board/diagram signal.");
  if (signals.numericalPatterns.length === 0) warnings.push("No extracted numerical pattern.");
  return warnings;
}

function confidenceFor(signals: PhysicsChapterSourcePack["extractedSignals"], sourceRefs: LessonSeedSourceRef[]): "high" | "medium" | "low" {
  if (sourceRefs.length === 0) return "low";
  if (signals.definitions.length === 0 && signals.formulas.length === 0) return "low";

  const signalScore = [
    signals.definitions.length > 0,
    signals.formulas.length > 0,
    signals.possibleQuestions.length >= 10,
    signals.diagrams.length > 0,
    signals.numericalPatterns.length > 0,
  ].filter(Boolean).length;

  if (signalScore >= 4) return "high";
  if (signalScore >= 2) return "medium";
  return "low";
}

function buildPack(chapter: ChapterSummary): PhysicsChapterSourcePack {
  const sourceRefs = [buildSourceRef(chapter)];
  const extractedSignals: PhysicsChapterSourcePack["extractedSignals"] = {
    formulas: formulaSignals(chapter.chapter_id),
    definitions: definitionSignals(chapter.chapter_id),
    possibleQuestions: questionSignals(chapter.chapter_id),
    keyTerms: termSignals(chapter.chapter_id),
    diagrams: diagramSignals(chapter.chapter_id),
    numericalPatterns: numericalSignals(chapter.chapter_id),
  };

  const override = evidenceOverrideFor(chapter.chapter_id);
  if (override) {
    extractedSignals.definitions = unique([...extractedSignals.definitions, ...override.definitions]);
    extractedSignals.keyTerms = unique([...extractedSignals.keyTerms, ...override.keyTerms]);
    extractedSignals.diagrams = unique([...extractedSignals.diagrams, ...override.diagrams]);
    extractedSignals.possibleQuestions = unique([...extractedSignals.possibleQuestions, ...override.possibleQuestions]);
  }

  const warnings = buildWarnings(extractedSignals, sourceRefs);
  if (override) {
    warnings.push(
      "Source manually repaired from the extracted textbook question bank and figure references (Phase 6I evidence override).",
    );
  }

  return {
    subject: "Physics",
    chapterId: chapter.chapter_id,
    chapterTitle: getChapterTitle(chapter),
    sourceBook: SOURCE_BOOK,
    pageRange: sourceRefs[0]?.pageRange,
    sourceRefs,
    extractedSignals,
    coverageConfidence: confidenceFor(extractedSignals, sourceRefs),
    warnings,
    sourceRepaired: Boolean(override),
  };
}

export function getPhysicsSourcePacks(): PhysicsChapterSourcePack[] {
  return getChapters().map(buildPack);
}

export function getPhysicsSourcePack(chapterId: string): PhysicsChapterSourcePack | null {
  return getPhysicsSourcePacks().find((pack) => pack.chapterId === chapterId) ?? null;
}