143 lines
4.1 KiB
Python
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)
|