Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user