'use strict'; /** * snapshotScheduler — in-process snapshot cron (Session 45). * * No new dependency: a 1-minute unref'd interval that fires `runAllSnapshots` * at the configured UTC hours (default 14,19,22,1,3 = 10AM/3PM/6PM/9PM/11PM ET, * matching the sports cycle — morning research, afternoon news, pre-game lock, * in-game). Gated on SNAPSHOT_CRON=1 so it never runs in dev/test or on a * container that shouldn't own the schedule. Prefer an EXTERNAL cron (n8n) hitting * POST /api/internal/snapshot/all when running multiple API replicas — this * in-process variant assumes a single scheduler instance. */ const HOURS_UTC = (process.env.SNAPSHOT_HOURS_UTC || '14,19,22,1,3') .split(',') .map((n) => parseInt(n, 10)) .filter((n) => Number.isInteger(n) && n >= 0 && n <= 23); function startSnapshotScheduler(opts = {}) { if (process.env.SNAPSHOT_CRON !== '1') return null; const runAll = opts.runAllSnapshots || require('./services/snapshotService').runAllSnapshots; const now = opts.now || (() => new Date()); let lastFiredSlot = null; const tick = async () => { const d = now(); if (d.getUTCMinutes() !== 0) return; const h = d.getUTCHours(); if (!HOURS_UTC.includes(h)) return; const slot = `${d.getUTCFullYear()}-${d.getUTCMonth()}-${d.getUTCDate()}-${h}`; if (slot === lastFiredSlot) return; // fire once per slot lastFiredSlot = slot; try { const results = await runAll(); const ok = results.filter((r) => r.status === 'ok'); console.log(`[snapshot] cron fired ${h}:00 UTC — ${ok.length}/${results.length} sports graded`); } catch (e) { console.warn('[snapshot] cron run failed:', e.message); } }; const interval = setInterval(tick, 60_000); if (interval.unref) interval.unref(); console.log(`[snapshot] scheduler armed for UTC hours: ${HOURS_UTC.join(', ')}`); return { interval, tick }; } module.exports = { startSnapshotScheduler, HOURS_UTC };