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