feat: персональные AI модели, оптимизация задач, фильтрация словаря

- Добавлена поддержка персональных AI моделей для каждого пользователя
- Оптимизация создания заданий: батч-запрос к AI вместо N запросов
- Фильтрация слов по языку изучения (source_lang) в словаре
- Удалены неиспользуемые колонки examples и category из vocabulary
- Миграции для ai_model_id и удаления колонок

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-08 16:43:08 +03:00
parent 6138af4e63
commit 16a7df0343
13 changed files with 507 additions and 142 deletions

View File

@@ -17,8 +17,6 @@ class VocabularyService:
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
@@ -32,8 +30,6 @@ class VocabularyService:
word_original: Оригинальное слово
word_translation: Перевод
transcription: Транскрипция
examples: Примеры использования
category: Категория слова
difficulty_level: Уровень сложности
source: Источник добавления
notes: Заметки пользователя
@@ -56,8 +52,6 @@ class VocabularyService:
source_lang=source_lang,
translation_lang=translation_lang,
transcription=transcription,
examples=examples,
category=category,
difficulty_level=difficulty_enum,
source=source,
notes=notes
@@ -138,7 +132,12 @@ class VocabularyService:
return len(words)
@staticmethod
async def find_word(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]:
async def find_word(
session: AsyncSession,
user_id: int,
word: str,
source_lang: Optional[str] = None
) -> Optional[Vocabulary]:
"""
Найти слово в словаре пользователя
@@ -146,19 +145,28 @@ class VocabularyService:
session: Сессия базы данных
user_id: ID пользователя
word: Слово для поиска
source_lang: Язык изучения для фильтрации (если указан)
Returns:
Объект слова или None
"""
result = await session.execute(
query = (
select(Vocabulary)
.where(Vocabulary.user_id == user_id)
.where(Vocabulary.word_original.ilike(f"%{word}%"))
)
if source_lang:
query = query.where(Vocabulary.source_lang == source_lang.lower())
result = await session.execute(query)
return result.scalar_one_or_none()
@staticmethod
async def get_word_by_original(session: AsyncSession, user_id: int, word: str) -> Optional[Vocabulary]:
async def get_word_by_original(
session: AsyncSession,
user_id: int,
word: str,
source_lang: Optional[str] = None
) -> Optional[Vocabulary]:
"""
Получить слово по точному совпадению
@@ -166,15 +174,19 @@ class VocabularyService:
session: Сессия базы данных
user_id: ID пользователя
word: Слово для поиска (точное совпадение)
source_lang: Язык изучения для фильтрации (если указан)
Returns:
Объект слова или None
"""
result = await session.execute(
query = (
select(Vocabulary)
.where(Vocabulary.user_id == user_id)
.where(Vocabulary.word_original == word.lower())
)
if source_lang:
query = query.where(Vocabulary.source_lang == source_lang.lower())
result = await session.execute(query)
return result.scalar_one_or_none()
@staticmethod