Study-with-ChampAI / quiz /models.py
SolusOps's picture
feat: quiz package
676d6d1 verified
Raw
History Blame Contribute Delete
2.21 kB
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List
import time
VALID_DIFFICULTIES = {"easy", "medium", "hard"}
VALID_TYPES = {"mcq", "tf", "fill"}
@dataclass
class Question:
text: str
topic: str # subject area — "Determinants" not question text
options: List[str]
correct_idx: int
explanation: str
difficulty: str
q_type: str
is_boss: bool = False
source_excerpt: str = "" # snippet from original material for tutor context
language: str = "en"
def __post_init__(self):
if self.difficulty not in VALID_DIFFICULTIES:
raise ValueError(f"difficulty must be one of {VALID_DIFFICULTIES}, got '{self.difficulty}'")
if self.q_type not in VALID_TYPES:
raise ValueError(f"q_type must be one of {VALID_TYPES}, got '{self.q_type}'")
if not (0 <= self.correct_idx < len(self.options)):
raise ValueError(f"correct_idx {self.correct_idx} out of range for {len(self.options)} options")
@property
def correct_answer(self) -> str:
return self.options[self.correct_idx]
@dataclass
class QuizSession:
quest_name: str
questions: List[Question] = field(default_factory=list)
current_idx: int = 0
score: int = 0
consecutive_correct: int = 0
xp_earned: int = 0
start_time: float = field(default_factory=time.time)
wrong_topics: List[str] = field(default_factory=list) # topic strings, not question text
@property
def is_finished(self) -> bool:
return self.current_idx >= len(self.questions)
@property
def current_question(self) -> Question | None:
if self.is_finished:
return None
return self.questions[self.current_idx]
@dataclass
class Quest:
name: str
topics: List[str]
boss_topic: str
difficulty: str
questions: List[Question] = field(default_factory=list)
unlocked: bool = True
completed: bool = False # True only after user finishes the quest
def has_questions(self) -> bool:
return len(self.questions) > 0
def total_questions(self) -> int:
return len(self.questions)