110 lines
3.9 KiB
Python
Executable File
110 lines
3.9 KiB
Python
Executable File
|
|
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()
|