Files
vyndr/specs/propline-audit.md
T
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

7.4 KiB
Raw Blame History

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)
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 failurebatter_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.