185 lines
6.4 KiB
Python
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})>"
|