140 lines
4.2 KiB
Python
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",
|
|
),
|
|
)
|