Files
yellow-bank-soal/app/models/tryout_snapshot_question.py
2026-04-02 17:04:01 +07:00

140 lines
4.2 KiB
Python

"""
Read-only normalized reference rows for imported tryout questions.
These rows reflect the latest imported source version of each question and are
kept separate from operational items and AI-generated variants.
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, JSON, String, Text, UniqueConstraint, func
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
class TryoutSnapshotQuestion(Base):
__tablename__ = "tryout_snapshot_questions"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
website_id: Mapped[int] = mapped_column(
ForeignKey("websites.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="Website identifier",
)
source_tryout_id: Mapped[str] = mapped_column(
String(255),
nullable=False,
index=True,
comment="External source tryout identifier",
)
source_question_id: Mapped[str] = mapped_column(
String(255),
nullable=False,
comment="External source question identifier",
)
latest_snapshot_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("tryout_import_snapshots.id", ondelete="SET NULL", onupdate="CASCADE"),
nullable=True,
index=True,
comment="Latest snapshot containing this question",
)
question_title: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="Imported title or short label",
)
question_html: Mapped[str] = mapped_column(
Text,
nullable=False,
comment="Imported question body HTML",
)
explanation_html: Mapped[Optional[str]] = mapped_column(
Text,
nullable=True,
comment="Imported explanation HTML",
)
raw_options: Mapped[list] = mapped_column(
JSON,
nullable=False,
comment="Raw source options payload",
)
correct_answer: Mapped[str] = mapped_column(
String(10),
nullable=False,
comment="Imported correct answer key",
)
category_id: Mapped[Optional[int]] = mapped_column(
Integer,
nullable=True,
comment="Imported category id",
)
category_name: Mapped[Optional[str]] = mapped_column(
String(255),
nullable=True,
comment="Imported category name",
)
category_code: Mapped[Optional[str]] = mapped_column(
String(255),
nullable=True,
comment="Imported category code",
)
option_count: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=0,
comment="Count of source options",
)
has_option_labels: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="Whether source options include visible labels",
)
is_active: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
comment="Whether question is still present in latest source import",
)
content_checksum: Mapped[str] = mapped_column(
String(64),
nullable=False,
comment="Checksum of normalized question content",
)
raw_payload: Mapped[dict] = mapped_column(
JSON,
nullable=False,
comment="Original source question payload",
)
first_seen_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
)
last_seen_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default=func.now(),
onupdate=func.now(),
)
__table_args__ = (
UniqueConstraint(
"website_id",
"source_tryout_id",
"source_question_id",
name="uq_snapshot_questions_website_tryout_question",
),
)