Sessions 29-30: Content templates + PropLine 3-key adapter + MLB Stats API + ESPN summary (1694 tests)
This commit is contained in:
+56
-19
@@ -280,6 +280,27 @@ function parseQuota(headers) {
|
||||
return val != null ? parseInt(val, 10) : null;
|
||||
}
|
||||
|
||||
// Best-effort post-fetch processing shared by both providers (PropLine +
|
||||
// odds-api): line movement, scratch cascade, and rolling line snapshots.
|
||||
// Never throws — a failure here must not break the odds response.
|
||||
async function recordDownstream(sport, props) {
|
||||
let movements = [];
|
||||
let scratchedPlayers = [];
|
||||
try {
|
||||
const lineMovement = require('./lineMovementService');
|
||||
const cascade = require('./cascadeService');
|
||||
const moveResult = await lineMovement.processNewOdds(sport, props);
|
||||
movements = moveResult.movements || [];
|
||||
const cascadeResult = await cascade.detectScratches(sport, props);
|
||||
scratchedPlayers = cascadeResult.scratchedPlayers || [];
|
||||
const lineSnapshots = require('./lineSnapshotService');
|
||||
await lineSnapshots.recordSnapshots(sport, props);
|
||||
} catch (e) {
|
||||
console.warn('[VYNDR] Movement/cascade detection error:', e.message);
|
||||
}
|
||||
return { movements, scratchedPlayers };
|
||||
}
|
||||
|
||||
async function getOdds(sport) {
|
||||
const redis = getRedisClient();
|
||||
const apiKey = process.env.ODDS_API_KEY;
|
||||
@@ -294,12 +315,43 @@ async function getOdds(sport) {
|
||||
sport,
|
||||
updated_at: data.updated_at,
|
||||
source: 'cache',
|
||||
provider: data.provider || 'odds-api',
|
||||
quota_remaining: quota,
|
||||
props: data.props,
|
||||
spreads: data.spreads || [],
|
||||
};
|
||||
}
|
||||
|
||||
// Session 30 — PropLine is the PRIMARY props provider when configured:
|
||||
// 3 free keys, 3,000 req/day combined (vs odds-api's 500/month). Try it
|
||||
// first; on empty/error fall through to the conserved odds-api path
|
||||
// below. Gated on hasKeys() so environments without PropLine keys (incl.
|
||||
// the test suite) keep the exact prior behavior.
|
||||
const propline = require('./adapters/proplineAdapter');
|
||||
if (propline.hasKeys()) {
|
||||
try {
|
||||
const pl = await propline.getProps(sport);
|
||||
if (pl && Array.isArray(pl.props) && pl.props.length > 0) {
|
||||
const now = new Date().toISOString();
|
||||
const cacheData = { updated_at: now, props: pl.props, spreads: pl.spreads || [], provider: 'propline' };
|
||||
await redis.set(cacheKey, JSON.stringify(cacheData), 'EX', CACHE_TTL);
|
||||
const { movements, scratchedPlayers } = await recordDownstream(sport, pl.props);
|
||||
return {
|
||||
sport,
|
||||
updated_at: now,
|
||||
source: 'live',
|
||||
provider: 'propline',
|
||||
props: pl.props,
|
||||
spreads: pl.spreads || [],
|
||||
movements,
|
||||
scratchedPlayers,
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[oddsService] PropLine failed, falling back to odds-api:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Session 22 — pre-flight quota check now reads from the
|
||||
// Session 20 tracker (truth source: synced from upstream
|
||||
// response headers on every call). The legacy
|
||||
@@ -333,32 +385,17 @@ async function getOdds(sport) {
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
const cacheData = { updated_at: now, props, spreads };
|
||||
const cacheData = { updated_at: now, props, spreads, provider: 'odds-api' };
|
||||
await redis.set(cacheKey, JSON.stringify(cacheData), 'EX', CACHE_TTL);
|
||||
|
||||
// Line movement + cascade detection (best-effort, don't block response)
|
||||
let movements = [];
|
||||
let scratchedPlayers = [];
|
||||
try {
|
||||
const lineMovement = require('./lineMovementService');
|
||||
const cascade = require('./cascadeService');
|
||||
const moveResult = await lineMovement.processNewOdds(sport, props);
|
||||
movements = moveResult.movements || [];
|
||||
const cascadeResult = await cascade.detectScratches(sport, props);
|
||||
scratchedPlayers = cascadeResult.scratchedPlayers || [];
|
||||
// Session 28 — append a rolling line-history snapshot per prop so the
|
||||
// sparkline / biggest-movers views have data. Redis-only, free.
|
||||
const lineSnapshots = require('./lineSnapshotService');
|
||||
await lineSnapshots.recordSnapshots(sport, props);
|
||||
} catch (e) {
|
||||
// Non-fatal — log and continue
|
||||
console.warn('[VYNDR] Movement/cascade detection error:', e.message);
|
||||
}
|
||||
// Line movement + cascade + snapshots (best-effort; shared helper).
|
||||
const { movements, scratchedPlayers } = await recordDownstream(sport, props);
|
||||
|
||||
return {
|
||||
sport,
|
||||
updated_at: now,
|
||||
source: 'live',
|
||||
provider: 'odds-api',
|
||||
quota_remaining: quotaRemaining,
|
||||
props,
|
||||
spreads,
|
||||
|
||||
Reference in New Issue
Block a user