rairo commited on
Commit
33de748
·
verified ·
1 Parent(s): b01dc2a

Create content_pack.py

Browse files
Files changed (1) hide show
  1. content_pack.py +352 -0
content_pack.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ContentPack — Hardcoded KLP 1-7 vocabulary and KLP 7-10 grammar rules.
3
+ Can be replaced at runtime via the load_content_pack socket event.
4
+ """
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # KLP 7 Vocabulary (from XLSX, lesson-tagged)
8
+ # ---------------------------------------------------------------------------
9
+ KLP7_VOCAB = [
10
+ # Nouns (good for sentence construction)
11
+ {"korean": "학생", "english": "student", "type": "noun"},
12
+ {"korean": "의사", "english": "doctor", "type": "noun"},
13
+ {"korean": "선생님", "english": "teacher", "type": "noun"},
14
+ {"korean": "친구", "english": "friend", "type": "noun"},
15
+ {"korean": "사과", "english": "apple", "type": "noun"},
16
+ {"korean": "책", "english": "book", "type": "noun"},
17
+ {"korean": "커피", "english": "coffee", "type": "noun"},
18
+ {"korean": "음료", "english": "beverage", "type": "noun"},
19
+ {"korean": "날씨", "english": "weather", "type": "noun"},
20
+ {"korean": "마음", "english": "mind/heart", "type": "noun"},
21
+ {"korean": "일상", "english": "daily life", "type": "noun"},
22
+ {"korean": "역할", "english": "role", "type": "noun"},
23
+ {"korean": "분위기", "english": "atmosphere", "type": "noun"},
24
+ {"korean": "등록금", "english": "tuition", "type": "noun"},
25
+ {"korean": "생활비", "english": "living expense", "type": "noun"},
26
+ {"korean": "시급", "english": "hourly wage", "type": "noun"},
27
+ {"korean": "이력서", "english": "resume", "type": "noun"},
28
+ {"korean": "약국", "english": "pharmacy", "type": "noun"},
29
+ {"korean": "병원", "english": "hospital", "type": "noun"},
30
+ {"korean": "목", "english": "throat/neck", "type": "noun"},
31
+ {"korean": "열", "english": "fever", "type": "noun"},
32
+ {"korean": "두통", "english": "headache", "type": "noun"},
33
+ {"korean": "식사", "english": "meal", "type": "noun"},
34
+ {"korean": "공부", "english": "studying", "type": "noun"},
35
+ {"korean": "운동", "english": "exercise", "type": "noun"},
36
+ {"korean": "음악", "english": "music", "type": "noun"},
37
+ {"korean": "영화", "english": "movie", "type": "noun"},
38
+ {"korean": "여행", "english": "travel", "type": "noun"},
39
+ {"korean": "회의", "english": "meeting", "type": "noun"},
40
+ {"korean": "선물", "english": "gift", "type": "noun"},
41
+ # Pronouns / common subjects
42
+ {"korean": "저", "english": "I (formal)", "type": "pronoun"},
43
+ {"korean": "나", "english": "I (informal)", "type": "pronoun"},
44
+ {"korean": "우리", "english": "we/my", "type": "pronoun"},
45
+ {"korean": "이것", "english": "this", "type": "pronoun"},
46
+ {"korean": "그것", "english": "that", "type": "pronoun"},
47
+ # Verbs (stems for conjugation)
48
+ {"korean": "가다", "english": "to go", "type": "verb", "stem": "가"},
49
+ {"korean": "오다", "english": "to come", "type": "verb", "stem": "오"},
50
+ {"korean": "먹다", "english": "to eat", "type": "verb", "stem": "먹"},
51
+ {"korean": "마시다", "english": "to drink", "type": "verb", "stem": "마시"},
52
+ {"korean": "공부하다", "english": "to study", "type": "verb", "stem": "공부하"},
53
+ {"korean": "운동하다", "english": "to exercise", "type": "verb", "stem": "운동하"},
54
+ {"korean": "일하다", "english": "to work", "type": "verb", "stem": "일하"},
55
+ {"korean": "쉬다", "english": "to rest", "type": "verb", "stem": "쉬"},
56
+ {"korean": "만나다", "english": "to meet", "type": "verb", "stem": "만나"},
57
+ {"korean": "도와주다", "english": "to help", "type": "verb", "stem": "도와주"},
58
+ {"korean": "사다", "english": "to buy", "type": "verb", "stem": "사"},
59
+ {"korean": "팔다", "english": "to sell", "type": "verb", "stem": "팔"},
60
+ {"korean": "읽다", "english": "to read", "type": "verb", "stem": "읽"},
61
+ {"korean": "쓰다", "english": "to write", "type": "verb", "stem": "쓰"},
62
+ {"korean": "자다", "english": "to sleep", "type": "verb", "stem": "자"},
63
+ # Adjectives
64
+ {"korean": "크다", "english": "to be big", "type": "adjective", "stem": "크"},
65
+ {"korean": "작다", "english": "to be small", "type": "adjective", "stem": "작"},
66
+ {"korean": "좋다", "english": "to be good", "type": "adjective", "stem": "좋"},
67
+ {"korean": "나쁘다", "english": "to be bad", "type": "adjective", "stem": "나쁘"},
68
+ {"korean": "맛있다", "english": "to be delicious", "type": "adjective", "stem": "맛있"},
69
+ {"korean": "재미있다", "english": "to be fun", "type": "adjective", "stem": "재미있"},
70
+ {"korean": "힘들다", "english": "to be difficult", "type": "adjective", "stem": "힘들"},
71
+ {"korean": "바쁘다", "english": "to be busy", "type": "adjective", "stem": "바쁘"},
72
+ {"korean": "춥다", "english": "to be cold", "type": "adjective", "stem": "춥"},
73
+ {"korean": "덥다", "english": "to be hot", "type": "adjective", "stem": "덥"},
74
+ {"korean": "피곤하다", "english": "to be tired", "type": "adjective", "stem": "피곤하"},
75
+ {"korean": "달콤하다", "english": "to be sweet", "type": "adjective", "stem": "달콤하"},
76
+ ]
77
+
78
+ # Lesson-specific vocab from KLP7-10 slides
79
+ KLP7_LESSON_VOCAB = [
80
+ {"korean": "음료", "english": "beverage", "type": "noun"},
81
+ {"korean": "달콤하다", "english": "to be sweet", "type": "adjective", "stem": "달콤하"},
82
+ {"korean": "분위기", "english": "atmosphere", "type": "noun"},
83
+ {"korean": "날씨", "english": "weather", "type": "noun"},
84
+ {"korean": "김이 나다", "english": "to steam", "type": "verb", "stem": "나"},
85
+ {"korean": "긴장을 풀다", "english": "to relax", "type": "verb", "stem": "풀"},
86
+ {"korean": "마음", "english": "mind/heart", "type": "noun"},
87
+ {"korean": "일상", "english": "daily life", "type": "noun"},
88
+ {"korean": "역할", "english": "role", "type": "noun"},
89
+ {"korean": "따뜻함", "english": "warmth", "type": "noun"},
90
+ {"korean": "수업", "english": "class/lesson", "type": "noun"},
91
+ {"korean": "숙제", "english": "homework", "type": "noun"},
92
+ {"korean": "시험", "english": "exam", "type": "noun"},
93
+ {"korean": "친구", "english": "friend", "type": "noun"},
94
+ {"korean": "커피", "english": "coffee", "type": "noun"},
95
+ {"korean": "저녁", "english": "dinner/evening", "type": "noun"},
96
+ {"korean": "담배", "english": "cigarette", "type": "noun"},
97
+ {"korean": "노래", "english": "song", "type": "noun"},
98
+ {"korean": "춤", "english": "dance", "type": "noun"},
99
+ ]
100
+
101
+ # ---------------------------------------------------------------------------
102
+ # Grammar Rule Definitions
103
+ # ---------------------------------------------------------------------------
104
+ GRAMMAR_RULES = {
105
+ "topic_marker": {
106
+ "id": "topic_marker",
107
+ "name": "Topic Marker 은/는",
108
+ "lesson": "KLP7-base",
109
+ "description": "Attach 은 to consonant-ending nouns, 는 to vowel-ending nouns",
110
+ "examples": [
111
+ {"word": "학생", "result": "학생은", "note": "consonant ending"},
112
+ {"word": "사과", "result": "사과는", "note": "vowel ending"},
113
+ ],
114
+ "difficulty": 1,
115
+ },
116
+ "copula": {
117
+ "id": "copula",
118
+ "name": "Copula 이에요/예요",
119
+ "lesson": "KLP7-base",
120
+ "description": "이에요 after consonant endings, 예요 after vowel endings",
121
+ "examples": [
122
+ {"word": "학생", "result": "학생이에요", "note": "consonant ending"},
123
+ {"word": "의사", "result": "의사예요", "note": "vowel ending"},
124
+ ],
125
+ "difficulty": 1,
126
+ },
127
+ "negative_copula": {
128
+ "id": "negative_copula",
129
+ "name": "Negative Copula 이/가 아니에요",
130
+ "lesson": "KLP7-base",
131
+ "description": "이 아니에요 after consonant, 가 아니에요 after vowel/ㄹ",
132
+ "examples": [
133
+ {"sentence": "저는 의사가 아니에요", "translation": "I am not a doctor"},
134
+ {"sentence": "이것은 사과가 아니에요", "translation": "This is not an apple"},
135
+ ],
136
+ "difficulty": 1,
137
+ },
138
+ "indirect_quote_dago": {
139
+ "id": "indirect_quote_dago",
140
+ "name": "Indirect Quotation -다고",
141
+ "lesson": "KLP7-8",
142
+ "description": "Quote statements. Verb: -ㄴ/는다고. Adjective/있다: -다고. Past: -었/았다고. Future: -ㄹ 거라고.",
143
+ "conjugation_table": {
144
+ "adjective_present": "-다고",
145
+ "verb_present_consonant": "-는다고",
146
+ "verb_present_vowel": "-ㄴ다고",
147
+ "past": "-었/았다고",
148
+ "future": "-ㄹ/을 거라고",
149
+ "copula": "-(이)라고",
150
+ },
151
+ "examples": [
152
+ {"direct": "감기 때문에 많이 아파요", "indirect": "감기 때문에 많이 아프다고 했어요"},
153
+ {"direct": "다음 달에 독일에 돌아가요", "indirect": "독일에 돌아간다고 해요"},
154
+ ],
155
+ "difficulty": 2,
156
+ },
157
+ "indirect_quote_commands": {
158
+ "id": "indirect_quote_commands",
159
+ "name": "Indirect Quotation -(으)라고 / -지 말라고 / -달라고 / -주라고",
160
+ "lesson": "KLP7-9",
161
+ "description": "Quote commands, negative commands, and requests",
162
+ "forms": {
163
+ "command": "V + (으)라고 하다",
164
+ "neg_command": "V + 지 말라고 하다",
165
+ "request_me": "V + 아/어 달라고 하다",
166
+ "request_other": "V + 아/어 주라고 하다",
167
+ },
168
+ "examples": [
169
+ {"direct": "약을 먹으세요", "indirect": "약을 먹으라고 했어요", "form": "command"},
170
+ {"direct": "담배를 피지 마세요", "indirect": "담배를 피지 말라고 했어요", "form": "neg_command"},
171
+ {"direct": "도와주세요", "indirect": "도와 달라고 했어요", "form": "request_me"},
172
+ ],
173
+ "difficulty": 2,
174
+ },
175
+ "indirect_quote_questions": {
176
+ "id": "indirect_quote_questions",
177
+ "name": "Indirect Quotation -냐고 / -느냐고",
178
+ "lesson": "KLP7-10",
179
+ "description": "Quote questions",
180
+ "forms": {
181
+ "question": "V/Adj + 냐고 하다 (drop ㄹ)",
182
+ "formal_question": "V + 느냐고 하다 (drop ㄹ)",
183
+ },
184
+ "examples": [
185
+ {"direct": "어디 가요?", "indirect": "어디 가냐고 했어요"},
186
+ {"direct": "사는 곳이 어디예요?", "indirect": "사는 곳이 어디냐고 물어봤어요"},
187
+ ],
188
+ "difficulty": 2,
189
+ },
190
+ "indirect_quote_suggestions": {
191
+ "id": "indirect_quote_suggestions",
192
+ "name": "Indirect Quotation -자고",
193
+ "lesson": "KLP7-10",
194
+ "description": "Quote suggestions",
195
+ "forms": {"suggestion": "V + 자고 하다"},
196
+ "examples": [
197
+ {"direct": "같이 운동하자", "indirect": "운동을 같이 하자고 했어요"},
198
+ {"direct": "수업을 시작해요", "indirect": "수업을 시작하자고 하셨어요"},
199
+ ],
200
+ "difficulty": 2,
201
+ },
202
+ "regret_expression": {
203
+ "id": "regret_expression",
204
+ "name": "Expressing Regrets -(으)ㄹ 걸 그랬다",
205
+ "lesson": "KLP7-10",
206
+ "description": "Express regret about past actions. Positive: -(으)ㄹ 걸 그랬다. Negative: -지 말 걸 그랬다",
207
+ "examples": [
208
+ {"sentence": "더 일찍 일어날 걸", "translation": "I should have gotten up earlier", "negative": False},
209
+ {"sentence": "라면을 먹지 말 걸 그랬어", "translation": "I shouldn't have eaten ramyeon", "negative": True},
210
+ ],
211
+ "difficulty": 2,
212
+ },
213
+ }
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Sentence Templates for Question Generation
217
+ # ---------------------------------------------------------------------------
218
+ # Each template has slots that Gemini or rule engine fills
219
+ SENTENCE_TEMPLATES = {
220
+ "topic_marker": [
221
+ {
222
+ "template": "{noun}____ {noun2}{copula}",
223
+ "slots": ["topic_marker", "copula"],
224
+ "translation_pattern": "{noun_en} is a/an {noun2_en}",
225
+ }
226
+ ],
227
+ "copula": [
228
+ {
229
+ "template": "{subject}{topic} {noun}____",
230
+ "slots": ["copula"],
231
+ "translation_pattern": "{subject_en} is a/an {noun_en}",
232
+ }
233
+ ],
234
+ "negative_copula": [
235
+ {
236
+ "template": "{subject}{topic} {noun}____ 아니에요",
237
+ "slots": ["neg_subject_marker"],
238
+ "translation_pattern": "{subject_en} is not a/an {noun_en}",
239
+ }
240
+ ],
241
+ "scrabble": [
242
+ {
243
+ "pattern": "[Subject+Topic] [Object+Object_marker] [Verb]",
244
+ "example": "저는 커피를 마셔요",
245
+ "translation": "I drink coffee",
246
+ },
247
+ {
248
+ "pattern": "[Subject+Topic] [Noun+Copula]",
249
+ "example": "저는 학생이에요",
250
+ "translation": "I am a student",
251
+ },
252
+ {
253
+ "pattern": "[Subject+Topic] [Adj]",
254
+ "example": "날씨가 추워요",
255
+ "translation": "The weather is cold",
256
+ },
257
+ ],
258
+ "indirect_quote_dago": [
259
+ {
260
+ "pattern": "[Person]이/가 [sentence]다고 했어요/해요",
261
+ "example": "민호가 감기 때문에 아프다고 했어요",
262
+ "translation": "Minho said he is sick because of a cold",
263
+ }
264
+ ],
265
+ "indirect_quote_commands": [
266
+ {
267
+ "pattern": "[Person]이/가 [verb](으)라고 했어요",
268
+ "example": "의사가 약을 먹으라고 했어요",
269
+ "translation": "The doctor said to take medicine",
270
+ }
271
+ ],
272
+ "indirect_quote_questions": [
273
+ {
274
+ "pattern": "[Person]이/가 [question]냐고 물었어요",
275
+ "example": "친구가 어디 가냐고 물었어요",
276
+ "translation": "My friend asked where I was going",
277
+ }
278
+ ],
279
+ "indirect_quote_suggestions": [
280
+ {
281
+ "pattern": "[Person]이/가 [verb]자고 했어요",
282
+ "example": "친구가 같이 밥 먹자고 했어요",
283
+ "translation": "My friend suggested eating together",
284
+ }
285
+ ],
286
+ "regret_expression": [
287
+ {
288
+ "pattern": "[verb stem](으)ㄹ 걸 그랬다",
289
+ "example": "더 열심히 공부할 걸 그랬어",
290
+ "translation": "I should have studied harder",
291
+ }
292
+ ],
293
+ }
294
+
295
+ # ---------------------------------------------------------------------------
296
+ # Active Content Pack (can be replaced at runtime)
297
+ # ---------------------------------------------------------------------------
298
+ _active_pack = {
299
+ "version": "1.0",
300
+ "lesson": "KLP7-10",
301
+ "vocab": KLP7_VOCAB + KLP7_LESSON_VOCAB,
302
+ "grammar_rules": GRAMMAR_RULES,
303
+ "templates": SENTENCE_TEMPLATES,
304
+ "metadata": {
305
+ "source": "hardcoded",
306
+ "description": "KLP 7-10 Korean Learning Program",
307
+ }
308
+ }
309
+
310
+
311
+ def get_active_pack() -> dict:
312
+ return _active_pack
313
+
314
+
315
+ def replace_active_pack(new_pack: dict) -> dict:
316
+ """
317
+ Replace the active content pack with a parsed one from an uploaded file.
318
+ Merges with defaults to ensure required keys always exist.
319
+ """
320
+ global _active_pack
321
+ _active_pack = {
322
+ "version": new_pack.get("version", "1.0"),
323
+ "lesson": new_pack.get("lesson", "custom"),
324
+ "vocab": new_pack.get("vocab", KLP7_VOCAB),
325
+ "grammar_rules": new_pack.get("grammar_rules", GRAMMAR_RULES),
326
+ "templates": new_pack.get("templates", SENTENCE_TEMPLATES),
327
+ "metadata": {
328
+ "source": "uploaded",
329
+ "description": new_pack.get("description", "Custom content pack"),
330
+ }
331
+ }
332
+ return _active_pack
333
+
334
+
335
+ def get_nouns(pack: dict = None) -> list:
336
+ pack = pack or get_active_pack()
337
+ return [v for v in pack["vocab"] if v["type"] == "noun"]
338
+
339
+
340
+ def get_pronouns(pack: dict = None) -> list:
341
+ pack = pack or get_active_pack()
342
+ return [v for v in pack["vocab"] if v["type"] == "pronoun"]
343
+
344
+
345
+ def get_verbs(pack: dict = None) -> list:
346
+ pack = pack or get_active_pack()
347
+ return [v for v in pack["vocab"] if v["type"] == "verb"]
348
+
349
+
350
+ def get_adjectives(pack: dict = None) -> list:
351
+ pack = pack or get_active_pack()
352
+ return [v for v in pack["vocab"] if v["type"] == "adjective"]