| """ |
| Learning Service - Vocabulary, Alphabet, and SRS (Spaced Repetition System) |
| """ |
|
|
| import json |
| from pathlib import Path |
| from typing import List, Dict, Any, Optional |
| from datetime import datetime, timedelta |
|
|
|
|
| class LearningService: |
| """Service for vocabulary and learning features""" |
| |
| def __init__(self): |
| self.data_dir = Path(__file__).parent.parent / "data" |
| self._vocabulary: Dict[str, Any] = {} |
| self._alphabet: Dict[str, Any] = {} |
| self._progress: Dict[str, Any] = {} |
| self._load_data() |
| |
| def _load_data(self): |
| """Load vocabulary and alphabet data""" |
| try: |
| vocab_path = self.data_dir / "vocabulary.json" |
| if vocab_path.exists(): |
| with open(vocab_path, "r", encoding="utf-8") as f: |
| self._vocabulary = json.load(f) |
| else: |
| self._vocabulary = self._get_default_vocabulary() |
| |
| alphabet_path = self.data_dir / "alphabet.json" |
| if alphabet_path.exists(): |
| with open(alphabet_path, "r", encoding="utf-8") as f: |
| self._alphabet = json.load(f) |
| else: |
| self._alphabet = self._get_default_alphabet() |
| |
| except Exception as e: |
| print(f"Error loading data: {e}") |
| self._vocabulary = self._get_default_vocabulary() |
| self._alphabet = self._get_default_alphabet() |
| |
| def get_vocabulary_categories(self) -> List[str]: |
| """Get list of vocabulary categories""" |
| return list(self._vocabulary.get("categories", {}).keys()) |
| |
| def get_vocabulary_by_category(self, category: Optional[str] = None) -> Dict[str, Any]: |
| """Get vocabulary items, optionally filtered by category""" |
| categories = self._vocabulary.get("categories", {}) |
| |
| if category and category in categories: |
| return { |
| "category_id": category, |
| "category_name_zh": categories[category].get("name_zh", category), |
| "category_name_en": categories[category].get("name_en", category), |
| "items": categories[category].get("items", []) |
| } |
| |
| |
| result = [] |
| for cat_id, cat_data in categories.items(): |
| result.append({ |
| "category_id": cat_id, |
| "category_name_zh": cat_data.get("name_zh", cat_id), |
| "category_name_en": cat_data.get("name_en", cat_id), |
| "items": cat_data.get("items", []) |
| }) |
| return {"categories": result} |
| |
| def get_alphabet(self) -> Dict[str, Any]: |
| """Get Tibetan alphabet data""" |
| return self._alphabet |
| |
| def get_flashcards(self, category: Optional[str] = None, limit: int = 10) -> List[Dict[str, Any]]: |
| """Get flashcards for practice""" |
| all_items = [] |
| categories = self._vocabulary.get("categories", {}) |
| |
| if category and category in categories: |
| all_items = categories[category].get("items", []) |
| else: |
| for cat_data in categories.values(): |
| all_items.extend(cat_data.get("items", [])) |
| |
| |
| |
| return all_items[:limit] |
| |
| def update_progress(self, vocabulary_id: str, quality: int) -> Dict[str, Any]: |
| """ |
| Update learning progress using SM-2 algorithm |
| |
| quality: 0-5 (0=complete blackout, 5=perfect response) |
| """ |
| progress = self._progress.get(vocabulary_id, { |
| "ease_factor": 2.5, |
| "interval_days": 1, |
| "repetitions": 0, |
| "next_review": None |
| }) |
| |
| |
| if quality >= 3: |
| |
| if progress["repetitions"] == 0: |
| progress["interval_days"] = 1 |
| elif progress["repetitions"] == 1: |
| progress["interval_days"] = 6 |
| else: |
| progress["interval_days"] = round( |
| progress["interval_days"] * progress["ease_factor"] |
| ) |
| progress["repetitions"] += 1 |
| else: |
| |
| progress["repetitions"] = 0 |
| progress["interval_days"] = 1 |
| |
| |
| progress["ease_factor"] = max( |
| 1.3, |
| progress["ease_factor"] + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)) |
| ) |
| |
| |
| next_review = datetime.now() + timedelta(days=progress["interval_days"]) |
| progress["next_review"] = next_review.isoformat() |
| |
| self._progress[vocabulary_id] = progress |
| return progress |
| |
| def _get_default_vocabulary(self) -> Dict[str, Any]: |
| """Default vocabulary data""" |
| return { |
| "categories": { |
| "greetings": { |
| "name_zh": "问候语", |
| "name_en": "Greetings", |
| "items": [ |
| { |
| "id": "greet_001", |
| "tibetan": "བཀྲ་ཤིས་བདེ་ལེགས།", |
| "chinese": "扎西德勒(吉祥如意/你好)", |
| "english": "Tashi Delek (Hello/Blessings)", |
| "pronunciation": "tashi delek", |
| "category": "greetings" |
| }, |
| { |
| "id": "greet_002", |
| "tibetan": "ཐུགས་རྗེ་ཆེ།", |
| "chinese": "谢谢", |
| "english": "Thank you", |
| "pronunciation": "thuk je che", |
| "category": "greetings" |
| }, |
| { |
| "id": "greet_003", |
| "tibetan": "མཇལ་བར་དགའ་པོ་བྱུང་།", |
| "chinese": "很高兴见到你", |
| "english": "Nice to meet you", |
| "pronunciation": "jal war ga po chung", |
| "category": "greetings" |
| }, |
| { |
| "id": "greet_004", |
| "tibetan": "བདེ་མོ།", |
| "chinese": "再见", |
| "english": "Goodbye", |
| "pronunciation": "de mo", |
| "category": "greetings" |
| } |
| ] |
| }, |
| "numbers": { |
| "name_zh": "数字", |
| "name_en": "Numbers", |
| "items": [ |
| {"id": "num_001", "tibetan": "གཅིག", "chinese": "一", "english": "One", "pronunciation": "chik", "category": "numbers"}, |
| {"id": "num_002", "tibetan": "གཉིས", "chinese": "二", "english": "Two", "pronunciation": "nyi", "category": "numbers"}, |
| {"id": "num_003", "tibetan": "གསུམ", "chinese": "三", "english": "Three", "pronunciation": "sum", "category": "numbers"}, |
| {"id": "num_004", "tibetan": "བཞི", "chinese": "四", "english": "Four", "pronunciation": "shi", "category": "numbers"}, |
| {"id": "num_005", "tibetan": "ལྔ", "chinese": "五", "english": "Five", "pronunciation": "nga", "category": "numbers"}, |
| {"id": "num_006", "tibetan": "དྲུག", "chinese": "六", "english": "Six", "pronunciation": "druk", "category": "numbers"}, |
| {"id": "num_007", "tibetan": "བདུན", "chinese": "七", "english": "Seven", "pronunciation": "dün", "category": "numbers"}, |
| {"id": "num_008", "tibetan": "བརྒྱད", "chinese": "八", "english": "Eight", "pronunciation": "gyé", "category": "numbers"}, |
| {"id": "num_009", "tibetan": "དགུ", "chinese": "九", "english": "Nine", "pronunciation": "gu", "category": "numbers"}, |
| {"id": "num_010", "tibetan": "བཅུ", "chinese": "十", "english": "Ten", "pronunciation": "chu", "category": "numbers"} |
| ] |
| }, |
| "colors": { |
| "name_zh": "颜色", |
| "name_en": "Colors", |
| "items": [ |
| {"id": "color_001", "tibetan": "དཀར་པོ", "chinese": "白色", "english": "White", "pronunciation": "kar po", "category": "colors"}, |
| {"id": "color_002", "tibetan": "ནག་པོ", "chinese": "黑色", "english": "Black", "pronunciation": "nak po", "category": "colors"}, |
| {"id": "color_003", "tibetan": "དམར་པོ", "chinese": "红色", "english": "Red", "pronunciation": "mar po", "category": "colors"}, |
| {"id": "color_004", "tibetan": "སྔོན་པོ", "chinese": "蓝色", "english": "Blue", "pronunciation": "ngön po", "category": "colors"}, |
| {"id": "color_005", "tibetan": "སེར་པོ", "chinese": "黄色", "english": "Yellow", "pronunciation": "ser po", "category": "colors"}, |
| {"id": "color_006", "tibetan": "ལྗང་ཁུ", "chinese": "绿色", "english": "Green", "pronunciation": "jang khu", "category": "colors"} |
| ] |
| }, |
| "daily": { |
| "name_zh": "日常用语", |
| "name_en": "Daily Phrases", |
| "items": [ |
| {"id": "daily_001", "tibetan": "ང", "chinese": "我", "english": "I/Me", "pronunciation": "nga", "category": "daily"}, |
| {"id": "daily_002", "tibetan": "ཁྱོད", "chinese": "你", "english": "You", "pronunciation": "khyö", "category": "daily"}, |
| {"id": "daily_003", "tibetan": "ཁོ", "chinese": "他", "english": "He", "pronunciation": "kho", "category": "daily"}, |
| {"id": "daily_004", "tibetan": "མོ", "chinese": "她", "english": "She", "pronunciation": "mo", "category": "daily"}, |
| {"id": "daily_005", "tibetan": "ཡིན།", "chinese": "是", "english": "Yes/Is", "pronunciation": "yin", "category": "daily"}, |
| {"id": "daily_006", "tibetan": "མིན།", "chinese": "不是", "english": "No/Is not", "pronunciation": "min", "category": "daily"}, |
| {"id": "daily_007", "tibetan": "དགོས།", "chinese": "需要", "english": "Need", "pronunciation": "gö", "category": "daily"}, |
| {"id": "daily_008", "tibetan": "ཆུ", "chinese": "水", "english": "Water", "pronunciation": "chu", "category": "daily"} |
| ] |
| } |
| } |
| } |
| |
| def _get_default_alphabet(self) -> Dict[str, Any]: |
| """Default Tibetan alphabet data""" |
| return { |
| "consonants": [ |
| {"id": "con_01", "letter": "ཀ", "unicode": "U+0F40", "pronunciation": "ka", "description_zh": "清辅音,类似汉语拼音'k'", "description_en": "Voiceless velar stop, like 'k' in 'kite'"}, |
| {"id": "con_02", "letter": "ཁ", "unicode": "U+0F41", "pronunciation": "kha", "description_zh": "送气音,类似'k'但带强气流", "description_en": "Aspirated 'k', like 'k' in 'king' with more breath"}, |
| {"id": "con_03", "letter": "ག", "unicode": "U+0F42", "pronunciation": "ga", "description_zh": "浊辅音,类似汉语拼音'g'", "description_en": "Voiced velar stop, like 'g' in 'go'"}, |
| {"id": "con_04", "letter": "ང", "unicode": "U+0F44", "pronunciation": "nga", "description_zh": "鼻音,类似汉语拼音'ng'", "description_en": "Velar nasal, like 'ng' in 'sing'"}, |
| {"id": "con_05", "letter": "ཅ", "unicode": "U+0F45", "pronunciation": "ca", "description_zh": "塞擦音,类似汉语拼音'j'", "description_en": "Voiceless palatal affricate, like 'ch' in 'church'"}, |
| {"id": "con_06", "letter": "ཆ", "unicode": "U+0F46", "pronunciation": "cha", "description_zh": "送气塞擦音", "description_en": "Aspirated 'ch'"}, |
| {"id": "con_07", "letter": "ཇ", "unicode": "U+0F47", "pronunciation": "ja", "description_zh": "浊塞擦音,类似汉语拼音'zh'", "description_en": "Voiced palatal affricate, like 'j' in 'judge'"}, |
| {"id": "con_08", "letter": "ཉ", "unicode": "U+0F49", "pronunciation": "nya", "description_zh": "鼻音,类似西班牙语'ñ'", "description_en": "Palatal nasal, like 'ny' in 'canyon'"}, |
| {"id": "con_09", "letter": "ཏ", "unicode": "U+0F4F", "pronunciation": "ta", "description_zh": "清塞音,类似汉语拼音't'", "description_en": "Voiceless dental stop, like 't' in 'top'"}, |
| {"id": "con_10", "letter": "ཐ", "unicode": "U+0F50", "pronunciation": "tha", "description_zh": "送气塞音", "description_en": "Aspirated 't'"}, |
| {"id": "con_11", "letter": "ད", "unicode": "U+0F51", "pronunciation": "da", "description_zh": "浊塞音,类似汉语拼音'd'", "description_en": "Voiced dental stop, like 'd' in 'dog'"}, |
| {"id": "con_12", "letter": "ན", "unicode": "U+0F53", "pronunciation": "na", "description_zh": "鼻音,类似汉语拼音'n'", "description_en": "Dental nasal, like 'n' in 'no'"}, |
| {"id": "con_13", "letter": "པ", "unicode": "U+0F54", "pronunciation": "pa", "description_zh": "清塞音,类似汉语拼音'p'", "description_en": "Voiceless bilabial stop, like 'p' in 'pot'"}, |
| {"id": "con_14", "letter": "ཕ", "unicode": "U+0F55", "pronunciation": "pha", "description_zh": "送气塞音", "description_en": "Aspirated 'p'"}, |
| {"id": "con_15", "letter": "བ", "unicode": "U+0F56", "pronunciation": "ba", "description_zh": "浊塞音,类似汉语拼音'b'", "description_en": "Voiced bilabial stop, like 'b' in 'boy'"}, |
| {"id": "con_16", "letter": "མ", "unicode": "U+0F58", "pronunciation": "ma", "description_zh": "鼻音,类似汉语拼音'm'", "description_en": "Bilabial nasal, like 'm' in 'mom'"}, |
| {"id": "con_17", "letter": "ཙ", "unicode": "U+0F59", "pronunciation": "tsa", "description_zh": "塞擦音,类似汉语拼音'c'", "description_en": "Voiceless alveolar affricate, like 'ts' in 'cats'"}, |
| {"id": "con_18", "letter": "ཚ", "unicode": "U+0F5A", "pronunciation": "tsha", "description_zh": "送气塞擦音", "description_en": "Aspirated 'ts'"}, |
| {"id": "con_19", "letter": "ཛ", "unicode": "U+0F5B", "pronunciation": "dza", "description_zh": "浊塞擦音,类似汉语拼音'z'", "description_en": "Voiced alveolar affricate, like 'dz' in 'adze'"}, |
| {"id": "con_20", "letter": "ཝ", "unicode": "U+0F5D", "pronunciation": "wa", "description_zh": "半元音,类似汉语拼音'w'", "description_en": "Labial approximant, like 'w' in 'water'"}, |
| {"id": "con_21", "letter": "ཞ", "unicode": "U+0F5E", "pronunciation": "zha", "description_zh": "浊擦音,类似汉语拼音'r'", "description_en": "Voiced retroflex fricative, like 'zh' in 'measure'"}, |
| {"id": "con_22", "letter": "ཟ", "unicode": "U+0F5F", "pronunciation": "za", "description_zh": "浊擦音,类似汉语拼音'z'", "description_en": "Voiced alveolar fricative, like 'z' in 'zoo'"}, |
| {"id": "con_23", "letter": "འ", "unicode": "U+0F60", "pronunciation": "'a", "description_zh": "声门塞音,表示元音开始", "description_en": "Glottal stop, used as a vowel carrier"}, |
| {"id": "con_24", "letter": "ཡ", "unicode": "U+0F61", "pronunciation": "ya", "description_zh": "半元音,类似汉语拼音'y'", "description_en": "Palatal approximant, like 'y' in 'yes'"}, |
| {"id": "con_25", "letter": "ར", "unicode": "U+0F62", "pronunciation": "ra", "description_zh": "颤音,类似西班牙语'r'", "description_en": "Alveolar trill, like rolled 'r'"}, |
| {"id": "con_26", "letter": "ལ", "unicode": "U+0F63", "pronunciation": "la", "description_zh": "边音,类似汉语拼音'l'", "description_en": "Alveolar lateral, like 'l' in 'love'"}, |
| {"id": "con_27", "letter": "ཤ", "unicode": "U+0F64", "pronunciation": "sha", "description_zh": "清擦音,类似汉语拼音'sh'", "description_en": "Voiceless palatal fricative, like 'sh' in 'ship'"}, |
| {"id": "con_28", "letter": "ས", "unicode": "U+0F66", "pronunciation": "sa", "description_zh": "清擦音,类似汉语拼音's'", "description_en": "Voiceless alveolar fricative, like 's' in 'sun'"}, |
| {"id": "con_29", "letter": "ཧ", "unicode": "U+0F67", "pronunciation": "ha", "description_zh": "清擦音,类似汉语拼音'h'", "description_en": "Voiceless glottal fricative, like 'h' in 'hat'"}, |
| {"id": "con_30", "letter": "ཨ", "unicode": "U+0F68", "pronunciation": "a", "description_zh": "元音字母", "description_en": "Vowel letter 'a'"} |
| ], |
| "vowels": [ |
| {"id": "vow_01", "letter": "ི", "unicode": "U+0F72", "pronunciation": "i", "description_zh": "元音符号 i,加在辅音上方", "description_en": "Vowel sign 'i', placed above the consonant"}, |
| {"id": "vow_02", "letter": "ུ", "unicode": "U+0F74", "pronunciation": "u", "description_zh": "元音符号 u,加在辅音下方", "description_en": "Vowel sign 'u', placed below the consonant"}, |
| {"id": "vow_03", "letter": "ེ", "unicode": "U+0F7A", "pronunciation": "e", "description_zh": "元音符号 e,加在辅音上方", "description_en": "Vowel sign 'e', placed above the consonant"}, |
| {"id": "vow_04", "letter": "ོ", "unicode": "U+0F7C", "pronunciation": "o", "description_zh": "元音符号 o,加在辅音上方", "description_en": "Vowel sign 'o', placed above the consonant"} |
| ] |
| } |
|
|
|
|
| |
| learning_service = LearningService() |
|
|