chartbastan/backend/app/services/user_prediction_service.py
2026-02-01 09:31:38 +01:00

217 lines
7.7 KiB
Python

"""
User Prediction Service.
This module provides business logic for tracking user predictions,
calculating ROI and accuracy rates.
"""
from datetime import datetime
from typing import List, Tuple, Optional
from sqlalchemy.orm import Session
from app.models import User, UserPrediction, Prediction, Match
class UserPredictionService:
"""
Service for managing user predictions and statistics.
This service handles:
- Tracking which predictions users have viewed
- Updating prediction results when matches complete
- Calculating user accuracy rates
- Calculating user ROI (Return on Investment)
"""
def __init__(self, db: Session):
"""Initialize service with database session."""
self.db = db
def record_prediction_view(self, user_id: int, prediction_id: int) -> UserPrediction:
"""
Record that a user viewed a prediction.
Args:
user_id: ID of the user
prediction_id: ID of the prediction viewed
Returns:
Created or existing UserPrediction record
Raises:
ValueError: If user or prediction doesn't exist
"""
# Check if user exists
user = self.db.query(User).filter(User.id == user_id).first()
if not user:
raise ValueError(f"User with id {user_id} not found")
# Check if prediction exists
prediction = self.db.query(Prediction).filter(Prediction.id == prediction_id).first()
if not prediction:
raise ValueError(f"Prediction with id {prediction_id} not found")
# Check if already viewed (unique constraint)
existing = self.db.query(UserPrediction).filter(
UserPrediction.user_id == user_id,
UserPrediction.prediction_id == prediction_id
).first()
if existing:
return existing
# Create new record
user_prediction = UserPrediction(
user_id=user_id,
prediction_id=prediction_id,
viewed_at=datetime.utcnow(),
was_correct=None # Will be set when match completes
)
self.db.add(user_prediction)
self.db.commit()
self.db.refresh(user_prediction)
return user_prediction
def get_user_prediction_history(
self,
user_id: int,
limit: int = 50,
offset: int = 0
) -> Tuple[List[dict], int]:
"""
Get a user's prediction viewing history.
Args:
user_id: ID of the user
limit: Maximum number of records to return
offset: Number of records to skip
Returns:
Tuple of (list of user predictions with details, total count)
"""
query = self.db.query(UserPrediction).filter(
UserPrediction.user_id == user_id
).order_by(UserPrediction.viewed_at.desc())
total = query.count()
user_predictions = query.limit(limit).offset(offset).all()
# Build response with full details
result = []
for up in user_predictions:
pred = up.prediction
match = pred.match if pred else None
result.append({
'id': up.id,
'user_id': up.user_id,
'prediction_id': up.prediction_id,
'viewed_at': up.viewed_at.isoformat() if up.viewed_at else None,
'was_correct': up.was_correct,
'prediction': {
'id': pred.id if pred else None,
'match_id': pred.match_id if pred else None,
'energy_score': pred.energy_score if pred else None,
'confidence': pred.confidence if pred else None,
'predicted_winner': pred.predicted_winner if pred else None,
'created_at': pred.created_at.isoformat() if pred and pred.created_at else None
} if pred else None,
'match': {
'id': match.id if match else None,
'home_team': match.home_team if match else None,
'away_team': match.away_team if match else None,
'date': match.date.isoformat() if match and match.date else None,
'league': match.league if match else None,
'status': match.status if match else None,
'actual_winner': match.actual_winner if match else None
} if match else None
})
return result, total
def update_prediction_result(self, prediction_id: int, actual_winner: Optional[str]) -> None:
"""
Update all user predictions for a given prediction with match result.
This is called when a match completes to mark which predictions were correct.
Args:
prediction_id: ID of the prediction
actual_winner: Actual winner of the match ("home", "away", "draw", or None)
"""
# Get prediction
prediction = self.db.query(Prediction).filter(Prediction.id == prediction_id).first()
if not prediction:
return
# Update match result
match = self.db.query(Match).filter(Match.id == prediction.match_id).first()
if match:
match.actual_winner = actual_winner
# Determine if prediction was correct
was_correct = False
if actual_winner:
# Normalize winner comparison
predicted = prediction.predicted_winner.lower()
actual = actual_winner.lower()
if predicted == actual:
was_correct = True
elif (predicted == 'home' and actual == 'home_team') or \
(predicted == 'away' and actual == 'away_team'):
was_correct = True
# Update all user predictions for this prediction
user_predictions = self.db.query(UserPrediction).filter(
UserPrediction.prediction_id == prediction_id
).all()
for up in user_predictions:
up.was_correct = was_correct
self.db.commit()
def get_user_stats(self, user_id: int) -> dict:
"""
Calculate user statistics including accuracy and ROI.
Args:
user_id: ID of the user
Returns:
Dictionary with statistics:
- total_predictions_viewed: Total predictions viewed
- correct_predictions: Number of correct predictions
- incorrect_predictions: Number of incorrect predictions
- accuracy_rate: Accuracy as percentage
- roi: Return on Investment in EUR
"""
user_predictions = self.db.query(UserPrediction).filter(
UserPrediction.user_id == user_id
).all()
total = len(user_predictions)
correct = sum(1 for up in user_predictions if up.was_correct is True)
incorrect = sum(1 for up in user_predictions if up.was_correct is False)
# Calculate accuracy
accuracy_rate = 0.0
if correct + incorrect > 0:
accuracy_rate = (correct / (correct + incorrect)) * 100
# Calculate ROI
# Assumptions: Each correct prediction = +100€, Each incorrect = -50€
# This is a simplified model - can be adjusted based on actual betting rules
roi = (correct * 100) - (incorrect * 50)
return {
'total_predictions_viewed': total,
'correct_predictions': correct,
'incorrect_predictions': incorrect,
'accuracy_rate': round(accuracy_rate, 1),
'roi': roi
}