match-decoded / backend /model.py
AGP
Initial commit: Match Decoded — AI football explainability platform
c7db54a
Raw
History Blame Contribute Delete
3.51 kB
"""
Prediction model wrapper — loads Random Forest trained in the lab
"""
import os
import joblib
import pandas as pd
from pathlib import Path
MODELS_DIR = Path(os.path.dirname(os.path.dirname(__file__))) / "models"
class MatchPredictor:
def __init__(self):
self.model = None
self.team_stats = None
self.feature_cols = None
self._loaded = False
def load(self):
if self._loaded:
return True
try:
model_path = MODELS_DIR / "match_predictor.pkl"
data_path = MODELS_DIR / "team_data.pkl"
if not model_path.exists() or not data_path.exists():
raise FileNotFoundError(f"Model files not found in {MODELS_DIR}")
self.model = joblib.load(model_path)
data = joblib.load(data_path)
self.team_stats = data["team_stats"]
self.feature_cols = data["feature_cols"]
self._loaded = True
return True
except Exception as e:
print(f"Model load error: {e}")
return False
def predict(self, team_a: str, team_b: str, is_neutral: bool = True,
is_major_tournament: bool = True) -> dict:
if not self._loaded:
raise RuntimeError("Model not loaded")
if team_a not in self.team_stats:
raise ValueError(f"Unknown team: {team_a}")
if team_b not in self.team_stats:
raise ValueError(f"Unknown team: {team_b}")
if team_a == team_b:
raise ValueError("Teams must be different")
a = self.team_stats[team_a]
b = self.team_stats[team_b]
row = pd.DataFrame([{
"team_a_winrate": a["winrate"],
"team_b_winrate": b["winrate"],
"team_a_goal_avg": a["goal_avg"],
"team_b_goal_avg": b["goal_avg"],
"team_a_recent_form": a["recent_form"],
"team_b_recent_form": b["recent_form"],
"is_neutral": int(is_neutral),
"is_major_tournament": int(is_major_tournament),
}])[self.feature_cols]
proba = self.model.predict_proba(row)[0]
return {
"team_a": team_a,
"team_b": team_b,
"team_a_win_prob": round(float(proba[0]), 4),
"draw_prob": round(float(proba[1]), 4),
"team_b_win_prob": round(float(proba[2]), 4),
"is_neutral": is_neutral,
"is_major_tournament": is_major_tournament,
"stats_a": {
"winrate": round(a["winrate"], 4),
"goal_avg": round(a["goal_avg"], 4),
"recent_form": round(a["recent_form"], 4),
"matches_played": a["matches_played"],
},
"stats_b": {
"winrate": round(b["winrate"], 4),
"goal_avg": round(b["goal_avg"], 4),
"recent_form": round(b["recent_form"], 4),
"matches_played": b["matches_played"],
},
}
def get_team_names(self) -> list:
if not self._loaded:
return []
return sorted(self.team_stats.keys())
def get_feature_importances(self) -> list:
if not self._loaded:
return []
names = self.feature_cols
imps = self.model.feature_importances_
return [{"name": n, "importance": round(float(i), 4)}
for n, i in sorted(zip(names, imps), key=lambda x: -x[1])]
# Singleton
predictor = MatchPredictor()