KoreAI-API / content_pack.py
rairo's picture
Create content_pack.py
33de748 verified
"""
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"]