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:
Kev
2026-07-12 00:45:29 -04:00
parent 14dc9cf270
commit b5d3fd14bb
18 changed files with 1666 additions and 18 deletions
+44
View File
@@ -0,0 +1,44 @@
'use strict';
/**
* GET /api/live/:sport — LIVE TRACKING read (A1 board, Session 11).
*
* Public, rate-limited 60/min. Cache-aside via liveTrackingService:
* reads `live:{sport}:{date}` (TTL 90s); on a miss it consults the day's
* schedule and fetches boxscores ONLY when games are actually live — so the
* total upstream cost is 1 schedule call + N-live-games boxscore calls per
* 90s window across ALL users, and zero boxscore calls when nothing is live
* (specs/LIVE-TRACKING.md POLLING RULE).
*
* This is TRACKING, not re-grading — grades are locked pre-game and this
* endpoint never touches them.
*/
const express = require('express');
const { createRateLimit } = require('../middleware/rateLimit');
const { getLiveTracking } = require('../services/liveTrackingService');
const router = express.Router();
router.use(createRateLimit({ windowMs: 60_000, max: 60 }));
const LIVE_SPORTS = new Set(['mlb', 'wnba']);
router.get('/:sport', async (req, res) => {
const sport = String(req.params.sport || '').toLowerCase();
if (!LIVE_SPORTS.has(sport)) {
// Honest empty for sports without a free live box feed — never an error.
return res.json({ sport, hasLive: false, games: [] });
}
try {
const data = await getLiveTracking(sport);
// Short CDN/browser cache — matches the 90s shared window without
// holding a live slate stale for long.
res.set('Cache-Control', 'public, max-age=30');
return res.json(data);
} catch (err) {
console.error('[live]', err.message);
return res.status(200).json({ sport, hasLive: false, games: [] });
}
});
module.exports = router;