This commit is contained in:
2026-04-16 17:21:48 +03:00
parent c8fa4c442d
commit c8e7e4e927
116 changed files with 3720 additions and 4197 deletions
+24 -24
View File
@@ -12,7 +12,7 @@
* Usage: npx ts-node --transpile-only -r tsconfig-paths/register src/scripts/compute-elo-ratings.ts
*/
import { PrismaClient } from '@prisma/client';
import { PrismaClient } from "@prisma/client";
// ─────────────────────────────────────────────────────────────
// Types
@@ -72,9 +72,9 @@ function getResultChar(
scoreAway: number,
isHomeTeam: boolean,
): string {
if (scoreHome > scoreAway) return isHomeTeam ? 'W' : 'L';
if (scoreHome < scoreAway) return isHomeTeam ? 'L' : 'W';
return 'D';
if (scoreHome > scoreAway) return isHomeTeam ? "W" : "L";
if (scoreHome < scoreAway) return isHomeTeam ? "L" : "W";
return "D";
}
function calculateFormElo(recentResults: string[]): number {
@@ -86,7 +86,7 @@ function calculateFormElo(recentResults: string[]): number {
for (let i = 0; i < recentResults.length; i++) {
const weight = Math.pow(FORM_DECAY, i); // Most recent = highest weight
const result = recentResults[i];
const score = result === 'W' ? 3 : result === 'D' ? 1 : 0;
const score = result === "W" ? 3 : result === "D" ? 1 : 0;
formScore += score * weight;
totalWeight += 3 * weight; // Max possible per match
}
@@ -105,14 +105,14 @@ async function computeEloRatings(): Promise<void> {
const startTime = Date.now();
try {
console.log('🏟️ ELO Rating Computation — Starting...');
console.log('─'.repeat(60));
console.log("🏟️ ELO Rating Computation — Starting...");
console.log("─".repeat(60));
// 1. Fetch all finished football matches in chronological order
const matches: MatchRecord[] = await prisma.match.findMany({
where: {
sport: 'football',
status: 'FT',
sport: "football",
status: "FT",
scoreHome: { not: null },
scoreAway: { not: null },
homeTeamId: { not: null },
@@ -126,7 +126,7 @@ async function computeEloRatings(): Promise<void> {
scoreAway: true,
mstUtc: true,
},
orderBy: { mstUtc: 'asc' },
orderBy: { mstUtc: "asc" },
});
console.log(
@@ -228,7 +228,7 @@ async function computeEloRatings(): Promise<void> {
);
// 4. Bulk upsert to team_elo_ratings
console.log('💾 Writing to team_elo_ratings...');
console.log("💾 Writing to team_elo_ratings...");
const BATCH_SIZE = 500;
const teams = Array.from(eloMap.entries());
@@ -246,7 +246,7 @@ async function computeEloRatings(): Promise<void> {
awayElo: Math.round(state.awayElo * 10) / 10,
formElo: Math.round(state.formElo * 10) / 10,
matchesPlayed: state.matchesPlayed,
recentForm: state.recentResults.join(''),
recentForm: state.recentResults.join(""),
},
create: {
teamId,
@@ -255,7 +255,7 @@ async function computeEloRatings(): Promise<void> {
awayElo: Math.round(state.awayElo * 10) / 10,
formElo: Math.round(state.formElo * 10) / 10,
matchesPlayed: state.matchesPlayed,
recentForm: state.recentResults.join(''),
recentForm: state.recentResults.join(""),
},
}),
),
@@ -276,38 +276,38 @@ async function computeEloRatings(): Promise<void> {
.map((s) => s.overallElo)
.sort((a, b) => b - a);
console.log('─'.repeat(60));
console.log('📊 ELO Rating Summary:');
console.log("─".repeat(60));
console.log("📊 ELO Rating Summary:");
console.log(` Teams rated: ${eloMap.size.toLocaleString()}`);
console.log(` Matches used: ${processed.toLocaleString()}`);
console.log(` Highest ELO: ${overallElos[0]?.toFixed(1) ?? 'N/A'}`);
console.log(` Highest ELO: ${overallElos[0]?.toFixed(1) ?? "N/A"}`);
console.log(
` Lowest ELO: ${overallElos[overallElos.length - 1]?.toFixed(1) ?? 'N/A'}`,
` Lowest ELO: ${overallElos[overallElos.length - 1]?.toFixed(1) ?? "N/A"}`,
);
console.log(
` Median ELO: ${overallElos[Math.floor(overallElos.length / 2)]?.toFixed(1) ?? 'N/A'}`,
` Median ELO: ${overallElos[Math.floor(overallElos.length / 2)]?.toFixed(1) ?? "N/A"}`,
);
console.log(` Duration: ${elapsedTotal}s`);
console.log('─'.repeat(60));
console.log("─".repeat(60));
// Top 20 teams
const topTeams = await prisma.teamEloRating.findMany({
orderBy: { overallElo: 'desc' },
orderBy: { overallElo: "desc" },
take: 20,
include: { team: { select: { name: true } } },
});
console.log('\n🏆 Top 20 Teams by ELO:');
console.log("\n🏆 Top 20 Teams by ELO:");
topTeams.forEach((t, i) => {
const form = t.recentForm.split('').join('-');
const form = t.recentForm.split("").join("-");
console.log(
` ${String(i + 1).padStart(2)}. ${t.team.name.padEnd(25)} Overall: ${t.overallElo.toFixed(1).padStart(7)} Home: ${t.homeElo.toFixed(1).padStart(7)} Away: ${t.awayElo.toFixed(1).padStart(7)} Form: ${form}`,
);
});
console.log('\n✅ Done!');
console.log("\n✅ Done!");
} catch (error) {
console.error('❌ ELO computation failed:', error);
console.error("❌ ELO computation failed:", error);
process.exit(1);
} finally {
await prisma.$disconnect();