Session 16: Live hero prop, sport-specific markets fix, soccer weather, Sentry CSP (1429 tests)

This commit is contained in:
Kev
2026-06-11 18:15:25 -04:00
parent 167996d99a
commit 73b65a0248
11 changed files with 1010 additions and 101 deletions
+92 -3
View File
@@ -32,7 +32,89 @@ const SPORT_KEYS = {
const SOCCER_SPORT_KEYS = Object.freeze(
Object.keys(SPORT_KEYS).filter((k) => k.startsWith('soccer_'))
);
// Session 16 — per-sport market lists.
//
// The old `ALL_MARKETS = every key in MARKET_MAP` would send
// soccer markets (player_goals, player_shots_on_target, etc.) to
// basketball + baseball endpoints, triggering odds-api 422 errors.
// Production briefly worked around this with a runtime axios
// interceptor injected via `NODE_OPTIONS=--require /app/data/patch.js`;
// the proper fix is to scope the markets list to the sport before
// the request leaves the process.
//
// After this lands, the operator can drop the NODE_OPTIONS env var
// from Coolify and delete /app/data/patch.js.
const NBA_MARKETS = [
'player_points',
'player_rebounds',
'player_assists',
'player_threes',
'player_blocks',
'player_steals',
'player_turnovers',
'player_points_rebounds_assists',
];
const WNBA_MARKETS = [
'player_points',
'player_rebounds',
'player_assists',
'player_threes',
'player_blocks',
'player_steals',
'player_turnovers',
];
const MLB_MARKETS = [
'batter_home_runs',
'batter_hits',
'batter_total_bases',
'batter_rbis',
'batter_runs_scored',
'batter_stolen_bases',
'pitcher_strikeouts',
'pitcher_outs',
];
const SOCCER_MARKETS = [
'player_goals',
'player_shots_on_target',
'player_shots',
'player_tackles',
'player_cards',
'player_corners',
'player_saves',
'player_goals_conceded',
'player_passes',
'team_clean_sheet',
];
function buildMarketString(markets) {
return [...markets, 'spreads'].join(',');
}
// Indexed by the local sport key (the keys in SPORT_KEYS, not the
// odds-api keys). Soccer leagues all share the same market list.
const SPORT_MARKETS = Object.freeze({
nba: buildMarketString(NBA_MARKETS),
wnba: buildMarketString(WNBA_MARKETS),
mlb: buildMarketString(MLB_MARKETS),
ncaab: buildMarketString(NBA_MARKETS), // NCAAB markets mirror NBA
// Every soccer league code shares the same market set.
...Object.fromEntries(
Object.keys(SPORT_KEYS)
.filter((k) => k.startsWith('soccer_'))
.map((k) => [k, buildMarketString(SOCCER_MARKETS)]),
),
});
// Kept for backward-compat with any caller that still imports it,
// but the call site (`fetchEventOddsFromApi`) now uses the sport-
// specific lookup. Composed once on module load.
const ALL_MARKETS = Object.keys(MARKET_MAP).join(',') + ',spreads';
function getMarketsForSport(sport) {
if (!sport) return SPORT_MARKETS.nba; // safe default (basketball)
return SPORT_MARKETS[sport] || SPORT_MARKETS.nba;
}
const BOOKMAKERS = 'draftkings,fanduel,betmgm,caesars,fanatics,bet365,hardrockbet,pointsbet,betrivers';
function getCacheKey(sport) {
@@ -75,13 +157,17 @@ async function fetchEventsFromApi(sportKey, apiKey) {
return { data: response.data, headers: response.headers };
}
async function fetchEventOddsFromApi(sportKey, eventId, apiKey) {
// Session 16 — third arg is now a local sport key (nba, mlb,
// soccer_wc, ...) so we can scope the markets list. Backwards-
// compatible: if `sport` is omitted, falls back to the basketball
// market set, which is what every legacy caller assumed.
async function fetchEventOddsFromApi(sportKey, eventId, apiKey, sport) {
const url = `${ODDS_API_BASE}/${sportKey}/events/${eventId}/odds`;
const response = await axios.get(url, {
params: {
apiKey,
regions: 'us',
markets: ALL_MARKETS,
markets: getMarketsForSport(sport),
bookmakers: BOOKMAKERS,
oddsFormat: 'american',
},
@@ -112,7 +198,7 @@ async function fetchAllOdds(sport, apiKey) {
break;
}
const oddsResult = await fetchEventOddsFromApi(sportKey, event.id, apiKey);
const oddsResult = await fetchEventOddsFromApi(sportKey, event.id, apiKey, sport);
eventsWithOdds.push(oddsResult.data);
lastHeaders = oddsResult.headers;
}
@@ -230,6 +316,9 @@ module.exports = {
getCacheKey,
SPORT_KEYS,
SOCCER_SPORT_KEYS,
// Session 16 — per-sport market scoping.
SPORT_MARKETS,
getMarketsForSport,
getQuotaKey,
updateQuota,
getQuotaRemaining,