Spaces:
Sleeping
Sleeping
| 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=["*"], | |
| ) | |
| 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 | |
| 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 | |
| def health(): | |
| return {"status": "ok"} | |