458 lines
17 KiB
JavaScript
458 lines
17 KiB
JavaScript
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);
|