Codex commited on
Commit
9331364
·
1 Parent(s): e1eb4d1

Fix Caesars odds API home run lookups

Browse files
src/constants.js CHANGED
@@ -2,7 +2,7 @@ export const BOOK_CHOICES = [
2
  { name: 'FanDuel', value: 'FanDuel' },
3
  { name: 'DraftKings', value: 'DraftKings' },
4
  { name: 'BetMGM', value: 'BetMGM' },
5
- { name: 'Ceasars', value: 'Ceasars' },
6
  { name: 'Bet365', value: 'Bet365' },
7
  ];
8
 
 
2
  { name: 'FanDuel', value: 'FanDuel' },
3
  { name: 'DraftKings', value: 'DraftKings' },
4
  { name: 'BetMGM', value: 'BetMGM' },
5
+ { name: 'Caesars', value: 'Caesars' },
6
  { name: 'Bet365', value: 'Bet365' },
7
  ];
8
 
src/market-scanner.js CHANGED
@@ -521,6 +521,7 @@ export function normalizeOddsApiEntries(payload = []) {
521
  const entry = {
522
  source: 'odds_api',
523
  book: bookName,
 
524
  eventName: `${event.away_team} @ ${event.home_team}`,
525
  eventId: event.id,
526
  team: null,
@@ -2676,11 +2677,7 @@ export async function fetchOddsApiEntries(config) {
2676
  for (const event of events) {
2677
  const oddsUrl = new URL(`${baseUrl}/sports/${config.oddsApiSportKey}/events/${event.id}/odds`);
2678
  oddsUrl.searchParams.set('apiKey', config.oddsApiKey);
2679
- if (Array.isArray(config.oddsApiBookmakers) && config.oddsApiBookmakers.length > 0) {
2680
- oddsUrl.searchParams.set('bookmakers', config.oddsApiBookmakers.join(','));
2681
- } else {
2682
- oddsUrl.searchParams.set('regions', config.oddsApiRegions);
2683
- }
2684
  oddsUrl.searchParams.set('markets', config.oddsApiMarkets.join(','));
2685
  oddsUrl.searchParams.set('oddsFormat', 'american');
2686
  oddsUrl.searchParams.set('dateFormat', 'iso');
@@ -2702,7 +2699,21 @@ export async function fetchOddsApiEntries(config) {
2702
  payload.push(await response.json());
2703
  }
2704
 
2705
- return normalizeOddsApiEntries(payload);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2706
  }
2707
 
2708
  function formatZonedParts(date, timeZone) {
@@ -2777,7 +2788,7 @@ function normalizeBookFilter(book) {
2777
  if (normalized === 'betmgm') {
2778
  return 'BetMGM';
2779
  }
2780
- if (normalized === 'caesars' || normalized === 'williamhill_us') {
2781
  return 'Caesars';
2782
  }
2783
 
 
521
  const entry = {
522
  source: 'odds_api',
523
  book: bookName,
524
+ bookKey: String(bookmaker.key || '').trim(),
525
  eventName: `${event.away_team} @ ${event.home_team}`,
526
  eventId: event.id,
527
  team: null,
 
2677
  for (const event of events) {
2678
  const oddsUrl = new URL(`${baseUrl}/sports/${config.oddsApiSportKey}/events/${event.id}/odds`);
2679
  oddsUrl.searchParams.set('apiKey', config.oddsApiKey);
2680
+ oddsUrl.searchParams.set('regions', config.oddsApiRegions);
 
 
 
 
2681
  oddsUrl.searchParams.set('markets', config.oddsApiMarkets.join(','));
2682
  oddsUrl.searchParams.set('oddsFormat', 'american');
2683
  oddsUrl.searchParams.set('dateFormat', 'iso');
 
2699
  payload.push(await response.json());
2700
  }
2701
 
2702
+ const normalizedEntries = normalizeOddsApiEntries(payload);
2703
+ if (!Array.isArray(config.oddsApiBookmakers) || config.oddsApiBookmakers.length === 0) {
2704
+ return normalizedEntries;
2705
+ }
2706
+
2707
+ const allowedBooks = new Set(
2708
+ config.oddsApiBookmakers
2709
+ .map((book) => normalizeBookFilter(book))
2710
+ .filter(Boolean),
2711
+ );
2712
+
2713
+ return normalizedEntries.filter((entry) =>
2714
+ allowedBooks.has(normalizeBookFilter(entry.book))
2715
+ || allowedBooks.has(normalizeBookFilter(entry.bookKey))
2716
+ );
2717
  }
2718
 
2719
  function formatZonedParts(date, timeZone) {
 
2788
  if (normalized === 'betmgm') {
2789
  return 'BetMGM';
2790
  }
2791
+ if (normalized === 'caesars' || normalized === 'williamhill_us' || normalized === 'williamhill us') {
2792
  return 'Caesars';
2793
  }
2794
 
test/market-scanner.test.js CHANGED
@@ -373,6 +373,7 @@ test('fetches odds api entries from event-level endpoints', async () => {
373
  home_team: 'Red Sox',
374
  bookmakers: [
375
  {
 
376
  title: 'FanDuel',
377
  markets: [
378
  {
@@ -405,7 +406,8 @@ test('fetches odds api entries from event-level endpoints', async () => {
405
  assert.equal(entries[0].playerName, 'Aaron Judge');
406
  assert.ok(calls.some((url) => url.includes('/events?')));
407
  assert.ok(calls.some((url) => url.includes('/events/event-1/odds')));
408
- assert.ok(calls.some((url) => url.includes('bookmakers=fanduel%2Cdraftkings%2Cbetmgm%2Cwilliamhill_us')));
 
409
  } finally {
410
  global.fetch = originalFetch;
411
  }
@@ -439,6 +441,7 @@ test('returns partial odds results when later event requests hit 429', async ()
439
  home_team: 'Red Sox',
440
  bookmakers: [
441
  {
 
442
  title: 'FanDuel',
443
  markets: [
444
  {
@@ -482,6 +485,80 @@ test('returns partial odds results when later event requests hit 429', async ()
482
  }
483
  });
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  test('matches expected circa mlb filenames', () => {
486
  assert.equal(matchesCircaMlbFilename('MLB Props - 2026-4-3.pdf'), true);
487
  assert.equal(matchesCircaMlbFilename('MLB Props - 2026-04-03.pdf'), true);
 
373
  home_team: 'Red Sox',
374
  bookmakers: [
375
  {
376
+ key: 'fanduel',
377
  title: 'FanDuel',
378
  markets: [
379
  {
 
406
  assert.equal(entries[0].playerName, 'Aaron Judge');
407
  assert.ok(calls.some((url) => url.includes('/events?')));
408
  assert.ok(calls.some((url) => url.includes('/events/event-1/odds')));
409
+ assert.ok(calls.some((url) => url.includes('regions=us')));
410
+ assert.ok(calls.every((url) => !url.includes('bookmakers=')));
411
  } finally {
412
  global.fetch = originalFetch;
413
  }
 
441
  home_team: 'Red Sox',
442
  bookmakers: [
443
  {
444
+ key: 'fanduel',
445
  title: 'FanDuel',
446
  markets: [
447
  {
 
485
  }
486
  });
487
 
488
+ test('filters event-level odds to configured books after regional fetch', async () => {
489
+ const originalFetch = global.fetch;
490
+
491
+ global.fetch = async (input) => {
492
+ const asString = String(input);
493
+
494
+ if (asString.includes('/events?')) {
495
+ return {
496
+ ok: true,
497
+ status: 200,
498
+ json: async () => [{ id: 'event-1', away_team: 'Royals', home_team: 'Guardians' }],
499
+ };
500
+ }
501
+
502
+ if (asString.includes('/events/event-1/odds')) {
503
+ return {
504
+ ok: true,
505
+ status: 200,
506
+ json: async () => ({
507
+ id: 'event-1',
508
+ away_team: 'Royals',
509
+ home_team: 'Guardians',
510
+ bookmakers: [
511
+ {
512
+ key: 'williamhill_us',
513
+ title: 'Caesars',
514
+ markets: [
515
+ {
516
+ key: 'batter_home_runs',
517
+ outcomes: [
518
+ { name: 'Yes', description: 'Salvador Perez', price: 475 },
519
+ ],
520
+ },
521
+ ],
522
+ },
523
+ {
524
+ key: 'espnbet',
525
+ title: 'ESPN BET',
526
+ markets: [
527
+ {
528
+ key: 'batter_home_runs',
529
+ outcomes: [
530
+ { name: 'Yes', description: 'Salvador Perez', price: 500 },
531
+ ],
532
+ },
533
+ ],
534
+ },
535
+ ],
536
+ }),
537
+ };
538
+ }
539
+
540
+ throw new Error(`Unexpected fetch URL: ${asString}`);
541
+ };
542
+
543
+ try {
544
+ const entries = await fetchOddsApiEntries({
545
+ oddsApiBaseUrl: 'https://api.the-odds-api.com/v4',
546
+ oddsApiSportKey: 'baseball_mlb',
547
+ oddsApiKey: 'test-key',
548
+ oddsApiRegions: 'us',
549
+ oddsApiBookmakers: ['fanduel', 'draftkings', 'betmgm', 'williamhill_us'],
550
+ oddsApiMarkets: ['batter_home_runs'],
551
+ });
552
+
553
+ assert.equal(entries.length, 1);
554
+ assert.equal(entries[0].playerName, 'Salvador Perez');
555
+ assert.equal(entries[0].book, 'Caesars');
556
+ assert.equal(entries[0].oddsInput, '475');
557
+ } finally {
558
+ global.fetch = originalFetch;
559
+ }
560
+ });
561
+
562
  test('matches expected circa mlb filenames', () => {
563
  assert.equal(matchesCircaMlbFilename('MLB Props - 2026-4-3.pdf'), true);
564
  assert.equal(matchesCircaMlbFilename('MLB Props - 2026-04-03.pdf'), true);