Files
vyndr/specs/audit-data/clv-capture-broken.md
T
builtbykev 89a2977f57 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>
2026-07-17 16:08:18 -04:00

2.2 KiB

CLV Capture Broken — C4 Finding (2026-07-17)

Source: Truth-Everywhere Part 2, item 7. Escalated from the "0% BEAT CLOSE" observation on the public accuracy surfaces.

The bug

Every settled ledger row has closing_line == locked_line (and closing_odds == locked_odds), so the computed clv is 0/flat on the ENTIRE sample. beat_close_pct therefore reads a fabricated-looking 0% — it is comparing a number to itself, not measuring closing-line value.

Evidence (live /api/ledger/model, 2026-07-17): sampled rows show line == closing_line, clv: null/0, clv_result: null/flat across the board.

Root cause (to fix in C4)

ledgerService.captureClosing overwrites today's unsettled rows' closing_line/closing_odds on every snapshot with the CURRENT feed values. The intent (S58) was "the last write before game start is the close." But in practice the captured value equals the locked line every time — either the lines genuinely don't move in the captured window, or captureClosing is reading the same feed field the lock came from and writing it back unchanged. Net: the "closing" column is a copy of the lock, so CLV is structurally always 0.

The fix (C4 — not done here)

Capture the REAL last line before game start into a SEPARATE field, never defaulted to the lock:

  • Record closing_line only from a distinct closing snapshot (the last feed read before first pitch/tip), and only when it actually differs from the lock — otherwise leave it null (absent, not a copy).
  • CLV = signed(locked - closing) by side, computed only when a real, distinct closing line exists. No close captured ⇒ CLV null for that row (honest), not 0.
  • Verify against a few known line moves before trusting the aggregate.

Interim (shipped this pass, item 7)

beat_close_pct and clv_distribution are SUPPRESSED at the source (getModelAggregate, gated by clvCaptureReliable() / CLV_CAPTURE_RELIABLE=1). Every public surface (ModelRecord, ledger MODEL tab, public profiles, OG images) already renders BEAT CLOSE only when non-null, so they all hide it now — no measured-wrong zero on any public surface. HIT RATE (which is real) is unaffected. Flip CLV_CAPTURE_RELIABLE=1 once C4 lands and real closes are verified.