File size: 16,306 Bytes
33de748
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
"""
ContentPack — Hardcoded KLP 1-7 vocabulary and KLP 7-10 grammar rules.
Can be replaced at runtime via the load_content_pack socket event.
"""

# ---------------------------------------------------------------------------
# KLP 7 Vocabulary (from XLSX, lesson-tagged)
# ---------------------------------------------------------------------------
KLP7_VOCAB = [
    # Nouns (good for sentence construction)
    {"korean": "학생", "english": "student", "type": "noun"},
    {"korean": "의사", "english": "doctor", "type": "noun"},
    {"korean": "선생님", "english": "teacher", "type": "noun"},
    {"korean": "친구", "english": "friend", "type": "noun"},
    {"korean": "사과", "english": "apple", "type": "noun"},
    {"korean": "책", "english": "book", "type": "noun"},
    {"korean": "커피", "english": "coffee", "type": "noun"},
    {"korean": "음료", "english": "beverage", "type": "noun"},
    {"korean": "날씨", "english": "weather", "type": "noun"},
    {"korean": "마음", "english": "mind/heart", "type": "noun"},
    {"korean": "일상", "english": "daily life", "type": "noun"},
    {"korean": "역할", "english": "role", "type": "noun"},
    {"korean": "분위기", "english": "atmosphere", "type": "noun"},
    {"korean": "등록금", "english": "tuition", "type": "noun"},
    {"korean": "생활비", "english": "living expense", "type": "noun"},
    {"korean": "시급", "english": "hourly wage", "type": "noun"},
    {"korean": "이력서", "english": "resume", "type": "noun"},
    {"korean": "약국", "english": "pharmacy", "type": "noun"},
    {"korean": "병원", "english": "hospital", "type": "noun"},
    {"korean": "목", "english": "throat/neck", "type": "noun"},
    {"korean": "열", "english": "fever", "type": "noun"},
    {"korean": "두통", "english": "headache", "type": "noun"},
    {"korean": "식사", "english": "meal", "type": "noun"},
    {"korean": "공부", "english": "studying", "type": "noun"},
    {"korean": "운동", "english": "exercise", "type": "noun"},
    {"korean": "음악", "english": "music", "type": "noun"},
    {"korean": "영화", "english": "movie", "type": "noun"},
    {"korean": "여행", "english": "travel", "type": "noun"},
    {"korean": "회의", "english": "meeting", "type": "noun"},
    {"korean": "선물", "english": "gift", "type": "noun"},
    # Pronouns / common subjects
    {"korean": "저", "english": "I (formal)", "type": "pronoun"},
    {"korean": "나", "english": "I (informal)", "type": "pronoun"},
    {"korean": "우리", "english": "we/my", "type": "pronoun"},
    {"korean": "이것", "english": "this", "type": "pronoun"},
    {"korean": "그것", "english": "that", "type": "pronoun"},
    # Verbs (stems for conjugation)
    {"korean": "가다", "english": "to go", "type": "verb", "stem": "가"},
    {"korean": "오다", "english": "to come", "type": "verb", "stem": "오"},
    {"korean": "먹다", "english": "to eat", "type": "verb", "stem": "먹"},
    {"korean": "마시다", "english": "to drink", "type": "verb", "stem": "마시"},
    {"korean": "공부하다", "english": "to study", "type": "verb", "stem": "공부하"},
    {"korean": "운동하다", "english": "to exercise", "type": "verb", "stem": "운동하"},
    {"korean": "일하다", "english": "to work", "type": "verb", "stem": "일하"},
    {"korean": "쉬다", "english": "to rest", "type": "verb", "stem": "쉬"},
    {"korean": "만나다", "english": "to meet", "type": "verb", "stem": "만나"},
    {"korean": "도와주다", "english": "to help", "type": "verb", "stem": "도와주"},
    {"korean": "사다", "english": "to buy", "type": "verb", "stem": "사"},
    {"korean": "팔다", "english": "to sell", "type": "verb", "stem": "팔"},
    {"korean": "읽다", "english": "to read", "type": "verb", "stem": "읽"},
    {"korean": "쓰다", "english": "to write", "type": "verb", "stem": "쓰"},
    {"korean": "자다", "english": "to sleep", "type": "verb", "stem": "자"},
    # Adjectives
    {"korean": "크다", "english": "to be big", "type": "adjective", "stem": "크"},
    {"korean": "작다", "english": "to be small", "type": "adjective", "stem": "작"},
    {"korean": "좋다", "english": "to be good", "type": "adjective", "stem": "좋"},
    {"korean": "나쁘다", "english": "to be bad", "type": "adjective", "stem": "나쁘"},
    {"korean": "맛있다", "english": "to be delicious", "type": "adjective", "stem": "맛있"},
    {"korean": "재미있다", "english": "to be fun", "type": "adjective", "stem": "재미있"},
    {"korean": "힘들다", "english": "to be difficult", "type": "adjective", "stem": "힘들"},
    {"korean": "바쁘다", "english": "to be busy", "type": "adjective", "stem": "바쁘"},
    {"korean": "춥다", "english": "to be cold", "type": "adjective", "stem": "춥"},
    {"korean": "덥다", "english": "to be hot", "type": "adjective", "stem": "덥"},
    {"korean": "피곤하다", "english": "to be tired", "type": "adjective", "stem": "피곤하"},
    {"korean": "달콤하다", "english": "to be sweet", "type": "adjective", "stem": "달콤하"},
]

