// Session 62 (A1-S1) — the feature-promise locks. Every pricing claim that // was vapor is now real, and these tests keep it that way. const { quarterKelly, americanToDecimal } = require('../../src/utils/kelly'); const { applyTierGating } = require('../../src/utils/tierGating'); const { getScanLimit } = require('../../src/config/tiers'); 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'); describe('quarter-Kelly (pricing: "Quarter-Kelly sizing recommendations")', () => { test('computes from real probability × real odds', () => { // p=0.55 at -110: b=0.909, f=(0.909*0.55-0.45)/0.909 ≈ 0.055 → quarter ≈ 1.4% const k = quarterKelly(0.55, -110); expect(k.quarter).toBeCloseTo(0.0138, 3); expect(k.pct).toBeCloseTo(1.4, 1); }); test('no edge → null (Kelly says: no bet, never a fabricated size)', () => { expect(quarterKelly(0.5, -110)).toBeNull(); }); test('missing inputs → null, never defaults', () => { expect(quarterKelly(null, -110)).toBeNull(); expect(quarterKelly(0.6, null)).toBeNull(); expect(quarterKelly(1.2, -110)).toBeNull(); }); test('american conversion both signs', () => { expect(americanToDecimal(-110)).toBeCloseTo(1.909, 3); expect(americanToDecimal(150)).toBeCloseTo(2.5, 3); }); }); describe('Desk-only gate at the API (alt ladder + kelly never leave the server below Desk)', () => { const RESULT = { grade: 'A', confidence: 70, alt_lines: [{ line: 1.5, grade: 'A' }], kelly: { pct: 1.4 }, reasoning: { summary: 'x' }, kill_conditions_triggered: [] }; test('desk keeps both', () => { const g = applyTierGating(RESULT, 'desk'); expect(g.alt_lines).toBeTruthy(); expect(g.kelly).toBeTruthy(); }); test('analyst loses both, keeps reasoning', () => { const g = applyTierGating(RESULT, 'analyst'); expect(g.alt_lines).toBeUndefined(); expect(g.kelly).toBeUndefined(); expect(g.reasoning.summary).toBe('x'); }); test('free loses both AND gets locked reasoning', () => { const g = applyTierGating(RESULT, 'free'); expect(g.alt_lines).toBeUndefined(); expect(g.kelly).toBeUndefined(); expect(g.reasoning.locked).toBe(true); }); }); describe('read limits match the pricing page', () => { test('free 3/day · africa 10/day · analyst unlimited (Founder promise) · desk unlimited', () => { expect(getScanLimit('free')).toBe(3); expect(getScanLimit('africa')).toBe(10); expect(getScanLimit('analyst')).toBe(Infinity); expect(getScanLimit('desk')).toBe(Infinity); }); }); describe('no unverifiable factor-count claims anywhere', () => { it.each([ 'components/Pricing.tsx', 'app/help/page.tsx', 'app/about/page.tsx', 'app/upgrade/desk/page.tsx', 'components/GradeCard.tsx', 'components/vyndr/GradeResultCard.tsx', 'components/vyndr/ProcessingGrade.tsx', ])('%s carries no "40+" claim', (rel) => { expect(read(rel)).not.toContain('40+'); }); }); describe('free tier sees the promised LOCKED PREVIEW of kill conditions', () => { const { mapScanToGradeResult } = require('../../web/src/lib/gradeAdapter'); test('free gets code-only teaser chips; analyst gets reasons', () => { const input = { player: 'X', stat: 'hits', line: 1.5, direction: 'over', grade: 'B', kill_conditions: [{ code: 'TRAP', reason: 'Multiple trap signals firing.' }], }; const free = mapScanToGradeResult({ ...input, tier: 'free' }); expect(free.killConditions).toEqual(['TRAP — upgrade to see details']); const paid = mapScanToGradeResult({ ...input, tier: 'analyst' }); expect(paid.killConditions).toEqual(['Multiple trap signals firing.']); }); });