chartbastan/backend/app/api/v1/leaderboard.py
2026-02-01 09:31:38 +01:00

143 lines
4.1 KiB
Python

"""
Leaderboard API Routes.
This module provides REST endpoints for retrieving user rankings
and leaderboards based on prediction accuracy.
"""
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session
from app.database import get_db
from app.services.leaderboard_service import LeaderboardService
from app.schemas.leaderboard import LeaderboardResponse, LeaderboardEntry, PersonalRankData
router = APIRouter(prefix="/api/v1/leaderboard", tags=["leaderboard"])
@router.get("", response_model=LeaderboardResponse)
def get_leaderboard(
limit: int = Query(100, ge=1, le=100, description="Maximum number of users to return (max 100)"),
user_id: Optional[int] = Query(None, description="Current user ID to include personal rank data"),
db: Session = Depends(get_db)
):
"""
Get leaderboard with top 100 users sorted by accuracy.
This endpoint retrieves the top users based on their prediction accuracy
and includes personal rank data if user_id is provided.
Ranking criteria:
- Primary: Accuracy percentage (higher is better)
- Secondary: Number of predictions viewed (more is better, used as tie-breaker)
Args:
limit: Maximum number of users to return (1-100, default: 100)
user_id: Optional current user ID to include personal rank data
db: Database session (injected)
Returns:
Leaderboard with top users and optional personal rank data
Raises:
404: If user_id provided but user doesn't exist
Example Request:
GET /api/v1/leaderboard
GET /api/v1/leaderboard?user_id=1
Example Response:
{
"data": [
{
"user_id": 1,
"username": "JohnDoe",
"accuracy": 95.5,
"predictions_count": 100
},
{
"user_id": 2,
"username": "JaneSmith",
"accuracy": 90.0,
"predictions_count": 85
}
],
"personal_data": {
"rank": 42,
"accuracy": 75.5,
"predictions_count": 25
},
"meta": {
"total": 2,
"limit": 100,
"timestamp": "2026-01-18T10:30:00Z",
"version": "v1"
}
}
"""
service = LeaderboardService(db)
leaderboard = service.get_leaderboard(limit=limit)
# Get personal rank if user_id provided
personal_data = None
if user_id:
personal_rank = service.get_personal_rank(user_id)
if personal_rank:
personal_data = PersonalRankData(**personal_rank)
# Format leaderboard entries
entries = [LeaderboardEntry(**entry) for entry in leaderboard]
return {
"data": entries,
"personal_data": personal_data,
"meta": {
"total": len(entries),
"limit": limit,
"timestamp": datetime.utcnow().isoformat() + "Z",
"version": "v1"
}
}
@router.get("/personal/{user_id}", response_model=PersonalRankData)
def get_personal_rank(
user_id: int,
db: Session = Depends(get_db)
):
"""
Get personal rank data for a specific user.
Args:
user_id: ID of the user
db: Database session (injected)
Returns:
Personal rank data with rank, accuracy, and predictions count
Raises:
404: If user doesn't exist or has no completed predictions
Example Request:
GET /api/v1/leaderboard/personal/1
Example Response:
{
"rank": 42,
"accuracy": 75.5,
"predictions_count": 25
}
"""
service = LeaderboardService(db)
personal_rank = service.get_personal_rank(user_id)
if not personal_rank:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"User {user_id} not found or has no completed predictions"
)
return PersonalRankData(**personal_rank)