Files
yellow-bank-soal/app/models/tryout.py
Dwindi Ramadhana cf193d7ea0 first commit
2026-03-21 23:32:59 +07:00

185 lines
6.4 KiB
Python

"""
Tryout model with configuration for assessment sessions.
Represents tryout exams with configurable scoring, selection, and normalization modes.
"""
from datetime import datetime
from typing import Literal, Union
from sqlalchemy import Boolean, CheckConstraint, DateTime, Float, ForeignKey, Index, Integer, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class Tryout(Base):
"""
Tryout model with configuration for assessment sessions.
Supports multiple scoring modes (CTT, IRT, hybrid), selection strategies
(fixed, adaptive, hybrid), and normalization modes (static, dynamic, hybrid).
Attributes:
id: Primary key
website_id: Website identifier
tryout_id: Tryout identifier (unique per website)
name: Tryout name
description: Tryout description
scoring_mode: Scoring algorithm (ctt, irt, hybrid)
selection_mode: Item selection strategy (fixed, adaptive, hybrid)
normalization_mode: Normalization method (static, dynamic, hybrid)
min_sample_for_dynamic: Minimum sample size for dynamic normalization
static_rataan: Static mean value for manual normalization
static_sb: Static standard deviation for manual normalization
AI_generation_enabled: Enable/disable AI question generation
hybrid_transition_slot: Slot number to transition from fixed to adaptive
min_calibration_sample: Minimum responses needed for IRT calibration
theta_estimation_method: Method for estimating theta (mle, map, eap)
fallback_to_ctt_on_error: Fallback to CTT if IRT fails
created_at: Record creation timestamp
updated_at: Record update timestamp
website: Website relationship
items: Items in this tryout
sessions: Sessions for this tryout
stats: Tryout statistics
"""
__tablename__ = "tryouts"
# Primary key
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
# Foreign keys
website_id: Mapped[int] = mapped_column(
ForeignKey("websites.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="Website identifier",
)
# Tryout identifier (unique per website)
tryout_id: Mapped[str] = mapped_column(
String(255),
nullable=False,
index=True,
comment="Tryout identifier (unique per website)",
)
# Basic information
name: Mapped[str] = mapped_column(
String(255), nullable=False, comment="Tryout name"
)
description: Mapped[Union[str, None]] = mapped_column(
String(1000), nullable=True, comment="Tryout description"
)
# Scoring mode: ctt (Classical Test Theory), irt (Item Response Theory), hybrid
scoring_mode: Mapped[Literal["ctt", "irt", "hybrid"]] = mapped_column(
String(50), nullable=False, default="ctt", comment="Scoring mode"
)
# Selection mode: fixed (slot order), adaptive (CAT), hybrid (mixed)
selection_mode: Mapped[Literal["fixed", "adaptive", "hybrid"]] = mapped_column(
String(50), nullable=False, default="fixed", comment="Item selection mode"
)
# Normalization mode: static (hardcoded), dynamic (real-time), hybrid
normalization_mode: Mapped[Literal["static", "dynamic", "hybrid"]] = mapped_column(
String(50), nullable=False, default="static", comment="Normalization mode"
)
# Normalization settings
min_sample_for_dynamic: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=100,
comment="Minimum sample size for dynamic normalization",
)
static_rataan: Mapped[float] = mapped_column(
Float,
nullable=False,
default=500.0,
comment="Static mean value for manual normalization",
)
static_sb: Mapped[float] = mapped_column(
Float,
nullable=False,
default=100.0,
comment="Static standard deviation for manual normalization",
)
# AI generation settings
ai_generation_enabled: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=False,
comment="Enable/disable AI question generation",
)
# Hybrid mode settings
hybrid_transition_slot: Mapped[Union[int, None]] = mapped_column(
Integer,
nullable=True,
comment="Slot number to transition from fixed to adaptive (hybrid mode)",
)
# IRT settings
min_calibration_sample: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=100,
comment="Minimum responses needed for IRT calibration",
)
theta_estimation_method: Mapped[Literal["mle", "map", "eap"]] = mapped_column(
String(50),
nullable=False,
default="mle",
comment="Method for estimating theta",
)
fallback_to_ctt_on_error: Mapped[bool] = mapped_column(
Boolean,
nullable=False,
default=True,
comment="Fallback to CTT if IRT fails",
)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default="NOW()"
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
nullable=False,
server_default="NOW()",
onupdate="NOW()",
)
# Relationships
website: Mapped["Website"] = relationship(
"Website", back_populates="tryouts", lazy="selectin"
)
items: Mapped[list["Item"]] = relationship(
"Item", back_populates="tryout", lazy="selectin", cascade="all, delete-orphan"
)
sessions: Mapped[list["Session"]] = relationship(
"Session", back_populates="tryout", lazy="selectin", cascade="all, delete-orphan"
)
stats: Mapped["TryoutStats"] = relationship(
"TryoutStats", back_populates="tryout", lazy="selectin", uselist=False
)
# Constraints and indexes
__table_args__ = (
Index(
"ix_tryouts_website_id_tryout_id", "website_id", "tryout_id", unique=True
),
CheckConstraint("min_sample_for_dynamic > 0", "ck_min_sample_positive"),
CheckConstraint("static_rataan > 0", "ck_static_rataan_positive"),
CheckConstraint("static_sb > 0", "ck_static_sb_positive"),
CheckConstraint("min_calibration_sample > 0", "ck_min_calibration_positive"),
)
def __repr__(self) -> str:
return f"<Tryout(id={self.id}, tryout_id={self.tryout_id}, website_id={self.website_id})>"