Sessions 5-7a: 955 tests, deployment ready

This commit is contained in:
Kev
2026-06-08 18:35:13 -04:00
parent 06b82624a2
commit 1fa04dc776
371 changed files with 49366 additions and 955 deletions
+129
View File
@@ -0,0 +1,129 @@
/**
* UnifiedOddsProvider — orchestrator.
*
* Fan-out to every adapter via Promise.allSettled, normalize, attach
* cross-source signals, run processing engines, return a structured
* `sources` array describing what contributed.
*
* Never throws. Adapter failures are surfaced per-source so the API caller
* can see exactly which upstreams contributed to a given refresh.
*/
const espn = require('./adapters/ESPNAdapter');
const pinnacle = require('./adapters/PinnacleAdapter');
const draftkings = require('./adapters/DraftKingsAdapter');
const fanduel = require('./adapters/FanDuelAdapter');
const betmgm = require('./adapters/BetMGMAdapter');
const caesars = require('./adapters/CaesarsAdapter');
const prizepicks = require('./adapters/PrizePicksAdapter');
const covers = require('./adapters/CoversAdapter');
const rotowire = require('./adapters/RotowireAdapter');
const lineShopping = require('./processing/LineShoppingEngine');
const middles = require('./processing/MiddlesDetector');
const ev = require('./processing/EVCalculator');
const { shouldCollect, isActiveSport } = require('../config/sports');
const rateLimiter = require('./rateLimiter');
const breaker = require('./circuitBreaker');
const ADAPTERS = [espn, pinnacle, draftkings, fanduel, betmgm, caesars, prizepicks, covers, rotowire];
function settleToResult(name, settled) {
if (settled.status === 'fulfilled') {
const value = settled.value;
const count = Array.isArray(value) ? value.length : (value?.projections?.length ?? 0);
return { source: name, ok: true, count };
}
const err = settled.reason;
return {
source: name,
ok: false,
error: err?.code === 'NOT_IMPLEMENTED' ? 'not_implemented'
: err?.code === 'BREAKER_OPEN' ? 'breaker_open'
: err?.code === 'RATE_LIMIT_TIMEOUT' ? 'rate_limited'
: (err?.message || 'unknown'),
};
}
async function fullRefresh(sport, { gradedProps = [] } = {}) {
if (!shouldCollect(sport)) {
return {
sport,
collected: false,
reason: 'sport not in collection set',
sources: [],
data: { games: [], props: [], shopped: [], middles: [] },
refreshed_at: new Date().toISOString(),
};
}
const results = await Promise.allSettled([
espn.getGames(sport),
pinnacle.getGames(sport),
draftkings.getPlayerProps(sport),
fanduel.getPlayerProps(sport),
betmgm.getPlayerProps(sport),
caesars.getPlayerProps(sport),
prizepicks.getPlayerProps(sport),
covers.getConsensus?.(sport) ?? Promise.resolve([]),
rotowire.getProjections(sport),
]);
const sources = [
settleToResult('espn', results[0]),
settleToResult('pinnacle', results[1]),
settleToResult('draftkings', results[2]),
settleToResult('fanduel', results[3]),
settleToResult('betmgm', results[4]),
settleToResult('caesars', results[5]),
settleToResult('prizepicks', results[6]),
settleToResult('covers', results[7]),
settleToResult('rotowire', results[8]),
];
const games = results[0].status === 'fulfilled' ? results[0].value : [];
// Merge every adapter's player-prop payload (those that returned arrays).
const props = [];
for (const idx of [2, 3, 4, 5, 6]) {
if (results[idx].status === 'fulfilled' && Array.isArray(results[idx].value)) {
props.push(...results[idx].value);
}
}
const shopped = lineShopping.process(props);
const middlesFound = middles.detect(shopped);
// EV labels for any already-graded props the caller fed in.
const evRows = (gradedProps || []).map((g) => ({
key: g.key,
grade: g.grade,
odds: g.odds,
ev: ev.calculate({ grade: g.grade, odds: g.odds }),
}));
return {
sport,
active: isActiveSport(sport),
sources,
data: {
games,
props,
shopped,
middles: middlesFound,
ev: evRows,
},
refreshed_at: new Date().toISOString(),
};
}
function status() {
return {
rate_limiters: rateLimiter.snapshot(),
breakers: breaker.snapshot(),
adapters: ADAPTERS.map((a) => a.name),
};
}
module.exports = { fullRefresh, status };