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
+161
View File
@@ -0,0 +1,161 @@
-- ---------------------------------------------------------------
-- 014 — Data pipeline tables.
-- odds_cache : last-seen odds per book/prop
-- line_history : append-only line samples (drives steam detection + CLV)
-- cascade_alerts : injury / lineup / ump-ref / weather deltas
-- player_stats_cache : enricher payloads from nba_api, pybaseball, etc.
-- grade_history : every grade issued + result (drives Ledger + CLV)
-- prop_correlations : measured correlation between stat pairs
-- player_id_map : cross-source player ID mapping
--
-- RLS posture:
-- Every table here is WRITTEN by the service role only (no policy = no
-- row visible to anon/authenticated for INSERT/UPDATE/DELETE — service
-- role bypasses RLS). Public-readable surfaces (grade_history) get a
-- SELECT policy. Internal-only tables stay locked down.
-- ---------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.odds_cache (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
game_id TEXT NOT NULL,
sport TEXT NOT NULL,
player_id TEXT,
player_name TEXT,
stat_type TEXT,
book TEXT NOT NULL,
line NUMERIC,
odds_over INTEGER,
odds_under INTEGER,
source TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (game_id, COALESCE(player_id, ''), COALESCE(stat_type, ''), book)
);
CREATE INDEX IF NOT EXISTS idx_odds_cache_game ON public.odds_cache (game_id, fetched_at DESC);
CREATE INDEX IF NOT EXISTS idx_odds_cache_player ON public.odds_cache (player_id, stat_type);
CREATE TABLE IF NOT EXISTS public.line_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
game_id TEXT NOT NULL,
player_id TEXT,
stat_type TEXT,
book TEXT NOT NULL,
line NUMERIC,
odds INTEGER,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_line_history_recent
ON public.line_history (game_id, recorded_at DESC);
CREATE INDEX IF NOT EXISTS idx_line_history_prop
ON public.line_history (player_id, stat_type, recorded_at DESC);
CREATE TABLE IF NOT EXISTS public.cascade_alerts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
trigger_type TEXT NOT NULL CHECK (trigger_type IN (
'injury', 'lineup', 'weather', 'ref', 'umpire', 'manual'
)),
trigger_detail JSONB NOT NULL,
affected_props JSONB NOT NULL DEFAULT '[]'::jsonb,
affected_count INTEGER NOT NULL DEFAULT 0,
resolved BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_cascade_recent
ON public.cascade_alerts (created_at DESC) WHERE resolved = FALSE;
CREATE TABLE IF NOT EXISTS public.player_stats_cache (
player_id TEXT NOT NULL,
sport TEXT NOT NULL,
bucket TEXT NOT NULL DEFAULT 'default',
stats_data JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (player_id, sport, bucket)
);
CREATE TABLE IF NOT EXISTS public.grade_history (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
player_id TEXT NOT NULL,
player_name TEXT,
sport TEXT NOT NULL,
stat_type TEXT,
line NUMERIC,
direction TEXT CHECK (direction IN ('over', 'under')),
grade TEXT,
projection NUMERIC,
modeled_prob NUMERIC,
implied_prob NUMERIC,
game_date DATE,
result TEXT CHECK (result IN ('hit', 'miss', 'push', 'pending')),
actual_value NUMERIC,
graded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_grade_history_player
ON public.grade_history (player_id, game_date DESC);
CREATE INDEX IF NOT EXISTS idx_grade_history_recent
ON public.grade_history (graded_at DESC);
CREATE TABLE IF NOT EXISTS public.prop_correlations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
player_id TEXT NOT NULL,
stat_a TEXT NOT NULL,
stat_b TEXT NOT NULL,
correlation NUMERIC NOT NULL,
sample_size INTEGER,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (player_id, stat_a, stat_b)
);
CREATE TABLE IF NOT EXISTS public.player_id_map (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
display_name TEXT NOT NULL,
sport TEXT NOT NULL,
team TEXT,
nba_id TEXT,
espn_id TEXT,
mlbam_id TEXT,
nhl_id TEXT,
nfl_gsis_id TEXT,
ufc_id TEXT,
headshot_url TEXT,
active BOOLEAN NOT NULL DEFAULT TRUE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (sport, display_name, team)
);
CREATE INDEX IF NOT EXISTS idx_player_sport ON public.player_id_map (sport, active);
CREATE INDEX IF NOT EXISTS idx_player_name ON public.player_id_map (display_name);
-- ── RLS ────────────────────────────────────────────────────────────────────
ALTER TABLE public.odds_cache ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.line_history ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.cascade_alerts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.player_stats_cache ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.grade_history ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.prop_correlations ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.player_id_map ENABLE ROW LEVEL SECURITY;
-- Public-readable surfaces. The Ledger is published; correlation data and
-- the player ID map are referenced by the UI for headshots + cross-source
-- IDs. odds_cache / line_history / cascade_alerts / player_stats_cache stay
-- locked to the service role.
DROP POLICY IF EXISTS "Anyone can read grade history" ON public.grade_history;
CREATE POLICY "Anyone can read grade history"
ON public.grade_history
FOR SELECT
USING (TRUE);
DROP POLICY IF EXISTS "Anyone can read prop correlations" ON public.prop_correlations;
CREATE POLICY "Anyone can read prop correlations"
ON public.prop_correlations
FOR SELECT
USING (TRUE);
DROP POLICY IF EXISTS "Anyone can read player id map" ON public.player_id_map;
CREATE POLICY "Anyone can read player id map"
ON public.player_id_map
FOR SELECT
USING (TRUE);
GRANT SELECT ON public.grade_history TO anon, authenticated;
GRANT SELECT ON public.prop_correlations TO anon, authenticated;
GRANT SELECT ON public.player_id_map TO anon, authenticated;