Item 7 — public accuracy reads the CLEAN ledger; BEAT CLOSE hidden until C4

Kev's call: the 30D accuracy surfaces must read TRUTH, not a cache that can't be
filtered. My earlier degraded-row exclusion only touched getModelAggregate
(Postgres); the public buckets/badge still read outcomeService (Redis outcome
log), which counts degraded projection-0 outcomes and has no field to filter on.

- /api/accuracy (AccuracyBadge) + /api/ledger/accuracy (buckets/ModelRecord)
  now source from the clean Postgres ledger aggregate via new
  ledgerService.getAccuracyView + accuracyBucketsFromAgg (model_value > 0
  excludes degraded rows). Same response shapes → no frontend change. Redis
  outcome log is now read by nothing public; it can age out or be rebuilt.

- BEAT CLOSE is a MEASURED-WRONG ZERO: captureClosing re-records the locked line
  as the "closing" line, so clv is flat on the whole sample and beat_close reads
  0% (comparing a number to itself). Full write-up: specs/audit-data/
  clv-capture-broken.md (the fix belongs to C4). Until then, beat_close_pct +
  clv_distribution are SUPPRESSED at the source (getModelAggregate, gated by
  clvCaptureReliable() / CLV_CAPTURE_RELIABLE=1). Every public surface already
  renders BEAT CLOSE only when non-null, so they all hide it now — no wrong zero
  anywhere. HIT RATE (real) is unaffected.

Suite 271/3261 green, web build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-17 16:08:18 -04:00
parent 36e653d695
commit 89a2977f57
7 changed files with 222 additions and 35 deletions
+2
View File
@@ -65,6 +65,7 @@ describe('getModelAggregate scoping', () => {
});
test('user scope renders percentages at n≥20 like the public record', async () => {
process.env.CLV_CAPTURE_RELIABLE = '1'; // exercise the CLV math (suppressed by default, item 7)
const rows = [
...Array.from({ length: 14 }, () => ({ outcome: 'hit', clv_result: 'beat', grade: 'A' })),
...Array.from({ length: 7 }, () => ({ outcome: 'miss', clv_result: 'faded', grade: 'B' })),
@@ -73,5 +74,6 @@ describe('getModelAggregate scoping', () => {
expect(agg.settled).toBe(21);
expect(agg.hit_pct).toBe(Math.round((14 / 21) * 100));
expect(agg.beat_close_pct).toBe(Math.round((14 / 21) * 100));
delete process.env.CLV_CAPTURE_RELIABLE;
});
});
+24
View File
@@ -206,6 +206,27 @@ describe('captureClosing — real feed values only', () => {
});
describe('getModelAggregate — never a % under min sample', () => {
// beat_close/CLV are suppressed by default (item 7 — CLV capture broken until
// C4). These tests exercise the CLV MATH, so enable the reliable flag; a
// separate test below locks the default-suppressed behavior.
beforeAll(() => { process.env.CLV_CAPTURE_RELIABLE = '1'; });
afterAll(() => { delete process.env.CLV_CAPTURE_RELIABLE; });
test('beat_close is SUPPRESSED by default until C4 (CLV capture broken)', async () => {
delete process.env.CLV_CAPTURE_RELIABLE; // default state
const sb = fakeSb();
sb._state.selectResults = [[
...Array.from({ length: 13 }, () => ({ outcome: 'hit', clv_result: 'beat' })),
...Array.from({ length: 7 }, () => ({ outcome: 'miss', clv_result: 'faded' })),
]];
sb._state.countResult = 0;
const agg = await ledger.getModelAggregate({ sb });
expect(agg.hit_pct).toBe(65); // hit rate still renders (it's real)
expect(agg.beat_close_pct).toBeNull(); // BEAT CLOSE hidden — measured-wrong
expect(agg.clv_distribution).toBeNull();
process.env.CLV_CAPTURE_RELIABLE = '1'; // restore for the rest of the block
});
test('below 20 settles → hit_pct/beat_close_pct null, counts real', async () => {
const sb = fakeSb();
sb._state.selectResults = [
@@ -296,6 +317,9 @@ describe('indexProps — prefers a book row with both sides priced', () => {
// centralized HERE (getModelAggregate) — consumers never re-derive it.
describe('getModelAggregate — clv_distribution (n>=20 gate lives in the service)', () => {
const { clvBucketIndex, CLV_BUCKETS } = ledger.__internals;
// CLV suppressed by default (item 7); enable to test the distribution math.
beforeAll(() => { process.env.CLV_CAPTURE_RELIABLE = '1'; });
afterAll(() => { delete process.env.CLV_CAPTURE_RELIABLE; });
test('below 20 settles → clv_distribution is null (never a small-sample chart)', async () => {
const sb = fakeSb();