Files
vyndr/specs/propline-audit.md
builtbykev 2ae8a5697e Session 56: Full audit — PropLine + boxscore + pipeline + sport coverage (2289 tests)
Research (verified against live MLB Stats / ESPN / The Odds APIs):
- specs/propline-audit.md — every stat_type mapped against our 4-layer pipeline;
  real MLB boxscore fields; sport coverage status; pipeline gap analysis.
- specs/vyndr-roadmap.md — priority-ordered Sessions 57–64 + coverage targets.
- scripts/propline-audit.js + specs/audit-data/ (raw capture).

Headline bug: oddsNormalizer mapped batter_rbis → 'rbis' while the whole
grade/feature/outcome chain keys on 'rbi' — every PropLine RBI prop silently
failed to grade AND settle. Fixed (+ regression test).

Phase 4 — wired missing MLB stats end-to-end:
- PropLine MLB markets 6 → 12 (+runs, walks, doubles, earned_runs, hits_allowed,
  outs — same request, no extra quota).
- doubles/outs/triples added to featureCache + outcomeService MLB_LOG_FIELD and
  all three grade whitelists (analyze/scan/validation.py).

Phase 6 — pipeline resilience:
- opsNotify.js: ntfy alerts (never throws, test-disabled). Snapshot success/
  stale/failure alerts; retry-once on hard odds error (not on empty slate).
- Missed-cron watchdog (mostRecentExpectedSlot/isSnapshotOverdue); status probe
  now returns `overdue`.

Coverage truth: MLB is the only end-to-end-live sport; outcome settlement is
MLB-only (WNBA/NBA/soccer never settle) — documented as the #1 roadmap gap.

Backend 2276 → 2289 tests (+13). Web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 17:00:29 -04:00

