118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Odds-cache prewarmer (Session 22).
|
|
*
|
|
* On the free 500-credit/month odds-api tier this script is a
|
|
* negative-EV operation: a single fetchAllOdds() call costs
|
|
* 1 (events lookup) + N (per-event odds) credits, and a 1h-TTL
|
|
* warmer for one sport would spend ~24 credits/day, blowing the
|
|
* monthly budget across multiple sports. It only makes sense
|
|
* once the account is on a higher tier.
|
|
*
|
|
* Therefore: gated by `ODDS_PREWARM=1`. The script REFUSES to run
|
|
* when the flag is unset — operators opt in explicitly per
|
|
* deployment. The flag check is the first thing main() does so
|
|
* accidental cron invocations no-op before any provider call.
|
|
*
|
|
* Usage (CLI):
|
|
* node scripts/odds-prefetch.js --sports=nba,mlb,wnba
|
|
* ODDS_PREWARM=1 node scripts/odds-prefetch.js --dry-run
|
|
*
|
|
* Returns a summary object with the per-sport outcome and the
|
|
* total cost in odds-api credits (read from the tracker delta).
|
|
* Designed to be wrapped by an internal HTTP endpoint or a cron;
|
|
* not auto-mounted to either.
|
|
*/
|
|
|
|
function parseArgs(argv) {
|
|
const flags = { sports: ['nba', 'wnba', 'mlb'], dryRun: false };
|
|
for (const a of argv.slice(2)) {
|
|
if (a === '--dry-run') flags.dryRun = true;
|
|
else if (a.startsWith('--sports=')) {
|
|
const list = a.slice('--sports='.length).split(',').map((s) => s.trim()).filter(Boolean);
|
|
if (list.length) flags.sports = list;
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
async function main(argv = process.argv) {
|
|
const enabled = process.env.ODDS_PREWARM === '1';
|
|
const args = parseArgs(argv);
|
|
const summary = {
|
|
enabled,
|
|
dryRun: args.dryRun,
|
|
sports: {},
|
|
creditsSpent: 0,
|
|
startedAt: new Date().toISOString(),
|
|
};
|
|
|
|
if (!enabled) {
|
|
console.warn('[odds-prefetch] disabled — set ODDS_PREWARM=1 to enable');
|
|
summary.skipped = 'not_enabled';
|
|
return summary;
|
|
}
|
|
|
|
// Lazy-require so the script can be loaded for inspection (or
|
|
// imported by tests) without dragging in axios + redis just to
|
|
// hit the env-flag short-circuit above.
|
|
const { getOdds } = require('../src/services/oddsService');
|
|
const quotaTracker = require('../src/services/quotaTracker');
|
|
|
|
const before = await quotaTracker.getQuotaStatus('odds-api');
|
|
console.log(`[odds-prefetch] starting — sports=${args.sports.join(',')} dry_run=${args.dryRun} quota_before=${before.used}/${before.limit}`);
|
|
|
|
for (const sport of args.sports) {
|
|
if (args.dryRun) {
|
|
summary.sports[sport] = { skipped: 'dry_run' };
|
|
console.log(`[odds-prefetch] ${sport}: dry-run`);
|
|
continue;
|
|
}
|
|
|
|
// Pre-flight: if quota is already blocked, bail out for the
|
|
// remaining sports too — no point spending the next sport's
|
|
// credits on a check that the tracker already failed.
|
|
const status = await quotaTracker.getQuotaStatus('odds-api');
|
|
if (!status.allowed) {
|
|
summary.sports[sport] = { skipped: 'quota_blocked', pct: status.pct };
|
|
console.warn(`[odds-prefetch] ${sport}: skipped — quota ${(status.pct * 100).toFixed(0)}%`);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
const result = await getOdds(sport);
|
|
const propCount = Array.isArray(result.props) ? result.props.length : 0;
|
|
summary.sports[sport] = {
|
|
source: result.source || 'live',
|
|
props: propCount,
|
|
quota_remaining: result.quota_remaining,
|
|
};
|
|
console.log(`[odds-prefetch] ${sport}: ${result.source} (${propCount} props, quota=${result.quota_remaining})`);
|
|
} catch (err) {
|
|
summary.sports[sport] = { error: err && err.message ? err.message : String(err) };
|
|
console.warn(`[odds-prefetch] ${sport}: failed — ${err && err.message}`);
|
|
}
|
|
}
|
|
|
|
const after = await quotaTracker.getQuotaStatus('odds-api');
|
|
summary.creditsSpent = Math.max(0, after.used - before.used);
|
|
summary.quota = { before: before.used, after: after.used, limit: after.limit, pct: after.pct };
|
|
console.log(`[odds-prefetch] done — credits_spent=${summary.creditsSpent} quota=${after.used}/${after.limit} (${(after.pct * 100).toFixed(0)}%)`);
|
|
return summary;
|
|
}
|
|
|
|
if (require.main === module) {
|
|
main().then((s) => {
|
|
process.exit(s.skipped === 'not_enabled' ? 2 : 0);
|
|
}).catch((err) => {
|
|
console.error('[odds-prefetch] fatal:', err);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
main,
|
|
__internals: { parseArgs },
|
|
};
|