/** * Baseline Capture Script - Phase 1a Task 0 * * Captures the current state of qualifying-points data to JSON fixtures for * verification across all 5 migration phases. This ensures no scoring drift * occurs during the canonical tournament layer migration. * * Captured data: * 1. participant_qualifying_totals rows for all qualifying_points sports_seasons * 2. event_results rows with non-null qualifying_points_awarded * 3. participant_surface_elos rows for tennis sports_seasons * 4. Monte Carlo simulator output for each in-flight qualifying-points season * * WARNING: Simulator output is NON-DETERMINISTIC. The current simulator * implementations use Math.random() directly without seed support. This means * simulator fixture files will differ on each run and cannot be used for exact * byte-for-byte comparisons. They serve as reference baselines for manual * review and approximate validation only. */ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; import { eq, sql } from "drizzle-orm"; import { writeFileSync, mkdirSync } from "fs"; import { join } from "path"; import * as schema from "../database/schema.js"; import { getSimulator } from "../app/services/simulations/registry.js"; const OUT_DIR = join(process.cwd(), "test-fixtures", "baselines"); async function main() { const dbUrl = process.env.DATABASE_URL; if (!dbUrl) { console.error("ERROR: DATABASE_URL is required"); process.exit(1); } console.log("Connecting to database..."); const client = postgres(dbUrl, { max: 1 }); const db = drizzle(client, { schema }); mkdirSync(OUT_DIR, { recursive: true }); console.log(`Output directory: ${OUT_DIR}\n`); // ─── 1. Capture participant_qualifying_totals ───────────────────────────────── console.log("Capturing participant_qualifying_totals..."); const qpTotals = await db.execute(sql` SELECT pqt.*, ss.id as sports_season_id, ss.name as sports_season_name, s.name as sport_name FROM participant_qualifying_totals pqt JOIN sports_seasons ss ON ss.id = pqt.sports_season_id JOIN sports s ON s.id = ss.sport_id WHERE ss.scoring_pattern = 'qualifying_points' ORDER BY ss.id, pqt.participant_id `); writeFileSync( join(OUT_DIR, "qp-totals-pre-phase1.json"), JSON.stringify(qpTotals, null, 2) ); console.log(` Wrote ${qpTotals.length} rows → qp-totals-pre-phase1.json`); // ─── 2. Capture event_results with qualifying_points_awarded ───────────────── console.log("Capturing event_results with qualifying_points_awarded..."); const eventResults = await db.execute(sql` SELECT er.*, se.sports_season_id, se.name as event_name FROM event_results er JOIN scoring_events se ON se.id = er.scoring_event_id JOIN sports_seasons ss ON ss.id = se.sports_season_id WHERE ss.scoring_pattern = 'qualifying_points' AND er.qualifying_points_awarded IS NOT NULL ORDER BY se.sports_season_id, er.scoring_event_id, er.participant_id `); writeFileSync( join(OUT_DIR, "event-results-pre-phase1.json"), JSON.stringify(eventResults, null, 2) ); console.log(` Wrote ${eventResults.length} rows → event-results-pre-phase1.json`); // ─── 3. Capture participant_surface_elos ────────────────────────────────────── console.log("Capturing participant_surface_elos..."); const surfaceElos = await db.execute(sql` SELECT pse.*, ss.sport_id, ss.name as sports_season_name, s.name as sport_name FROM participant_surface_elos pse JOIN sports_seasons ss ON ss.id = pse.sports_season_id JOIN sports s ON s.id = ss.sport_id WHERE ss.scoring_pattern = 'qualifying_points' ORDER BY ss.id, pse.participant_id `); writeFileSync( join(OUT_DIR, "surface-elos-pre-phase1.json"), JSON.stringify(surfaceElos, null, 2) ); console.log(` Wrote ${surfaceElos.length} rows → surface-elos-pre-phase1.json`); // ─── 4. Capture simulator output for in-flight qualifying-points seasons ───── console.log("\nCapturing Monte Carlo simulator output..."); const inFlightSeasons = await db .select() .from(schema.sportsSeasons) .where(eq(schema.sportsSeasons.scoringPattern, "qualifying_points")); console.log(` Found ${inFlightSeasons.length} qualifying-points sports_seasons`); let simCount = 0; for (const ss of inFlightSeasons) { if (!ss.simulatorType) { console.log(` Skipping ${ss.name} (id: ${ss.id}) - no simulatorType set`); continue; } console.log(` Running simulator for ${ss.name} (type: ${ss.simulatorType})...`); try { const sim = getSimulator(ss.simulatorType); // NOTE: Simulators do not accept seed parameter - output is non-deterministic const output = await sim.simulate(ss.id); const filename = `sim-output-${ss.simulatorType}-${ss.id}.json`; writeFileSync( join(OUT_DIR, filename), JSON.stringify(output, null, 2) ); console.log(` Wrote ${output.length} results → ${filename}`); simCount++; } catch (err) { console.error(` ERROR simulating ${ss.name}:`, err); // Continue with other seasons even if one fails } } console.log(`\n✓ Baseline capture complete!`); console.log(` ${qpTotals.length} QP totals`); console.log(` ${eventResults.length} event results`); console.log(` ${surfaceElos.length} surface elo rows`); console.log(` ${simCount} simulator outputs (non-deterministic)`); console.log(`\nOutput: ${OUT_DIR}`); await client.end(); } main() .then(() => process.exit(0)) .catch((e) => { console.error("FATAL:", e); process.exit(1); });