# Lesson-specific vocab from KLP7-10 slides
KLP7_LESSON_VOCAB = [
    {"korean": "음료", "english": "beverage", "type": "noun"},
    {"korean": "달콤하다", "english": "to be sweet", "type": "adjective", "stem": "달콤하"},
    {"korean": "분위기", "english": "atmosphere", "type": "noun"},
    {"korean": "날씨", "english": "weather", "type": "noun"},
    {"korean": "김이 나다", "english": "to steam", "type": "verb", "stem": "나"},
    {"korean": "긴장을 풀다", "english": "to relax", "type": "verb", "stem": "풀"},
    {"korean": "마음", "english": "mind/heart", "type": "noun"},
    {"korean": "일상", "english": "daily life", "type": "noun"},
    {"korean": "역할", "english": "role", "type": "noun"},
    {"korean": "따뜻함", "english": "warmth", "type": "noun"},
    {"korean": "수업", "english": "class/lesson", "type": "noun"},
    {"korean": "숙제", "english": "homework", "type": "noun"},
    {"korean": "시험", "english": "exam", "type": "noun"},
    {"korean": "친구", "english": "friend", "type": "noun"},
    {"korean": "커피", "english": "coffee", "type": "noun"},
    {"korean": "저녁", "english": "dinner/evening", "type": "noun"},
    {"korean": "담배", "english": "cigarette", "type": "noun"},
    {"korean": "노래", "english": "song", "type": "noun"},
    {"korean": "춤", "english": "dance", "type": "noun"},
]

