This commit is contained in:
Executable
+100
@@ -0,0 +1,100 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
import pandas as pd
|
||||
|
||||
MATCH_ID = '3jv3r7dd46nx6cnmpqq9d4x9m'
|
||||
|
||||
def analyze_win():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# 1. Search for High Odds Match
|
||||
print("🔍 Searching for matches with Odds > 17.0 in the last 7 days...")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
m.id, m.mst_utc,
|
||||
m.score_home, m.score_away,
|
||||
m.home_team_id, m.away_team_id,
|
||||
ht.name as home_team, at.name as away_team,
|
||||
l.name as league,
|
||||
os.odd_value, os.name as selection
|
||||
FROM matches m
|
||||
JOIN teams ht ON m.home_team_id = ht.id
|
||||
JOIN teams at ON m.away_team_id = at.id
|
||||
JOIN leagues l ON m.league_id = l.id
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
JOIN odd_selections os ON oc.db_id = os.odd_category_db_id
|
||||
WHERE m.sport = 'football'
|
||||
AND m.score_home IS NOT NULL
|
||||
AND oc.name = 'Maç Sonucu'
|
||||
AND os.odd_value::numeric > 14.0
|
||||
AND m.mst_utc > (EXTRACT(EPOCH FROM NOW()) * 1000 - 7 * 24 * 60 * 60 * 1000)
|
||||
ORDER BY m.mst_utc DESC
|
||||
""")
|
||||
|
||||
matches = cursor.fetchall()
|
||||
|
||||
target_match = None
|
||||
for m in matches:
|
||||
# Check if this high odd actually won
|
||||
sel = m['selection']
|
||||
won = False
|
||||
if sel == '1' and m['score_home'] > m['score_away']: won = True
|
||||
if sel == '2' and m['score_away'] > m['score_home']: won = True
|
||||
|
||||
if won:
|
||||
target_match = m
|
||||
break
|
||||
|
||||
if not target_match:
|
||||
print("No high odds WIN found in the last 7 days.")
|
||||
return
|
||||
|
||||
print(f"\n🏆 THE MIRACLE MATCH FOUND: {target_match['id']} 🏆")
|
||||
print(f"League: {target_match['league']}")
|
||||
print(f"Match: {target_match['home_team']} vs {target_match['away_team']}")
|
||||
print(f"Score: {target_match['score_home']} - {target_match['score_away']}")
|
||||
print(f"Bet: {target_match['selection']} (Odds: {target_match['odd_value']})")
|
||||
|
||||
# Analyze Form
|
||||
print(f"\n📜 {target_match['home_team']} Recent Form:")
|
||||
cursor.execute("""
|
||||
SELECT score_home, score_away
|
||||
FROM matches
|
||||
WHERE (home_team_id = %s OR away_team_id = %s) AND mst_utc < %s
|
||||
ORDER BY mst_utc DESC LIMIT 5
|
||||
""", (target_match['home_team_id'], target_match['home_team_id'], target_match['mst_utc']))
|
||||
for gm in cursor.fetchall():
|
||||
print(f" {gm['score_home']}-{gm['score_away']}")
|
||||
|
||||
print(f"\n📜 {target_match['away_team']} Recent Form:")
|
||||
cursor.execute("""
|
||||
SELECT score_home, score_away
|
||||
FROM matches
|
||||
WHERE (home_team_id = %s OR away_team_id = %s) AND mst_utc < %s
|
||||
ORDER BY mst_utc DESC LIMIT 5
|
||||
""", (target_match['away_team_id'], target_match['away_team_id'], target_match['mst_utc']))
|
||||
for gm in cursor.fetchall():
|
||||
print(f" {gm['score_home']}-{gm['score_away']}")
|
||||
|
||||
# 3. Odds Check
|
||||
cursor.execute("""
|
||||
SELECT os.name, os.odd_value
|
||||
FROM odd_categories oc
|
||||
JOIN odd_selections os ON oc.db_id = os.odd_category_db_id
|
||||
WHERE oc.match_id = %s AND oc.name = 'Maç Sonucu'
|
||||
""", (MATCH_ID,))
|
||||
print("\n💰 Odds Table:")
|
||||
for odd in cursor.fetchall():
|
||||
print(f" {odd['name']}: {odd['odd_value']}")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_win()
|
||||
Executable
+109
@@ -0,0 +1,109 @@
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check Bayern vs Augsburg match details"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, 'ai-engine')
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
import os
|
||||
|
||||
# Read .env file manually
|
||||
db_url = None
|
||||
with open('.env') as f:
|
||||
for line in f:
|
||||
if line.startswith('DATABASE_URL'):
|
||||
db_url = line.split('=', 1)[1].strip().strip('"').strip("'")
|
||||
break
|
||||
|
||||
if not db_url:
|
||||
print("DATABASE_URL not found in .env")
|
||||
sys.exit(1)
|
||||
|
||||
# Remove schema parameter if present (psycopg2 doesn't support it)
|
||||
if "?schema=" in db_url:
|
||||
db_url = db_url.split("?schema=")[0]
|
||||
|
||||
conn = psycopg2.connect(db_url)
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# Find the match
|
||||
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, m.league_id,
|
||||
ht.name as home_team_name,
|
||||
at.name as away_team_name,
|
||||
l.name as league_name
|
||||
FROM matches m
|
||||
JOIN teams ht ON ht.id = m.home_team_id
|
||||
JOIN teams at ON at.id = m.away_team_id
|
||||
JOIN leagues l ON l.id = m.league_id
|
||||
WHERE ht.name ILIKE '%bayern%' AND at.name ILIKE '%augsburg%'
|
||||
AND m.mst_utc >= EXTRACT(EPOCH FROM '2026-01-01'::timestamp)
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
matches = cur.fetchall()
|
||||
for m in matches:
|
||||
print(f"Match ID: {m['id']}")
|
||||
print(f"Teams: {m['home_team_name']} vs {m['away_team_name']}")
|
||||
print(f"Score: HT {m['ht_score_home']}-{m['ht_score_away']}, FT {m['score_home']}-{m['score_away']}")
|
||||
print(f"Timestamp: {m['mst_utc']}")
|
||||
print(f"League: {m['league_name']}")
|
||||
print()
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
def check_enums():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT DISTINCT status FROM matches")
|
||||
statuses = [r[0] for r in cursor.fetchall()]
|
||||
print(f"Distict Statuses: {statuses}")
|
||||
|
||||
cursor.execute("SELECT DISTINCT sport FROM matches")
|
||||
sports = [r[0] for r in cursor.fetchall()]
|
||||
print(f"Distinct Sports: {sports}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_enums()
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check finished football matches with odds"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, 'ai-engine')
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Read .env file manually
|
||||
db_url = None
|
||||
with open('.env') as f:
|
||||
for line in f:
|
||||
if line.startswith('DATABASE_URL'):
|
||||
db_url = line.split('=', 1)[1].strip().strip('"').strip("'")
|
||||
break
|
||||
|
||||
if '?schema=' in db_url:
|
||||
db_url = db_url.split('?schema=')[0]
|
||||
|
||||
conn = psycopg2.connect(db_url)
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# Get matches with odds data
|
||||
cur.execute('''
|
||||
SELECT
|
||||
m.id, m.score_home, m.score_away, m.ht_score_home, m.ht_score_away,
|
||||
m.mst_utc,
|
||||
ht.name as home_team_name,
|
||||
at.name as away_team_name,
|
||||
l.name as league_name,
|
||||
o.ms_h, o.ms_d, o.ms_a
|
||||
FROM matches m
|
||||
JOIN teams ht ON ht.id = m.home_team_id
|
||||
JOIN teams at ON at.id = m.away_team_id
|
||||
JOIN leagues l ON l.id = m.league_id
|
||||
LEFT JOIN odds o ON o.match_id = m.id
|
||||
WHERE l.sport = 'football'
|
||||
AND m.score_home IS NOT NULL
|
||||
AND o.ms_h IS NOT NULL
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 30
|
||||
''')
|
||||
|
||||
matches = cur.fetchall()
|
||||
print('Last 30 finished football matches with odds:')
|
||||
print()
|
||||
|
||||
for m in matches:
|
||||
ts = m['mst_utc'] / 1000
|
||||
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
|
||||
score = 'HT: {}-{}, FT: {}-{}'.format(m['ht_score_home'], m['ht_score_away'], m['score_home'], m['score_away'])
|
||||
odds = 'Odds: H={:.2f} D={:.2f} A={:.2f}'.format(float(m['ms_h'] or 0), float(m['ms_d'] or 0), float(m['ms_a'] or 0))
|
||||
league = (m['league_name'] or 'Unknown')[:15]
|
||||
home = (m['home_team_name'] or 'Unknown')[:15]
|
||||
away = (m['away_team_name'] or 'Unknown')[:15]
|
||||
print('{} | {:15} | {:15} vs {:15} | {} | {}'.format(dt.strftime('%Y-%m-%d %H:%M'), league, home, away, score, odds))
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from datetime import datetime
|
||||
|
||||
MATCH_ID = '8hlli3zuh2q05utzcjmkca8lw' # Watford vs Portsmouth
|
||||
|
||||
def check_form():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# 1. Get Match Info
|
||||
cursor.execute("""
|
||||
SELECT m.home_team_id, m.away_team_id, m.mst_utc,
|
||||
ht.name as home_name, at.name as away_name
|
||||
FROM matches m
|
||||
JOIN teams ht ON m.home_team_id = ht.id
|
||||
JOIN teams at ON m.away_team_id = at.id
|
||||
WHERE m.id = %s
|
||||
""", (MATCH_ID,))
|
||||
|
||||
match = cursor.fetchone()
|
||||
if not match:
|
||||
print("Match not found")
|
||||
return
|
||||
|
||||
print(f"Match: {match['home_name']} vs {match['away_name']}")
|
||||
print(f"Date: {datetime.fromtimestamp(match['mst_utc']/1000).strftime('%Y-%m-%d %H:%M')}")
|
||||
|
||||
# 2. Function to get relative form
|
||||
def get_team_form(team_id, team_name, match_date_ms):
|
||||
print(f"\n📜 {team_name} Last 5 Matches BEFORE this game:")
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
m.id, m.mst_utc,
|
||||
m.score_home, m.score_away,
|
||||
m.home_team_id, m.away_team_id,
|
||||
t_opp.name as opponent_name
|
||||
FROM matches m
|
||||
JOIN teams t_opp ON (
|
||||
CASE
|
||||
WHEN m.home_team_id = %s THEN m.away_team_id
|
||||
ELSE m.home_team_id
|
||||
END = t_opp.id
|
||||
)
|
||||
WHERE (m.home_team_id = %s OR m.away_team_id = %s)
|
||||
AND m.mst_utc < %s
|
||||
AND m.score_home IS NOT NULL
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 5
|
||||
""", (team_id, team_id, team_id, match_date_ms))
|
||||
|
||||
rows = cursor.fetchall()
|
||||
for r in rows:
|
||||
is_home = (r['home_team_id'] == team_id)
|
||||
goals_for = r['score_home'] if is_home else r['score_away']
|
||||
goals_against = r['score_away'] if is_home else r['score_home']
|
||||
|
||||
result = "DRAW"
|
||||
if goals_for > goals_against: result = "WIN"
|
||||
if goals_for < goals_against: result = "LOSS"
|
||||
|
||||
loc = "(H)" if is_home else "(A)"
|
||||
date_str = datetime.fromtimestamp(r['mst_utc']/1000).strftime('%d/%m')
|
||||
|
||||
print(f" {date_str} vs {r['opponent_name'][:15]} {loc}: {goals_for}-{goals_against} [{result}]")
|
||||
|
||||
get_team_form(match['home_team_id'], match['home_name'], match['mst_utc'])
|
||||
get_team_form(match['away_team_id'], match['away_name'], match['mst_utc'])
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_form()
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
def check_count():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM matches")
|
||||
total = cursor.fetchone()[0]
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM matches WHERE ht_score_home IS NOT NULL")
|
||||
ht_count = cursor.fetchone()[0]
|
||||
|
||||
print(f"Total Matches: {total}")
|
||||
print(f"Matches with HT: {ht_count}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_count()
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
def check_live_schema():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT column_name FROM information_schema.columns WHERE table_name = 'live_matches'")
|
||||
cols = [r[0] for r in cursor.fetchall()]
|
||||
print("Columns in 'live_matches':")
|
||||
print(cols)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_live_schema()
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
import json
|
||||
|
||||
path = "ai-engine/data/mappings.json"
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
leagues = data.get('leagues', {})
|
||||
print(f"Total Leagues in Mapping: {len(leagues)}")
|
||||
print("Sample Leagues:", list(leagues.keys())[:5])
|
||||
|
||||
target_id = "e21cf135btr8t3upw0vl6n6x0"
|
||||
if target_id in leagues:
|
||||
print(f"✅ Found target league {target_id}: {leagues[target_id]}")
|
||||
else:
|
||||
print(f"❌ Target league {target_id} NOT FOUND!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
MATCH_ID = '8yl78ecnv1fqynawwtf5159uc'
|
||||
|
||||
def check():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url, cursor_factory=RealDictCursor)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT home_team_id, away_team_id, match_name FROM live_matches WHERE id = %s", (MATCH_ID,))
|
||||
row = cursor.fetchone()
|
||||
print(f"Match Raw Data: {row}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check()
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
|
||||
def check_schema():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT column_name FROM information_schema.columns WHERE table_name = 'matches'")
|
||||
cols = [r[0] for r in cursor.fetchall()]
|
||||
print("Columns in 'matches':")
|
||||
print(cols)
|
||||
|
||||
# Check specifically for HT
|
||||
ht_cols = [c for c in cols if 'ht' in c or 'half' in c]
|
||||
print(f"\nFound Halftime Columns: {ht_cols}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_schema()
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function checkSlugs() {
|
||||
console.log("🔍 Checking League Slugs...");
|
||||
|
||||
// En çok maçı olan ilk 50 ligin slug'ını getir
|
||||
const leagues = await prisma.league.findMany({
|
||||
where: { sport: 'football' },
|
||||
select: { name: true, competitionSlug: true, _count: { select: { matches: true } } },
|
||||
orderBy: { matches: { _count: 'desc' } },
|
||||
take: 50
|
||||
});
|
||||
|
||||
leagues.forEach(l => {
|
||||
console.log(`Slug: '${l.competitionSlug}' | Name: '${l.name}' | Matches: ${l._count.matches}`);
|
||||
});
|
||||
}
|
||||
|
||||
checkSlugs()
|
||||
.catch(e => console.error(e))
|
||||
.finally(async () => await prisma.$disconnect());
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
cursor.execute("SELECT event_subtype, COUNT(*) as count FROM match_player_events GROUP BY event_subtype ORDER BY count DESC LIMIT 20")
|
||||
results = cursor.fetchall()
|
||||
|
||||
print("\n--- Event Subtypes ---")
|
||||
for row in results:
|
||||
print(f"{row['event_subtype']}: {row['count']}")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check today's football matches"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, 'ai-engine')
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from datetime import datetime, timezone
|
||||
|
||||
# Read .env file manually
|
||||
db_url = None
|
||||
with open('.env') as f:
|
||||
for line in f:
|
||||
if line.startswith('DATABASE_URL'):
|
||||
db_url = line.split('=', 1)[1].strip().strip('"').strip("'")
|
||||
break
|
||||
|
||||
if '?schema=' in db_url:
|
||||
db_url = db_url.split('?schema=')[0]
|
||||
|
||||
conn = psycopg2.connect(db_url)
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# Get matches that have finished (have scores)
|
||||
cur.execute('''
|
||||
SELECT
|
||||
m.id, m.score_home, m.score_away, m.ht_score_home, m.ht_score_away,
|
||||
m.mst_utc, m.status, m.state,
|
||||
ht.name as home_team_name,
|
||||
at.name as away_team_name,
|
||||
l.name as league_name,
|
||||
l.sport as sport
|
||||
FROM matches m
|
||||
JOIN teams ht ON ht.id = m.home_team_id
|
||||
JOIN teams at ON at.id = m.away_team_id
|
||||
JOIN leagues l ON l.id = m.league_id
|
||||
WHERE l.sport = 'football'
|
||||
AND m.score_home IS NOT NULL
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 20
|
||||
''')
|
||||
|
||||
matches = cur.fetchall()
|
||||
print('Last 20 finished football matches:')
|
||||
print()
|
||||
|
||||
for m in matches:
|
||||
ts = m['mst_utc'] / 1000
|
||||
dt = datetime.fromtimestamp(ts, tz=timezone.utc)
|
||||
score = "HT: {}-{}, FT: {}-{}".format(m['ht_score_home'], m['ht_score_away'], m['score_home'], m['score_away'])
|
||||
league = (m['league_name'] or 'Unknown')[:20]
|
||||
home = (m['home_team_name'] or 'Unknown')[:20]
|
||||
away = (m['away_team_name'] or 'Unknown')[:20]
|
||||
print("{} UTC | {} | {} vs {} | {}".format(dt.strftime('%Y-%m-%d %H:%M'), league.ljust(20), home.ljust(20), away.ljust(20), score))
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def check_health():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
print("📊 Data Health Check Starting...\n")
|
||||
|
||||
# 1. Total Football Matches (Finished)
|
||||
cursor.execute("SELECT COUNT(*) as count FROM matches WHERE sport='football' AND state='postGame'")
|
||||
total_matches = cursor.fetchone()['count']
|
||||
print(f"Total Finished Football Matches: {total_matches:,}")
|
||||
|
||||
if total_matches == 0:
|
||||
return
|
||||
|
||||
# 2. Matches with Team Stats (Possession, Shots)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN match_team_stats s ON m.id = s.match_id
|
||||
WHERE m.sport='football' AND m.state='postGame'
|
||||
""")
|
||||
stats_count = cursor.fetchone()['count']
|
||||
print(f"Matches with Team Stats: {stats_count:,} ({stats_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 3. Matches with Player Events (Goals/Cards)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN match_player_events e ON m.id = e.match_id
|
||||
WHERE m.sport='football' AND m.state='postGame'
|
||||
""")
|
||||
events_count = cursor.fetchone()['count']
|
||||
print(f"Matches with Player Events: {events_count:,} ({events_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 4. Matches with Odds (1X2)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
WHERE m.sport='football' AND m.state='postGame'
|
||||
""")
|
||||
odds_count = cursor.fetchone()['count']
|
||||
print(f"Matches with Odds Data: {odds_count:,} ({odds_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 5. Full Data Set (Intersection)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN match_team_stats s ON m.id = s.match_id
|
||||
JOIN match_player_events e ON m.id = e.match_id
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
WHERE m.sport='football' AND m.state='postGame'
|
||||
""")
|
||||
full_data_count = cursor.fetchone()['count']
|
||||
print(f"\n✅ GOLDEN DATASET (All 3 present): {full_data_count:,} ({full_data_count/total_matches*100:.1f}%)")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_health()
|
||||
@@ -0,0 +1,311 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function inspectDatabase() {
|
||||
console.log('\n========================================');
|
||||
console.log('📊 VERİTABANI İNCELEME RAPORU');
|
||||
console.log('========================================\n');
|
||||
|
||||
// 1. Countries
|
||||
console.log('🌍 ÜLKELER (Countries)');
|
||||
console.log('----------------------------------------');
|
||||
const countries = await prisma.country.findMany({ take: 20 });
|
||||
console.log(`Toplam: ${await prisma.country.count()} kayıt`);
|
||||
console.table(countries);
|
||||
|
||||
// 2. Leagues
|
||||
console.log('\n🏆 LİGLER (Leagues)');
|
||||
console.log('----------------------------------------');
|
||||
const leagues = await prisma.league.findMany({
|
||||
take: 20,
|
||||
include: { country: true },
|
||||
});
|
||||
console.log(`Toplam: ${await prisma.league.count()} kayıt`);
|
||||
console.table(
|
||||
leagues.map((l) => ({
|
||||
id: l.id,
|
||||
name: l.name,
|
||||
sport: l.sport,
|
||||
country: l.country?.name || 'N/A',
|
||||
code: l.code,
|
||||
})),
|
||||
);
|
||||
|
||||
// 3. Teams
|
||||
console.log('\n👥 TAKIMLAR (Teams)');
|
||||
console.log('----------------------------------------');
|
||||
const teams = await prisma.team.findMany({ take: 30 });
|
||||
console.log(`Toplam: ${await prisma.team.count()} kayıt`);
|
||||
console.table(teams);
|
||||
|
||||
// 4. Players
|
||||
console.log('\n🏃 OYUNCULAR (Players)');
|
||||
console.log('----------------------------------------');
|
||||
const players = await prisma.player.findMany({ take: 20 });
|
||||
console.log(`Toplam: ${await prisma.player.count()} kayıt`);
|
||||
console.table(players);
|
||||
|
||||
// 5. Matches
|
||||
console.log('\n⚽ MAÇLAR (Matches)');
|
||||
console.log('----------------------------------------');
|
||||
const matchCount = await prisma.match.count();
|
||||
console.log(`Toplam: ${matchCount} kayıt`);
|
||||
|
||||
const matches = await prisma.match.findMany({
|
||||
take: 15,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
},
|
||||
});
|
||||
console.table(
|
||||
matches.map((m) => ({
|
||||
id: m.id,
|
||||
homeTeam: m.homeTeam?.name || 'N/A',
|
||||
awayTeam: m.awayTeam?.name || 'N/A',
|
||||
score: `${m.scoreHome}-${m.scoreAway}`,
|
||||
status: m.status,
|
||||
state: m.state,
|
||||
sport: m.sport,
|
||||
iddaaCode: m.iddaaCode,
|
||||
mstUtc: new Date(Number(m.mstUtc)).toISOString(),
|
||||
})),
|
||||
);
|
||||
|
||||
// 6. Live Matches
|
||||
console.log('\n🔴 CANLI MAÇLAR (Live Matches)');
|
||||
console.log('----------------------------------------');
|
||||
const liveMatchCount = await prisma.liveMatch.count();
|
||||
console.log(`Toplam: ${liveMatchCount} kayıt`);
|
||||
|
||||
const liveMatches = await prisma.liveMatch.findMany({
|
||||
take: 10,
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
},
|
||||
});
|
||||
console.table(
|
||||
liveMatches.map((m) => ({
|
||||
id: m.id,
|
||||
homeTeam: m.homeTeam?.name || 'N/A',
|
||||
awayTeam: m.awayTeam?.name || 'N/A',
|
||||
score: `${m.scoreHome}-${m.scoreAway}`,
|
||||
state: m.state,
|
||||
status: m.status,
|
||||
})),
|
||||
);
|
||||
|
||||
// 7. Match AI Features
|
||||
console.log('\n🤖 MAÇ AI ÖZELLİKLERİ (MatchAiFeatures)');
|
||||
console.log('----------------------------------------');
|
||||
const aiFeaturesCount = await prisma.matchAiFeature.count();
|
||||
console.log(`Toplam: ${aiFeaturesCount} kayıt`);
|
||||
|
||||
const aiFeatures = await prisma.matchAiFeature.findMany({ take: 10 });
|
||||
console.table(aiFeatures);
|
||||
|
||||
// 8. Odd Categories
|
||||
console.log('\n📊 ORAN KATEGORİLERİ (OddCategories)');
|
||||
console.log('----------------------------------------');
|
||||
const oddCategoriesCount = await prisma.oddCategory.count();
|
||||
console.log(`Toplam: ${oddCategoriesCount} kayıt`);
|
||||
|
||||
const oddCategories = await prisma.oddCategory.findMany({
|
||||
take: 10,
|
||||
include: { match: true },
|
||||
});
|
||||
console.table(
|
||||
oddCategories.map((oc) => ({
|
||||
id: oc.dbId,
|
||||
matchId: oc.matchId,
|
||||
name: oc.name,
|
||||
})),
|
||||
);
|
||||
|
||||
// 9. Odd Selections
|
||||
console.log('\n🎯 ORAN SEÇENEKLERİ (OddSelections)');
|
||||
console.log('----------------------------------------');
|
||||
const oddSelectionsCount = await prisma.oddSelection.count();
|
||||
console.log(`Toplam: ${oddSelectionsCount} kayıt`);
|
||||
|
||||
const oddSelections = await prisma.oddSelection.findMany({ take: 15 });
|
||||
console.table(oddSelections);
|
||||
|
||||
// 10. Predictions
|
||||
console.log('\n🔮 TAHMİNLER (Predictions)');
|
||||
console.log('----------------------------------------');
|
||||
const predictionsCount = await prisma.prediction.count();
|
||||
console.log(`Toplam: ${predictionsCount} kayıt`);
|
||||
|
||||
const predictions = await prisma.prediction.findMany({
|
||||
take: 5,
|
||||
include: { match: true },
|
||||
});
|
||||
predictions.forEach((p, i) => {
|
||||
console.log(`\nPrediction ${i + 1}:`);
|
||||
console.log(` Match: ${p.match?.matchName || p.matchId}`);
|
||||
console.log(
|
||||
` JSON:`,
|
||||
JSON.stringify(p.predictionJson, null, 2).substring(0, 500) + '...',
|
||||
);
|
||||
});
|
||||
|
||||
// 11. AI Predictions Log
|
||||
console.log('\n📝 AI TAHMİN LOGARI (AiPredictionsLog)');
|
||||
console.log('----------------------------------------');
|
||||
const aiLogCount = await prisma.aiPredictionsLog.count();
|
||||
console.log(`Toplam: ${aiLogCount} kayıt`);
|
||||
|
||||
const aiLogs = await prisma.aiPredictionsLog.findMany({
|
||||
take: 10,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
console.table(
|
||||
aiLogs.map((log) => ({
|
||||
id: log.id,
|
||||
matchId: log.matchId,
|
||||
modelVersion: log.modelVersion,
|
||||
confidence: log.confidenceScore,
|
||||
isResolved: log.isResolved,
|
||||
isCorrect: log.isCorrect,
|
||||
createdAt: log.createdAt,
|
||||
})),
|
||||
);
|
||||
|
||||
// 12. Users
|
||||
console.log('\n👤 KULLANICILAR (Users)');
|
||||
console.log('----------------------------------------');
|
||||
const usersCount = await prisma.user.count();
|
||||
console.log(`Toplam: ${usersCount} kayıt`);
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
role: true,
|
||||
subscriptionStatus: true,
|
||||
isActive: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
console.table(users);
|
||||
|
||||
// 13. User Coupons
|
||||
console.log('\n🎫 KULLANICI KUPONLARI (UserCoupons)');
|
||||
console.log('----------------------------------------');
|
||||
const couponsCount = await prisma.userCoupon.count();
|
||||
console.log(`Toplam: ${couponsCount} kayıt`);
|
||||
|
||||
const coupons = await prisma.userCoupon.findMany({
|
||||
take: 10,
|
||||
include: { user: true },
|
||||
});
|
||||
console.table(
|
||||
coupons.map((c) => ({
|
||||
id: c.id,
|
||||
user: c.user.email,
|
||||
strategy: c.strategy,
|
||||
totalOdds: c.totalOdds,
|
||||
status: c.status,
|
||||
isPublic: c.isPublic,
|
||||
})),
|
||||
);
|
||||
|
||||
// 14. Match Team Stats
|
||||
console.log('\n📈 MAÇ TAKIM İSTATİSTİKLERİ (MatchTeamStats)');
|
||||
console.log('----------------------------------------');
|
||||
const teamStatsCount = await prisma.matchTeamStats.count();
|
||||
console.log(`Toplam: ${teamStatsCount} kayıt`);
|
||||
|
||||
const teamStats = await prisma.matchTeamStats.findMany({ take: 5 });
|
||||
console.table(teamStats);
|
||||
|
||||
// 15. Match Player Stats
|
||||
console.log('\n🏀 MAÇ OYUNCU İSTATİSTİKLERİ (MatchPlayerStats)');
|
||||
console.log('----------------------------------------');
|
||||
const playerStatsCount = await prisma.matchPlayerStats.count();
|
||||
console.log(`Toplam: ${playerStatsCount} kayıt`);
|
||||
|
||||
const playerStats = await prisma.matchPlayerStats.findMany({ take: 5 });
|
||||
console.table(playerStats);
|
||||
|
||||
// 16. Match Player Events
|
||||
console.log('\n⚡ MAÇ OLAYLARI (MatchPlayerEvents)');
|
||||
console.log('----------------------------------------');
|
||||
const eventsCount = await prisma.matchPlayerEvents.count();
|
||||
console.log(`Toplam: ${eventsCount} kayıt`);
|
||||
|
||||
const events = await prisma.matchPlayerEvents.findMany({ take: 10 });
|
||||
console.table(events);
|
||||
|
||||
// 17. Official Roles
|
||||
console.log('\n👔 HAKEM ROLLERİ (OfficialRoles)');
|
||||
console.log('----------------------------------------');
|
||||
const officialRoles = await prisma.officialRole.findMany();
|
||||
console.log(`Toplam: ${officialRoles.length} kayıt`);
|
||||
console.table(officialRoles);
|
||||
|
||||
// 18. Match Officials
|
||||
console.log('\n🚨 MAÇ HAKEMLERİ (MatchOfficials)');
|
||||
console.log('----------------------------------------');
|
||||
const officialsCount = await prisma.matchOfficial.count();
|
||||
console.log(`Toplam: ${officialsCount} kayıt`);
|
||||
|
||||
const officials = await prisma.matchOfficial.findMany({ take: 10 });
|
||||
console.table(officials);
|
||||
|
||||
// 19. App Settings
|
||||
console.log('\n⚙️ UYGULAMA AYARLARI (AppSettings)');
|
||||
console.log('----------------------------------------');
|
||||
const settings = await prisma.appSetting.findMany();
|
||||
console.log(`Toplam: ${settings.length} kayıt`);
|
||||
console.table(settings);
|
||||
|
||||
// 20. Translations
|
||||
console.log('\n🌐 ÇEVRİLER (Translations)');
|
||||
console.log('----------------------------------------');
|
||||
const translationsCount = await prisma.translation.count();
|
||||
console.log(`Toplam: ${translationsCount} kayıt`);
|
||||
|
||||
const translations = await prisma.translation.findMany({ take: 10 });
|
||||
console.table(translations);
|
||||
|
||||
// Summary
|
||||
console.log('\n========================================');
|
||||
console.log('📊 ÖZET');
|
||||
console.log('========================================');
|
||||
console.log(`Ülkeler: ${await prisma.country.count()}`);
|
||||
console.log(`Ligler: ${await prisma.league.count()}`);
|
||||
console.log(`Takımlar: ${await prisma.team.count()}`);
|
||||
console.log(`Oyuncular: ${await prisma.player.count()}`);
|
||||
console.log(`Maçlar: ${await prisma.match.count()}`);
|
||||
console.log(`Canlı Maçlar: ${await prisma.liveMatch.count()}`);
|
||||
console.log(`AI Özellikleri: ${await prisma.matchAiFeature.count()}`);
|
||||
console.log(`Oran Kategorileri: ${await prisma.oddCategory.count()}`);
|
||||
console.log(`Oran Seçenekleri: ${await prisma.oddSelection.count()}`);
|
||||
console.log(`Tahminler: ${await prisma.prediction.count()}`);
|
||||
console.log(`AI Log: ${await prisma.aiPredictionsLog.count()}`);
|
||||
console.log(`Kullanıcılar: ${await prisma.user.count()}`);
|
||||
console.log(`Kuponlar: ${await prisma.userCoupon.count()}`);
|
||||
console.log(`Takım İstatistikleri: ${await prisma.matchTeamStats.count()}`);
|
||||
console.log(
|
||||
`Oyuncu İstatistikleri: ${await prisma.matchPlayerStats.count()}`,
|
||||
);
|
||||
console.log(`Olaylar: ${await prisma.matchPlayerEvents.count()}`);
|
||||
console.log(`Hakemler: ${await prisma.matchOfficial.count()}`);
|
||||
console.log(`Çeviriler: ${await prisma.translation.count()}`);
|
||||
console.log('========================================\n');
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
inspectDatabase().catch((e) => {
|
||||
console.error('Hata:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
Executable
+94
@@ -0,0 +1,94 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import psycopg2
|
||||
import pandas as pd
|
||||
from datetime import datetime
|
||||
|
||||
# Database Connection
|
||||
DSN = os.getenv('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
if '?' in DSN: DSN = DSN.split('?')[0]
|
||||
|
||||
def diagnose():
|
||||
try:
|
||||
conn = psycopg2.connect(DSN)
|
||||
cursor = conn.cursor()
|
||||
|
||||
print("🔍 DIAGNOSTIC REPORT: AI Data Coverage")
|
||||
print("=======================================")
|
||||
|
||||
# 1. Total Football Matches (Finished)
|
||||
cursor.execute("SELECT COUNT(*) FROM matches WHERE sport='football' AND score_home IS NOT NULL")
|
||||
total_matches = cursor.fetchone()[0]
|
||||
print(f"Total Finished Football Matches: {total_matches:,}")
|
||||
|
||||
if total_matches == 0:
|
||||
print("❌ No matches found!")
|
||||
return
|
||||
|
||||
# 2. Stats Coverage (match_team_stats)
|
||||
cursor.execute("SELECT COUNT(DISTINCT match_id) FROM match_team_stats")
|
||||
stats_count = cursor.fetchone()[0]
|
||||
print(f"Matches with Team Stats: {stats_count:,} ({stats_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 3. Squad Coverage (match_player_participation)
|
||||
cursor.execute("SELECT COUNT(DISTINCT match_id) FROM match_player_participation")
|
||||
squad_count = cursor.fetchone()[0]
|
||||
print(f"Matches with Lineups (Squad): {squad_count:,} ({squad_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 4. Officials Coverage
|
||||
cursor.execute("SELECT COUNT(DISTINCT match_id) FROM match_officials")
|
||||
officials_count = cursor.fetchone()[0]
|
||||
print(f"Matches with Officials: {officials_count:,} ({officials_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 5. Overlap (Gold Standard Data)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(m.id)
|
||||
FROM matches m
|
||||
JOIN match_team_stats mts ON m.id = mts.match_id
|
||||
JOIN match_player_participation mpp ON m.id = mpp.match_id
|
||||
WHERE m.sport='football' AND m.score_home IS NOT NULL
|
||||
""")
|
||||
# Note: This join might be slow on huge DB without distinct on join inputs, but distinct count matches is better logic
|
||||
# Rewrite for speed: check distinct IDs in intersection
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM (
|
||||
SELECT id FROM matches WHERE sport='football' AND score_home IS NOT NULL
|
||||
INTERSECT
|
||||
SELECT match_id FROM match_team_stats
|
||||
INTERSECT
|
||||
SELECT match_id FROM match_player_participation
|
||||
INTERSECT
|
||||
SELECT match_id FROM match_officials
|
||||
) as overlap
|
||||
""")
|
||||
overlap_count = cursor.fetchone()[0]
|
||||
print(f"Matches with ALL Data (Golden): {overlap_count:,} ({overlap_count/total_matches*100:.1f}%)")
|
||||
|
||||
print("\n🔍 RECENT DATA QUALITY (Last 1000 Matches)")
|
||||
print("==========================================")
|
||||
# Check last 1000 matches specifically
|
||||
cursor.execute("""
|
||||
WITH recent AS (
|
||||
SELECT id FROM matches
|
||||
WHERE sport='football' AND score_home IS NOT NULL
|
||||
ORDER BY mst_utc DESC LIMIT 1000
|
||||
)
|
||||
SELECT
|
||||
(SELECT COUNT(DISTINCT match_id) FROM match_team_stats WHERE match_id IN (SELECT id FROM recent)) as has_stats,
|
||||
(SELECT COUNT(DISTINCT match_id) FROM match_player_participation WHERE match_id IN (SELECT id FROM recent)) as has_squad,
|
||||
(SELECT COUNT(DISTINCT match_id) FROM match_officials WHERE match_id IN (SELECT id FROM recent)) as has_officials
|
||||
""")
|
||||
recent_stats = cursor.fetchone()
|
||||
print(f"Has Stats: {recent_stats[0]/10:.1f}%")
|
||||
print(f"Has Lineups: {recent_stats[1]/10:.1f}%")
|
||||
print(f"Has Officials: {recent_stats[2]/10:.1f}%")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
finally:
|
||||
if conn: conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
diagnose()
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
# ==============================================================================
|
||||
# Database Sample Export Script (WINDOWS VERSİYONU)
|
||||
# Bu script önemli tablolardan örnek veri çeker ve mds/DATABASE_SAMPLES.md'ye yazar
|
||||
# AI asistanların veritabanı yapısını anlaması için kullanılır
|
||||
# ==============================================================================
|
||||
|
||||
# Kullanım:
|
||||
# 1. Önce SSM port forwarding başlat: dbconnect
|
||||
# 2. Yeni PowerShell aç ve çalıştır: .\scripts\export-db-samples.ps1
|
||||
|
||||
$OutputFile = "mds\DATABASE_SAMPLES.md"
|
||||
$DbHost = "localhost"
|
||||
$DbPort = "15432"
|
||||
$DbUser = "suggestbet"
|
||||
$DbName = "boilerplate_db"
|
||||
$env:PGPASSWORD = "SuGGesT2026SecuRe"
|
||||
|
||||
# psql yolunu kontrol et
|
||||
$psqlPath = "psql"
|
||||
if (-not (Get-Command $psqlPath -ErrorAction SilentlyContinue)) {
|
||||
# PostgreSQL kurulu değilse yaygın yolları dene
|
||||
$possiblePaths = @(
|
||||
"C:\Program Files\PostgreSQL\18\bin\psql.exe",
|
||||
"C:\Program Files\PostgreSQL\17\bin\psql.exe",
|
||||
"C:\Program Files\PostgreSQL\16\bin\psql.exe",
|
||||
"C:\Program Files\PostgreSQL\15\bin\psql.exe"
|
||||
)
|
||||
foreach ($path in $possiblePaths) {
|
||||
if (Test-Path $path) {
|
||||
$psqlPath = $path
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Veritabanı bağlantı kontrolü
|
||||
try {
|
||||
$test = & $psqlPath -h $DbHost -p $DbPort -U $DbUser -d $DbName -c "SELECT 1" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) { throw "Baglanti hatasi" }
|
||||
} catch {
|
||||
Write-Host "X Veritabanina baglanilamadi!" -ForegroundColor Red
|
||||
Write-Host "Once SSM port forwarding baslat: dbconnect" -ForegroundColor Yellow
|
||||
Write-Host "PostgreSQL kurulu oldugundan emin ol" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "Veritabani ornekleri cekiliyor..." -ForegroundColor Cyan
|
||||
|
||||
$currentDate = Get-Date -Format "yyyy-MM-dd HH:mm"
|
||||
|
||||
# Helper function
|
||||
function Run-Query($query) {
|
||||
& $psqlPath -h $DbHost -p $DbPort -U $DbUser -d $DbName -t -c $query 2>$null
|
||||
}
|
||||
|
||||
# Build content as array of lines
|
||||
$lines = @()
|
||||
$lines += "# Database Sample Data"
|
||||
$lines += ""
|
||||
$lines += "Bu dosya AI asistanlarin veritabani yapisini anlamasi icin ornek veriler icerir."
|
||||
$lines += "**Son Guncelleme:** $currentDate"
|
||||
$lines += ""
|
||||
$lines += "> Bu dosya otomatik olusturulmustur. Elle duzenlemeyin."
|
||||
$lines += "> Script: ``scripts/export-db-samples.ps1``"
|
||||
$lines += ""
|
||||
$lines += "---"
|
||||
$lines += ""
|
||||
$lines += "## Tablo Istatistikleri"
|
||||
$lines += ""
|
||||
$lines += "| Tablo | Kayit Sayisi |"
|
||||
$lines += "|-------|--------------|"
|
||||
|
||||
# Tablo sayıları
|
||||
$tables = @("countries", "leagues", "teams", "players", "matches", "predictions", "odd_categories", "odd_selections", "match_team_stats", "live_matches", "users", "app_settings")
|
||||
foreach ($table in $tables) {
|
||||
$count = (Run-Query "SELECT COUNT(*) FROM $table;").Trim()
|
||||
if ($count) {
|
||||
$lines += "| $table | $count |"
|
||||
}
|
||||
}
|
||||
|
||||
$lines += ""
|
||||
$lines += "---"
|
||||
$lines += ""
|
||||
$lines += "## Matches (Son 5 Mac)"
|
||||
$lines += '```json'
|
||||
$matchesJson = Run-Query "SELECT json_agg(t) FROM (SELECT id, match_name, sport, score_home, score_away, state, to_timestamp(mst_utc/1000) as match_time FROM matches ORDER BY mst_utc DESC LIMIT 5) t;"
|
||||
$lines += $matchesJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
|
||||
$lines += "## Leagues (Ilk 10)"
|
||||
$lines += '```json'
|
||||
$leaguesJson = Run-Query "SELECT json_agg(t) FROM (SELECT id, name, sport, country_id FROM leagues LIMIT 10) t;"
|
||||
$lines += $leaguesJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
|
||||
$lines += "## Teams (Ilk 10)"
|
||||
$lines += '```json'
|
||||
$teamsJson = Run-Query "SELECT json_agg(t) FROM (SELECT id, name, sport, logo_url FROM teams LIMIT 10) t;"
|
||||
$lines += $teamsJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
|
||||
$lines += "## Countries (Ilk 10)"
|
||||
$lines += '```json'
|
||||
$countriesJson = Run-Query "SELECT json_agg(t) FROM (SELECT id, name, flag_url FROM countries LIMIT 10) t;"
|
||||
$lines += $countriesJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
|
||||
$lines += "## Predictions (Son 5)"
|
||||
$lines += '```json'
|
||||
$predictionsJson = Run-Query "SELECT json_agg(t) FROM (SELECT match_id, created_at FROM predictions ORDER BY created_at DESC LIMIT 5) t;"
|
||||
$lines += $predictionsJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
|
||||
$lines += "## Match Team Stats (Ornek 5)"
|
||||
$lines += '```json'
|
||||
$statsJson = Run-Query "SELECT json_agg(t) FROM (SELECT match_id, team_id, possession_percentage, shots_on_target, shots_off_target FROM match_team_stats LIMIT 5) t;"
|
||||
$lines += $statsJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
|
||||
$lines += "## App Settings"
|
||||
$lines += '```json'
|
||||
$settingsJson = Run-Query "SELECT json_agg(t) FROM (SELECT key, value FROM app_settings) t;"
|
||||
$lines += $settingsJson
|
||||
$lines += '```'
|
||||
$lines += ""
|
||||
$lines += "---"
|
||||
$lines += ""
|
||||
$lines += "_Bu dosya scripts/export-db-samples.ps1 tarafindan olusturulmustur._"
|
||||
|
||||
# Dosyaya yaz
|
||||
$lines | Out-File -FilePath $OutputFile -Encoding utf8
|
||||
|
||||
Write-Host "Export tamamlandi: $OutputFile" -ForegroundColor Green
|
||||
Executable
+171
@@ -0,0 +1,171 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ==============================================================================
|
||||
# Database Sample Export Script (LOKAL VERSİYON)
|
||||
# Bu script önemli tablolardan örnek veri çeker ve mds/DATABASE_SAMPLES.md'ye yazar
|
||||
# AI asistanların veritabanı yapısını anlaması için kullanılır
|
||||
# ==============================================================================
|
||||
|
||||
# Kullanım:
|
||||
# 1. Önce SSM port forwarding başlat: dbconnect
|
||||
# 2. Yeni terminal aç ve çalıştır: bash scripts/export-db-samples.sh
|
||||
|
||||
OUTPUT_FILE="mds/DATABASE_SAMPLES.md"
|
||||
DB_HOST="localhost"
|
||||
DB_PORT="15432"
|
||||
DB_USER="suggestbet"
|
||||
DB_NAME="boilerplate_db"
|
||||
PGPASSWORD="SuGGesT2026SecuRe"
|
||||
|
||||
export PGPASSWORD
|
||||
|
||||
# Veritabanı bağlantı kontrolü
|
||||
if ! psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -c "SELECT 1" > /dev/null 2>&1; then
|
||||
echo "❌ Veritabanına bağlanılamadı!"
|
||||
echo "📌 Önce SSM port forwarding başlat: dbconnect"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📊 Veritabanı örnekleri çekiliyor..."
|
||||
|
||||
# Tarih
|
||||
CURRENT_DATE=$(date '+%Y-%m-%d %H:%M')
|
||||
|
||||
# Markdown dosyasını oluştur
|
||||
cat > $OUTPUT_FILE << EOF
|
||||
# Database Sample Data
|
||||
|
||||
Bu dosya AI asistanların veritabanı yapısını anlaması için örnek veriler içerir.
|
||||
**Son Güncelleme:** $CURRENT_DATE
|
||||
|
||||
> ⚠️ Bu dosya otomatik oluşturulmuştur. Elle düzenlemeyin.
|
||||
> Script: \`scripts/export-db-samples.sh\`
|
||||
|
||||
---
|
||||
|
||||
## 📈 Tablo İstatistikleri
|
||||
|
||||
| Tablo | Kayıt Sayısı |
|
||||
|-------|-------------|
|
||||
EOF
|
||||
|
||||
# Tablo sayılarını çek
|
||||
for table in countries leagues teams players matches predictions odd_categories odd_selections match_team_stats live_matches users app_settings; do
|
||||
count=$(psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "SELECT COUNT(*) FROM $table;" 2>/dev/null | tr -d ' ')
|
||||
if [ -n "$count" ]; then
|
||||
echo "| $table | $count |" >> $OUTPUT_FILE
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" >> $OUTPUT_FILE
|
||||
echo "---" >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
# Örnek veriler
|
||||
echo "## 🏟️ Matches (Son 5 Maç)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT id, match_name, sport, score_home, score_away, state,
|
||||
to_timestamp(mst_utc/1000) as match_time
|
||||
FROM matches
|
||||
ORDER BY mst_utc DESC
|
||||
LIMIT 5
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## 🏆 Leagues (İlk 10)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT id, name, sport, country_id
|
||||
FROM leagues
|
||||
LIMIT 10
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## ⚽ Teams (İlk 10)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT id, name, sport, logo_url
|
||||
FROM teams
|
||||
LIMIT 10
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## 🌍 Countries (İlk 10)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT id, name, flag_url
|
||||
FROM countries
|
||||
LIMIT 10
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## 🎯 Predictions (Son 5)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT match_id, prediction_json, created_at
|
||||
FROM predictions
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## 📊 Match Team Stats (Örnek 5)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT match_id, team_id, possession_percentage, shots_on_target,
|
||||
shots_off_target, corners, fouls
|
||||
FROM match_team_stats
|
||||
LIMIT 5
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## 💰 Odd Categories (Örnek 5)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT db_id, match_id, name, category_json_id
|
||||
FROM odd_categories
|
||||
LIMIT 5
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## 🎰 Odd Selections (Örnek 10)" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT db_id, odd_category_db_id, name, odd_value, position
|
||||
FROM odd_selections
|
||||
LIMIT 10
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "## ⚙️ App Settings" >> $OUTPUT_FILE
|
||||
echo '```json' >> $OUTPUT_FILE
|
||||
psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d $DB_NAME -t -c "
|
||||
SELECT json_agg(t) FROM (
|
||||
SELECT key, value
|
||||
FROM app_settings
|
||||
) t;" 2>/dev/null >> $OUTPUT_FILE
|
||||
echo '```' >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
|
||||
echo "---" >> $OUTPUT_FILE
|
||||
echo "" >> $OUTPUT_FILE
|
||||
echo "_Bu dosya \`scripts/export-db-samples.sh\` tarafından oluşturulmuştur._" >> $OUTPUT_FILE
|
||||
|
||||
echo "✅ Export tamamlandı: $OUTPUT_FILE"
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
MATCH_ID = '8yl78ecnv1fqynawwtf5159uc' # Eyüpspor vs Beşiktaş
|
||||
|
||||
def fetch():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url, cursor_factory=RealDictCursor)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT score_home, score_away FROM live_matches WHERE id = %s", (MATCH_ID,))
|
||||
row = cursor.fetchone()
|
||||
print(f"Match Scores: {row}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
fetch()
|
||||
@@ -0,0 +1,214 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function findUpcomingMatch() {
|
||||
// 1 gün sonraki maçları bul (4-5 Mart 2026)
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
tomorrow.setHours(0, 0, 0, 0);
|
||||
|
||||
const dayAfter = new Date(tomorrow);
|
||||
dayAfter.setDate(dayAfter.getDate() + 1);
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('📅 1 GÜN SONRAKİ MAÇLAR');
|
||||
console.log('========================================\n');
|
||||
|
||||
// Timestamp'e çevir
|
||||
const startTs = BigInt(tomorrow.getTime());
|
||||
const endTs = BigInt(dayAfter.getTime());
|
||||
|
||||
const matches = await prisma.match.findMany({
|
||||
where: {
|
||||
sport: 'football',
|
||||
mstUtc: {
|
||||
gte: startTs,
|
||||
lt: endTs,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
oddCategories: {
|
||||
include: {
|
||||
selections: true,
|
||||
},
|
||||
},
|
||||
prediction: true,
|
||||
aiFeatures: true,
|
||||
},
|
||||
take: 5,
|
||||
orderBy: { mstUtc: 'asc' },
|
||||
});
|
||||
|
||||
console.log(`Bulunan maç sayısı: ${matches.length}`);
|
||||
|
||||
for (const match of matches) {
|
||||
console.log('\n----------------------------------------');
|
||||
console.log(
|
||||
`⚽ ${match.homeTeam?.name || 'N/A'} vs ${match.awayTeam?.name || 'N/A'}`,
|
||||
);
|
||||
console.log(`🏆 Lig: ${match.league?.name || 'N/A'}`);
|
||||
console.log(`📅 Tarih: ${new Date(Number(match.mstUtc)).toISOString()}`);
|
||||
console.log(`🔢 İddaa Kodu: ${match.iddaaCode || 'N/A'}`);
|
||||
console.log(`🏟️ Durum: ${match.state} / ${match.status}`);
|
||||
|
||||
// Oranlar
|
||||
console.log('\n📊 ORANLAR:');
|
||||
for (const cat of match.oddCategories.slice(0, 5)) {
|
||||
console.log(` ${cat.name}:`);
|
||||
for (const sel of cat.selections.slice(0, 5)) {
|
||||
console.log(` - ${sel.name}: ${sel.oddValue}`);
|
||||
}
|
||||
}
|
||||
|
||||
// AI Features
|
||||
if (match.aiFeatures) {
|
||||
console.log('\n🤖 AI ÖZELLİKLER:');
|
||||
console.log(` Home ELO: ${match.aiFeatures.homeElo}`);
|
||||
console.log(` Away ELO: ${match.aiFeatures.awayElo}`);
|
||||
console.log(` Home Form: ${match.aiFeatures.homeFormScore}`);
|
||||
console.log(` Away Form: ${match.aiFeatures.awayFormScore}`);
|
||||
}
|
||||
|
||||
// Tahmin
|
||||
if (match.prediction) {
|
||||
console.log('\n🔮 MEVCUT TAHMİN:');
|
||||
console.log(
|
||||
JSON.stringify(match.prediction.predictionJson, null, 2).substring(
|
||||
0,
|
||||
500,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Detaylı analiz için ilk maçı seç
|
||||
if (matches.length > 0) {
|
||||
const selectedMatch = matches[0];
|
||||
console.log('\n\n========================================');
|
||||
console.log('🎯 SEÇİLEN MAÇ DETAY ANALİZİ');
|
||||
console.log('========================================');
|
||||
console.log(`Maç ID: ${selectedMatch.id}`);
|
||||
|
||||
// Bu maç için ne kadar verimiz var?
|
||||
console.log('\n📈 VERİ KALİTESİ ANALİZİ:');
|
||||
console.log(
|
||||
` - Oran Kategorisi: ${selectedMatch.oddCategories.length} adet`,
|
||||
);
|
||||
|
||||
let totalSelections = 0;
|
||||
for (const cat of selectedMatch.oddCategories) {
|
||||
totalSelections += cat.selections.length;
|
||||
}
|
||||
console.log(` - Toplam Oran Seçeneği: ${totalSelections} adet`);
|
||||
console.log(` - AI Features: ${selectedMatch.aiFeatures ? 'VAR' : 'YOK'}`);
|
||||
console.log(` - Prediction: ${selectedMatch.prediction ? 'VAR' : 'YOK'}`);
|
||||
|
||||
// Bu takımların geçmiş maçları
|
||||
console.log('\n📚 TAKIM GEÇMİŞİ:');
|
||||
|
||||
// Ev sahibi takımın son maçları
|
||||
const homeTeamMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ homeTeamId: selectedMatch.homeTeamId },
|
||||
{ awayTeamId: selectedMatch.homeTeamId },
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
},
|
||||
take: 5,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
console.log(`\n ${selectedMatch.homeTeam?.name} Son 5 Maç:`);
|
||||
for (const m of homeTeamMatches) {
|
||||
const isHome = m.homeTeamId === selectedMatch.homeTeamId;
|
||||
const goalsFor = isHome ? m.scoreHome : m.scoreAway;
|
||||
const goalsAgainst = isHome ? m.scoreAway : m.scoreHome;
|
||||
const result =
|
||||
goalsFor > goalsAgainst ? 'W' : goalsFor < goalsAgainst ? 'L' : 'D';
|
||||
console.log(
|
||||
` ${result} ${m.homeTeam?.name} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Deplasman takımının son maçları
|
||||
const awayTeamMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ homeTeamId: selectedMatch.awayTeamId },
|
||||
{ awayTeamId: selectedMatch.awayTeamId },
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
},
|
||||
take: 5,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
console.log(`\n ${selectedMatch.awayTeam?.name} Son 5 Maç:`);
|
||||
for (const m of awayTeamMatches) {
|
||||
const isHome = m.homeTeamId === selectedMatch.awayTeamId;
|
||||
const goalsFor = isHome ? m.scoreHome : m.scoreAway;
|
||||
const goalsAgainst = isHome ? m.scoreAway : m.scoreHome;
|
||||
const result =
|
||||
goalsFor > goalsAgainst ? 'W' : goalsFor < goalsAgainst ? 'L' : 'D';
|
||||
console.log(
|
||||
` ${result} ${m.homeTeam?.name} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Head-to-Head
|
||||
console.log('\n🔄 HEAD-TO-HEAD (Karşılıklı):');
|
||||
const h2hMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
homeTeamId: selectedMatch.homeTeamId,
|
||||
awayTeamId: selectedMatch.awayTeamId,
|
||||
},
|
||||
{
|
||||
homeTeamId: selectedMatch.awayTeamId,
|
||||
awayTeamId: selectedMatch.homeTeamId,
|
||||
},
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
},
|
||||
take: 5,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
if (h2hMatches.length > 0) {
|
||||
for (const m of h2hMatches) {
|
||||
console.log(
|
||||
` ${m.homeTeam?.name} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(' Karşılıklı maç bulunamadı');
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
findUpcomingMatch().catch((e) => {
|
||||
console.error('Hata:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find surprise matches from database."""
|
||||
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# Use the same DSN as the main project
|
||||
conn = psycopg2.connect(os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db'))
|
||||
cur = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
# Bayern Munich vs Augsburg - 24 Jan 2026
|
||||
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 th.name ILIKE '%bayern%' AND ta.name ILIKE '%augsburg%'
|
||||
AND m.mst_utc >= EXTRACT(EPOCH FROM '2026-01-20'::timestamp) * 1000
|
||||
AND m.mst_utc <= EXTRACT(EPOCH FROM '2026-01-30'::timestamp) * 1000
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
print('=== Bayern vs Augsburg (24 Jan 2026) ===')
|
||||
for row in cur.fetchall():
|
||||
match_date = datetime.fromtimestamp(row['mst_utc'] / 1000)
|
||||
print(f"ID: {row['id']}")
|
||||
print(f"Match: {row['home_name']} vs {row['away_name']}")
|
||||
print(f"Score: {row['score_home']}-{row['score_away']} (HT: {row['ht_score_home']}-{row['ht_score_away']})")
|
||||
print(f"Date: {match_date}")
|
||||
print(f"League: {row['league']}")
|
||||
print()
|
||||
|
||||
# Benfica vs Real Madrid - 18 Feb 2026
|
||||
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 th.name ILIKE '%benfica%' AND ta.name ILIKE '%real madrid%'
|
||||
AND m.mst_utc >= EXTRACT(EPOCH FROM '2026-02-15'::timestamp) * 1000
|
||||
AND m.mst_utc <= EXTRACT(EPOCH FROM '2026-02-20'::timestamp) * 1000
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
print('=== Benfica vs Real Madrid (18 Feb 2026) ===')
|
||||
for row in cur.fetchall():
|
||||
match_date = datetime.fromtimestamp(row['mst_utc'] / 1000)
|
||||
print(f"ID: {row['id']}")
|
||||
print(f"Match: {row['home_name']} vs {row['away_name']}")
|
||||
print(f"Score: {row['score_home']}-{row['score_away']} (HT: {row['ht_score_home']}-{row['ht_score_away']})")
|
||||
print(f"Date: {match_date}")
|
||||
print(f"League: {row['league']}")
|
||||
print()
|
||||
|
||||
# Find all 1/2 and 2/1 HT/FT results in recent matches
|
||||
cur.execute("""
|
||||
SELECT m.id, m.score_home, m.score_away, m.ht_score_home, m.ht_score_away,
|
||||
th.name as home_name, ta.name as away_name, l.name as league, m.mst_utc
|
||||
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.mst_utc >= EXTRACT(EPOCH FROM '2026-01-01'::timestamp) * 1000
|
||||
AND m.score_home IS NOT NULL AND m.score_away IS NOT NULL
|
||||
AND m.ht_score_home IS NOT NULL AND m.ht_score_away IS NOT NULL
|
||||
AND (
|
||||
(m.ht_score_home > m.ht_score_away AND m.score_home < m.score_away) -- 1/2 reversal
|
||||
OR (m.ht_score_home < m.ht_score_away AND m.score_home > m.score_away) -- 2/1 reversal
|
||||
)
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 30
|
||||
""")
|
||||
print('=== Recent HT/FT Reversals (1/2 or 2/1) ===')
|
||||
for row in cur.fetchall():
|
||||
match_date = datetime.fromtimestamp(row['mst_utc'] / 1000)
|
||||
ht_result = "1" if row['ht_score_home'] > row['ht_score_away'] else ("2" if row['ht_score_home'] < row['ht_score_away'] else "X")
|
||||
ft_result = "1" if row['score_home'] > row['score_away'] else ("2" if row['score_home'] < row['score_away'] else "X")
|
||||
print(f"{row['home_name']} vs {row['away_name']} | HT: {row['ht_score_home']}-{row['ht_score_away']} FT: {row['score_home']}-{row['score_away']} | {ht_result}/{ft_result} | {match_date}")
|
||||
|
||||
conn.close()
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def find_teams():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url, cursor_factory=RealDictCursor)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT id, name FROM teams WHERE name ILIKE '%Eyüp%' OR name ILIKE '%Beşiktaş%'")
|
||||
rows = cursor.fetchall()
|
||||
print("Found Teams:")
|
||||
for r in rows:
|
||||
print(f"ID: {r['id']} | Name: {r['name']}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
find_teams()
|
||||
Executable
+132
@@ -0,0 +1,132 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.optim as optim
|
||||
from torch.utils.data import DataLoader, TensorDataset
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
# Path alignment
|
||||
sys.path.append(os.getcwd())
|
||||
sys.path.append(os.path.join(os.getcwd(), 'ai-engine'))
|
||||
|
||||
from models.hybrid_v11 import HybridDeepModel
|
||||
from pipeline.sequence_builder import SequenceBuilder
|
||||
from pipeline.tiered_loader import TieredDataLoader
|
||||
|
||||
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
MODEL_PATH = 'ai-engine/models/v11_hybrid_model.pth'
|
||||
LEARNING_RATE = 1e-4 # Lower for fine-tuning
|
||||
EPOCHS = 10 # More focus on small set
|
||||
|
||||
def fine_tune():
|
||||
print("🧠 Starting Error-Driven Fine-Tuning (Last 3 Days)...")
|
||||
|
||||
# 1. Build Sequences
|
||||
builder = SequenceBuilder()
|
||||
X, y, meta = builder.build_sequences()
|
||||
|
||||
# Current is Jan 27. Filter after Jan 24.
|
||||
# relative to Max Timestamp in meta
|
||||
max_ts = meta['date'].max()
|
||||
three_days_ms = 3 * 24 * 60 * 60 * 1000
|
||||
filter_ts = max_ts - three_days_ms
|
||||
|
||||
mask = meta['date'] >= filter_ts
|
||||
X_recent = X[mask]
|
||||
y_recent = y[mask]
|
||||
meta_recent = meta[mask]
|
||||
|
||||
if len(X_recent) == 0:
|
||||
print("❌ No recent matches found to fine-tune on!")
|
||||
return
|
||||
|
||||
print(f"✅ Found {len(X_recent)} recent samples for fine-tuning.")
|
||||
|
||||
# 3. Loader
|
||||
# We need Odds/Context for these
|
||||
loader = TieredDataLoader()
|
||||
# For speed in this script, we'll use average context if full loader is too slow
|
||||
# But let's try to get real context
|
||||
from features.odds_history import OddsHistoryEngine
|
||||
eng = OddsHistoryEngine()
|
||||
|
||||
# Pre-build context
|
||||
ctx_list = []
|
||||
print("📊 Building Context for recent matches...")
|
||||
for i, row in meta_recent.iterrows():
|
||||
# Get odds (simulated or real from DB)
|
||||
# Using 1.5 - 3.0 - 2.5 as baseline if not found
|
||||
ctx_list.append([2.0, 3.2, 2.5, 1.8, 1.8, 1.35, 1.35, eng.get_feature(row['team_id'], 2.0)])
|
||||
|
||||
X_tensor = torch.FloatTensor(X_recent).to(DEVICE)
|
||||
y_tensor = torch.FloatTensor(y_recent).to(DEVICE)
|
||||
ctx_tensor = torch.FloatTensor(ctx_list).to(DEVICE)
|
||||
|
||||
# Entity Mapping
|
||||
unique_teams = meta['team_id'].unique()
|
||||
team_map = {tid: i for i, tid in enumerate(unique_teams)}
|
||||
entities_list = [[team_map.get(row['team_id'], 0), 0] for _, row in meta_recent.iterrows()]
|
||||
entities_tensor = torch.LongTensor(entities_list).to(DEVICE)
|
||||
|
||||
# 4. Load Model
|
||||
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).to(DEVICE)
|
||||
new_state = {k.replace('team_embedding', 'entity_emb'): v for k, v in state.items()}
|
||||
model.load_state_dict(new_state, strict=False)
|
||||
model.train()
|
||||
|
||||
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
|
||||
|
||||
# Weights
|
||||
# 0=Home, 1=Draw, 2=Away
|
||||
# High weight for Draw (1.5) and Miracle turnarounds (x10)
|
||||
class_weights = torch.FloatTensor([1.0, 2.0, 1.0]).to(DEVICE) # More Draw focus
|
||||
ht_weights = torch.FloatTensor([1.0, 1.0, 10.0, 1.0, 1.0, 1.0, 10.0, 1.0, 1.0]).to(DEVICE)
|
||||
|
||||
crit_res = nn.CrossEntropyLoss(weight=class_weights)
|
||||
crit_ht = nn.CrossEntropyLoss(weight=ht_weights)
|
||||
crit_goals = nn.MSELoss()
|
||||
|
||||
dataset = TensorDataset(entities_tensor, X_tensor, ctx_tensor, y_tensor)
|
||||
train_loader = DataLoader(dataset, batch_size=32, shuffle=True)
|
||||
|
||||
print(f"🚀 Fine-tuning for {EPOCHS} epochs...")
|
||||
for epoch in range(EPOCHS):
|
||||
total_loss = 0
|
||||
for b_ent, b_seq, b_ctx, b_y in train_loader:
|
||||
optimizer.zero_grad()
|
||||
|
||||
l_res, p_goals, l_btts, l_ht = model(b_ent, b_seq, b_ctx)
|
||||
|
||||
# 1X2 Loss
|
||||
target_res = b_y[:, 0].long()
|
||||
loss_res = crit_res(l_res, target_res)
|
||||
|
||||
# Goals Loss
|
||||
target_goals = (b_y[:, 1] + b_y[:, 2]).unsqueeze(1)
|
||||
loss_goals = crit_goals(p_goals, target_goals)
|
||||
|
||||
# HT/FT Loss
|
||||
target_ht = b_y[:, 3].long()
|
||||
loss_ht = crit_ht(l_ht, target_ht)
|
||||
|
||||
loss = loss_res + loss_goals + (0.5 * loss_ht)
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
total_loss += loss.item()
|
||||
|
||||
print(f" Epoch {epoch+1}/{EPOCHS} | Loss: {total_loss/len(train_loader):.4f}")
|
||||
|
||||
# 5. Save
|
||||
print(f"💾 Saving fine-tuned model to {MODEL_PATH}")
|
||||
torch.save(model.state_dict(), MODEL_PATH)
|
||||
print("✅ Fine-tuning complete.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
fine_tune()
|
||||
@@ -0,0 +1,457 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* GLM-5 Tahmin Yaklaşımı
|
||||
* ======================
|
||||
* Bir maç için eldeki veriler:
|
||||
* - Takım isimleri (ev sahibi, deplasman)
|
||||
* - Oranlar (MS, Alt/Üst, BTTS, DC)
|
||||
* - Sakatlar/Cezalılar
|
||||
* - Lig
|
||||
* - Hakem
|
||||
*
|
||||
* Hedef: Maç sonucunu tahmin etmek (1/X/2)
|
||||
*/
|
||||
|
||||
async function glm5PredictionApproach() {
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ GLM-5 TAHMİN YAKLAŞIMI - VERİTABANI ANALİZİ ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
console.log('\n');
|
||||
|
||||
// =====================================
|
||||
// BÖLÜM 1: VERİTABANI HAZİNESİ
|
||||
// =====================================
|
||||
console.log('📦 BÖLÜM 1: VERİTABANI HAZİNESİ');
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
const stats = {
|
||||
matches: await prisma.match.count({
|
||||
where: { sport: 'football', state: 'postGame' },
|
||||
}),
|
||||
odds: await prisma.oddSelection.count(),
|
||||
events: await prisma.matchPlayerEvents.count(),
|
||||
teamStats: await prisma.matchTeamStats.count(),
|
||||
officials: await prisma.matchOfficial.count(),
|
||||
};
|
||||
|
||||
console.log(`\nKullanılabilir Veri Miktarı:`);
|
||||
console.log(` ⚽ ${stats.matches.toLocaleString()} bitmiş futbol maçı`);
|
||||
console.log(` 📊 ${stats.odds.toLocaleString()} oran kaydı`);
|
||||
console.log(
|
||||
` ⚡ ${stats.events.toLocaleString()} maç olayı (gol, kart, değişiklik)`,
|
||||
);
|
||||
console.log(` 📈 ${stats.teamStats.toLocaleString()} takım istatistiği`);
|
||||
console.log(` 👨⚖️ ${stats.officials.toLocaleString()} hakem kaydı`);
|
||||
|
||||
// =====================================
|
||||
// BÖLÜM 2: TAHMİN FAKTÖRLERİ
|
||||
// =====================================
|
||||
console.log('\n\n🧠 BÖLÜM 2: TAHMİN FAKTÖRLERİ');
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
console.log(`
|
||||
Bir maç sonucunu etkileyen faktörler ve veritabanından nasıl çıkarılır:
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ FAKTÖR │ VERİTABANI KAYNAĞI │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ 1. Takım Gücü │ Son maçlarda gol atma/yeme ortalaması │
|
||||
│ 2. Ev/Deplasman Avantajı │ Ev sahibi %52, Deplasman %28, Berabere %20│
|
||||
│ 3. Form Durumu │ Son 5 maçta alınan puanlar │
|
||||
│ 4. Oranlar │ Bookmaker'ın fiyatlaması (implied prob) │
|
||||
│ 5. Sakat/Cezalı │ sidelined_data JSON alanı │
|
||||
│ 6. Hakem Etkisi │ Hakemin istatistikleri │
|
||||
│ 7. Lig Özelliği │ Gol ortalamaları, ev avantajı │
|
||||
│ 8. Head-to-Head │ Karşılıklı geçmiş maçlar │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
`);
|
||||
|
||||
// =====================================
|
||||
// BÖLÜM 3: ÖRNEK ANALİZ
|
||||
// =====================================
|
||||
console.log('\n\n🎯 BÖLÜM 3: ÖRNEK MAÇ ANALİZİ');
|
||||
console.log('─'.repeat(50));
|
||||
|
||||
// preGame durumundaki bir maç bul
|
||||
const match = await prisma.match.findFirst({
|
||||
where: { sport: 'football', state: 'preGame' },
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
oddCategories: { include: { selections: true } },
|
||||
officials: { include: { role: true } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
// Son bitmiş maçı kullan
|
||||
const finishedMatch = await prisma.match.findFirst({
|
||||
where: { sport: 'football', state: 'postGame', scoreHome: { not: null } },
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
oddCategories: { include: { selections: true } },
|
||||
officials: { include: { role: true } },
|
||||
},
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
if (finishedMatch) {
|
||||
await analyzeMatch(finishedMatch, true);
|
||||
}
|
||||
} else {
|
||||
await analyzeMatch(match, false);
|
||||
}
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
async function analyzeMatch(match, isFinished) {
|
||||
console.log(`\n⚽ MAÇ: ${match.homeTeam?.name} vs ${match.awayTeam?.name}`);
|
||||
console.log(`🏆 Lig: ${match.league?.name}`);
|
||||
console.log(`📅 Tarih: ${new Date(Number(match.mstUtc)).toISOString()}`);
|
||||
if (isFinished) {
|
||||
console.log(`📊 Gerçek Skor: ${match.scoreHome} - ${match.scoreAway}`);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// ADIM 1: ORANLARI OKU VE IMPLIED PROBABILITY HESAPLA
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n📊 ADIM 1: ORAN ANALİZİ');
|
||||
console.log('─'.repeat(40));
|
||||
|
||||
const odds = {};
|
||||
for (const cat of match.oddCategories) {
|
||||
for (const sel of cat.selections) {
|
||||
if (cat.name?.includes('Maç Sonucu') || cat.name === 'MS') {
|
||||
if (sel.name === '1') odds.ms_home = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') odds.ms_draw = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') odds.ms_away = parseFloat(sel.oddValue);
|
||||
}
|
||||
if (cat.name?.includes('2,5') || cat.name?.includes('2.5')) {
|
||||
if (sel.name === 'Alt') odds.ou25_under = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Üst') odds.ou25_over = parseFloat(sel.oddValue);
|
||||
}
|
||||
if (cat.name?.includes('Karşılıklı') || cat.name?.includes('KG')) {
|
||||
if (sel.name === 'Var' || sel.name === 'Evet')
|
||||
odds.btts_yes = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Yok' || sel.name === 'Hayır')
|
||||
odds.btts_no = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implied probability hesapla
|
||||
if (odds.ms_home && odds.ms_draw && odds.ms_away) {
|
||||
const rawHome = (1 / odds.ms_home) * 100;
|
||||
const rawDraw = (1 / odds.ms_draw) * 100;
|
||||
const rawAway = (1 / odds.ms_away) * 100;
|
||||
const total = rawHome + rawDraw + rawAway;
|
||||
|
||||
console.log(
|
||||
`\nOranlar: 1=${odds.ms_home} | X=${odds.ms_draw} | 2=${odds.ms_away}`,
|
||||
);
|
||||
console.log(
|
||||
`Ham Implied Probability: 1=%${rawHome.toFixed(1)} | X=%${rawDraw.toFixed(1)} | 2=%${rawAway.toFixed(1)}`,
|
||||
);
|
||||
console.log(`Bookmaker Margin: %${(total - 100).toFixed(1)}`);
|
||||
|
||||
// Normalize edilmiş
|
||||
const normHome = (rawHome / total) * 100;
|
||||
const normDraw = (rawDraw / total) * 100;
|
||||
const normAway = (rawAway / total) * 100;
|
||||
console.log(
|
||||
`Normalize: 1=%${normHome.toFixed(1)} | X=%${normDraw.toFixed(1)} | 2=%${normAway.toFixed(1)}`,
|
||||
);
|
||||
|
||||
odds.normHome = normHome;
|
||||
odds.normDraw = normDraw;
|
||||
odds.normAway = normAway;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// ADIM 2: TAKIM GEÇMİŞ PERFORMANSI
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n📈 ADIM 2: TAKIM PERFORMANS ANALİZİ');
|
||||
console.log('─'.repeat(40));
|
||||
|
||||
// Ev sahibi son 10 maç
|
||||
const homeMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [{ homeTeamId: match.homeTeamId }, { awayTeamId: match.homeTeamId }],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 10,
|
||||
});
|
||||
|
||||
const homeStats = calculateTeamStats(homeMatches, match.homeTeamId);
|
||||
console.log(`\n🏠 ${match.homeTeam?.name}:`);
|
||||
console.log(
|
||||
` Son 10: ${homeStats.wins}G ${homeStats.draws}B ${homeStats.losses}M`,
|
||||
);
|
||||
console.log(
|
||||
` Gol: ${homeStats.goalsFor} attı, ${homeStats.goalsAgainst} yedi`,
|
||||
);
|
||||
console.log(` Ortalama: ${(homeStats.goalsFor / 10).toFixed(2)} gol/maç`);
|
||||
|
||||
// Deplasman son 10 maç
|
||||
const awayMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [{ homeTeamId: match.awayTeamId }, { awayTeamId: match.awayTeamId }],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 10,
|
||||
});
|
||||
|
||||
const awayStats = calculateTeamStats(awayMatches, match.awayTeamId);
|
||||
console.log(`\n✈️ ${match.awayTeam?.name}:`);
|
||||
console.log(
|
||||
` Son 10: ${awayStats.wins}G ${awayStats.draws}B ${awayStats.losses}M`,
|
||||
);
|
||||
console.log(
|
||||
` Gol: ${awayStats.goalsFor} attı, ${awayStats.goalsAgainst} yedi`,
|
||||
);
|
||||
console.log(` Ortalama: ${(awayStats.goalsFor / 10).toFixed(2)} gol/maç`);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// ADIM 3: HAKEM ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n👨⚖️ ADIM 3: HAKEM ANALİZİ');
|
||||
console.log('─'.repeat(40));
|
||||
|
||||
const mainReferee = match.officials?.find(
|
||||
(o) => o.role?.name === 'Orta Hakem',
|
||||
);
|
||||
if (mainReferee) {
|
||||
console.log(`\nHakem: ${mainReferee.name}`);
|
||||
|
||||
// Bu hakemin yönettiği maçları bul
|
||||
const refereeMatches = await prisma.matchOfficial.findMany({
|
||||
where: { name: mainReferee.name, roleId: 1 },
|
||||
include: { match: true },
|
||||
take: 20,
|
||||
});
|
||||
|
||||
if (refereeMatches.length > 0) {
|
||||
let homeWins = 0,
|
||||
draws = 0,
|
||||
awayWins = 0;
|
||||
let totalCards = 0;
|
||||
|
||||
for (const rm of refereeMatches) {
|
||||
if (rm.match?.scoreHome !== null && rm.match?.scoreAway !== null) {
|
||||
if (rm.match.scoreHome > rm.match.scoreAway) homeWins++;
|
||||
else if (rm.match.scoreHome < rm.match.scoreAway) awayWins++;
|
||||
else draws++;
|
||||
}
|
||||
}
|
||||
|
||||
const total = homeWins + draws + awayWins;
|
||||
if (total > 0) {
|
||||
console.log(` Yönettiği maçlar: ${total}`);
|
||||
console.log(
|
||||
` Ev sahibi kazanma: %${((homeWins / total) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(` Beraberlik: %${((draws / total) * 100).toFixed(1)}`);
|
||||
console.log(
|
||||
` Deplasman kazanma: %${((awayWins / total) * 100).toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('Hakem bilgisi yok');
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// ADIM 4: LİG ÖZELLİKLERİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n🏆 ADIM 4: LİG ANALİZİ');
|
||||
console.log('─'.repeat(40));
|
||||
|
||||
const leagueMatches = await prisma.match.findMany({
|
||||
where: { leagueId: match.leagueId, sport: 'football', state: 'postGame' },
|
||||
take: 100,
|
||||
});
|
||||
|
||||
let leagueHomeWins = 0,
|
||||
leagueDraws = 0,
|
||||
leagueAwayWins = 0;
|
||||
let leagueGoals = 0;
|
||||
|
||||
for (const lm of leagueMatches) {
|
||||
if (lm.scoreHome !== null && lm.scoreAway !== null) {
|
||||
leagueGoals += lm.scoreHome + lm.scoreAway;
|
||||
if (lm.scoreHome > lm.scoreAway) leagueHomeWins++;
|
||||
else if (lm.scoreHome < lm.scoreAway) leagueAwayWins++;
|
||||
else leagueDraws++;
|
||||
}
|
||||
}
|
||||
|
||||
const leagueTotal = leagueHomeWins + leagueDraws + leagueAwayWins;
|
||||
if (leagueTotal > 0) {
|
||||
console.log(`\nLig: ${match.league?.name}`);
|
||||
console.log(
|
||||
` Ev sahibi kazanma: %${((leagueHomeWins / leagueTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Beraberlik: %${((leagueDraws / leagueTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Deplasman kazanma: %${((leagueAwayWins / leagueTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Ortalama gol: ${(leagueGoals / leagueTotal).toFixed(2)}/maç`,
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// ADIM 5: GLM-5 TAHMİN MODELİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n\n🤖 ADIM 5: GLM-5 TAHMİN MODELİ');
|
||||
console.log('═'.repeat(50));
|
||||
|
||||
// Ağırlıklar
|
||||
const weights = {
|
||||
odds: 0.4, // Bookmaker en güvenilir
|
||||
form: 0.25, // Son performans
|
||||
homeAdvantage: 0.15, // Ev sahibi avantajı
|
||||
league: 0.1, // Lig eğilimleri
|
||||
referee: 0.1, // Hakem etkisi
|
||||
};
|
||||
|
||||
console.log(`\nAğırlıklar:`);
|
||||
console.log(` Oranlar: %${weights.odds * 100}`);
|
||||
console.log(` Form: %${weights.form * 100}`);
|
||||
console.log(` Ev Avantajı: %${weights.homeAdvantage * 100}`);
|
||||
console.log(` Lig: %${weights.league * 100}`);
|
||||
console.log(` Hakem: %${weights.referee * 100}`);
|
||||
|
||||
// Base skorlar (oranlardan)
|
||||
let homeScore = odds.normHome || 33;
|
||||
let drawScore = odds.normDraw || 33;
|
||||
let awayScore = odds.normAway || 33;
|
||||
|
||||
// Form düzeltmesi
|
||||
const homeFormScore = ((homeStats.wins * 3 + homeStats.draws) / 30) * 100;
|
||||
const awayFormScore = ((awayStats.wins * 3 + awayStats.draws) / 30) * 100;
|
||||
const formDiff = homeFormScore - awayFormScore;
|
||||
|
||||
console.log(`\nForm Skorları:`);
|
||||
console.log(` Ev Sahibi: ${homeFormScore.toFixed(1)}`);
|
||||
console.log(` Deplasman: ${awayFormScore.toFixed(1)}`);
|
||||
console.log(` Fark: ${formDiff.toFixed(1)} (ev lehine pozitif)`);
|
||||
|
||||
// Ev sahibi avantajı (genel istatistik)
|
||||
const homeAdvantageBonus = 8; // %8 ev sahibi avantajı
|
||||
|
||||
// Final hesaplama
|
||||
homeScore =
|
||||
(odds.normHome || 33) * weights.odds +
|
||||
homeFormScore * 0.5 * weights.form +
|
||||
homeAdvantageBonus * weights.homeAdvantage +
|
||||
(leagueHomeWins / leagueTotal) * 100 * weights.league;
|
||||
|
||||
awayScore =
|
||||
(odds.normAway || 33) * weights.odds +
|
||||
awayFormScore * 0.5 * weights.form +
|
||||
0 * weights.homeAdvantage + // Deplasman avantajı yok
|
||||
(leagueAwayWins / leagueTotal) * 100 * weights.league;
|
||||
|
||||
drawScore =
|
||||
(odds.normDraw || 33) * weights.odds +
|
||||
(100 - Math.abs(formDiff)) * 0.1 * weights.form +
|
||||
(leagueDraws / leagueTotal) * 100 * weights.league;
|
||||
|
||||
// Normalize
|
||||
const totalScore = homeScore + drawScore + awayScore;
|
||||
const finalHome = (homeScore / totalScore) * 100;
|
||||
const finalDraw = (drawScore / totalScore) * 100;
|
||||
const finalAway = (awayScore / totalScore) * 100;
|
||||
|
||||
console.log(`\n🎯 FINAL TAHMİN:`);
|
||||
console.log('─'.repeat(40));
|
||||
console.log(` 1 (Ev Sahibi): %${finalHome.toFixed(1)}`);
|
||||
console.log(` X (Beraberlik): %${finalDraw.toFixed(1)}`);
|
||||
console.log(` 2 (Deplasman): %${finalAway.toFixed(1)}`);
|
||||
|
||||
// Kazanan belirle
|
||||
let prediction, confidence;
|
||||
if (finalHome > finalDraw && finalHome > finalAway) {
|
||||
prediction = '1';
|
||||
confidence = finalHome;
|
||||
} else if (finalAway > finalDraw) {
|
||||
prediction = '2';
|
||||
confidence = finalAway;
|
||||
} else {
|
||||
prediction = 'X';
|
||||
confidence = finalDraw;
|
||||
}
|
||||
|
||||
console.log(`\n🏆 TAHMİN: ${prediction}`);
|
||||
console.log(` Güven: %${confidence.toFixed(1)}`);
|
||||
|
||||
// Alt/Üst tahmini
|
||||
const avgGoals =
|
||||
(homeStats.goalsFor +
|
||||
homeStats.goalsAgainst +
|
||||
awayStats.goalsFor +
|
||||
awayStats.goalsAgainst) /
|
||||
20;
|
||||
const ou25Prediction = avgGoals > 2.5 ? 'ÜST' : 'ALT';
|
||||
console.log(`\n⚽ 2.5 ${ou25Prediction} (Ort: ${avgGoals.toFixed(2)} gol)`);
|
||||
|
||||
if (isFinished) {
|
||||
console.log(`\n✅ GERÇEK SONUÇ: ${match.scoreHome} - ${match.scoreAway}`);
|
||||
const actual =
|
||||
match.scoreHome > match.scoreAway
|
||||
? '1'
|
||||
: match.scoreHome < match.scoreAway
|
||||
? '2'
|
||||
: 'X';
|
||||
console.log(` Tahmin ${prediction === actual ? 'DOĞRU ✓' : 'YANLIŞ ✗'}`);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateTeamStats(matches, teamId) {
|
||||
let wins = 0,
|
||||
draws = 0,
|
||||
losses = 0;
|
||||
let goalsFor = 0,
|
||||
goalsAgainst = 0;
|
||||
|
||||
for (const m of matches) {
|
||||
const isHome = m.homeTeamId === teamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
|
||||
goalsFor += gf || 0;
|
||||
goalsAgainst += ga || 0;
|
||||
|
||||
if (gf > ga) wins++;
|
||||
else if (gf < ga) losses++;
|
||||
else draws++;
|
||||
}
|
||||
|
||||
return { wins, draws, losses, goalsFor, goalsAgainst };
|
||||
}
|
||||
|
||||
glm5PredictionApproach().catch(console.error);
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def inspect_counts():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
print("\n🔍 Detailed Match Counts Investigation\n")
|
||||
|
||||
# 1. Total by Sport
|
||||
print("--- Total by Sport ---")
|
||||
cursor.execute("SELECT sport, COUNT(*) as count FROM matches GROUP BY sport")
|
||||
for row in cursor.fetchall():
|
||||
print(f"{row['sport']}: {row['count']:,}")
|
||||
|
||||
# 2. Football Matches by State
|
||||
print("\n--- Football Matches by State ---")
|
||||
cursor.execute("SELECT state, COUNT(*) as count FROM matches WHERE sport='football' GROUP BY state ORDER BY count DESC")
|
||||
for row in cursor.fetchall():
|
||||
state_display = row['state'] if row['state'] else "NULL"
|
||||
print(f"{state_display}: {row['count']:,}")
|
||||
|
||||
# 3. Total Football Matches (All States)
|
||||
cursor.execute("SELECT COUNT(*) as count FROM matches WHERE sport='football'")
|
||||
total_football = cursor.fetchone()['count']
|
||||
print(f"\nTOTAL FOOTBALL MATCHES (All States): {total_football:,}")
|
||||
|
||||
# 4. Check for 'postGame' equivalent states
|
||||
# Sometimes 'FT', 'Ended', 'finished' might be used
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_counts()
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def inspect_deep_intersection():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
print("\n🔍 DEEP DATA INTERSECTION CHECK\n")
|
||||
|
||||
# 1. Base: Scored Football Matches
|
||||
base_query_from = "FROM matches m"
|
||||
base_query_where = "WHERE m.sport='football' AND m.score_home IS NOT NULL"
|
||||
cursor.execute(f"SELECT COUNT(*) as count {base_query_from} {base_query_where}")
|
||||
total = cursor.fetchone()['count']
|
||||
print(f"Total Scored Matches: {total:,}")
|
||||
|
||||
# 2. Check Individual Coverage
|
||||
tables = {
|
||||
"Team Stats": "JOIN match_team_stats s ON m.id = s.match_id",
|
||||
"Player Events": "JOIN match_player_events e ON m.id = e.match_id",
|
||||
"Odds": "JOIN odd_categories oc ON m.id = oc.match_id",
|
||||
"Officials (Referees)": "JOIN match_officials mo ON m.id = mo.match_id",
|
||||
"Player Stats (Detailed)": "JOIN match_player_stats ps ON m.id = ps.match_id"
|
||||
}
|
||||
|
||||
for name, join_clause in tables.items():
|
||||
cursor.execute(f"SELECT COUNT(DISTINCT m.id) as count {base_query_from} {join_clause} {base_query_where}")
|
||||
count = cursor.fetchone()['count']
|
||||
print(f"With {name}: {count:,} ({count/total*100:.1f}%)")
|
||||
|
||||
# 3. The User's "All-in-One" Question (Golden 28k + Officials)
|
||||
print("\n--- Intersections ---")
|
||||
|
||||
# Previous Golden (Stats + Events + Odds)
|
||||
cursor.execute(f"""
|
||||
SELECT COUNT(DISTINCT m.id) as count {base_query_from}
|
||||
JOIN match_team_stats s ON m.id = s.match_id
|
||||
JOIN match_player_events e ON m.id = e.match_id
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
{base_query_where}
|
||||
""")
|
||||
golden = cursor.fetchone()['count']
|
||||
print(f"Golden (Stats + Events + Odds): {golden:,}")
|
||||
|
||||
# Golden + Officials
|
||||
cursor.execute(f"""
|
||||
SELECT COUNT(DISTINCT m.id) as count {base_query_from}
|
||||
JOIN match_team_stats s ON m.id = s.match_id
|
||||
JOIN match_player_events e ON m.id = e.match_id
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
JOIN match_officials mo ON m.id = mo.match_id
|
||||
{base_query_where}
|
||||
""")
|
||||
platinum = cursor.fetchone()['count']
|
||||
print(f"Platinum (Golden + Officials): {platinum:,}")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_deep_intersection()
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def inspect_odds():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
print("\n🔍 ODDS COVERAGE INSPECTION\n")
|
||||
|
||||
# 1. Total Scored Football Matches
|
||||
cursor.execute("SELECT COUNT(*) as count FROM matches WHERE sport='football' AND score_home IS NOT NULL")
|
||||
total_matches = cursor.fetchone()['count']
|
||||
print(f"Total Scored Matches: {total_matches:,}")
|
||||
|
||||
# 2. Matches with Link to Odds Category
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
WHERE m.sport='football' AND m.score_home IS NOT NULL
|
||||
""")
|
||||
odds_linked_count = cursor.fetchone()['count']
|
||||
print(f"Matches with ANY Odds Linked: {odds_linked_count:,} ({odds_linked_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 3. Matches with Actual Odds Values
|
||||
# Check if selections exist and have values
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
JOIN odd_selections os ON oc.db_id = os.odd_category_db_id
|
||||
WHERE m.sport='football'
|
||||
AND m.score_home IS NOT NULL
|
||||
AND os.odd_value IS NOT NULL
|
||||
""")
|
||||
odds_values_count = cursor.fetchone()['count']
|
||||
print(f"Matches with VALID Odds Values: {odds_values_count:,} ({odds_values_count/total_matches*100:.1f}%)")
|
||||
|
||||
# 4. Investigate Discrepancy (Golden vs Odds Only)
|
||||
print(f"\n💡 Insight: The Golden Dataset was ~28k. This Odds-Only check will show if the bottleneck is Odds (approx {odds_values_count}) or Stats.")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_odds()
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def inspect_recoverable_data():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
print("\n🔍 RECOVERABLE DATA INSPECTION (Ignoring 'state' column)\n")
|
||||
|
||||
# 1. Total Scored Football Matches
|
||||
# Logic: Valid score and valid date is what matters for training
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) as count
|
||||
FROM matches
|
||||
WHERE sport='football'
|
||||
AND score_home IS NOT NULL
|
||||
AND score_away IS NOT NULL
|
||||
""")
|
||||
total_scored = cursor.fetchone()['count']
|
||||
print(f"Total Scored Football Matches: {total_scored:,}")
|
||||
|
||||
if total_scored == 0:
|
||||
return
|
||||
|
||||
# 2. Recoverable Matches with Stats
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN match_team_stats s ON m.id = s.match_id
|
||||
WHERE m.sport='football'
|
||||
AND m.score_home IS NOT NULL
|
||||
AND m.score_away IS NOT NULL
|
||||
""")
|
||||
stats_count = cursor.fetchone()['count']
|
||||
print(f"Matches with Team Stats: {stats_count:,} ({stats_count/total_scored*100:.1f}%)")
|
||||
|
||||
# 3. Recoverable 'Golden Dataset' (Stats + Odds + Events)
|
||||
cursor.execute("""
|
||||
SELECT COUNT(DISTINCT m.id) as count
|
||||
FROM matches m
|
||||
JOIN match_team_stats s ON m.id = s.match_id
|
||||
JOIN match_player_events e ON m.id = e.match_id
|
||||
JOIN odd_categories oc ON m.id = oc.match_id
|
||||
WHERE m.sport='football'
|
||||
AND m.score_home IS NOT NULL
|
||||
AND m.score_away IS NOT NULL
|
||||
""")
|
||||
golden_count = cursor.fetchone()['count']
|
||||
print(f"\n✨ TRUE GOLDEN DATASET (Scored + All 3): {golden_count:,} ({golden_count/total_scored*100:.1f}%)")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
inspect_recoverable_data()
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function inspectMatch(matchId: string) {
|
||||
console.log(`Inspecting Match: ${matchId}`);
|
||||
|
||||
// 1. Ana Mac Bilgileri
|
||||
const match = await prisma.match.findUnique({
|
||||
where: { id: matchId },
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
console.log("Mac bulunamadi!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`\nLeague: ${match.league?.name} (${match.sport})`);
|
||||
console.log(`Match: ${match.matchName}`);
|
||||
console.log(`Date: ${new Date(Number(match.mstUtc)).toLocaleString()}`);
|
||||
console.log(`Status: ${match.status} (Score: ${match.scoreHome}-${match.scoreAway}) (HT: ${match.htScoreHome}-${match.htScoreAway})`);
|
||||
|
||||
// 2. Istatistikler
|
||||
const stats = await prisma.footballTeamStats.findMany({
|
||||
where: { matchId }
|
||||
});
|
||||
console.log("\nStats:");
|
||||
stats.forEach(s => {
|
||||
const teamName = s.teamId === match.homeTeamId ? "Home" : "Away";
|
||||
console.log(` ${teamName}: Ball: %${s.possessionPercentage}, Shots: ${s.totalShots} (${s.shotsOnTarget} on target), Pass: ${s.totalPasses}`);
|
||||
});
|
||||
|
||||
// 3. Olaylar
|
||||
const events = await prisma.matchPlayerEvents.findMany({
|
||||
where: { matchId },
|
||||
orderBy: { id: 'asc' },
|
||||
include: { player: true }
|
||||
});
|
||||
console.log("\nEvents:");
|
||||
events.forEach(e => {
|
||||
console.log(` [${e.timeMinute}'] ${e.eventType} - ${e.player?.name} (${e.eventSubtype || ''})`);
|
||||
});
|
||||
|
||||
// 4. Oranlar
|
||||
const odds = await prisma.oddCategory.findMany({
|
||||
where: { matchId },
|
||||
include: { selections: true },
|
||||
take: 5
|
||||
});
|
||||
console.log("\nOdds (Sample):");
|
||||
odds.forEach(cat => {
|
||||
console.log(` Category: ${cat.name}`);
|
||||
cat.selections.forEach(sel => {
|
||||
console.log(` - ${sel.name}: ${sel.oddValue}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
inspectMatch(process.argv[2])
|
||||
.catch(e => console.error(e))
|
||||
.finally(async () => await prisma.$disconnect());
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def list_live():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url, cursor_factory=RealDictCursor)
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT m.id, ht.name as home, at.name as away
|
||||
FROM live_matches m
|
||||
JOIN teams ht ON m.home_team_id = ht.id
|
||||
JOIN teams at ON m.away_team_id = at.id
|
||||
LIMIT 10
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
print("Live Matches in DB:")
|
||||
for r in rows:
|
||||
print(f"ID: {r['id']} | {r['home']} vs {r['away']}")
|
||||
|
||||
cursor.execute("SELECT COUNT(*) FROM live_matches")
|
||||
print(f"\nTotal Live Matches: {cursor.fetchone()['count']}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
list_live()
|
||||
@@ -0,0 +1,655 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function analyzeMatch() {
|
||||
const matchId = '2ivwkprq3bnkkp2bfa2cdintg';
|
||||
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ GLM-5 TAHMİN & SÜRPRİZ ANALİZİ ║',
|
||||
);
|
||||
console.log(
|
||||
'║ Maç ID: ' +
|
||||
matchId.substring(0, 20) +
|
||||
'... ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
|
||||
const match = await prisma.match.findUnique({
|
||||
where: { id: matchId },
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: { include: { country: true } },
|
||||
oddCategories: { include: { selections: true } },
|
||||
officials: { include: { role: true } },
|
||||
aiFeatures: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
console.log('❌ Maç bulunamadı!');
|
||||
await prisma.$disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('⚽ MAÇ BİLGİLERİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log(`\n🏠 Ev Sahibi: ${match.homeTeam?.name}`);
|
||||
console.log(`✈️ Deplasman: ${match.awayTeam?.name}`);
|
||||
console.log(
|
||||
`🏆 Lig: ${match.league?.name} (${match.league?.country?.name || ''})`,
|
||||
);
|
||||
console.log(
|
||||
`📅 Tarih: ${new Date(Number(match.mstUtc)).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}`,
|
||||
);
|
||||
console.log(`📊 Durum: ${match.state} / ${match.status}`);
|
||||
console.log(`🔢 İddaa Kodu: ${match.iddaaCode || 'Yok'}`);
|
||||
|
||||
// SKORU GİZLE - Sona saklayacağız
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 1. ORAN ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('📊 1. ORAN ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
const odds = {
|
||||
ms_h: null,
|
||||
ms_d: null,
|
||||
ms_a: null,
|
||||
dc_1x: null,
|
||||
dc_x2: null,
|
||||
dc_12: null,
|
||||
ou25_o: null,
|
||||
ou25_u: null,
|
||||
btts_y: null,
|
||||
btts_n: null,
|
||||
ht_h: null,
|
||||
ht_d: null,
|
||||
ht_a: null,
|
||||
};
|
||||
|
||||
console.log('\n📋 Tüm Oran Kategorileri:\n');
|
||||
for (const cat of match.oddCategories) {
|
||||
const selections = cat.selections
|
||||
.map((s) => `${s.name}: ${s.oddValue}`)
|
||||
.join(' | ');
|
||||
console.log(` ${cat.name}: ${selections}`);
|
||||
|
||||
if (cat.name?.includes('Maç Sonucu') || cat.name === 'MS') {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1') odds.ms_h = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') odds.ms_d = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') odds.ms_a = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
if (cat.name?.includes('Çifte Şans')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1-X') odds.dc_1x = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X-2') odds.dc_x2 = parseFloat(sel.oddValue);
|
||||
if (sel.name === '1-2') odds.dc_12 = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
if (cat.name?.includes('2,5') || cat.name?.includes('2.5')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === 'Alt' || sel.name === 'A')
|
||||
odds.ou25_u = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Üst' || sel.name === 'Ü')
|
||||
odds.ou25_o = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
if (
|
||||
cat.name?.toLowerCase().includes('karşılıklı') ||
|
||||
cat.name?.includes('KG')
|
||||
) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === 'Var' || sel.name === 'Evet')
|
||||
odds.btts_y = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Yok' || sel.name === 'Hayır')
|
||||
odds.btts_n = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
if (cat.name?.includes('1. Yarı') && cat.name?.includes('Sonuc')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1') odds.ht_h = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') odds.ht_d = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') odds.ht_a = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let margin = null;
|
||||
let normHome = 33,
|
||||
normDraw = 33,
|
||||
normAway = 33;
|
||||
let favorite = null,
|
||||
favoriteOdds = null;
|
||||
|
||||
if (odds.ms_h && odds.ms_d && odds.ms_a) {
|
||||
const rawHome = (1 / odds.ms_h) * 100;
|
||||
const rawDraw = (1 / odds.ms_d) * 100;
|
||||
const rawAway = (1 / odds.ms_a) * 100;
|
||||
const total = rawHome + rawDraw + rawAway;
|
||||
margin = total - 100;
|
||||
|
||||
normHome = (rawHome / total) * 100;
|
||||
normDraw = (rawDraw / total) * 100;
|
||||
normAway = (rawAway / total) * 100;
|
||||
|
||||
// Favori kim?
|
||||
if (odds.ms_h < odds.ms_a) {
|
||||
favorite = 'home';
|
||||
favoriteOdds = odds.ms_h;
|
||||
} else if (odds.ms_a < odds.ms_h) {
|
||||
favorite = 'away';
|
||||
favoriteOdds = odds.ms_a;
|
||||
} else {
|
||||
favorite = 'draw';
|
||||
favoriteOdds = odds.ms_d;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n📊 MS Oranları: 1=${odds.ms_h} | X=${odds.ms_d} | 2=${odds.ms_a}`,
|
||||
);
|
||||
console.log(`📈 Bookmaker Margin: %${margin.toFixed(1)}`);
|
||||
console.log(`🎯 Normalize Olasılık:`);
|
||||
console.log(
|
||||
` 1 (${match.homeTeam?.name?.substring(0, 15)}): %${normHome.toFixed(1)}`,
|
||||
);
|
||||
console.log(` X (Beraberlik): %${normDraw.toFixed(1)}`);
|
||||
console.log(
|
||||
` 2 (${match.awayTeam?.name?.substring(0, 15)}): %${normAway.toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
`\n🏆 Favori: ${favorite === 'home' ? match.homeTeam?.name + ' (1)' : favorite === 'away' ? match.awayTeam?.name + ' (2)' : 'Beraberlik (X)'} @ ${favoriteOdds}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 2. TAKIM FORM ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('📈 2. TAKIM FORM ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
// Ev sahibi son 10 maç
|
||||
const homeMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [{ homeTeamId: match.homeTeamId }, { awayTeamId: match.homeTeamId }],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 10,
|
||||
});
|
||||
|
||||
let homeWins = 0,
|
||||
homeDraws = 0,
|
||||
homeLosses = 0;
|
||||
let homeGoalsFor = 0,
|
||||
homeGoalsAgainst = 0;
|
||||
|
||||
for (const m of homeMatches) {
|
||||
const isHome = m.homeTeamId === match.homeTeamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
homeGoalsFor += gf || 0;
|
||||
homeGoalsAgainst += ga || 0;
|
||||
if (gf > ga) homeWins++;
|
||||
else if (gf < ga) homeLosses++;
|
||||
else homeDraws++;
|
||||
}
|
||||
|
||||
const homeFormScore =
|
||||
homeMatches.length > 0
|
||||
? ((homeWins * 3 + homeDraws) / (homeMatches.length * 3)) * 100
|
||||
: 50;
|
||||
|
||||
console.log(`\n🏠 ${match.homeTeam?.name}:`);
|
||||
console.log(
|
||||
` Son ${homeMatches.length} Maç: ${homeWins}G ${homeDraws}B ${homeLosses}M`,
|
||||
);
|
||||
console.log(` Gol: ${homeGoalsFor} attı, ${homeGoalsAgainst} yedi`);
|
||||
console.log(` Form Skoru: ${homeFormScore.toFixed(1)}/100`);
|
||||
|
||||
// Deplasman son 10 maç
|
||||
const awayMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [{ homeTeamId: match.awayTeamId }, { awayTeamId: match.awayTeamId }],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 10,
|
||||
});
|
||||
|
||||
let awayWins = 0,
|
||||
awayDraws = 0,
|
||||
awayLosses = 0;
|
||||
let awayGoalsFor = 0,
|
||||
awayGoalsAgainst = 0;
|
||||
|
||||
for (const m of awayMatches) {
|
||||
const isHome = m.homeTeamId === match.awayTeamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
awayGoalsFor += gf || 0;
|
||||
awayGoalsAgainst += ga || 0;
|
||||
if (gf > ga) awayWins++;
|
||||
else if (gf < ga) awayLosses++;
|
||||
else awayDraws++;
|
||||
}
|
||||
|
||||
const awayFormScore =
|
||||
awayMatches.length > 0
|
||||
? ((awayWins * 3 + awayDraws) / (awayMatches.length * 3)) * 100
|
||||
: 50;
|
||||
|
||||
console.log(`\n✈️ ${match.awayTeam?.name}:`);
|
||||
console.log(
|
||||
` Son ${awayMatches.length} Maç: ${awayWins}G ${awayDraws}B ${awayLosses}M`,
|
||||
);
|
||||
console.log(` Gol: ${awayGoalsFor} attı, ${awayGoalsAgainst} yedi`);
|
||||
console.log(` Form Skoru: ${awayFormScore.toFixed(1)}/100`);
|
||||
|
||||
const formDiff = homeFormScore - awayFormScore;
|
||||
console.log(
|
||||
`\n📈 Form Farkı: ${formDiff > 0 ? '+' : ''}${formDiff.toFixed(1)}`,
|
||||
);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 3. HEAD-TO-HEAD
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('🔄 3. HEAD-TO-HEAD');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
const h2hMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ homeTeamId: match.homeTeamId, awayTeamId: match.awayTeamId },
|
||||
{ homeTeamId: match.awayTeamId, awayTeamId: match.homeTeamId },
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
},
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 5,
|
||||
});
|
||||
|
||||
let h2hHomeWins = 0,
|
||||
h2hAwayWins = 0,
|
||||
h2hDraws = 0;
|
||||
|
||||
if (h2hMatches.length > 0) {
|
||||
for (const m of h2hMatches) {
|
||||
const homeTeamIsHome = m.homeTeamId === match.homeTeamId;
|
||||
const result =
|
||||
m.scoreHome > m.scoreAway
|
||||
? homeTeamIsHome
|
||||
? '1'
|
||||
: '2'
|
||||
: m.scoreHome < m.scoreAway
|
||||
? homeTeamIsHome
|
||||
? '2'
|
||||
: '1'
|
||||
: 'X';
|
||||
|
||||
if (result === '1') h2hHomeWins++;
|
||||
else if (result === '2') h2hAwayWins++;
|
||||
else h2hDraws++;
|
||||
|
||||
console.log(
|
||||
` ${m.homeTeam?.name} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name} [${result}]`,
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`\n H2H: ${match.homeTeam?.name} ${h2hHomeWins}G, ${h2hDraws}B, ${match.awayTeam?.name} ${h2hAwayWins}G`,
|
||||
);
|
||||
} else {
|
||||
console.log(' Karşılıklı maç bulunamadı');
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 4. HAKEM ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('👨⚖️ 4. HAKEM ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
let refUpsetRate = 0;
|
||||
const mainReferee = match.officials?.find(
|
||||
(o) => o.role?.name === 'Orta Hakem',
|
||||
);
|
||||
|
||||
if (mainReferee) {
|
||||
console.log(`\n👤 Hakem: ${mainReferee.name}`);
|
||||
|
||||
const refereeMatches = await prisma.matchOfficial.findMany({
|
||||
where: { name: mainReferee.name, roleId: 1 },
|
||||
include: { match: true },
|
||||
take: 30,
|
||||
});
|
||||
|
||||
let refHomeWins = 0,
|
||||
refDraws = 0,
|
||||
refAwayWins = 0;
|
||||
let refUpsets = 0,
|
||||
refTotal = 0;
|
||||
|
||||
for (const rm of refereeMatches) {
|
||||
if (rm.match?.scoreHome !== null && rm.match?.scoreAway !== null) {
|
||||
refTotal++;
|
||||
// Bu maç için favori belirle (basit: ev sahibi favori varsay)
|
||||
// Gerçek hayatta oranlar kontrol edilmeli
|
||||
if (rm.match.scoreHome > rm.match.scoreAway) refHomeWins++;
|
||||
else if (rm.match.scoreHome < rm.match.scoreAway) {
|
||||
refAwayWins++;
|
||||
refUpsets++; // Ev sahibi kaybetti = sürpriz (basitleştirilmiş)
|
||||
} else refDraws++;
|
||||
}
|
||||
}
|
||||
|
||||
if (refTotal > 0) {
|
||||
console.log(` Yönettiği maçlar: ${refTotal}`);
|
||||
console.log(
|
||||
` Ev kazanma: %${((refHomeWins / refTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Beraberlik: %${((refDraws / refTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Deplasman: %${((refAwayWins / refTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
refUpsetRate = (refAwayWins / refTotal) * 100;
|
||||
console.log(
|
||||
` ⚠️ Sürpriz (ev kaybı) oranı: %${refUpsetRate.toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(' Hakem bilgisi yok');
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 5. SÜRPRİZ SKORU HESAPLAMA
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('🎯 5. SÜRPRİZ SKORU HESAPLAMA');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
let upsetScore = 0;
|
||||
const upsetReasons = [];
|
||||
|
||||
console.log('\n📊 Sürpriz Skoru Hesaplaması:\n');
|
||||
|
||||
// 1. Margin
|
||||
if (margin !== null) {
|
||||
if (margin > 20) {
|
||||
upsetScore += 15;
|
||||
upsetReasons.push('Margin yüksek (%20+)');
|
||||
console.log(` ✓ Margin %${margin.toFixed(1)} > %20 → +15 puan`);
|
||||
} else if (margin > 18) {
|
||||
upsetScore += 10;
|
||||
upsetReasons.push('Margin orta-yüksek');
|
||||
console.log(` ✓ Margin %${margin.toFixed(1)} > %18 → +10 puan`);
|
||||
} else {
|
||||
console.log(` ✗ Margin %${margin.toFixed(1)} < %18 → +0 puan`);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Favori Oran
|
||||
if (favoriteOdds !== null) {
|
||||
if (favoriteOdds >= 1.5 && favoriteOdds < 1.6) {
|
||||
upsetScore += 25;
|
||||
upsetReasons.push('Favori oran 1.50-1.60 arası (riskli)');
|
||||
console.log(` ✓ Favori oran ${favoriteOdds} (1.50-1.60) → +25 puan`);
|
||||
} else if (favoriteOdds >= 1.4 && favoriteOdds < 1.5) {
|
||||
upsetScore += 20;
|
||||
upsetReasons.push('Favori oran 1.40-1.50 arası');
|
||||
console.log(` ✓ Favori oran ${favoriteOdds} (1.40-1.50) → +20 puan`);
|
||||
} else if (favoriteOdds >= 1.3 && favoriteOdds < 1.4) {
|
||||
upsetScore += 15;
|
||||
upsetReasons.push('Favori oran 1.30-1.40 arası');
|
||||
console.log(` ✓ Favori oran ${favoriteOdds} (1.30-1.40) → +15 puan`);
|
||||
} else if (favoriteOdds < 1.2) {
|
||||
upsetScore += 20;
|
||||
upsetReasons.push('Favori oran çok düşük (tuzak şüphesi)');
|
||||
console.log(
|
||||
` ✓ Favori oran ${favoriteOdds} (<1.20, tuzak?) → +20 puan`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ✗ Favori oran ${favoriteOdds} (güvenli aralık) → +0 puan`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Hakem
|
||||
if (refUpsetRate > 30) {
|
||||
upsetScore += 20;
|
||||
upsetReasons.push(
|
||||
`Hakem sürpriz oranı yüksek (%${refUpsetRate.toFixed(0)})`,
|
||||
);
|
||||
console.log(
|
||||
` ✓ Hakem sürpriz oranı %${refUpsetRate.toFixed(0)} > %30 → +20 puan`,
|
||||
);
|
||||
} else if (refUpsetRate > 20) {
|
||||
upsetScore += 10;
|
||||
upsetReasons.push('Hakem sürpriz oranı orta');
|
||||
console.log(
|
||||
` ✓ Hakem sürpriz oranı %${refUpsetRate.toFixed(0)} > %20 → +10 puan`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ✗ Hakem sürpriz oranı %${refUpsetRate.toFixed(0)} < %20 → +0 puan`,
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Form Farkı
|
||||
if (Math.abs(formDiff) > 40) {
|
||||
upsetScore += 15;
|
||||
upsetReasons.push(
|
||||
`Form farkı çok büyük (${formDiff > 0 ? '+' : ''}${formDiff.toFixed(0)})`,
|
||||
);
|
||||
console.log(
|
||||
` ✓ Form farkı ${formDiff.toFixed(0)} > 40 (tuzak?) → +15 puan`,
|
||||
);
|
||||
} else {
|
||||
console.log(` ✗ Form farkı ${formDiff.toFixed(0)} < 40 → +0 puan`);
|
||||
}
|
||||
|
||||
// 5. H2H Sürpriz
|
||||
if (h2hAwayWins > 0 && favorite === 'home') {
|
||||
upsetScore += 10;
|
||||
upsetReasons.push('H2H geçmişinde deplasman galibiyeti var');
|
||||
console.log(` ✓ H2H'de deplasman ${h2hAwayWins} galibiyet → +10 puan`);
|
||||
} else if (h2hHomeWins > 0 && favorite === 'away') {
|
||||
upsetScore += 10;
|
||||
upsetReasons.push('H2H geçmişinde ev sahibi galibiyeti var');
|
||||
console.log(` ✓ H2H'de ev sahibi ${h2hHomeWins} galibiyet → +10 puan`);
|
||||
} else {
|
||||
console.log(` ✗ H2H'de sürpriz yok → +0 puan`);
|
||||
}
|
||||
|
||||
console.log(`\n ─────────────────────────────`);
|
||||
console.log(` 📊 SÜRPRİZ SKORU: ${upsetScore}/100`);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 6. FİNAL TAHMİN
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ 🎯 FİNAL TAHMİN ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
|
||||
// Normal tahmin
|
||||
const w = { odds: 0.4, form: 0.3, home: 0.15, league: 0.15 };
|
||||
|
||||
const homeScore = normHome * w.odds + homeFormScore * w.form + 8 * w.home;
|
||||
const awayScore = normAway * w.odds + awayFormScore * w.form + 0 * w.home;
|
||||
const drawScore =
|
||||
normDraw * w.odds + (100 - Math.abs(formDiff)) * 0.1 * w.form;
|
||||
|
||||
const total = homeScore + drawScore + awayScore;
|
||||
const finalHome = (homeScore / total) * 100;
|
||||
const finalDraw = (drawScore / total) * 100;
|
||||
const finalAway = (awayScore / total) * 100;
|
||||
|
||||
let prediction, confidence;
|
||||
if (finalHome > finalDraw && finalHome > finalAway) {
|
||||
prediction = '1';
|
||||
confidence = finalHome;
|
||||
} else if (finalAway > finalDraw) {
|
||||
prediction = '2';
|
||||
confidence = finalAway;
|
||||
} else {
|
||||
prediction = 'X';
|
||||
confidence = finalDraw;
|
||||
}
|
||||
|
||||
// Alt/Üst
|
||||
const avgGoals =
|
||||
(homeGoalsFor + homeGoalsAgainst + awayGoalsFor + awayGoalsAgainst) /
|
||||
(homeMatches.length + awayMatches.length) || 2.5;
|
||||
const ou25 = avgGoals > 2.5 ? 'ÜST' : 'ALT';
|
||||
|
||||
console.log(`\n📊 NORMAL TAHMİN (Oran + Form):`);
|
||||
console.log(` 1: %${finalHome.toFixed(1)}`);
|
||||
console.log(` X: %${finalDraw.toFixed(1)}`);
|
||||
console.log(` 2: %${finalAway.toFixed(1)}`);
|
||||
console.log(`\n 🏆 TAHMİN: ${prediction}`);
|
||||
console.log(` 📊 Güven: %${confidence.toFixed(1)}`);
|
||||
console.log(` ⚽ 2.5 ${ou25}`);
|
||||
|
||||
// Sürpriz tahmini
|
||||
console.log(`\n⚠️ SÜRPRİZ ANALİZİ:`);
|
||||
console.log(` Sürpriz Skoru: ${upsetScore}/100`);
|
||||
|
||||
if (upsetScore >= 50) {
|
||||
console.log(`\n 🔴 YÜKSEK SÜRPRİZ RİSKİ!`);
|
||||
console.log(` Sürpriz işaretleri:`);
|
||||
for (const reason of upsetReasons) {
|
||||
console.log(` • ${reason}`);
|
||||
}
|
||||
|
||||
// Value bet önerisi
|
||||
const upsetPrediction =
|
||||
prediction === '1' ? '2' : prediction === '2' ? '1' : 'X';
|
||||
const upsetOdds =
|
||||
prediction === '1'
|
||||
? odds.ms_a
|
||||
: prediction === '2'
|
||||
? odds.ms_h
|
||||
: odds.ms_d;
|
||||
console.log(`\n 💰 VALUE BET ÖNERİSİ:`);
|
||||
console.log(
|
||||
` Normal tahmin: ${prediction} @ ${prediction === '1' ? odds.ms_h : prediction === '2' ? odds.ms_a : odds.ms_d}`,
|
||||
);
|
||||
console.log(
|
||||
` Sürpriz tahmin: ${upsetPrediction} @ ${upsetOdds} ← VALUE BET!`,
|
||||
);
|
||||
} else if (upsetScore >= 30) {
|
||||
console.log(`\n 🟡 ORTA SÜRPRİZ RİSKİ`);
|
||||
console.log(` Dikkatli olun, çifte şans düşünülebilir`);
|
||||
} else {
|
||||
console.log(`\n 🟢 DÜŞÜK SÜRPRİZ RİSKİ`);
|
||||
console.log(` Normal tahmin güvenle oynanabilir`);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// GERÇEK SONUÇ (SON)
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ 📊 GERÇEK SONUÇ ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
|
||||
if (match.scoreHome !== null && match.scoreAway !== null) {
|
||||
const actual =
|
||||
match.scoreHome > match.scoreAway
|
||||
? '1'
|
||||
: match.scoreHome < match.scoreAway
|
||||
? '2'
|
||||
: 'X';
|
||||
const actualGoals = match.scoreHome + match.scoreAway;
|
||||
const actualOU = actualGoals > 2.5 ? 'ÜST' : 'ALT';
|
||||
|
||||
console.log(
|
||||
`\n Skor: ${match.homeTeam?.name} ${match.scoreHome} - ${match.scoreAway} ${match.awayTeam?.name}`,
|
||||
);
|
||||
console.log(` Sonuç: ${actual}`);
|
||||
console.log(` Alt/Üst: ${actualOU} (${actualGoals} gol)`);
|
||||
|
||||
console.log(`\n ─────────────────────────────`);
|
||||
console.log(
|
||||
` 🎯 Normal Tahmin (${prediction}): ${prediction === actual ? '✅ DOĞRU' : '❌ YANLIŞ'}`,
|
||||
);
|
||||
console.log(
|
||||
` 🎯 2.5 ${ou25}: ${ou25 === actualOU ? '✅ DOĞRU' : '❌ YANLIŞ'}`,
|
||||
);
|
||||
|
||||
if (upsetScore >= 50) {
|
||||
const upsetPrediction =
|
||||
prediction === '1' ? '2' : prediction === '2' ? '1' : 'X';
|
||||
console.log(
|
||||
` 🎯 Sürpriz Tahmin (${upsetPrediction}): ${upsetPrediction === actual ? '✅ DOĞRU' : '❌ YANLIŞ'}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(`\n Maç henüz oynanmamış veya skor yok`);
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
analyzeMatch().catch(console.error);
|
||||
Executable
+265
@@ -0,0 +1,265 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
from datetime import datetime
|
||||
|
||||
# Add path
|
||||
sys.path.append(os.getcwd())
|
||||
sys.path.append(os.path.join(os.getcwd(), 'ai-engine'))
|
||||
|
||||
from models.hybrid_v11 import HybridDeepModel
|
||||
from features.odds_history import OddsHistoryEngine
|
||||
|
||||
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
MODEL_PATH = 'ai-engine/models/v11_hybrid_model.pth'
|
||||
MATCH_ID = '8yl78ecnv1fqynawwtf5159uc' # User Request Re-test
|
||||
|
||||
def get_db_conn():
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
return psycopg2.connect(db_url, cursor_factory=RealDictCursor)
|
||||
|
||||
def get_team_history(conn, team_id, match_date, seq_len=10):
|
||||
query = """
|
||||
SELECT
|
||||
m.home_team_id, m.away_team_id,
|
||||
m.score_home, m.score_away,
|
||||
m.ht_score_home, m.ht_score_away
|
||||
FROM matches m
|
||||
WHERE (m.home_team_id = %s OR m.away_team_id = %s)
|
||||
AND m.mst_utc < %s
|
||||
AND m.status = 'FT'
|
||||
AND m.score_home IS NOT NULL
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT %s
|
||||
"""
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (team_id, team_id, match_date, seq_len))
|
||||
rows = cur.fetchall()
|
||||
|
||||
if len(rows) < seq_len:
|
||||
print(f"⚠️ Warning: Not enough history for team {team_id} (Found {len(rows)})")
|
||||
# Pad with zeros or return None?
|
||||
# Let's pad with simple placeholders if needed, but better to just use what we have or duplicate
|
||||
pass
|
||||
|
||||
# Process rows (Reverse order to be chronological)
|
||||
history = []
|
||||
for r in reversed(rows):
|
||||
# Normalize to Team Perspective
|
||||
is_home = (r['home_team_id'] == team_id)
|
||||
if is_home:
|
||||
gf = r['score_home']
|
||||
ga = r['score_away']
|
||||
res = 1.0 if gf > ga else (0.5 if gf == ga else 0.0)
|
||||
loc = 1.0
|
||||
else:
|
||||
gf = r['score_away']
|
||||
ga = r['score_home']
|
||||
res = 1.0 if gf > ga else (0.5 if gf == ga else 0.0)
|
||||
loc = 0.0
|
||||
|
||||
history.append([gf, ga, res, loc])
|
||||
|
||||
# Pad if short
|
||||
while len(history) < seq_len:
|
||||
history.insert(0, [0, 0, 0.5, 0.5]) # Neutral padding
|
||||
|
||||
return np.array(history, dtype=np.float32)
|
||||
|
||||
def predict():
|
||||
print(f"🔮 Predicting Match: {MATCH_ID}")
|
||||
|
||||
conn = get_db_conn()
|
||||
|
||||
# 1. Get Match Info
|
||||
with conn.cursor() as cur:
|
||||
# Try 'matches' table first
|
||||
cur.execute("""
|
||||
SELECT m.home_team_id, m.away_team_id, m.mst_utc,
|
||||
ht.name as home_name_db, at.name as away_name_db
|
||||
FROM matches m
|
||||
LEFT JOIN teams ht ON m.home_team_id = ht.id
|
||||
LEFT JOIN teams at ON m.away_team_id = at.id
|
||||
WHERE m.id = %s
|
||||
""", (MATCH_ID,))
|
||||
match = cur.fetchone()
|
||||
|
||||
if not match:
|
||||
print("⚠️ Match not found in 'matches'. Checking 'live_matches'...")
|
||||
cur.execute("""
|
||||
SELECT m.home_team_id, m.away_team_id, m.mst_utc, m.match_name,
|
||||
ht.name as home_name_db, at.name as away_name_db
|
||||
FROM live_matches m
|
||||
LEFT JOIN teams ht ON m.home_team_id = ht.id
|
||||
LEFT JOIN teams at ON m.away_team_id = at.id
|
||||
WHERE m.id = %s
|
||||
""", (MATCH_ID,))
|
||||
match = cur.fetchone()
|
||||
|
||||
if not match:
|
||||
print("❌ Match not found in either table!")
|
||||
return
|
||||
|
||||
# Fallback for names
|
||||
home_name = match.get('home_name_db') or (match.get('match_name') or 'Unknown Home').split(' vs ')[0]
|
||||
away_name = match.get('away_name_db') or (match.get('match_name') or 'Unknown Away').split(' vs ')[-1]
|
||||
|
||||
# CRITICAL FIX for match 8yl... where IDs are null
|
||||
if MATCH_ID == '8yl78ecnv1fqynawwtf5159uc':
|
||||
if not match['home_team_id']:
|
||||
match['home_team_id'] = 'bmgtxgipsznlb1j20zwjti3xh' # Eyüpspor
|
||||
print(f" 🛠️ Injected Home ID: {match['home_team_id']} (Eyüpspor)")
|
||||
if not match['away_team_id']:
|
||||
match['away_team_id'] = '2ez9cvam9lp9jyhng3eh3znb4' # Beşiktaş
|
||||
print(f" 🛠️ Injected Away ID: {match['away_team_id']} (Beşiktaş)")
|
||||
|
||||
print(f"⚔️ {home_name} vs {away_name}")
|
||||
print(f"📅 Date: {datetime.fromtimestamp(match['mst_utc']/1000)}")
|
||||
|
||||
# 2. Get Odds (Context) -> Odds might be in 'odds' column JSON in live_matches?
|
||||
# Or odd_categories table might link to live_matches? Usually they link via match_id regardless of table.
|
||||
# We will assume odd_categories works or default.
|
||||
with conn.cursor() as cur:
|
||||
try:
|
||||
cur.execute("""
|
||||
SELECT oc.name, os.name as selection, os.odd_value
|
||||
FROM odd_categories oc
|
||||
JOIN odd_selections os ON oc.db_id = os.odd_category_db_id
|
||||
WHERE oc.match_id = %s AND oc.name IN ('Maç Sonucu', '2,5 Alt/Üst')
|
||||
""", (MATCH_ID,))
|
||||
odds_rows = cur.fetchall()
|
||||
except Exception as e:
|
||||
print(f"⚠️ Odds fetch failed: {e}")
|
||||
conn.rollback()
|
||||
odds_rows = []
|
||||
|
||||
odds = {'1': 2.5, 'X': 3.2, '2': 2.5, 'Over': 1.80, 'Under': 1.80} # Defaults
|
||||
for r in odds_rows:
|
||||
sel = r['selection']
|
||||
val = float(r['odd_value'])
|
||||
if r['name'] == 'Maç Sonucu':
|
||||
if sel in ['1', 'X', '2']: odds[sel] = val
|
||||
elif r['name'] == '2,5 Alt/Üst':
|
||||
if 'Üst' in sel or 'Over' in sel: odds['Over'] = val
|
||||
if 'Alt' in sel or 'Under' in sel: odds['Under'] = val
|
||||
|
||||
print(f"📊 Market Odds: 1:{odds['1']} X:{odds['X']} 2:{odds['2']} | O:{odds['Over']} U:{odds['Under']}")
|
||||
|
||||
# 3. Build Sequences
|
||||
seq_home = get_team_history(conn, match['home_team_id'], match['mst_utc'])
|
||||
|
||||
# 4. Reconstruct Team Map (MUST match training logic)
|
||||
# This ensures Team IDs map to the correct Embedding Indices.
|
||||
from pipeline.sequence_builder import SequenceBuilder
|
||||
print(" 🗺️ Reconstructing Team Map for Identity alignment...")
|
||||
builder = SequenceBuilder()
|
||||
_, _, meta_all = builder.build_sequences()
|
||||
unique_teams = meta_all['team_id'].unique()
|
||||
team_map = {tid: i for i, tid in enumerate(unique_teams)}
|
||||
|
||||
# Get Indices (Fallback to 0 if not found)
|
||||
home_idx = team_map.get(match['home_team_id'], 0)
|
||||
away_idx = team_map.get(match['away_team_id'], 0)
|
||||
print(f" 🆔 Identity: {home_name} (Idx:{home_idx}) vs {away_name} (Idx:{away_idx})")
|
||||
|
||||
# 5. Load Model
|
||||
# ... (Model loading logic follows)
|
||||
|
||||
try:
|
||||
state = torch.load(MODEL_PATH, map_location=DEVICE)
|
||||
# Handle shape mismatch if num_teams changed?
|
||||
# State dict has specific size for 'entity_emb.weight'.
|
||||
emb_key = 'entity_emb.weight' if 'entity_emb.weight' in state else 'team_embedding.weight'
|
||||
saved_vocab_size = state[emb_key].shape[0]
|
||||
|
||||
# Initialize & Load
|
||||
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)
|
||||
print("✅ Model loaded successfully.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Model load failed: {e}")
|
||||
return
|
||||
|
||||
model.eval()
|
||||
|
||||
# 5. Prepare Input Tensors
|
||||
entities = torch.LongTensor([home_idx, away_idx]).unsqueeze(0).to(DEVICE)
|
||||
|
||||
seq = torch.FloatTensor(seq_home).unsqueeze(0).to(DEVICE)
|
||||
|
||||
eng = OddsHistoryEngine()
|
||||
hist_win_rate = eng.get_feature(match['home_team_id'], odds['1'])
|
||||
syn_xg = 1.35 # Avg
|
||||
|
||||
ctx = torch.FloatTensor([
|
||||
odds['1'], odds['X'], odds['2'],
|
||||
odds['Over'], odds['Under'],
|
||||
syn_xg, syn_xg,
|
||||
hist_win_rate
|
||||
]).unsqueeze(0).to(DEVICE)
|
||||
|
||||
# 6. Predict
|
||||
with torch.no_grad():
|
||||
logits_res, pred_goals, logits_btts, logits_ht_ft = model(entities, seq, ctx)
|
||||
|
||||
# 1X2
|
||||
probs_1x2 = F.softmax(logits_res, dim=1).cpu().numpy()[0]
|
||||
|
||||
# Goals
|
||||
exp_goals = pred_goals.item()
|
||||
|
||||
# BTTS
|
||||
prob_btts = torch.sigmoid(logits_btts).item()
|
||||
|
||||
# HT/FT
|
||||
probs_ht = F.softmax(logits_ht_ft, dim=1).cpu().numpy()[0]
|
||||
|
||||
# 7. Report with Value Analysis
|
||||
print("\n🧠 AI PREDICTION REPORT (V11 REFINED)")
|
||||
print("-" * 40)
|
||||
|
||||
print(f"\n🏆 MATCH RESULT (1X2) - VALUE ANALYSIS")
|
||||
headers = ["Selection", "AI Prob", "Market Odd", "Exp. Value (EV)"]
|
||||
print(f"{headers[0]:<10} | {headers[1]:<8} | {headers[2]:<10} | {headers[3]:<12}")
|
||||
print("-" * 40)
|
||||
|
||||
outcomes = [('1', probs_1x2[0]), ('X', probs_1x2[1]), ('2', probs_1x2[2])]
|
||||
best_ev = -99
|
||||
value_bet = None
|
||||
|
||||
for label, prob in outcomes:
|
||||
odd = float(odds.get(label, 1.0))
|
||||
ev = (prob * odd) - 1.0 # Expected profit per 1 unit stake
|
||||
color = "✅" if ev > 0.1 else ("⚠️" if ev > -0.1 else "❌")
|
||||
print(f"{label:<10} | {prob*100:>7.1f}% | {odd:>10.2f} | {ev:>12.2f} {color}")
|
||||
|
||||
if ev > best_ev:
|
||||
best_ev = ev
|
||||
value_bet = label
|
||||
|
||||
print(f"\n👉 AI RECOMMENDATION: {value_bet} (EV: {best_ev:.2f})")
|
||||
if best_ev > 1.0:
|
||||
print("🔥 WARNING: HIGH VALUE ALERT! Odds significantly underpriced by market.")
|
||||
|
||||
print(f"\n⚽ GOALS ANALYSIS")
|
||||
print(f" Expected Goals: {exp_goals:.2f}")
|
||||
# Banko check
|
||||
if exp_goals < 4.5: print(" 🛡️ BANKO: 4.5 Alt/Under (High Conf)")
|
||||
if exp_goals > 1.5: print(" 🛡️ BANKO: 1.5 Üst/Over (High Conf)")
|
||||
|
||||
print(f"\n⌛ HT/FT (Half Time / Full Time) - ALL CLASSES")
|
||||
ht_map = ["1/1", "1/X", "1/2", "X/1", "X/X", "X/2", "2/1", "2/X", "2/2"]
|
||||
all_idx = np.argsort(probs_ht)[::-1]
|
||||
for idx in all_idx:
|
||||
print(f" {ht_map[idx]:<4}: {probs_ht[idx]*100:>5.1f}%")
|
||||
|
||||
if __name__ == "__main__":
|
||||
predict()
|
||||
@@ -0,0 +1,500 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function analyzePredictionProcess() {
|
||||
console.log('\n========================================');
|
||||
console.log('🎯 MAÇ TAHMİN SÜRECİ ANALİZİ');
|
||||
console.log('========================================\n');
|
||||
|
||||
// Mevcut maçların tarih aralığı
|
||||
const oldestMatch = await prisma.match.findFirst({
|
||||
where: { sport: 'football' },
|
||||
orderBy: { mstUtc: 'asc' },
|
||||
});
|
||||
|
||||
const newestMatch = await prisma.match.findFirst({
|
||||
where: { sport: 'football' },
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
console.log('📅 FUTBOL MAÇ TARİH ARALIĞI:');
|
||||
console.log(
|
||||
` En Eski: ${new Date(Number(oldestMatch.mstUtc)).toISOString()}`,
|
||||
);
|
||||
console.log(
|
||||
` En Yeni: ${new Date(Number(newestMatch.mstUtc)).toISOString()}`,
|
||||
);
|
||||
|
||||
// preGame durumundaki maçları bul
|
||||
const upcomingMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
sport: 'football',
|
||||
state: 'preGame',
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
oddCategories: {
|
||||
include: { selections: true },
|
||||
},
|
||||
aiFeatures: true,
|
||||
},
|
||||
take: 5,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
console.log(`\n🔴 preGame Durumundaki Maçlar: ${upcomingMatches.length}`);
|
||||
|
||||
// Eğer preGame yoksa, son bitmiş maçları al
|
||||
let selectedMatch = null;
|
||||
if (upcomingMatches.length > 0) {
|
||||
selectedMatch = upcomingMatches[0];
|
||||
} else {
|
||||
// Son bitmiş futbol maçını al (analiz için)
|
||||
const recentMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
oddCategories: {
|
||||
include: { selections: true },
|
||||
},
|
||||
aiFeatures: true,
|
||||
},
|
||||
take: 1,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
if (recentMatches.length > 0) {
|
||||
selectedMatch = recentMatches[0];
|
||||
console.log(
|
||||
'\n⚠️ preGame maçı bulunamadı. Son bitmiş maç analiz edilecek.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectedMatch) {
|
||||
console.log('Analiz edilecek maç bulunamadı.');
|
||||
await prisma.$disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('⚽ SEÇİLEN MAÇ');
|
||||
console.log('========================================');
|
||||
console.log(`Maç ID: ${selectedMatch.id}`);
|
||||
console.log(`Ev Sahibi: ${selectedMatch.homeTeam?.name}`);
|
||||
console.log(`Deplasman: ${selectedMatch.awayTeam?.name}`);
|
||||
console.log(`Lig: ${selectedMatch.league?.name}`);
|
||||
console.log(`Tarih: ${new Date(Number(selectedMatch.mstUtc)).toISOString()}`);
|
||||
console.log(`Durum: ${selectedMatch.state}`);
|
||||
if (selectedMatch.scoreHome !== null) {
|
||||
console.log(
|
||||
`Skor: ${selectedMatch.scoreHome} - ${selectedMatch.scoreAway}`,
|
||||
);
|
||||
}
|
||||
console.log(`İddaa Kodu: ${selectedMatch.iddaaCode}`);
|
||||
|
||||
// =====================================
|
||||
// ADIM 1: ORAN VERİLERİNİ TOPLA
|
||||
// =====================================
|
||||
console.log('\n========================================');
|
||||
console.log('📊 ADIM 1: ORAN VERİLERİ');
|
||||
console.log('========================================');
|
||||
|
||||
const oddsData = {};
|
||||
const msCategory = selectedMatch.oddCategories.find((c) =>
|
||||
c.name?.includes('Maç Sonucu'),
|
||||
);
|
||||
if (msCategory) {
|
||||
for (const sel of msCategory.selections) {
|
||||
if (sel.name === '1') oddsData.ms_h = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === 'X') oddsData.ms_d = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === '2') oddsData.ms_a = parseFloat(sel.oddValue) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const dcCategory = selectedMatch.oddCategories.find((c) =>
|
||||
c.name?.includes('Çifte Şans'),
|
||||
);
|
||||
if (dcCategory) {
|
||||
for (const sel of dcCategory.selections) {
|
||||
if (sel.name === '1-X') oddsData.dc_1x = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === 'X-2') oddsData.dc_x2 = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === '1-2') oddsData.dc_12 = parseFloat(sel.oddValue) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const ou25Category = selectedMatch.oddCategories.find((c) =>
|
||||
c.name?.includes('2,5'),
|
||||
);
|
||||
if (ou25Category) {
|
||||
for (const sel of ou25Category.selections) {
|
||||
if (sel.name === 'Alt') oddsData.ou25_u = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === 'Üst') oddsData.ou25_o = parseFloat(sel.oddValue) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const ou15Category = selectedMatch.oddCategories.find((c) =>
|
||||
c.name?.includes('1,5'),
|
||||
);
|
||||
if (ou15Category) {
|
||||
for (const sel of ou15Category.selections) {
|
||||
if (sel.name === 'Alt') oddsData.ou15_u = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === 'Üst') oddsData.ou15_o = parseFloat(sel.oddValue) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
const bttsCategory = selectedMatch.oddCategories.find(
|
||||
(c) =>
|
||||
c.name?.toLowerCase().includes('karşılıklı') || c.name?.includes('KG'),
|
||||
);
|
||||
if (bttsCategory) {
|
||||
for (const sel of bttsCategory.selections) {
|
||||
if (sel.name === 'Var' || sel.name === 'Evet')
|
||||
oddsData.btts_y = parseFloat(sel.oddValue) || 0;
|
||||
if (sel.name === 'Yok' || sel.name === 'Hayır')
|
||||
oddsData.btts_n = parseFloat(sel.oddValue) || 0;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Toplanan Oranlar:');
|
||||
console.log(
|
||||
` MS (1/X/2): ${oddsData.ms_h || 'N/A'} / ${oddsData.ms_d || 'N/A'} / ${oddsData.ms_a || 'N/A'}`,
|
||||
);
|
||||
console.log(
|
||||
` DC (1X/X2/12): ${oddsData.dc_1x || 'N/A'} / ${oddsData.dc_x2 || 'N/A'} / ${oddsData.dc_12 || 'N/A'}`,
|
||||
);
|
||||
console.log(
|
||||
` 2.5 Alt/Üst: ${oddsData.ou25_u || 'N/A'} / ${oddsData.ou25_o || 'N/A'}`,
|
||||
);
|
||||
console.log(
|
||||
` 1.5 Alt/Üst: ${oddsData.ou15_u || 'N/A'} / ${oddsData.ou15_o || 'N/A'}`,
|
||||
);
|
||||
|
||||
// =====================================
|
||||
// ADIM 2: TAKIM İSTATİSTİKLERİ
|
||||
// =====================================
|
||||
console.log('\n========================================');
|
||||
console.log('📈 ADIM 2: TAKIM İSTATİSTİKLERİ');
|
||||
console.log('========================================');
|
||||
|
||||
// Ev sahibi son 10 maç
|
||||
const homeTeamRecentMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ homeTeamId: selectedMatch.homeTeamId },
|
||||
{ awayTeamId: selectedMatch.homeTeamId },
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
take: 10,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
// Ev sahibi istatistik hesapla
|
||||
let homeWins = 0,
|
||||
homeDraws = 0,
|
||||
homeLosses = 0;
|
||||
let homeGoalsFor = 0,
|
||||
homeGoalsAgainst = 0;
|
||||
|
||||
for (const m of homeTeamRecentMatches) {
|
||||
const isHome = m.homeTeamId === selectedMatch.homeTeamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
homeGoalsFor += gf || 0;
|
||||
homeGoalsAgainst += ga || 0;
|
||||
|
||||
if (gf > ga) homeWins++;
|
||||
else if (gf < ga) homeLosses++;
|
||||
else homeDraws++;
|
||||
}
|
||||
|
||||
console.log(`\n🏠 ${selectedMatch.homeTeam?.name} (Ev Sahibi):`);
|
||||
console.log(` Son 10 Maç: ${homeWins}G ${homeDraws}B ${homeLosses}M`);
|
||||
console.log(` Gol Averajı: ${homeGoalsFor} attı, ${homeGoalsAgainst} yedi`);
|
||||
console.log(` Ortalama Gol: ${(homeGoalsFor / 10).toFixed(2)} / maç`);
|
||||
console.log(
|
||||
` Ortalama Yenilen: ${(homeGoalsAgainst / 10).toFixed(2)} / maç`,
|
||||
);
|
||||
|
||||
// Deplasman takımı son 10 maç
|
||||
const awayTeamRecentMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ homeTeamId: selectedMatch.awayTeamId },
|
||||
{ awayTeamId: selectedMatch.awayTeamId },
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
take: 10,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
let awayWins = 0,
|
||||
awayDraws = 0,
|
||||
awayLosses = 0;
|
||||
let awayGoalsFor = 0,
|
||||
awayGoalsAgainst = 0;
|
||||
|
||||
for (const m of awayTeamRecentMatches) {
|
||||
const isHome = m.homeTeamId === selectedMatch.awayTeamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
awayGoalsFor += gf || 0;
|
||||
awayGoalsAgainst += ga || 0;
|
||||
|
||||
if (gf > ga) awayWins++;
|
||||
else if (gf < ga) awayLosses++;
|
||||
else awayDraws++;
|
||||
}
|
||||
|
||||
console.log(`\n✈️ ${selectedMatch.awayTeam?.name} (Deplasman):`);
|
||||
console.log(` Son 10 Maç: ${awayWins}G ${awayDraws}B ${awayLosses}M`);
|
||||
console.log(` Gol Averajı: ${awayGoalsFor} attı, ${awayGoalsAgainst} yedi`);
|
||||
console.log(` Ortalama Gol: ${(awayGoalsFor / 10).toFixed(2)} / maç`);
|
||||
console.log(
|
||||
` Ortalama Yenilen: ${(awayGoalsAgainst / 10).toFixed(2)} / maç`,
|
||||
);
|
||||
|
||||
// =====================================
|
||||
// ADIM 3: ELO VE FORM SKORLARI
|
||||
// =====================================
|
||||
console.log('\n========================================');
|
||||
console.log('🤖 ADIM 3: ELO & FORM SKORLARI');
|
||||
console.log('========================================');
|
||||
|
||||
if (selectedMatch.aiFeatures) {
|
||||
console.log(
|
||||
`\nEv Sahibi ELO: ${selectedMatch.aiFeatures.homeElo.toFixed(0)}`,
|
||||
);
|
||||
console.log(
|
||||
`Deplasman ELO: ${selectedMatch.aiFeatures.awayElo.toFixed(0)}`,
|
||||
);
|
||||
console.log(
|
||||
`Ev Sahibi Form: ${selectedMatch.aiFeatures.homeFormScore.toFixed(1)}/100`,
|
||||
);
|
||||
console.log(
|
||||
`Deplasman Form: ${selectedMatch.aiFeatures.awayFormScore.toFixed(1)}/100`,
|
||||
);
|
||||
console.log(
|
||||
`Eksik Oyuncu Etkisi: ${selectedMatch.aiFeatures.missingPlayersImpact}`,
|
||||
);
|
||||
} else {
|
||||
console.log('⚠️ AI Features mevcut değil');
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// ADIM 4: HEAD-TO-HEAD
|
||||
// =====================================
|
||||
console.log('\n========================================');
|
||||
console.log('🔄 ADIM 4: HEAD-TO-HEAD');
|
||||
console.log('========================================');
|
||||
|
||||
const h2hMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{
|
||||
homeTeamId: selectedMatch.homeTeamId,
|
||||
awayTeamId: selectedMatch.awayTeamId,
|
||||
},
|
||||
{
|
||||
homeTeamId: selectedMatch.awayTeamId,
|
||||
awayTeamId: selectedMatch.homeTeamId,
|
||||
},
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
},
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
take: 5,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
if (h2hMatches.length > 0) {
|
||||
let h2hHomeWins = 0,
|
||||
h2hDraws = 0,
|
||||
h2hAwayWins = 0;
|
||||
for (const m of h2hMatches) {
|
||||
const homeTeamIsHome = m.homeTeamId === selectedMatch.homeTeamId;
|
||||
const winner =
|
||||
m.scoreHome > m.scoreAway
|
||||
? 'home'
|
||||
: m.scoreHome < m.scoreAway
|
||||
? 'away'
|
||||
: 'draw';
|
||||
|
||||
if (winner === 'draw') h2hDraws++;
|
||||
else if (
|
||||
(winner === 'home' && homeTeamIsHome) ||
|
||||
(winner === 'away' && !homeTeamIsHome)
|
||||
)
|
||||
h2hHomeWins++;
|
||||
else h2hAwayWins++;
|
||||
|
||||
console.log(
|
||||
` ${m.homeTeam?.name} ${m.scoreHome} - ${m.scoreAway} ${m.awayTeam?.name}`,
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`\nKarşılıklı: ${selectedMatch.homeTeam?.name} ${h2hHomeWins}G, Beraberlik ${h2hDraws}, ${selectedMatch.awayTeam?.name} ${h2hAwayWins}G`,
|
||||
);
|
||||
} else {
|
||||
console.log('Karşılıklı maç bulunamadı');
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// ADIM 5: TAHMİN HESAPLAMA
|
||||
// =====================================
|
||||
console.log('\n========================================');
|
||||
console.log('🎯 ADIM 5: TAHMİN HESAPLAMA');
|
||||
console.log('========================================');
|
||||
|
||||
// Basit bir tahmin modeli (AI Engine'den bağımsız)
|
||||
const homeForm = selectedMatch.aiFeatures?.homeFormScore || 50;
|
||||
const awayForm = selectedMatch.aiFeatures?.awayFormScore || 50;
|
||||
const homeElo = selectedMatch.aiFeatures?.homeElo || 1500;
|
||||
const awayElo = selectedMatch.aiFeatures?.awayElo || 1500;
|
||||
|
||||
// Oranları olasılığa çevir
|
||||
const msHomeProb = oddsData.ms_h ? (1 / oddsData.ms_h) * 100 : 33;
|
||||
const msDrawProb = oddsData.ms_d ? (1 / oddsData.ms_d) * 100 : 33;
|
||||
const msAwayProb = oddsData.ms_a ? (1 / oddsData.ms_a) * 100 : 33;
|
||||
|
||||
// Normalize et
|
||||
const totalProb = msHomeProb + msDrawProb + msAwayProb;
|
||||
const normHomeProb = (msHomeProb / totalProb) * 100;
|
||||
const normDrawProb = (msDrawProb / totalProb) * 100;
|
||||
const normAwayProb = (msAwayProb / totalProb) * 100;
|
||||
|
||||
console.log('\n📊 Oran Bazlı Olasılıklar:');
|
||||
console.log(` 1 (Ev): %${normHomeProb.toFixed(1)}`);
|
||||
console.log(` X (Beraberlik): %${normDrawProb.toFixed(1)}`);
|
||||
console.log(` 2 (Deplasman): %${normAwayProb.toFixed(1)}`);
|
||||
|
||||
// Form bazlı ağırlık
|
||||
const formDiff = homeForm - awayForm;
|
||||
const eloDiff = homeElo - awayElo;
|
||||
|
||||
console.log(`\n📈 Form Farkı: ${formDiff.toFixed(1)} (Ev lehine pozitif)`);
|
||||
console.log(`📈 ELO Farkı: ${eloDiff.toFixed(0)} (Ev lehine pozitif)`);
|
||||
|
||||
// Tahmin skoru hesapla
|
||||
let homeScore = normHomeProb + formDiff * 0.3 + eloDiff * 0.02;
|
||||
let awayScore = normAwayProb - formDiff * 0.3 - eloDiff * 0.02;
|
||||
let drawScore = normDrawProb;
|
||||
|
||||
// Ev sahibi avantajı
|
||||
homeScore += 5;
|
||||
|
||||
console.log(`\n🎯 Tahmin Skorları:`);
|
||||
console.log(` Ev Sahibi: ${homeScore.toFixed(1)}`);
|
||||
console.log(` Beraberlik: ${drawScore.toFixed(1)}`);
|
||||
console.log(` Deplasman: ${awayScore.toFixed(1)}`);
|
||||
|
||||
// Final tahmin
|
||||
const maxScore = Math.max(homeScore, drawScore, awayScore);
|
||||
let prediction, confidence;
|
||||
|
||||
if (maxScore === homeScore) {
|
||||
prediction = '1 (Ev Sahibi Kazanır)';
|
||||
confidence = Math.min(95, Math.max(40, homeScore));
|
||||
} else if (maxScore === awayScore) {
|
||||
prediction = '2 (Deplasman Kazanır)';
|
||||
confidence = Math.min(95, Math.max(40, awayScore));
|
||||
} else {
|
||||
prediction = 'X (Beraberlik)';
|
||||
confidence = Math.min(95, Math.max(40, drawScore));
|
||||
}
|
||||
|
||||
// Alt/Üst tahmini
|
||||
const avgGoals =
|
||||
((homeGoalsFor + homeGoalsAgainst) / 10 +
|
||||
(awayGoalsFor + awayGoalsAgainst) / 10) /
|
||||
2;
|
||||
const ou25Prediction = avgGoals > 2.5 ? '2.5 ÜST' : '2.5 ALT';
|
||||
const ou25Confidence = Math.min(85, Math.abs(avgGoals - 2.5) * 20 + 50);
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('🏆 FİNAL TAHMİN');
|
||||
console.log('========================================');
|
||||
console.log(`\n⚽ Maç Sonucu: ${prediction}`);
|
||||
console.log(` Güven: %${confidence.toFixed(1)}`);
|
||||
console.log(`\n🎯 2.5 Alt/Üst: ${ou25Prediction}`);
|
||||
console.log(` Güven: %${ou25Confidence.toFixed(1)}`);
|
||||
console.log(` Ortalama Gol Beklentisi: ${avgGoals.toFixed(2)}`);
|
||||
|
||||
// Risk değerlendirmesi
|
||||
console.log('\n⚠️ RİSK DEĞERLENDİRMESİ:');
|
||||
const riskFactors = [];
|
||||
|
||||
if (Math.abs(homeForm - awayForm) < 10) {
|
||||
riskFactors.push('Takımların form durumları yakın - BELIRSIZ');
|
||||
}
|
||||
if (
|
||||
oddsData.ms_h &&
|
||||
oddsData.ms_a &&
|
||||
Math.abs(oddsData.ms_h - oddsData.ms_a) < 0.5
|
||||
) {
|
||||
riskFactors.push('Oranlar birbirine yakın - ZOR MAÇ');
|
||||
}
|
||||
if (h2hMatches.length < 3) {
|
||||
riskFactors.push('Yeterli H2H verisi yok');
|
||||
}
|
||||
if (!selectedMatch.aiFeatures) {
|
||||
riskFactors.push('AI özellikleri hesaplanmamış');
|
||||
}
|
||||
|
||||
if (riskFactors.length === 0) {
|
||||
console.log(' ✅ Düşük risk - Yüksek güvenilirlik');
|
||||
} else {
|
||||
for (const risk of riskFactors) {
|
||||
console.log(` ⚠️ ${risk}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Gerçek sonuç (eğer maç bitmişse)
|
||||
if (selectedMatch.state === 'postGame' && selectedMatch.scoreHome !== null) {
|
||||
console.log('\n========================================');
|
||||
console.log('📊 GERÇEK SONUÇ');
|
||||
console.log('========================================');
|
||||
const actualResult =
|
||||
selectedMatch.scoreHome > selectedMatch.scoreAway
|
||||
? '1'
|
||||
: selectedMatch.scoreHome < selectedMatch.scoreAway
|
||||
? '2'
|
||||
: 'X';
|
||||
const totalGoals =
|
||||
(selectedMatch.scoreHome || 0) + (selectedMatch.scoreAway || 0);
|
||||
const actualOU = totalGoals > 2.5 ? '2.5 ÜST' : '2.5 ALT';
|
||||
|
||||
console.log(
|
||||
`Skor: ${selectedMatch.scoreHome} - ${selectedMatch.scoreAway}`,
|
||||
);
|
||||
console.log(`Sonuç: ${actualResult}`);
|
||||
console.log(`Alt/Üst: ${actualOU} (${totalGoals} gol)`);
|
||||
}
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
analyzePredictionProcess().catch((e) => {
|
||||
console.error('Hata:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
SEARCH_TERM = '8yl'
|
||||
|
||||
def search():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url, cursor_factory=RealDictCursor)
|
||||
cursor = conn.cursor()
|
||||
|
||||
print(f"Searching for ID starting with '{SEARCH_TERM}' in live_matches...")
|
||||
cursor.execute("SELECT id, match_name FROM live_matches WHERE id LIKE %s", (SEARCH_TERM + '%',))
|
||||
live_rows = cursor.fetchall()
|
||||
for r in live_rows:
|
||||
print(f"LIVE FOUND: {r['id']} | {r['match_name']}")
|
||||
|
||||
print(f"\nSearching for ID starting with '{SEARCH_TERM}' in matches...")
|
||||
cursor.execute("SELECT id, sport, mst_utc FROM matches WHERE id LIKE %s", (SEARCH_TERM + '%',))
|
||||
match_rows = cursor.fetchall()
|
||||
for r in match_rows:
|
||||
print(f"ARCHIVE FOUND: {r['id']} | {r['sport']}")
|
||||
|
||||
if not live_rows and not match_rows:
|
||||
print("\n❌ No matches found with that partial ID.")
|
||||
print("\nShowing first 10 live matches as sample:")
|
||||
cursor.execute("SELECT id, match_name FROM live_matches LIMIT 10")
|
||||
for r in cursor.fetchall():
|
||||
print(f"SAMPLE: {r['id']} | {r['match_name']}")
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if __name__ == "__main__":
|
||||
search()
|
||||
@@ -0,0 +1,585 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function predictMatch() {
|
||||
const matchId = '2m4sef2l4im49rda90k3p41lg';
|
||||
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ GLM-5 TAHMİN - MAÇ ID: ' + matchId.substring(0, 20) + '... ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
|
||||
// MAÇ BİLGİLERİNİ GETİR
|
||||
const match = await prisma.match.findUnique({
|
||||
where: { id: matchId },
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: { include: { country: true } },
|
||||
oddCategories: {
|
||||
include: { selections: true },
|
||||
},
|
||||
officials: { include: { role: true } },
|
||||
aiFeatures: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!match) {
|
||||
console.log('❌ Maç bulunamadı!');
|
||||
await prisma.$disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('⚽ MAÇ BİLGİLERİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log(`\n🏠 Ev Sahibi: ${match.homeTeam?.name || 'Bilinmiyor'}`);
|
||||
console.log(`✈️ Deplasman: ${match.awayTeam?.name || 'Bilinmiyor'}`);
|
||||
console.log(
|
||||
`🏆 Lig: ${match.league?.name || 'Bilinmiyor'} (${match.league?.country?.name || ''})`,
|
||||
);
|
||||
console.log(
|
||||
`📅 Tarih: ${new Date(Number(match.mstUtc)).toLocaleString('tr-TR', { timeZone: 'Europe/Istanbul' })}`,
|
||||
);
|
||||
console.log(`📊 Durum: ${match.state} / ${match.status}`);
|
||||
console.log(`🔢 İddaa Kodu: ${match.iddaaCode || 'Yok'}`);
|
||||
|
||||
if (match.scoreHome !== null && match.scoreAway !== null) {
|
||||
console.log(
|
||||
`\n⚠️ DİKKAT: Maç bitmiş! Skor: ${match.scoreHome} - ${match.scoreAway}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 1. ORAN ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('📊 1. ORAN ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
const odds = {
|
||||
ms_h: null,
|
||||
ms_d: null,
|
||||
ms_a: null,
|
||||
dc_1x: null,
|
||||
dc_x2: null,
|
||||
dc_12: null,
|
||||
ou25_o: null,
|
||||
ou25_u: null,
|
||||
ou15_o: null,
|
||||
ou15_u: null,
|
||||
ou35_o: null,
|
||||
ou35_u: null,
|
||||
btts_y: null,
|
||||
btts_n: null,
|
||||
ht_h: null,
|
||||
ht_d: null,
|
||||
ht_a: null,
|
||||
};
|
||||
|
||||
console.log('\n📋 Tüm Oran Kategorileri:\n');
|
||||
for (const cat of match.oddCategories) {
|
||||
const selections = cat.selections
|
||||
.map((s) => `${s.name}: ${s.oddValue}`)
|
||||
.join(' | ');
|
||||
console.log(` ${cat.name}: ${selections}`);
|
||||
|
||||
// Maç Sonucu
|
||||
if (cat.name?.includes('Maç Sonucu') || cat.name === 'MS') {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1') odds.ms_h = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') odds.ms_d = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') odds.ms_a = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
// Çifte Şans
|
||||
if (cat.name?.includes('Çifte Şans')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1-X') odds.dc_1x = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X-2') odds.dc_x2 = parseFloat(sel.oddValue);
|
||||
if (sel.name === '1-2') odds.dc_12 = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
// 2.5 Alt/Üst
|
||||
if (cat.name?.includes('2,5') || cat.name?.includes('2.5')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === 'Alt' || sel.name === 'A')
|
||||
odds.ou25_u = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Üst' || sel.name === 'Ü')
|
||||
odds.ou25_o = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
// 1.5 Alt/Üst
|
||||
if (cat.name?.includes('1,5') || cat.name?.includes('1.5')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === 'Alt' || sel.name === 'A')
|
||||
odds.ou15_u = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Üst' || sel.name === 'Ü')
|
||||
odds.ou15_o = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
// 3.5 Alt/Üst
|
||||
if (cat.name?.includes('3,5') || cat.name?.includes('3.5')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === 'Alt' || sel.name === 'A')
|
||||
odds.ou35_u = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Üst' || sel.name === 'Ü')
|
||||
odds.ou35_o = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
// KG Var/Yok
|
||||
if (
|
||||
cat.name?.toLowerCase().includes('karşılıklı') ||
|
||||
cat.name?.includes('KG') ||
|
||||
cat.name?.includes('Gol')
|
||||
) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === 'Var' || sel.name === 'Evet' || sel.name === 'KG Var')
|
||||
odds.btts_y = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'Yok' || sel.name === 'Hayır' || sel.name === 'KG Yok')
|
||||
odds.btts_n = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
// İlk Yarı
|
||||
if (cat.name?.includes('1. Yarı') && cat.name?.includes('Sonuc')) {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1') odds.ht_h = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') odds.ht_d = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') odds.ht_a = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implied Probability Hesapla
|
||||
if (odds.ms_h && odds.ms_d && odds.ms_a) {
|
||||
const rawHome = (1 / odds.ms_h) * 100;
|
||||
const rawDraw = (1 / odds.ms_d) * 100;
|
||||
const rawAway = (1 / odds.ms_a) * 100;
|
||||
const total = rawHome + rawDraw + rawAway;
|
||||
|
||||
console.log(
|
||||
`\n📊 MS Oranları: 1=${odds.ms_h} | X=${odds.ms_d} | 2=${odds.ms_a}`,
|
||||
);
|
||||
console.log(`📈 Bookmaker Margin: %${(total - 100).toFixed(1)}`);
|
||||
console.log(`🎯 Normalize Olasılık:`);
|
||||
console.log(` 1 (Ev): %${((rawHome / total) * 100).toFixed(1)}`);
|
||||
console.log(` X (Beraberlik): %${((rawDraw / total) * 100).toFixed(1)}`);
|
||||
console.log(` 2 (Deplasman): %${((rawAway / total) * 100).toFixed(1)}`);
|
||||
|
||||
odds.normHome = (rawHome / total) * 100;
|
||||
odds.normDraw = (rawDraw / total) * 100;
|
||||
odds.normAway = (rawAway / total) * 100;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 2. TAKIM FORM ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('📈 2. TAKIM FORM ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
// Ev sahibi son 10 maç
|
||||
const homeMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [{ homeTeamId: match.homeTeamId }, { awayTeamId: match.homeTeamId }],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
include: { homeTeam: true, awayTeam: true, league: true },
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 10,
|
||||
});
|
||||
|
||||
console.log(`\n🏠 ${match.homeTeam?.name} - Son ${homeMatches.length} Maç:`);
|
||||
let homeWins = 0,
|
||||
homeDraws = 0,
|
||||
homeLosses = 0;
|
||||
let homeGoalsFor = 0,
|
||||
homeGoalsAgainst = 0;
|
||||
|
||||
for (const m of homeMatches) {
|
||||
const isHome = m.homeTeamId === match.homeTeamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
homeGoalsFor += gf || 0;
|
||||
homeGoalsAgainst += ga || 0;
|
||||
|
||||
let result;
|
||||
if (gf > ga) {
|
||||
homeWins++;
|
||||
result = '✅';
|
||||
} else if (gf < ga) {
|
||||
homeLosses++;
|
||||
result = '❌';
|
||||
} else {
|
||||
homeDraws++;
|
||||
result = '🤝';
|
||||
}
|
||||
|
||||
console.log(
|
||||
` ${result} ${m.homeTeam?.name?.substring(0, 15).padEnd(15)} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name?.substring(0, 15)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const homeFormScore = ((homeWins * 3 + homeDraws) / 30) * 100;
|
||||
console.log(
|
||||
`\n 📊 Özet: ${homeWins}G ${homeDraws}B ${homeLosses}M | ${homeGoalsFor} attı, ${homeGoalsAgainst} yedi`,
|
||||
);
|
||||
console.log(` ⚡ Form Skoru: ${homeFormScore.toFixed(1)}/100`);
|
||||
|
||||
// Deplasman son 10 maç
|
||||
const awayMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [{ homeTeamId: match.awayTeamId }, { awayTeamId: match.awayTeamId }],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
include: { homeTeam: true, awayTeam: true, league: true },
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 10,
|
||||
});
|
||||
|
||||
console.log(`\n✈️ ${match.awayTeam?.name} - Son ${awayMatches.length} Maç:`);
|
||||
let awayWins = 0,
|
||||
awayDraws = 0,
|
||||
awayLosses = 0;
|
||||
let awayGoalsFor = 0,
|
||||
awayGoalsAgainst = 0;
|
||||
|
||||
for (const m of awayMatches) {
|
||||
const isHome = m.homeTeamId === match.awayTeamId;
|
||||
const gf = isHome ? m.scoreHome : m.scoreAway;
|
||||
const ga = isHome ? m.scoreAway : m.scoreHome;
|
||||
awayGoalsFor += gf || 0;
|
||||
awayGoalsAgainst += ga || 0;
|
||||
|
||||
let result;
|
||||
if (gf > ga) {
|
||||
awayWins++;
|
||||
result = '✅';
|
||||
} else if (gf < ga) {
|
||||
awayLosses++;
|
||||
result = '❌';
|
||||
} else {
|
||||
awayDraws++;
|
||||
result = '🤝';
|
||||
}
|
||||
|
||||
console.log(
|
||||
` ${result} ${m.homeTeam?.name?.substring(0, 15).padEnd(15)} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name?.substring(0, 15)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const awayFormScore = ((awayWins * 3 + awayDraws) / 30) * 100;
|
||||
console.log(
|
||||
`\n 📊 Özet: ${awayWins}G ${awayDraws}B ${awayLosses}M | ${awayGoalsFor} attı, ${awayGoalsAgainst} yedi`,
|
||||
);
|
||||
console.log(` ⚡ Form Skoru: ${awayFormScore.toFixed(1)}/100`);
|
||||
|
||||
const formDiff = homeFormScore - awayFormScore;
|
||||
console.log(
|
||||
`\n📈 Form Farkı: ${formDiff > 0 ? '+' : ''}${formDiff.toFixed(1)} (${formDiff > 0 ? 'Ev lehine' : 'Deplasman lehine'})`,
|
||||
);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 3. HEAD-TO-HEAD
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('🔄 3. HEAD-TO-HEAD (Karşılıklı Maçlar)');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
const h2hMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ homeTeamId: match.homeTeamId, awayTeamId: match.awayTeamId },
|
||||
{ homeTeamId: match.awayTeamId, awayTeamId: match.homeTeamId },
|
||||
],
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
},
|
||||
include: { homeTeam: true, awayTeam: true },
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
take: 5,
|
||||
});
|
||||
|
||||
if (h2hMatches.length > 0) {
|
||||
let h2hHomeWins = 0,
|
||||
h2hDraws = 0,
|
||||
h2hAwayWins = 0;
|
||||
|
||||
for (const m of h2hMatches) {
|
||||
const homeTeamIsHome = m.homeTeamId === match.homeTeamId;
|
||||
const result =
|
||||
m.scoreHome > m.scoreAway
|
||||
? homeTeamIsHome
|
||||
? '1'
|
||||
: '2'
|
||||
: m.scoreHome < m.scoreAway
|
||||
? homeTeamIsHome
|
||||
? '2'
|
||||
: '1'
|
||||
: 'X';
|
||||
|
||||
if (result === '1') h2hHomeWins++;
|
||||
else if (result === '2') h2hAwayWins++;
|
||||
else h2hDraws++;
|
||||
|
||||
console.log(
|
||||
` ${m.homeTeam?.name} ${m.scoreHome}-${m.scoreAway} ${m.awayTeam?.name} [${result}]`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`\n 📊 H2H: ${match.homeTeam?.name} ${h2hHomeWins}G, ${h2hDraws}B, ${match.awayTeam?.name} ${h2hAwayWins}G`,
|
||||
);
|
||||
} else {
|
||||
console.log(' ⚠️ Karşılıklı maç bulunamadı');
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 4. HAKEM ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('👨⚖️ 4. HAKEM ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
const mainReferee = match.officials?.find(
|
||||
(o) => o.role?.name === 'Orta Hakem',
|
||||
);
|
||||
if (mainReferee) {
|
||||
console.log(`\n👤 Hakem: ${mainReferee.name}`);
|
||||
|
||||
const refereeMatches = await prisma.matchOfficial.findMany({
|
||||
where: { name: mainReferee.name, roleId: 1 },
|
||||
include: { match: true },
|
||||
take: 20,
|
||||
});
|
||||
|
||||
let refHomeWins = 0,
|
||||
refDraws = 0,
|
||||
refAwayWins = 0;
|
||||
|
||||
for (const rm of refereeMatches) {
|
||||
if (rm.match?.scoreHome !== null && rm.match?.scoreAway !== null) {
|
||||
if (rm.match.scoreHome > rm.match.scoreAway) refHomeWins++;
|
||||
else if (rm.match.scoreHome < rm.match.scoreAway) refAwayWins++;
|
||||
else refDraws++;
|
||||
}
|
||||
}
|
||||
|
||||
const refTotal = refHomeWins + refDraws + refAwayWins;
|
||||
if (refTotal > 0) {
|
||||
console.log(` 📊 Yönettiği ${refTotal} maç:`);
|
||||
console.log(
|
||||
` Ev sahibi kazanma: %${((refHomeWins / refTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Beraberlik: %${((refDraws / refTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
console.log(
|
||||
` Deplasman kazanma: %${((refAwayWins / refTotal) * 100).toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log(' ⚠️ Hakem bilgisi yok');
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 5. LİG ANALİZİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('🏆 5. LİG ANALİZİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
|
||||
const leagueMatches = await prisma.match.findMany({
|
||||
where: { leagueId: match.leagueId, sport: 'football', state: 'postGame' },
|
||||
take: 100,
|
||||
});
|
||||
|
||||
let lgHomeWins = 0,
|
||||
lgDraws = 0,
|
||||
lgAwayWins = 0,
|
||||
lgGoals = 0;
|
||||
|
||||
for (const lm of leagueMatches) {
|
||||
if (lm.scoreHome !== null && lm.scoreAway !== null) {
|
||||
lgGoals += lm.scoreHome + lm.scoreAway;
|
||||
if (lm.scoreHome > lm.scoreAway) lgHomeWins++;
|
||||
else if (lm.scoreHome < lm.scoreAway) lgAwayWins++;
|
||||
else lgDraws++;
|
||||
}
|
||||
}
|
||||
|
||||
const lgTotal = lgHomeWins + lgDraws + lgAwayWins;
|
||||
if (lgTotal > 0) {
|
||||
console.log(
|
||||
`\n📊 ${match.league?.name} - İstatistikler (son ${lgTotal} maç):`,
|
||||
);
|
||||
console.log(` Ev kazanma: %${((lgHomeWins / lgTotal) * 100).toFixed(1)}`);
|
||||
console.log(` Beraberlik: %${((lgDraws / lgTotal) * 100).toFixed(1)}`);
|
||||
console.log(` Deplasman: %${((lgAwayWins / lgTotal) * 100).toFixed(1)}`);
|
||||
console.log(` Ortalama gol: ${(lgGoals / lgTotal).toFixed(2)}/maç`);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// 6. AI FEATURES (VARSa)
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
if (match.aiFeatures) {
|
||||
console.log(
|
||||
'\n═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log('🤖 6. AI ÖZELLİKLERİ');
|
||||
console.log(
|
||||
'═══════════════════════════════════════════════════════════════',
|
||||
);
|
||||
console.log(`\n Home ELO: ${match.aiFeatures.homeElo.toFixed(0)}`);
|
||||
console.log(` Away ELO: ${match.aiFeatures.awayElo.toFixed(0)}`);
|
||||
console.log(` Home Form: ${match.aiFeatures.homeFormScore.toFixed(1)}`);
|
||||
console.log(` Away Form: ${match.aiFeatures.awayFormScore.toFixed(1)}`);
|
||||
console.log(
|
||||
` Eksik Oyuncu Etkisi: ${match.aiFeatures.missingPlayersImpact}`,
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// FİNAL TAHMİN
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ 🎯 GLM-5 FİNAL TAHMİN ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
|
||||
// Ağırlıklar
|
||||
const w = { odds: 0.4, form: 0.3, home: 0.15, league: 0.15 };
|
||||
|
||||
let homeScore =
|
||||
(odds.normHome || 33) * w.odds +
|
||||
homeFormScore * w.form +
|
||||
8 * w.home + // Ev avantajı
|
||||
(lgHomeWins / lgTotal) * 100 * w.league;
|
||||
|
||||
let awayScore =
|
||||
(odds.normAway || 33) * w.odds +
|
||||
awayFormScore * w.form +
|
||||
0 * w.home +
|
||||
(lgAwayWins / lgTotal) * 100 * w.league;
|
||||
|
||||
let drawScore =
|
||||
(odds.normDraw || 33) * w.odds +
|
||||
(100 - Math.abs(formDiff)) * 0.2 * w.form +
|
||||
(lgDraws / lgTotal) * 100 * w.league;
|
||||
|
||||
const total = homeScore + drawScore + awayScore;
|
||||
const finalHome = (homeScore / total) * 100;
|
||||
const finalDraw = (drawScore / total) * 100;
|
||||
const finalAway = (awayScore / total) * 100;
|
||||
|
||||
console.log(`\n📊 Olasılıklar:`);
|
||||
console.log(
|
||||
` 1 (${match.homeTeam?.name?.substring(0, 15)}): %${finalHome.toFixed(1)}`,
|
||||
);
|
||||
console.log(` X (Beraberlik): %${finalDraw.toFixed(1)}`);
|
||||
console.log(
|
||||
` 2 (${match.awayTeam?.name?.substring(0, 15)}): %${finalAway.toFixed(1)}`,
|
||||
);
|
||||
|
||||
// Tahmin
|
||||
let prediction, confidence;
|
||||
if (finalHome > finalDraw && finalHome > finalAway) {
|
||||
prediction = '1';
|
||||
confidence = finalHome;
|
||||
} else if (finalAway > finalDraw) {
|
||||
prediction = '2';
|
||||
confidence = finalAway;
|
||||
} else {
|
||||
prediction = 'X';
|
||||
confidence = finalDraw;
|
||||
}
|
||||
|
||||
// Alt/Üst
|
||||
const avgGoals =
|
||||
(homeGoalsFor + homeGoalsAgainst + awayGoalsFor + awayGoalsAgainst) / 20;
|
||||
const ou25 = avgGoals > 2.5 ? 'ÜST' : 'ALT';
|
||||
const ou25Conf = Math.min(80, Math.abs(avgGoals - 2.5) * 30 + 50);
|
||||
|
||||
console.log(`\n🏆 TAHMİN: ${prediction}`);
|
||||
console.log(` Güven: %${confidence.toFixed(1)}`);
|
||||
console.log(`\n⚽ 2.5 ${ou25}`);
|
||||
console.log(` Güven: %${ou25Conf.toFixed(1)}`);
|
||||
console.log(` Beklenen Gol: ${avgGoals.toFixed(2)}`);
|
||||
|
||||
// Gerçek sonuç (eğer maç bitmişse)
|
||||
if (match.scoreHome !== null && match.scoreAway !== null) {
|
||||
const actual =
|
||||
match.scoreHome > match.scoreAway
|
||||
? '1'
|
||||
: match.scoreHome < match.scoreAway
|
||||
? '2'
|
||||
: 'X';
|
||||
const actualGoals = match.scoreHome + match.scoreAway;
|
||||
const actualOU = actualGoals > 2.5 ? 'ÜST' : 'ALT';
|
||||
|
||||
console.log(
|
||||
`\n═══════════════════════════════════════════════════════════════`,
|
||||
);
|
||||
console.log(`📊 GERÇEK SONUÇ`);
|
||||
console.log(
|
||||
`═══════════════════════════════════════════════════════════════`,
|
||||
);
|
||||
console.log(` Skor: ${match.scoreHome} - ${match.scoreAway}`);
|
||||
console.log(` Sonuç: ${actual}`);
|
||||
console.log(` Alt/Üst: ${actualOU} (${actualGoals} gol)`);
|
||||
|
||||
const msCorrect = prediction === actual;
|
||||
const ouCorrect = ou25 === actualOU;
|
||||
|
||||
console.log(`\n 🎯 MS Tahmin: ${msCorrect ? '✅ DOĞRU' : '❌ YANLIŞ'}`);
|
||||
console.log(` 🎯 2.5 Tahmin: ${ouCorrect ? '✅ DOĞRU' : '❌ YANLIŞ'}`);
|
||||
}
|
||||
|
||||
console.log('\n');
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
predictMatch().catch(console.error);
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/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')
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/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)
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/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')
|
||||
@@ -0,0 +1,462 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* 🎯 SÜRPRİZ AVCISI (Upset Hunter)
|
||||
* ================================
|
||||
* Favorilerin kaybettiği maçları analiz ederek,
|
||||
* gelecekteki sürprizleri önceden tespit etmeye çalışıyoruz.
|
||||
*/
|
||||
|
||||
async function upsetHunter() {
|
||||
console.log('\n');
|
||||
console.log(
|
||||
'╔══════════════════════════════════════════════════════════════════╗',
|
||||
);
|
||||
console.log(
|
||||
'║ 🎯 SÜRPRİZ AVCISI (Upset Hunter) ║',
|
||||
);
|
||||
console.log(
|
||||
'║ Favorilerin kaybettiği maçların analizi ║',
|
||||
);
|
||||
console.log(
|
||||
'╚══════════════════════════════════════════════════════════════════╝',
|
||||
);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// BÖLÜM 1: FAVORİ KAYIPLARINI BUL
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n📊 BÖLÜM 1: FAVORİ KAYIPLARINI TESPİT ET');
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
// Favori: MS oranı 1.60'tan düşük olanlar
|
||||
// Favori kaybı: Favori takımın kaybettiği maçlar
|
||||
|
||||
// Bitmiş maçları ve oranlarını al
|
||||
const finishedMatches = await prisma.match.findMany({
|
||||
where: {
|
||||
sport: 'football',
|
||||
state: 'postGame',
|
||||
scoreHome: { not: null },
|
||||
scoreAway: { not: null },
|
||||
},
|
||||
include: {
|
||||
homeTeam: true,
|
||||
awayTeam: true,
|
||||
league: true,
|
||||
oddCategories: {
|
||||
include: { selections: true },
|
||||
},
|
||||
officials: { include: { role: true } },
|
||||
},
|
||||
take: 500,
|
||||
orderBy: { mstUtc: 'desc' },
|
||||
});
|
||||
|
||||
console.log(`\nAnaliz edilen maç sayısı: ${finishedMatches.length}`);
|
||||
|
||||
// Favori kayıplarını tespit et
|
||||
const upsets = [];
|
||||
const normalResults = [];
|
||||
|
||||
for (const match of finishedMatches) {
|
||||
// MS oranlarını bul
|
||||
let msHome = null,
|
||||
msDraw = null,
|
||||
msAway = null;
|
||||
|
||||
for (const cat of match.oddCategories) {
|
||||
if (cat.name?.includes('Maç Sonucu') || cat.name === 'MS') {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1') msHome = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') msDraw = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') msAway = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!msHome || !msDraw || !msAway) continue;
|
||||
|
||||
// Sonucu belirle
|
||||
const result =
|
||||
match.scoreHome > match.scoreAway
|
||||
? '1'
|
||||
: match.scoreHome < match.scoreAway
|
||||
? '2'
|
||||
: 'X';
|
||||
|
||||
// Favori kim?
|
||||
const favorite = msHome < msAway ? '1' : msAway < msHome ? '2' : 'draw';
|
||||
const favoriteOdds = favorite === '1' ? msHome : msAway;
|
||||
|
||||
// Sadece net favori olan maçları al (1.60 altı)
|
||||
if (favoriteOdds < 1.6) {
|
||||
// Favori kaybetti mi?
|
||||
const isUpset =
|
||||
(favorite === '1' && result === '2') ||
|
||||
(favorite === '2' && result === '1');
|
||||
|
||||
// X de favori kaybı sayılır (favori kazanamadı)
|
||||
const isDrawUpset = result === 'X';
|
||||
|
||||
if (isUpset || isDrawUpset) {
|
||||
upsets.push({
|
||||
match,
|
||||
favorite,
|
||||
favoriteOdds,
|
||||
result,
|
||||
msHome,
|
||||
msDraw,
|
||||
msAway,
|
||||
isUpset,
|
||||
isDrawUpset,
|
||||
});
|
||||
} else {
|
||||
normalResults.push({
|
||||
match,
|
||||
favorite,
|
||||
favoriteOdds,
|
||||
result,
|
||||
msHome,
|
||||
msDraw,
|
||||
msAway,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n📈 Sonuçlar:`);
|
||||
console.log(` Favori kazandı: ${normalResults.length} maç`);
|
||||
console.log(
|
||||
` Favori kaybetti (SÜRPRİZ): ${upsets.filter((u) => u.isUpset).length} maç`,
|
||||
);
|
||||
console.log(
|
||||
` Favori berabere: ${upsets.filter((u) => u.isDrawUpset).length} maç`,
|
||||
);
|
||||
|
||||
// Sürpriz oranı
|
||||
const totalFavMatches = normalResults.length + upsets.length;
|
||||
const upsetRate = (
|
||||
(upsets.filter((u) => u.isUpset).length / totalFavMatches) *
|
||||
100
|
||||
).toFixed(1);
|
||||
console.log(`\n⚠️ Favori kayıp oranı: %${upsetRate}`);
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// BÖLÜM 2: SÜRPRİZ MAÇLARIN ORTAK ÖZELLİKLERİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n\n🔍 BÖLÜM 2: SÜRPRİZ MAÇLARIN ORTAK ÖZELLİKLERİ');
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
// Sadece net sürprizleri (favori kaybı) analiz et
|
||||
const realUpsets = upsets.filter((u) => u.isUpset);
|
||||
|
||||
// 2.1 ORAN ANALİZİ
|
||||
console.log('\n📊 2.1 ORAN ANALİZİ:');
|
||||
|
||||
// Bookmaker margin analizi
|
||||
let upsetMargins = [];
|
||||
let normalMargins = [];
|
||||
|
||||
for (const upset of realUpsets) {
|
||||
const margin = 1 / upset.msHome + 1 / upset.msDraw + 1 / upset.msAway - 1;
|
||||
upsetMargins.push(margin);
|
||||
}
|
||||
|
||||
for (const normal of normalResults) {
|
||||
const margin =
|
||||
1 / normal.msHome + 1 / normal.msDraw + 1 / normal.msAway - 1;
|
||||
normalMargins.push(margin);
|
||||
}
|
||||
|
||||
const avgUpsetMargin =
|
||||
upsetMargins.length > 0
|
||||
? (
|
||||
(upsetMargins.reduce((a, b) => a + b, 0) / upsetMargins.length) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: 0;
|
||||
const avgNormalMargin =
|
||||
normalMargins.length > 0
|
||||
? (
|
||||
(normalMargins.reduce((a, b) => a + b, 0) / normalMargins.length) *
|
||||
100
|
||||
).toFixed(1)
|
||||
: 0;
|
||||
|
||||
console.log(
|
||||
` Sürpriz maçlarda ortalama bookmaker margin: %${avgUpsetMargin}`,
|
||||
);
|
||||
console.log(
|
||||
` Normal maçlarda ortalama bookmaker margin: %${avgNormalMargin}`,
|
||||
);
|
||||
|
||||
// Margin farkı yüksek mi?
|
||||
if (parseFloat(avgUpsetMargin) > parseFloat(avgNormalMargin) + 2) {
|
||||
console.log(` ⚠️ DİKKAT: Sürpriz maçlarda margin YÜKSEK! Şüpheli!`);
|
||||
}
|
||||
|
||||
// 2.2 FAVORİ ORAN ARALIĞI
|
||||
console.log('\n📊 2.2 FAVORİ ORAN ARALIĞI:');
|
||||
|
||||
const upsetOddsRanges = {
|
||||
'1.10-1.20': 0,
|
||||
'1.20-1.30': 0,
|
||||
'1.30-1.40': 0,
|
||||
'1.40-1.50': 0,
|
||||
'1.50-1.60': 0,
|
||||
};
|
||||
const normalOddsRanges = {
|
||||
'1.10-1.20': 0,
|
||||
'1.20-1.30': 0,
|
||||
'1.30-1.40': 0,
|
||||
'1.40-1.50': 0,
|
||||
'1.50-1.60': 0,
|
||||
};
|
||||
|
||||
for (const upset of realUpsets) {
|
||||
const odds = upset.favoriteOdds;
|
||||
if (odds >= 1.1 && odds < 1.2) upsetOddsRanges['1.10-1.20']++;
|
||||
else if (odds >= 1.2 && odds < 1.3) upsetOddsRanges['1.20-1.30']++;
|
||||
else if (odds >= 1.3 && odds < 1.4) upsetOddsRanges['1.30-1.40']++;
|
||||
else if (odds >= 1.4 && odds < 1.5) upsetOddsRanges['1.40-1.50']++;
|
||||
else if (odds >= 1.5 && odds < 1.6) upsetOddsRanges['1.50-1.60']++;
|
||||
}
|
||||
|
||||
for (const normal of normalResults) {
|
||||
const odds = normal.favoriteOdds;
|
||||
if (odds >= 1.1 && odds < 1.2) normalOddsRanges['1.10-1.20']++;
|
||||
else if (odds >= 1.2 && odds < 1.3) normalOddsRanges['1.20-1.30']++;
|
||||
else if (odds >= 1.3 && odds < 1.4) normalOddsRanges['1.30-1.40']++;
|
||||
else if (odds >= 1.4 && odds < 1.5) normalOddsRanges['1.40-1.50']++;
|
||||
else if (odds >= 1.5 && odds < 1.6) normalOddsRanges['1.50-1.60']++;
|
||||
}
|
||||
|
||||
console.log('\n Sürpriz maçlarda favori oran dağılımı:');
|
||||
for (const [range, count] of Object.entries(upsetOddsRanges)) {
|
||||
const total = count + normalOddsRanges[range];
|
||||
const pct = total > 0 ? ((count / total) * 100).toFixed(1) : 0;
|
||||
console.log(
|
||||
` ${range}: ${count} sürpriz / ${total} toplam → %${pct} sürpriz oranı`,
|
||||
);
|
||||
}
|
||||
|
||||
// 2.3 HAKEM ANALİZİ
|
||||
console.log('\n📊 2.3 HAKEM ANALİZİ:');
|
||||
|
||||
const upsetReferees = {};
|
||||
for (const upset of realUpsets) {
|
||||
const referee = upset.match.officials?.find(
|
||||
(o) => o.role?.name === 'Orta Hakem',
|
||||
);
|
||||
if (referee) {
|
||||
if (!upsetReferees[referee.name]) {
|
||||
upsetReferees[referee.name] = { upsets: 0, total: 0 };
|
||||
}
|
||||
upsetReferees[referee.name].upsets++;
|
||||
}
|
||||
}
|
||||
|
||||
// Tüm maçlarda bu hakemler
|
||||
const allReferees = {};
|
||||
for (const match of finishedMatches) {
|
||||
const referee = match.officials?.find((o) => o.role?.name === 'Orta Hakem');
|
||||
if (referee) {
|
||||
if (!allReferees[referee.name]) {
|
||||
allReferees[referee.name] = 0;
|
||||
}
|
||||
allReferees[referee.name]++;
|
||||
}
|
||||
}
|
||||
|
||||
// En çok sürprize sebep olan hakemler
|
||||
const sortedUpsetReferees = Object.entries(upsetReferees)
|
||||
.map(([name, data]) => ({
|
||||
name,
|
||||
upsets: data.upsets,
|
||||
total: allReferees[name] || data.upsets,
|
||||
rate: ((data.upsets / (allReferees[name] || data.upsets)) * 100).toFixed(
|
||||
1,
|
||||
),
|
||||
}))
|
||||
.filter((r) => r.total >= 3) // En az 3 maç yönetenler
|
||||
.sort((a, b) => parseFloat(b.rate) - parseFloat(a.rate));
|
||||
|
||||
console.log('\n En çok sürpriz yaşatan hakemler:');
|
||||
for (const ref of sortedUpsetReferees.slice(0, 5)) {
|
||||
console.log(
|
||||
` ${ref.name}: ${ref.upsets}/${ref.total} maç → %${ref.rate} sürpriz`,
|
||||
);
|
||||
}
|
||||
|
||||
// 2.4 LİG ANALİZİ
|
||||
console.log('\n📊 2.4 LİG ANALİZİ:');
|
||||
|
||||
const upsetLeagues = {};
|
||||
const allLeagues = {};
|
||||
|
||||
for (const upset of realUpsets) {
|
||||
const leagueName = upset.match.league?.name || 'Bilinmeyen';
|
||||
if (!upsetLeagues[leagueName]) upsetLeagues[leagueName] = 0;
|
||||
upsetLeagues[leagueName]++;
|
||||
}
|
||||
|
||||
for (const match of finishedMatches) {
|
||||
const leagueName = match.league?.name || 'Bilinmeyen';
|
||||
if (!allLeagues[leagueName]) allLeagues[leagueName] = 0;
|
||||
allLeagues[leagueName]++;
|
||||
}
|
||||
|
||||
const sortedUpsetLeagues = Object.entries(upsetLeagues)
|
||||
.map(([name, count]) => ({
|
||||
name,
|
||||
upsets: count,
|
||||
total: allLeagues[name] || count,
|
||||
rate: ((count / (allLeagues[name] || count)) * 100).toFixed(1),
|
||||
}))
|
||||
.filter((l) => l.total >= 5)
|
||||
.sort((a, b) => parseFloat(b.rate) - parseFloat(a.rate));
|
||||
|
||||
console.log('\n En çok sürpriz yaşanan ligler:');
|
||||
for (const league of sortedUpsetLeagues.slice(0, 5)) {
|
||||
console.log(
|
||||
` ${league.name}: ${league.upsets}/${league.total} maç → %${league.rate} sürpriz`,
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// BÖLÜM 3: SÜRPRİZ TESPİT MODELİ
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n\n🎯 BÖLÜM 3: SÜRPRİZ TESPİT İŞARETLERİ');
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
console.log(`
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ SÜRPRİZ TESPİT İŞARETLERİ (Upset Indicators) │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 1. ⚠️ YÜKSEK MARGIN (%18+) │
|
||||
│ → Bookmaker kendini koruyor, favori riskli │
|
||||
│ │
|
||||
│ 2. 👨⚖️ SÜRPRİZ HAKEM │
|
||||
│ → Bazı hakemler favorilere karşı sert │
|
||||
│ │
|
||||
│ 3. 📉 ORAN HAREKETİ │
|
||||
│ → Favori oranı yükseliyorsa, para dışarı akıyor │
|
||||
│ │
|
||||
│ 4. 🏆 DERBİ/ÖZEL MAÇ │
|
||||
│ → Form tablosu işlemez, motivasyon farkı │
|
||||
│ │
|
||||
│ 5. 📊 ÇOK DÜŞÜK FAVORİ ORANI (<1.20) │
|
||||
│ → "Çok iyi görünen" fırsatlar genelde tuzak │
|
||||
│ │
|
||||
│ 6. 🔄 H2H SÜRPRİZ GEÇMİŞİ │
|
||||
│ → Geçmişte sürpriz olmuşsa tekrar edebilir │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
`);
|
||||
|
||||
// Real Madrid örneği
|
||||
console.log('\n📌 REAL MADRID vs GETAFE ANALİZİ (Sürpriz Neden Oldu?):');
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
const realMadridMatch = finishedMatches.find(
|
||||
(m) => m.id === '2m4sef2l4im49rda90k3p41lg',
|
||||
);
|
||||
if (realMadridMatch) {
|
||||
let msH = null,
|
||||
msD = null,
|
||||
msA = null;
|
||||
for (const cat of realMadridMatch.oddCategories) {
|
||||
if (cat.name?.includes('Maç Sonucu') || cat.name === 'MS') {
|
||||
for (const sel of cat.selections) {
|
||||
if (sel.name === '1') msH = parseFloat(sel.oddValue);
|
||||
if (sel.name === 'X') msD = parseFloat(sel.oddValue);
|
||||
if (sel.name === '2') msA = parseFloat(sel.oddValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const margin =
|
||||
msH && msD && msA
|
||||
? ((1 / msH + 1 / msD + 1 / msA - 1) * 100).toFixed(1)
|
||||
: 'N/A';
|
||||
|
||||
console.log(`\n 📊 Oranlar: 1=${msH} | X=${msD} | 2=${msA}`);
|
||||
console.log(
|
||||
` 📈 Bookmaker Margin: %${margin} ${parseFloat(margin) > 18 ? '⚠️ YÜKSEK!' : ''}`,
|
||||
);
|
||||
|
||||
const referee = realMadridMatch.officials?.find(
|
||||
(o) => o.role?.name === 'Orta Hakem',
|
||||
);
|
||||
if (referee) {
|
||||
const refUpsetData = upsetReferees[referee.name];
|
||||
console.log(` 👨⚖️ Hakem: ${referee.name}`);
|
||||
if (refUpsetData) {
|
||||
console.log(
|
||||
` Bu hakemde sürpriz oranı: %${((refUpsetData.upsets / allReferees[referee.name]) * 100).toFixed(1)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n ✅ SÜRPRİZ İŞARETLERİ:`);
|
||||
if (parseFloat(margin) > 18) {
|
||||
console.log(` ⚠️ Margin yüksek → Bookmaker risk görüyordu`);
|
||||
}
|
||||
console.log(` 🏆 Madrid derbisi → Derbide form işlemez`);
|
||||
console.log(` 📉 Favori oranı çok düşük (1.25) → "Tuzak" oranı`);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
// BÖLÜM 4: SÜRPRİZ TAHMİN FONKSİYONU
|
||||
// ═════════════════════════════════════════════════════════════════
|
||||
console.log('\n\n🎯 BÖLÜM 4: SÜRPRİZ TAHMİN FONKSİYONU');
|
||||
console.log('─'.repeat(60));
|
||||
|
||||
console.log(`
|
||||
// Sürpriz Skoru Hesaplama
|
||||
function calculateUpsetScore(match, odds, referee, league) {
|
||||
let score = 0;
|
||||
|
||||
// 1. Margin Kontrolü
|
||||
const margin = (1/odds.ms_h + 1/odds.ms_d + 1/odds.ms_a) - 1;
|
||||
if (margin > 0.20) score += 15; // Yüksek margin = risk
|
||||
|
||||
// 2. Hakem Faktörü
|
||||
if (referee.upsetRate > 25) score += 20;
|
||||
else if (referee.upsetRate > 20) score += 10;
|
||||
|
||||
// 3. Favori Oran Çok Düşük
|
||||
if (odds.favorite < 1.20) score += 25; // Tuzak oranı
|
||||
else if (odds.favorite < 1.30) score += 15;
|
||||
|
||||
// 4. Derbi/Özel Maç
|
||||
if (isDerby(match)) score += 15;
|
||||
|
||||
// 5. H2H Sürpriz Geçmişi
|
||||
if (h2h.upsetCount > 0) score += 10;
|
||||
|
||||
// 6. Form Farkı Çok Büyük
|
||||
if (formDiff > 40) score += 10; // "Çok iyi" görünüyorsa risk
|
||||
|
||||
return score; // 0-100 arası
|
||||
}
|
||||
|
||||
// EŞİK: 50+ = Sürpriz bekleniyor, Value bet var
|
||||
`);
|
||||
|
||||
console.log('\n📌 ÖRNEK: Real Madrid vs Getafe için sürpriz skoru:');
|
||||
console.log(' Margin (%20.1 > %18): +15');
|
||||
console.log(' Favori oran (1.25 < 1.30): +15');
|
||||
console.log(' Derbi maçı: +15');
|
||||
console.log(' Hakem sürpriz oranı yüksek: +10');
|
||||
console.log(' ─────────────────────────────');
|
||||
console.log(' TOPLAM: 55 → ⚠️ SÜRPRİZ BEKLENİYOR!');
|
||||
|
||||
console.log('\n');
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
upsetHunter().catch(console.error);
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
from psycopg2.extras import RealDictCursor
|
||||
|
||||
def verify_bayern():
|
||||
try:
|
||||
db_url = os.environ.get('DATABASE_URL', 'postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db')
|
||||
conn = psycopg2.connect(db_url)
|
||||
cursor = conn.cursor(cursor_factory=RealDictCursor)
|
||||
|
||||
print("\n🔍 BAYERN MUNICH 'CRISIS' CHECK")
|
||||
|
||||
cursor.execute("""
|
||||
SELECT
|
||||
m.mst_utc,
|
||||
ht.name as home, at.name as away,
|
||||
m.score_home, m.score_away
|
||||
FROM matches m
|
||||
JOIN teams ht ON m.home_team_id = ht.id
|
||||
JOIN teams at ON m.away_team_id = at.id
|
||||
WHERE (ht.name ILIKE '%Bayern Münih%' OR at.name ILIKE '%Bayern Münih%')
|
||||
AND (m.score_home >= 4 OR m.score_away >= 4)
|
||||
ORDER BY m.mst_utc DESC
|
||||
LIMIT 5
|
||||
""")
|
||||
|
||||
results = cursor.fetchall()
|
||||
for r in results:
|
||||
print(f"{r['home']} {r['score_home']} - {r['score_away']} {r['away']}")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
verify_bayern()
|
||||
Reference in New Issue
Block a user