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"}