first (part 1: root files)
Deploy Iddaai Backend / build-and-deploy (push) Failing after 4s

This commit is contained in:
2026-04-16 15:09:10 +03:00
parent b4173c10bb
commit 7814e0bc6b
38 changed files with 18494 additions and 0 deletions
+365
View File
@@ -0,0 +1,365 @@
import { PrismaClient } from '@prisma/client';
import * as dotenv from 'dotenv';
dotenv.config();
(BigInt.prototype as any).toJSON = function () {
return this.toString();
};
const prisma = new PrismaClient();
async function analyzeReversalMatches() {
console.log('🔍 ANALYZING HT/FT REVERSAL MATCHES (1/2 & 2/1)');
console.log('='.repeat(80));
// Fetch all completed matches with HT and FT scores
const matches = await prisma.match.findMany({
where: {
status: 'FT',
htScoreHome: { not: null },
htScoreAway: { not: null },
scoreHome: { not: null },
scoreAway: { not: null },
oddCategories: { some: {} }
},
include: {
homeTeam: true,
awayTeam: true,
league: true,
oddCategories: { include: { selections: true } }
},
orderBy: { mstUtc: 'desc' }
});
console.log(`📊 Total completed matches with odds: ${matches.length}`);
// Analyze HT/FT results
const reversalMatches: any[] = [];
let totalMatches = 0;
let htftCounts: Record<string, number> = {
'1/1': 0, '1/X': 0, '1/2': 0,
'X/1': 0, 'X/X': 0, 'X/2': 0,
'2/1': 0, '2/X': 0, '2/2': 0
};
for (const match of matches) {
const htHome = match.htScoreHome!;
const htAway = match.htScoreAway!;
const ftHome = match.scoreHome!;
const ftAway = match.scoreAway!;
const htResult = htHome > htAway ? '1' : htHome === htAway ? 'X' : '2';
const ftResult = ftHome > ftAway ? '1' : ftHome === ftAway ? 'X' : '2';
const htft = `${htResult}/${ftResult}`;
htftCounts[htft] = (htftCounts[htft] || 0) + 1;
totalMatches++;
if (htft === '1/2' || htft === '2/1') {
// Extract odds
let msHomeOdds: number | null = null;
let msDrawOdds: number | null = null;
let msAwayOdds: number | null = null;
let htHomeOdds: number | null = null;
let htDrawOdds: number | null = null;
let htAwayOdds: number | null = null;
for (const cat of match.oddCategories) {
const catName = (cat.name || '').toLowerCase();
const isHT = catName.includes('1.yarı');
for (const sel of cat.selections) {
const selName = (sel.name || '').toLowerCase();
if (!sel.oddValue) continue;
const odd = parseFloat(sel.oddValue.toString());
if (catName.includes('maç sonucu') || catName.includes('1.yarı sonucu')) {
if (selName === '1') { if (isHT) htHomeOdds = odd; else msHomeOdds = odd; }
else if (selName === 'x' || selName === '0') { if (isHT) htDrawOdds = odd; else msDrawOdds = odd; }
else if (selName === '2') { if (isHT) htAwayOdds = odd; else msAwayOdds = odd; }
}
}
}
if (!match.homeTeam || !match.awayTeam || !match.league) continue;
reversalMatches.push({
id: match.id,
homeTeam: match.homeTeam.name,
awayTeam: match.awayTeam.name,
league: match.league.name,
htHome, htAway, ftHome, ftAway,
htft,
msHomeOdds, msDrawOdds, msAwayOdds,
htHomeOdds, htDrawOdds, htAwayOdds,
date: match.mstUtc,
});
}
}
// Print HT/FT distribution
console.log('\n📊 HT/FT DISTRIBUTION:');
for (const [key, count] of Object.entries(htftCounts)) {
const pct = (count / totalMatches * 100).toFixed(2);
const marker = (key === '1/2' || key === '2/1') ? ' ⚠️ REVERSAL' : '';
console.log(` ${key}: ${count} (${pct}%)${marker}`);
}
console.log(`\n⚠️ TOTAL REVERSAL MATCHES: ${reversalMatches.length} (${(reversalMatches.length / totalMatches * 100).toFixed(2)}%)`);
// ANALYSIS 1: League distribution
console.log('\n📈 ANALYSIS 1: LEAGUE DISTRIBUTION OF REVERSALS');
console.log('-'.repeat(80));
const leagueCounts: Record<string, { total: number, reversal: number }> = {};
for (const match of matches) {
if (!match.league) continue;
const htHome = match.htScoreHome!;
const htAway = match.htScoreAway!;
const ftHome = match.scoreHome!;
const ftAway = match.scoreAway!;
const htResult = htHome > htAway ? '1' : htHome === htAway ? 'X' : '2';
const ftResult = ftHome > ftAway ? '1' : ftHome === ftAway ? 'X' : '2';
const htft = `${htResult}/${ftResult}`;
const league = match.league.name;
if (!leagueCounts[league]) leagueCounts[league] = { total: 0, reversal: 0 };
leagueCounts[league].total++;
if (htft === '1/2' || htft === '2/1') leagueCounts[league].reversal++;
}
const leagueSorted = Object.entries(leagueCounts)
.filter(([_, v]) => v.reversal > 0 && v.total >= 50)
.sort((a, b) => (b[1].reversal / b[1].total) - (a[1].reversal / a[1].total))
.slice(0, 20);
console.log('\nTop 20 leagues by reversal rate (min 50 matches):');
for (const [league, data] of leagueSorted) {
const rate = (data.reversal / data.total * 100).toFixed(2);
console.log(` ${league}: ${data.reversal}/${data.total} (${rate}%)`);
}
// ANALYSIS 2: Odds patterns
console.log('\n📈 ANALYSIS 2: ODDS PATTERNS IN REVERSAL MATCHES');
console.log('-'.repeat(80));
const ms1_2 = reversalMatches.filter(m => m.htft === '1/2');
const ms2_1 = reversalMatches.filter(m => m.htft === '2/1');
console.log(`\n1/2 Reversals: ${ms1_2.length}`);
console.log(`2/1 Reversals: ${ms2_1.length}`);
// MS odds analysis for 1/2
const ms1_2_withOdds = ms1_2.filter(m => m.msHomeOdds && m.msAwayOdds);
if (ms1_2_withOdds.length > 0) {
const avgHomeOdd = ms1_2_withOdds.reduce((sum, m) => sum + m.msHomeOdds!, 0) / ms1_2_withOdds.length;
const avgAwayOdd = ms1_2_withOdds.reduce((sum, m) => sum + m.msAwayOdds!, 0) / ms1_2_withOdds.length;
const avgDrawOdd = ms1_2_withOdds.filter(m => m.msDrawOdds).reduce((sum, m) => sum + m.msDrawOdds!, 0) / ms1_2_withOdds.filter(m => m.msDrawOdds).length || 0;
console.log(`\n 1/2 Matches - Average MS Odds:`);
console.log(` Home Win: ${avgHomeOdd.toFixed(2)} (HT was WINNING!)`);
console.log(` Draw: ${avgDrawOdd.toFixed(2)}`);
console.log(` Away Win: ${avgAwayOdd.toFixed(2)} (but AWAY won FT!)`);
// Favorite analysis
let favoriteWon = 0;
let underdogWon = 0;
let noFavorite = 0;
for (const m of ms1_2_withOdds) {
if (m.msHomeOdds! < m.msAwayOdds!) {
// Home was favorite, but away won = UNDERDOG
underdogWon++;
} else if (m.msAwayOdds! < m.msHomeOdds!) {
// Away was favorite and won = FAVORITE
favoriteWon++;
} else {
noFavorite++;
}
}
console.log(`\n 1/2 - Who was favored vs who won:`);
console.log(` Favorite won (Away was fav): ${favoriteWon} (${(favoriteWon / ms1_2_withOdds.length * 100).toFixed(1)}%)`);
console.log(` Underdog won (Home was fav): ${underdogWon} (${(underdogWon / ms1_2_withOdds.length * 100).toFixed(1)}%) ⚠️`);
}
// MS odds analysis for 2/1
const ms2_1_withOdds = ms2_1.filter(m => m.msHomeOdds && m.msAwayOdds);
if (ms2_1_withOdds.length > 0) {
const avgHomeOdd = ms2_1_withOdds.reduce((sum, m) => sum + m.msHomeOdds!, 0) / ms2_1_withOdds.length;
const avgAwayOdd = ms2_1_withOdds.reduce((sum, m) => sum + m.msAwayOdds!, 0) / ms2_1_withOdds.length;
const avgDrawOdd = ms2_1_withOdds.filter(m => m.msDrawOdds).reduce((sum, m) => sum + m.msDrawOdds!, 0) / ms2_1_withOdds.filter(m => m.msDrawOdds).length || 0;
console.log(`\n 2/1 Matches - Average MS Odds:`);
console.log(` Home Win: ${avgHomeOdd.toFixed(2)} (HOME won FT!)`);
console.log(` Draw: ${avgDrawOdd.toFixed(2)}`);
console.log(` Away Win: ${avgAwayOdd.toFixed(2)} (Away was WINNING at HT!)`);
let favoriteWon = 0;
let underdogWon = 0;
for (const m of ms2_1_withOdds) {
if (m.msAwayOdds! < m.msHomeOdds!) {
// Away was favorite at HT, but home won = UNDERDOG
underdogWon++;
} else if (m.msHomeOdds! < m.msAwayOdds!) {
// Home was favorite and won = FAVORITE
favoriteWon++;
}
}
console.log(`\n 2/1 - Who was favored vs who won:`);
console.log(` Favorite won (Home was fav): ${favoriteWon} (${(favoriteWon / ms2_1_withOdds.length * 100).toFixed(1)}%)`);
console.log(` Underdog won (Away was fav): ${underdogWon} (${(underdogWon / ms2_1_withOdds.length * 100).toFixed(1)}%) ⚠️`);
}
// ANALYSIS 3: Suspicious patterns
console.log('\n📈 ANALYSIS 3: SUSPICIOUS PATTERNS');
console.log('-'.repeat(80));
// Pattern 1: Heavy favorite loses after leading (1/2 with low home odds)
const suspicious_1_2 = ms1_2_withOdds.filter(m => m.msHomeOdds! < 1.5);
console.log(`\n⚠️ PATTERN 1: Heavy Home Favorite loses after HT lead (MS Home Odds < 1.5):`);
console.log(` Count: ${suspicious_1_2.length}`);
if (suspicious_1_2.length > 0) {
const avgOdd = suspicious_1_2.reduce((sum, m) => sum + m.msHomeOdds!, 0) / suspicious_1_2.length;
console.log(` Avg Home Odds: ${avgOdd.toFixed(2)}`);
console.log(` Sample matches:`);
suspicious_1_2.slice(0, 5).forEach(m => {
console.log(` ${m.league}: ${m.homeTeam} (${m.msHomeOdds}) vs ${m.awayTeam} (${m.msAwayOdds}) => HT: ${m.htHome}-${m.htAway}, FT: ${m.ftHome}-${m.ftAway}`);
});
}
// Pattern 2: Heavy away favorite loses after leading (2/1 with low away odds)
const suspicious_2_1 = ms2_1_withOdds.filter(m => m.msAwayOdds! < 1.5);
console.log(`\n⚠️ PATTERN 2: Heavy Away Favorite loses after HT lead (MS Away Odds < 1.5):`);
console.log(` Count: ${suspicious_2_1.length}`);
if (suspicious_2_1.length > 0) {
const avgOdd = suspicious_2_1.reduce((sum, m) => sum + m.msAwayOdds!, 0) / suspicious_2_1.length;
console.log(` Avg Away Odds: ${avgOdd.toFixed(2)}`);
console.log(` Sample matches:`);
suspicious_2_1.slice(0, 5).forEach(m => {
console.log(` ${m.league}: ${m.homeTeam} (${m.msHomeOdds}) vs ${m.awayTeam} (${m.msAwayOdds}) => HT: ${m.htHome}-${m.htAway}, FT: ${m.ftHome}-${m.ftAway}`);
});
}
// ANALYSIS 4: HT Odds vs MS Odds correlation
console.log('\n📈 ANALYSIS 4: HT ODDS CORRELATION');
console.log('-'.repeat(80));
const withHTOdds = reversalMatches.filter(m => m.htHomeOdds && m.htAwayOdds);
if (withHTOdds.length > 0) {
console.log(`\n Matches with HT odds: ${withHTOdds.length}`);
let htCorrectlyPredicted = 0;
for (const m of withHTOdds) {
const htFav = m.htHomeOdds! < m.htAwayOdds! ? '1' : m.htAwayOdds! < m.htHomeOdds! ? '2' : 'X';
const htActual = m.htHome > m.htAway ? '1' : m.htAway > m.htHome ? '2' : 'X';
if (htFav === htActual) htCorrectlyPredicted++;
}
console.log(` HT Favorite correctly led at HT: ${htCorrectlyPredicted}/${withHTOdds.length} (${(htCorrectlyPredicted / withHTOdds.length * 100).toFixed(1)}%)`);
// How often did HT favorite lose FT?
let htFavoriteLostFT = 0;
for (const m of withHTOdds) {
const htFav = m.htHomeOdds! < m.htAwayOdds! ? '1' : m.htAwayOdds! < m.htHomeOdds! ? '2' : 'X';
const ftActual = m.ftHome > m.ftAway ? '1' : m.ftAway > m.ftHome ? '2' : 'X';
if (htFav !== ftActual) htFavoriteLostFT++;
}
console.log(` HT Favorite lost FT: ${htFavoriteLostFT}/${withHTOdds.length} (${(htFavoriteLostFT / withHTOdds.length * 100).toFixed(1)}%) ⚠️`);
}
// ANALYSIS 5: Score patterns
console.log('\n📈 ANALYSIS 5: SCORE PATTERNS IN REVERSALS');
console.log('-'.repeat(80));
// HT score distribution for reversals
const htScores: Record<string, number> = {};
for (const m of reversalMatches) {
const key = `${m.htHome}-${m.htAway}`;
htScores[key] = (htScores[key] || 0) + 1;
}
console.log('\nMost common HT scores in reversal matches:');
Object.entries(htScores)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([score, count]) => {
console.log(` HT ${score}: ${count} matches`);
});
// FT score distribution
const ftScores: Record<string, number> = {};
for (const m of reversalMatches) {
const key = `${m.ftHome}-${m.ftAway}`;
ftScores[key] = (ftScores[key] || 0) + 1;
}
console.log('\nMost common FT scores in reversal matches:');
Object.entries(ftScores)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.forEach(([score, count]) => {
console.log(` FT ${score}: ${count} matches`);
});
// ANALYSIS 6: Goal difference patterns
console.log('\n📈 ANALYSIS 6: COMEBACK MAGNITUDE');
console.log('-'.repeat(80));
let comebackBy1 = 0;
let comebackBy2 = 0;
let comebackBy3Plus = 0;
for (const m of reversalMatches) {
const htDiff = Math.abs(m.htHome - m.htAway);
const ftDiff = Math.abs(m.ftHome - m.ftAway);
if (m.htft === '1/2') {
// Home was leading, away won
const margin = (m.ftAway - m.ftHome);
if (margin === 1) comebackBy1++;
else if (margin === 2) comebackBy2++;
else comebackBy3Plus++;
} else {
// Away was leading, home won
const margin = (m.ftHome - m.ftAway);
if (margin === 1) comebackBy1++;
else if (margin === 2) comebackBy2++;
else comebackBy3Plus++;
}
}
console.log(`\n Comeback by 1 goal: ${comebackBy1} (${(comebackBy1 / reversalMatches.length * 100).toFixed(1)}%)`);
console.log(` Comeback by 2 goals: ${comebackBy2} (${(comebackBy2 / reversalMatches.length * 100).toFixed(1)}%)`);
console.log(` Comeback by 3+ goals: ${comebackBy3Plus} (${(comebackBy3Plus / reversalMatches.length * 100).toFixed(1)}%) ⚠️`);
// Show extreme comebacks
const extremeComebacks = reversalMatches
.filter(m => {
if (m.htft === '1/2') return (m.ftAway - m.ftHome) >= 2;
return (m.ftHome - m.ftAway) >= 2;
})
.sort((a, b) => {
const diffA = a.htft === '1/2' ? (a.ftAway - a.ftHome) : (a.ftHome - a.ftAway);
const diffB = b.htft === '1/2' ? (b.ftAway - b.ftHome) : (b.ftHome - b.ftAway);
return diffB - diffA;
})
.slice(0, 10);
console.log('\nTop 10 most extreme comebacks:');
extremeComebacks.forEach(m => {
const diff = m.htft === '1/2' ? (m.ftAway - m.ftHome) : (m.ftHome - m.ftAway);
console.log(` ${m.league}: ${m.homeTeam} vs ${m.awayTeam} | HT: ${m.htHome}-${m.htAway} => FT: ${m.ftHome}-${m.ftAway} (Diff: ${diff})`);
});
console.log('\n' + '='.repeat(80));
console.log('✅ ANALYSIS COMPLETE');
console.log('='.repeat(80));
await prisma.$disconnect();
}
analyzeReversalMatches().catch(console.error);