Files
secondary-school-grade-archive/backend/app/models/user.py
T
dekun e329d3398a Initial commit: secondary school grade archive system.
Add FastAPI/React app with Docker deployment, Ubuntu one-click install, and docs for junior/senior high score tracking and mistake bank.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 11:18:58 +08:00

131 lines
5.3 KiB
Python

import enum
import uuid
from datetime import date, datetime, timezone
from sqlalchemy import Date, DateTime, Enum, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class ExamType(str, enum.Enum):
weekly = "weekly"
monthly = "monthly"
final = "final"
class WrongQuestionStatus(str, enum.Enum):
pending = "pending"
ocr_done = "ocr_done"
solved = "solved"
failed = "failed"
class SchoolLevel(str, enum.Enum):
junior_high = "junior_high"
senior_high = "senior_high"
class User(Base):
__tablename__ = "users"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255))
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
students: Mapped[list["Student"]] = relationship(back_populates="user", cascade="all, delete-orphan")
class Student(Base):
__tablename__ = "students"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
user_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("users.id"), index=True)
name: Mapped[str] = mapped_column(String(64))
school_level: Mapped[SchoolLevel] = mapped_column(
Enum(SchoolLevel), default=SchoolLevel.junior_high
)
grade: Mapped[str | None] = mapped_column(String(32), nullable=True)
class_name: Mapped[str | None] = mapped_column(String(32), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
user: Mapped["User"] = relationship(back_populates="students")
exam_records: Mapped[list["ExamRecord"]] = relationship(
back_populates="student", cascade="all, delete-orphan"
)
wrong_questions: Mapped[list["WrongQuestion"]] = relationship(
back_populates="student", cascade="all, delete-orphan"
)
class Subject(Base):
__tablename__ = "subjects"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(32), unique=True)
scores: Mapped[list["SubjectScore"]] = relationship(back_populates="subject")
wrong_questions: Mapped[list["WrongQuestion"]] = relationship(back_populates="subject")
class ExamRecord(Base):
__tablename__ = "exam_records"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
student_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("students.id"), index=True)
exam_type: Mapped[ExamType] = mapped_column(Enum(ExamType))
exam_date: Mapped[date] = mapped_column(Date)
title: Mapped[str | None] = mapped_column(String(128), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
student: Mapped["Student"] = relationship(back_populates="exam_records")
scores: Mapped[list["SubjectScore"]] = relationship(
back_populates="exam_record", cascade="all, delete-orphan"
)
class SubjectScore(Base):
__tablename__ = "subject_scores"
__table_args__ = (UniqueConstraint("exam_record_id", "subject_id", name="uq_exam_subject"),)
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
exam_record_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("exam_records.id"), index=True
)
subject_id: Mapped[int] = mapped_column(Integer, ForeignKey("subjects.id"))
total_score: Mapped[float] = mapped_column(Numeric(8, 2))
obtained_score: Mapped[float] = mapped_column(Numeric(8, 2))
ratio: Mapped[float] = mapped_column(Numeric(8, 4))
exam_record: Mapped["ExamRecord"] = relationship(back_populates="scores")
subject: Mapped["Subject"] = relationship(back_populates="scores")
class WrongQuestion(Base):
__tablename__ = "wrong_questions"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
student_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("students.id"), index=True)
subject_id: Mapped[int] = mapped_column(Integer, ForeignKey("subjects.id"))
image_path: Mapped[str] = mapped_column(String(512))
ocr_raw_text: Mapped[str | None] = mapped_column(Text, nullable=True)
question_text: Mapped[str | None] = mapped_column(Text, nullable=True)
solution_text: Mapped[str | None] = mapped_column(Text, nullable=True)
status: Mapped[WrongQuestionStatus] = mapped_column(
Enum(WrongQuestionStatus), default=WrongQuestionStatus.pending
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
)
student: Mapped["Student"] = relationship(back_populates="wrong_questions")
subject: Mapped["Subject"] = relationship(back_populates="wrong_questions")