9b9aab4262
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>
86 lines
3.9 KiB
JavaScript
86 lines
3.9 KiB
JavaScript
'use strict';
|
|
|
|
// Item 5 (Truth-Everywhere Part 2) — the daily hero prop is a deterministic
|
|
// live RULE: largest |projection - line| gap among A/B grades. Empty slate →
|
|
// most recent real read. Nothing → hidden.
|
|
|
|
const { pickHeroProp, __internals } = require('../../src/services/heroPropService');
|
|
|
|
function cacheFrom(map) {
|
|
return async (key) => (key in map ? map[key] : null);
|
|
}
|
|
const grade = (o) => ({
|
|
player_name: o.player, stat_type: o.stat, line: o.line, projection: o.proj,
|
|
direction: o.dir || 'over', grade: o.grade, book: o.book || 'dk',
|
|
gradedAt: { line: o.line, odds: -110, timestamp: o.ts || '2026-07-17T19:00:00Z' },
|
|
});
|
|
|
|
describe('pickHeroProp', () => {
|
|
test('picks the LARGEST |projection - line| gap among A/B grades', async () => {
|
|
const cacheGet = cacheFrom({
|
|
'snapshot:mlb:latest': { grades: [
|
|
grade({ player: 'Small Gap', stat: 'hits', line: 0.5, proj: 0.6, grade: 'A' }), // gap .1
|
|
grade({ player: 'Big Gap', stat: 'strikeouts', line: 6.5, proj: 9.0, grade: 'B' }), // gap 2.5
|
|
] },
|
|
});
|
|
const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] });
|
|
expect(hero.available).toBe(true);
|
|
expect(hero.player).toBe('Big Gap');
|
|
expect(hero.gap).toBe(2.5);
|
|
expect(hero.is_recent).toBe(false);
|
|
expect(hero.graded_at).toBeTruthy(); // real timestamp
|
|
expect(hero.line).toBe(6.5); // the book number
|
|
expect(hero.projection).toBe(9.0); // the model number
|
|
});
|
|
|
|
test('C/D/F grades are NOT eligible (conviction gate)', async () => {
|
|
const cacheGet = cacheFrom({
|
|
'snapshot:mlb:latest': { grades: [
|
|
grade({ player: 'Huge Gap C', stat: 'hits', line: 0.5, proj: 3.0, grade: 'C' }), // gap 2.5 but C
|
|
grade({ player: 'Real A', stat: 'hits', line: 0.5, proj: 0.9, grade: 'A' }), // gap .4
|
|
] },
|
|
});
|
|
const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] });
|
|
expect(hero.player).toBe('Real A'); // the C is excluded despite a bigger gap
|
|
});
|
|
|
|
test('a prop with no projection or no line is not a candidate (never gap on 0)', async () => {
|
|
const cacheGet = cacheFrom({
|
|
'snapshot:mlb:latest': { grades: [
|
|
{ player_name: 'No Proj', stat_type: 'hits', line: 0.5, projection: 0, grade: 'A', gradedAt: { timestamp: '2026-07-17T19:00:00Z' } },
|
|
grade({ player: 'Valid', stat: 'hits', line: 1.5, proj: 2.2, grade: 'B' }),
|
|
] },
|
|
});
|
|
const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] });
|
|
expect(hero.player).toBe('Valid');
|
|
});
|
|
|
|
test('empty A/B slate → MOST RECENT real graded read (any grade), flagged', async () => {
|
|
const cacheGet = cacheFrom({
|
|
'snapshot:mlb:latest': { grades: [
|
|
grade({ player: 'Older C', stat: 'hits', line: 0.5, proj: 0.4, grade: 'C', ts: '2026-07-17T14:00:00Z' }),
|
|
grade({ player: 'Newer C', stat: 'hits', line: 0.5, proj: 0.3, grade: 'C', ts: '2026-07-17T19:00:00Z' }),
|
|
] },
|
|
});
|
|
const hero = await pickHeroProp({ cacheGet, sports: ['mlb'] });
|
|
expect(hero.available).toBe(true);
|
|
expect(hero.is_recent).toBe(true);
|
|
expect(hero.player).toBe('Newer C'); // most recent by timestamp
|
|
});
|
|
|
|
test('nothing cached → { available:false } (card hides, no fabrication)', async () => {
|
|
const hero = await pickHeroProp({ cacheGet: cacheFrom({}), sports: ['mlb', 'nba'] });
|
|
expect(hero).toEqual({ available: false });
|
|
});
|
|
|
|
test('picks across sports (max gap wins regardless of sport)', async () => {
|
|
const cacheGet = cacheFrom({
|
|
'snapshot:mlb:latest': { grades: [grade({ player: 'MLB', stat: 'hits', line: 0.5, proj: 0.9, grade: 'A' })] }, // .4
|
|
'snapshot:wnba:latest': { grades: [grade({ player: 'WNBA', stat: 'points', line: 18.5, proj: 24.0, grade: 'B' })] }, // 5.5
|
|
});
|
|
const hero = await pickHeroProp({ cacheGet, sports: ['mlb', 'wnba'] });
|
|
expect(hero.player).toBe('WNBA');
|
|
expect(hero.sport).toBe('wnba');
|
|
});
|
|
});
|