Codex commited on
Commit
2744367
·
1 Parent(s): 85681b0

Refine home run odds command defaults

Browse files
Files changed (4) hide show
  1. src/commands.js +10 -3
  2. src/embeds.js +3 -3
  3. src/index.js +2 -2
  4. src/market-scanner.js +43 -13
src/commands.js CHANGED
@@ -263,11 +263,18 @@ export const commands = [
263
  .setDescription('Player name to look up, for example Aaron Judge.')
264
  .setRequired(true)
265
  )
266
- .addBooleanOption((option) =>
267
  option
268
- .setName('include_circa')
269
- .setDescription('Include the latest parsed Circa Home Runs price.')
270
  .setRequired(false)
 
 
 
 
 
 
 
271
  ),
272
  new SlashCommandBuilder()
273
  .setName('alerts')
 
263
  .setDescription('Player name to look up, for example Aaron Judge.')
264
  .setRequired(true)
265
  )
266
+ .addStringOption((option) =>
267
  option
268
+ .setName('book')
269
+ .setDescription('Optionally limit the result to one book. Defaults to all books including Circa.')
270
  .setRequired(false)
271
+ .addChoices(
272
+ { name: 'Circa', value: 'Circa' },
273
+ { name: 'FanDuel', value: 'FanDuel' },
274
+ { name: 'DraftKings', value: 'DraftKings' },
275
+ { name: 'BetMGM', value: 'BetMGM' },
276
+ { name: 'Caesars', value: 'Caesars' }
277
+ )
278
  ),
279
  new SlashCommandBuilder()
280
  .setName('alerts')
src/embeds.js CHANGED
@@ -306,7 +306,7 @@ export function buildCommandsEmbed() {
306
  { name: '/scanstatus', value: 'Show the market scanner status and latest run info. Admin only.' },
307
  { name: '/scanrun', value: 'Run the market scanner manually. Admin only.' },
308
  { name: '/scanreport', value: 'Post the morning scan reports immediately. Admin only.' },
309
- { name: '/hrodds', value: 'Show live Home Run odds for one player across FanDuel, DraftKings, BetMGM, Caesars, and optionally Circa. Admin only.' },
310
  { name: '/circatest', value: 'Run a Circa OCR diagnostic preview. Admin only.' },
311
  { name: 'Circa Market Commands', value: '`/circamarket`, `/circahr`, `/circahits`, `/circatb`, `/circarbis`, `/circaruns`, `/circasb`, `/circahrri`, `/circak` post the latest parsed Circa markets in the channel where you run them. Admin only.' },
312
  { name: '/alerts', value: 'Post the public analyst alert-role panel to the welcome channel. Only for jew_olympics.' },
@@ -314,11 +314,11 @@ export function buildCommandsEmbed() {
314
  );
315
  }
316
 
317
- export function buildPlayerOddsEmbed({ playerName, marketLabel, entries, includeCirca }) {
318
  const embed = new EmbedBuilder()
319
  .setColor(PALETTE.primary)
320
  .setTitle(`${marketLabel} Odds`)
321
- .setDescription(`Current book prices for **${playerName}**${includeCirca ? ', including Circa' : ''}.`);
322
 
323
  if (!entries.length) {
324
  return embed.setDescription(`No ${marketLabel.toLowerCase()} odds were found for **${playerName}**.`);
 
306
  { name: '/scanstatus', value: 'Show the market scanner status and latest run info. Admin only.' },
307
  { name: '/scanrun', value: 'Run the market scanner manually. Admin only.' },
308
  { name: '/scanreport', value: 'Post the morning scan reports immediately. Admin only.' },
309
+ { name: '/hrodds', value: 'Show live Home Run odds for one player across FanDuel, DraftKings, BetMGM, Caesars, and Circa, or filter to one book. Admin only.' },
310
  { name: '/circatest', value: 'Run a Circa OCR diagnostic preview. Admin only.' },
311
  { name: 'Circa Market Commands', value: '`/circamarket`, `/circahr`, `/circahits`, `/circatb`, `/circarbis`, `/circaruns`, `/circasb`, `/circahrri`, `/circak` post the latest parsed Circa markets in the channel where you run them. Admin only.' },
312
  { name: '/alerts', value: 'Post the public analyst alert-role panel to the welcome channel. Only for jew_olympics.' },
 
314
  );
315
  }
316
 