146 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PropLine + Box Score + Pipeline Audit — 2026-07-10 (Session 56)
Ground-truth inventory of what data we can access, what our pipeline handles, and
the gaps. Live sections were fetched against real APIs (MLB Stats, ESPN, The Odds
API `/v4/sports`). PropLine has no dev keys, so its inventory is derived from the
authoritative code (`proplineAdapter.MARKETS` × `oddsNormalizer.MARKET_MAP`) —
that IS the set our pipeline requests + normalizes. Raw capture:
`specs/audit-data/propline-audit-raw.json`.
Baseline: **2276 tests** passing at session start.
---
## 0. What is in-season RIGHT NOW (The Odds API `/v4/sports`, live 2026-07-10)
| Sport | Active key(s) | Notes |
|-------|---------------|-------|
| MLB | `baseball_mlb` | ✅ full season — the live product |
| WNBA | `basketball_wnba` | ✅ in-season (3 games tonight per ESPN) |
| NBA | `basketball_nba_summer_league` only | ⚠️ regular NBA off until Oct; summer league live |
| NFL | `americanfootball_nfl`, `_nfl_preseason` | ⚠️ preseason lines starting |
| Soccer | 40+ leagues (`soccer_epl`, `soccer_usa_mls`, `soccer_fifa_world_cup`, La Liga, Serie A, …) | ✅ year-round via odds-api |
| NHL | (absent) | off-season — no lines |
Takeaway: **MLB + WNBA + soccer are the live opportunity today.** NBA/NFL are
pre-season shells; NHL is dark.
---
## 1. PropLine / odds inventory — what we REQUEST vs what we CAN normalize
`proplineAdapter.MARKETS` = the markets we actually ask for. `MARKET_MAP` = every
market we can normalize (superset). A market we request but don't map → **silent
zero**; a market we map but don't request → **unused capacity** (free to add — same
call).
### MLB
| Requested market | → stat_type | Grade whitelist? | Features (l5/l20)? | Outcome settle? |
|---|---|---|---|---|
| batter_hits | hits | ✅ | ✅ hits | ✅ hits |
| batter_home_runs | home_runs | ✅ | ✅ homeRuns | ✅ homeRuns |
| batter_total_bases | total_bases | ✅ | ✅ totalBases | ✅ totalBases |
| batter_rbis | **rbis** ❌ | ✅ (`rbi`) | ✅ (`rbi`) | ✅ (`rbi`) | ← **BUG: normalizes to `rbis`, whole chain keys on `rbi` → never grades/settles** |
| batter_stolen_bases | stolen_bases | ✅ | ✅ stolenBases | ✅ stolenBases |
| pitcher_strikeouts | strikeouts | ✅ | ✅ strikeOuts | ✅ strikeOuts |
**Mapped but NOT requested (free capacity — all supported downstream already):**
`batter_runs`→runs, `batter_walks`→walks, `pitcher_earned_runs`→earned_runs,
`pitcher_hits_allowed`→hits_allowed. **Mapped, not requested, and needs feature/
settle wiring:** `batter_doubles`→doubles, `pitcher_outs`→outs. Confirmed present
in the real boxscore (§2).
### WNBA / NBA
Requested: `player_points`, `player_rebounds`, `player_assists`, `player_threes`
(+ NBA adds `player_blocks`, `player_steals`). All grade-whitelisted. **Features:
NBA/WNBA game logs come from the Python nba_api service (usually OFFLINE in prod)
→ l5/l20 often empty; ESPN fallback exists for season avgs only. Outcomes: NOT
settled at all (outcomeService is MLB-only).** `player_turnovers`/`player_pra` are
mapped but not requested.
### Soccer
**PropLine has NO soccer entry** (`SPORT_KEYS`/`MARKETS` omit it) — soccer props
can only come from the odds-api backup. `MARKET_MAP` maps goals/shots/shots_on_
target/tackles/cards/corners/saves/passes/clean_sheet. Features: soccer extractor
exists; outcomes: NOT settled.
### NFL / NHL
NFL markets mapped + requested (pass/rush/rec yds, receptions, TDs) but the flow
isn't graded end-to-end yet. NHL mapped/requested but off-season.
---
## 2. MLB box score field map (REAL — Braves @ Pirates, 2026-07-09)
Batting fields present: `runs, doubles, triples, homeRuns, strikeOuts,
baseOnBalls, hits, atBats, stolenBases, totalBases, rbi, sacFlies, …`
Pitching fields present: `strikeOuts, baseOnBalls, hits, inningsPitched,
earnedRuns, outs, battersFaced, …`
**OUTCOME_MAP (MLB) — stat_type → game-log/boxscore field:**
```
hits → hits total_bases → totalBases home_runs → homeRuns
rbi → rbi runs → runs walks → baseOnBalls
stolen_bases → stolenBases strikeouts → strikeOuts earned_runs → earnedRuns
hits_allowed → hits (pitching) innings_pitched → inningsPitched
doubles → doubles ⟵ NEW triples → triples ⟵ NEW outs → outs ⟵ NEW
batter_strikeouts → strikeOuts ⟵ (batter Ks — roadmap)
```
Every field above is real in the boxscore/game log → all are settleable today.
## 2b. ESPN box score (WNBA/soccer — the non-MLB settle path)
WNBA settlement source = ESPN summary `boxscore.players[].statistics[]`
(`labels`/`names` arrays: PTS/REB/AST/3PM/STL/BLK/TO). No completed game at fetch
time (all 3 tonight were Scheduled), so labels weren't captured — wiring the ESPN
settle path is a roadmap item (`espnStatsAdapter` already parses this shape for
season avgs).
---
## 3. Pipeline flow + gaps
**Current flow:** cron (`SNAPSHOT_CRON=1`, UTC 14,19,22,1,3) → per sport:
`settleAllOutcomes()` (S55, MLB only) → `snapshotService.runSnapshot` → PropLine
(3-key rotation) / odds-api backup → `gradeAndCacheSlate` → per-player archetype +
features → Redis (`snapshot:{sport}:latest`, `grades:{sport}`) → ticker.
**Gaps found:**
1. **RBI silent failure**`batter_rbis → rbis` mismatch (§1). FIX this session.
2. **Under-requesting MLB markets** — only 6 of ~12 supported; runs/walks/doubles/
earned_runs/hits_allowed/outs are free in the same call. EXPAND this session.
3. **Outcome settlement is MLB-only** — WNBA/NBA/soccer grades NEVER settle, so
`accuracy` is MLB-only. Biggest roadmap item (needs the ESPN settle path).
4. **No pipeline alerting** — a failed/empty/missed cron run is silent. ADD ntfy
this session.
5. **No retry** — one PropLine blip marks a sport unavailable for the whole cycle.
ADD retry-once this session.
6. **No missed-cron detector** — nothing notices if a slot didn't fire. ADD.
7. **NBA/WNBA features depend on an offline Python service** — l5/l20 often empty;
ESPN gives season avgs only. Roadmap: an ESPN game-log feature path.
8. **Soccer has no PropLine source** — odds-api backup only; document + monitor.
---
## 4. Sport coverage status (verified 2026-07-10)
| Sport | Odds props | Grade pipeline | Features | Outcomes | Status |
|-------|-----------|----------------|----------|----------|--------|
| MLB | ✅ PropLine + odds-api | ✅ | ✅ real (statsapi) | ✅ (S55) | **LIVE** |
| WNBA | ✅ odds-api/PropLine | ✅ | ⚠️ Python offline → thin | ❌ not settled | **PARTIAL** |
| NBA | ⚠️ summer league only | ✅ | ⚠️ offline | ❌ | **OFF-SEASON** |
| Soccer | ✅ odds-api (no PropLine) | ⚠️ extractor exists | ⚠️ | ❌ | **PARTIAL** |
| NFL | ⚠️ preseason | ⚠️ mapped, not e2e | ❌ | ❌ | **PRE-SEASON** |
| NHL | ❌ off-season | ⚠️ mapped | ❌ | ❌ | **DARK** |
---
## 5. Fixes shipped this session (Phase 4/6)
- `batter_rbis → rbi` (unblocks a market we already request).
- PropLine MLB MARKETS expanded: +batter_runs, +batter_walks, +batter_doubles,
+pitcher_earned_runs, +pitcher_hits_allowed, +pitcher_outs.
- `doubles` + `outs` wired into featureCache + outcomeService + all three grade
whitelists (analyze.js / scan.js / validation.py).
- Pipeline alerting (ntfy) + PropLine retry-once + missed-cron detector.
See `specs/vyndr-roadmap.md` for the priority-ordered plan that follows from this.