diff --git a/tests/unit/edgeBoard.test.js b/tests/unit/edgeBoard.test.js index d80c449..f14297f 100644 --- a/tests/unit/edgeBoard.test.js +++ b/tests/unit/edgeBoard.test.js @@ -70,4 +70,20 @@ describe('flattenToEdgeBoard', () => { expect(flattenToEdgeBoard(null)).toEqual([]); expect(flattenToEdgeBoard([{}])).toEqual([]); }); + + // P1-7 — an impossible edge (the pipeline's 60/100/140 placeholder) is not a + // market signal. It must be treated as absent so the board neither DISPLAYS + // nor RANKS on it — a fake +140% must never outrank a real +8.4%. + test('insane edge (>40%) is nulled so it neither displays nor ranks', () => { + const rows = flattenToEdgeBoard([card({ playerStrips: [ + strip('RealEdge', [{ stat: 'PTS', line: 20, side: 'O', grade: 'B', edge: 8.4 }]), + strip('BrokenEdge', [{ stat: 'PTS', line: 20, side: 'O', grade: 'A', edge: 140 }]), + ] })]); + const broken = rows.find((r) => r.player === 'BrokenEdge'); + const real = rows.find((r) => r.player === 'RealEdge'); + expect(broken.edge).toBeNull(); // not displayed as a fake % + expect(real.edge).toBe(8.4); // real edge preserved + // real edge outranks the nulled placeholder despite the placeholder's higher grade + expect(rows[0].player).toBe('RealEdge'); + }); }); diff --git a/web/src/components/vyndr/MobileEdgeBoard.tsx b/web/src/components/vyndr/MobileEdgeBoard.tsx index 6c829a9..f9ad872 100644 --- a/web/src/components/vyndr/MobileEdgeBoard.tsx +++ b/web/src/components/vyndr/MobileEdgeBoard.tsx @@ -38,8 +38,13 @@ function rampOpacity(rank: number): number { return Math.max(0.5, 0.7 - (rank - 5) * 0.06); } +// A real prop-market edge (model prob − implied prob) is single-digit, rarely +// past ~15%, never past ~40. Values beyond that are not a market edge — they're +// the pipeline's miscalibrated placeholder (seen live: 60/100/140). Rendering +// "+140%" would fabricate a signal, so we show it absent. Backend fix pending. +const SANE_EDGE_MAX = 40; function EdgeCell({ edge }: { edge: number | null }) { - if (edge == null) { + if (edge == null || Math.abs(edge) > SANE_EDGE_MAX) { return ; } const pos = edge >= 0; diff --git a/web/src/lib/slateAdapter.js b/web/src/lib/slateAdapter.js index bd71305..2d408c9 100644 --- a/web/src/lib/slateAdapter.js +++ b/web/src/lib/slateAdapter.js @@ -608,6 +608,10 @@ const EDGE_GRADE_RANK = { 'A+': 0, A: 1, 'A-': 2, 'B+': 3, B: 4, 'B-': 5, C: 6, function edgeGradeRank(g) { return EDGE_GRADE_RANK[g] != null ? EDGE_GRADE_RANK[g] : 9; } +// Ceiling for a plausible prop-market edge; beyond it the value is a broken +// placeholder, not a signal (mirrored in MobileEdgeBoard.EdgeCell). +const EDGE_BOARD_SANE_MAX = 40; + function flattenToEdgeBoard(cards) { const rows = []; for (const c of Array.isArray(cards) ? cards : []) { @@ -624,7 +628,11 @@ function flattenToEdgeBoard(cards) { line: p.line, side: p.side || 'O', grade: p.grade, - edge: (p.edge != null && Number.isFinite(p.edge)) ? p.edge : null, + // A real prop edge is single-digit %, never past ~40. Values beyond + // that are the pipeline's miscalibrated placeholder (live: 60/100/140) + // — treat as absent so the board neither displays nor RANKS on a fake + // edge. When absent, rows fall through to the grade-rank tiebreak. + edge: (p.edge != null && Number.isFinite(p.edge) && Math.abs(p.edge) <= EDGE_BOARD_SANE_MAX) ? p.edge : null, sport: c.sport || '', away, home,