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:
+59
-3
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user