2c79373a3b
Migration 019: the truth-infrastructure table. Committed BEFORE it runs, per the Phase 1 GO instructions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
79 lines
3.5 KiB
SQL
79 lines
3.5 KiB
SQL
-- ---------------------------------------------------------------
|
|
-- 019 — ledger_entries (Session 58, work-order Phase 1 + CLV amendments).
|
|
--
|
|
-- The truth infrastructure: every grade — user scan AND pipeline pre-grade —
|
|
-- persists here, settles against the real result, and carries closing-line
|
|
-- value. Rows with user_id NULL are the PUBLIC model record.
|
|
--
|
|
-- DATA SEMANTICS: `line`, `locked_odds`, `book`, `closing_line`,
|
|
-- `closing_odds` are REAL book values captured at a timestamp — never
|
|
-- generated. `model_value` / grade / edge / confidence are VYNDR model
|
|
-- output. A row must never exist with a fabricated market value.
|
|
--
|
|
-- All writes go through the service role (pipeline + API). Clients only
|
|
-- read: their own rows, plus the public (user_id IS NULL) record.
|
|
-- ---------------------------------------------------------------
|
|
|
|
CREATE TABLE IF NOT EXISTS public.ledger_entries (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid NULL REFERENCES auth.users (id) ON DELETE CASCADE,
|
|
player_key text NOT NULL,
|
|
player_name text NOT NULL,
|
|
sport text NOT NULL,
|
|
stat text NOT NULL,
|
|
line numeric NOT NULL,
|
|
side text NOT NULL CHECK (side IN ('over', 'under')),
|
|
locked_odds text NULL,
|
|
book text NULL,
|
|
grade text NOT NULL,
|
|
edge numeric NULL,
|
|
confidence numeric NULL,
|
|
model_value numeric NULL,
|
|
graded_at timestamptz NOT NULL DEFAULT now(),
|
|
game_id text NOT NULL,
|
|
game_date date NOT NULL,
|
|
closing_line numeric NULL,
|
|
closing_odds text NULL,
|
|
clv numeric NULL,
|
|
clv_result text NULL CHECK (clv_result IN ('beat', 'faded', 'flat')),
|
|
outcome text NULL CHECK (outcome IN ('hit', 'miss', 'push')),
|
|
actual_value numeric NULL,
|
|
settled_at timestamptz NULL,
|
|
revised_from_grade text NULL
|
|
);
|
|
|
|
-- Idempotency: pipeline re-runs and double-taps upsert into this. PG15+
|
|
-- NULLS NOT DISTINCT makes the null-user (public record) rows dedupe too,
|
|
-- and — unlike a coalesce-expression index — PostgREST `on_conflict` can
|
|
-- target a real constraint's column list.
|
|
ALTER TABLE public.ledger_entries
|
|
ADD CONSTRAINT ledger_entries_dedupe
|
|
UNIQUE NULLS NOT DISTINCT (user_id, player_key, stat, line, side, game_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_ledger_user_graded
|
|
ON public.ledger_entries (user_id, graded_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_ledger_gamedate_outcome
|
|
ON public.ledger_entries (game_date, outcome);
|
|
CREATE INDEX IF NOT EXISTS idx_ledger_player_stat
|
|
ON public.ledger_entries (player_key, stat);
|
|
CREATE INDEX IF NOT EXISTS idx_ledger_public_clv
|
|
ON public.ledger_entries (clv_result) WHERE user_id IS NULL;
|
|
-- The settlement worker's scan: unsettled rows by date.
|
|
CREATE INDEX IF NOT EXISTS idx_ledger_unsettled
|
|
ON public.ledger_entries (game_date) WHERE outcome IS NULL;
|
|
|
|
ALTER TABLE public.ledger_entries ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users read their own rows.
|
|
CREATE POLICY ledger_select_own ON public.ledger_entries
|
|
FOR SELECT TO authenticated
|
|
USING (user_id = auth.uid());
|
|
|
|
-- Everyone (anon + authenticated) reads the public model record.
|
|
CREATE POLICY ledger_select_public ON public.ledger_entries
|
|
FOR SELECT TO anon, authenticated
|
|
USING (user_id IS NULL);
|
|
|
|
-- No INSERT/UPDATE/DELETE policies: client-side writes are impossible.
|
|
-- The pipeline + API write via the service role, which bypasses RLS.
|