Session 32: Grades pipeline + NFL/NHL wiring + rate limiting + audit cleanup (1718 tests)

- gradeSlateService writes grades:{sport} cache (closes content pipeline →
  dataLevel full); fire-and-forget from oddsService.recordDownstream, gated
  by shouldGradeSlate (off in test, GRADE_SLATE_ON_FETCH override)
- NFL/NHL wired: oddsService SPORT_KEYS/SPORT_MARKETS (correct the-odds-api
  keys americanfootball_nfl/icehockey_nhl), proplineAdapter MARKETS, NHL
  MARKET_MAP keys to avoid silent-zero
- rate limiting mounted on 8 public cached routers (odds/parlay 30/min,
  rest 60/min)
- jsonlLogger writes to temp under test (no more dirtied tracked artifact);
  5MB pipeline test given 20s timeout

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-15 18:21:32 -04:00
parent 2ba3958c7a
commit f0c8b4f29b
20 changed files with 667 additions and 9 deletions
+86 -2
View File
@@ -1,10 +1,94 @@
# VYNDR — Build State
## Last Updated
2026-06-14
2026-06-15
## Current Phase
AUDIT v31.0 — Full code audit + security review + cleanup (Session 31)
SHIP BUILD v32.0 — Close audit gaps: grades pipeline + NFL/NHL wiring +
rate limiting + test-artifact cleanup (Session 32)
## Session 32 (2026-06-15) — SHIPPED
Surgical close of the four functional gaps Session 31's audit documented.
No new feature scope (design session runs in parallel). Backend
1695 → **1718 tests** (+23), 140 suites, zero regressions. Web build clean.
Tracked `data/` tree stays clean after a full test run (artifact fixed).
### PHASE 1 — Grades cache writer (closes the content pipeline)
- Traced: `analyzeViaEngine1` (computeFeatures → engine1 → toLegacyShape)
produces the legacy grade shape; `contentTemplateService` reads
`grades:{sport}:{utc}` then `grades:{sport}`, accepting an array or
`{grades:[...]}`. The legacy shape already matches `normalizeGrade` — no
remap needed. Session 31 confirmed NO writer existed.
- `gradeSlateService.gradeAndCacheSlate(sport, props, opts)` — dedupes to
unique player+stat+line (cap 25, concurrency 5), grades BOTH sides
(engine1 is direction-aware) keeping the higher-confidence verdict, sorts
by confidence desc, writes `grades:{sport}` = `{grades, updated_at,
source}` TTL 2h. Injectable grader/cacheSet/now → fully unit-tested.
- Wired fire-and-forget into `oddsService.recordDownstream` (fires on a
fresh odds fetch only, never blocks the odds response). Gated by
`shouldGradeSlate()`: default ON, OFF under `NODE_ENV==='test'` (its
feature-compute fan-out otherwise pollutes the odds integration tests'
axios call-count assertions via floating promises), `GRADE_SLATE_ON_FETCH`
override (`0` = operator kill-switch). 12 tests. Self-eval 9/10.
### PHASE 2 — NFL + NHL sport-key wiring
- **Corrected the spec's sport keys.** Spec said `football_nfl`/`hockey_nhl`
for `oddsService.SPORT_KEYS`, but that file maps to the-odds-api, whose
keys are `americanfootball_nfl` / `icehockey_nhl` (verified against the
the-odds-api sports list). The spec's values would have silently 404'd —
the exact silent-failure class this session fights. (`football_nfl`/
`hockey_nhl` ARE correct for PropLine in `proplineAdapter`, unchanged.)
- Added nfl/nhl to `SPORT_KEYS` + `SPORT_MARKETS` (NFL_MARKETS/NHL_MARKETS).
Filled `proplineAdapter.MARKETS.nfl/nhl`. Added NHL keys
(`player_shots_on_goal`, `goalie_saves`) to `MARKET_MAP` so NHL props
don't normalize to zero in-season (same fix family as Session 31's NFL
MARKET_MAP gap). 10 tests, incl. end-to-end MARKET_MAP normalization +
off-season empty handling. Self-eval 9/10.
### PHASE 3 — Rate limiting on public routes
- Mounted the existing `middleware/rateLimit` (`createRateLimit`, in-memory
per-IP, independent bucket per call site) via `router.use` on every public
cached router: odds + parlay = 30/min; schedule/gamelines/streaks/hotlist/
content/lines/books = 60/min. `/api/analyze` keeps its own 10/min.
- 3 route-level tests (429 after limit, tighter odds limit, independent
per-router buckets) complementing the existing middleware-level tests.
Self-eval 9/10.
### PHASE 4 — Test artifact + cleanup
- `jsonlLogger` ROOT now resolves to `os.tmpdir()/vyndr-training-test` under
`NODE_ENV==='test'` (override `TRAINING_DATA_DIR`), so tests no longer
append to the tracked `data/training/resolutions-YYYY-MM.jsonl`. Reverted
the dirtied artifact; verified the tree stays clean after a full run.
- Gave the 5MB-payload pipeline regression test a 20s timeout — it's
CPU-bound and flaked on Jest's 5s default under the heavier full-suite
load from the +23 new tests (Jest's own error message recommends this).
### Files created
- `src/services/gradeSlateService.js`
- `tests/unit/gradeSlateService.test.js`,
`tests/unit/nflNhlWiring.test.js`,
`tests/integration/publicRouteRateLimit.test.js`
### Files modified
- `src/services/oddsService.js` (SPORT_KEYS/SPORT_MARKETS nfl+nhl,
recordDownstream grades trigger + shouldGradeSlate gate)
- `src/utils/oddsNormalizer.js` (NHL MARKET_MAP keys)
- `src/services/adapters/proplineAdapter.js` (MARKETS.nfl/nhl)
- `src/services/training/jsonlLogger.js` (test-env temp path)
- 8 public route files (rate-limit mount): odds, parlay, schedule,
gameLines, streaks, hotlist, content, lineMovement, bookComparison
- `tests/integration/pipeline.test.js` (5MB test timeout)
- `CLAUDE.md`
### Deliberately deferred (per spec — await design session)
- CLV tracking + prop resolution (need PropLine Hobby-tier endpoint access
confirmed). Design implementation. Player watchlists + push.
- The grades auto-grade trigger is ON by default but its per-prop
feature-compute cost is unmeasured in prod; `GRADE_SLATE_ON_FETCH=0` is
the kill-switch. Worth profiling once real PropLine slates flow.
---
## Session 31 (2026-06-14) — SHIPPED (audit)