Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
@@ -0,0 +1,145 @@
-- ---------------------------------------------------------------
-- 016 — Resolution infrastructure.
--
-- closing_lines : Pinnacle closing reference, captured at tip-off
-- (CLV vs our graded line)
-- player_id_map : ESPN ↔ MLB Stats API ↔ NBA API cross-references
-- resolution_results : append-only per-prop outcome with full audit trail
-- (DNP, corrections, closing line link, CLV)
--
-- Also retrofits grade_history for the resolution pipeline:
-- + game_id (resolution route queries by this)
-- + resolved_at (idempotency: resolved_at IS NULL = unresolved)
-- + margin (actual_value - line for grading-loop analytics)
-- + correction_* (audit trail for morning correction sweeps)
-- + player_minutes / was_starter (DNP detection inputs)
-- + result CHECK (relaxed to include 'void' for postponed games)
--
-- RLS posture: all three new tables are service-role-only (no public read).
-- The Ledger queries grade_history directly; resolution_results is an
-- internal audit table.
-- ---------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.closing_lines (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
game_id text NOT NULL,
sport text NOT NULL,
player_name text NOT NULL,
player_espn_id text,
stat_type text NOT NULL,
pinnacle_line numeric,
pinnacle_over_odds integer,
pinnacle_under_odds integer,
fair_over_probability numeric,
fair_under_probability numeric,
captured_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (game_id, player_espn_id, stat_type)
);
CREATE INDEX IF NOT EXISTS idx_closing_lines_game ON public.closing_lines(game_id);
CREATE INDEX IF NOT EXISTS idx_closing_lines_sport_date ON public.closing_lines(sport, captured_at);
ALTER TABLE public.closing_lines ENABLE ROW LEVEL SECURITY;
-- ---------------------------------------------------------------
-- player_id_map: first introduced in migration 014 with a wider but
-- differently-shaped column set. CREATE TABLE IF NOT EXISTS would no-op
-- if 014 already created the table, leaving 016's columns missing —
-- which would break populate-player-ids.js and oddsPapiAdapter.js.
--
-- Strategy: create the table if absent, then ALTER ADD COLUMN IF NOT
-- EXISTS to bring 014's schema forward. The columns 014 has that 016
-- doesn't (nba_id, nhl_id, nfl_gsis_id, ufc_id, headshot_url) stay
-- intact — harmless extras.
CREATE TABLE IF NOT EXISTS public.player_id_map (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
display_name text NOT NULL,
normalized_name text NOT NULL,
espn_id text NOT NULL UNIQUE,
mlbam_id text,
nba_api_id text,
sport text NOT NULL,
team_abbr text,
active boolean NOT NULL DEFAULT true,
updated_at timestamptz NOT NULL DEFAULT now()
);
-- Bring forward 016's columns onto whatever shape 014 left behind.
-- These are no-ops when 016 created the table from scratch.
ALTER TABLE public.player_id_map
ADD COLUMN IF NOT EXISTS normalized_name text,
ADD COLUMN IF NOT EXISTS nba_api_id text,
ADD COLUMN IF NOT EXISTS team_abbr text,
ADD COLUMN IF NOT EXISTS espn_id text,
ADD COLUMN IF NOT EXISTS mlbam_id text;
-- 014's UNIQUE(sport, display_name, team) does NOT cover espn_id alone,
-- which the new code's upsert relies on. Add it idempotently via a
-- unique index — does nothing if espn_id is already unique-enforced.
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_id_espn_unique
ON public.player_id_map(espn_id) WHERE espn_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_player_id_sport ON public.player_id_map(sport);
CREATE INDEX IF NOT EXISTS idx_player_id_name ON public.player_id_map(normalized_name);
CREATE INDEX IF NOT EXISTS idx_player_id_mlbam ON public.player_id_map(mlbam_id) WHERE mlbam_id IS NOT NULL;
ALTER TABLE public.player_id_map ENABLE ROW LEVEL SECURITY;
-- ---------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.resolution_results (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
grade_id uuid NOT NULL,
game_id text NOT NULL,
sport text NOT NULL,
player_espn_id text NOT NULL,
player_name text NOT NULL,
stat_type text NOT NULL,
line numeric NOT NULL,
direction text NOT NULL CHECK (direction IN ('over', 'under')),
actual_value numeric NOT NULL,
result text NOT NULL CHECK (result IN ('hit', 'miss', 'push', 'void')),
margin numeric,
player_minutes numeric,
was_starter boolean,
closing_line_id uuid REFERENCES public.closing_lines(id),
clv numeric,
resolved_at timestamptz NOT NULL DEFAULT now(),
correction_note text,
correction_original_value numeric,
correction_original_result text
);
CREATE INDEX IF NOT EXISTS idx_resolution_game ON public.resolution_results(game_id);
CREATE INDEX IF NOT EXISTS idx_resolution_sport_date ON public.resolution_results(sport, resolved_at);
CREATE INDEX IF NOT EXISTS idx_resolution_player ON public.resolution_results(player_espn_id);
CREATE INDEX IF NOT EXISTS idx_resolution_result ON public.resolution_results(result);
ALTER TABLE public.resolution_results ENABLE ROW LEVEL SECURITY;
-- ---------------------------------------------------------------
-- grade_history retrofit. Columns confirmed missing by Session 6a audit.
-- ---------------------------------------------------------------
ALTER TABLE public.grade_history
ADD COLUMN IF NOT EXISTS game_id text,
ADD COLUMN IF NOT EXISTS resolved_at timestamptz,
ADD COLUMN IF NOT EXISTS margin numeric,
ADD COLUMN IF NOT EXISTS player_minutes numeric,
ADD COLUMN IF NOT EXISTS was_starter boolean,
ADD COLUMN IF NOT EXISTS closing_line_id uuid REFERENCES public.closing_lines(id),
ADD COLUMN IF NOT EXISTS clv numeric,
ADD COLUMN IF NOT EXISTS correction_note text,
ADD COLUMN IF NOT EXISTS correction_original_value numeric,
ADD COLUMN IF NOT EXISTS correction_original_result text;
CREATE INDEX IF NOT EXISTS idx_grade_history_game ON public.grade_history(game_id);
CREATE INDEX IF NOT EXISTS idx_grade_history_unresolved
ON public.grade_history(game_id) WHERE resolved_at IS NULL;
-- Relax the result CHECK to include 'void' for postponed/canceled games.
-- Drop-and-recreate is atomic inside the migration; production data with
-- any of the legacy values (hit/miss/push/pending) revalidates cleanly.
ALTER TABLE public.grade_history DROP CONSTRAINT IF EXISTS grade_history_result_check;
ALTER TABLE public.grade_history
ADD CONSTRAINT grade_history_result_check
CHECK (result IS NULL OR result IN ('hit', 'miss', 'push', 'pending', 'void'));