6c97f59546
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
94 lines
4.4 KiB
JavaScript
94 lines
4.4 KiB
JavaScript
/**
|
|
* ORDER B — edge_pct DISPLAY retirement (specs/edge-pct-display-retirement.md).
|
|
*
|
|
* The point of these tests: the CLAIM is withdrawn from the UI, but the COMPUTATION and
|
|
* the board's signed-edge sort fallback SURVIVE. Deleting those would re-break the
|
|
* grade-board sort fixed on 2026-07-29.
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const read = (p) => fs.readFileSync(path.join(__dirname, '../../', p), 'utf8');
|
|
|
|
describe('the "% edge" claim is gone from the user-facing cards', () => {
|
|
test('GradeResultCard no longer renders an edge percentage', () => {
|
|
const src = read('web/src/components/vyndr/GradeResultCard.tsx');
|
|
expect(src).not.toMatch(/\{d\.edge >= 0 \? '\+' : ''\}\{d\.edge\}% edge/);
|
|
expect(src).not.toMatch(/\$\{d\.edge >= 0 \? '\+' : ''\}\$\{d\.edge\}%/);
|
|
// the EDGE stat cell is honest-absent
|
|
expect(src).toMatch(/l: 'EDGE', v: '—'/);
|
|
});
|
|
|
|
test('the alt-ladder rung no longer renders the same price-free number', () => {
|
|
const src = read('web/src/components/vyndr/GradeResultCard.tsx');
|
|
expect(src).not.toMatch(/\{Number\(a\.edge\) >= 0 \? '\+' : ''\}\{a\.edge\}%/);
|
|
});
|
|
|
|
test('SoccerGradeResult no longer renders "% edge"', () => {
|
|
const src = read('web/src/components/SoccerGradeResult.tsx');
|
|
expect(src).not.toMatch(/\{edge_pct >= 0 \? '\+' : ''\}\{edge_pct\.toFixed\(1\)\}% edge/);
|
|
});
|
|
|
|
test('no dangling unused edgeColor import (build-breaker guard)', () => {
|
|
const src = read('web/src/components/vyndr/GradeResultCard.tsx');
|
|
const imports = src.match(/import \{[^}]*\} from '@\/lib\/colorContract'/);
|
|
if (imports && /edgeColor/.test(imports[0])) {
|
|
expect(src).toMatch(/edgeColor\(/); // imported ⇒ must be used
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('COMPUTATION and SORT survive — removing them would re-break the board', () => {
|
|
test('backend edgePctFor still computes edge_pct', () => {
|
|
expect(read('src/services/intelligence/analyzeViaEngine1.js')).toMatch(/function edgePctFor/);
|
|
});
|
|
|
|
test('web computeEdge still exists', () => {
|
|
expect(read('web/src/lib/gradeAdapter.js')).toMatch(/function computeEdge/);
|
|
});
|
|
|
|
// SUPERSEDED 2026-08-01 by the p_win flip. These two asserted that the SIGNED
|
|
// EDGE SORT was intact. Edge no longer sorts anything — measured on n=200
|
|
// settled MLB rows, corr(edge, outcome) = -0.010 / -0.022 against
|
|
// corr(p_win, outcome) = +0.26. The retirement these tests guard is now
|
|
// deeper: edge is retired from RANKING as well as from hero DISPLAY.
|
|
//
|
|
// The replacements assert the INVERSE, which is strictly stronger: the sort
|
|
// keys must not mention edge at all, in either ranking function.
|
|
test('NO ranking function sorts on edge any more', () => {
|
|
const strip = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
for (const f of ['web/src/lib/slateAdapter.js', 'src/utils/gradeRanking.js']) {
|
|
const code = strip(read(f));
|
|
expect(code).not.toMatch(/descNullsLast\(a\.edge, b\.edge\)/);
|
|
expect(code).not.toMatch(/Math\.abs\(numOr\(g\.edge/);
|
|
expect(code).not.toMatch(/edge: strictNum\(g\.edge/);
|
|
}
|
|
});
|
|
|
|
test('edge is still CARRIED on the row — retired from ranking, not deleted', () => {
|
|
// Losing the record would be worse than mis-using it: edge stays computed,
|
|
// stored and displayed as a labelled diagnostic.
|
|
const code = read('web/src/lib/slateAdapter.js');
|
|
expect(code).toMatch(/edge: \(rec\.edge_pct != null/);
|
|
});
|
|
|
|
test('the sort is driven by forecast_rank, and edge cannot move it', () => {
|
|
const adapter = require('../../web/src/lib/slateAdapter');
|
|
const names = (rows) => rows.map((g) => g.player);
|
|
// identical rows except edge → identical order
|
|
expect(names(adapter.selectTopGrades([
|
|
{ player: 'a', grade: 'B', confidence: 50, edge: -40 },
|
|
{ player: 'b', grade: 'B', confidence: 50, edge: 12 },
|
|
], 10))).toEqual(['a', 'b']);
|
|
// forecast_rank decides
|
|
expect(names(adapter.selectTopGrades([
|
|
{ player: 'worseGradeBetterForecast', grade: 'C', confidence: 10, forecast_rank: 1, edge: -40 },
|
|
{ player: 'betterGradeWorseForecast', grade: 'A', confidence: 99, forecast_rank: 2, edge: 40 },
|
|
], 10))).toEqual(['worseGradeBetterForecast', 'betterGradeWorseForecast']);
|
|
});
|
|
|
|
test('DeskShowcase keeps its own already-honest rendering', () => {
|
|
const src = read('web/src/app/pricing/DeskShowcase.tsx');
|
|
expect(src).toMatch(/edge == null \? '—'/);
|
|
});
|
|
});
|