// A1 Session 3 — best available price detection. Honesty rules: a best // price only exists when ≥2 books post the SAME line for the side and // their prices differ. Absent beats wrong. const { detectBestBook, buildPlayerStripsFromProps } = require('../../web/src/lib/slateAdapter'); describe('detectBestBook', () => { const rows = [ { book: 'draftkings', line: 1.5, over_odds: -115, under_odds: -105 }, { book: 'fanduel', line: 1.5, over_odds: -105, under_odds: -115 }, { book: 'betmgm', line: 1.5, over_odds: -120, under_odds: 100 }, ]; it('picks the best over price at the shared line', () => { const out = detectBestBook(rows, 'over', 1.5); expect(out).toEqual({ book: 'fanduel', odds: -105 }); }); it('picks the best under price independently', () => { const out = detectBestBook(rows, 'under', 1.5); expect(out).toEqual({ book: 'betmgm', odds: 100 }); }); it('returns null for a single book (a lone price is not "best")', () => { expect(detectBestBook([rows[0]], 'over', 1.5)).toBeNull(); expect(detectBestBook([], 'over', 1.5)).toBeNull(); expect(detectBestBook(null, 'over', 1.5)).toBeNull(); }); it('returns null when all books post the identical price', () => { const flat = [ { book: 'draftkings', line: 1.5, over_odds: -110 }, { book: 'fanduel', line: 1.5, over_odds: -110 }, ]; expect(detectBestBook(flat, 'over', 1.5)).toBeNull(); }); it('returns null when books only post DIFFERENT lines (odds are not comparable)', () => { const split = [ { book: 'draftkings', line: 1.5, over_odds: -200 }, { book: 'fanduel', line: 2.5, over_odds: 120 }, ]; expect(detectBestBook(split, 'over', 1.5)).toBeNull(); expect(detectBestBook(split, 'over', 2.5)).toBeNull(); }); it('without a reference line, compares at the modal line', () => { const mixed = [ { book: 'draftkings', line: 1.5, over_odds: -115 }, { book: 'fanduel', line: 1.5, over_odds: -105 }, { book: 'betmgm', line: 2.5, over_odds: 200 }, ]; expect(detectBestBook(mixed, 'over')).toEqual({ book: 'fanduel', odds: -105 }); }); it('ignores rows with missing book / line / side odds', () => { const dirty = [ { book: 'draftkings', line: 1.5, over_odds: -115 }, { book: null, line: 1.5, over_odds: -100 }, { book: 'fanduel', line: 1.5, over_odds: null }, { book: 'betmgm', over_odds: -105 }, ]; expect(detectBestBook(dirty, 'over', 1.5)).toBeNull(); }); }); describe('buildPlayerStripsFromProps — bestBook attachment', () => { const books = [ { book: 'draftkings', line: 1.5, over_odds: -115, under_odds: -105 }, { book: 'fanduel', line: 1.5, over_odds: -105, under_odds: -115 }, ]; it('attaches bestBook + book to a graded prop (graded side)', () => { const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', book: 'draftkings', books }]; const gradeIndex = { 'aaron judge|total_bases': { player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', grade: 'A', gradedAt: { line: 1.5, odds: -115, timestamp: new Date().toISOString() }, }, }; const strips = buildPlayerStripsFromProps(props, gradeIndex, {}); expect(strips).toHaveLength(1); const p = strips[0].props[0]; expect(p.book).toBe('draftkings'); expect(p.bestBook).toEqual({ book: 'fanduel', odds: -105 }); }); it('attaches bestBook on awaiting props too (still real market data)', () => { const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'under', book: 'draftkings', books }]; const strips = buildPlayerStripsFromProps(props, {}, {}); const p = strips[0].props[0]; expect(p.awaiting).toBe(true); expect(p.bestBook).toEqual({ book: 'draftkings', odds: -105 }); }); it('leaves bestBook null when only one book posted the prop', () => { const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', book: 'draftkings', books: [books[0]] }]; const strips = buildPlayerStripsFromProps(props, {}, {}); expect(strips[0].props[0].bestBook).toBeNull(); }); it('leaves bestBook null when props carry no books array (legacy shape)', () => { const props = [{ player: 'Aaron Judge', stat_type: 'total_bases', line: 1.5, direction: 'over', book: 'draftkings' }]; const strips = buildPlayerStripsFromProps(props, {}, {}); expect(strips[0].props[0].bestBook).toBeNull(); }); });