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