The interface was about to restore the lie: null confidence rendered as 0%
The backend already refuses to state a probability it cannot certify. The card
did not. `gradeAdapter.js:59` read
input.confidence != null ? Math.round(Number(input.confidence)) : 0
and `GradeResultCard` printed `{d.confidence}% confidence` unconditionally, so
an UNCERTIFIED Read — which by contract carries no exact probability — would
have rendered "0% CONFIDENCE" to a user. That is `Number(null) === 0`, the
standing fabrication bug in this codebase, arriving at the last surface before
the eye.
Found by running two REAL production rows (model_snapshots 453021 and 453033
from proven cohort 27ce152f) through the real serving seam and the real adapter,
before rendering anything.
Repaired at the two places it lives, presentation only:
- the adapter carries null through instead of coercing;
- the card renders "Confidence unavailable" as TEXT when there is no number.
Wording follows the house pattern already in `readHistory.js` ("History
unavailable") — no dash, no dimmed zero, no icon-only state, and no engineering
language: a test forbids isotonic / artifact / probability_contract /
UNCERTIFIED / certification_version reaching the card.
A second, narrower correction: the derived-field withdrawal was
`if (f in out) out[f] = null`, so it depended on whether the input happened to
carry the key and a field added by a later merge would have arrived
un-withdrawn. An uncertified row now DECLARES each probability-derived claim
null rather than merely lacking it.
The uncertified fixture is the strong case, not a convenient one: Luis Robert
carries value:true and ev_pct 38.5 on the raw row, so the contract has a live
VALUE badge to withdraw rather than an absent one to leave absent. Verified
withdrawn, while grade B+, the side, the Read and the market (book -160,
fair -135) all survive.
The certified case moves the number materially and visibly: raw 0.798 -> served
0.638, and EV is recomputed from the served probability, not carried from raw.
PriceTriplet needed no change — it already renders an absent leg as a dash and
was documented never to print a zero.
Suite 407/407, 5,691 passed. Teeth 45/45 + 10/10 + 23/23. Live OFF.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQJeAG8vcDoL5zkiaJyVb8
This commit is contained in:
@@ -292,9 +292,12 @@ inject(38, 'live default is not OFF',
|
|||||||
'src/services/model/probabilityContract.js',
|
'src/services/model/probabilityContract.js',
|
||||||
` const flag = String(raw || '') === '1';`, ` const flag = String(raw ?? '1') !== '0';`,
|
` const flag = String(raw || '') === '1';`, ` const flag = String(raw ?? '1') !== '0';`,
|
||||||
'tests/unit/servedProbability.test.js');
|
'tests/unit/servedProbability.test.js');
|
||||||
inject(39, 'an uncertified row keeps a probability-derived claim',
|
inject(39, 'an uncertified row keeps its exact probability',
|
||||||
'src/services/model/servedProbability.js',
|
'src/services/model/servedProbability.js',
|
||||||
` for (const f of DERIVED_FIELDS) if (f in out) out[f] = null;`, ``,
|
` out.p_win = null;
|
||||||
|
out.confidence = null;`,
|
||||||
|
` out.p_win = row.p_win;
|
||||||
|
out.confidence = row.confidence;`,
|
||||||
'tests/unit/servedProbability.test.js');
|
'tests/unit/servedProbability.test.js');
|
||||||
inject(40, 'a certified row derives EV from raw instead of served',
|
inject(40, 'a certified row derives EV from raw instead of served',
|
||||||
'src/services/model/servedProbability.js',
|
'src/services/model/servedProbability.js',
|
||||||
@@ -328,6 +331,31 @@ logic(45, 'the stage promotion changed the artifact', () => {
|
|||||||
detail: `id/knot/curve/cutoff/fit_n unchanged across promotion; stage=${A2.stage}` };
|
detail: `id/knot/curve/cutoff/fit_n unchanged across promotion; stage=${A2.stage}` };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── RENDER CONTRACT (this tranche) ───────────────────────────────────────
|
||||||
|
inject(46, 'a null confidence is coerced to a number for the card',
|
||||||
|
'web/src/lib/gradeAdapter.js',
|
||||||
|
` const confidence = input.confidence != null && Number.isFinite(Number(input.confidence))
|
||||||
|
? Math.round(Number(input.confidence))
|
||||||
|
: null;`,
|
||||||
|
` const confidence = input.confidence != null ? Math.round(Number(input.confidence)) : 0;`,
|
||||||
|
'tests/unit/liveRenderContract.test.js');
|
||||||
|
inject(47, 'the card prints a confidence number unconditionally',
|
||||||
|
'web/src/components/vyndr/GradeResultCard.tsx',
|
||||||
|
` {d.confidence != null
|
||||||
|
? <span>{d.confidence}% confidence</span>
|
||||||
|
: <span style={{ color: 'rgba(232,255,244,.55)' }}>Confidence unavailable</span>}`,
|
||||||
|
` <span>{d.confidence}% confidence</span>`,
|
||||||
|
'tests/unit/liveRenderContract.test.js');
|
||||||
|
inject(48, 'an uncertified row keeps its VALUE badge',
|
||||||
|
'src/services/model/servedProbability.js',
|
||||||
|
` for (const f of DERIVED_FIELDS) out[f] = null;`, ``,
|
||||||
|
'tests/unit/liveRenderContract.test.js');
|
||||||
|
inject(49, 'a certified row shows the raw probability as confidence',
|
||||||
|
'src/services/model/servedProbability.js',
|
||||||
|
` out.confidence = Math.round(resolution.served_probability * 100);`,
|
||||||
|
` out.confidence = Math.round(resolution.raw_model_probability * 100);`,
|
||||||
|
'tests/unit/liveRenderContract.test.js');
|
||||||
|
|
||||||
const landed = results.filter((r) => r.landed).length;
|
const landed = results.filter((r) => r.landed).length;
|
||||||
console.log(JSON.stringify({ teeth_landed: `${landed}/${results.length}`, results }, null, 2));
|
console.log(JSON.stringify({ teeth_landed: `${landed}/${results.length}`, results }, null, 2));
|
||||||
process.exit(landed === results.length ? 0 : 1);
|
process.exit(landed === results.length ? 0 : 1);
|
||||||
|
|||||||
@@ -87,7 +87,11 @@ function applyToRows(rows, opts = {}) {
|
|||||||
out.confidence = null;
|
out.confidence = null;
|
||||||
out.confidence_basis = 'uncertified';
|
out.confidence_basis = 'uncertified';
|
||||||
out.kelly = null;
|
out.kelly = null;
|
||||||
for (const f of DERIVED_FIELDS) if (f in out) out[f] = null;
|
// DECLARED NULL, NOT MERELY ABSENT. `if (f in out)` made the withdrawal
|
||||||
|
// depend on whether the input happened to carry the key, so a field added
|
||||||
|
// by a later merge would arrive un-withdrawn. An uncertified row states
|
||||||
|
// that each probability-derived claim is unavailable.
|
||||||
|
for (const f of DERIVED_FIELDS) out[f] = null;
|
||||||
return out;
|
return out;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THE INTERFACE MUST NOT RESTORE THE LIE.
|
||||||
|
*
|
||||||
|
* The backend already refuses to state a probability it cannot certify. This
|
||||||
|
* holds the presentation layer to the same standard: an uncertified Read shows
|
||||||
|
* the Read, the grade, the side and the market — and says plainly that the
|
||||||
|
* exact confidence is unavailable, rather than printing a number nobody stands
|
||||||
|
* behind.
|
||||||
|
*
|
||||||
|
* Found by this suite before any pixel was rendered: `gradeAdapter` coerced a
|
||||||
|
* null confidence to 0, so `GradeResultCard` rendered "0% confidence".
|
||||||
|
*/
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { mapScanToGradeResult } = require('../../web/src/lib/gradeAdapter');
|
||||||
|
const sp = require('../../src/services/model/servedProbability');
|
||||||
|
const registry = require('../../src/services/model/artifactRegistry');
|
||||||
|
|
||||||
|
const ERA = 'engine1@2026-08-07-fullwindow';
|
||||||
|
const CARD = fs.readFileSync(path.join(__dirname, '../../web/src/components/vyndr/GradeResultCard.tsx'), 'utf8');
|
||||||
|
|
||||||
|
/** REAL production rows from proven shadow cohort 27ce152f, run through the
|
||||||
|
* REAL serving seam with live forced on for the test process only. */
|
||||||
|
// Exact production values, model_snapshots ids 453021 and 453033.
|
||||||
|
const REAL = {
|
||||||
|
// raw 0.798 -> served 0.638: calibration materially moves the displayed number
|
||||||
|
certified: { sport: 'mlb', stat_type: 'hits', player: 'Christian Encarnacion Strand',
|
||||||
|
line: 1.5, side: 'under', direction: 'under', model_version: ERA, p_win: 0.798,
|
||||||
|
confidence: 80, grade: 'B+', ev_pct: 17.9, value: false,
|
||||||
|
book_odds: -210, fair_odds: -173, fair_prob: 0.633, over_odds: 155, under_odds: -210,
|
||||||
|
tier: 'desk' },
|
||||||
|
// raw 0.852, and it carries value:true / ev 38.5 — so the contract has a real
|
||||||
|
// actionability badge to withdraw, not merely an absent one to leave absent.
|
||||||
|
uncertified: { sport: 'mlb', stat_type: 'hits', player: 'Luis Robert',
|
||||||
|
line: 1.5, side: 'under', direction: 'under', model_version: ERA, p_win: 0.852,
|
||||||
|
confidence: 85, grade: 'B+', ev_pct: 38.5, value: true,
|
||||||
|
book_odds: -160, fair_odds: -135, fair_prob: 0.575, over_odds: 120, under_odds: -160,
|
||||||
|
tier: 'desk' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const live = (row) => sp.applyToRows([{ ...row }], { liveState: { live: 'ON' } })[0];
|
||||||
|
const card = (row) => mapScanToGradeResult(live(row));
|
||||||
|
|
||||||
|
describe('the live contract reaches the card correctly', () => {
|
||||||
|
it('CERTIFIED: the card shows the SERVED probability, not the raw one', () => {
|
||||||
|
const served = registry.applyCurve(registry.load('mlb', 'hits'), 0.798);
|
||||||
|
const d = card(REAL.certified);
|
||||||
|
expect(d.confidence).toBe(Math.round(Math.round(served * 1000) / 1000 * 100));
|
||||||
|
expect(d.confidence).not.toBe(80); // the raw confidence
|
||||||
|
expect(String(d.confidence)).not.toBe('0');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CERTIFIED: EV comes from the served probability, not the raw one', () => {
|
||||||
|
const out = live(REAL.certified);
|
||||||
|
const { evPct } = require('../../src/utils/devig');
|
||||||
|
expect(out.ev_pct).toBe(evPct(out.p_win, -210));
|
||||||
|
expect(out.ev_pct).not.toBe(17.9); // the raw-derived value
|
||||||
|
});
|
||||||
|
|
||||||
|
it('UNCERTIFIED: confidence is NULL — never 0, never 50, never NaN', () => {
|
||||||
|
const d = card(REAL.uncertified);
|
||||||
|
expect(d.confidence).toBeNull();
|
||||||
|
expect(d.confidence).not.toBe(0);
|
||||||
|
expect(d.confidence).not.toBe(50);
|
||||||
|
expect(Number.isNaN(d.confidence)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('UNCERTIFIED: no raw probability survives anywhere the card can print it', () => {
|
||||||
|
const d = card(REAL.uncertified);
|
||||||
|
const json = JSON.stringify(d);
|
||||||
|
expect(json).not.toContain('0.852');
|
||||||
|
expect(json).not.toContain('"confidence":85');
|
||||||
|
expect(json).not.toContain('38.5'); // raw EV
|
||||||
|
});
|
||||||
|
|
||||||
|
it('UNCERTIFIED: the Read, the grade, the side and the market all survive', () => {
|
||||||
|
const d = card(REAL.uncertified);
|
||||||
|
expect(d.grade).toBe('B+'); // grade untouched
|
||||||
|
expect(d.player).toBe('Luis Robert'); // Read exists
|
||||||
|
expect(d.priceTriplet.book_odds).toBe(-160); // market visible
|
||||||
|
expect(d.priceTriplet.model_odds).toBeNull(); // model leg withheld
|
||||||
|
expect(d.priceTriplet.ev_pct).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('UNCERTIFIED: a TRUE value badge is withdrawn, not left standing', () => {
|
||||||
|
expect(REAL.uncertified.value).toBe(true); // the raw row claims VALUE
|
||||||
|
const out = live(REAL.uncertified);
|
||||||
|
expect(out.value).toBeNull(); // and the contract withdraws it
|
||||||
|
expect(out.kelly).toBeNull();
|
||||||
|
expect(out.model_odds).toBeNull();
|
||||||
|
expect(out.ev_pct).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('what the card actually renders', () => {
|
||||||
|
it('renders a WORD, not a number, when confidence is unavailable', () => {
|
||||||
|
expect(CARD).toContain('Confidence unavailable');
|
||||||
|
// and the numeric clause is conditional, not unconditional
|
||||||
|
expect(CARD).toContain('d.confidence != null');
|
||||||
|
expect(CARD).not.toMatch(/<span>\{d\.confidence\}% confidence<\/span>\s*\n\s*<\/div>/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the unavailable state is TEXT — not colour, an icon or an absence', () => {
|
||||||
|
const i = CARD.indexOf('Confidence unavailable');
|
||||||
|
const clause = CARD.slice(i - 200, i + 60);
|
||||||
|
expect(clause).toContain('<span'); // rendered as readable text
|
||||||
|
// it must not be conveyed by a dash or an empty node
|
||||||
|
expect(clause).not.toContain('—');
|
||||||
|
expect(clause).not.toMatch(/>\s*—\s*</);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('no engineering language reaches the user', () => {
|
||||||
|
for (const term of ['isotonic', 'artifact', 'knot_digest', 'probability_contract',
|
||||||
|
'UNCERTIFIED', 'CERTIFIED_CALIBRATED', 'certification_version', 'model era']) {
|
||||||
|
expect(CARD).not.toContain(term);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('grade and confidence availability stay separate concepts', () => {
|
||||||
|
const d = card(REAL.uncertified);
|
||||||
|
expect(d.confidence).toBeNull();
|
||||||
|
expect(d.grade).toBe('B+');
|
||||||
|
// no strike-through / revision styling tied to the confidence state
|
||||||
|
const i = CARD.indexOf('Confidence unavailable');
|
||||||
|
expect(CARD.slice(i - 300, i + 100)).not.toContain('line-through');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -40,7 +40,9 @@ export interface GradeResultData {
|
|||||||
line: number;
|
line: number;
|
||||||
side: 'Over' | 'Under';
|
side: 'Over' | 'Under';
|
||||||
grade: string;
|
grade: string;
|
||||||
confidence: number;
|
/** Null when the serving contract has no certified probability for this Read.
|
||||||
|
* Rendered as an explicit unavailable state, never as a number. */
|
||||||
|
confidence: number | null;
|
||||||
edge: number | null;
|
edge: number | null;
|
||||||
projection: number | null;
|
projection: number | null;
|
||||||
phosphorConfirmed?: boolean;
|
phosphorConfirmed?: boolean;
|
||||||
@@ -202,7 +204,11 @@ export default function GradeResultCard({
|
|||||||
user-facing claim is withdrawn until a real price-aware edge (ev_pct) is
|
user-facing claim is withdrawn until a real price-aware edge (ev_pct) is
|
||||||
live. See specs/edge-pct-display-retirement.md. */}
|
live. See specs/edge-pct-display-retirement.md. */}
|
||||||
<span style={{ color: 'rgba(232,255,244,.4)', margin: '0 9px' }}>·</span>
|
<span style={{ color: 'rgba(232,255,244,.4)', margin: '0 9px' }}>·</span>
|
||||||
<span>{d.confidence}% confidence</span>
|
{/* An uncertified Read has no exact probability. Say so in words —
|
||||||
|
a dimmed number, a dash or a 0 would each read as a value. */}
|
||||||
|
{d.confidence != null
|
||||||
|
? <span>{d.confidence}% confidence</span>
|
||||||
|
: <span style={{ color: 'rgba(232,255,244,.55)' }}>Confidence unavailable</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{d.phosphorConfirmed && (
|
{d.phosphorConfirmed && (
|
||||||
|
|||||||
@@ -56,7 +56,14 @@ function mapScanToGradeResult(input = {}) {
|
|||||||
const side = direction === 'under' ? 'Under' : 'Over';
|
const side = direction === 'under' ? 'Under' : 'Over';
|
||||||
const line = input.line != null ? Number(input.line) : 0;
|
const line = input.line != null ? Number(input.line) : 0;
|
||||||
const projection = input.projection != null ? Number(input.projection) : undefined;
|
const projection = input.projection != null ? Number(input.projection) : undefined;
|
||||||
const confidence = input.confidence != null ? Math.round(Number(input.confidence)) : 0;
|
// NULL IS NOT ZERO. Under the certified serving contract an UNCERTIFIED Read
|
||||||
|
// carries no exact probability, so `confidence` arrives null — and coercing
|
||||||
|
// that to 0 made the card render "0% confidence", which states a number the
|
||||||
|
// model explicitly declined to give. `Number(null) === 0` is the standing
|
||||||
|
// fabrication bug in this codebase; this was one.
|
||||||
|
const confidence = input.confidence != null && Number.isFinite(Number(input.confidence))
|
||||||
|
? Math.round(Number(input.confidence))
|
||||||
|
: null;
|
||||||
const tier = input.tier || 'free';
|
const tier = input.tier || 'free';
|
||||||
const includeAlt = tier === 'desk';
|
const includeAlt = tier === 'desk';
|
||||||
// Tier gating so the new card doesn't give paid content away (free users get
|
// Tier gating so the new card doesn't give paid content away (free users get
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user