cr
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user