first (part 2: other directories)
Deploy Iddaai Backend / build-and-deploy (push) Failing after 18s

This commit is contained in:
2026-04-16 15:11:25 +03:00
parent 7814e0bc6b
commit 2f0b85a0c7
203 changed files with 59989 additions and 0 deletions
+100
View File
@@ -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()
+109
View File
@@ -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()
+59
View File
@@ -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()
+23
View File
@@ -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()
+61
View File
@@ -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()
+78
View File
@@ -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()
+24
View File
@@ -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()
+20
View File
@@ -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()
+18
View File
@@ -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}")
+22
View File
@@ -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()
+24
View File
@@ -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()
+23
View File
@@ -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());
+20
View File
@@ -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}")
+58
View File
@@ -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()
+69
View File
@@ -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()
+311
View File
@@ -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);
});
+94
View File
@@ -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()
+140
View File
@@ -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
+171
View File
@@ -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"
+22
View File
@@ -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()
+214
View File
@@ -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);
});
+88
View File
@@ -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()
+22
View File
@@ -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()
+132
View File
@@ -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()
+457
View File
@@ -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);
+40
View File
@@ -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()
+66
View File
@@ -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()
+51
View File
@@ -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()
+60
View File
@@ -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()
+66
View File
@@ -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());
+32
View File
@@ -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()
+655
View File
@@ -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);
+265
View File
@@ -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()
+500
View File
@@ -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);
});
+37
View File
@@ -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()
+585
View File
@@ -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);
+58
View File
@@ -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')
+95
View File
@@ -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)
+65
View File
@@ -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')
+462
View File
@@ -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);
+37
View File
@@ -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()