ai-backend / backend /models.py
Arin Sujal
Add files via upload
656cbff unverified
Raw History Blame Contribute Delete
15.5 kB
"""
Shared domain models for the multi-tenant AI Quiz system.
Every request carries school_id + board_id to route to the correct tenant silo.
"""
from typing import List, Optional, Dict, Any
from pydantic import BaseModel, Field, model_validator, ConfigDict
class UserProfile(BaseModel):
"""Unified profile for student, teacher, admin, and super_admin roles."""
model_config = ConfigDict(str_strip_whitespace=True)
user_id: str = "student-default"
name: str = "Student"
school_id: Optional[str] = None
board_id: Optional[str] = None
role: str = "student"
class_id: Optional[str] = None
section_id: Optional[str] = "A"
subjects: List[str] = Field(default_factory=lambda: ["Science", "Mathematics", "English"])
sub_subject: Optional[str] = "General"
class PermissionRequest(BaseModel):
user: UserProfile
action: str
resource: Dict[str, Any] = Field(default_factory=dict)
class PermissionDecision(BaseModel):
allowed: bool
reason: str
user_id: str
timestamp: Optional[str] = None
class TrainingDeleteRequest(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
user: Optional[UserProfile] = None
user_id: Optional[str] = None
user_name: Optional[str] = None
user_role: Optional[str] = None
school_id: str
board_id: str
class_id: str
subject: str
section_id: Optional[str] = "A"
sub_subject: Optional[str] = "General"
chapter: Optional[str] = None
lesson: Optional[str] = None
topic: Optional[str] = None
@model_validator(mode="before")
@classmethod
def sync_user_and_tenant(cls, data: Any) -> Any:
if isinstance(data, dict):
effective_ch = data.get("chapter") or data.get("lesson")
if effective_ch:
data.setdefault("chapter", effective_ch)
data.setdefault("lesson", effective_ch)
user_raw = data.get("user")
if isinstance(user_raw, dict):
for k in ("school_id", "board_id", "class_id", "section_id", "sub_subject"):
if not user_raw.get(k) and data.get(k):
user_raw[k] = data[k]
return data
@property
def effective_lesson(self) -> str:
return self.lesson or self.chapter or ""
class DescriptiveConfigItem(BaseModel):
level: str = "medium" # easy, medium, hard
format: str = "short" # short, long, comprehensive
count: int = 1
class QuizGenerateRequest(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
user: Optional[UserProfile] = None
school_id: Optional[str] = None
board_id: Optional[str] = None
class_id: Optional[str] = None
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: Optional[str] = None
lesson: Optional[str] = None
topic: Optional[str] = None
num_questions: int = 5
hf_token: Optional[str] = None
difficulty: Optional[str] = "medium" # easy, medium, hard
question_format: Optional[str] = "short" # short, long, comprehensive
descriptive_distribution: Optional[List[DescriptiveConfigItem]] = None
@model_validator(mode="before")
@classmethod
def sync_tenant_fields(cls, data: Any) -> Any:
if isinstance(data, dict):
effective_ch = data.get("chapter") or data.get("lesson")
if effective_ch:
data.setdefault("chapter", effective_ch)
data.setdefault("lesson", effective_ch)
user_raw = data.get("user")
if isinstance(user_raw, dict):
for k in ("school_id", "board_id", "class_id", "section_id", "sub_subject"):
if not data.get(k) and user_raw.get(k):
data[k] = user_raw[k]
elif not user_raw.get(k) and data.get(k):
user_raw[k] = data[k]
elif isinstance(user_raw, UserProfile):
for k in ("school_id", "board_id", "class_id", "section_id", "sub_subject"):
val = getattr(user_raw, k, None)
if not data.get(k) and val:
data[k] = val
elif user_raw is None:
data["user"] = {
"user_id": "student-default",
"name": "Student",
"role": "student",
"school_id": data.get("school_id") or "school_default",
"board_id": data.get("board_id") or "cbse",
"class_id": data.get("class_id") or "10",
"section_id": data.get("section_id") or "A",
"sub_subject": data.get("sub_subject") or "General",
"subjects": [data.get("subject", "Science")]
}
return data
@property
def effective_lesson(self) -> str:
return self.lesson or self.chapter or ""
class QuizEvaluateRequest(BaseModel):
user: Optional[UserProfile] = None
school_id: Optional[str] = None
board_id: Optional[str] = None
class_id: Optional[str] = None
section_id: Optional[str] = "A"
subject: Optional[str] = None
sub_subject: Optional[str] = "General"
chapter: Optional[str] = None
lesson: Optional[str] = None
topic: Optional[str] = None
question: str
selected_option: str
correct_option: str
hf_token: Optional[str] = None
@model_validator(mode="before")
@classmethod
def sync_tenant_fields(cls, data: Any) -> Any:
if isinstance(data, dict):
effective_ch = data.get("chapter") or data.get("lesson")
if effective_ch:
data.setdefault("chapter", effective_ch)
data.setdefault("lesson", effective_ch)
user_raw = data.get("user")
if isinstance(user_raw, dict):
for k in ("school_id", "board_id", "class_id", "section_id", "sub_subject"):
if not data.get(k) and user_raw.get(k):
data[k] = user_raw[k]
elif not user_raw.get(k) and data.get(k):
user_raw[k] = data[k]
elif isinstance(user_raw, UserProfile):
for k in ("school_id", "board_id", "class_id", "section_id", "sub_subject"):
val = getattr(user_raw, k, None)
if not data.get(k) and val:
data[k] = val
elif user_raw is None:
data["user"] = {
"user_id": "student-default",
"name": "Student",
"role": "student",
"school_id": data.get("school_id") or "school_default",
"board_id": data.get("board_id") or "cbse",
"class_id": data.get("class_id") or "10",
"section_id": data.get("section_id") or "A",
"sub_subject": data.get("sub_subject") or "General",
"subjects": [data.get("subject", "Science")] if data.get("subject") else ["Science"]
}
return data
@property
def effective_lesson(self) -> str:
return self.lesson or self.chapter or ""
class MCQOption(BaseModel):
id: str
text: str
class QuizQuestion(BaseModel):
id: int
question: str
options: List[MCQOption]
correct_option: str
topic: str
explanation: str
source_context: Optional[str] = None
class QuizSet(BaseModel):
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[QuizQuestion]
class EvaluationResult(BaseModel):
is_correct: bool
selected_option: str
correct_option: str
explanation: str
evidence_quote: str
topic: str
class TrainingStatus(BaseModel):
school_id: str
board_id: str
chapters: Dict[str, Dict[str, Any]]
# ── Student Anti-Cheat Quiz Models ──────────────────────────────────────────
class StudentQuizQuestion(BaseModel):
id: int
question: str
options: List[MCQOption]
class StudentQuizSet(BaseModel):
quiz_id: str
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[StudentQuizQuestion]
class StudentAnswerItem(BaseModel):
question_id: int
selected_option: str
class StudentQuizSubmitRequest(BaseModel):
user: Optional[UserProfile] = None
quiz_id: str
answers: List[StudentAnswerItem]
hf_token: Optional[str] = None
class StudentQuestionResult(BaseModel):
question_id: int
question: str
selected_option: str
correct_option: str
is_correct: bool
explanation: str
evidence_quote: Optional[str] = None
class StudentQuizResult(BaseModel):
quiz_id: str
score: int
total: int
percentage: float
results: List[StudentQuestionResult]
# ── Fill-in-the-Blanks Models (Append-Only) ───────────────────────────────────
class BlankQuestion(BaseModel):
id: int
question: str
correct_answer: str
acceptable_alternatives: List[str] = Field(default_factory=list)
topic: str
explanation: str
source_context: Optional[str] = None
class BlankQuizSet(BaseModel):
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[BlankQuestion]
class StudentBlankQuizQuestion(BaseModel):
id: int
question: str
class StudentBlankQuizSet(BaseModel):
quiz_id: str
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[StudentBlankQuizQuestion]
class StudentBlankAnswerItem(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
question_id: int
student_answer: str
class StudentBlankQuizSubmitRequest(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
user: Optional[UserProfile] = None
quiz_id: str
answers: List[StudentBlankAnswerItem]
hf_token: Optional[str] = None
class StudentBlankQuestionResult(BaseModel):
question_id: int
question: str
student_answer: str
correct_answer: str
is_correct: bool
explanation: str
evidence_quote: Optional[str] = None
class StudentBlankQuizResult(BaseModel):
quiz_id: str
score: int
total: int
percentage: float
results: List[StudentBlankQuestionResult]
# ── True/False Models (Append-Only) ──────────────────────────────────────────
class TrueFalseQuestion(BaseModel):
id: int
question: str
options: List[MCQOption]
correct_option: str # "A" or "B"
topic: Optional[str] = "General"
explanation: str
source_context: Optional[str] = None
class TrueFalseQuizSet(BaseModel):
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[TrueFalseQuestion]
class StudentTrueFalseQuizQuestion(BaseModel):
id: int
question: str
options: List[MCQOption]
class StudentTrueFalseQuizSet(BaseModel):
quiz_id: str
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[StudentTrueFalseQuizQuestion]
class StudentTrueFalseAnswerItem(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
question_id: int
selected_option: str # "A" or "B"
class StudentTrueFalseQuizSubmitRequest(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
user: Optional[UserProfile] = None
quiz_id: str
answers: List[StudentTrueFalseAnswerItem]
hf_token: Optional[str] = None
class StudentTrueFalseQuestionResult(BaseModel):
question_id: int
question: str
selected_option: str
correct_option: str
is_correct: bool
explanation: str
evidence_quote: Optional[str] = None
class StudentTrueFalseQuizResult(BaseModel):
quiz_id: str
score: int
total: int
percentage: float
results: List[StudentTrueFalseQuestionResult]
# ── Descriptive Question Models (Append-Only) ─────────────────────────────────
class DescriptiveQuestion(BaseModel):
id: int
question: str
model_answer: str
key_facts: List[str] = Field(default_factory=list)
difficulty: str = "medium" # easy, medium, hard
format: str = "short" # short, long, comprehensive
max_score: int = 5
topic: Optional[str] = "General"
explanation: str
source_context: Optional[str] = None
class DescriptiveQuizSet(BaseModel):
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[DescriptiveQuestion]
class StudentDescriptiveQuizQuestion(BaseModel):
id: int
question: str
difficulty: str = "medium"
format: str = "short"
max_score: int = 5
topic: Optional[str] = "General"
class StudentDescriptiveQuizSet(BaseModel):
quiz_id: str
school_id: str
board_id: str
class_id: str
section_id: Optional[str] = "A"
subject: str
sub_subject: Optional[str] = "General"
chapter: str
lesson: Optional[str] = None
topic: Optional[str] = None
questions: List[StudentDescriptiveQuizQuestion]
class StudentDescriptiveAnswerItem(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
question_id: int
student_answer: str
class StudentDescriptiveQuizSubmitRequest(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True)
user: Optional[UserProfile] = None
quiz_id: str
answers: List[StudentDescriptiveAnswerItem]
hf_token: Optional[str] = None
class StudentDescriptiveQuestionResult(BaseModel):
question_id: int
question: str
student_answer: str
model_answer: str
score: float
max_score: int
is_correct: bool
feedback: str
key_points_covered: List[str] = Field(default_factory=list)
key_points_missing: List[str] = Field(default_factory=list)
evidence_quote: Optional[str] = None
explanation: Optional[str] = None
class StudentDescriptiveQuizResult(BaseModel):
quiz_id: str
score: float
total: int
percentage: float
results: List[StudentDescriptiveQuestionResult]