feat: Feature 1.1 — Odds API integration complete, 28 tests passing
This commit is contained in:
@@ -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 };
|
||||
@@ -0,0 +1,12 @@
|
||||
const Redis = require('ioredis');
|
||||
|
||||
let client = null;
|
||||
|
||||
function getRedisClient() {
|
||||
if (!client) {
|
||||
client = new Redis(process.env.REDIS_URL || 'redis://127.0.0.1:6379');
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
module.exports = { getRedisClient };
|
||||
@@ -0,0 +1,38 @@
|
||||
const TEAM_ABBREVIATIONS = {
|
||||
'Atlanta Hawks': 'ATL',
|
||||
'Boston Celtics': 'BOS',
|
||||
'Brooklyn Nets': 'BKN',
|
||||
'Charlotte Hornets': 'CHA',
|
||||
'Chicago Bulls': 'CHI',
|
||||
'Cleveland Cavaliers': 'CLE',
|
||||
'Dallas Mavericks': 'DAL',
|
||||
'Denver Nuggets': 'DEN',
|
||||
'Detroit Pistons': 'DET',
|
||||
'Golden State Warriors': 'GSW',
|
||||
'Houston Rockets': 'HOU',
|
||||
'Indiana Pacers': 'IND',
|
||||
'Los Angeles Clippers': 'LAC',
|
||||
'Los Angeles Lakers': 'LAL',
|
||||
'Memphis Grizzlies': 'MEM',
|
||||
'Miami Heat': 'MIA',
|
||||
'Milwaukee Bucks': 'MIL',
|
||||
'Minnesota Timberwolves': 'MIN',
|
||||
'New Orleans Pelicans': 'NOP',
|
||||
'New York Knicks': 'NYK',
|
||||
'Oklahoma City Thunder': 'OKC',
|
||||
'Orlando Magic': 'ORL',
|
||||
'Philadelphia 76ers': 'PHI',
|
||||
'Phoenix Suns': 'PHX',
|
||||
'Portland Trail Blazers': 'POR',
|
||||
'Sacramento Kings': 'SAC',
|
||||
'San Antonio Spurs': 'SAS',
|
||||
'Toronto Raptors': 'TOR',
|
||||
'Utah Jazz': 'UTA',
|
||||
'Washington Wizards': 'WAS',
|
||||
};
|
||||
|
||||
function getAbbreviation(fullName) {
|
||||
return TEAM_ABBREVIATIONS[fullName] || fullName;
|
||||
}
|
||||
|
||||
module.exports = { TEAM_ABBREVIATIONS, getAbbreviation };
|
||||
Reference in New Issue
Block a user