Session 45: Snapshot pipeline + GameCard swap + live ticker (2100 tests)

The on-demand "Read" grade model is RETIRED. A scheduled pipeline pre-grades the
slate, locks grades to the line, tracks movement; the dashboard shows them already
there. Orchestrates existing services — nothing rebuilt.

- snapshotService.runSnapshot(sport): getOdds → gradeAndCacheSlate → classify
  archetype per player → lock gradedAt → line deltas vs previous snapshot → write
  snapshot:{sport}:latest/previous + grades:{sport} → ticker events. Fully
  injectable, zero-network unit tests. runAllSnapshots = cron entrypoint.
- Internal trigger POST /api/internal/snapshot/:sport + /all (requireInternalAuth).
  In-process cron (SNAPSHOT_CRON=1, UTC 14,19,22,1,3) in server.js, no new dep.
- Public reads: GET /api/snapshot/:sport (cache-only) + GET /api/ticker (merges
  TICKER_MANUAL pins) + Next proxies.
- GameCard swap: live Slate renders vyndr/GameCard (legacy kept for types only),
  overlays locked grades onto game props → player name once + archetype badge +
  "Graded Xh ago at -115 · Current 2.5 · ▲ TOWARD +1.0". Ungraded → "Awaiting next
  scan", NO Read button. On-demand onGrade flow deleted.
- Ticker polls /api/ticker every 30s, graceful fallback to hardcoded items.
- NBA/WNBA: espnStatsAdapter free fallback (defensive parse → found:false on shape
  mismatch) wired into resolvePlayerStats after the offline Python service.

Env: PROPLINE_API_KEY_1/2/3, VYNDR_INTERNAL_KEY, SNAPSHOT_CRON=1, TICKER_MANUAL.
Backend 2061 -> 2100 tests (+39), 173 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-18 21:34:29 -04:00
parent 7969a4971a
commit f8b120c0aa
24 changed files with 1425 additions and 129 deletions
+59 -3
View File
@@ -4,9 +4,65 @@
2026-06-18
## Current Phase
SHIP BUILD v44.0 — Make it visible: VYNDR Original archetype names, grade-card
intel wiring, stale-game filtering, landing copy, depth proxies. Frontend +
wiring only (no new backend services).
SHIP BUILD v45.0 — Snapshot pipeline + GameCard swap + live ticker. The product
shifted: on-demand "Read" grading is RETIRED; a scheduled pipeline pre-grades the
slate, locks grades to the line, and the dashboard shows them already there.
## Session 45 (2026-06-18) — SHIPPED ✅ SNAPSHOT PIPELINE
The on-demand grade model is retired. Backend 2061 → **2100 tests** (+39), 173
suites. Web build clean (exit 0).
### Phase 1 — snapshotService (orchestration of existing services)
`src/services/snapshotService.js` `runSnapshot(sport)`: getOdds (PropLine
rotation) → gradeAndCacheSlate (captured via injected cacheSet) → classify each
player's archetype (resolvePlayerStats + archetypeService, pure math) → attach
`gradedAt {line, odds, timestamp}` (LOCKED) → compute line deltas vs the previous
snapshot → write `snapshot:{sport}:latest|previous` + `grades:{sport}` → emit
ticker events. `runAllSnapshots()` loops mlb/nba/wnba/soccer. Everything
injectable → fully unit-tested with zero network. Deltas: `toward` = market
confirming our side, `away` = opposing; noise filtered <0.5.
### Phase 2 — internal API + cron + ticker API
- `POST /api/internal/snapshot/:sport` + `/snapshot/all` (existing
`requireInternalAuth`; `/all` registered first so it isn't captured as a sport).
- `GET /api/ticker` (public, cache-only, merges `TICKER_MANUAL` pins) + Next proxy.
- In-process cron `src/snapshotScheduler.js` (gated `SNAPSHOT_CRON=1`, UTC hours
14,19,22,1,3 = 10AM/3PM/6PM/9PM/11PM ET), armed in server.js. No new dep —
1-min unref'd interval, fires once per slot. Prefer external n8n cron hitting
the internal endpoint for multi-replica deploys.
### Phase 3 — GameCard swap (THE product shift)
The live Slate now renders `vyndr/GameCard` (legacy kept for TYPES only). Reads
`GET /api/snapshot/:sport` (cache-only, public; falls back to `grades:{sport}`),
overlays the locked grades onto each game's odds-derived props via
`slateAdapter.buildPlayerStripsFromProps` → player name ONCE + archetype badge +
locked grade + "Graded Xh ago at -115 · Current 2.5 · ▲ TOWARD +1.0" line delta.
Ungraded props → "Awaiting next scan" (NO Read button). The on-demand onGrade
flow was deleted.
### Phase 4 — live ticker
`vyndr/Ticker` now polls `/api/ticker` every 30s (graceful: keeps the passed
hardcoded items as initial/fallback, never blanks). Tag colors: A+/A/SCAN green,
MOVE/CASCADE amber, ALERT white.
### Phase 5 — NBA/WNBA ESPN fallback
`espnStatsAdapter.getSeasonAverages(name, sport)` — free public ESPN stats,
DEFENSIVE (`parseAthleteStats` returns null on any unrecognized shape → caller
degrades to found:false; never a wrong-but-confident number). Wired as the
NBA/WNBA fallback in `resolvePlayerStats` after the offline Python service. Note:
the live ESPN athlete-stats shape may need tuning against production — the parser
is tolerant and tested against a representative payload.
### Env vars (set in Coolify)
- `PROPLINE_API_KEY_1/2/3` — PropLine props rotation (snapshot odds source).
- `VYNDR_INTERNAL_KEY` — the internal-auth secret (header `x-internal-key`) for
the snapshot trigger. (This is the existing key, NOT a new INTERNAL_AUTH_TOKEN.)
- `SNAPSHOT_CRON=1` — arm the in-process scheduler (omit if using n8n).
- `TICKER_MANUAL` — JSON array of editorial pins, e.g.
`[{"tag":"ALERT","text":"VYNDR 2.0 is live."}]`.
## Session 44 (2026-06-18) — SHIPPED ✅ MAKE IT VISIBLE
## Session 44 (2026-06-18) — SHIPPED ✅ MAKE IT VISIBLE