from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from app.core.security import AuthUser, get_current_user, hash_user_id from app.db import SessionLocal from app.models import Quiz, QuizAttempt from app.services import analytics router = APIRouter(tags=["quiz"]) class AnswersReq(BaseModel): answers: dict[str, Any] def _grade(q: dict[str, Any], given: Any) -> bool: t = q["type"] try: if t == "mcq": return int(given) == int(q["answer"]) if t == "true_false": g = given if isinstance(given, bool) else str(given).lower() in {"true", "vrai", "1"} return g == bool(q["answer"]) if t == "numeric": val = float(str(given).replace(" ", "").replace(",", ".").replace("$", "").replace("%", "")) ans = float(q["answer"]) tol = float(q.get("tolerance", 0.02)) return abs(val - ans) <= max(abs(ans) * tol, 1e-9) except (TypeError, ValueError): return False return False @router.get("/quiz/{quiz_id}") async def get_quiz(quiz_id: str, auth: AuthUser = Depends(get_current_user)) -> dict: async with SessionLocal() as session: quiz = await session.get(Quiz, quiz_id) if not quiz or quiz.user_id != auth.id: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Quiz introuvable.") payload = dict(quiz.payload) payload["questions"] = [{k: v for k, v in q.items() if k not in {"answer", "explanation"}} for q in payload.get("questions", [])] return payload @router.post("/quiz/{quiz_id}/answers") async def submit(quiz_id: str, req: AnswersReq, auth: AuthUser = Depends(get_current_user)) -> dict: async with SessionLocal() as session: quiz = await session.get(Quiz, quiz_id) if not quiz or quiz.user_id != auth.id: raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Quiz introuvable.") questions = quiz.payload.get("questions", []) results = [] correct = 0 for q in questions: given = req.answers.get(q["id"]) ok = _grade(q, given) if given is not None else False correct += int(ok) results.append({"id": q["id"], "correct": ok, "given": given, "answer": q["answer"], "explanation": q.get("explanation", ""), "unit": q.get("unit", "")}) score = correct / len(questions) if questions else 0.0 session.add(QuizAttempt(quiz_id=quiz_id, answers=req.answers, score=score)) await session.commit() await analytics.record_event(hash_user_id(auth.id), quiz.course_code, "quiz", quiz.topic) return {"score": score, "correct": correct, "total": len(questions), "results": results}