From a80a775fa0bfeec307487ccfde2b083e1e1bda6e Mon Sep 17 00:00:00 2001 From: Kev Date: Tue, 4 Aug 2026 16:19:18 -0400 Subject: [PATCH] Record the lineup-context ingest: prod-verified, 153 lineups / 149 opportunity Verified in production via the new on-demand endpoint: 153 batting-order rows across 10 games (orders 1-9), and 149 hitter-opportunity rows with RISP shares ranging 0.170 to 0.528. The data passes its own coherence check on arrival: the highest RISP-share hitters all bat fourth and fifth, which is exactly where the mechanism says the RBI opportunity lives. Nothing was fitted to produce that -- it is the two tables joining and agreeing. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9 --- BUILD-STATE.md | 15 +++++++++++++++ CLAUDE.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/BUILD-STATE.md b/BUILD-STATE.md index af9cfed..bd09e96 100755 --- a/BUILD-STATE.md +++ b/BUILD-STATE.md @@ -3,6 +3,21 @@ ## Last Updated 2026-08-03 +## Session 89 (2026-08-04) — Lineup + baserunner context ingested ✅ +Spec: commit history + `src/services/lineupContextService.js`. 4,250 tests / 338 +suites green, build exit 0. Counter + frozen clusters byte-identical. +- **THE INPUT RBI/RUNS ALWAYS NEEDED, ingested free from statsapi.** + `lineup_context` (batting order, 153 rows / 10 games) + `hitter_opportunity` + (RISP share, 149 rows, range 0.170–0.528). Both DATED in the PK. +- **Rung 2 was cheap:** situational splits give the RISP aggregate in ONE call + per player, not play-by-play reconstruction. +- **Prod-verified** via the new `POST /api/internal/lineup-context/refresh` + (added because the first run wrote 0 while the parser worked locally — a 0 is + a wiring bug until proven otherwise). +- **Coherence check passes:** top RISP-share hitters all bat 4th/5th. +- DRIVER/CATALYST theories now INPUT-READY, sample-blocked. Proofs run later + under native cumulative correction — ingesting is not proving. + ## Session 88 (2026-08-04) — Re-adjudication: nothing to demote, hole closed ✅ Spec: `specs/re-adjudication.md`. 4,238 tests / 337 suites green, build exit 0. Counter byte-identical. Nothing recalibrated — nothing needed to be. diff --git a/CLAUDE.md b/CLAUDE.md index 6405136..8dad875 100755 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1557,6 +1557,35 @@ phased plan in the Session-57 conversation / BUILD-STATE Next section). - **Don't emit a public "recalibrated after re-adjudication" ledger event when nothing changed** — announcing rigour that did no work is itself a false signal. +## Lineup + baserunner context ingest (Session 89 — non-obvious) +- **RBI/runs were INPUT-blocked, and the input is now ingested** — both halves + free from statsapi (already used for game logs/schedules/pitchers). + `src/services/lineupContextService.js` → `lineup_context` (batting order) + + `hitter_opportunity` (RISP share). Both DATED in the PK. +- **`schedule?hydrate=lineups` → `homePlayers`/`awayPlayers` are ORDERED arrays + of 9 — the array order IS the batting order** (index 0 = leadoff). Nothing is + inferred; a short lineup records fewer slots rather than padding to nine. +- **RUNG 2 IS CHEAP, contrary to expectation.** "How often does he bat with + runners on" looked like a play-by-play reconstruction; statsapi serves it via + `people/{id}/stats?stats=statSplits&sitCodes=risp,r0` — ONE call PER PLAYER + (season aggregate), not per game. Real example: 87 PA with RISP → 25 RBI vs + 302 PA bases-empty → 17 RBI. Probe for a cheaper aggregate endpoint before + assuming per-event reconstruction. +- **`risp_share` is RISP ÷ (RISP + bases-empty)** — deliberately NOT ÷ season PA, + because runner-on-first-only belongs to neither split. It is "RISP as a + fraction of the PAs we can classify", stated exactly rather than implied. +- **A hitter with no splits is NULL, never a 0 share** — 0 would assert he never + bats with runners on, a strong and usually false claim. +- **`POST /api/internal/lineup-context/refresh`** verifies the ingest in seconds. + Built because the first prod run wrote 0 rows while the parser worked locally + (144 rows / 10 games) — diagnosing that via a full snapshot costs ~3 min and + 524s at the edge. Same lesson as the defence feed: **a 0 is a wiring bug until + proven an honest absence.** +- **Coherence check that the data passes:** the top RISP-share hitters all bat + 4th/5th — the mechanism showing up the moment both tables joined. +- DRIVER/CATALYST pre-registered theories are now INPUT-READY (were + input-blocked); they are sample-blocked from here. Ingesting is not proving. + ## Active Skills - vyndr-voice (all user-facing output) - prop-analysis (grading methodology)