Codex commited on
Commit
63b4caa
·
1 Parent(s): 8128600

Fix odds API event-level scanner requests

Browse files
Files changed (2) hide show
  1. src/market-scanner.js +35 -10
  2. test/market-scanner.test.js +62 -0
src/market-scanner.js CHANGED
@@ -382,6 +382,14 @@ async function fetchArrayBuffer(url) {
382
  return response.arrayBuffer();
383
  }
384
 
 
 
 
 
 
 
 
 
385
  async function extractPdfText(buffer) {
386
  const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(buffer), useWorkerFetch: false, isEvalSupported: false });
387
  const pdf = await loadingTask.promise;
@@ -433,19 +441,36 @@ export async function fetchCircaEntries(config) {
433
  }
434
 
435
  export async function fetchOddsApiEntries(config) {
436
- const url = new URL(`${config.oddsApiBaseUrl.replace(/\/$/, '')}/sports/${config.oddsApiSportKey}/odds/`);
437
- url.searchParams.set('apiKey', config.oddsApiKey);
438
- url.searchParams.set('regions', config.oddsApiRegions);
439
- url.searchParams.set('markets', config.oddsApiMarkets.join(','));
440
- url.searchParams.set('oddsFormat', 'american');
441
- url.searchParams.set('dateFormat', 'iso');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
442
 
443
- const response = await fetch(url);
444
- if (!response.ok) {
445
- throw new Error(`Odds API request failed with ${response.status}`);
446
  }
447
 
448
- const payload = await response.json();
449
  return normalizeOddsApiEntries(payload);
450
  }
451
 
 
382
  return response.arrayBuffer();
383
  }
384
 
385
+ async function fetchJson(url) {
386
+ const response = await fetch(url);
387
+ if (!response.ok) {
388
+ throw new Error(`Request failed with ${response.status} for ${url}`);
389
+ }
390
+ return response.json();
391
+ }
392
+
393
  async function extractPdfText(buffer) {
394
  const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(buffer), useWorkerFetch: false, isEvalSupported: false });
395
  const pdf = await loadingTask.promise;
 
441
  }
442
 
443
  export async function fetchOddsApiEntries(config) {
444
+ const baseUrl = config.oddsApiBaseUrl.replace(/\/$/, '');
445
+ const eventsUrl = new URL(`${baseUrl}/sports/${config.oddsApiSportKey}/events`);
446
+ eventsUrl.searchParams.set('apiKey', config.oddsApiKey);
447
+ eventsUrl.searchParams.set('dateFormat', 'iso');
448
+
449
+ const events = await fetchJson(eventsUrl);
450
+ if (!Array.isArray(events) || events.length === 0) {
451
+ return [];
452
+ }
453
+
454
+ const payload = [];
455
+ for (const event of events) {
456
+ const oddsUrl = new URL(`${baseUrl}/sports/${config.oddsApiSportKey}/events/${event.id}/odds`);
457
+ oddsUrl.searchParams.set('apiKey', config.oddsApiKey);
458
+ oddsUrl.searchParams.set('regions', config.oddsApiRegions);
459
+ oddsUrl.searchParams.set('markets', config.oddsApiMarkets.join(','));
460
+ oddsUrl.searchParams.set('oddsFormat', 'american');
461
+ oddsUrl.searchParams.set('dateFormat', 'iso');
462
+
463
+ const response = await fetch(oddsUrl);
464
+ if (response.status === 404 || response.status === 422) {
465
+ continue;
466
+ }
467
+ if (!response.ok) {
468
+ throw new Error(`Odds API request failed with ${response.status}`);
469
+ }
470
 
471
+ payload.push(await response.json());
 
 
472
  }
473
 
 
474
  return normalizeOddsApiEntries(payload);
475
  }
476
 
test/market-scanner.test.js CHANGED
@@ -4,6 +4,7 @@ import {
4
  americanToImpliedProbability,
5
  analyzeMarkets,
6
  buildMarketKey,
 
7
  normalizeOddsApiEntries,
8
  parseCircaOcrText,
9
  } from '../src/market-scanner.js';
@@ -103,3 +104,64 @@ test('ranks discrepancy, width, and circa alerts', () => {
103
  assert.equal(analysis.circaAlerts.length, 1);
104
  assert.equal(analysis.circaAlerts[0].furthestBookName, 'FanDuel');
105
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  americanToImpliedProbability,
5
  analyzeMarkets,
6
  buildMarketKey,
7
+ fetchOddsApiEntries,
8
  normalizeOddsApiEntries,
9
  parseCircaOcrText,
10
  } from '../src/market-scanner.js';
 
104
  assert.equal(analysis.circaAlerts.length, 1);
105
  assert.equal(analysis.circaAlerts[0].furthestBookName, 'FanDuel');
106
  });
107
+
108
+ test('fetches odds api entries from event-level endpoints', async () => {
109
+ const originalFetch = global.fetch;
110
+ const calls = [];
111
+
112
+ global.fetch = async (url) => {
113
+ const asString = String(url);
114
+ calls.push(asString);
115
+
116
+ if (asString.includes('/events?')) {
117
+ return {
118
+ ok: true,
119
+ json: async () => [{ id: 'event-1' }],
120
+ };
121
+ }
122
+
123
+ if (asString.includes('/events/event-1/odds')) {
124
+ return {
125
+ ok: true,
126
+ status: 200,
127
+ json: async () => ({
128
+ id: 'event-1',
129
+ away_team: 'Yankees',
130
+ home_team: 'Red Sox',
131
+ bookmakers: [
132
+ {
133
+ title: 'FanDuel',
134
+ markets: [
135
+ {
136
+ key: 'batter_total_bases',
137
+ outcomes: [
138
+ { name: 'Aaron Judge', description: 'Over', point: 1.5, price: -110 },
139
+ ],
140
+ },
141
+ ],
142
+ },
143
+ ],
144
+ }),
145
+ };
146
+ }
147
+
148
+ throw new Error(`Unexpected fetch URL: ${asString}`);
149
+ };
150
+
151
+ try {
152
+ const entries = await fetchOddsApiEntries({
153
+ oddsApiBaseUrl: 'https://api.the-odds-api.com/v4',
154
+ oddsApiSportKey: 'baseball_mlb',
155
+ oddsApiKey: 'test-key',
156
+ oddsApiRegions: 'us',
157
+ oddsApiMarkets: ['batter_total_bases'],
158
+ });
159
+
160
+ assert.equal(entries.length, 1);
161
+ assert.equal(entries[0].playerName, 'Aaron Judge');
162
+ assert.ok(calls.some((url) => url.includes('/events?')));
163
+ assert.ok(calls.some((url) => url.includes('/events/event-1/odds')));
164
+ } finally {
165
+ global.fetch = originalFetch;
166
+ }
167
+ });