166 lines
6.9 KiB
Python
166 lines
6.9 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 WrongQuestionCategory(str, enum.Enum):
|
|
regular = "regular"
|
|
olympiad = "olympiad"
|
|
|
|
|
|
class AIProvider(str, enum.Enum):
|
|
ollama = "ollama"
|
|
openai = "openai"
|
|
|
|
|
|
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))
|
|
is_superuser: Mapped[bool] = mapped_column(default=False)
|
|
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_approach: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
solution_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
mark_regions_json: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
annotated_image_path: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[WrongQuestionStatus] = mapped_column(
|
|
Enum(WrongQuestionStatus), default=WrongQuestionStatus.pending
|
|
)
|
|
category: Mapped[WrongQuestionCategory] = mapped_column(
|
|
Enum(WrongQuestionCategory), default=WrongQuestionCategory.regular
|
|
)
|
|
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")
|
|
|
|
|
|
class SystemSettings(Base):
|
|
__tablename__ = "system_settings"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, default=1)
|
|
registration_enabled: Mapped[bool] = mapped_column(default=True)
|
|
ai_provider: Mapped[str] = mapped_column(String(16), default="ollama")
|
|
ollama_base_url: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
ollama_model: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
openai_base_url: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
openai_model: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
openai_api_key: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
ocr_service_url: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
|
|
)
|