Session 52: Coming Soon teaser + infrastructure verification (2239 tests)

Phase 1 — Push-to-Book teaser (feature not live; teaser only):
- StatStrip: "BOOK IT ⟶" per graded prop (hover: "Push-to-Book coming soon").
- GradeResultCard: "PUSH-TO-BOOK · COMING SOON" footer.

Phase 2 — infrastructure verification:
- snapshotScheduler logs armed AND disarmed state (incl SNAPSHOT_CRON) so
  container logs disambiguate off-vs-crashed.
- NEW GET /api/internal/snapshot/status (internal-key gated): cron_armed,
  cron_hours_utc, last_snapshot per sport (gradeCount/deltaCount), redis_keys
  existence map, ticker_count. The post-deploy pipeline health probe.
- Finding: Redis AOF/RDB persistence is a server-side (Coolify) config the app
  can't set/verify — documented.

Phase 3 — delta pipeline (verified sound, no fix needed):
- runSnapshot already rotates :latest->:previous and diffs locked lines; added
  opt-in SNAPSHOT_DEBUG=1 [deltas] log + a trace test asserting :previous is
  preserved verbatim and the delta math is correct.

Backend 2234 -> 2239 tests (+5), 192 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-19 13:55:39 -04:00
parent f0674ca07d
commit cdedecf55b
12 changed files with 221 additions and 5 deletions
+43
View File
@@ -104,6 +104,49 @@ router.post('/snapshot/all', async (req, res) => {
}
});
/**
* GET /api/internal/snapshot/status (Session 52) — verification probe. Reports
* whether the in-process cron is armed, the freshest snapshot per sport, which
* pipeline Redis keys exist, and the ticker item count. Read-only; safe to poll.
* GET vs the POST /snapshot/:sport below — no route collision.
*/
router.get('/snapshot/status', async (req, res) => {
const { cacheGet } = require('../utils/redis');
const { HOURS_UTC } = require('../snapshotScheduler');
const SPORTS = ['mlb', 'nba', 'wnba'];
try {
const redis_keys = {};
const last_snapshot = {};
for (const sp of SPORTS) {
const latestKey = `snapshot:${sp}:latest`;
const prevKey = `snapshot:${sp}:previous`;
const gradesKey = `grades:${sp}`;
const [latest, prev, grades] = await Promise.all([cacheGet(latestKey), cacheGet(prevKey), cacheGet(gradesKey)]);
redis_keys[latestKey] = !!latest;
redis_keys[prevKey] = !!prev;
redis_keys[gradesKey] = !!grades;
if (latest) {
last_snapshot[sp] = {
updated_at: latest.updated_at || null,
gradeCount: Array.isArray(latest.grades) ? latest.grades.length : 0,
deltaCount: Array.isArray(latest.deltas) ? latest.deltas.length : 0,
};
}
}
const ticker = await cacheGet('ticker:items');
redis_keys['ticker:items'] = !!ticker;
return res.json({
cron_armed: process.env.SNAPSHOT_CRON === '1',
cron_hours_utc: HOURS_UTC,
last_snapshot,
redis_keys,
ticker_count: Array.isArray(ticker) ? ticker.length : 0,
});
} catch (err) {
return res.status(500).json({ ok: false, error: err.message });
}
});
router.post('/snapshot/:sport', async (req, res) => {
const snapshot = require('../services/snapshotService');
try {