worldcup-api / api.py
wihaha's picture
fix(api): add root health endpoint
be47437
Raw
History Blame Contribute Delete
3.1 kB
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from typing import List, Optional
from pydantic import BaseModel, Field
import os
from predict import get_predictions
app = FastAPI(title="2026 World Cup Prediction API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins for local dev; can restrict later
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def root():
return {
"status": "ok",
"service": "FDE World Cup Prediction API",
"endpoints": ["/api/health", "/api/predict"],
}
class OddsSnapshotModel(BaseModel):
bookmaker_key: Optional[str] = None
bookmaker_title: Optional[str] = None
market_key: Optional[str] = None
market_title: Optional[str] = None
home_odds: Optional[float] = None
draw_odds: Optional[float] = None
away_odds: Optional[float] = None
last_update: Optional[str] = None
class WeatherSnapshotModel(BaseModel):
forecast_time: Optional[str] = None
temperature_c: Optional[float] = None
apparent_temperature_c: Optional[float] = None
humidity_pct: Optional[float] = None
precipitation_probability_pct: Optional[float] = None
precipitation_mm: Optional[float] = None
wind_speed_kmh: Optional[float] = None
wind_gusts_kmh: Optional[float] = None
weather_code: Optional[int] = None
class PredictionModel(BaseModel):
match_id: str
home_team_id: str
away_team_id: str
prob_home_win: float
prob_draw: float
prob_away_win: float
manual_features_applied: bool = False
odds: List[OddsSnapshotModel] = Field(default_factory=list)
weather: Optional[WeatherSnapshotModel] = None
class SkippedMatchModel(BaseModel):
match_id: str
home_team_id: Optional[str] = None
away_team_id: Optional[str] = None
reason: str
class PredictionResponse(BaseModel):
predictions: List[PredictionModel]
skipped: List[SkippedMatchModel]
predictions_count: int
skipped_count: int
@app.get("/api/predict", response_model=PredictionResponse)
def predict_upcoming():
"""
Returns Win/Draw/Loss probabilities for all scheduled/active matches where
both teams are known.
"""
try:
# Resolve artifacts path relative to the current file
current_dir = os.path.dirname(os.path.abspath(__file__))
artifacts_dir = os.path.join(current_dir, "artifacts")
data = get_predictions(model_dir=artifacts_dir)
return {
"predictions": data["predictions"],
"skipped": data["skipped"],
"predictions_count": len(data["predictions"]),
"skipped_count": len(data["skipped"])
}
except FileNotFoundError as e:
raise HTTPException(status_code=503, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal prediction error: {str(e)}")
# Add a simple health check
@app.get("/api/health")
def health():
return {"status": "ok"}