from pydantic import BaseModel, Field from datetime import datetime from typing import Any, List class StudentCreate(BaseModel): first_name: str = Field(..., min_length=1) age: int = Field(..., ge=8, le=12) grade: str class StudentRead(BaseModel): id: int first_name: str age: int grade: str class Config: from_attributes = True class UserRead(BaseModel): id: int username: str role: str student_id: int | None = None student: StudentRead | None = None class Config: from_attributes = True class LoginRequest(BaseModel): username: str = Field(..., min_length=1) password: str = Field(..., min_length=1) class LoginResponse(BaseModel): access_token: str token_type: str = "bearer" user: UserRead class StudentAccountCreate(StudentCreate): username: str = Field(..., min_length=1) password: str = Field(..., min_length=1) class StudentAccountResponse(BaseModel): student: StudentRead user: UserRead class ChatRequest(BaseModel): student_id: int message: str lesson_title: str | None = None asset_title: str | None = None section_title: str | None = None section_context_key: str | None = None section_context: str | None = None step_percent: int | None = None class ChatResponse(BaseModel): reply: str should_speak: bool = True advance_lesson_step: bool = False class MessageRead(BaseModel): id: int student_id: int role: str content: str created_at: datetime class Config: from_attributes = True class TTSProfile(BaseModel): id: str label: str description: str class TTSProfilesResponse(BaseModel): profiles: List[TTSProfile] class TTSRequest(BaseModel): text: str = Field(..., min_length=1, max_length=4096) profile_id: str = Field(..., min_length=1) class SkillProgress(BaseModel): code: str subject: str label: str mastery_score: float confidence: float evidence_count: int class ProgressResponse(BaseModel): student: StudentRead progress: List[SkillProgress] class ProgramAsset(BaseModel): name: str title: str path: str url: str sections: List[dict] = [] class ProgramLesson(BaseModel): id: str title: str subject: str grade: str cycle: str asset_count: int assets: List[ProgramAsset] = [] class ProgramLessonListResponse(BaseModel): lessons: List[ProgramLesson] class ProgramReviewRequest(BaseModel): node_key: str = Field(..., min_length=1) status: str = "needs_changes" comment: str = "" class StudentProgramAssignRequest(BaseModel): lesson_id: str = Field(..., min_length=1) class StudentProgramResponse(BaseModel): student: StudentRead lesson: ProgramLesson | None = None start_asset_index: int = 0 start_section_index: int = 0 class StudentProgramStartRequest(BaseModel): asset_index: int = Field(..., ge=0) section_index: int = Field(..., ge=0) class LessonCardState(BaseModel): lesson_id: str asset_path: str asset_title: str section_key: str section_title: str comprehension_score: float = Field(..., ge=0, le=100) attempts: int = Field(0, ge=0) validated: bool = False class LessonSessionEndRequest(BaseModel): student_id: int lesson_id: str lesson_title: str conversation: List[dict[str, Any]] card_states: List[LessonCardState] = [] class LessonSessionLogRead(BaseModel): id: int student_id: int lesson_id: str lesson_title: str started_at: datetime ended_at: datetime conversation: str card_results: str class Config: from_attributes = True class LessonCardProgressRead(BaseModel): id: int student_id: int lesson_id: str asset_path: str asset_title: str section_key: str section_title: str comprehension_score: float attempts: int validated_at: datetime | None = None updated_at: datetime class Config: from_attributes = True class AssessmentQuestionResponse(BaseModel): skill_code: str skill_label: str question: str class AssessmentAnswerRequest(BaseModel): student_id: int skill_code: str answer: str class AssessmentAnswerResponse(BaseModel): correct: bool feedback: str mastery_score: float