Codex commited on
Commit ·
cf14c48
1
Parent(s): a16ce98
Refine Circa packed-row odds extraction
Browse files- src/market-scanner.js +81 -1
- test/market-scanner.test.js +11 -0
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 =
|
| 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 |
{
|