317
+ export function buildPlayerOddsEmbed({ playerName, marketLabel, entries, bookFilter }) {
318
  const embed = new EmbedBuilder()
319
  .setColor(PALETTE.primary)
320
  .setTitle(`${marketLabel} Odds`)
321
+ .setDescription(`Current ${bookFilter ? `**${bookFilter}**` : 'book'} prices for **${playerName}**.`);
322
 
323
  if (!entries.length) {
324
  return embed.setDescription(`No ${marketLabel.toLowerCase()} odds were found for **${playerName}**.`);
src/index.js CHANGED
@@ -1068,8 +1068,8 @@ async function handleHomeRunOdds(interaction, config) {
1068
 
1069
  try {
1070
  const player = interaction.options.getString('player', true);
1071
- const includeCirca = interaction.options.getBoolean('include_circa') ?? true;
1072
- const result = await scanner.getPlayerHomeRunOdds(player, { includeCirca });
1073
  await interaction.editReply({
1074
  embeds: [buildPlayerOddsEmbed(result)],
1075
  });
 
1068
 
1069
  try {
1070
  const player = interaction.options.getString('player', true);
1071
+ const book = interaction.options.getString('book') ?? null;
1072
+ const result = await scanner.getPlayerHomeRunOdds(player, { book });
1073
  await interaction.editReply({
1074
  embeds: [buildPlayerOddsEmbed(result)],
1075
  });
src/market-scanner.js CHANGED
@@ -2733,6 +2733,31 @@ function findBestPlayerEntryMatch(entries, playerQuery) {
2733
  });
2734
  }
2735
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2736
  function normalizeCircaMarketType(marketType) {
2737
  return CIRCA_MARKET_ALIASES[marketType] ?? marketType;
2738
  }
@@ -3383,19 +3408,17 @@ export class MarketScanner {
3383
  }
3384
 
3385
  async getPlayerHomeRunOdds(playerQuery, options = {}) {
3386
- const includeCirca = options.includeCirca !== false;
3387
  const oddsEntries = await fetchOddsApiEntries(this.config);
3388
  const homeRunEntries = oddsEntries.filter((entry) => entry.marketType === 'batter_home_runs' && entry.side === 'yes');
3389
  const matchedEntries = findBestPlayerEntryMatch(homeRunEntries, playerQuery);
3390
 
3391
- if (includeCirca) {
3392
- const snapshot = await this.getCircaSnapshotForAnalysis().catch(() => null);
3393
- if (snapshot?.entries?.length) {
3394
- const circaEntries = snapshot.entries.filter((entry) =>
3395
- normalizeCircaMarketType(entry.marketType) === 'home_runs' && entry.side === 'yes'
3396
- );
3397
- matchedEntries.push(...findBestPlayerEntryMatch(circaEntries, playerQuery));
3398
- }
3399
  }
3400
 
3401
  const dedupedEntries = dedupeBy(
@@ -3407,15 +3430,22 @@ export class MarketScanner {
3407
  (entry) => `${entry.book}|${entry.marketKey}|${entry.oddsInput}`,
3408
  );
3409
 
3410
- if (dedupedEntries.length === 0) {
3411
- throw new Error(`No Home Runs odds were found for ${playerQuery}.`);
 
 
 
 
 
 
3412
  }
3413
 
3414
- const canonicalName = dedupedEntries[0].playerName;
3415
  return {
3416
  playerName: canonicalName,
3417
  marketLabel: 'Home Runs',
3418
- entries: dedupedEntries,
 
3419
  };
3420
  }
3421
 
 
2733
  });
2734
  }
2735
 
2736
+ function normalizeBookFilter(book) {
2737
+ const normalized = normalizeWhitespace(book).toLowerCase();
2738
+ if (!normalized) {
2739
+ return null;
2740
+ }
2741
+
2742
+ if (normalized === 'circa') {
2743
+ return 'Circa';
2744
+ }
2745
+ if (normalized === 'fanduel') {
2746
+ return 'FanDuel';
2747
+ }
2748
+ if (normalized === 'draftkings') {
2749
+ return 'DraftKings';
2750
+ }
2751
+ if (normalized === 'betmgm') {
2752
+ return 'BetMGM';
2753
+ }
2754
+ if (normalized === 'caesars' || normalized === 'williamhill_us') {
2755
+ return 'Caesars';
2756
+ }
2757
+
2758
+ return book;
2759
+ }
2760
+
2761
  function normalizeCircaMarketType(marketType) {
2762
  return CIRCA_MARKET_ALIASES[marketType] ?? marketType;
2763
  }
 
3408
  }
3409
 
3410
  async getPlayerHomeRunOdds(playerQuery, options = {}) {
3411
+ const bookFilter = normalizeBookFilter(options.book ?? '');
3412
  const oddsEntries = await fetchOddsApiEntries(this.config);
3413
  const homeRunEntries = oddsEntries.filter((entry) => entry.marketType === 'batter_home_runs' && entry.side === 'yes');
3414
  const matchedEntries = findBestPlayerEntryMatch(homeRunEntries, playerQuery);
3415
 
3416
+ const snapshot = await this.getCircaSnapshotForAnalysis().catch(() => null);
3417
+ if (snapshot?.entries?.length) {
3418
+ const circaEntries = snapshot.entries.filter((entry) =>
3419
+ normalizeCircaMarketType(entry.marketType) === 'home_runs' && entry.side === 'yes'
3420
+ );
3421
+ matchedEntries.push(...findBestPlayerEntryMatch(circaEntries, playerQuery));
 
 
3422
  }
3423
 
3424
  const dedupedEntries = dedupeBy(
 
3430
  (entry) => `${entry.book}|${entry.marketKey}|${entry.oddsInput}`,
3431
  );
3432
 
3433
+ const filteredEntries = bookFilter
3434
+ ? dedupedEntries.filter((entry) => normalizeBookFilter(entry.book) === bookFilter)
3435
+ : dedupedEntries;
3436
+
3437
+ if (filteredEntries.length === 0) {
3438
+ throw new Error(bookFilter
3439
+ ? `No ${bookFilter} Home Runs odds were found for ${playerQuery}.`
3440
+ : `No Home Runs odds were found for ${playerQuery}.`);
3441
  }
3442
 
3443
+ const canonicalName = filteredEntries[0].playerName;
3444
  return {
3445
  playerName: canonicalName,
3446
  marketLabel: 'Home Runs',
3447
+ entries: filteredEntries,
3448
+ bookFilter,
3449
  };
3450
  }
3451