Files
secondary-school-grade-archive/backend/app/schemas/__init__.py
T
dekun f1ad4273f4 零 Node 部署、超级管理员,并完善本地构建发布文档。
- FastAPI 单进程托管 frontend/dist,systemd 替代 PM2

- 超级管理员 admin、注册开关与用户管理

- README/DEPLOY/USAGE 说明:改代码须本地构建 dist 后 push,服务器 update.sh

- 提交 frontend/dist 与 build-frontend 脚本
2026-06-28 13:19:41 +08:00

224 lines
5.0 KiB
Python

from datetime import date, datetime
from enum import Enum
from uuid import UUID
from pydantic import BaseModel, Field, field_validator
class ExamTypeEnum(str, Enum):
weekly = "weekly"
monthly = "monthly"
final = "final"
class WrongQuestionStatusEnum(str, Enum):
pending = "pending"
ocr_done = "ocr_done"
solved = "solved"
failed = "failed"
class SchoolLevelEnum(str, Enum):
junior_high = "junior_high"
senior_high = "senior_high"
class TokenResponse(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
class UserRegister(BaseModel):
username: str = Field(min_length=3, max_length=64)
password: str = Field(min_length=6, max_length=128)
class UserLogin(BaseModel):
username: str
password: str
class RefreshRequest(BaseModel):
refresh_token: str
class UserOut(BaseModel):
id: UUID
username: str
is_superuser: bool = False
created_at: datetime
model_config = {"from_attributes": True}
class PublicSettingsOut(BaseModel):
registration_enabled: bool
class SystemSettingsOut(BaseModel):
registration_enabled: bool
updated_at: datetime
model_config = {"from_attributes": True}
class SystemSettingsUpdate(BaseModel):
registration_enabled: bool | None = None
class AdminProfileUpdate(BaseModel):
username: str | None = Field(default=None, min_length=3, max_length=64)
current_password: str | None = None
password: str | None = Field(default=None, min_length=6, max_length=128)
class AdminUserCreate(BaseModel):
username: str = Field(min_length=3, max_length=64)
password: str = Field(min_length=6, max_length=128)
class AdminUserPasswordUpdate(BaseModel):
password: str = Field(min_length=6, max_length=128)
class AdminUserOut(BaseModel):
id: UUID
username: str
is_superuser: bool
created_at: datetime
model_config = {"from_attributes": True}
class StudentCreate(BaseModel):
name: str = Field(min_length=1, max_length=64)
school_level: SchoolLevelEnum = SchoolLevelEnum.junior_high
grade: str | None = None
class_name: str | None = None
class StudentUpdate(BaseModel):
name: str | None = None
school_level: SchoolLevelEnum | None = None
grade: str | None = None
class_name: str | None = None
class StudentOut(BaseModel):
id: UUID
name: str
school_level: SchoolLevelEnum
grade: str | None
class_name: str | None
created_at: datetime
model_config = {"from_attributes": True}
class SubjectOut(BaseModel):
id: int
name: str
model_config = {"from_attributes": True}
class ScoreInput(BaseModel):
subject_id: int
total_score: float
obtained_score: float
@field_validator("total_score")
@classmethod
def validate_total(cls, v: float) -> float:
if v <= 0:
raise ValueError("总分必须大于0")
return v
@field_validator("obtained_score")
@classmethod
def validate_obtained(cls, v: float, info) -> float:
total = info.data.get("total_score")
if total is not None and v > total:
raise ValueError("得分不能大于总分")
if v < 0:
raise ValueError("得分不能为负")
return v
class ScoreOut(BaseModel):
id: UUID
subject_id: int
subject_name: str | None = None
total_score: float
obtained_score: float
ratio: float
model_config = {"from_attributes": True}
class ExamCreate(BaseModel):
exam_type: ExamTypeEnum
exam_date: date
title: str | None = None
scores: list[ScoreInput] = []
class ExamUpdate(BaseModel):
exam_type: ExamTypeEnum | None = None
exam_date: date | None = None
title: str | None = None
scores: list[ScoreInput] | None = None
class ExamOut(BaseModel):
id: UUID
exam_type: ExamTypeEnum
exam_date: date
title: str | None
created_at: datetime
scores: list[ScoreOut] = []
model_config = {"from_attributes": True}
class TrendPoint(BaseModel):
exam_id: UUID
exam_type: ExamTypeEnum
exam_date: date
title: str | None
ratio: float
ratio_percent: float
delta: float | None = None
delta_percent: float | None = None
is_volatile: bool = False
direction: str | None = None
class TrendResponse(BaseModel):
subject_id: int
subject_name: str
threshold: float
points: list[TrendPoint]
class WrongQuestionOut(BaseModel):
id: UUID
student_id: UUID
subject_id: int
subject_name: str | None = None
image_path: str
ocr_raw_text: str | None
question_text: str | None
solution_text: str | None
status: WrongQuestionStatusEnum
created_at: datetime
model_config = {"from_attributes": True}
class WrongQuestionUpdate(BaseModel):
question_text: str | None = None
solution_text: str | None = None
subject_id: int | None = None