import os import sys import torch import torch.nn.functional as F import pandas as pd import numpy as np # Path alignment sys.path.append(os.getcwd()) sys.path.append(os.path.join(os.getcwd(), 'ai-engine')) from pipeline.tiered_loader import TieredDataLoader from pipeline.sequence_builder import SequenceBuilder from models.hybrid_v11 import HybridDeepModel from features.odds_history import OddsHistoryEngine from features.synthetic_xg import SyntheticXGModel DEVICE = 'cpu' MODEL_PATH = 'ai-engine/models/v11_hybrid_model.pth' TARGET_ID = 'en78ih6ec7exnpxcku3xc3das' def audit(): print(f"🕵️ Auditing Match: {TARGET_ID}") # 1. Pipeline Data builder = SequenceBuilder() X, y, meta = builder.build_sequences() # Check if target is in dataset idx_list = meta.index[meta['match_id'] == TARGET_ID].tolist() if not idx_list: print("❌ Match not found in generated sequences. Is it too old or too new?") return idx = idx_list[0] row_meta = meta.iloc[idx] # 2. Features loader = TieredDataLoader() odds_df = loader.load_gold_data([TARGET_ID]) eng = OddsHistoryEngine() xg_model = SyntheticXGModel() # Team Mapping unique_teams = meta['team_id'].unique() team_map = {tid: i for i, tid in enumerate(unique_teams)} # 3. Predict exactly like Backtest state = torch.load(MODEL_PATH, map_location=DEVICE) emb_key = 'entity_emb.weight' if 'entity_emb.weight' in state else 'team_embedding.weight' saved_vocab_size = state[emb_key].shape[0] model = HybridDeepModel(num_teams=saved_vocab_size) new_state = {k.replace('team_embedding', 'entity_emb'): v for k, v in state.items()} model.load_state_dict(new_state, strict=False) model.eval() # Data components team_idx = team_map.get(row_meta['team_id'], 0) entities = torch.LongTensor([team_idx, 0]).unsqueeze(0) seq = torch.FloatTensor(X[idx]).unsqueeze(0) # Context (Odds + xG) odds_lookup = {} for _, r in odds_df.iterrows(): mid = r['match_id'] if mid not in odds_lookup: odds_lookup[mid] = {} if r['category'] == 'Maç Sonucu': odds_lookup[mid][r['selection']] = r['odd_value'] elif r['category'] == '2,5 Alt/Üst': if 'Üst' in r['selection']: odds_lookup[mid]['Over'] = r['odd_value'] else: odds_lookup[mid]['Under'] = r['odd_value'] odds = odds_lookup.get(TARGET_ID, {'1': 1.0, 'X': 1.0, '2': 1.0, 'Over': 1.0, 'Under': 1.0}) syn_xg = 1.35 # Placeholder in trainer for xG component if used hist_win_rate = eng.get_feature(row_meta['team_id'], float(odds.get('1', 1.0))) ctx = torch.FloatTensor([ float(odds.get('1', 1.0)), float(odds.get('X', 1.0)), float(odds.get('2', 1.0)), float(odds.get('Over', 1.0)), float(odds.get('Under', 1.0)), syn_xg, syn_xg, hist_win_rate ]).unsqueeze(0) with torch.no_grad(): logits_res, pred_goals, logits_btts, logits_ht_ft = model(entities, seq, ctx) probs = F.softmax(logits_res, dim=1).numpy()[0] prob_btts = torch.sigmoid(logits_btts).item() probs_ht = F.softmax(logits_ht_ft, dim=1).numpy()[0] print("\n📊 INTERNAL PIPELINE PREDICTION:") print(f"Target Team: {row_meta['team_id']}") print(f"1X2 Probs: Home:{probs[0]:.4f} Draw:{probs[1]:.4f} Away:{probs[2]:.4f}") print(f"BTTS Prob: {prob_btts:.4f}") ht_map = ["1/1", "1/X", "1/2", "X/1", "X/X", "X/2", "2/1", "2/X", "2/2"] top3_ht = np.argsort(probs_ht)[-3:][::-1] print("Top 3 HT/FT:") for idx_ht in top3_ht: print(f" {ht_map[idx_ht]}: {probs_ht[idx_ht]:.4f}") actual_res = y[idx][0] actual_ht_idx = int(y[idx][3]) print(f"\n✅ ACTUAL REALITY:") print(f"Result (Y): {actual_res} (0.0=Away)") print(f"HT/FT Class: {actual_ht_idx} ({ht_map[actual_ht_idx]})") if __name__ == "__main__": audit()