# ---------------------------------------------------------------------------
# Grammar Rule Definitions
# ---------------------------------------------------------------------------
GRAMMAR_RULES = {
    "topic_marker": {
        "id": "topic_marker",
        "name": "Topic Marker 은/는",
        "lesson": "KLP7-base",
        "description": "Attach 은 to consonant-ending nouns, 는 to vowel-ending nouns",
        "examples": [
            {"word": "학생", "result": "학생은", "note": "consonant ending"},
            {"word": "사과", "result": "사과는", "note": "vowel ending"},
        ],
        "difficulty": 1,
    },
    "copula": {
        "id": "copula",
        "name": "Copula 이에요/예요",
        "lesson": "KLP7-base",
        "description": "이에요 after consonant endings, 예요 after vowel endings",
        "examples": [
            {"word": "학생", "result": "학생이에요", "note": "consonant ending"},
            {"word": "의사", "result": "의사예요", "note": "vowel ending"},
        ],
        "difficulty": 1,
    },
    "negative_copula": {
        "id": "negative_copula",
        "name": "Negative Copula 이/가 아니에요",
        "lesson": "KLP7-base",
        "description": "이 아니에요 after consonant, 가 아니에요 after vowel/ㄹ",
        "examples": [
            {"sentence": "저는 의사가 아니에요", "translation": "I am not a doctor"},
            {"sentence": "이것은 사과가 아니에요", "translation": "This is not an apple"},
        ],
        "difficulty": 1,
    },
    "indirect_quote_dago": {
        "id": "indirect_quote_dago",
        "name": "Indirect Quotation -다고",
        "lesson": "KLP7-8",
        "description": "Quote statements. Verb: -ㄴ/는다고. Adjective/있다: -다고. Past: -었/았다고. Future: -ㄹ 거라고.",
        "conjugation_table": {
            "adjective_present": "-다고",
            "verb_present_consonant": "-는다고",
            "verb_present_vowel": "-ㄴ다고",
            "past": "-었/았다고",
            "future": "-ㄹ/을 거라고",
            "copula": "-(이)라고",
        },
        "examples": [
            {"direct": "감기 때문에 많이 아파요", "indirect": "감기 때문에 많이 아프다고 했어요"},
            {"direct": "다음 달에 독일에 돌아가요", "indirect": "독일에 돌아간다고 해요"},
        ],
        "difficulty": 2,
    },
    "indirect_quote_commands": {
        "id": "indirect_quote_commands",
        "name": "Indirect Quotation -(으)라고 / -지 말라고 / -달라고 / -주라고",
        "lesson": "KLP7-9",
        "description": "Quote commands, negative commands, and requests",
        "forms": {
            "command": "V + (으)라고 하다",
            "neg_command": "V + 지 말라고 하다",
            "request_me": "V + 아/어 달라고 하다",
            "request_other": "V + 아/어 주라고 하다",
        },
        "examples": [
            {"direct": "약을 먹으세요", "indirect": "약을 먹으라고 했어요", "form": "command"},
            {"direct": "담배를 피지 마세요", "indirect": "담배를 피지 말라고 했어요", "form": "neg_command"},
            {"direct": "도와주세요", "indirect": "도와 달라고 했어요", "form": "request_me"},
        ],
        "difficulty": 2,
    },
    "indirect_quote_questions": {
        "id": "indirect_quote_questions",
        "name": "Indirect Quotation -냐고 / -느냐고",
        "lesson": "KLP7-10",
        "description": "Quote questions",
        "forms": {
            "question": "V/Adj + 냐고 하다 (drop ㄹ)",
            "formal_question": "V + 느냐고 하다 (drop ㄹ)",
        },
        "examples": [
            {"direct": "어디 가요?", "indirect": "어디 가냐고 했어요"},
            {"direct": "사는 곳이 어디예요?", "indirect": "사는 곳이 어디냐고 물어봤어요"},
        ],
        "difficulty": 2,
    },
    "indirect_quote_suggestions": {
        "id": "indirect_quote_suggestions",
        "name": "Indirect Quotation -자고",
        "lesson": "KLP7-10",
        "description": "Quote suggestions",
        "forms": {"suggestion": "V + 자고 하다"},
        "examples": [
            {"direct": "같이 운동하자", "indirect": "운동을 같이 하자고 했어요"},
            {"direct": "수업을 시작해요", "indirect": "수업을 시작하자고 하셨어요"},
        ],
        "difficulty": 2,
    },
    "regret_expression": {
        "id": "regret_expression",
        "name": "Expressing Regrets -(으)ㄹ 걸 그랬다",
        "lesson": "KLP7-10",
        "description": "Express regret about past actions. Positive: -(으)ㄹ 걸 그랬다. Negative: -지 말 걸 그랬다",
        "examples": [
            {"sentence": "더 일찍 일어날 걸", "translation": "I should have gotten up earlier", "negative": False},
            {"sentence": "라면을 먹지 말 걸 그랬어", "translation": "I shouldn't have eaten ramyeon", "negative": True},
        ],
        "difficulty": 2,
    },
}

