from __future__ import annotations from dataclasses import dataclass from sqlalchemy import desc, select from sqlalchemy.orm import Session from fan_passport import models from fan_passport.errors import ValidationError ALLOWED_LEADERBOARD_METRICS = { "points_total", "collection_points", "quiz_points", "prediction_points", "challenge_points", "achievement_points", } @dataclass(frozen=True) class LeaderboardRow: rank: int user_id: str display_name: str country_code: str | None metric: str score: int class LeaderboardService: def __init__(self, session: Session): self.session = session def get_leaderboard( self, *, metric: str = "points_total", limit: int = 50, country_code: str | None = None, ) -> list[LeaderboardRow]: if metric not in ALLOWED_LEADERBOARD_METRICS: raise ValidationError( "Unsupported leaderboard metric.", details={"metric": metric, "supported": sorted(ALLOWED_LEADERBOARD_METRICS)}, ) limit = max(1, min(limit, 100)) metric_column = getattr(models.UserProfile, metric) stmt = select(models.UserProfile).order_by(desc(metric_column), models.UserProfile.created_at).limit( limit ) if country_code: stmt = stmt.where(models.UserProfile.country_code == country_code.upper()) profiles = self.session.scalars(stmt).all() return [ LeaderboardRow( rank=index + 1, user_id=profile.id, display_name=profile.display_name, country_code=profile.country_code, metric=metric, score=int(getattr(profile, metric)), ) for index, profile in enumerate(profiles) ]