Session 58: Phase 1 spec + ledger_entries migration (pre-apply commit)
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>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
-- ---------------------------------------------------------------
|
||||
-- 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.
|
||||
Reference in New Issue
Block a user