Spaces:
Sleeping
Sleeping
File size: 3,099 Bytes
a3d10df 336608c a3d10df be47437 336608c a3d10df 336608c a3d10df | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | 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"}
|