b6f12daa98
Phase A #2: the champion grade (l5/l20 result-based form) is a HYPOTHESIS that contact quality predicts better — unmeasured on our props, with zero settled p_win yet. Swapping l5/l20 (the champion's two heaviest ±1.0 factors) blind could degrade the core grade undetectably for weeks. So this NOMINATES contact quality as a second challenger, records what it WOULD project per prop, and lets the settled ledger decide. Nothing users see changes; the champion is untouched. - src/services/contactChallenger.js — pure, mirrors challengerProjection. Log- odds lean (capped, never a re-forecast) from SEASON contact quality vs league percentiles. Metric→prop mapping is the whole game: barrel_pct→HR, hard_hit_pct→TB/doubles, k_pct-INVERSE→hits (singles resolve on contact frequency, not barrels), k_pct→batter K. rbi/runs/walks ABSTAIN (opportunity/ discipline — no clean contact predictor). Honest-absent: thin (<50 PA)/absent/ unmapped/non-batter → p_win_contact NULL (no projection), never a fallback; "measured but unremarkable" is distinct (equals champion, delta 0). - Wired in snapshotService AFTER arch-v1, reusing the already-loaded statcast rows; its own try so a second challenger can't break the pipeline. Reads g.p_win, never writes it. - Retained SEPARATELY on the ledger (p_win_contact/contact_delta/ contact_adjustments/contact_version='contact-v1') so each challenger's marginal contribution is measured independently; ledger_entries.stat gives per-prop-type segmentation. Migration 031 (applied to prod). Phase 0 (prod-verified): statcast_aggregates is SEASON cumulative (not rolling), 48h stale now but season-scoped so ~8 PA/600 is negligible; 100% of graded hitters covered, 92% at ≥50 PA; no xBA/xwOBA in the feed. Forward-only, version-stamped (contact_version null on pre-nomination rows). Promotion is a LATER decision on settled evidence, per prop type — never asserted here. contactChallenger 14/14; snapshot/ledger/arch-v1 suites 80 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
22 lines
1.2 KiB
SQL
22 lines
1.2 KiB
SQL
-- Phase A #2 — CONTACT-QUALITY CHALLENGER (contact-v1).
|
|
-- A SECOND challenger retained beside the champion (p_win) and the archetype
|
|
-- challenger (p_win_challenger / arch-v1). Kept in its OWN columns so each
|
|
-- challenger's marginal contribution is measurable independently — folding it
|
|
-- into p_win_challenger would contaminate a clean A/B.
|
|
--
|
|
-- null p_win_contact = HONEST ABSTENTION (no coverage / thin sample / unmapped
|
|
-- stat) — a real datum, distinct from a no-lean projection equal to the
|
|
-- champion. Segmentable by prop type via the existing ledger_entries.stat.
|
|
--
|
|
-- Additive + forward-only: existing rows get null contact_version (pre-nomination);
|
|
-- new rows carry 'contact-v1'. Champion and settled/locked grades are untouched.
|
|
--
|
|
-- NOTE: migrations 023-029 were applied directly to prod and are not in this
|
|
-- repo; this file resumes tracked migrations and is idempotent so it is safe to
|
|
-- (re)run against the live schema.
|
|
alter table ledger_entries
|
|
add column if not exists p_win_contact numeric null,
|
|
add column if not exists contact_delta numeric null,
|
|
add column if not exists contact_adjustments jsonb null,
|
|
add column if not exists contact_version text null;
|