diff --git a/ai-engine/models/v25_ensemble.py b/ai-engine/models/v25_ensemble.py index 968745a..57bf161 100644 --- a/ai-engine/models/v25_ensemble.py +++ b/ai-engine/models/v25_ensemble.py @@ -141,8 +141,10 @@ class V25Predictor: Each market (MS, OU25, BTTS) has its own trained models. """ - # Feature columns (82 features, NO target leakage) - FEATURE_COLS = [ + # Feature columns — loaded dynamically from feature_cols.json to stay + # in sync with the trained models. The hardcoded list below is only a + # fallback in case the JSON file is missing. + _FALLBACK_FEATURE_COLS = [ # ELO Features (8) 'home_overall_elo', 'away_overall_elo', 'elo_diff', 'home_home_elo', 'away_away_elo', @@ -178,6 +180,17 @@ class V25Predictor: 'odds_ht_ou15_o', 'odds_ht_ou15_u', 'odds_btts_y', 'odds_btts_n', + # Odds Presence Flags (20) + 'odds_ms_h_present', 'odds_ms_d_present', 'odds_ms_a_present', + 'odds_ht_ms_h_present', 'odds_ht_ms_d_present', 'odds_ht_ms_a_present', + 'odds_ou05_o_present', 'odds_ou05_u_present', + 'odds_ou15_o_present', 'odds_ou15_u_present', + 'odds_ou25_o_present', 'odds_ou25_u_present', + 'odds_ou35_o_present', 'odds_ou35_u_present', + 'odds_ht_ou05_o_present', 'odds_ht_ou05_u_present', + 'odds_ht_ou15_o_present', 'odds_ht_ou15_u_present', + 'odds_btts_y_present', 'odds_btts_n_present', + # League Features (4) 'home_xga', 'away_xga', 'league_avg_goals', 'league_zero_goal_rate', @@ -198,6 +211,24 @@ class V25Predictor: 'home_missing_impact', 'away_missing_impact', 'home_goals_form', 'away_goals_form', ] + + @staticmethod + def _load_feature_cols() -> list: + """Load feature columns from feature_cols.json, falling back to hardcoded list.""" + feature_json = os.path.join(MODELS_DIR, 'feature_cols.json') + try: + if os.path.exists(feature_json): + with open(feature_json, 'r', encoding='utf-8') as f: + cols = json.load(f) + if isinstance(cols, list) and len(cols) > 0: + print(f"[V25] Loaded {len(cols)} feature columns from feature_cols.json") + return cols + except Exception as e: + print(f"[V25] Warning: could not load feature_cols.json: {e}") + print(f"[V25] Using fallback feature columns ({len(V25Predictor._FALLBACK_FEATURE_COLS)} features)") + return V25Predictor._FALLBACK_FEATURE_COLS + + FEATURE_COLS = _load_feature_cols.__func__() # Model weights for ensemble DEFAULT_WEIGHTS = {