feat: Feature 1.1 — Odds API integration complete, 28 tests passing

This commit is contained in:
Kev
2026-03-21 08:31:15 -04:00
parent f70db389e2
commit 00409fd6cd
16 changed files with 6896 additions and 6 deletions
+78
View File
@@ -0,0 +1,78 @@
const { getAbbreviation } = require('./teamMap');
const ALLOWED_BOOKS = new Set(['draftkings', 'fanduel', 'betmgm']);
const MARKET_MAP = {
player_points: 'points',
player_rebounds: 'rebounds',
player_assists: 'assists',
player_threes: 'threes',
player_blocks: 'blocks',
player_steals: 'steals',
player_points_rebounds_assists: 'pra',
player_turnovers: 'turnovers',
};
function normalizeProps(eventsWithOdds) {
const props = [];
for (const event of eventsWithOdds) {
const homeTeam = getAbbreviation(event.home_team);
const awayTeam = getAbbreviation(event.away_team);
const gameTime = event.commence_time;
if (!Array.isArray(event.bookmakers)) continue;
for (const bookmaker of event.bookmakers) {
if (!ALLOWED_BOOKS.has(bookmaker.key)) continue;
if (!Array.isArray(bookmaker.markets)) continue;
for (const market of bookmaker.markets) {
const statType = MARKET_MAP[market.key];
if (!statType) continue;
const fetchedAt = market.last_update;
const outcomes = market.outcomes || [];
// Group outcomes by player+point to pair Over/Under
const grouped = {};
for (const outcome of outcomes) {
if (!outcome.description || outcome.point == null) continue;
const key = `${outcome.description}::${outcome.point}`;
if (!grouped[key]) {
grouped[key] = { player: outcome.description, point: outcome.point };
}
if (outcome.name === 'Over') {
grouped[key].over_odds = outcome.price;
} else if (outcome.name === 'Under') {
grouped[key].under_odds = outcome.price;
}
}
for (const entry of Object.values(grouped)) {
// Skip if we don't have both sides
if (entry.over_odds == null && entry.under_odds == null) continue;
// Player-to-team resolution deferred to Feature 1.2 (roster data)
props.push({
player: entry.player,
home_team: homeTeam,
away_team: awayTeam,
game_time: gameTime,
stat_type: statType,
book: bookmaker.key,
line: entry.point,
over_odds: entry.over_odds ?? null,
under_odds: entry.under_odds ?? null,
fetched_at: fetchedAt,
});
}
}
}
}
return props;
}
module.exports = { normalizeProps, MARKET_MAP, ALLOWED_BOOKS };