* feat: EV simulation framework with F1 Monte Carlo simulator - Add EV snapshot tables (participant_ev_snapshots, team_ev_snapshots) and simulation_status column on sports seasons - Add ev-snapshot model with upsert and history query functions - Add simulator framework: types, bracket/F1/golf simulators, registry - F1 simulator: vig-removed ICM weighted draw (pre-season) + race-by-race Monte Carlo from current standings (in-season); per-position column normalization to prevent floating-point EV drift - Add admin simulate route and Run Simulation button on sports season page - Rework futures-odds admin page to save odds then run simulation in one action - Remove recalculate-probabilities route (superseded by simulate route) - Remove EV trend chart panel and associated DB queries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: map simulators to sports via simulatorType field Adds a `simulator_type` enum column to the `sports` table so each sport can be assigned a specific simulation algorithm rather than deriving it from the sports season's scoring pattern. - Add `simulatorTypeEnum` (f1_standings, indycar_standings, golf_qualifying_points, playoff_bracket) + `simulatorType` nullable column on `sports` table; migration 0037 - Rewrite simulator registry to key off `SimulatorType` instead of `ScoringPattern`; indycar_standings shares F1Simulator for now - `findSportsSeasonById` now returns `SportsSeasonWithSport` so callers have typed access to `sport.simulatorType` - Simulate and futures-odds actions read `sport.simulatorType`; guard fires before setting `simulationStatus: running` - Admin sport edit page gains a Simulator Type dropdown Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
46 lines
2.1 KiB
SQL
46 lines
2.1 KiB
SQL
-- Phase 2: EV Simulation Framework
|
|
-- Add simulation_status enum + column to sports_seasons
|
|
-- Add participant_ev_snapshots and team_ev_snapshots tables
|
|
-- Wrapped in IF NOT EXISTS guards so re-running is safe if partially applied.
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'simulation_status') THEN
|
|
CREATE TYPE "simulation_status" AS ENUM('idle', 'running', 'failed');
|
|
END IF;
|
|
END $$;
|
|
|
|
ALTER TABLE "sports_seasons" ADD COLUMN IF NOT EXISTS "simulation_status" "simulation_status" NOT NULL DEFAULT 'idle';
|
|
|
|
CREATE TABLE IF NOT EXISTS "participant_ev_snapshots" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
"participant_id" uuid NOT NULL REFERENCES "participants"("id") ON DELETE CASCADE,
|
|
"sports_season_id" uuid NOT NULL REFERENCES "sports_seasons"("id") ON DELETE CASCADE,
|
|
"snapshot_date" date NOT NULL,
|
|
"prob_first" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_second" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_third" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_fourth" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_fifth" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_sixth" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_seventh" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"prob_eighth" numeric(5, 4) NOT NULL DEFAULT '0',
|
|
"calculated_ev" numeric(10, 2) NOT NULL DEFAULT '0',
|
|
"source" varchar(100) NOT NULL,
|
|
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "participant_ev_snapshots_unique" ON "participant_ev_snapshots" ("participant_id", "sports_season_id", "snapshot_date");
|
|
|
|
CREATE TABLE IF NOT EXISTS "team_ev_snapshots" (
|
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
"team_id" uuid NOT NULL REFERENCES "teams"("id") ON DELETE CASCADE,
|
|
"season_id" uuid NOT NULL REFERENCES "seasons"("id") ON DELETE CASCADE,
|
|
"snapshot_date" date NOT NULL,
|
|
"projected_points" numeric(10, 2) NOT NULL DEFAULT '0',
|
|
"actual_points" numeric(10, 2) NOT NULL DEFAULT '0',
|
|
"created_at" timestamp DEFAULT now() NOT NULL
|
|
);
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "team_ev_snapshots_unique" ON "team_ev_snapshots" ("team_id", "season_id", "snapshot_date");
|