Fix: the incumbent ruler respects the allow-list (correcting my own model)

My first delta run modelled the incumbent as first-row-wins over the RAW
feed and reported that an EXCLUDED book was "the market" on 69% of MLB
prop-lines, with prizepicks alone at 47%. That is WRONG and I caught it
before it went anywhere.

normalizeProps applies ALLOWED_BOOKS BEFORE gradeSlateService.dedupeProps
runs, so DFS books never reach the incumbent. The allow-list, for all the
coverage it costs, does keep DFS out of the ruler.

incumbentFairProb now takes the allow-list (defaulting to the live
ALLOWED_BOOKS) and reproduces the real chain. Two tests lock it, including
that a prop with no admitted book has NO incumbent -- it is never graded
at all, which is the real loss and is already measured as invisible_props.

Overstating the incumbent's badness would have been as dishonest as
understating it, and more persuasive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
Kev
2026-07-31 23:47:13 -04:00
parent a55dd2a6a0
commit c38db1ad65
3 changed files with 40 additions and 14 deletions
+17 -5
View File
@@ -124,15 +124,27 @@ function consensusFairProb(quotes, line, side, opts = {}) {
}
/**
* CHALLENGER DELTA. The incumbent is "first row wins" — literally the first
* quote in feed order, whatever book that is. This reproduces it faithfully so
* the comparison measures the real change and not an idealised one.
* CHALLENGER DELTA — the incumbent, reproduced FAITHFULLY.
*
* The live chain is: PropLine -> `normalizeProps` (which applies the
* ALLOWED_BOOKS filter FIRST) -> `gradeSlateService.dedupeProps` (first row per
* player+stat+line wins). So the incumbent is the first quote from an ADMITTED
* book — not the first quote in the raw feed.
*
* This distinction is load-bearing and easy to get wrong in the alarming
* direction: ignoring the allow-list makes it look as though DFS pick'em has
* been pricing the model (prizepicks alone is 47% of raw first-rows). It has
* not — the allow-list, for all the coverage it costs, does keep DFS out of the
* incumbent. Overstating the incumbent's badness would be as dishonest as
* understating it.
*/
function incumbentFairProb(quotes, line, side) {
function incumbentFairProb(quotes, line, side, allowedBooks) {
const want = String(side || 'over').toLowerCase() === 'under' ? 'under' : 'over';
const target = num(line);
const allow = allowedBooks || require('../utils/oddsNormalizer').ALLOWED_BOOKS;
for (const q of quotes || []) {
if (!q || !q.book) continue;
if (allow && !allow.has(String(q.book).toLowerCase())) continue;
if (target != null && num(q.line) !== target) continue;
const over = num(q.over_odds);
const under = num(q.under_odds);
@@ -150,7 +162,7 @@ function incumbentFairProb(quotes, line, side) {
/** Both rulers plus the signed delta, for a single prop. */
function compareRulers(quotes, line, side, opts = {}) {
const incumbent = incumbentFairProb(quotes, line, side);
const incumbent = incumbentFairProb(quotes, line, side, opts.allowedBooks);
const consensus = consensusFairProb(quotes, line, side, opts);
const delta = (incumbent.fair_prob != null && consensus.fair_prob != null)
? round4(consensus.fair_prob - incumbent.fair_prob)