Files
vyndr/tests/unit/rankingInstrument.test.js
T
builtbykev 6c97f59546 WNBA truth correction + THE p_win FLIP (live, rollback armed)
PART A -- WNBA TRUTH CORRECTION (no behaviour change).
WNBA does not "abstain" and is not "anti-predictive". The -0.12 that
produced those words was NBA-template machinery run on WNBA data -- WNBA
has never had its own archetypes, variables, conditions or calibration,
which is precisely the "sport stubbed in on another sport's template"
CLAUDE.md forbids. That is an UNBUILT MODEL'S EXPECTED FAILURE, not a
verdict on the sport; reading it as a verdict would quietly retire a sport
we never actually attempted. Its own build is QUEUED, after MLB.

The guard CODE is unchanged -- FORECAST_RANKED_SPORTS = {'mlb'} and the
inheritance test are correct live safety either way. Only the meaning is
corrected, and generalised into the doctrine-as-a-gate: a sport ranks on
p_win ONLY once its OWN model is built and shown to predict (calibration
AND resolution on its own holdout). Others are held out as NOT-BUILT,
never as failed. Re-labelled across gradeRanking, snapshot route, tests,
MASTER-PLAN and the challenger report.

PART B -- THE FLIP, gated on a full-slate re-run.

The re-run found something better than a bigger sample. An induced
snapshot graded 7 props: gradeAndCacheSlate runs with DEFAULT_LIMIT = 25
and ~72% of those refuse for insufficient_data, while 546 props are
gradeable. So 8 props IS the board, structurally -- not a small sample of
it. Logged as its own finding; the cap is a separate order.

For a statistically meaningful delta I used 11 real historical boards
(n=328, board sizes 14-57): 79.9% of rows move, mean 5.16 places per
board, TOP READ CHANGES ON 9 OF 11 BOARDS. The re-ordering holds at real
board size. Query committed.

FLIPPED:
- rankGrades drops its edge key (safe for every sport: removes a
  non-predictive tiebreak without putting p_win in front).
- selectTopGrades leads on forecast_rank, edge key removed.
- flattenToEdgeBoard sorts on forecastRank, not edge -- this board had
  edge as its PRIMARY key, so the whole mobile board was ordered by a
  quantity measured not to predict.
- forecast_rank threaded onto strip props.

Sports whose model is not built supply no forecast_rank, so their boards
fall through to the unchanged grade chain -- the fallback is the guard.

ROLLBACK ARMED: boards sort by forecast_rank WHEN PRESENT, so
FORECAST_RANK=0 reverts every surface on the next response -- no deploy,
no client release.

Edge is still computed, stored, carried and displayed as a labelled
diagnostic. Retired from ranking, not deleted.

Eight superseded tests updated to strictly stronger INVERSE properties --
they now fail if edge is ever re-introduced as a ranking key, which the
originals could not detect.

Gates: 4,045 tests / 323 suites green; next build exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
2026-08-01 01:55:43 -04:00

122 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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, ranksOnForecast, FORECAST_RANKED_SPORTS,
} = 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 — still grade-first (p_win leading is rankByForecast only)', () => {
it('leads with the grade letter, NOT p_win', () => {
const out = rankGrades([g('lowPwinA', 'A', 0.51), g('highPwinC', 'C', 0.74)]);
expect(out[0].player).toBe('lowPwinA'); // grade-first, unchanged
});
// SUPERSEDED 2026-08-01: the edge KEY was removed from rankGrades entirely.
// Removing it is safe for every sport — it takes a non-predictive signal out
// without putting p_win in front (that is `rankByForecast`, gated to sports
// whose own model has passed).
it('edge can no longer break a tie — the key is gone for EVERY sport', () => {
const out = rankGrades([
g('first', 'B', 0.6, -110, { edge: 1 }),
g('second', 'B', 0.6, -110, { edge: 9 }),
]);
expect(out[0].player).toBe('first'); // stable input order, edge ignored
});
});
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);
});
});
describe('per-sport doctrine — a sport ranks only once ITS OWN model is built', () => {
// WNBA is held out as NOT-BUILT, not as failed: its -0.12 came from
// NBA-template machinery run on WNBA data, which is an unbuilt model's
// expected failure rather than a verdict on the sport. Same live behaviour,
// corrected meaning.
it('MLB may (its own model passed); WNBA may not (its model is not built yet)', () => {
expect(ranksOnForecast('mlb')).toBe(true);
expect(ranksOnForecast('MLB')).toBe(true);
expect(ranksOnForecast('wnba')).toBe(false);
});
it('no sport inherits MLBs result — every unbuilt sport is held out', () => {
for (const s of ['nba', 'nfl', 'soccer', 'nhl', 'ncaab', '', null, undefined]) {
expect(ranksOnForecast(s)).toBe(false);
}
expect([...FORECAST_RANKED_SPORTS]).toEqual(['mlb']);
});
});