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
+41 -41
View File
@@ -16,7 +16,7 @@
* Usage: npx ts-node --transpile-only -r tsconfig-paths/register src/scripts/populate-feature-store.ts
*/
import { PrismaClient } from '@prisma/client';
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
@@ -180,16 +180,16 @@ function buildFormIndex(
const homeResult =
match.scoreHome > match.scoreAway
? 'W'
? "W"
: match.scoreHome < match.scoreAway
? 'L'
: 'D';
? "L"
: "D";
const awayResult =
match.scoreAway > match.scoreHome
? 'W'
? "W"
: match.scoreAway < match.scoreHome
? 'L'
: 'D';
? "L"
: "D";
homeState.results.unshift(homeResult);
awayState.results.unshift(awayResult);
@@ -222,14 +222,14 @@ function extractFormFeatures(formState: TeamFormState): {
let winStreak = 0;
for (const r of formState.results) {
if (r === 'W') winStreak++;
if (r === "W") winStreak++;
else break;
}
// Form score: (W=3, D=1, L=0) over last 5, normalized to 0-100
const last5Results = formState.results.slice(0, 5);
const points = last5Results.reduce(
(sum, r) => sum + (r === 'W' ? 3 : r === 'D' ? 1 : 0),
(sum, r) => sum + (r === "W" ? 3 : r === "D" ? 1 : 0),
0,
);
const maxPoints = last5Results.length * 3 || 1;
@@ -302,20 +302,20 @@ async function loadOddsIndex(): Promise<Map<string, OddsData>> {
let bttsY = 0;
for (const s of selections) {
if (s.cat === 'Maç Sonucu') {
if (s.sel === '1') msH = s.odds;
else if (s.sel === 'X' || s.sel === '0') msD = s.odds;
else if (s.sel === '2') msA = s.odds;
} else if (s.cat === 'Alt/Üst 2,5') {
if (s.cat === "Maç Sonucu") {
if (s.sel === "1") msH = s.odds;
else if (s.sel === "X" || s.sel === "0") msD = s.odds;
else if (s.sel === "2") msA = s.odds;
} else if (s.cat === "Alt/Üst 2,5") {
if (
s.sel.toLowerCase().includes('üst') ||
s.sel.toLowerCase().includes('over')
s.sel.toLowerCase().includes("üst") ||
s.sel.toLowerCase().includes("over")
)
ou25O = s.odds;
} else if (s.cat === 'Karşılıklı Gol') {
} else if (s.cat === "Karşılıklı Gol") {
if (
s.sel.toLowerCase().includes('var') ||
s.sel.toLowerCase().includes('yes')
s.sel.toLowerCase().includes("var") ||
s.sel.toLowerCase().includes("yes")
)
bttsY = s.odds;
}
@@ -411,7 +411,7 @@ function buildLeagueIndex(matches: MatchRow[]): Map<string, LeagueStats> {
const leagueMap = new Map<string, LeagueStats>();
for (const match of matches) {
const key = match.leagueId ?? 'unknown';
const key = match.leagueId ?? "unknown";
let stats = leagueMap.get(key);
if (!stats) {
stats = { totalMatches: 0, totalGoals: 0, homeWins: 0, over25Count: 0 };
@@ -520,15 +520,15 @@ async function populateFeatureStore(): Promise<void> {
const startTime = Date.now();
try {
console.log('🧠 Feature Store Population — Starting...');
console.log('─'.repeat(60));
console.log("🧠 Feature Store Population — Starting...");
console.log("─".repeat(60));
// Load all finished football matches
console.log('📥 Loading matches...');
console.log("📥 Loading matches...");
const rawMatches = await prisma.match.findMany({
where: {
sport: 'football',
status: 'FT',
sport: "football",
status: "FT",
scoreHome: { not: null },
scoreAway: { not: null },
homeTeamId: { not: null },
@@ -543,7 +543,7 @@ async function populateFeatureStore(): Promise<void> {
scoreAway: true,
mstUtc: true,
},
orderBy: { mstUtc: 'asc' },
orderBy: { mstUtc: "asc" },
});
const matches: MatchRow[] = rawMatches.map((m) => ({
@@ -559,31 +559,31 @@ async function populateFeatureStore(): Promise<void> {
console.log(` 📊 Matches loaded: ${matches.length.toLocaleString()}`);
// Pre-compute all indexes
console.log('\n📊 Building feature indexes...');
console.log("\n📊 Building feature indexes...");
console.log(' 🏅 Pillar 1: Loading ELO ratings...');
console.log(" 🏅 Pillar 1: Loading ELO ratings...");
const eloMap = await loadEloMap();
console.log(' 📈 Pillar 2: Building form index...');
console.log(" 📈 Pillar 2: Building form index...");
const formIndex = buildFormIndex(matches);
console.log(' 💰 Pillar 3: Loading odds data...');
console.log(" 💰 Pillar 3: Loading odds data...");
const oddsIndex = await loadOddsIndex();
console.log(' ⚔️ Pillar 5: Building H2H index...');
console.log(" ⚔️ Pillar 5: Building H2H index...");
const h2hIndex = buildH2HIndex(matches);
console.log(' 📋 Pillar 6: Loading referee data...');
console.log(" 📋 Pillar 6: Loading referee data...");
const refereeIndex = await loadRefereeIndex(matches);
console.log(' 🏟️ Pillar 7: Building league DNA...');
console.log(" 🏟️ Pillar 7: Building league DNA...");
const leagueIndex = buildLeagueIndex(matches);
console.log('\n✅ All indexes built!');
console.log('─'.repeat(60));
console.log("\n✅ All indexes built!");
console.log("─".repeat(60));
// Build feature vectors and batch upsert
console.log('💾 Writing features to database...');
console.log("💾 Writing features to database...");
const BATCH_SIZE = 1000;
let processed = 0;
@@ -651,7 +651,7 @@ async function populateFeatureStore(): Promise<void> {
const refTotal = refStats?.totalMatches ?? 0;
// Pillar 7: League DNA
const leagueKey = match.leagueId ?? 'unknown';
const leagueKey = match.leagueId ?? "unknown";
const leagueStats = leagueIndex.get(leagueKey) ?? {
totalMatches: 1,
totalGoals: 0,
@@ -730,7 +730,7 @@ async function populateFeatureStore(): Promise<void> {
),
// Meta
missingPlayersImpact: 0,
calculatorVer: 'v2.0',
calculatorVer: "v2.0",
});
}
@@ -749,7 +749,7 @@ async function populateFeatureStore(): Promise<void> {
}
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log('─'.repeat(60));
console.log("─".repeat(60));
console.log(`✅ Feature Store population complete!`);
console.log(` Features written: ${processed.toLocaleString()}`);
console.log(` Skipped: ${skipped}`);
@@ -758,9 +758,9 @@ async function populateFeatureStore(): Promise<void> {
// Verify
const count = await prisma.footballAiFeature.count();
console.log(` DB row count: ${count.toLocaleString()}`);
console.log('─'.repeat(60));
console.log("─".repeat(60));
} catch (error) {
console.error('❌ Feature store population failed:', error);
console.error("❌ Feature store population failed:", error);
process.exit(1);
} finally {
await prisma.$disconnect();