from pydantic import BaseModel, Field class SystemSettingsOut(BaseModel): system_name: str logo_url: str | None = None class SystemSettingsUpdate(BaseModel): system_name: str = Field(min_length=1, max_length=128) class LoginRequest(BaseModel): username: str password: str class ChangePasswordRequest(BaseModel): old_password: str = Field(min_length=1, max_length=128) new_password: str = Field(min_length=6, max_length=128) class TokenResponse(BaseModel): access_token: str token_type: str = "bearer" class UserMe(BaseModel): id: int username: str display_name: str role: str class Config: from_attributes = True class ScoreRow(BaseModel): examinee_id: str name: str | None id_card: str | None subject_name: str score: float attempt_id: str room_id: str | None = None paper_name: str | None = None start_time: str | None = None end_time: str | None = None class ScoresResponse(BaseModel): total: int page: int page_size: int items: list[ScoreRow] class QuestionDetail(BaseModel): external_item_id: str part_name: str | None item_type: int | None item_order: int | None title_html: str | None detail_json: str | None max_score: float | None reference_answer: str | None user_answer: str | None is_correct: bool score: float class PartGroup(BaseModel): part_name: str questions: list[QuestionDetail] class AttemptDetail(BaseModel): attempt_id: str examinee_name: str | None id_card: str | None score: float paper_total_score: float | None paper_name: str | None subject_name: str | None room_id: str | None seat_number: str | None start_time: str | None end_time: str | None parts: list[PartGroup] class GraderOut(BaseModel): id: int username: str display_name: str is_active: bool created_at: str subjects: list[str] = [] class GraderCreate(BaseModel): username: str = Field(min_length=2, max_length=64) password: str = Field(min_length=6, max_length=128) display_name: str = Field(default="", max_length=128) subjects: list[str] = Field(min_length=1) class GraderUpdate(BaseModel): username: str | None = Field(default=None, min_length=2, max_length=64) password: str | None = Field(default=None, min_length=6, max_length=128) display_name: str | None = Field(default=None, max_length=128) is_active: bool | None = None subjects: list[str] | None = None class GradersResponse(BaseModel): items: list[GraderOut] class ImportBatchOut(BaseModel): id: int subject_name: str exam_id: int | None exam_ids: list[int] = [] zip_filename: str imported_at: str status: str attempt_count: int = 0 paper_name: str | None = None class Config: from_attributes = True