proj-v1: book-implied from book_odds (grades carry odds, not fair_prob)

The live fingerprint showed proj_book_implied null on every real row: grades
carry book_odds/locked_odds (e.g. -264) but NOT a de-vigged fair_prob, so keying
the book comparison off fair_prob yielded null. The book ODDS are exactly "the
book's implied probability" the handicapper test needs. Now proj_book_implied +
the traded rung's book_implied derive from americanToImplied(book_odds),
expressed on the OVER basis (under props → 1 - implied) so it's directly
comparable to our P(≥rung). Vigged (a known offset the ledger measures both
sides of). proj-v1 suites 24/24.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
Kev
2026-07-23 04:14:04 -04:00
parent 316b79733e
commit e96b0dbb6d
2 changed files with 29 additions and 7 deletions
+12 -3
View File
@@ -76,7 +76,8 @@ describe('projectProp — full object, never abstains, plausible', () => {
const hitLog = (vals) => vals.map((h) => ({ isHome: true, opponent: 'Boston Red Sox', stat: { hits: h } }));
it('emits distribution + full ladder + point + factor breakdown', () => {
const grade = { stat_type: 'hits', line: 0.5, direction: 'over', season_avg: 0.9, fair_prob: 0.62, team: 'NYY' };
// book_odds -150 → implied 0.6 (the book's number the grade actually carries)
const grade = { stat_type: 'hits', line: 0.5, direction: 'over', season_avg: 0.9, book_odds: -150, team: 'NYY' };
const p = pc.projectProp({ grade, gameLog: hitLog([1, 0, 2, 1, 1, 0, 1, 2, 1, 0]) });
expect(p.proj_version).toBe('proj-v1');
expect(p.proj_distribution.family).toBe('negative_binomial');
@@ -85,11 +86,19 @@ describe('projectProp — full object, never abstains, plausible', () => {
expect(p.proj_factors.breakdown.map((f) => f.label)).toEqual(
expect.arrayContaining(['park_relative', 'weather', 'platoon', 'matchup']),
);
// traded rung (ceil 0.5 = 1) carries the book-implied for comparison
expect(p.proj_ladder.find((r) => r.rung === 1).book_implied).toBeCloseTo(0.62, 3);
// traded rung (ceil 0.5 = 1) carries the BOOK-implied (from odds) for comparison
expect(p.proj_ladder.find((r) => r.rung === 1).book_implied).toBeCloseTo(0.6, 3);
expect(p.proj_book_implied).toBeCloseTo(0.6, 3);
expect(p.proj_p_over_line).toBeGreaterThan(0);
});
it('book implied is expressed on the OVER basis for an under prop', () => {
// under at -150 (implied 0.6 the ball stays under) → over-basis implied 0.4
const grade = { stat_type: 'hits', line: 1.5, direction: 'under', season_avg: 0.9, book_odds: -150, team: 'NYY' };
const p = pc.projectProp({ grade, gameLog: hitLog([1, 0, 2, 1, 1, 0, 1, 2, 1, 0]) });
expect(p.proj_book_implied).toBeCloseTo(0.4, 3);
});
it('NEVER abstains — an empty game log still projects (wide, from the prior)', () => {
const grade = { stat_type: 'hits', line: 0.5, direction: 'over', season_avg: 0.9, team: 'NYY' };
const p = pc.projectProp({ grade, gameLog: [] });