S11 (a1): live tracking — the read locked, the game watched
MLB statsapi + WNBA ESPN live boxscores -> per-player current values
(live:{sport}:{date} TTL 90s, /api/live/:sport + Next proxy). Pure
propState math (HIT / ON PACE / NEEDS N / HOLDS / LINE PASSED — never
red in-progress), attachLiveProgress strip join on nameKey+statType,
proximity-to-hit slate float, StatStrip LiveTracker in the ROW-GRAMMAR
outcome slot (spec amended + lock test updated). Grades never change
in-game — tracking, labeled as such. 2698 -> 2757 tests, web build 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
# VYNDR — LIVE TRACKING v1.0 (A1 board, Session 11)
|
||||
### The read is locked pre-game. The game is watched. GRADES NEVER CHANGE IN-GAME — this layer is TRACKING, labeled as such, per the Ledger ethos.
|
||||
|
||||
## PRINCIPLES
|
||||
1. **Zero out-of-pocket.** Free feeds only: statsapi.mlb.com (MLB live boxscore)
|
||||
and the ESPN site API (WNBA summary boxscore). No new dependency, no quota key.
|
||||
2. **Grades never change in-game.** The locked pre-game read is untouched;
|
||||
live-progress marks are proto-outcomes rendered in the OUTCOME slot region
|
||||
(ROW-GRAMMAR §2 slot 6). Every live card carries the label
|
||||
"TRACKING — read locked pre-game" exactly once.
|
||||
3. **One meaning per color.** Green = on-pace or already-cleared; amber =
|
||||
needs-more / caution; red stays reserved for settled-negative truth
|
||||
(miss / DEAD from NOT-IN-LINEUP). An in-progress prop is NEVER red.
|
||||
4. **Only real box-line values.** A player not yet in the box score is ABSENT
|
||||
(no mark, no bar, no zero). `Number(null) === 0` is the classic fabrication
|
||||
bug — every numeric path uses strict null guards.
|
||||
5. **An under is never "hit" until final.** Under semantics are HOLDS-IF: green
|
||||
HOLDING while the count sits under the line, amber LINE PASSED once the
|
||||
count reaches/passes it (counting stats can still be re-scored; nothing
|
||||
settles until the game is final — the settle pass owns the truth).
|
||||
6. **DEAD is not declared here.** An over that "can no longer hit" is only
|
||||
honestly declarable for NOT-IN-LINEUP (S5, already shipped). Live tracking
|
||||
tracks until final; it never over-claims.
|
||||
|
||||
## DATA SOURCES (real shapes captured live, 2026-07-11)
|
||||
- **MLB** — `GET statsapi.mlb.com/api/v1/schedule?sportId=1&date=YYYY-MM-DD&hydrate=linescore`
|
||||
identifies Live games (`status.abstractGameState === 'Live'`) AND carries the
|
||||
per-game linescore (currentInning / inningState / scheduledInnings) in the
|
||||
same call. Per live game: `GET /api/v1/game/{gamePk}/boxscore` →
|
||||
`teams.{home,away}.players.ID{personId}.stats.{batting,pitching}`. A player
|
||||
who hasn't appeared has EMPTY stats objects → absent.
|
||||
- **WNBA** — ESPN scoreboard (already cached by scheduleService) identifies
|
||||
in-progress events (`status.type.state === 'in'`, `status.period`). Per live
|
||||
event: `GET site.api.espn.com/.../wnba/summary?event={id}` →
|
||||
`boxscore.players[].statistics[0]` with parallel `keys` + per-athlete
|
||||
`stats` string arrays. `didNotPlay` / empty stats → absent.
|
||||
|
||||
## STAT MAPS
|
||||
- **MLB (LOCAL map, `LIVE_BOX_FIELD`)** — mirrors the idea of
|
||||
outcomeService.MLB_LOG_FIELD but is deliberately LOCAL: the live boxscore
|
||||
splits batting vs pitching into separate stat objects (the game log picks
|
||||
ONE group by position), and innings_pitched must be parsed in thirds
|
||||
('5.2' = 5⅔ = 5.667) for live pace math. Wired stats: hits, total_bases,
|
||||
home_runs, rbi, runs, stolen_bases, doubles, walks (batting);
|
||||
strikeouts, earned_runs, innings_pitched, outs, hits_allowed (pitching).
|
||||
- **WNBA (`WNBA_BOX_KEY`)** — points, rebounds, assists, steals, blocks,
|
||||
turnovers, threes (made, parsed from "M-A"), pra (points+rebounds+assists,
|
||||
computed only when all three components are present).
|
||||
|
||||
## PROP STATE (pure math, `web/src/lib/liveProgress.js`)
|
||||
`propState({ side, line, current, progress })` →
|
||||
- over, current > line → `hit` (✓ HIT — the over has already cleared; a
|
||||
counting stat cannot un-clear; still labeled TRACKING until the settle pass)
|
||||
- over, not cleared → needs `N = floor(line) + 1 − current` (beats a push on
|
||||
integer lines); `on_pace` (green) when `current / progress ≥ floor(line)+1`,
|
||||
else `needs` (amber, "NEEDS N")
|
||||
- under, current < line → `holding` (green HOLDS chip — never ✓ until final)
|
||||
- under, current ≥ line → `past` (amber LINE PASSED — not red; nothing settled)
|
||||
- current == null → NO state (absent beats wrong)
|
||||
Progress fraction: MLB `(inning − (top ? 1 : 0.5)) / scheduledInnings`;
|
||||
WNBA `(period − 0.5) / 4`, clamped to [0, 1].
|
||||
|
||||
## ENDPOINTS
|
||||
- **`GET /api/live/:sport`** (public, rate-limited 60/min, mounted in app.js)
|
||||
→ `{ sport, date, hasLive, updated_at, games: [{ id, home, away, progress:
|
||||
{ label, fraction, inning|period, half }, players: { [nameKey]: { name,
|
||||
values: { stat_type: number } } } }] }`. mlb + wnba only; other sports →
|
||||
`{ hasLive: false, games: [] }`.
|
||||
- **POLLING RULE:** the route reads `live:{sport}:{date}` (TTL 90s). On a cache
|
||||
MISS it consults the day's schedule; ONLY when the schedule shows live games
|
||||
does it fetch boxscores. Quota math: 1 schedule call + N boxscore calls per
|
||||
90s window across ALL users (shared cache), during live windows only — zero
|
||||
boxscore calls when nothing is live.
|
||||
- **Next proxy** `web/src/app/api/live/[sport]/route.ts` (S25 rule — Express
|
||||
is not browser-reachable).
|
||||
|
||||
## FRONTEND (LIVE SLATE MODE)
|
||||
- Slate polls `/api/live/{sport}` every 60s ONLY while live games are on
|
||||
screen (mlb/wnba, status 'in').
|
||||
- `liveProgress.attachLiveProgress(strips, liveIndex)` (pure) joins built
|
||||
player strips to live values by nameKey + canonical statType; only graded,
|
||||
unsettled, non-dead props get `prop.live`.
|
||||
- StatStrip renders the live mark in the OUTCOME slot region:
|
||||
`1/2 TB · ▲6th` + a small game-progress bar + the state chip
|
||||
(ON PACE green / NEEDS 2 amber / HIT ✓ green filled / HOLDS green /
|
||||
LINE PASSED amber). Actions (parlay +, BOOK IT) are suppressed on live
|
||||
props — the pre-game market for the locked line is closed.
|
||||
- Live games with tracked props float to the top of the slate, ordered by
|
||||
proximity-to-hit (max over-fraction `current / needed-total`).
|
||||
- The card label "TRACKING — read locked pre-game" renders once per live card.
|
||||
|
||||
## ACCEPTANCE CRITERIA
|
||||
1. Parsers reproduce per-player values from REAL captured feed fixtures; a
|
||||
player absent from the box yields NO entry (never 0).
|
||||
2. propState covers: over cleared / on-pace / needs-N / under-holds /
|
||||
under-passed / absent — unit-tested.
|
||||
3. `/api/live/:sport` cache behavior: hit → no fetch; miss + live games →
|
||||
boxscore fetch + cache write; miss + no live games → schedule check only.
|
||||
4. attachLiveProgress joins strips ↔ live index and never touches settled,
|
||||
dead, or ungraded props.
|
||||
5. Full jest suite green from the worktree; `cd web && npm run build` exit 0.
|
||||
6. If a game is genuinely live during the build, the service runs once against
|
||||
the real feed and the report shows a real tracked line.
|
||||
|
||||
## TEST PLAN
|
||||
- `tests/unit/liveTrackingService.test.js` — MLB boxscore/linescore + WNBA
|
||||
summary parsers on real-shape fixtures; stat resolution incl. IP thirds;
|
||||
absent-player semantics; live-game identification from schedules.
|
||||
- `tests/unit/liveProgress.test.js` — propState math table; progress
|
||||
fractions; attachLiveProgress; proximity sort.
|
||||
- `tests/integration/liveRoute.test.js` — cache hit / miss+live / miss+idle /
|
||||
unknown sport, with injected deps (zero network).
|
||||
|
||||
— LIVE-TRACKING v1.0 · July 2026 —
|
||||
@@ -23,8 +23,8 @@ graded prop inline) reads left → right in this fixed order:
|
||||
| 3 stat+line+side | `TB O1.5` | white, mono — the subject of the row |
|
||||
| 4 market context | best-price dot · movement chip (STEAM ▲ / VALUE ▲) | what the MARKET is doing, before what the model says |
|
||||
| 5 model output | revision strikethrough (original grade) → current grade badge | history then present: `A̶ B` reads "was A, now B" |
|
||||
| 6 outcome | settled chip (✓ HIT / ✕ MISS / PUSH + actual) | once settled, actions (slot 7) disappear — the bet is over |
|
||||
| 7 actions | parlay `+` · BOOK IT ⟶ | suppressed when dead or settled |
|
||||
| 6 outcome | live TRACKING mark (`1/2 TB · ▲6th` + progress bar + state chip), then settled chip (✓ HIT / ✕ MISS / PUSH + actual) | S11 amendment: live progress is a PROTO-OUTCOME and lives in this slot; the two are mutually exclusive (a settled prop shows only the settled chip). Once settled, actions (slot 7) disappear — the bet is over |
|
||||
| 7 actions | parlay `+` · BOOK IT ⟶ | suppressed when live, dead or settled (the pre-game market for a locked line closes at first pitch) |
|
||||
| 8 provenance | `Graded 2h ago at -115` | the receipt, always last |
|
||||
|
||||
**Sub-line (stat + market context, one line under the row, in this order):**
|
||||
@@ -49,6 +49,13 @@ Red is never used for mere movement-against or a lower price — those are amber
|
||||
the net move is TOWARD the graded side, amber when net AGAINST, dim when flat —
|
||||
never red (nothing has settled).
|
||||
|
||||
**S11 amendment — live TRACKING marks (slot 6 proto-outcomes):** green =
|
||||
already-cleared over (HIT ✓, filled), on-pace over, or an under still holding;
|
||||
amber = NEEDS N or an under whose line has been passed. An IN-PROGRESS prop is
|
||||
NEVER red — red stays reserved for the settled miss and the NOT-IN-LINEUP dead
|
||||
read. The read is locked pre-game and never re-grades; every live card carries
|
||||
"TRACKING — read locked pre-game" exactly once (specs/LIVE-TRACKING.md).
|
||||
|
||||
## 4. MARK LAW — the micro-vocabulary
|
||||
- **● / ○ dot strip** — last 10 games vs TONIGHT'S locked line, newest first.
|
||||
Filled green = that game's stat cleared the line; hollow dim = it didn't.
|
||||
|
||||
Reference in New Issue
Block a user