Item 5 — daily hero prop is a live RULE (biggest model-vs-market disagreement)

The landing hero was a static Jokic "Example" card with a name-length pick and a
hardcoded A- 73% +6.2% fallback. Now it's deterministic and live:

- heroPropService.pickHeroProp reads the pre-graded snapshot and selects the
  prop with the LARGEST |projection - line| gap among A/B grades (conviction,
  not noise) — the read where VYNDR disagrees most with the market, the card
  that makes a stranger argue. No curation, no grading (reads cache → no API
  credits). GET /api/hero-prop (backend) + repointed Next proxy.
- The card shows the disagreement EXPLICITLY: the book's line vs VYNDR's model,
  side by side (model in green), with the real grade timestamp ("Graded 2:14
  PM"). The EXAMPLE chip is gone.
- Empty slate → the MOST RECENT real graded read (flagged "LATEST READ", real
  date). Nothing cached → { available:false } and the card HIDES. No
  hand-written fallback — the Jokic card is deleted. Survives a dead night: a
  live rule shows tonight's real MLB read, never a phantom July NBA card.

7 service tests lock the rule (max-gap, A/B gate, projection/line required,
empty→recent, hidden, cross-sport). colorContract updated to the new
disagreement display. Change-affected suites 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-18 01:29:55 -04:00
parent 89a2977f57
commit 9b9aab4262
8 changed files with 345 additions and 460 deletions
+13 -4
View File
@@ -136,11 +136,20 @@ describe('GradeBadge.tsx — glow gated to A-tier', () => {
});
});
describe('LiveHeroProp.tsx — edge tone is sign-driven', () => {
describe('LiveHeroProp.tsx — the disagreement display (item 5)', () => {
// The hero card now shows the book's line vs VYNDR's model side by side (the
// largest model-vs-market gap), not a signed edge %. Model = green (our
// number, the signal), book line = neutral. No static Jokic fallback.
const src = read('components/LiveHeroProp.tsx');
it('negative edge maps to muted red, positive to green', () => {
expect(src).toMatch(/edge > 0 \? 'positive' : edge < 0 \? 'negative'/);
expect(src).toContain("tone === 'negative' ? 'var(--miss)'");
it('renders BOOK LINE vs VYNDR MODEL, model in green', () => {
expect(src).toContain('LINE'); // "{bookLabel} LINE"
expect(src).toContain('VYNDR MODEL');
expect(src).toMatch(/VYNDR MODEL[^]*?color: 'var\(--grade-a\)'/);
});
it('has no hand-written static fallback (no hardcoded Jokic example)', () => {
expect(src).not.toContain('Nikola Jokic');
expect(src).not.toContain('aria-label="Example grade"'); // the old EXAMPLE chip is gone
expect(src).not.toContain('26.5'); // the old hardcoded line
});
});