first commit
This commit is contained in:
151
app/models/tryout_stats.py
Normal file
151
app/models/tryout_stats.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
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})>"
|
||||
Reference in New Issue
Block a user