2025-12-04 14:30:06 +03:00
|
|
|
|
import random
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from typing import List, Dict, Optional
|
|
|
|
|
|
from sqlalchemy import select
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
2025-12-06 21:29:41 +03:00
|
|
|
|
from database.models import Task, Vocabulary, WordTranslation
|
2025-12-04 14:46:30 +03:00
|
|
|
|
from services.ai_service import ai_service
|
2025-12-04 14:30:06 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TaskService:
|
|
|
|
|
|
"""Сервис для работы с заданиями"""
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def generate_translation_tasks(
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
user_id: int,
|
2025-12-08 15:16:24 +03:00
|
|
|
|
count: int = 5,
|
|
|
|
|
|
learning_lang: str = 'en'
|
2025-12-04 14:30:06 +03:00
|
|
|
|
) -> List[Dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Генерация заданий на перевод слов
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
count: Количество заданий
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Список заданий
|
|
|
|
|
|
"""
|
2025-12-08 15:16:24 +03:00
|
|
|
|
# Получаем слова пользователя на изучаемом языке
|
2025-12-04 14:30:06 +03:00
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Vocabulary)
|
|
|
|
|
|
.where(Vocabulary.user_id == user_id)
|
2025-12-08 15:16:24 +03:00
|
|
|
|
.where(Vocabulary.source_lang == learning_lang)
|
2025-12-04 14:30:06 +03:00
|
|
|
|
.order_by(Vocabulary.last_reviewed.asc().nullsfirst())
|
|
|
|
|
|
.limit(count * 2) # Берем больше, чтобы было из чего выбрать
|
|
|
|
|
|
)
|
|
|
|
|
|
words = list(result.scalars().all())
|
|
|
|
|
|
|
|
|
|
|
|
if not words:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
# Выбираем случайные слова
|
|
|
|
|
|
selected_words = random.sample(words, min(count, len(words)))
|
|
|
|
|
|
|
|
|
|
|
|
tasks = []
|
|
|
|
|
|
for word in selected_words:
|
|
|
|
|
|
# Случайно выбираем направление перевода
|
|
|
|
|
|
direction = random.choice(['en_to_ru', 'ru_to_en'])
|
|
|
|
|
|
|
|
|
|
|
|
if direction == 'en_to_ru':
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'translate_to_ru',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': f"Переведи слово: <b>{word.word_original}</b>",
|
|
|
|
|
|
'word': word.word_original,
|
|
|
|
|
|
'correct_answer': word.word_translation,
|
|
|
|
|
|
'transcription': word.transcription
|
|
|
|
|
|
}
|
|
|
|
|
|
else:
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'translate_to_en',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': f"Переведи слово: <b>{word.word_translation}</b>",
|
|
|
|
|
|
'word': word.word_translation,
|
|
|
|
|
|
'correct_answer': word.word_original,
|
|
|
|
|
|
'transcription': word.transcription
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tasks.append(task)
|
|
|
|
|
|
|
|
|
|
|
|
return tasks
|
|
|
|
|
|
|
2025-12-04 14:46:30 +03:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def generate_mixed_tasks(
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
user_id: int,
|
2025-12-04 19:40:01 +03:00
|
|
|
|
count: int = 5,
|
|
|
|
|
|
learning_lang: str = 'en',
|
|
|
|
|
|
translation_lang: str = 'ru'
|
2025-12-04 14:46:30 +03:00
|
|
|
|
) -> List[Dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Генерация заданий разных типов (переводы + заполнение пропусков)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
count: Количество заданий
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Список заданий разных типов
|
|
|
|
|
|
"""
|
2025-12-08 15:16:24 +03:00
|
|
|
|
# Получаем слова пользователя на изучаемом языке
|
2025-12-04 14:46:30 +03:00
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Vocabulary)
|
|
|
|
|
|
.where(Vocabulary.user_id == user_id)
|
2025-12-08 15:16:24 +03:00
|
|
|
|
.where(Vocabulary.source_lang == learning_lang)
|
2025-12-04 14:46:30 +03:00
|
|
|
|
.order_by(Vocabulary.last_reviewed.asc().nullsfirst())
|
|
|
|
|
|
.limit(count * 2)
|
|
|
|
|
|
)
|
|
|
|
|
|
words = list(result.scalars().all())
|
|
|
|
|
|
|
|
|
|
|
|
if not words:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
# Выбираем случайные слова
|
|
|
|
|
|
selected_words = random.sample(words, min(count, len(words)))
|
|
|
|
|
|
|
|
|
|
|
|
tasks = []
|
|
|
|
|
|
for word in selected_words:
|
2025-12-06 21:29:41 +03:00
|
|
|
|
# Получаем переводы из таблицы WordTranslation
|
|
|
|
|
|
translations_result = await session.execute(
|
|
|
|
|
|
select(WordTranslation)
|
|
|
|
|
|
.where(WordTranslation.vocabulary_id == word.id)
|
|
|
|
|
|
.order_by(WordTranslation.is_primary.desc())
|
|
|
|
|
|
)
|
|
|
|
|
|
translations = list(translations_result.scalars().all())
|
|
|
|
|
|
|
2025-12-04 14:46:30 +03:00
|
|
|
|
# Случайно выбираем тип задания
|
2025-12-06 21:29:41 +03:00
|
|
|
|
# Если есть переводы с контекстом, добавляем тип 'context_translate'
|
|
|
|
|
|
task_types = ['translate', 'fill_in']
|
|
|
|
|
|
if translations and any(tr.context for tr in translations):
|
|
|
|
|
|
task_types.append('context_translate')
|
|
|
|
|
|
|
|
|
|
|
|
task_type = random.choice(task_types)
|
|
|
|
|
|
|
|
|
|
|
|
if task_type == 'context_translate' and translations:
|
|
|
|
|
|
# Задание на перевод по контексту
|
|
|
|
|
|
# Выбираем случайный перевод с контекстом
|
|
|
|
|
|
translations_with_context = [tr for tr in translations if tr.context]
|
|
|
|
|
|
if translations_with_context:
|
|
|
|
|
|
selected_tr = random.choice(translations_with_context)
|
|
|
|
|
|
|
|
|
|
|
|
# Локализация фразы
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
prompt = "Translate the highlighted word in context:"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
prompt = "文脈に合った翻訳を入力してください:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
prompt = "Переведи выделенное слово в контексте:"
|
|
|
|
|
|
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'context_translate',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'translation_id': selected_tr.id,
|
|
|
|
|
|
'question': (
|
|
|
|
|
|
f"{prompt}\n\n"
|
|
|
|
|
|
f"<i>«{selected_tr.context}»</i>\n\n"
|
|
|
|
|
|
f"<b>{word.word_original}</b> = ?"
|
|
|
|
|
|
),
|
|
|
|
|
|
'word': word.word_original,
|
|
|
|
|
|
'correct_answer': selected_tr.translation,
|
|
|
|
|
|
'transcription': word.transcription,
|
|
|
|
|
|
'context': selected_tr.context,
|
|
|
|
|
|
'context_translation': selected_tr.context_translation
|
|
|
|
|
|
}
|
|
|
|
|
|
tasks.append(task)
|
|
|
|
|
|
continue
|
2025-12-04 14:46:30 +03:00
|
|
|
|
|
|
|
|
|
|
if task_type == 'translate':
|
2025-12-04 19:40:01 +03:00
|
|
|
|
# Задание на перевод между языком обучения и языком перевода
|
|
|
|
|
|
direction = random.choice(['learn_to_tr', 'tr_to_learn'])
|
|
|
|
|
|
|
|
|
|
|
|
# Локализация фразы "Переведи слово"
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
prompt = "Translate the word:"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
prompt = "単語を訳してください:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
prompt = "Переведи слово:"
|
2025-12-04 14:46:30 +03:00
|
|
|
|
|
2025-12-06 21:29:41 +03:00
|
|
|
|
# Определяем правильный ответ - берём из таблицы переводов если есть
|
|
|
|
|
|
correct_translation = word.word_translation
|
|
|
|
|
|
if translations:
|
|
|
|
|
|
# Берём основной перевод или первый
|
|
|
|
|
|
primary = next((tr for tr in translations if tr.is_primary), translations[0] if translations else None)
|
|
|
|
|
|
if primary:
|
|
|
|
|
|
correct_translation = primary.translation
|
|
|
|
|
|
|
2025-12-04 19:40:01 +03:00
|
|
|
|
if direction == 'learn_to_tr':
|
2025-12-04 14:46:30 +03:00
|
|
|
|
task = {
|
2025-12-04 19:40:01 +03:00
|
|
|
|
'type': f'translate_to_{translation_lang}',
|
2025-12-04 14:46:30 +03:00
|
|
|
|
'word_id': word.id,
|
2025-12-04 19:40:01 +03:00
|
|
|
|
'question': f"{prompt} <b>{word.word_original}</b>",
|
2025-12-04 14:46:30 +03:00
|
|
|
|
'word': word.word_original,
|
2025-12-06 21:29:41 +03:00
|
|
|
|
'correct_answer': correct_translation,
|
|
|
|
|
|
'transcription': word.transcription,
|
|
|
|
|
|
# Все допустимые ответы для проверки
|
|
|
|
|
|
'all_translations': [tr.translation for tr in translations] if translations else [correct_translation]
|
2025-12-04 14:46:30 +03:00
|
|
|
|
}
|
|
|
|
|
|
else:
|
|
|
|
|
|
task = {
|
2025-12-04 19:40:01 +03:00
|
|
|
|
'type': f'translate_to_{learning_lang}',
|
2025-12-04 14:46:30 +03:00
|
|
|
|
'word_id': word.id,
|
2025-12-06 21:29:41 +03:00
|
|
|
|
'question': f"{prompt} <b>{correct_translation}</b>",
|
|
|
|
|
|
'word': correct_translation,
|
2025-12-04 14:46:30 +03:00
|
|
|
|
'correct_answer': word.word_original,
|
|
|
|
|
|
'transcription': word.transcription
|
|
|
|
|
|
}
|
|
|
|
|
|
else:
|
|
|
|
|
|
# Задание на заполнение пропуска
|
|
|
|
|
|
# Генерируем предложение с пропуском через AI
|
2025-12-04 19:40:01 +03:00
|
|
|
|
sentence_data = await ai_service.generate_fill_in_sentence(
|
|
|
|
|
|
word.word_original,
|
|
|
|
|
|
learning_lang=learning_lang,
|
|
|
|
|
|
translation_lang=translation_lang
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Локализация заголовка
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
fill_title = "Fill in the blank in the sentence:"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
fill_title = "文の空欄を埋めてください:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
fill_title = "Заполни пропуск в предложении:"
|
2025-12-04 14:46:30 +03:00
|
|
|
|
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'fill_in',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': (
|
2025-12-04 19:40:01 +03:00
|
|
|
|
f"{fill_title}\n\n"
|
2025-12-04 14:46:30 +03:00
|
|
|
|
f"<b>{sentence_data['sentence']}</b>\n\n"
|
|
|
|
|
|
f"<i>{sentence_data.get('translation', '')}</i>"
|
|
|
|
|
|
),
|
|
|
|
|
|
'word': word.word_original,
|
|
|
|
|
|
'correct_answer': sentence_data['answer'],
|
|
|
|
|
|
'sentence': sentence_data['sentence']
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tasks.append(task)
|
|
|
|
|
|
|
|
|
|
|
|
return tasks
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def generate_tasks_by_type(
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
user_id: int,
|
|
|
|
|
|
count: int = 5,
|
|
|
|
|
|
task_type: str = 'mix',
|
|
|
|
|
|
learning_lang: str = 'en',
|
|
|
|
|
|
translation_lang: str = 'ru'
|
|
|
|
|
|
) -> List[Dict]:
|
|
|
|
|
|
"""
|
2025-12-08 16:43:08 +03:00
|
|
|
|
Генерация заданий определённого типа (оптимизировано - 1 запрос к AI)
|
2025-12-08 15:16:24 +03:00
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
count: Количество заданий
|
|
|
|
|
|
task_type: Тип заданий (mix, word_translate, fill_blank, sentence_translate)
|
|
|
|
|
|
learning_lang: Язык обучения
|
|
|
|
|
|
translation_lang: Язык перевода
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Список заданий
|
|
|
|
|
|
"""
|
|
|
|
|
|
# Получаем слова пользователя на изучаемом языке
|
|
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Vocabulary)
|
|
|
|
|
|
.where(Vocabulary.user_id == user_id)
|
|
|
|
|
|
.where(Vocabulary.source_lang == learning_lang)
|
|
|
|
|
|
.order_by(Vocabulary.last_reviewed.asc().nullsfirst())
|
|
|
|
|
|
.limit(count * 2)
|
|
|
|
|
|
)
|
|
|
|
|
|
words = list(result.scalars().all())
|
|
|
|
|
|
|
|
|
|
|
|
if not words:
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
# Выбираем случайные слова
|
|
|
|
|
|
selected_words = random.sample(words, min(count, len(words)))
|
|
|
|
|
|
|
2025-12-08 16:43:08 +03:00
|
|
|
|
# 1. Подготовка: определяем типы и собираем данные для всех слов
|
|
|
|
|
|
word_data_list = []
|
2025-12-08 15:16:24 +03:00
|
|
|
|
for word in selected_words:
|
2025-12-08 16:43:08 +03:00
|
|
|
|
# Получаем переводы
|
2025-12-08 15:16:24 +03:00
|
|
|
|
translations_result = await session.execute(
|
|
|
|
|
|
select(WordTranslation)
|
|
|
|
|
|
.where(WordTranslation.vocabulary_id == word.id)
|
|
|
|
|
|
.order_by(WordTranslation.is_primary.desc())
|
|
|
|
|
|
)
|
|
|
|
|
|
translations = list(translations_result.scalars().all())
|
|
|
|
|
|
|
|
|
|
|
|
# Определяем тип задания
|
|
|
|
|
|
if task_type == 'mix':
|
|
|
|
|
|
chosen_type = random.choice(['word_translate', 'fill_blank', 'sentence_translate'])
|
|
|
|
|
|
else:
|
|
|
|
|
|
chosen_type = task_type
|
|
|
|
|
|
|
2025-12-08 16:43:08 +03:00
|
|
|
|
# Определяем перевод
|
2025-12-08 15:16:24 +03:00
|
|
|
|
correct_translation = word.word_translation
|
|
|
|
|
|
if translations:
|
|
|
|
|
|
primary = next((tr for tr in translations if tr.is_primary), translations[0] if translations else None)
|
|
|
|
|
|
if primary:
|
|
|
|
|
|
correct_translation = primary.translation
|
|
|
|
|
|
|
2025-12-08 16:43:08 +03:00
|
|
|
|
word_data_list.append({
|
|
|
|
|
|
'word': word,
|
|
|
|
|
|
'translations': translations,
|
|
|
|
|
|
'correct_translation': correct_translation,
|
|
|
|
|
|
'chosen_type': chosen_type
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# 2. Собираем задания, требующие AI
|
|
|
|
|
|
ai_tasks = []
|
|
|
|
|
|
ai_task_indices = []
|
|
|
|
|
|
|
|
|
|
|
|
for i, wd in enumerate(word_data_list):
|
|
|
|
|
|
if wd['chosen_type'] in ('fill_blank', 'sentence_translate'):
|
|
|
|
|
|
ai_tasks.append({
|
|
|
|
|
|
'word': wd['word'].word_original,
|
|
|
|
|
|
'task_type': wd['chosen_type']
|
|
|
|
|
|
})
|
|
|
|
|
|
ai_task_indices.append(i)
|
|
|
|
|
|
|
|
|
|
|
|
# 3. Один запрос к AI
|
|
|
|
|
|
ai_results = []
|
|
|
|
|
|
if ai_tasks:
|
|
|
|
|
|
ai_results = await ai_service.generate_task_sentences_batch(
|
|
|
|
|
|
ai_tasks,
|
|
|
|
|
|
learning_lang=learning_lang,
|
|
|
|
|
|
translation_lang=translation_lang
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Маппинг результатов
|
|
|
|
|
|
ai_results_map = {}
|
|
|
|
|
|
for idx, result in zip(ai_task_indices, ai_results):
|
|
|
|
|
|
ai_results_map[idx] = result
|
|
|
|
|
|
|
|
|
|
|
|
# 4. Собираем финальные задания
|
|
|
|
|
|
tasks = []
|
|
|
|
|
|
for i, wd in enumerate(word_data_list):
|
|
|
|
|
|
word = wd['word']
|
|
|
|
|
|
translations = wd['translations']
|
|
|
|
|
|
correct_translation = wd['correct_translation']
|
|
|
|
|
|
chosen_type = wd['chosen_type']
|
|
|
|
|
|
|
2025-12-08 15:16:24 +03:00
|
|
|
|
if chosen_type == 'word_translate':
|
|
|
|
|
|
direction = random.choice(['learn_to_tr', 'tr_to_learn'])
|
|
|
|
|
|
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
prompt = "Translate the word:"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
prompt = "単語を訳してください:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
prompt = "Переведи слово:"
|
|
|
|
|
|
|
|
|
|
|
|
if direction == 'learn_to_tr':
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': f'translate_to_{translation_lang}',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': f"{prompt} <b>{word.word_original}</b>",
|
|
|
|
|
|
'word': word.word_original,
|
|
|
|
|
|
'correct_answer': correct_translation,
|
|
|
|
|
|
'transcription': word.transcription,
|
|
|
|
|
|
'all_translations': [tr.translation for tr in translations] if translations else [correct_translation]
|
|
|
|
|
|
}
|
|
|
|
|
|
else:
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': f'translate_to_{learning_lang}',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': f"{prompt} <b>{correct_translation}</b>",
|
|
|
|
|
|
'word': correct_translation,
|
|
|
|
|
|
'correct_answer': word.word_original,
|
|
|
|
|
|
'transcription': word.transcription
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
elif chosen_type == 'fill_blank':
|
2025-12-08 16:43:08 +03:00
|
|
|
|
sentence_data = ai_results_map.get(i, {})
|
2025-12-08 15:16:24 +03:00
|
|
|
|
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
fill_title = "Fill in the blank:"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
fill_title = "空欄を埋めてください:"
|
|
|
|
|
|
else:
|
|
|
|
|
|
fill_title = "Заполни пропуск:"
|
|
|
|
|
|
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'fill_in',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': (
|
|
|
|
|
|
f"{fill_title}\n\n"
|
2025-12-08 16:43:08 +03:00
|
|
|
|
f"<b>{sentence_data.get('sentence', '___')}</b>\n\n"
|
2025-12-08 15:16:24 +03:00
|
|
|
|
f"<i>{sentence_data.get('translation', '')}</i>"
|
|
|
|
|
|
),
|
|
|
|
|
|
'word': word.word_original,
|
2025-12-08 16:43:08 +03:00
|
|
|
|
'correct_answer': sentence_data.get('answer', word.word_original),
|
|
|
|
|
|
'sentence': sentence_data.get('sentence', '___')
|
2025-12-08 15:16:24 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
elif chosen_type == 'sentence_translate':
|
2025-12-08 16:43:08 +03:00
|
|
|
|
sentence_data = ai_results_map.get(i, {})
|
2025-12-08 15:16:24 +03:00
|
|
|
|
|
|
|
|
|
|
if translation_lang == 'en':
|
|
|
|
|
|
sentence_title = "Translate the sentence:"
|
|
|
|
|
|
word_hint = "Word"
|
|
|
|
|
|
elif translation_lang == 'ja':
|
|
|
|
|
|
sentence_title = "文を翻訳してください:"
|
|
|
|
|
|
word_hint = "単語"
|
|
|
|
|
|
else:
|
|
|
|
|
|
sentence_title = "Переведи предложение:"
|
|
|
|
|
|
word_hint = "Слово"
|
|
|
|
|
|
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'sentence_translate',
|
|
|
|
|
|
'word_id': word.id,
|
2025-12-08 16:43:08 +03:00
|
|
|
|
'question': f"{sentence_title}\n\n<b>{sentence_data.get('sentence', word.word_original)}</b>\n\n📝 {word_hint}: <code>{word.word_original}</code> — {correct_translation}",
|
2025-12-08 15:16:24 +03:00
|
|
|
|
'word': word.word_original,
|
2025-12-08 16:43:08 +03:00
|
|
|
|
'correct_answer': sentence_data.get('translation', correct_translation),
|
|
|
|
|
|
'sentence': sentence_data.get('sentence', word.word_original)
|
2025-12-08 15:16:24 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tasks.append(task)
|
|
|
|
|
|
|
|
|
|
|
|
return tasks
|
|
|
|
|
|
|
2025-12-04 14:30:06 +03:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def save_task_result(
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
user_id: int,
|
|
|
|
|
|
task_type: str,
|
|
|
|
|
|
content: Dict,
|
|
|
|
|
|
user_answer: str,
|
|
|
|
|
|
correct_answer: str,
|
|
|
|
|
|
is_correct: bool,
|
|
|
|
|
|
ai_feedback: Optional[str] = None
|
|
|
|
|
|
) -> Task:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Сохранение результата выполнения задания
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
task_type: Тип задания
|
|
|
|
|
|
content: Содержимое задания
|
|
|
|
|
|
user_answer: Ответ пользователя
|
|
|
|
|
|
correct_answer: Правильный ответ
|
|
|
|
|
|
is_correct: Правильность ответа
|
|
|
|
|
|
ai_feedback: Обратная связь от AI
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Сохраненное задание
|
|
|
|
|
|
"""
|
|
|
|
|
|
task = Task(
|
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
|
task_type=task_type,
|
|
|
|
|
|
content=content,
|
|
|
|
|
|
user_answer=user_answer,
|
|
|
|
|
|
correct_answer=correct_answer,
|
|
|
|
|
|
is_correct=is_correct,
|
|
|
|
|
|
ai_feedback=ai_feedback,
|
|
|
|
|
|
completed_at=datetime.utcnow()
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
session.add(task)
|
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
await session.refresh(task)
|
|
|
|
|
|
|
|
|
|
|
|
return task
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def update_word_statistics(
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
word_id: int,
|
|
|
|
|
|
is_correct: bool
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Обновление статистики слова
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
word_id: ID слова
|
|
|
|
|
|
is_correct: Правильность ответа
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Vocabulary).where(Vocabulary.id == word_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
word = result.scalar_one_or_none()
|
|
|
|
|
|
|
|
|
|
|
|
if word:
|
|
|
|
|
|
word.times_reviewed += 1
|
|
|
|
|
|
if is_correct:
|
|
|
|
|
|
word.correct_answers += 1
|
|
|
|
|
|
word.last_reviewed = datetime.utcnow()
|
|
|
|
|
|
|
|
|
|
|
|
await session.commit()
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def get_user_stats(session: AsyncSession, user_id: int) -> Dict:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Получение статистики пользователя
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Статистика пользователя
|
|
|
|
|
|
"""
|
|
|
|
|
|
# Количество слов
|
|
|
|
|
|
words_result = await session.execute(
|
|
|
|
|
|
select(Vocabulary).where(Vocabulary.user_id == user_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
words = list(words_result.scalars().all())
|
|
|
|
|
|
total_words = len(words)
|
|
|
|
|
|
|
|
|
|
|
|
# Количество выполненных заданий
|
|
|
|
|
|
tasks_result = await session.execute(
|
|
|
|
|
|
select(Task).where(Task.user_id == user_id)
|
|
|
|
|
|
)
|
|
|
|
|
|
tasks = list(tasks_result.scalars().all())
|
|
|
|
|
|
total_tasks = len(tasks)
|
|
|
|
|
|
|
|
|
|
|
|
# Правильные ответы
|
|
|
|
|
|
correct_tasks = len([t for t in tasks if t.is_correct])
|
|
|
|
|
|
accuracy = int((correct_tasks / total_tasks * 100)) if total_tasks > 0 else 0
|
|
|
|
|
|
|
|
|
|
|
|
# Слова с повторениями
|
|
|
|
|
|
reviewed_words = len([w for w in words if w.times_reviewed > 0])
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
'total_words': total_words,
|
|
|
|
|
|
'reviewed_words': reviewed_words,
|
|
|
|
|
|
'total_tasks': total_tasks,
|
|
|
|
|
|
'correct_tasks': correct_tasks,
|
|
|
|
|
|
'accuracy': accuracy
|
|
|
|
|
|
}
|
2025-12-06 21:29:41 +03:00
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
async def get_correctly_answered_words(
|
|
|
|
|
|
session: AsyncSession,
|
|
|
|
|
|
user_id: int
|
|
|
|
|
|
) -> List[str]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Получить список слов, на которые пользователь правильно ответил в заданиях
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Список слов (строки) с правильными ответами
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Task)
|
|
|
|
|
|
.where(Task.user_id == user_id)
|
|
|
|
|
|
.where(Task.is_correct == True)
|
|
|
|
|
|
)
|
|
|
|
|
|
tasks = list(result.scalars().all())
|
|
|
|
|
|
|
|
|
|
|
|
words = []
|
|
|
|
|
|
for task in tasks:
|
|
|
|
|
|
if task.content and isinstance(task.content, dict):
|
|
|
|
|
|
word = task.content.get('word')
|
|
|
|
|
|
if word:
|
|
|
|
|
|
words.append(word.lower())
|
|
|
|
|
|
|
|
|
|
|
|
return list(set(words))
|