File size: 11,492 Bytes
7c6ffa6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
"""Concept-level learning graph.

Data-driven registry of teachable concepts, keyed by the SAME ``topic_key``
convention the product already persists mastery under:
``"{chapter_catalog_id}:{mission_id}"`` (see ``topic_mastery.topic_key`` and
GuidedClass's assessment sync). For v1 each curated Sound Waves mission is one
concept node; the shape is chapter-agnostic so later chapters extend the data,
not the code.

Honesty rules encoded here:
- Only Sound Waves (``phy-p1-c1``) is a complete curated class; its nodes carry
  real objectives/misconceptions lifted from the hand-authored, source-backed
  lesson seeds (``src/lib/teaching/physicsLessonSeeds.ts``).
- ``pyq`` resources are ``False`` everywhere because no PYQ evidence is curated
  yet (``pyqEvidenceStatus: "not_curated"`` in the seeds).
- Unknown chapters/missions simply are not in the graph — callers must treat
  absence as "content unavailable", never invent coverage.
"""

from __future__ import annotations

from dataclasses import dataclass, field


SOUND_WAVES_CHAPTER_ID = "phy-p1-c1"


@dataclass(frozen=True)
class ConceptResources:
    lesson: bool = False
    quiz: bool = False
    flashcards: bool = False
    board_answer: bool = False
    numericals: bool = False
    pyq: bool = False


@dataclass(frozen=True)
class ConceptNode:
    concept_key: str  # "{chapter_catalog_id}:{mission_id}"
    chapter_catalog_id: str
    mission_id: str
    subject: str
    title: str
    objective: str
    prerequisites: tuple[str, ...]  # concept_keys
    exam_importance: float  # 0..1, relative weight inside the chapter
    typical_misconceptions: tuple[str, ...]
    practice_types: tuple[str, ...]
    estimated_minutes: int
    resources: ConceptResources = field(default_factory=ConceptResources)
    availability: str = "curated"  # curated | notes_only | locked
    textbook_reference: str = ""


def _sw(mission_id: str) -> str:
    return f"{SOUND_WAVES_CHAPTER_ID}:{mission_id}"


_SW_REFERENCE = "Kerala SCERT Physics, Standard X (2025) — Sound Waves, pp. 7-26"

_SW_RESOURCES = ConceptResources(lesson=True, quiz=True, flashcards=True, board_answer=True, pyq=False)
_SW_NUMERIC_RESOURCES = ConceptResources(lesson=True, quiz=True, flashcards=True, board_answer=True, numericals=True, pyq=False)


