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);