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',
|
||||
` const flag = String(raw || '') === '1';`, ` const flag = String(raw ?? '1') !== '0';`,
|
||||
'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',
|
||||
` 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');
|
||||
inject(40, 'a certified row derives EV from raw instead of served',
|
||||
'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}` };
|
||||
});
|
||||
|
||||
// ── 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;
|
||||
console.log(JSON.stringify({ teeth_landed: `${landed}/${results.length}`, results }, null, 2));
|
||||
process.exit(landed === results.length ? 0 : 1);
|
||||
|
||||
Reference in New Issue
Block a user