d242b11b4b
PROMISE-AUDIT.md: every /pricing claim → verified/built/reworded. BUILT (was vapor): alt line ladder + edge ranking (same-features regrade at shifted lines, Desk-gated at the API), quarter-Kelly (engine quantile P(win) x real captured odds — either missing → no sizing), free-tier kill-condition locked previews. FIXED (was false): analyst 15/day cap vs the Founder 'Unlimited reads' promise → analyst unlimited; every '40+ factors' claim (real count: 22 named features) reworded truthfully in 7 files. VERIFIED: cascade alerts (real, wired), phi correlation, leg history, cross-book comparison, WC soccer, real-time feed. Locked by tests/unit/promiseAudit.test.js. Jest now ignores .claude/worktrees (parallel agents' suites no longer leak into runs). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86 lines
3.7 KiB
JavaScript
86 lines
3.7 KiB
JavaScript
// 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.']);
|
||
});
|
||
});
|