86d123945c
MEASURED BASIS (n=200 settled MLB rows): corr(p_win, outcome) = +0.26; corr(edge, outcome) = -0.010 incumbent ruler / -0.022 consensus ruler. Subtracting the market destroys the signal under BOTH rulers, so a quantity that does not predict must not rank, gate or decide. CHALLENGER-FIRST -- live ordering is byte-identical. rankGrades (the incumbent, grade-first with edge as its 4th key) is untouched and tested as untouched. NEW: rankByForecast -- takeable-gated p_win -> grade -> confidence -> stable order, with NO edge term anywhere. p_win LEADS and the letter follows, deliberately: the letter measured r ~ 0.005 and is inverted (B 52.4% < C 56.9%) while p_win measures +0.26, so leading with the letter would sort by the weaker signal and use the stronger one only to break ties. Recorded in the code: isotonic calibration is a MONOTONE transform, so ranking on raw vs calibrated p_win gives the SAME ORDER. Calibration matters when p_win is displayed or thresholded; it cannot change a ranking. Nothing here needs the calibrated value. rankingDelta + GET /api/internal/ranking-delta measure how far the board would move before any flip. The endpoint reports p_win coverage alongside the delta -- if p_win is absent the challenger degrades to grade order and the delta UNDERSTATES, which is worth saying rather than reporting a clean zero. forecast_rank is stamped on snapshot grades BEFORE stripModelPrice, so every tier gets the correct order without the paid values (the topGradedService precedent -- an ordinal can travel where the magnitude cannot). Additive only: nothing sorts by it yet. RETIRED AS DECISIONS (not rankings, so done now): - altLineScanner.compareToBookImplied no longer returns value_detected: edge > 0. Edge is still COMPUTED and returned -- losing the record would be worse than mis-using it -- but the verdict is an honest null with value_basis: 'retired:edge_does_not_predict'. - scanAltLines no longer filters to edge>0 or calls the survivor "optimal". The whole ladder is returned ranked and labelled 'price_gap_diagnostic_unvalidated'. The module has ZERO callers (verified) -- unwired like mlbGrader.js, left in place and made honest. An honest asymmetry recorded there: ranking props AGAINST EACH OTHER must not use edge, but choosing between RUNGS OF THE SAME PROP is inherently price-relative -- ranking rungs by model probability alone would always pick the lowest line, since P(over 0.5) > P(over 2.5) by construction. So the gap stays the rung key, explicitly labelled unvalidated. Two superseded tests updated to stronger properties. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
99 lines
3.9 KiB
JavaScript
99 lines
3.9 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Ranking instrument — challenger vs incumbent (2026-08-01).
|
|
*
|
|
* These lock the reason the challenger exists: measured on n=200 settled MLB
|
|
* rows, corr(p_win, outcome) = +0.26 while corr(edge, outcome) = -0.010 under
|
|
* the incumbent ruler and -0.022 under the consensus ruler. A quantity that
|
|
* does not predict must not rank, gate or decide.
|
|
*/
|
|
|
|
const {
|
|
rankGrades, rankByForecast, rankingDelta, gradeKey, takeablePWin,
|
|
} = require('../../src/utils/gradeRanking');
|
|
|
|
const g = (player, grade, p_win, odds = -110, extra = {}) => ({
|
|
player, stat_type: 'hits', line: 1.5, direction: 'over',
|
|
grade, p_win, book_odds: odds, ...extra,
|
|
});
|
|
|
|
describe('rankByForecast — the challenger', () => {
|
|
it('contains NO edge term: edge cannot move the order at all', () => {
|
|
const a = [g('A', 'B', 0.62, -110, { edge: -99 }), g('B', 'B', 0.55, -110, { edge: +99 })];
|
|
const b = [g('A', 'B', 0.62, -110, { edge: +99 }), g('B', 'B', 0.55, -110, { edge: -99 })];
|
|
expect(rankByForecast(a).map((x) => x.player)).toEqual(['A', 'B']);
|
|
expect(rankByForecast(b).map((x) => x.player)).toEqual(['A', 'B']);
|
|
});
|
|
|
|
it('leads with p_win, not the grade letter', () => {
|
|
// The letter measured r ~ 0.005 and is INVERTED; p_win measures +0.26.
|
|
// A high-p_win C must outrank a low-p_win A.
|
|
const out = rankByForecast([g('lowPwinA', 'A', 0.51), g('highPwinC', 'C', 0.74)]);
|
|
expect(out[0].player).toBe('highPwinC');
|
|
});
|
|
|
|
it('keeps the takeable gate — raw p_win would crown chalk', () => {
|
|
const chalk = g('chalk', 'A', 0.93, -300); // untakeable price
|
|
const real = g('real', 'B', 0.61, -115);
|
|
expect(takeablePWin(chalk)).toBeNull();
|
|
expect(rankByForecast([chalk, real])[0].player).toBe('real');
|
|
});
|
|
|
|
it('sorts a missing p_win LAST, never first (Number(null) === 0 guard)', () => {
|
|
const out = rankByForecast([g('none', 'A', null), g('has', 'C', 0.58)]);
|
|
expect(out.map((x) => x.player)).toEqual(['has', 'none']);
|
|
});
|
|
|
|
it('is stable for genuinely tied rows', () => {
|
|
const rows = [g('first', 'B', 0.6), g('second', 'B', 0.6)];
|
|
expect(rankByForecast(rows).map((x) => x.player)).toEqual(['first', 'second']);
|
|
});
|
|
|
|
it('drops ungraded rows, like the incumbent', () => {
|
|
expect(rankByForecast([g('x', null, 0.9), g('y', 'B', 0.5)]).map((r) => r.player)).toEqual(['y']);
|
|
});
|
|
});
|
|
|
|
describe('rankGrades — the incumbent is UNTOUCHED (live ordering byte-identical)', () => {
|
|
it('still leads with the grade letter and still consults edge', () => {
|
|
const out = rankGrades([g('lowPwinA', 'A', 0.51), g('highPwinC', 'C', 0.74)]);
|
|
expect(out[0].player).toBe('lowPwinA'); // grade-first, unchanged
|
|
});
|
|
|
|
it('edge still breaks a true tie in the incumbent', () => {
|
|
const out = rankGrades([
|
|
g('lowEdge', 'B', 0.6, -110, { edge: 1 }),
|
|
g('highEdge', 'B', 0.6, -110, { edge: 9 }),
|
|
]);
|
|
expect(out[0].player).toBe('highEdge');
|
|
});
|
|
});
|
|
|
|
describe('rankingDelta — the challenger-first measurement', () => {
|
|
it('reports how far the board moves and whether the top read changes', () => {
|
|
const rows = [g('A', 'A', 0.52), g('B', 'C', 0.77), g('C', 'B', 0.64)];
|
|
const d = rankingDelta(rows, 3);
|
|
expect(d.n).toBe(3);
|
|
expect(d.incumbent_top).toBe(gradeKey(rows[0])); // A-grade leads incumbent
|
|
expect(d.challenger_top).toBe(gradeKey(rows[1])); // highest p_win leads challenger
|
|
expect(d.top_changed).toBe(true);
|
|
expect(d.moved).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('reports zero movement when both instruments agree', () => {
|
|
const rows = [g('A', 'A', 0.80), g('B', 'B', 0.60), g('C', 'C', 0.40)];
|
|
const d = rankingDelta(rows, 3);
|
|
expect(d.moved).toBe(0);
|
|
expect(d.top_changed).toBe(false);
|
|
expect(d.top_n_overlap_pct).toBe(100);
|
|
});
|
|
|
|
it('changes nothing about the inputs (pure)', () => {
|
|
const rows = [g('A', 'A', 0.52), g('B', 'C', 0.77)];
|
|
const snapshot = JSON.stringify(rows);
|
|
rankingDelta(rows);
|
|
expect(JSON.stringify(rows)).toBe(snapshot);
|
|
});
|
|
});
|