Codex commited on
Commit
cf14c48
·
1 Parent(s): a16ce98

Refine Circa packed-row odds extraction

Browse files
src/market-scanner.js CHANGED
@@ -508,6 +508,86 @@ function extractBinaryYesNoEntriesV2(line, section) {
508
  return entries;
509
  }
510
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  function extractOverUnderEntries(line, section) {
512
  const entries = [];
513
  const normalizedLine = normalizeWhitespace(line).toUpperCase().replace(/[~]/g, '-');
@@ -633,7 +713,7 @@ export function parseCircaOcrText(text) {
633
  }
634
 
635
  if (currentSection?.mode === 'binary_yes_no') {
636
- const binaryEntries = extractBinaryYesNoEntriesV2(line, currentSection);
637
  if (binaryEntries.length > 0) {
638
  entries.push(...binaryEntries);
639
  continue;
 
508
  return entries;
509
  }
510
 
511
+ function extractOddsTokensFromSegment(segment) {
512
+ const cleaned = normalizeWhitespace(segment)
513
+ .toUpperCase()
514
+ .replace(/\//g, ' ')
515
+ .replace(/\s+/g, ' ');
516
+ const tokens = [...cleaned.matchAll(/(?:\d{4,6}|[0-9BOSI]{4,6})\s+([+\-~]?[0-9BOSI]{2,4})/g)]
517
+ .map((match) => match[1]);
518
+
519
+ if (tokens.length > 0) {
520
+ return tokens;
521
+ }
522
+
523
+ return [...cleaned.matchAll(/([+\-~][0-9BOSI]{2,4})/g)].map((match) => match[1]);
524
+ }
525
+
526
+ function extractBinaryYesNoEntriesV3(line, section) {
527
+ const entries = [];
528
+ const normalizedLine = normalizeWhitespace(line)
529
+ .toUpperCase()
530
+ .replace(/\//g, ' ')
531
+ .replace(/\s+/g, ' ');
532
+ const playerPattern = /([A-Z][A-Z.'\- ]{2,40}\([A-Z]{2,3}\))/g;
533
+ const playerMatches = [...normalizedLine.matchAll(playerPattern)];
534
+
535
+ for (let index = 0; index < playerMatches.length; index += 1) {
536
+ const current = playerMatches[index];
537
+ const next = playerMatches[index + 1];
538
+ const segmentStart = current.index ?? 0;
539
+ const segmentEnd = next?.index ?? normalizedLine.length;
540
+ const segment = normalizedLine.slice(segmentStart, segmentEnd);
541
+ const { playerName, team } = extractPlayerAndTeam(current[1]);
542
+ if (!playerName || playerName.length < 3) {
543
+ continue;
544
+ }
545
+
546
+ const oddsTokens = extractOddsTokensFromSegment(segment);
547
+ if (oddsTokens.length < 2) {
548
+ continue;
549
+ }
550
+
551
+ const yesOdds = normalizeOcrOddsTokenForSide(oddsTokens[0], 'yes');
552
+ const noOdds = normalizeOcrOddsTokenForSide(oddsTokens[1], 'no');
553
+
554
+ for (const sideEntry of [
555
+ { side: section.side ?? 'yes', oddsInput: yesOdds },
556
+ { side: 'no', oddsInput: noOdds },
557
+ ]) {
558
+ if (!sideEntry.oddsInput || !isReasonableCircaOdds(sideEntry.oddsInput, section)) {
559
+ continue;
560
+ }
561
+
562
+ const impliedProbability = americanToImpliedProbability(sideEntry.oddsInput);
563
+ if (impliedProbability === null) {
564
+ continue;
565
+ }
566
+
567
+ const entry = {
568
+ source: 'circa',
569
+ book: 'Circa',
570
+ eventName: 'Circa MLB',
571
+ eventId: null,
572
+ team,
573
+ playerName,
574
+ playerKey: normalizePlayerName(playerName),
575
+ marketType: section.type,
576
+ marketLabel: section.label,
577
+ side: sideEntry.side,
578
+ lineValue: section.lineValue,
579
+ oddsInput: sideEntry.oddsInput,
580
+ impliedProbability,
581
+ rawLabel: segment,
582
+ };
583
+ entry.marketKey = buildMarketKey(entry);
584
+ entries.push(entry);
585
+ }
586
+ }
587
+
588
+ return entries;
589
+ }
590
+
591
  function extractOverUnderEntries(line, section) {
592
  const entries = [];
593
  const normalizedLine = normalizeWhitespace(line).toUpperCase().replace(/[~]/g, '-');
 
713
  }
714
 
715
  if (currentSection?.mode === 'binary_yes_no') {
716
+ const binaryEntries = extractBinaryYesNoEntriesV3(line, currentSection);
717
  if (binaryEntries.length > 0) {
718
  entries.push(...binaryEntries);
719
  continue;
test/market-scanner.test.js CHANGED
@@ -82,6 +82,17 @@ test('drops unreasonable binary odds from circa OCR rows', () => {
82
  assert.equal(entries[0].oddsInput, '-770');
83
  });
84
 
 
 
 
 
 
 
 
 
 
 
 
85
  test('ranks discrepancy, width, and circa alerts', () => {
86
  const entries = [
87
  {
 
82
  assert.equal(entries[0].oddsInput, '-770');
83
  });
84
 
85
+ test('recovers multiple packed home run rows from one OCR line', () => {
86
+ const entries = parseCircaOcrText([
87
+ 'WILL [PLAYER] HIT A HOME RUN?',
88
+ 'SHOHEI OHTANI (LAD) 6381 +235 63682 -280 ADDISON BARGER (TOR) 60765 +470 60786 -620 GUNNAR HENDERSON (BAL) 63683 +525 63685 -710',
89
+ ].join('\n'));
90
+
91
+ assert.ok(entries.some((entry) => entry.playerName === 'SHOHEI OHTANI' && entry.oddsInput === '+235'));
92
+ assert.ok(entries.some((entry) => entry.playerName === 'ADDISON BARGER' && entry.oddsInput === '+470'));
93
+ assert.ok(entries.some((entry) => entry.playerName === 'GUNNAR HENDERSON' && entry.oddsInput === '+525'));
94
+ });
95
+
96
  test('ranks discrepancy, width, and circa alerts', () => {
97
  const entries = [
98
  {