gg
This commit is contained in:
@@ -1,109 +0,0 @@
|
||||
|
||||
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()
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test surprise detection on known surprise matches."""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, 'ai-engine')
|
||||
from services.single_match_orchestrator import SingleMatchOrchestrator
|
||||
import json
|
||||
|
||||
# Test Bayern vs Augsburg (24 Jan 2026) - 1/2 Reversal
|
||||
match_id = 'en78ih6ec7exnpxcku3xc3das'
|
||||
|
||||
orch = SingleMatchOrchestrator()
|
||||
result = orch.analyze_match(match_id)
|
||||
|
||||
if result:
|
||||
print('=== Bayern Munch vs Augsburg (24 Jan 2026) ===')
|
||||
print('Actual: HT 1-0, FT 1-2 (1/2 Reversal!)')
|
||||
print()
|
||||
|
||||
# Check risk
|
||||
risk = result.get('risk', {})
|
||||
print(f"Risk Level: {risk.get('level', 'N/A')}")
|
||||
print(f"Is Surprise Risk: {risk.get('is_surprise_risk', False)}")
|
||||
print(f"Surprise Type: {risk.get('surprise_type', 'N/A')}")
|
||||
print(f"Risk Score: {risk.get('score', 'N/A')}")
|
||||
print()
|
||||
|
||||
# Check HT/FT probabilities from market_board
|
||||
htft = result.get('market_board', {}).get('HTFT', {}).get('probs', {})
|
||||
print('HT/FT Probabilities:')
|
||||
if htft:
|
||||
for k, v in sorted(htft.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f" {k}: {v*100:.1f}%")
|
||||
else:
|
||||
print(" EMPTY!")
|
||||
print()
|
||||
|
||||
# Check main pick
|
||||
main = result.get('main_pick', {})
|
||||
print(f"Main Pick: {main.get('market', 'N/A')} - {main.get('pick', 'N/A')}")
|
||||
print(f"Confidence: {main.get('calibrated_confidence', 'N/A')}%")
|
||||
print(f"Is Guaranteed: {main.get('is_guaranteed', False)}")
|
||||
print()
|
||||
|
||||
# Check aggressive pick
|
||||
agg = result.get('aggressive_pick', {})
|
||||
if agg:
|
||||
print(f"Aggressive Pick: {agg.get('market', 'N/A')} - {agg.get('pick', 'N/A')}")
|
||||
print(f"Odds: {agg.get('odds', 'N/A')}")
|
||||
print()
|
||||
|
||||
# Check bet_summary for HTFT
|
||||
bet_summary = result.get('bet_summary', [])
|
||||
for bet in bet_summary:
|
||||
if bet.get('market') == 'HTFT':
|
||||
print(f"HTFT Bet: {bet}")
|
||||
else:
|
||||
print('Match not found')
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test the improved surprise detection logic"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, 'ai-engine')
|
||||
|
||||
from core.calculators.risk_assessor import RiskAssessor
|
||||
from config.config_loader import get_config
|
||||
|
||||
def test_surprise_detection():
|
||||
config = get_config()
|
||||
assessor = RiskAssessor(config)
|
||||
|
||||
# Test cases based on real scenarios
|
||||
test_cases = [
|
||||
{
|
||||
'name': 'Bayern vs Augsburg (1.30 odds, 2% 1/2 prob)',
|
||||
'odds': {'ms_h': 1.30, 'ms_d': 5.00, 'ms_a': 8.00},
|
||||
'ht_ft': {'1/1': 0.30, '1/X': 0.07, '1/2': 0.02, 'X/1': 0.15, 'X/X': 0.16, 'X/2': 0.09, '2/1': 0.03, '2/X': 0.04, '2/2': 0.14},
|
||||
'expected_surprise': True,
|
||||
'expected_type': '1/2 Potential Upset'
|
||||
},
|
||||
{
|
||||
'name': 'Strong favorite (1.20 odds, 1.5% 1/2 prob)',
|
||||
'odds': {'ms_h': 1.20, 'ms_d': 6.00, 'ms_a': 12.00},
|
||||
'ht_ft': {'1/1': 0.35, '1/X': 0.05, '1/2': 0.015, 'X/1': 0.20, 'X/X': 0.15, 'X/2': 0.05, '2/1': 0.02, '2/X': 0.03, '2/2': 0.10},
|
||||
'expected_surprise': True,
|
||||
'expected_type': '1/2 Potential Upset'
|
||||
},
|
||||
{
|
||||
'name': 'Moderate favorite (1.50 odds, 3% 1/2 prob)',
|
||||
'odds': {'ms_h': 1.50, 'ms_d': 4.00, 'ms_a': 6.00},
|
||||
'ht_ft': {'1/1': 0.28, '1/X': 0.08, '1/2': 0.03, 'X/1': 0.18, 'X/X': 0.15, 'X/2': 0.08, '2/1': 0.04, '2/X': 0.05, '2/2': 0.11},
|
||||
'expected_surprise': True,
|
||||
'expected_type': '1/2 Potential Upset'
|
||||
},
|
||||
{
|
||||
'name': 'Even match (2.00 odds, 5% 1/2 prob)',
|
||||
'odds': {'ms_h': 2.00, 'ms_d': 3.30, 'ms_a': 3.30},
|
||||
'ht_ft': {'1/1': 0.20, '1/X': 0.10, '1/2': 0.05, 'X/1': 0.15, 'X/X': 0.15, 'X/2': 0.10, '2/1': 0.05, '2/X': 0.10, '2/2': 0.10},
|
||||
'expected_surprise': False, # No clear favorite
|
||||
'expected_type': None
|
||||
},
|
||||
{
|
||||
'name': 'Away favorite (1.40 away odds, 2% 2/1 prob)',
|
||||
'odds': {'ms_h': 6.00, 'ms_d': 4.00, 'ms_a': 1.40},
|
||||
'ht_ft': {'1/1': 0.10, '1/X': 0.05, '1/2': 0.04, 'X/1': 0.08, 'X/X': 0.15, 'X/2': 0.20, '2/1': 0.02, '2/X': 0.06, '2/2': 0.30},
|
||||
'expected_surprise': True,
|
||||
'expected_type': '2/1 Potential Upset'
|
||||
},
|
||||
]
|
||||
|
||||
print("=" * 70)
|
||||
print("SURPRISE DETECTION TEST RESULTS")
|
||||
print("=" * 70)
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for tc in test_cases:
|
||||
class MockCtx:
|
||||
is_surprise = False
|
||||
is_top_league = True
|
||||
sport = 'football'
|
||||
xgboost_preds = {'ht_ft': tc['ht_ft']}
|
||||
odds_data = tc['odds']
|
||||
|
||||
result = assessor.assess_risk(MockCtx())
|
||||
|
||||
# Check if result matches expectation
|
||||
is_correct = result.is_surprise_risk == tc['expected_surprise']
|
||||
if tc['expected_type'] and result.surprise_type != tc['expected_type']:
|
||||
is_correct = False
|
||||
|
||||
status = "✅ PASS" if is_correct else "❌ FAIL"
|
||||
if is_correct:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
print(f"\n{status} - {tc['name']}")
|
||||
print(f" Expected: surprise={tc['expected_surprise']}, type={tc['expected_type']}")
|
||||
print(f" Got: surprise={result.is_surprise_risk}, type={result.surprise_type}")
|
||||
if result.reasons:
|
||||
print(f" Reasons: {result.reasons}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print(f"SUMMARY: {passed} passed, {failed} failed")
|
||||
print("=" * 70)
|
||||
|
||||
return failed == 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_surprise_detection()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -1,65 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test UpsetEngine on Bayern vs Augsburg match."""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, 'ai-engine')
|
||||
from features.upset_engine import get_upset_engine
|
||||
from data.db import get_clean_dsn
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from datetime import datetime
|
||||
|
||||
# Get match data
|
||||
conn = psycopg2.connect(get_clean_dsn())
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
cur.execute("""
|
||||
SELECT m.id, m.home_team_id, m.away_team_id, m.score_home, m.score_away,
|
||||
m.ht_score_home, m.ht_score_away, m.mst_utc,
|
||||
th.name as home_name, ta.name as away_name, l.name as league
|
||||
FROM matches m
|
||||
JOIN teams th ON m.home_team_id = th.id
|
||||
JOIN teams ta ON m.away_team_id = ta.id
|
||||
JOIN leagues l ON m.league_id = l.id
|
||||
WHERE m.id = 'en78ih6ec7exnpxcku3xc3das'
|
||||
""")
|
||||
match = cur.fetchone()
|
||||
conn.close()
|
||||
|
||||
if match:
|
||||
print('=== Bayern Munch vs Augsburg (24 Jan 2026) ===')
|
||||
print(f"Actual: HT {match['ht_score_home']}-{match['ht_score_away']}, FT {match['score_home']}-{match['score_away']} (1/2 Reversal!)")
|
||||
print()
|
||||
|
||||
# Test UpsetEngine
|
||||
engine = get_upset_engine()
|
||||
|
||||
# Calculate upset potential using get_features
|
||||
result = engine.get_features(
|
||||
home_team_name=match['home_name'],
|
||||
home_team_id=match['home_team_id'],
|
||||
away_team_name=match['away_name'],
|
||||
league_name=match['league'],
|
||||
home_position=1, # Bayern is typically top
|
||||
away_position=15, # Augsburg is typically lower
|
||||
match_date_ms=match['mst_utc'],
|
||||
total_teams=18,
|
||||
)
|
||||
|
||||
print('UpsetEngine Results:')
|
||||
print(f" Atmosphere Score: {result.get('upset_atmosphere', 0):.2f}")
|
||||
print(f" Motivation Score: {result.get('upset_motivation', 0):.2f}")
|
||||
print(f" Fatigue Score: {result.get('upset_fatigue', 0):.2f}")
|
||||
print(f" Historical Score: {result.get('upset_historical', 0):.2f}")
|
||||
print(f" TOTAL UPSET POTENTIAL: {result.get('upset_potential', 0):.2f}")
|
||||
print()
|
||||
|
||||
# Check if upset was detected
|
||||
if result.get('upset_potential', 0) > 0.5:
|
||||
print("🔥 HIGH UPSET POTENTIAL DETECTED!")
|
||||
elif result.get('upset_potential', 0) > 0.3:
|
||||
print("⚠️ MEDIUM UPSET POTENTIAL")
|
||||
else:
|
||||
print("❌ LOW UPSET POTENTIAL - Model did not detect this as upset")
|
||||
else:
|
||||
print('Match not found')
|
||||
Reference in New Issue
Block a user