152 lines
4.5 KiB
Python
152 lines
4.5 KiB
Python
"""
|
|
TryoutStats model for tracking tryout-level statistics.
|
|
|
|
Maintains running statistics for dynamic normalization and reporting.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Union
|
|
|
|
from sqlalchemy import CheckConstraint, DateTime, Float, ForeignKey, Index, Integer, String
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class TryoutStats(Base):
|
|
"""
|
|
TryoutStats model for maintaining tryout-level statistics.
|
|
|
|
Tracks participant counts, score distributions, and calculated
|
|
normalization parameters (rataan, sb) for dynamic normalization.
|
|
|
|
Attributes:
|
|
id: Primary key
|
|
website_id: Website identifier
|
|
tryout_id: Tryout identifier
|
|
participant_count: Number of completed sessions
|
|
total_nm_sum: Running sum of NM scores
|
|
total_nm_sq_sum: Running sum of squared NM scores (for variance calc)
|
|
rataan: Calculated mean of NM scores
|
|
sb: Calculated standard deviation of NM scores
|
|
min_nm: Minimum NM score observed
|
|
max_nm: Maximum NM score observed
|
|
last_calculated: Timestamp of last statistics update
|
|
created_at: Record creation timestamp
|
|
updated_at: Record update timestamp
|
|
tryout: Tryout relationship
|
|
"""
|
|
|
|
__tablename__ = "tryout_stats"
|
|
|
|
# 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_id: Mapped[str] = mapped_column(
|
|
String(255),
|
|
nullable=False,
|
|
index=True,
|
|
comment="Tryout identifier",
|
|
)
|
|
|
|
# Running statistics
|
|
participant_count: Mapped[int] = mapped_column(
|
|
Integer,
|
|
nullable=False,
|
|
default=0,
|
|
comment="Number of completed sessions",
|
|
)
|
|
total_nm_sum: Mapped[float] = mapped_column(
|
|
Float,
|
|
nullable=False,
|
|
default=0.0,
|
|
comment="Running sum of NM scores",
|
|
)
|
|
total_nm_sq_sum: Mapped[float] = mapped_column(
|
|
Float,
|
|
nullable=False,
|
|
default=0.0,
|
|
comment="Running sum of squared NM scores",
|
|
)
|
|
|
|
# Calculated statistics
|
|
rataan: Mapped[Union[float, None]] = mapped_column(
|
|
Float,
|
|
nullable=True,
|
|
comment="Calculated mean of NM scores",
|
|
)
|
|
sb: Mapped[Union[float, None]] = mapped_column(
|
|
Float,
|
|
nullable=True,
|
|
comment="Calculated standard deviation of NM scores",
|
|
)
|
|
|
|
# Score range
|
|
min_nm: Mapped[Union[int, None]] = mapped_column(
|
|
Integer,
|
|
nullable=True,
|
|
comment="Minimum NM score observed",
|
|
)
|
|
max_nm: Mapped[Union[int, None]] = mapped_column(
|
|
Integer,
|
|
nullable=True,
|
|
comment="Maximum NM score observed",
|
|
)
|
|
|
|
# Timestamps
|
|
last_calculated: Mapped[Union[datetime, None]] = mapped_column(
|
|
DateTime(timezone=True),
|
|
nullable=True,
|
|
comment="Timestamp of last statistics update",
|
|
)
|
|
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
|
|
tryout: Mapped["Tryout"] = relationship(
|
|
"Tryout", back_populates="stats", lazy="selectin"
|
|
)
|
|
|
|
# Constraints and indexes
|
|
__table_args__ = (
|
|
Index(
|
|
"ix_tryout_stats_website_id_tryout_id",
|
|
"website_id",
|
|
"tryout_id",
|
|
unique=True,
|
|
),
|
|
# Participant count must be non-negative
|
|
CheckConstraint("participant_count >= 0", "ck_participant_count_non_negative"),
|
|
# Min and max NM must be within valid range [0, 1000]
|
|
CheckConstraint(
|
|
"min_nm IS NULL OR (min_nm >= 0 AND min_nm <= 1000)",
|
|
"ck_min_nm_range",
|
|
),
|
|
CheckConstraint(
|
|
"max_nm IS NULL OR (max_nm >= 0 AND max_nm <= 1000)",
|
|
"ck_max_nm_range",
|
|
),
|
|
# Min must be less than or equal to max
|
|
CheckConstraint(
|
|
"min_nm IS NULL OR max_nm IS NULL OR min_nm <= max_nm",
|
|
"ck_min_max_nm_order",
|
|
),
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<TryoutStats(tryout_id={self.tryout_id}, participant_count={self.participant_count})>"
|