Un-claim CLV on the public profile; un-fabricate player-page FORM
Two Truth-Law fixes found by auditing the product logged-out. FIX 1 — /u/[handle] claimed a "CLV-verified record" with "closing-line value included" while ZERO closing-line value renders there. Verified live: GET /api/profiles/vyndr returns beat_close_pct null (gated behind CLV_CAPTURE_RELIABLE, unset while C4 is open). Eight instances found — two of them (the OG + portrait "CLV-VERIFIED RECORD · 30D" eyebrows) only by the post-removal residual sweep; two more printed the claim in exactly the no-record branch. Copy now describes what the page shows. The gated CLV-VERIFIED badge and the BEAT CLOSE figure are removed from the public profile, OG card and portrait card. DISPLAY ONLY: beat_close_pct, clvCaptureReliable() and the whole CLV data path are untouched, and the earned directional badge stays Analyst+Desk. The claim returns when CLV genuinely renders here. Also fixes the doubled "· VYNDR · VYNDR" title (layout's '%s · VYNDR' template already supplies the suffix); verified on composed output by serving the build and reading the real HTML, not on source. FIX 2 — the player page's FORM was `70 + 4 × (count of tonight's graded props)`. Nothing on the HTTP path ever sets stats.form, so that fallback WAS the live number: Josh Bell's "74" is 70 + 4×1 prop, confirmed against his live payload. MATCHUP was gradeFromForm(that number), with a hardcoded 'B' on the no-archetype branch — both fabricated letters with no opponent input on the path. Systemic: buildIntel is the unconditional path for every player and sport. FORM and MATCHUP now render "—" (kind 'plain', so no bar width or colour is computed off a null). gradeFromForm is deleted and the prop count is no longer passed into buildIntel. computeFormScore's hardcoded 75 now returns undefined. Induced across MLB/NBA/WNBA: all render cleanly, and real values (USAGE 3.6 AB/G, REST B2B) still render. Neither form value feeds the grade — engine1 reads raw l5_avg/l20_avg against the line and never a form key; buildIntelFields decorates the already-graded object. Grade inputs are byte-identical. Held (needs a per-sport headline-stat design call): a real player-level form metric + label disambiguation. Tests 3491 passed / 289 suites, web build exit 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
@@ -89,3 +89,60 @@ describe('getLeaders', () => {
|
||||
expect(await svc.getLeaders('nba', { cacheGet: async () => null })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Session 65 — UN-FABRICATE (Truth Law) ────────────────────────────────
|
||||
// FORM used to be `70 + 4 × (count of tonight's graded props)` whenever
|
||||
// `stats.form` was absent — and nothing on the HTTP path ever sets it, so that
|
||||
// fallback WAS the live number (Josh Bell's "74" = 70 + 4×1 prop). MATCHUP was
|
||||
// `gradeFromForm(that number)`, with a hardcoded 'B' when no archetype
|
||||
// resolved: both branches fabricated a letter with zero opponent input.
|
||||
describe('buildIntel — absent renders absent, never a manufactured number', () => {
|
||||
const { buildIntel } = svc._internals;
|
||||
const arch = { primary: { name: 'BOMBER' } };
|
||||
|
||||
it('renders FORM as "—" with no bar when no real form value exists', () => {
|
||||
const form = buildIntel({}, arch).find((m) => m.label === 'FORM');
|
||||
expect(form.value).toBe('—');
|
||||
expect(form.kind).toBe('plain'); // 'plain' draws no progress bar → no width math on a null
|
||||
expect(form.score).toBeUndefined();
|
||||
});
|
||||
|
||||
it('renders MATCHUP as "—", never a grade letter, with no opponent input', () => {
|
||||
const m = buildIntel({}, arch).find((x) => x.label === 'MATCHUP');
|
||||
expect(m.value).toBe('—');
|
||||
expect(m.kind).toBe('plain');
|
||||
// and not merely because the archetype was missing — the old code
|
||||
// hardcoded 'B' on that branch too.
|
||||
const noArch = buildIntel({}, { primary: null }).find((x) => x.label === 'MATCHUP');
|
||||
expect(noArch.value).toBe('—');
|
||||
});
|
||||
|
||||
it('the prop count can NEVER move FORM (the fabrication that shipped)', async () => {
|
||||
const mk = (n) => ({
|
||||
grades: Array.from({ length: n }, (_, i) => ({
|
||||
player: 'Josh Bell', stat_type: `stat_${i}`, line: 0.5, direction: 'over', grade: 'B', confidence: 57,
|
||||
})),
|
||||
});
|
||||
const formFor = async (n) => {
|
||||
const r = await svc.getPlayerIntel('Josh Bell', 'mlb', { cacheGet: cacheWith(mk(n)), stats: {} });
|
||||
return r.intel.find((m) => m.label === 'FORM').value;
|
||||
};
|
||||
expect(await formFor(1)).toBe('—'); // was '74'
|
||||
expect(await formFor(5)).toBe('—'); // was '90'
|
||||
expect(await formFor(0)).toBe('—'); // was '70'
|
||||
});
|
||||
|
||||
it('still renders a REAL form value when one is supplied (un-claim, not un-build)', () => {
|
||||
const form = buildIntel({ form: 63 }, arch).find((m) => m.label === 'FORM');
|
||||
expect(form.value).toBe('63');
|
||||
expect(form.kind).toBe('form');
|
||||
expect(form.score).toBe('63%');
|
||||
});
|
||||
|
||||
it('usage and rest keep their honest absent states', () => {
|
||||
const rows = buildIntel({}, arch);
|
||||
expect(rows.find((m) => m.label === 'USAGE').value).toBe('—');
|
||||
expect(rows.find((m) => m.label === 'REST').value).toBe('—');
|
||||
expect(buildIntel({ usage: '3.6 AB/G', rest: 'B2B' }, arch).find((m) => m.label === 'USAGE').value).toBe('3.6 AB/G');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user