Files
iddaai-be/scripts/upset-hunter.js
fahricansecer 2f0b85a0c7
Deploy Iddaai Backend / build-and-deploy (push) Failing after 18s
first (part 2: other directories)
2026-04-16 15:11:25 +03:00

463 lines
17 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);