SOUND_WAVES_CONCEPTS: tuple[ConceptNode, ...] = (
    ConceptNode(
        concept_key=_sw("M1"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M1",
        subject="Physics",
        title="Oscillation, Amplitude, Period and Frequency",
        objective="Understand the basic terms of oscillatory motion using a pendulum and swing.",
        prerequisites=(),
        exam_importance=0.8,
        typical_misconceptions=(
            "One oscillation is just going forward — it must return to complete one.",
        ),
        practice_types=("recall", "mcq", "board_answer"),
        estimated_minutes=25,
        resources=_SW_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M2"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M2",
        subject="Physics",
        title="Natural Frequency, Forced Vibration & Resonance",
        objective="Understand natural frequency and how forced vibrations lead to resonance.",
        prerequisites=(_sw("M1"),),
        exam_importance=0.7,
        typical_misconceptions=(
            "Not recognising that the table is forced to vibrate by the mixie (forced vibration).",
            "Forgetting that resonance needs matching frequencies AND produces maximum amplitude.",
        ),
        practice_types=("recall", "mcq", "board_answer"),
        estimated_minutes=25,
        resources=_SW_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M3"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M3",
        subject="Physics",
        title="Wave Motion & Types of Waves",
        objective="Learn how waves transfer energy without transferring particles, and differentiate longitudinal and transverse waves.",
        prerequisites=(_sw("M1"),),
        exam_importance=0.85,
        typical_misconceptions=(
            "Thinking the material itself moves forward with the wave — only energy moves.",
        ),
        practice_types=("recall", "mcq", "diagram", "board_answer"),
        estimated_minutes=30,
        resources=_SW_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M4"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M4",
        subject="Physics",
        title="Frequency, Wavelength and Wave Speed (v = fλ)",
        objective="Define wavelength, speed of a wave, and understand the relationship v = fλ.",
        prerequisites=(_sw("M3"),),
        exam_importance=1.0,
        typical_misconceptions=(
            "Confusing wavelength with amplitude.",
        ),
        practice_types=("recall", "mcq", "formula", "board_answer"),
        estimated_minutes=30,
        resources=_SW_NUMERIC_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M5"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M5",
        subject="Physics",
        title="Numericals using v = fλ",
        objective="Solve exam-style numericals step-by-step using v = fλ.",
        prerequisites=(_sw("M4"),),
        exam_importance=1.0,
        typical_misconceptions=(
            "Substituting wavelength in centimetres without converting to metres.",
        ),
        practice_types=("numerical", "board_answer"),
        estimated_minutes=30,
        resources=_SW_NUMERIC_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M6"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M6",
        subject="Physics",
        title="Reflection, Echo & Reverberation",
        objective="Understand how sound reflects, the conditions for an echo, and reverberation.",
        prerequisites=(_sw("M3"),),
        exam_importance=0.9,
        typical_misconceptions=(
            "Thinking reflection doesn't happen in a small room — it does, we just cannot distinguish the echo.",
        ),
        practice_types=("recall", "mcq", "numerical", "board_answer"),
        estimated_minutes=30,
        resources=_SW_NUMERIC_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M7"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M7",
        subject="Physics",
        title="Limits of Audibility & Ultrasonic Uses",
        objective="Understand audible range, infrasonic and ultrasonic sounds, and their applications.",
        prerequisites=(_sw("M4"),),
        exam_importance=0.7,
        typical_misconceptions=(
            "Assuming humans can hear everything — dogs and bats hear higher frequencies than us.",
        ),
        practice_types=("recall", "mcq", "board_answer"),
        estimated_minutes=25,
        resources=_SW_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M8"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M8",
        subject="Physics",
        title="Seismic Waves and Tsunami",
        objective="Understand the destructive power of seismic waves and tsunamis.",
        prerequisites=(_sw("M3"),),
        exam_importance=0.6,
        typical_misconceptions=(
            "Waves are not just sound or light — they can carry massive destructive energy.",
        ),
        practice_types=("recall", "mcq", "board_answer"),
        estimated_minutes=25,
        resources=_SW_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
    ConceptNode(
        concept_key=_sw("M9"),
        chapter_catalog_id=SOUND_WAVES_CHAPTER_ID,
        mission_id="M9",
        subject="Physics",
        title="Sound Waves Chapter Test & Revision",
        objective="Complete a source-backed final chapter test and turn remaining mistakes into targeted revision.",
        prerequisites=tuple(_sw(f"M{index}") for index in range(1, 9)),
        exam_importance=1.0,
        typical_misconceptions=(
            "Knowing isolated definitions without connecting the formula, condition and application.",
        ),
        practice_types=("revision", "chapter_test", "numerical", "board_answer"),
        estimated_minutes=40,
        resources=_SW_NUMERIC_RESOURCES,
        textbook_reference=_SW_REFERENCE,
    ),
)


_CONCEPTS_BY_KEY: dict[str, ConceptNode] = {node.concept_key: node for node in SOUND_WAVES_CONCEPTS}
_CONCEPTS_BY_CHAPTER: dict[str, tuple[ConceptNode, ...]] = {SOUND_WAVES_CHAPTER_ID: SOUND_WAVES_CONCEPTS}


def concept_for(topic_key: str) -> ConceptNode | None:
    return _CONCEPTS_BY_KEY.get(topic_key)


def concepts_for_chapter(chapter_catalog_id: str) -> tuple[ConceptNode, ...]:
    return _CONCEPTS_BY_CHAPTER.get(chapter_catalog_id, ())


def all_concepts() -> tuple[ConceptNode, ...]:
    return SOUND_WAVES_CONCEPTS


def prerequisites_met(concept: ConceptNode, secure_or_developing_keys: set[str]) -> bool:
    """A concept may be scheduled as NEW learning only when every prerequisite
    has at least been introduced/learned (present in the provided key set)."""
    return all(prerequisite in secure_or_developing_keys for prerequisite in concept.prerequisites)


def ordered_chapter_concepts(chapter_catalog_id: str) -> list[ConceptNode]:
    """Concepts in a safe teaching order (prerequisites before dependants).

    The seed data is already authored in teaching order (M1..M9); this sorts
    defensively anyway so data edits cannot silently break ordering.
    """
    nodes = list(concepts_for_chapter(chapter_catalog_id))
    placed: list[ConceptNode] = []
    placed_keys: set[str] = set()
    remaining = nodes[:]
    while remaining:
        progressed = False
        for node in list(remaining):
            if all(prerequisite in placed_keys for prerequisite in node.prerequisites):
                placed.append(node)
                placed_keys.add(node.concept_key)
                remaining.remove(node)
                progressed = True
        if not progressed:  # cycle in data — fail safe by appending as-authored
            placed.extend(remaining)
            break
    return placed


def concept_to_dict(node: ConceptNode) -> dict[str, object]:
    return {
        "concept_key": node.concept_key,
        "chapter_catalog_id": node.chapter_catalog_id,
        "mission_id": node.mission_id,
        "subject": node.subject,
        "title": node.title,
        "objective": node.objective,
        "prerequisites": list(node.prerequisites),
        "exam_importance": node.exam_importance,
        "typical_misconceptions": list(node.typical_misconceptions),
        "practice_types": list(node.practice_types),
        "estimated_minutes": node.estimated_minutes,
        "resources": {
            "lesson": node.resources.lesson,
            "quiz": node.resources.quiz,
            "flashcards": node.resources.flashcards,
            "board_answer": node.resources.board_answer,
            "numericals": node.resources.numericals,
            "pyq": node.resources.pyq,
        },
        "availability": node.availability,
        "textbook_reference": node.textbook_reference,
    }