/
servgri
/
interview
Обзор
Документация
Войти
/
servgri
/
interview
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
bot_api/view_task.py
260 строк
12 KB
Seal
full project
27 ноя 2025, 14:24
27 ноя 2025, 14:24
d1507ba
Код
Авторство
О чём код?
import logging from aiogram import Bot from asgiref.sync import async_to_sync from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from rest_framework import status from django.utils import timezone from Interview_project_v2 import settings from .docker_runner import run_in_sandbox from .permissions import TelegramUserPermission from tasks.models import Task, UserProgress from .qwen_api.task_gen_analyzer import SKILL_LIST from .telegram_sender import send_message from .test_runner import run_task_tests from bot_api.qwen_api.services import load_or_init_bkt_for_user, select_skill_for_task, get_or_generate_task_for_user, process_submission # Настройка логгера logger = logging.getLogger(__name__) # ========== 1. Список задач по уровню ========== @api_view(["GET"]) @permission_classes([TelegramUserPermission]) def tasks_by_level(request, level: str, max_tasks: int = 5): """ Возвращает task_ids по уровню сложности. """ logger.info(f"Пользователь {request.user.id} запрашивает задачи уровня: {level}") try: tasks = Task.objects.filter(difficulty=level, is_active=True).values_list("id", flat=True)[:max_tasks] task_list = list(tasks) logger.info(f"Найдено {len(task_list)} задач для уровня {level} для пользователя {request.user.id}") return Response({ "task_ids": task_list }) except Exception as e: logger.error(f"Ошибка при получении задач уровня {level} для пользователя {request.user.id}: {e}") return Response({"error": "Не удалось загрузить задачи"}, status=500) # ========== 2. Получение следующей задачи ========== @api_view(["POST"]) @permission_classes([TelegramUserPermission]) def next_task(request): """ Возвращает следующую задачу, выбранную по рекомендации BKT. Тело запроса: { "preferred_skill": optional } """ user = request.user logger.info(f"Пользователь {user.id} запрашивает следующую задачу") try: # Загружаем/инициализируем BKT bkt = load_or_init_bkt_for_user(user) logger.debug(f"BKT состояние для пользователя {user.id}: {bkt}") available_skills = set(SKILL_LIST) preferred = request.data.get("preferred_skill") if preferred: skill = preferred logger.info(f"Пользователь {user.id} предпочитает навык: {skill}") else: skill = select_skill_for_task(bkt, available_skills) logger.info(f"Автоматически выбран навык: {skill} для пользователя {user.id}") used_task_ids = set() task = get_or_generate_task_for_user(user, bkt, skill, llm_client=None, used_task_ids=used_task_ids) if not task: logger.warning(f"Не удалось сгенерировать задачу для пользователя {user.id}, навык: {skill}") return Response({"error": "Task generation failed"}, status=500) logger.info(f"Задача #{task.id} выдана пользователю {user.id}") return Response({ "task": { "id": task.id, "title": task.title, "difficulty": task.difficulty, "task_text": task.task_text, "topic": task.topic.name, } }) except Exception as e: logger.error(f"Ошибка при генерации следующей задачи для пользователя {user.id}: {e}", exc_info=True) return Response({"error": "Внутренняя ошибка сервера"}, status=500) # ========= 2.1. Получение задачи по ID ========== @api_view(["GET"]) @permission_classes([TelegramUserPermission]) def get_task_by_id(request, task_id: int): """ Возвращает задачу по ID. """ logger.info(f"Пользователь {request.user.id} запрашивает задачу по ID: {task_id}") try: task = Task.objects.get(id=task_id, is_active=True) logger.info(f"Задача {task_id} найдена и отправлена пользователю {request.user.id}") return Response({ "id": task.id, "title": task.title, "difficulty": task.difficulty, "task_text": task.task_text, "topic": task.topic.name, "test_cases": task.test_cases, }) except Task.DoesNotExist: logger.warning(f"Задача {task_id} не найдена при запросе от пользователя {request.user.id}") return Response({"error": "Task not found"}, status=404) except Exception as e: logger.error(f"Ошибка при получении задачи {task_id} для пользователя {request.user.id}: {e}", exc_info=True) return Response({"error": "Ошибка сервера"}, status=500) @api_view(["POST"]) @permission_classes([TelegramUserPermission]) def submit_solution(request, task_id: int): """ Принимает решение пользователя, запускает тесты, обновляет BKT, отправляет результат в Telegram. """ user = request.user code = request.data.get("user_solution", "") logger.info(f"Получено решение от пользователя {user.id} для задачи {task_id}") if not code: logger.warning(f"Пустое решение от пользователя {user.id} для задачи {task_id}") return Response({"error": "user_solution is required"}, status=400) try: task = Task.objects.get(id=task_id, is_active=True) logger.debug(f"Задача {task_id} найдена для проверки") except Task.DoesNotExist: logger.error(f"Задача {task_id} не найдена при попытке отправить решение от пользователя {user.id}") return Response({"error": "Task not found"}, status=404) try: result = process_submission(user, task, code, llm_coder=None) logger.info(f"Решение для задачи {task_id} обработано. Успешно: {result['passed']}") # ---------- ОТПРАВКА В TELEGRAM ---------- try: feedback_lines = [] for i, tc in enumerate(result["feedback"], 1): status_text = "ПРОЙДЕН" if tc.get("passed") else "НЕ ПРОЙДЕН" feedback_lines.append(f"Тест {i}: {status_text}") msg = ( f"Ваше решение для задачи {task.id} сохранено!\n\n" f"{chr(10).join(feedback_lines)}\n\n" f"Навык {task.topic.name}: {result['bkt_before']:.2f} → {result['bkt_after']:.2f}" ) send_message(user.telegram_id, msg) logger.info(f"Результат отправлен пользователю {user.id} в Telegram") # Отправка следующей задачи bkt = load_or_init_bkt_for_user(user) next_skill = select_skill_for_task(bkt, set(SKILL_LIST)) next_task = get_or_generate_task_for_user(user, bkt, next_skill, llm_client=None, used_task_ids=set()) if next_task: send_message( user.telegram_id, "Задача проверена. Получить следующую?", reply_markup={ "inline_keyboard": [ [ {"text": "➡ Следующая задача", "callback_data": "internal_next_task"} ] ] } ) logger.info(f"Предложение следующей задачи отправлено пользователю {user.id}") except Exception as e: logger.error(f"Ошибка при отправке сообщения в Telegram для пользователя {user.id}: {e}") # ---------- Ответ в веб-редактор ---------- logger.debug(f"Отправка ответа клиенту для задачи {task_id}") return Response({ "success": result["passed"], "feedback": result["feedback"], "ai_result": result["ai_result"], "message": "Решение отправлено", "bkt_before": result["bkt_before"], "bkt_after": result["bkt_after"], }, status=200) except Exception as e: logger.error(f"Критическая ошибка при обработке решения задачи {task_id} пользователем {user.id}: {e}", exc_info=True) return Response({"error": "Ошибка обработки решения"}, status=500) # ========== 3. Пропуск задачи ========== @api_view(["POST"]) @permission_classes([TelegramUserPermission]) def skip_task(request, task_id): """ Помечает задачу как пропущенную. """ user = request.user logger.info(f"Пользователь {user.id} пропускает задачу {task_id}") try: task = Task.objects.get(id=task_id, is_active=True) progress, created = UserProgress.objects.get_or_create(user=user, task=task) progress.skipped = True progress.finished_at = timezone.now() progress.save() logger.info(f"Задача {task_id} помечена как пропущенная для пользователя {user.id}") return Response({"status": "skipped"}) except Task.DoesNotExist: logger.warning(f"Попытка пропустить несуществующую задачу {task_id} пользователем {user.id}") return Response({"error": "Task not found"}, status=404) except Exception as e: logger.error(f"Ошибка при пропуске задачи {task_id} пользователем {user.id}: {e}", exc_info=True) return Response({"error": "Ошибка сервера"}, status=500) # ========== 4. Остановка теста ========== @api_view(["POST"]) @permission_classes([TelegramUserPermission]) def stop_test(request): """ Завершает текущий тест. """ user = request.user logger.info(f"Пользователь {user.id} остановил тест") return Response({"status": "stopped"}) # ========== 5. Итоги / статус теста ========== @api_view(["GET"]) @permission_classes([TelegramUserPermission]) def test_status(request): """ Возвращает краткую статистику по задачам пользователя. """ user = request.user logger.info(f"Запрос статуса теста от пользователя {user.id}") try: progress = UserProgress.objects.filter(user=user) total = progress.count() solved = progress.filter(correct=True).count() skipped = progress.filter(skipped=True).count() logger.debug(f"Статус теста для пользователя {user.id}: total={total}, solved={solved}, skipped={skipped}") return Response({ "total": total, "solved": solved, "skipped": skipped, "pending": total - solved - skipped }) except Exception as e: logger.error(f"Ошибка при получении статуса теста для пользователя {user.id}: {e}", exc_info=True) return Response({"error": "Ошибка сервера"}, status=500)