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
|
|
|
|
|
|
|
|
|
|
|
|
from database.models import Task, Vocabulary
|
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,
|
|
|
|
|
|
count: int = 5
|
|
|
|
|
|
) -> List[Dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Генерация заданий на перевод слов
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
count: Количество заданий
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Список заданий
|
|
|
|
|
|
"""
|
|
|
|
|
|
# Получаем слова пользователя
|
|
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Vocabulary)
|
|
|
|
|
|
.where(Vocabulary.user_id == user_id)
|
|
|
|
|
|
.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,
|
|
|
|
|
|
count: int = 5
|
|
|
|
|
|
) -> List[Dict]:
|
|
|
|
|
|
"""
|
|
|
|
|
|
Генерация заданий разных типов (переводы + заполнение пропусков)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
session: Сессия базы данных
|
|
|
|
|
|
user_id: ID пользователя
|
|
|
|
|
|
count: Количество заданий
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Список заданий разных типов
|
|
|
|
|
|
"""
|
|
|
|
|
|
# Получаем слова пользователя
|
|
|
|
|
|
result = await session.execute(
|
|
|
|
|
|
select(Vocabulary)
|
|
|
|
|
|
.where(Vocabulary.user_id == user_id)
|
|
|
|
|
|
.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:
|
|
|
|
|
|
# Случайно выбираем тип задания
|
|
|
|
|
|
task_type = random.choice(['translate', 'fill_in'])
|
|
|
|
|
|
|
|
|
|
|
|
if task_type == 'translate':
|
|
|
|
|
|
# Задание на перевод
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
else:
|
|
|
|
|
|
# Задание на заполнение пропуска
|
|
|
|
|
|
# Генерируем предложение с пропуском через AI
|
|
|
|
|
|
sentence_data = await ai_service.generate_fill_in_sentence(word.word_original)
|
|
|
|
|
|
|
|
|
|
|
|
task = {
|
|
|
|
|
|
'type': 'fill_in',
|
|
|
|
|
|
'word_id': word.id,
|
|
|
|
|
|
'question': (
|
|
|
|
|
|
f"Заполни пропуск в предложении:\n\n"
|
|
|
|
|
|
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-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
|
|
|
|
|
|
}
|