Реализованы задания и статистика (/task, /stats)

Создано:
- services/task_service.py - сервис для работы с заданиями
- bot/handlers/tasks.py - обработчики команд /task и /stats

Реализовано:
 /task - генерация заданий на перевод слов
  - 5 случайных слов из словаря пользователя
  - Два направления: EN→RU и RU→EN
  - Показ транскрипции
  - Проверка ответов через AI
  - Детальная обратная связь
  - Сохранение результатов в БД

 /stats - статистика обучения
  - Количество слов в словаре
  - Количество изученных слов
  - Выполненные задания
  - Процент правильных ответов

Функции:
- Умные повторения (слова с меньшим количеством повторений появляются чаще)
- Обновление статистики слов после каждого задания
- Прогресс-бар выполнения заданий
- Эмодзи-реакции на результат

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 14:30:06 +03:00
parent 6e735a2eab
commit dfbd2f53fd
3 changed files with 418 additions and 1 deletions

233
bot/handlers/tasks.py Normal file
View File

@@ -0,0 +1,233 @@
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from database.db import async_session_maker
from services.user_service import UserService
from services.task_service import TaskService
from services.ai_service import ai_service
router = Router()
class TaskStates(StatesGroup):
"""Состояния для прохождения заданий"""
doing_tasks = State()
waiting_for_answer = State()
@router.message(Command("task"))
async def cmd_task(message: Message, state: FSMContext):
"""Обработчик команды /task"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
if not user:
await message.answer("Сначала запусти бота командой /start")
return
# Генерируем задания
tasks = await TaskService.generate_translation_tasks(session, user.id, count=5)
if not tasks:
await message.answer(
"📚 У тебя пока нет слов для практики!\n\n"
"Добавь несколько слов командой /add, а затем возвращайся."
)
return
# Сохраняем задания в состоянии
await state.update_data(
tasks=tasks,
current_task_index=0,
correct_count=0,
user_id=user.id
)
await state.set_state(TaskStates.doing_tasks)
# Показываем первое задание
await show_current_task(message, state)
async def show_current_task(message: Message, state: FSMContext):
"""Показать текущее задание"""
data = await state.get_data()
tasks = data.get('tasks', [])
current_index = data.get('current_task_index', 0)
if current_index >= len(tasks):
# Все задания выполнены
await finish_tasks(message, state)
return
task = tasks[current_index]
task_text = (
f"📝 <b>Задание {current_index + 1} из {len(tasks)}</b>\n\n"
f"{task['question']}\n"
)
if task.get('transcription'):
task_text += f"🔊 [{task['transcription']}]\n"
task_text += f"\n💡 Напиши свой ответ:"
await state.set_state(TaskStates.waiting_for_answer)
await message.answer(task_text)
@router.message(TaskStates.waiting_for_answer)
async def process_answer(message: Message, state: FSMContext):
"""Обработка ответа пользователя"""
user_answer = message.text.strip()
data = await state.get_data()
tasks = data.get('tasks', [])
current_index = data.get('current_task_index', 0)
correct_count = data.get('correct_count', 0)
user_id = data.get('user_id')
task = tasks[current_index]
# Показываем индикатор проверки
checking_msg = await message.answer("⏳ Проверяю ответ...")
# Проверяем ответ через AI
check_result = await ai_service.check_answer(
question=task['question'],
correct_answer=task['correct_answer'],
user_answer=user_answer
)
await checking_msg.delete()
is_correct = check_result.get('is_correct', False)
feedback = check_result.get('feedback', '')
# Формируем ответ
if is_correct:
result_text = f"✅ <b>Правильно!</b>\n\n"
correct_count += 1
else:
result_text = f"❌ <b>Неправильно</b>\n\n"
result_text += f"Твой ответ: <i>{user_answer}</i>\n"
result_text += f"Правильный ответ: <b>{task['correct_answer']}</b>\n\n"
if feedback:
result_text += f"💬 {feedback}\n\n"
# Сохраняем результат в БД
async with async_session_maker() as session:
await TaskService.save_task_result(
session=session,
user_id=user_id,
task_type=task['type'],
content={
'question': task['question'],
'word': task['word']
},
user_answer=user_answer,
correct_answer=task['correct_answer'],
is_correct=is_correct,
ai_feedback=feedback
)
# Обновляем статистику слова
if 'word_id' in task:
await TaskService.update_word_statistics(
session=session,
word_id=task['word_id'],
is_correct=is_correct
)
# Обновляем счетчик
await state.update_data(
current_task_index=current_index + 1,
correct_count=correct_count
)
# Показываем результат и кнопку "Далее"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="➡️ Следующее задание", callback_data="next_task")]
])
await message.answer(result_text, reply_markup=keyboard)
@router.callback_query(F.data == "next_task", TaskStates.doing_tasks)
async def next_task(callback: CallbackQuery, state: FSMContext):
"""Переход к следующему заданию"""
await callback.message.delete()
await show_current_task(callback.message, state)
await callback.answer()
async def finish_tasks(message: Message, state: FSMContext):
"""Завершение всех заданий"""
data = await state.get_data()
tasks = data.get('tasks', [])
correct_count = data.get('correct_count', 0)
total_count = len(tasks)
accuracy = int((correct_count / total_count) * 100) if total_count > 0 else 0
# Определяем эмодзи на основе результата
if accuracy >= 90:
emoji = "🏆"
comment = "Отличный результат!"
elif accuracy >= 70:
emoji = "👍"
comment = "Хорошая работа!"
elif accuracy >= 50:
emoji = "📚"
comment = "Неплохо, продолжай практиковаться!"
else:
emoji = "💪"
comment = "Повтори эти слова еще раз!"
result_text = (
f"{emoji} <b>Задание завершено!</b>\n\n"
f"Правильных ответов: <b>{correct_count}</b> из {total_count}\n"
f"Точность: <b>{accuracy}%</b>\n\n"
f"{comment}\n\n"
f"Используй /task для нового задания\n"
f"Используй /stats для просмотра статистики"
)
await state.clear()
await message.answer(result_text)
@router.message(Command("stats"))
async def cmd_stats(message: Message):
"""Обработчик команды /stats"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
if not user:
await message.answer("Сначала запусти бота командой /start")
return
# Получаем статистику
stats = await TaskService.get_user_stats(session, user.id)
stats_text = (
f"📊 <b>Твоя статистика</b>\n\n"
f"📚 Слов в словаре: <b>{stats['total_words']}</b>\n"
f"📖 Слов изучено: <b>{stats['reviewed_words']}</b>\n"
f"✍️ Заданий выполнено: <b>{stats['total_tasks']}</b>\n"
f"✅ Правильных ответов: <b>{stats['correct_tasks']}</b>\n"
f"🎯 Точность: <b>{stats['accuracy']}%</b>\n\n"
)
if stats['total_words'] == 0:
stats_text += "Добавь слова командой /add чтобы начать обучение!"
elif stats['total_tasks'] == 0:
stats_text += "Выполни первое задание командой /task!"
else:
stats_text += "Продолжай практиковаться! 💪"
await message.answer(stats_text)

View File

@@ -6,7 +6,7 @@ from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode from aiogram.enums import ParseMode
from config.settings import settings from config.settings import settings
from bot.handlers import start, vocabulary from bot.handlers import start, vocabulary, tasks
from database.db import init_db from database.db import init_db
@@ -28,6 +28,7 @@ async def main():
# Регистрация роутеров # Регистрация роутеров
dp.include_router(start.router) dp.include_router(start.router)
dp.include_router(vocabulary.router) dp.include_router(vocabulary.router)
dp.include_router(tasks.router)
# Инициализация базы данных # Инициализация базы данных
await init_db() await init_db()

183
services/task_service.py Normal file
View File

@@ -0,0 +1,183 @@
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
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
@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
}