Free proof surface: /record — tier-record-forward, honest CLV building panel
Presentation over existing endpoints. src/ untouched (git diff empty): no grade, model or ledger change. Pricing/migration are Builds 2/3. TIER-RECORD-FORWARD. /record reads the canonical public aggregates (/api/accuracy + /api/ledger/model) and prints them as-is. B 60% n512 and C 57% n413 ship with C honestly BELOW B; A (n2), D (n5) and F (n5) render HOLLOW with their real sample instead of a rate. Sport slicing (all/mlb/wnba) is client-side because the endpoints ignore ?sport= — mlb 526 @62%, wnba 411 @54% come from the sports map. THE LOAD-BEARING RULE, enforced in lib/proofRecord.js and locked by tests: where the source withholds a percentage it stays null. A is 1/2 and therefore 50% is derivable — a test asserts we do NOT derive it, because the API withheld it on purpose (n < 20). CLV IS AN HONEST ABSENCE, NOT A NUMBER. beat_close_pct is null because clvCaptureReliable() has not passed. The panel says "NOT PUBLISHED YET" and explains that any percentage printed today would be measuring our collection gaps as much as our edge; it surfaces the accruing sample (937) but no rate. Tests assert the panel never falls back to clv_beat/clv_sample (34/937 = 3.6%) and that the serialized panel contains no "3.6" — that number is computable and would be wrong, which is the exact fabrication this surface exists to refuse. The panel is built to receive a real number later without a redesign. HELD, and named on the page rather than faked: calibration and accuracy-over-time are absent because there is no honest source (no claimed-vs-actual endpoint; window_days fixed at 30 with no series). The page says so, and says it is not because they are unflattering. A page-level test asserts no hard-coded percentage exists in the markup, so no figure can drift from the aggregate, and that the page never touches /api/snapshot or itemized rows — the Build-1 gate holds and the exploit stays dead. Floor: 320 suites / 3986 tests green (16 new), web build exit 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* FREE PROOF SURFACE — the honesty contract (specs/free-proof-surface-review-zero.md).
|
||||
* These tests exist to make fabrication fail loudly.
|
||||
*/
|
||||
const p = require('../../web/src/lib/proofRecord');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const page = fs.readFileSync(path.join(__dirname, '../../web/src/app/record/page.tsx'), 'utf8');
|
||||
|
||||
// the REAL canonical shape, live 2026-07-31
|
||||
const BY_GRADE = {
|
||||
B: { hits: 309, misses: 203, total: 512, pct: 60 },
|
||||
C: { hits: 235, misses: 178, total: 413, pct: 57 },
|
||||
A: { hits: 1, misses: 1, total: 2, pct: null },
|
||||
D: { hits: 1, misses: 4, total: 5, pct: null },
|
||||
F: { hits: 1, misses: 4, total: 5, pct: null },
|
||||
};
|
||||
|
||||
describe('hollow buckets stay hollow — never derived', () => {
|
||||
const rows = p.tierRows(BY_GRADE, 20);
|
||||
const by = (t) => rows.find((r) => r.tier === t);
|
||||
|
||||
test('a null pct is preserved as hollow, NOT computed from hits/total', () => {
|
||||
for (const t of ['A', 'D', 'F']) {
|
||||
expect(by(t).pct).toBeNull();
|
||||
expect(by(t).hollow).toBe(true);
|
||||
}
|
||||
// A is 1/2 = 50% — derivable, and deliberately NOT derived
|
||||
expect(by('A').hits).toBe(1);
|
||||
expect(by('A').total).toBe(2);
|
||||
expect(by('A').pct).not.toBe(50);
|
||||
});
|
||||
|
||||
test('real percentages pass through untouched — no rounding, no smoothing', () => {
|
||||
expect(by('B').pct).toBe(60);
|
||||
expect(by('C').pct).toBe(57);
|
||||
});
|
||||
|
||||
test('C ships BELOW B — the unflattering truth is not reordered or hidden', () => {
|
||||
expect(by('C').pct).toBeLessThan(by('B').pct);
|
||||
const order = rows.map((r) => r.tier);
|
||||
expect(order.indexOf('B')).toBeLessThan(order.indexOf('C')); // tier order, not rank order
|
||||
});
|
||||
|
||||
test('the sample is always shown, even when the rate is withheld', () => {
|
||||
for (const r of rows) expect(r.total).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CLV — the panel must never publish the untrustworthy number', () => {
|
||||
const live = { beat_close_pct: null, clv_sample: 937, clv_beat: 34 };
|
||||
|
||||
test('null beat_close_pct yields NO number and names the guard', () => {
|
||||
const c = p.clvPanel(live);
|
||||
expect(c.publishable).toBe(false);
|
||||
expect(c.beatClosePct).toBeNull();
|
||||
expect(c.reason).toBe('capture_reliability');
|
||||
});
|
||||
|
||||
test('it does NOT fall back to clv_beat/clv_sample (34/937 = 3.6%)', () => {
|
||||
const c = p.clvPanel(live);
|
||||
// the derived value would be ~3.63; assert we returned ABSENCE, not that number
|
||||
const derived = (34 / 937) * 100;
|
||||
expect(c.beatClosePct).toBeNull();
|
||||
expect(c.beatClosePct).not.toBe(3.6);
|
||||
expect(c.beatClosePct).not.toBe(Math.round(derived * 10) / 10);
|
||||
expect(JSON.stringify(c)).not.toMatch(/3\.6/);
|
||||
});
|
||||
|
||||
test('the real sample IS surfaced (accrual is honest, the rate is not)', () => {
|
||||
expect(p.clvPanel(live).sample).toBe(937);
|
||||
});
|
||||
|
||||
test('when the guard passes, the real number is used as-is', () => {
|
||||
const c = p.clvPanel({ beat_close_pct: 61, clv_sample: 1200 });
|
||||
expect(c.publishable).toBe(true);
|
||||
expect(c.beatClosePct).toBe(61);
|
||||
});
|
||||
|
||||
test('junk aggregate degrades to the honest not-published state', () => {
|
||||
for (const v of [null, undefined, {}, { beat_close_pct: 'x' }]) {
|
||||
expect(p.clvPanel(v).publishable).toBe(false);
|
||||
expect(p.clvPanel(v).beatClosePct).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('client-side slicing (the endpoints ignore ?sport=)', () => {
|
||||
const acc = { overall: { sample: 937, overall: { pct: 58 }, byGrade: BY_GRADE, min_sample: 20 },
|
||||
sports: { mlb: { sample: 526, overall: { pct: 62 }, byGrade: {}, min_sample: 20 },
|
||||
wnba: { sample: 411, overall: { pct: 54 }, byGrade: {}, min_sample: 20 } } };
|
||||
|
||||
test('all / mlb / wnba each read their own canonical numbers', () => {
|
||||
expect(p.sliceFor(acc, 'all').sample).toBe(937);
|
||||
expect(p.sliceFor(acc, 'mlb').sample).toBe(526);
|
||||
expect(p.sliceFor(acc, 'mlb').pct).toBe(62);
|
||||
expect(p.sliceFor(acc, 'wnba').pct).toBe(54);
|
||||
});
|
||||
|
||||
test('an unknown sport is empty, never a silent fallback to overall', () => {
|
||||
expect(p.sliceFor(acc, 'nfl').sample).toBe(0);
|
||||
expect(p.sliceFor(acc, 'nfl').pct).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('the page itself', () => {
|
||||
test('renders no hard-coded percentage — every figure comes from the aggregate', () => {
|
||||
const hard = page.match(/\b\d{2}%/g) || [];
|
||||
expect(hard).toEqual([]);
|
||||
});
|
||||
|
||||
test('carries the honest CLV copy and the hollow explanation', () => {
|
||||
expect(page).toMatch(/NOT PUBLISHED YET/);
|
||||
expect(page).toMatch(/reliability check/);
|
||||
expect(page).toMatch(/too few settled reads to quote a rate/);
|
||||
});
|
||||
|
||||
test('names the held pieces honestly rather than faking them', () => {
|
||||
expect(page).toMatch(/Calibration and accuracy-over-time are not shown/);
|
||||
expect(page).toMatch(/not because they are unflattering/);
|
||||
});
|
||||
|
||||
test('reads the canonical endpoints, uncached', () => {
|
||||
expect(page).toMatch(/\/api\/accuracy/);
|
||||
expect(page).toMatch(/\/api\/ledger\/model/);
|
||||
expect(page).toMatch(/cache: 'no-store'/);
|
||||
});
|
||||
|
||||
test('exposes NO itemized grade rows — aggregate only (Build-1 gate holds)', () => {
|
||||
expect(page).not.toMatch(/\/api\/snapshot/);
|
||||
expect(page).not.toMatch(/player/i);
|
||||
});
|
||||
});
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,129 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import SectionHead from '@/components/vyndr/SectionHead';
|
||||
import { tierRows, clvPanel, sliceFor } from '@/lib/proofRecord';
|
||||
|
||||
/**
|
||||
* THE RECORD (free proof surface, 2026-07-31) — tier-record-forward.
|
||||
*
|
||||
* Every figure is read from the canonical public aggregates and printed as-is.
|
||||
* Where the source withholds a percentage (thin bucket) this shows HOLLOW with the
|
||||
* real sample instead of deriving one. C sits below B here because that is true.
|
||||
*
|
||||
* CLV leads with an honest ABSENCE: `beat_close_pct` is null because the capture
|
||||
* reliability guard has not passed, and the computable 34/937 would be wrong. The
|
||||
* panel says so plainly — refusing to publish an untrustworthy number is itself
|
||||
* the credibility signal.
|
||||
*/
|
||||
|
||||
const SPORTS = ['all', 'mlb', 'wnba'];
|
||||
|
||||
export default function RecordPage() {
|
||||
const [accuracy, setAccuracy] = useState(null);
|
||||
const [agg, setAgg] = useState(null);
|
||||
const [sport, setSport] = useState('all');
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let off = false;
|
||||
Promise.all([
|
||||
fetch('/api/accuracy', { cache: 'no-store' }).then((r) => r.json()).catch(() => null),
|
||||
fetch('/api/ledger/model', { cache: 'no-store' }).then((r) => r.json()).catch(() => null),
|
||||
]).then((out) => {
|
||||
if (off) return;
|
||||
setAccuracy(out[0] || null);
|
||||
setAgg((out[1] && out[1].aggregate) || null);
|
||||
setLoaded(true);
|
||||
});
|
||||
return () => { off = true; };
|
||||
}, []);
|
||||
|
||||
const slice = useMemo(() => sliceFor(accuracy, sport), [accuracy, sport]);
|
||||
const rows = useMemo(() => tierRows(slice.byGrade, slice.minSample), [slice]);
|
||||
const clv = useMemo(() => clvPanel(agg), [agg]);
|
||||
|
||||
return (
|
||||
<section style={{ maxWidth: 900, margin: '0 auto', padding: '28px 16px 96px' }}>
|
||||
<SectionHead accent="var(--g-a)">▚ THE RECORD</SectionHead>
|
||||
<h1 className="mono" style={{ fontSize: 28, fontWeight: 800, letterSpacing: '-0.02em', margin: '8px 0 6px' }}>
|
||||
EVERY GRADE, SETTLED IN PUBLIC
|
||||
</h1>
|
||||
<p className="mono" style={{ fontSize: 12, color: 'var(--text-2)', marginBottom: 18, lineHeight: 1.6 }}>
|
||||
Read straight from the settled ledger. Thin buckets stay blank rather than showing a rate we
|
||||
can't stand behind, and nothing here is rounded up.
|
||||
</p>
|
||||
|
||||
<div style={{ display: 'flex', gap: 6, marginBottom: 16 }}>
|
||||
{SPORTS.map((s) => (
|
||||
<button key={s} onClick={() => setSport(s)} className="mono"
|
||||
style={{ padding: '6px 12px', fontSize: 11, borderRadius: 6, cursor: 'pointer',
|
||||
border: '1px solid ' + (sport === s ? 'var(--g-a)' : 'var(--border)'),
|
||||
background: sport === s ? 'rgba(0,212,160,.10)' : 'transparent',
|
||||
color: sport === s ? 'var(--g-a)' : 'var(--text-2)' }}>
|
||||
{s.toUpperCase()}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="intel-surface" style={{ borderRadius: 10, padding: '18px 16px', marginBottom: 16 }}>
|
||||
<div className="mono" style={{ fontSize: 10, letterSpacing: '.1em', color: 'var(--text-2)', marginBottom: 10 }}>
|
||||
HIT RATE BY GRADE{slice.sample ? ' · ' + slice.sample + ' SETTLED' : ''}
|
||||
</div>
|
||||
{!loaded ? (
|
||||
<div className="mono" style={{ fontSize: 12, color: 'var(--text-2)' }}>Reading the ledger…</div>
|
||||
) : rows.length === 0 ? (
|
||||
<div className="mono" style={{ fontSize: 12, color: 'var(--text-2)' }}>
|
||||
No settled grades for this filter yet.
|
||||
</div>
|
||||
) : rows.map((r) => (
|
||||
<div key={r.tier} style={{ display: 'grid', gridTemplateColumns: '48px 1fr 108px', alignItems: 'center',
|
||||
gap: 10, padding: '8px 0', borderTop: '1px solid var(--border)' }}>
|
||||
<div className="mono" style={{ fontSize: 15, fontWeight: 800, color: r.hollow ? 'var(--text-2)' : 'var(--text-0)' }}>{r.tier}</div>
|
||||
<div style={{ height: 6, borderRadius: 3, background: 'var(--bg-2)', overflow: 'hidden' }}>
|
||||
{r.pct != null && (
|
||||
<div style={{ width: Math.max(0, Math.min(100, r.pct)) + '%', height: '100%', background: 'var(--g-a)' }} />
|
||||
)}
|
||||
</div>
|
||||
<div className="mono" style={{ fontSize: 12, textAlign: 'right', color: r.hollow ? 'var(--text-2)' : 'var(--text-0)' }}>
|
||||
{r.pct != null ? r.pct + '% · n' + r.total : '— · n' + r.total}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{loaded && rows.some((r) => r.hollow) && (
|
||||
<div className="mono" style={{ fontSize: 10, color: 'var(--text-2)', marginTop: 10, lineHeight: 1.6 }}>
|
||||
A dash means that tier has too few settled reads to quote a rate ({slice.minSample} minimum).
|
||||
We show the sample instead of a number built on a handful.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="intel-surface" style={{ borderRadius: 10, padding: '18px 16px', marginBottom: 16 }}>
|
||||
<div className="mono" style={{ fontSize: 10, letterSpacing: '.1em', color: 'var(--text-2)', marginBottom: 8 }}>
|
||||
CLOSING-LINE VALUE
|
||||
</div>
|
||||
{clv.publishable ? (
|
||||
<div className="mono" style={{ fontSize: 20, fontWeight: 800, color: 'var(--g-a)' }}>
|
||||
{clv.beatClosePct}% BEAT CLOSE <span style={{ fontSize: 11, color: 'var(--text-2)' }}>· n{clv.sample}</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="mono" style={{ fontSize: 15, fontWeight: 800, color: 'var(--amber)' }}>NOT PUBLISHED YET</div>
|
||||
<p style={{ fontSize: 12.5, color: 'var(--text-2)', margin: '8px 0 0', lineHeight: 1.6 }}>
|
||||
We track closing-line value on every graded read, and we are not quoting a figure yet:
|
||||
our closing-price capture has not cleared its own reliability check, so any percentage
|
||||
we printed today would be measuring our collection gaps as much as our edge.
|
||||
{clv.sample ? ' ' + clv.sample + ' settled reads are accruing toward it.' : ''} When the
|
||||
capture clears, the number appears here — good or bad.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="mono" style={{ fontSize: 10, color: 'var(--text-2)', lineHeight: 1.7 }}>
|
||||
Calibration and accuracy-over-time are not shown because we do not yet have an honest source
|
||||
for either — not because they are unflattering. They arrive when the data does.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
/**
|
||||
* FREE PROOF RECORD (2026-07-31) — display model for the public record surface.
|
||||
*
|
||||
* 🔴 THE ONE LAW: this module READS the canonical aggregates and never recomputes
|
||||
* a prettier version. Where the source says `pct: null` it stays HOLLOW — it is
|
||||
* null because the sample is under the threshold, and dividing hits/total here
|
||||
* would manufacture a number the API deliberately withheld. No rounding, no
|
||||
* smoothing, no "provisional" figure anywhere.
|
||||
*
|
||||
* Sources (both public, both already honest):
|
||||
* /api/accuracy -> overall + byGrade + per-sport `sports` map
|
||||
* /api/ledger/model -> aggregate (by_tier, clv_*)
|
||||
*/
|
||||
|
||||
/** Strict pass-through: a real number stays, anything else is ABSENT (never 0). */
|
||||
function pctOrNull(v) {
|
||||
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* tierRows(byGrade, minSample) — one row per grade tier, in tier order.
|
||||
* `hollow: true` means the source withheld a percentage; we show the sample and
|
||||
* say so, rather than inventing a rate off a 2-row bucket.
|
||||
*/
|
||||
const TIER_ORDER = ['A+', 'A', 'B', 'C', 'D', 'F'];
|
||||
function tierRows(byGrade, minSample) {
|
||||
const src = byGrade && typeof byGrade === 'object' ? byGrade : {};
|
||||
const min = Number.isFinite(Number(minSample)) ? Number(minSample) : 20;
|
||||
const keys = TIER_ORDER.filter((k) => src[k]).concat(
|
||||
Object.keys(src).filter((k) => !TIER_ORDER.includes(k)),
|
||||
);
|
||||
return keys.map((tier) => {
|
||||
const b = src[tier] || {};
|
||||
const total = Number(b.total) || 0;
|
||||
const pct = pctOrNull(b.pct);
|
||||
return {
|
||||
tier,
|
||||
total,
|
||||
hits: Number(b.hits) || 0,
|
||||
misses: Number(b.misses) || 0,
|
||||
pct, // null => hollow, NEVER derived here
|
||||
hollow: pct === null,
|
||||
belowThreshold: total < min,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* clvPanel(aggregate) — the honest CLV state.
|
||||
*
|
||||
* 🔴 NEVER RETURNS A NUMBER when the guard hasn't passed. `beat_close_pct` is
|
||||
* null because `clvCaptureReliable()` is false — the capture instrument is not
|
||||
* yet trustworthy — NOT because the division wasn't done. `clv_beat/clv_sample`
|
||||
* (34/937 = 3.6%) is computable and would be WRONG; publishing it, even labelled
|
||||
* "provisional", is the fabrication this surface exists to refuse.
|
||||
*/
|
||||
function clvPanel(aggregate) {
|
||||
const a = aggregate && typeof aggregate === 'object' ? aggregate : {};
|
||||
const published = pctOrNull(a.beat_close_pct);
|
||||
if (published !== null) {
|
||||
return { publishable: true, beatClosePct: published, sample: Number(a.clv_sample) || 0 };
|
||||
}
|
||||
return {
|
||||
publishable: false,
|
||||
beatClosePct: null, // stays null — no fallback, no estimate
|
||||
sample: Number(a.clv_sample) || 0,
|
||||
reason: 'capture_reliability', // the named guard, shown to the reader
|
||||
};
|
||||
}
|
||||
|
||||
/** Per-sport rows straight from the `sports` map — no re-derivation. */
|
||||
function sportRows(sports) {
|
||||
const src = sports && typeof sports === 'object' ? sports : {};
|
||||
return Object.keys(src).map((sport) => {
|
||||
const s = src[sport] || {};
|
||||
return {
|
||||
sport,
|
||||
sample: Number(s.sample) || 0,
|
||||
pct: pctOrNull((s.overall || {}).pct),
|
||||
byGrade: s.byGrade || {},
|
||||
minSample: Number(s.min_sample) || 20,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/** Client-side slice: the endpoints ignore ?sport=, so filtering happens here. */
|
||||
function sliceFor(accuracy, sport) {
|
||||
const d = accuracy && typeof accuracy === 'object' ? accuracy : {};
|
||||
if (!sport || sport === 'all') {
|
||||
const o = d.overall || {};
|
||||
return { sample: Number(o.sample) || 0, pct: pctOrNull((o.overall || {}).pct),
|
||||
byGrade: o.byGrade || {}, minSample: Number(o.min_sample) || 20 };
|
||||
}
|
||||
const s = (d.sports || {})[sport] || {};
|
||||
return { sample: Number(s.sample) || 0, pct: pctOrNull((s.overall || {}).pct),
|
||||
byGrade: s.byGrade || {}, minSample: Number(s.min_sample) || 20 };
|
||||
}
|
||||
|
||||
module.exports = { pctOrNull, tierRows, clvPanel, sportRows, sliceFor, TIER_ORDER };
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user