Spaces:
Sleeping
Sleeping
backend commit
Browse files- app/main.py +138 -9
app/main.py
CHANGED
|
@@ -10,18 +10,15 @@ import json
|
|
| 10 |
from groq import Groq
|
| 11 |
from dotenv import load_dotenv
|
| 12 |
|
| 13 |
-
# Configure logging
|
| 14 |
logging.basicConfig(level=logging.INFO)
|
| 15 |
logger = logging.getLogger(__name__)
|
| 16 |
|
| 17 |
-
# Load environment variables
|
| 18 |
load_dotenv()
|
| 19 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 20 |
if not GROQ_API_KEY:
|
| 21 |
logger.error("GROQ_API_KEY not found in environment variables.")
|
| 22 |
raise Exception("GROQ_API_KEY not found in environment variables.")
|
| 23 |
|
| 24 |
-
# Log and clear proxy environment variables
|
| 25 |
logger.info(f"HTTP_PROXY: {os.environ.get('HTTP_PROXY')}")
|
| 26 |
logger.info(f"HTTPS_PROXY: {os.environ.get('HTTPS_PROXY')}")
|
| 27 |
os.environ.pop("HTTP_PROXY", None)
|
|
@@ -29,7 +26,6 @@ os.environ.pop("HTTPS_PROXY", None)
|
|
| 29 |
os.environ.pop("NO_PROXY", None)
|
| 30 |
logger.info("Proxy environment variables cleared to prevent 'proxies' error.")
|
| 31 |
|
| 32 |
-
# Initialize Groq client
|
| 33 |
try:
|
| 34 |
client = Groq(api_key=GROQ_API_KEY)
|
| 35 |
logger.info("Groq client initialized successfully.")
|
|
@@ -39,7 +35,6 @@ except Exception as e:
|
|
| 39 |
|
| 40 |
app = FastAPI()
|
| 41 |
|
| 42 |
-
# Enable CORS
|
| 43 |
app.add_middleware(
|
| 44 |
CORSMiddleware,
|
| 45 |
allow_origins=["*"],
|
|
@@ -48,7 +43,6 @@ app.add_middleware(
|
|
| 48 |
allow_headers=["*"],
|
| 49 |
)
|
| 50 |
|
| 51 |
-
# Load datasets and country codes
|
| 52 |
try:
|
| 53 |
matches_df = pd.read_csv('data/results.csv')
|
| 54 |
goals_df = pd.read_csv('data/goalscorers.csv')
|
|
@@ -75,10 +69,17 @@ goals_df['y_coord'] = np.random.uniform(20, 80, len(goals_df)).round()
|
|
| 75 |
teams = set(matches_df['home_team'].unique()).union(set(matches_df['away_team'].unique()))
|
| 76 |
players = sorted([str(scorer) for scorer in goals_df['scorer'].dropna().unique() if pd.notna(scorer)])
|
| 77 |
|
| 78 |
-
# Skip model loading for now
|
| 79 |
logger.warning("Model loading skipped due to compatibility issues. Prediction endpoint disabled.")
|
| 80 |
|
| 81 |
def summarize_with_groq(text):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
try:
|
| 83 |
chat_completion = client.chat.completions.create(
|
| 84 |
messages=[
|
|
@@ -94,6 +95,15 @@ def summarize_with_groq(text):
|
|
| 94 |
return "Summary unavailable due to an error."
|
| 95 |
|
| 96 |
def get_team_stats(team_name):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
home_matches = matches_df[matches_df['home_team'] == team_name]
|
| 98 |
away_matches = matches_df[matches_df['away_team'] == team_name]
|
| 99 |
|
|
@@ -113,7 +123,7 @@ def get_team_stats(team_name):
|
|
| 113 |
wins = len(home_matches[home_matches['home_score'] > home_matches['away_score']]) + \
|
| 114 |
len(away_matches[away_matches['away_score'] > away_matches['home_score']])
|
| 115 |
losses = len(home_matches[home_matches['home_score'] < home_matches['away_score']]) + \
|
| 116 |
-
len(away_matches[away_matches['away_score'] <
|
| 117 |
draws = len(home_matches[home_matches['home_score'] == home_matches['away_score']]) + \
|
| 118 |
len(away_matches[away_matches['away_score'] == away_matches['home_score']])
|
| 119 |
|
|
@@ -152,18 +162,38 @@ def get_team_stats(team_name):
|
|
| 152 |
}
|
| 153 |
|
| 154 |
def get_match_goalscorers(date, home_team, away_team):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
match_goals = goals_df[(goals_df['date'] == date) &
|
| 156 |
(goals_df['home_team'] == home_team) &
|
| 157 |
(goals_df['away_team'] == away_team)]
|
| 158 |
return match_goals[['scorer', 'minute', 'team', 'own_goal', 'penalty']].to_dict('records')
|
| 159 |
|
| 160 |
def get_head_to_head_stats(team1, team2, num_matches=5):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
matches = matches_df[((matches_df['home_team'] == team1) & (matches_df['away_team'] == team2)) |
|
| 162 |
((matches_df['home_team'] == team2) & (matches_df['away_team'] == team1))]
|
| 163 |
|
| 164 |
if matches.empty:
|
| 165 |
return {"total_matches": 0, f"{team1}_wins": 0, f"{team2}_wins": 0, "draws": 0,
|
| 166 |
-
f"{team1}_goals": 0, f"{team2}
|
| 167 |
"last_matches": [], "chart": None}
|
| 168 |
|
| 169 |
total_matches = len(matches)
|
|
@@ -214,6 +244,17 @@ def get_head_to_head_stats(team1, team2, num_matches=5):
|
|
| 214 |
}
|
| 215 |
|
| 216 |
def get_player_stats(player_name):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
player_goals = goals_df[goals_df['scorer'] == player_name]
|
| 218 |
if player_goals.empty:
|
| 219 |
raise HTTPException(status_code=404, detail="Player not found")
|
|
@@ -222,10 +263,24 @@ def get_player_stats(player_name):
|
|
| 222 |
return {"player_name": player_name, "country": player_team, "total_goals": total_goals}
|
| 223 |
|
| 224 |
def predict_match_outcome(team1, team2):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
raise HTTPException(status_code=503, detail="Prediction functionality is temporarily disabled due to model loading issues.")
|
| 226 |
|
| 227 |
@app.get("/")
|
| 228 |
async def home():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
return {
|
| 230 |
"message": "Welcome to Football Prediction API",
|
| 231 |
"description": "This API provides football statistics, match predictions, and data visualizations. Note: Prediction endpoint is currently disabled.",
|
|
@@ -243,18 +298,45 @@ async def home():
|
|
| 243 |
|
| 244 |
@app.get("/teams")
|
| 245 |
async def get_teams():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
return {"teams": sorted(list(teams))}
|
| 247 |
|
| 248 |
@app.get("/players")
|
| 249 |
async def get_players():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
return {"players": players}
|
| 251 |
|
| 252 |
@app.get("/country-codes")
|
| 253 |
async def get_country_codes():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
return COUNTRY_CODE_MAP
|
| 255 |
|
| 256 |
@app.get("/team/{team_name}")
|
| 257 |
async def get_team_statistics(team_name: str, summarize: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
if team_name not in teams:
|
| 259 |
raise HTTPException(status_code=404, detail=f"Team {team_name} not found")
|
| 260 |
try:
|
|
@@ -283,6 +365,20 @@ async def get_team_statistics(team_name: str, summarize: bool = False):
|
|
| 283 |
|
| 284 |
@app.get("/head-to-head/{team1}/{team2}")
|
| 285 |
async def get_head_to_head(team1: str, team2: str, num_matches: int = 5, summarize: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 286 |
if team1 not in teams or team2 not in teams:
|
| 287 |
raise HTTPException(status_code=404, detail="One or both teams not found")
|
| 288 |
if num_matches < 0:
|
|
@@ -299,6 +395,15 @@ async def get_head_to_head(team1: str, team2: str, num_matches: int = 5, summari
|
|
| 299 |
|
| 300 |
@app.get("/player/{player_name}")
|
| 301 |
async def get_player_statistics(player_name: str, summarize: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
stats = get_player_stats(player_name)
|
| 303 |
response = stats
|
| 304 |
if summarize:
|
|
@@ -309,10 +414,34 @@ async def get_player_statistics(player_name: str, summarize: bool = False):
|
|
| 309 |
|
| 310 |
@app.get("/predict/{team1}/{team2}")
|
| 311 |
async def predict_match(team1: str, team2: str, summarize: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
raise HTTPException(status_code=503, detail="Prediction functionality is temporarily disabled due to model loading issues.")
|
| 313 |
|
| 314 |
@app.get("/goal-spatial-heatmap/{team}")
|
| 315 |
async def get_goal_spatial_heatmap(team: str, start_year: int = 2000, end_year: int = 2023, summarize: bool = False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
if team not in teams:
|
| 317 |
raise HTTPException(status_code=404, detail=f"Team {team} not found")
|
| 318 |
|
|
|
|
| 10 |
from groq import Groq
|
| 11 |
from dotenv import load_dotenv
|
| 12 |
|
|
|
|
| 13 |
logging.basicConfig(level=logging.INFO)
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
|
|
|
| 16 |
load_dotenv()
|
| 17 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
| 18 |
if not GROQ_API_KEY:
|
| 19 |
logger.error("GROQ_API_KEY not found in environment variables.")
|
| 20 |
raise Exception("GROQ_API_KEY not found in environment variables.")
|
| 21 |
|
|
|
|
| 22 |
logger.info(f"HTTP_PROXY: {os.environ.get('HTTP_PROXY')}")
|
| 23 |
logger.info(f"HTTPS_PROXY: {os.environ.get('HTTPS_PROXY')}")
|
| 24 |
os.environ.pop("HTTP_PROXY", None)
|
|
|
|
| 26 |
os.environ.pop("NO_PROXY", None)
|
| 27 |
logger.info("Proxy environment variables cleared to prevent 'proxies' error.")
|
| 28 |
|
|
|
|
| 29 |
try:
|
| 30 |
client = Groq(api_key=GROQ_API_KEY)
|
| 31 |
logger.info("Groq client initialized successfully.")
|
|
|
|
| 35 |
|
| 36 |
app = FastAPI()
|
| 37 |
|
|
|
|
| 38 |
app.add_middleware(
|
| 39 |
CORSMiddleware,
|
| 40 |
allow_origins=["*"],
|
|
|
|
| 43 |
allow_headers=["*"],
|
| 44 |
)
|
| 45 |
|
|
|
|
| 46 |
try:
|
| 47 |
matches_df = pd.read_csv('data/results.csv')
|
| 48 |
goals_df = pd.read_csv('data/goalscorers.csv')
|
|
|
|
| 69 |
teams = set(matches_df['home_team'].unique()).union(set(matches_df['away_team'].unique()))
|
| 70 |
players = sorted([str(scorer) for scorer in goals_df['scorer'].dropna().unique() if pd.notna(scorer)])
|
| 71 |
|
|
|
|
| 72 |
logger.warning("Model loading skipped due to compatibility issues. Prediction endpoint disabled.")
|
| 73 |
|
| 74 |
def summarize_with_groq(text):
|
| 75 |
+
"""Generate a concise summary of the provided text using the Groq API.
|
| 76 |
+
|
| 77 |
+
Args:
|
| 78 |
+
text (str): The text to summarize.
|
| 79 |
+
|
| 80 |
+
Returns:
|
| 81 |
+
str: A summary of the text or an error message if summarization fails.
|
| 82 |
+
"""
|
| 83 |
try:
|
| 84 |
chat_completion = client.chat.completions.create(
|
| 85 |
messages=[
|
|
|
|
| 95 |
return "Summary unavailable due to an error."
|
| 96 |
|
| 97 |
def get_team_stats(team_name):
|
| 98 |
+
"""Calculate comprehensive statistics for a specified football team.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
team_name (str): The name of the team to analyze.
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
dict: A dictionary containing team statistics including total matches, wins, losses, draws,
|
| 105 |
+
home/away matches played, tournament performance, and country code.
|
| 106 |
+
"""
|
| 107 |
home_matches = matches_df[matches_df['home_team'] == team_name]
|
| 108 |
away_matches = matches_df[matches_df['away_team'] == team_name]
|
| 109 |
|
|
|
|
| 123 |
wins = len(home_matches[home_matches['home_score'] > home_matches['away_score']]) + \
|
| 124 |
len(away_matches[away_matches['away_score'] > away_matches['home_score']])
|
| 125 |
losses = len(home_matches[home_matches['home_score'] < home_matches['away_score']]) + \
|
| 126 |
+
len(away_matches[away_matches['away_score'] < away_matches['home_score']])
|
| 127 |
draws = len(home_matches[home_matches['home_score'] == home_matches['away_score']]) + \
|
| 128 |
len(away_matches[away_matches['away_score'] == away_matches['home_score']])
|
| 129 |
|
|
|
|
| 162 |
}
|
| 163 |
|
| 164 |
def get_match_goalscorers(date, home_team, away_team):
|
| 165 |
+
"""Retrieve goalscorers for a specific match.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
date (str): The date of the match.
|
| 169 |
+
home_team (str): The home team name.
|
| 170 |
+
away_team (str): The away team name.
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
list: A list of dictionaries containing goalscorer details for the match.
|
| 174 |
+
"""
|
| 175 |
match_goals = goals_df[(goals_df['date'] == date) &
|
| 176 |
(goals_df['home_team'] == home_team) &
|
| 177 |
(goals_df['away_team'] == away_team)]
|
| 178 |
return match_goals[['scorer', 'minute', 'team', 'own_goal', 'penalty']].to_dict('records')
|
| 179 |
|
| 180 |
def get_head_to_head_stats(team1, team2, num_matches=5):
|
| 181 |
+
"""Calculate head-to-head statistics between two teams.
|
| 182 |
+
|
| 183 |
+
Args:
|
| 184 |
+
team1 (str): The first team name.
|
| 185 |
+
team2 (str): The second team name.
|
| 186 |
+
num_matches (int, optional): Number of recent matches to include. Defaults to 5.
|
| 187 |
+
|
| 188 |
+
Returns:
|
| 189 |
+
dict: A dictionary containing head-to-head stats including wins, goals, last matches, and a chart.
|
| 190 |
+
"""
|
| 191 |
matches = matches_df[((matches_df['home_team'] == team1) & (matches_df['away_team'] == team2)) |
|
| 192 |
((matches_df['home_team'] == team2) & (matches_df['away_team'] == team1))]
|
| 193 |
|
| 194 |
if matches.empty:
|
| 195 |
return {"total_matches": 0, f"{team1}_wins": 0, f"{team2}_wins": 0, "draws": 0,
|
| 196 |
+
f"{team1}_goals": 0, f"{team2}_goals": 0, "goal_difference": "Even",
|
| 197 |
"last_matches": [], "chart": None}
|
| 198 |
|
| 199 |
total_matches = len(matches)
|
|
|
|
| 244 |
}
|
| 245 |
|
| 246 |
def get_player_stats(player_name):
|
| 247 |
+
"""Retrieve statistics for a specific player.
|
| 248 |
+
|
| 249 |
+
Args:
|
| 250 |
+
player_name (str): The name of the player.
|
| 251 |
+
|
| 252 |
+
Returns:
|
| 253 |
+
dict: A dictionary containing the player's name, country, and total goals.
|
| 254 |
+
|
| 255 |
+
Raises:
|
| 256 |
+
HTTPException: If the player is not found in the dataset.
|
| 257 |
+
"""
|
| 258 |
player_goals = goals_df[goals_df['scorer'] == player_name]
|
| 259 |
if player_goals.empty:
|
| 260 |
raise HTTPException(status_code=404, detail="Player not found")
|
|
|
|
| 263 |
return {"player_name": player_name, "country": player_team, "total_goals": total_goals}
|
| 264 |
|
| 265 |
def predict_match_outcome(team1, team2):
|
| 266 |
+
"""Predict the outcome of a match between two teams.
|
| 267 |
+
|
| 268 |
+
Args:
|
| 269 |
+
team1 (str): The first team name.
|
| 270 |
+
team2 (str): The second team name.
|
| 271 |
+
|
| 272 |
+
Raises:
|
| 273 |
+
HTTPException: Always raises an exception as prediction is currently disabled.
|
| 274 |
+
"""
|
| 275 |
raise HTTPException(status_code=503, detail="Prediction functionality is temporarily disabled due to model loading issues.")
|
| 276 |
|
| 277 |
@app.get("/")
|
| 278 |
async def home():
|
| 279 |
+
"""Return a welcome message and API description.
|
| 280 |
+
|
| 281 |
+
Returns:
|
| 282 |
+
dict: A dictionary containing welcome message, description, and available endpoints.
|
| 283 |
+
"""
|
| 284 |
return {
|
| 285 |
"message": "Welcome to Football Prediction API",
|
| 286 |
"description": "This API provides football statistics, match predictions, and data visualizations. Note: Prediction endpoint is currently disabled.",
|
|
|
|
| 298 |
|
| 299 |
@app.get("/teams")
|
| 300 |
async def get_teams():
|
| 301 |
+
"""Retrieve a list of all unique teams.
|
| 302 |
+
|
| 303 |
+
Returns:
|
| 304 |
+
dict: A dictionary containing a sorted list of team names.
|
| 305 |
+
"""
|
| 306 |
return {"teams": sorted(list(teams))}
|
| 307 |
|
| 308 |
@app.get("/players")
|
| 309 |
async def get_players():
|
| 310 |
+
"""Retrieve a list of all unique players.
|
| 311 |
+
|
| 312 |
+
Returns:
|
| 313 |
+
dict: A dictionary containing a sorted list of player names.
|
| 314 |
+
"""
|
| 315 |
return {"players": players}
|
| 316 |
|
| 317 |
@app.get("/country-codes")
|
| 318 |
async def get_country_codes():
|
| 319 |
+
"""Retrieve the country code mapping.
|
| 320 |
+
|
| 321 |
+
Returns:
|
| 322 |
+
dict: A dictionary mapping team names to their country codes.
|
| 323 |
+
"""
|
| 324 |
return COUNTRY_CODE_MAP
|
| 325 |
|
| 326 |
@app.get("/team/{team_name}")
|
| 327 |
async def get_team_statistics(team_name: str, summarize: bool = False):
|
| 328 |
+
"""Get detailed statistics for a specified team.
|
| 329 |
+
|
| 330 |
+
Args:
|
| 331 |
+
team_name (str): The name of the team.
|
| 332 |
+
summarize (bool, optional): Whether to include a summary. Defaults to False.
|
| 333 |
+
|
| 334 |
+
Returns:
|
| 335 |
+
dict: A dictionary containing team statistics and optionally a summary.
|
| 336 |
+
|
| 337 |
+
Raises:
|
| 338 |
+
HTTPException: If the team is not found or stats calculation fails.
|
| 339 |
+
"""
|
| 340 |
if team_name not in teams:
|
| 341 |
raise HTTPException(status_code=404, detail=f"Team {team_name} not found")
|
| 342 |
try:
|
|
|
|
| 365 |
|
| 366 |
@app.get("/head-to-head/{team1}/{team2}")
|
| 367 |
async def get_head_to_head(team1: str, team2: str, num_matches: int = 5, summarize: bool = False):
|
| 368 |
+
"""Get head-to-head statistics between two teams.
|
| 369 |
+
|
| 370 |
+
Args:
|
| 371 |
+
team1 (str): The first team name.
|
| 372 |
+
team2 (str): The second team name.
|
| 373 |
+
num_matches (int, optional): Number of recent matches to include. Defaults to 5.
|
| 374 |
+
summarize (bool, optional): Whether to include a summary. Defaults to False.
|
| 375 |
+
|
| 376 |
+
Returns:
|
| 377 |
+
dict: A dictionary containing head-to-head statistics and optionally a summary.
|
| 378 |
+
|
| 379 |
+
Raises:
|
| 380 |
+
HTTPException: If teams are not found or num_matches is negative.
|
| 381 |
+
"""
|
| 382 |
if team1 not in teams or team2 not in teams:
|
| 383 |
raise HTTPException(status_code=404, detail="One or both teams not found")
|
| 384 |
if num_matches < 0:
|
|
|
|
| 395 |
|
| 396 |
@app.get("/player/{player_name}")
|
| 397 |
async def get_player_statistics(player_name: str, summarize: bool = False):
|
| 398 |
+
"""Get statistics for a specified player.
|
| 399 |
+
|
| 400 |
+
Args:
|
| 401 |
+
player_name (str): The name of the player.
|
| 402 |
+
summarize (bool, optional): Whether to include a summary. Defaults to False.
|
| 403 |
+
|
| 404 |
+
Returns:
|
| 405 |
+
dict: A dictionary containing player statistics and optionally a summary.
|
| 406 |
+
"""
|
| 407 |
stats = get_player_stats(player_name)
|
| 408 |
response = stats
|
| 409 |
if summarize:
|
|
|
|
| 414 |
|
| 415 |
@app.get("/predict/{team1}/{team2}")
|
| 416 |
async def predict_match(team1: str, team2: str, summarize: bool = False):
|
| 417 |
+
"""Predict the outcome of a match between two teams (currently disabled).
|
| 418 |
+
|
| 419 |
+
Args:
|
| 420 |
+
team1 (str): The first team name.
|
| 421 |
+
team2 (str): The second team name.
|
| 422 |
+
summarize (bool, optional): Whether to include a summary. Defaults to False.
|
| 423 |
+
|
| 424 |
+
Raises:
|
| 425 |
+
HTTPException: Always raises an exception as prediction is disabled.
|
| 426 |
+
"""
|
| 427 |
raise HTTPException(status_code=503, detail="Prediction functionality is temporarily disabled due to model loading issues.")
|
| 428 |
|
| 429 |
@app.get("/goal-spatial-heatmap/{team}")
|
| 430 |
async def get_goal_spatial_heatmap(team: str, start_year: int = 2000, end_year: int = 2023, summarize: bool = False):
|
| 431 |
+
"""Generate a spatial heatmap of goal distribution for a team.
|
| 432 |
+
|
| 433 |
+
Args:
|
| 434 |
+
team (str): The team name.
|
| 435 |
+
start_year (int, optional): The starting year for analysis. Defaults to 2000.
|
| 436 |
+
end_year (int, optional): The ending year for analysis. Defaults to 2023.
|
| 437 |
+
summarize (bool, optional): Whether to include a summary. Defaults to False.
|
| 438 |
+
|
| 439 |
+
Returns:
|
| 440 |
+
dict: A dictionary containing the heatmap, total goals, and average goals per match.
|
| 441 |
+
|
| 442 |
+
Raises:
|
| 443 |
+
HTTPException: If team not found, years invalid, or no goal data exists.
|
| 444 |
+
"""
|
| 445 |
if team not in teams:
|
| 446 |
raise HTTPException(status_code=404, detail=f"Team {team} not found")
|
| 447 |
|