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 database.models import LanguageLevel from services.user_service import UserService from services.ai_service import ai_service router = Router() class LevelTestStates(StatesGroup): """Состояния для прохождения теста уровня""" taking_test = State() @router.message(Command("level_test")) async def cmd_level_test(message: Message, state: FSMContext): """Обработчик команды /level_test""" await start_level_test(message, state) async def start_level_test(message: Message, state: FSMContext): """Начать тест определения уровня""" # Показываем описание теста await message.answer( "📊 Тест определения уровня\n\n" "Этот короткий тест поможет определить твой уровень английского.\n\n" "📋 Тест включает 7 вопросов:\n" "• Грамматика\n" "• Лексика\n" "• Понимание\n\n" "⏱ Займёт около 2-3 минут\n\n" "Готов начать?" ) keyboard = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="✅ Начать тест", callback_data="start_test")], [InlineKeyboardButton(text="❌ Отмена", callback_data="cancel_test")] ]) await message.answer("Нажми кнопку когда будешь готов:", reply_markup=keyboard) @router.callback_query(F.data == "cancel_test") async def cancel_test(callback: CallbackQuery, state: FSMContext): """Отменить тест""" await state.clear() await callback.message.delete() await callback.message.answer("❌ Тест отменён") await callback.answer() @router.callback_query(F.data == "start_test") async def begin_test(callback: CallbackQuery, state: FSMContext): """Начать прохождение теста""" await callback.message.delete() # Показываем индикатор загрузки loading_msg = await callback.message.answer("🔄 Генерирую вопросы...") # Генерируем тест через AI questions = await ai_service.generate_level_test() await loading_msg.delete() if not questions: await callback.message.answer( "❌ Не удалось сгенерировать тест. Попробуй позже или используй /settings для ручной установки уровня." ) await state.clear() await callback.answer() return # Сохраняем данные в состоянии await state.update_data( questions=questions, current_question=0, correct_answers=0, answers=[] # Для отслеживания ответов по уровням ) await state.set_state(LevelTestStates.taking_test) # Показываем первый вопрос await show_question(callback.message, state) await callback.answer() async def show_question(message: Message, state: FSMContext): """Показать текущий вопрос""" data = await state.get_data() questions = data.get('questions', []) current_idx = data.get('current_question', 0) if current_idx >= len(questions): # Тест завершён await finish_test(message, state) return question = questions[current_idx] # Формируем текст вопроса text = ( f"❓ Вопрос {current_idx + 1} из {len(questions)}\n\n" f"{question['question']}\n" f"{question.get('question_ru', '')}\n\n" ) # Создаем кнопки с вариантами ответа keyboard = [] letters = ['A', 'B', 'C', 'D'] for idx, option in enumerate(question['options']): keyboard.append([ InlineKeyboardButton( text=f"{letters[idx]}) {option}", callback_data=f"answer_{idx}" ) ]) reply_markup = InlineKeyboardMarkup(inline_keyboard=keyboard) await message.answer(text, reply_markup=reply_markup) @router.callback_query(F.data.startswith("answer_"), LevelTestStates.taking_test) async def process_answer(callback: CallbackQuery, state: FSMContext): """Обработать ответ на вопрос""" answer_idx = int(callback.data.split("_")[1]) data = await state.get_data() questions = data.get('questions', []) current_idx = data.get('current_question', 0) correct_answers = data.get('correct_answers', 0) answers = data.get('answers', []) question = questions[current_idx] is_correct = (answer_idx == question['correct']) # Сохраняем результат if is_correct: correct_answers += 1 # Сохраняем ответ с уровнем вопроса answers.append({ 'level': question['level'], 'correct': is_correct }) # Показываем результат if is_correct: result_text = "✅ Правильно!" else: correct_option = question['options'][question['correct']] result_text = f"❌ Неправильно\nПравильный ответ: {correct_option}" await callback.message.edit_text( f"❓ Вопрос {current_idx + 1} из {len(questions)}\n\n" f"{result_text}" ) # Переходим к следующему вопросу await state.update_data( current_question=current_idx + 1, correct_answers=correct_answers, answers=answers ) # Небольшая пауза перед следующим вопросом import asyncio await asyncio.sleep(1.5) await show_question(callback.message, state) await callback.answer() async def finish_test(message: Message, state: FSMContext): """Завершить тест и определить уровень""" data = await state.get_data() questions = data.get('questions', []) correct_answers = data.get('correct_answers', 0) answers = data.get('answers', []) total = len(questions) accuracy = int((correct_answers / total) * 100) if total > 0 else 0 # Определяем уровень на основе правильных ответов по уровням level = determine_level(answers) # Сохраняем уровень в базе данных async with async_session_maker() as session: user = await UserService.get_user_by_telegram_id(session, message.chat.id) if user: user.level = level await session.commit() # Описания уровней level_descriptions = { "A1": "Начальный - понимаешь основные фразы и можешь представиться", "A2": "Элементарный - можешь общаться на простые темы", "B1": "Средний - можешь поддержать беседу на знакомые темы", "B2": "Выше среднего - свободно общаешься в большинстве ситуаций", "C1": "Продвинутый - используешь язык гибко и эффективно", "C2": "Профессиональный - владеешь языком на уровне носителя" } await state.clear() result_text = ( f"🎉 Тест завершён!\n\n" f"📊 Результаты:\n" f"Правильных ответов: {correct_answers} из {total}\n" f"Точность: {accuracy}%\n\n" f"🎯 Твой уровень: {level.value}\n" f"{level_descriptions.get(level.value, '')}\n\n" f"Теперь задания и материалы будут подбираться под твой уровень!\n" f"Ты можешь изменить уровень в любое время через /settings" ) await message.answer(result_text) def determine_level(answers: list) -> LanguageLevel: """ Определить уровень на основе ответов Args: answers: Список ответов с уровнями Returns: Определённый уровень """ # Подсчитываем правильные ответы по уровням level_stats = { 'A1': {'correct': 0, 'total': 0}, 'A2': {'correct': 0, 'total': 0}, 'B1': {'correct': 0, 'total': 0}, 'B2': {'correct': 0, 'total': 0}, 'C1': {'correct': 0, 'total': 0}, 'C2': {'correct': 0, 'total': 0} } for answer in answers: level = answer['level'] if level in level_stats: level_stats[level]['total'] += 1 if answer['correct']: level_stats[level]['correct'] += 1 # Определяем уровень: ищем последний уровень, где правильно >= 50% levels_order = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'] determined_level = 'A1' for level in levels_order: if level_stats[level]['total'] > 0: accuracy = level_stats[level]['correct'] / level_stats[level]['total'] if accuracy >= 0.5: # 50% и выше determined_level = level else: # Если не прошёл этот уровень, останавливаемся break return LanguageLevel[determined_level]