Spaces:
Running
Running
| """ | |
| 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"] |