c8790fde55
Work-order Phase 0 (Jul 10 live audit): every fabricated UI element deleted
or rewired to real data. Deletion sprint — no new product features.
0.1 Fake NBA game: root cause was scheduleService fetching the ESPN
scoreboard with no ?dates= param or date filter — off-season ESPN
returns the NEAREST slate (Jun 13 NYK@SA Finals rendered as tonight).
Now pinned to the requested ET date + defensive filter; undated events
dropped. Honest month-aware per-sport empty states (lib/emptyState.js).
0.2 Fake header counters: liveTick stripped to a bare 1s pulse (the
auto-incrementing "247 graded", sin-driven brain-%, aPlus/cascades are
dead). New GET /api/snapshot/summary (cache-only, before /:sport) +
Next proxy; HeartbeatBar shows the real graded count and SYNC =
elapsed since the last pipeline run (amber past 5 min).
0.3 Ticker: hardcoded fallback items deleted (real snapshot exhaust only);
MOVE kept — computeLineDeltas is real movement. <4 real items → no
ticker; bar publishes --ticker-h so the fixed header collapses cleanly.
0.4 /terminal retired: route redirects to /dashboard; VVI/injury-wire/
leaders layouts preserved unrouted as §12 content-engine templates.
Nav PRIMARY = Slate/Scan/Ledger; BottomTabBar Terminal→Explore; PWA
shortcut Terminal→Ledger; #terminal alias → /dashboard.
0.5 ›Query nav pill deleted (duplicate /scan link).
Backend 2289 → 2309 tests (199 suites), web build exit 0.
Spec: specs/phase-0-kill-the-lies.md. Next: work-order Phase 1 (ledger
persistence + settlement).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
160 lines
6.6 KiB
JavaScript
160 lines
6.6 KiB
JavaScript
// VYNDR 2.0 — Phase D core screens (Session 35). Adapter LOGIC is exercised
|
||
// directly via the CommonJS gradeAdapter; the .tsx screens/components are
|
||
// asserted against source text (the plain-JS Jest config has no TS transform).
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
|
||
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
|
||
const adapter = require('../../web/src/lib/gradeAdapter');
|
||
|
||
describe('Phase D — grade adapter (engine → §7 contract)', () => {
|
||
const base = {
|
||
player: 'Nikola Jokic', sport: 'NBA', stat: 'home_runs', line: 26.5, direction: 'over',
|
||
grade: 'A', projection: 29.4, confidence: 67, sample_size: 34,
|
||
factors: { matchup: 'soft interior', trend: 'hot', usage: 'up', rest: 'rested', pace: 'fast' },
|
||
kill_conditions: [{ code: 'BLOWOUT', reason: 'Blowout risk caps minutes' }],
|
||
alt_lines: [{ line: 24.5, grade: 'A+' }, { line: 28.5, grade: 'B' }],
|
||
};
|
||
|
||
it('maps direction → side and humanizes the stat', () => {
|
||
const out = adapter.mapScanToGradeResult({ ...base, tier: 'desk' });
|
||
expect(out.side).toBe('Over');
|
||
expect(out.stat).toBe('Home Runs');
|
||
expect(out.player).toBe('Nikola Jokic');
|
||
});
|
||
|
||
it('computes a signed percentage edge from projection vs line', () => {
|
||
expect(adapter.computeEdge(29.4, 26.5, 'over')).toBeCloseTo(10.9, 1);
|
||
expect(adapter.computeEdge(24, 26.5, 'over')).toBeLessThan(0);
|
||
expect(adapter.computeEdge(24, 26.5, 'under')).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('flags phosphor confirmed only for A/A+ with strong support', () => {
|
||
expect(adapter.isPhosphorConfirmed('A+', 80, 40)).toBe(true);
|
||
expect(adapter.isPhosphorConfirmed('A', 50, 35)).toBe(true); // sample carries it
|
||
expect(adapter.isPhosphorConfirmed('B', 90, 90)).toBe(false);
|
||
expect(adapter.isPhosphorConfirmed('A', 40, 5)).toBe(false);
|
||
});
|
||
|
||
it('FREE tier teases 3 signals, hides kill conditions and the alt ladder', () => {
|
||
const out = adapter.mapScanToGradeResult({ ...base, tier: 'free' });
|
||
expect(out.signals.length).toBe(3);
|
||
expect(out.killConditions).toEqual([]);
|
||
expect(out.altLadder).toEqual([]);
|
||
});
|
||
|
||
it('ANALYST tier shows kill conditions + all signals, but no alt ladder', () => {
|
||
const out = adapter.mapScanToGradeResult({ ...base, tier: 'analyst' });
|
||
expect(out.signals.length).toBe(5);
|
||
expect(out.killConditions).toEqual(['Blowout risk caps minutes']);
|
||
expect(out.altLadder).toEqual([]);
|
||
});
|
||
|
||
it('DESK tier unlocks the alt line ladder', () => {
|
||
const out = adapter.mapScanToGradeResult({ ...base, tier: 'desk' });
|
||
expect(out.altLadder).toEqual([{ line: 24.5, grade: 'A+' }, { line: 28.5, grade: 'B' }]);
|
||
});
|
||
|
||
it('defaults missing fields safely', () => {
|
||
const out = adapter.mapScanToGradeResult({});
|
||
expect(out.grade).toBe('—');
|
||
expect(out.side).toBe('Over');
|
||
expect(out.signals).toEqual([]);
|
||
expect(out.books).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('Phase D — GradeResultCard (the core moment)', () => {
|
||
const src = read('components/vyndr/GradeResultCard.tsx');
|
||
it('renders the grade letter at hero size (92–116px)', () => {
|
||
expect(src).toContain('116');
|
||
expect(src).toContain('92');
|
||
});
|
||
it('uses the intel-surface for the grade hero zone + grade-reveal', () => {
|
||
expect(src).toContain('intel-surface');
|
||
expect(src).toContain('grade-reveal');
|
||
});
|
||
it('highlights the best book with green tint + green left border', () => {
|
||
expect(src).toContain('rgba(0,212,160,.13)');
|
||
expect(src).toContain("borderLeft: b.best ? '2px solid var(--g-a)'");
|
||
});
|
||
it('renders kill conditions with amber border, gated on non-empty', () => {
|
||
expect(src).toContain('hasKill');
|
||
expect(src).toContain('rgba(255,179,71,.4)');
|
||
expect(src).toContain('KILL CONDITIONS');
|
||
});
|
||
it('self-hides books / alt ladder when empty', () => {
|
||
expect(src).toContain('hasBooks');
|
||
expect(src).toContain('hasAlt');
|
||
});
|
||
});
|
||
|
||
describe('Phase D — ProcessingGrade', () => {
|
||
const src = read('components/vyndr/ProcessingGrade.tsx');
|
||
it('plays the factor-ignite + proc-scan sequence then reveals the card', () => {
|
||
expect(src).toContain('factor-ignite');
|
||
expect(src).toContain('proc-scan');
|
||
expect(src).toContain('<GradeResultCard');
|
||
});
|
||
});
|
||
|
||
describe('Phase D — GameCard (Bloomberg best/worst lines)', () => {
|
||
const src = read('components/vyndr/GameCard.tsx');
|
||
it('renders team abbreviations', () => {
|
||
expect(src).toContain('g.away.abbr');
|
||
expect(src).toContain('g.home.abbr');
|
||
});
|
||
it('best line = green tint + green border; worst = subtle red', () => {
|
||
expect(src).toContain('rgba(0,212,160,.13)');
|
||
expect(src).toContain('rgba(255,82,82,.07)');
|
||
});
|
||
it('shows a live-dot for live games and grades props with GradeBadge', () => {
|
||
expect(src).toContain('live-dot');
|
||
expect(src).toContain('<GradeBadge');
|
||
});
|
||
});
|
||
|
||
describe('Phase D — ClaimMeter', () => {
|
||
const src = read('components/vyndr/ClaimMeter.tsx');
|
||
it('renders the amber founder-seat scarcity bar', () => {
|
||
expect(src).toContain('CLAIMED');
|
||
expect(src).toContain('var(--amber)');
|
||
});
|
||
});
|
||
|
||
// Session 57 (Phase 0) — the Terminal surface was fabricated sample data
|
||
// (Finals-era NBA content rendered as live intelligence). The route now
|
||
// redirects to /dashboard; the layouts survive UNROUTED as §12 templates.
|
||
describe('Phase 0 — Terminal retired (fabricated surface)', () => {
|
||
it('the route redirects to the slate instead of rendering fake intel', () => {
|
||
const src = read('app/terminal/page.tsx');
|
||
expect(src).toContain("redirect('/dashboard')");
|
||
expect(src).not.toContain('INJURY_WIRE');
|
||
expect(src).not.toContain('Wembanyama');
|
||
});
|
||
it('preserves the VVI / cascade / leaders layouts as unrouted templates', () => {
|
||
const tpl = read('components/intel/TerminalTemplates.tsx');
|
||
expect(tpl).toContain('NOT ROUTED');
|
||
expect(tpl).toContain('VOLATILITY INDEX');
|
||
expect(tpl).toContain('INJURY WIRE');
|
||
expect(tpl).toContain('GRADEABLE LEADERS');
|
||
});
|
||
});
|
||
|
||
describe('Phase D — scan + landing wiring', () => {
|
||
it('scan renders the new ProcessingGrade card via the adapter (legacy GradeCard removed)', () => {
|
||
const src = read('app/scan/page.tsx');
|
||
expect(src).toContain('ProcessingGrade');
|
||
expect(src).toContain('mapScanToGradeResult');
|
||
expect(src).not.toContain("from '@/components/GradeCard'");
|
||
});
|
||
it('scan keeps sportsbook deep-links safe (noopener noreferrer)', () => {
|
||
expect(read('app/scan/page.tsx')).toContain('noopener noreferrer');
|
||
});
|
||
it('landing mounts the ClaimMeter', () => {
|
||
expect(read('app/page.tsx')).toContain('ClaimMeter');
|
||
});
|
||
});
|