from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from database.models import Vocabulary, WordSource, LanguageLevel from typing import List, Optional import re class VocabularyService: """Сервис для работы со словарным запасом""" @staticmethod async def add_word( session: AsyncSession, user_id: int, word_original: str, word_translation: str, source_lang: Optional[str] = None, translation_lang: Optional[str] = None, transcription: Optional[str] = None, examples: Optional[dict] = None, category: Optional[str] = None, difficulty_level: Optional[str] = None, source: WordSource = WordSource.MANUAL, notes: Optional[str] = None ) -> Vocabulary: """ Добавить слово в словарь пользователя Args: session: Сессия базы данных user_id: ID пользователя word_original: Оригинальное слово word_translation: Перевод transcription: Транскрипция examples: Примеры использования category: Категория слова difficulty_level: Уровень сложности source: Источник добавления notes: Заметки пользователя Returns: Созданный объект слова """ # Преобразование difficulty_level в enum difficulty_enum = None if difficulty_level: try: difficulty_enum = LanguageLevel[difficulty_level] except KeyError: difficulty_enum = None new_word = Vocabulary( user_id=user_id, word_original=word_original, word_translation=word_translation, source_lang=source_lang, translation_lang=translation_lang, transcription=transcription, examples=examples, category=category, difficulty_level=difficulty_enum, source=source, notes=notes ) session.add(new_word) await session.commit() await session.refresh(new_word) return new_word @staticmethod @staticmethod def _is_japanese(text: str) -> bool: if not text: return False return re.search(r"[\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF]", text) is not None @staticmethod def _filter_by_learning_lang(words: List[Vocabulary], learning_lang: Optional[str]) -> List[Vocabulary]: if not learning_lang: return words # Если в БД указан source_lang – фильтруем по нему. with_lang = [w for w in words if getattr(w, 'source_lang', None)] if with_lang: return [w for w in words if (w.source_lang or '').lower() == learning_lang.lower()] # Фолбэк-эвристика для японского, если язык не сохранён if learning_lang.lower() == 'ja': return [w for w in words if VocabularyService._is_japanese(w.word_original)] return [w for w in words if not VocabularyService._is_japanese(w.word_original)] @staticmethod async def get_user_words(session: AsyncSession, user_id: int, limit: int = 50, learning_lang: Optional[str] = None) -> List[Vocabulary]: """ Получить все слова пользователя Args: session: Сессия базы данных user_id: ID пользователя limit: Максимальное количество слов Returns: Список слов пользователя """ result = await session.execute( select(Vocabulary) .where(Vocabulary.user_id == user_id) .order_by(Vocabulary.created_at.desc()) ) words = list(result.scalars().all()) words = VocabularyService._filter_by_learning_lang(words, learning_lang) return words[:limit] @staticmethod async def get_words_count(session: AsyncSession, user_id: int, learning_lang: Optional[str] = None) -> int: """ Получить количество слов в словаре пользователя Args: session: Сессия базы данных user_id: ID пользователя Returns: Количество слов """ result = await session.execute( select(Vocabulary).where(Vocabulary.user_id == user_id) ) words = list(result.scalars().all()) words = VocabularyService._filter_by_learning_lang(words, learning_lang) return len(words) @staticmethod async def find_word(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]: """ Найти слово в словаре пользователя Args: session: Сессия базы данных user_id: ID пользователя word: Слово для поиска Returns: Объект слова или None """ result = await session.execute( select(Vocabulary) .where(Vocabulary.user_id == user_id) .where(Vocabulary.word_original.ilike(f"%{word}%")) ) return result.scalar_one_or_none() @staticmethod async def get_word_by_original(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]: """ Получить слово по точному совпадению Args: session: Сессия базы данных user_id: ID пользователя word: Слово для поиска (точное совпадение) Returns: Объект слова или None """ result = await session.execute( select(Vocabulary) .where(Vocabulary.user_id == user_id) .where(Vocabulary.word_original == word.lower()) ) return result.scalar_one_or_none()