# ---------------------------------------------------------------------------
# Sentence Templates for Question Generation
# ---------------------------------------------------------------------------
# Each template has slots that Gemini or rule engine fills
SENTENCE_TEMPLATES = {
    "topic_marker": [
        {
            "template": "{noun}____ {noun2}{copula}",
            "slots": ["topic_marker", "copula"],
            "translation_pattern": "{noun_en} is a/an {noun2_en}",
        }
    ],
    "copula": [
        {
            "template": "{subject}{topic} {noun}____",
            "slots": ["copula"],
            "translation_pattern": "{subject_en} is a/an {noun_en}",
        }
    ],
    "negative_copula": [
        {
            "template": "{subject}{topic} {noun}____ 아니에요",
            "slots": ["neg_subject_marker"],
            "translation_pattern": "{subject_en} is not a/an {noun_en}",
        }
    ],
    "scrabble": [
        {
            "pattern": "[Subject+Topic] [Object+Object_marker] [Verb]",
            "example": "저는 커피를 마셔요",
            "translation": "I drink coffee",
        },
        {
            "pattern": "[Subject+Topic] [Noun+Copula]",
            "example": "저는 학생이에요",
            "translation": "I am a student",
        },
        {
            "pattern": "[Subject+Topic] [Adj]",
            "example": "날씨가 추워요",
            "translation": "The weather is cold",
        },
    ],
    "indirect_quote_dago": [
        {
            "pattern": "[Person]이/가 [sentence]다고 했어요/해요",
            "example": "민호가 감기 때문에 아프다고 했어요",
            "translation": "Minho said he is sick because of a cold",
        }
    ],
    "indirect_quote_commands": [
        {
            "pattern": "[Person]이/가 [verb](으)라고 했어요",
            "example": "의사가 약을 먹으라고 했어요",
            "translation": "The doctor said to take medicine",
        }
    ],
    "indirect_quote_questions": [
        {
            "pattern": "[Person]이/가 [question]냐고 물었어요",
            "example": "친구가 어디 가냐고 물었어요",
            "translation": "My friend asked where I was going",
        }
    ],
    "indirect_quote_suggestions": [
        {
            "pattern": "[Person]이/가 [verb]자고 했어요",
            "example": "친구가 같이 밥 먹자고 했어요",
            "translation": "My friend suggested eating together",
        }
    ],
    "regret_expression": [
        {
            "pattern": "[verb stem](으)ㄹ 걸 그랬다",
            "example": "더 열심히 공부할 걸 그랬어",
            "translation": "I should have studied harder",
        }
    ],
}

# ---------------------------------------------------------------------------
# Active Content Pack (can be replaced at runtime)
# ---------------------------------------------------------------------------
_active_pack = {
    "version": "1.0",
    "lesson": "KLP7-10",
    "vocab": KLP7_VOCAB + KLP7_LESSON_VOCAB,
    "grammar_rules": GRAMMAR_RULES,
    "templates": SENTENCE_TEMPLATES,
    "metadata": {
        "source": "hardcoded",
        "description": "KLP 7-10 Korean Learning Program",
    }
}


def get_active_pack() -> dict:
    return _active_pack


def replace_active_pack(new_pack: dict) -> dict:
    """
    Replace the active content pack with a parsed one from an uploaded file.
    Merges with defaults to ensure required keys always exist.
    """
    global _active_pack
    _active_pack = {
        "version": new_pack.get("version", "1.0"),
        "lesson": new_pack.get("lesson", "custom"),
        "vocab": new_pack.get("vocab", KLP7_VOCAB),
        "grammar_rules": new_pack.get("grammar_rules", GRAMMAR_RULES),
        "templates": new_pack.get("templates", SENTENCE_TEMPLATES),
        "metadata": {
            "source": "uploaded",
            "description": new_pack.get("description", "Custom content pack"),
        }
    }
    return _active_pack


def get_nouns(pack: dict = None) -> list:
    pack = pack or get_active_pack()
    return [v for v in pack["vocab"] if v["type"] == "noun"]


def get_pronouns(pack: dict = None) -> list:
    pack = pack or get_active_pack()
    return [v for v in pack["vocab"] if v["type"] == "pronoun"]


def get_verbs(pack: dict = None) -> list:
    pack = pack or get_active_pack()
    return [v for v in pack["vocab"] if v["type"] == "verb"]


def get_adjectives(pack: dict = None) -> list:
    pack = pack or get_active_pack()
    return [v for v in pack["vocab"] if v["type"] == "adjective"]