242 lines
6.9 KiB
Python
242 lines
6.9 KiB
Python
"""
|
|
User Prediction API Routes.
|
|
|
|
This module provides REST endpoints for tracking user predictions
|
|
and retrieving user statistics.
|
|
"""
|
|
|
|
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.user_prediction_service import UserPredictionService
|
|
from app.schemas.user_prediction import (
|
|
UserPredictionResponse,
|
|
UserPredictionListResponse,
|
|
UserStatsResponse
|
|
)
|
|
|
|
router = APIRouter(prefix="/api/v1/user-predictions", tags=["user-predictions"])
|
|
|
|
|
|
@router.post("", status_code=status.HTTP_201_CREATED)
|
|
def record_prediction_view(
|
|
user_id: int,
|
|
prediction_id: int,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Record that a user viewed a prediction.
|
|
|
|
This endpoint tracks when a user views a prediction for ROI and accuracy calculations.
|
|
Duplicate views are ignored (unique constraint on user_id + prediction_id).
|
|
|
|
Args:
|
|
user_id: ID of the user
|
|
prediction_id: ID of the prediction viewed
|
|
db: Database session (injected)
|
|
|
|
Returns:
|
|
Created user prediction record
|
|
|
|
Raises:
|
|
404: If user or prediction doesn't exist
|
|
422: If validation fails
|
|
|
|
Example Request:
|
|
POST /api/v1/user-predictions?user_id=1&prediction_id=5
|
|
|
|
Example Response:
|
|
{
|
|
"id": 1,
|
|
"user_id": 1,
|
|
"prediction_id": 5,
|
|
"viewed_at": "2026-01-18T10:00:00Z",
|
|
"was_correct": null
|
|
}
|
|
"""
|
|
try:
|
|
service = UserPredictionService(db)
|
|
user_prediction = service.record_prediction_view(user_id, prediction_id)
|
|
return user_prediction.to_dict()
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=str(e)
|
|
)
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to record prediction view: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/history/{user_id}", response_model=UserPredictionListResponse)
|
|
def get_prediction_history(
|
|
user_id: int,
|
|
limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return (max 100)"),
|
|
offset: int = Query(0, ge=0, description="Number of records to skip"),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Get a user's prediction viewing history.
|
|
|
|
This endpoint retrieves all predictions a user has viewed, sorted by most recent.
|
|
Includes full prediction and match details.
|
|
|
|
Args:
|
|
user_id: ID of the user
|
|
limit: Maximum number of records to return (1-100, default: 50)
|
|
offset: Number of records to skip (default: 0)
|
|
db: Database session (injected)
|
|
|
|
Returns:
|
|
Paginated list of user predictions with full details
|
|
|
|
Example Request:
|
|
GET /api/v1/user-predictions/history/1?limit=10&offset=0
|
|
|
|
Example Response:
|
|
{
|
|
"data": [
|
|
{
|
|
"id": 5,
|
|
"user_id": 1,
|
|
"prediction_id": 10,
|
|
"viewed_at": "2026-01-18T10:00:00Z",
|
|
"was_correct": true,
|
|
"prediction": {
|
|
"id": 10,
|
|
"match_id": 3,
|
|
"energy_score": "high",
|
|
"confidence": "75.0%",
|
|
"predicted_winner": "PSG",
|
|
"created_at": "2026-01-17T12:00:00Z"
|
|
},
|
|
"match": {
|
|
"id": 3,
|
|
"home_team": "PSG",
|
|
"away_team": "Marseille",
|
|
"date": "2026-01-18T20:00:00Z",
|
|
"league": "Ligue 1",
|
|
"status": "completed",
|
|
"actual_winner": "PSG"
|
|
}
|
|
}
|
|
],
|
|
"meta": {
|
|
"total": 25,
|
|
"limit": 10,
|
|
"offset": 0,
|
|
"timestamp": "2026-01-18T15:30:00Z"
|
|
}
|
|
}
|
|
"""
|
|
from datetime import datetime
|
|
|
|
service = UserPredictionService(db)
|
|
predictions, total = service.get_user_prediction_history(
|
|
user_id=user_id,
|
|
limit=limit,
|
|
offset=offset
|
|
)
|
|
|
|
return {
|
|
"data": predictions,
|
|
"meta": {
|
|
"total": total,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
"timestamp": datetime.utcnow().isoformat() + "Z"
|
|
}
|
|
}
|
|
|
|
|
|
@router.get("/stats/{user_id}", response_model=UserStatsResponse)
|
|
def get_user_statistics(
|
|
user_id: int,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Get user statistics including accuracy and ROI.
|
|
|
|
This endpoint calculates:
|
|
- Total predictions viewed
|
|
- Number of correct/incorrect predictions
|
|
- Accuracy rate percentage
|
|
- ROI (Return on Investment) in EUR
|
|
|
|
Args:
|
|
user_id: ID of the user
|
|
db: Database session (injected)
|
|
|
|
Returns:
|
|
User statistics
|
|
|
|
Raises:
|
|
404: If user doesn't exist
|
|
|
|
Example Request:
|
|
GET /api/v1/user-predictions/stats/1
|
|
|
|
Example Response:
|
|
{
|
|
"total_predictions_viewed": 25,
|
|
"correct_predictions": 18,
|
|
"incorrect_predictions": 5,
|
|
"accuracy_rate": 78.3,
|
|
"roi": 1550.0
|
|
}
|
|
"""
|
|
service = UserPredictionService(db)
|
|
stats = service.get_user_stats(user_id)
|
|
|
|
return UserStatsResponse(**stats)
|
|
|
|
|
|
@router.put("/result/{prediction_id}")
|
|
def update_prediction_result(
|
|
prediction_id: int,
|
|
actual_winner: Optional[str] = Query(None, description="Actual winner: home, away, draw, or null"),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Update prediction result when match completes.
|
|
|
|
This endpoint is called when a match finishes to update all user
|
|
predictions that referenced this prediction.
|
|
|
|
Args:
|
|
prediction_id: ID of the prediction
|
|
actual_winner: Actual winner of the match (home/away/draw or null)
|
|
db: Database session (injected)
|
|
|
|
Returns:
|
|
Success message
|
|
|
|
Example Request:
|
|
PUT /api/v1/user-predictions/result/10?actual_winner=home
|
|
|
|
Example Response:
|
|
{
|
|
"message": "Prediction results updated successfully",
|
|
"prediction_id": 10,
|
|
"actual_winner": "home"
|
|
}
|
|
"""
|
|
try:
|
|
service = UserPredictionService(db)
|
|
service.update_prediction_result(prediction_id, actual_winner)
|
|
|
|
return {
|
|
"message": "Prediction results updated successfully",
|
|
"prediction_id": prediction_id,
|
|
"actual_winner": actual_winner
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Failed to update prediction result: {str(e)}"
|
|
)
|