Python 64.6%
TypeScript 33.7%
CSS 0.8%
1from __future__ import annotations23from typing import Any45from fastapi import APIRouter, Depends, HTTPException, status6from pydantic import BaseModel78from app.core.security import AuthUser, get_current_user, hash_user_id9from app.db import SessionLocal10from app.models import Quiz, QuizAttempt11from app.services import analytics1213router = APIRouter(tags=["quiz"])141516class AnswersReq(BaseModel):17 answers: dict[str, Any]181920def _grade(q: dict[str, Any], given: Any) -> bool:21 t = q["type"]22 try:23 if t == "mcq":24 return int(given) == int(q["answer"])25 if t == "true_false":26 g = given if isinstance(given, bool) else str(given).lower() in {"true", "vrai", "1"}27 return g == bool(q["answer"])28 if t == "numeric":29 val = float(str(given).replace(" ", "").replace(",", ".").replace("$", "").replace("%", ""))30 ans = float(q["answer"])31 tol = float(q.get("tolerance", 0.02))32 return abs(val - ans) <= max(abs(ans) * tol, 1e-9)33 except (TypeError, ValueError):34 return False35 return False363738@router.get("/quiz/{quiz_id}")39async def get_quiz(quiz_id: str, auth: AuthUser = Depends(get_current_user)) -> dict:40 async with SessionLocal() as session:41 quiz = await session.get(Quiz, quiz_id)42 if not quiz or quiz.user_id != auth.id:43 raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Quiz introuvable.")44 payload = dict(quiz.payload)45 payload["questions"] = [{k: v for k, v in q.items() if k not in {"answer", "explanation"}}46 for q in payload.get("questions", [])]47 return payload484950@router.post("/quiz/{quiz_id}/answers")51async def submit(quiz_id: str, req: AnswersReq, auth: AuthUser = Depends(get_current_user)) -> dict:52 async with SessionLocal() as session:53 quiz = await session.get(Quiz, quiz_id)54 if not quiz or quiz.user_id != auth.id:55 raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Quiz introuvable.")56 questions = quiz.payload.get("questions", [])57 results = []58 correct = 059 for q in questions:60 given = req.answers.get(q["id"])61 ok = _grade(q, given) if given is not None else False62 correct += int(ok)63 results.append({"id": q["id"], "correct": ok, "given": given,64 "answer": q["answer"], "explanation": q.get("explanation", ""),65 "unit": q.get("unit", "")})66 score = correct / len(questions) if questions else 0.067 session.add(QuizAttempt(quiz_id=quiz_id, answers=req.answers, score=score))68 await session.commit()69 await analytics.record_event(hash_user_id(auth.id), quiz.course_code, "quiz", quiz.topic)70 return {"score": score, "correct": correct, "total": len(questions), "results": results}71