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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W1sivYNqY2TS5ftykmHBU9
This commit is contained in:
Kev
2026-08-04 16:19:18 -04:00
parent 7f69fef14c
commit a80a775fa0
2 changed files with 44 additions and 0 deletions
+29
View File
@@ -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)