S1 (a1): feature-promise audit — no claim survives unverified

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>
This commit is contained in:
Kev
2026-07-11 14:24:41 -04:00
parent e4d2e79f95
commit d242b11b4b
20 changed files with 296 additions and 30 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ import { markReadComplete } from '@/lib/reads';
// Short, plain-English explanations rendered when Explain Like I'm New is on.
// Each key maps to one piece of data we surface on this card.
const EXPLANATIONS = {
grade: "Our overall confidence. A-minus means we estimate about a 76% chance this prop hits, based on 40+ factors.",
grade: "Our overall confidence. A-minus means we estimate about a 76% chance this prop hits, based on every factor the model weighs for the prop.",
projection: "What our model predicts the player will actually do tonight for this stat.",
line: "The number the sportsbook set. The player needs to go over or under it.",
overUnder: 'Over = the player needs MORE than the line. Under = LESS.',
+6 -6
View File
@@ -36,8 +36,8 @@ const TIERS: TierConfig[] = [
'Confidence indicator',
],
locked: [
'Factor analysis (blurred)',
'Kill conditions (blurred)',
'Factor analysis (3-signal preview)',
'Kill conditions (locked preview)',
'Alt line ladder (locked)',
],
highlight: false,
@@ -54,13 +54,13 @@ const TIERS: TierConfig[] = [
cta: 'Unlock Africa Pricing',
features: [
'10 reads per day',
'Full factor analysis (40+ signals)',
'Full factor analysis — every signal the model weighs',
'Kill conditions surfaced inline',
'Grade + reasoning visible',
'World Cup soccer intelligence',
],
locked: [
'Cascade alerts (Analyst+)',
'Scratch cascade alerts (Analyst+)',
'Alt line ladder (Desk only)',
],
highlight: false,
@@ -76,9 +76,9 @@ const TIERS: TierConfig[] = [
cta: 'Lock Founder Price',
features: [
'Unlimited reads',
'Full factor analysis (40+ signals)',
'Full factor analysis — every signal the model weighs',
'Kill conditions surfaced inline',
'Cascade alerts when lineups shift',
'Scratch cascade alerts when players are pulled',
'Parlay leg history with grades',
'Sportsbook deep links',
],
+17 -4
View File
@@ -24,7 +24,9 @@ export interface GradeResultData {
signals: string[];
killConditions?: string[];
books: Array<{ name: string; line: number; odds: string; best?: boolean }>;
altLadder?: Array<{ line: number; grade: string }>;
altLadder?: Array<{ line: number; grade: string; edge?: number | null; base?: boolean }>;
// Session 62 (A1-S1) — quarter-Kelly sizing (Desk only; real p × real odds).
kelly?: { pct: number; quarter: number; odds: string };
// Session 42 — Player Intelligence additions (all optional; self-hide).
archetypeBlend?: Array<{ archetype: string; weight: number }>;
propDNA?: { reliable: string[]; volatile: string[] };
@@ -183,7 +185,7 @@ export default function GradeResultCard({
{/* 5. SIGNAL BREAKDOWN */}
{d.signals.length > 0 && (
<div style={{ padding: '16px 20px' }}>
<SectionHead style={{ marginBottom: 12 }}>SIGNAL BREAKDOWN · {d.signals.length} OF 40+ FACTORS</SectionHead>
<SectionHead style={{ marginBottom: 12 }}>SIGNAL BREAKDOWN · {d.signals.length} FACTORS SHOWN</SectionHead>
<div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
{d.signals.map((s, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 11 }}>
@@ -266,12 +268,23 @@ export default function GradeResultCard({
</SectionHead>
<div style={{ display: 'flex', gap: 7, flexWrap: 'wrap' }}>
{d.altLadder!.map((a, i) => (
<div key={i} style={{ flex: '1 1 0', minWidth: 64, textAlign: 'center', padding: '9px 6px', background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 6 }}>
<div className="mono" style={{ fontSize: 12, color: 'var(--text-1)', marginBottom: 5 }}>{a.line}</div>
<div key={i} style={{ flex: '1 1 0', minWidth: 64, textAlign: 'center', padding: '9px 6px', background: 'var(--bg-2)', border: a.base ? '1px solid var(--border-hi)' : '1px solid var(--border)', borderRadius: 6 }}>
<div className="mono" style={{ fontSize: 12, color: a.base ? 'var(--text-0)' : 'var(--text-1)', marginBottom: 5 }}>{a.line}{a.base ? ' •' : ''}</div>
<div className="mono" style={{ fontSize: 17, fontWeight: 800, color: gradeColor(a.grade) }}>{a.grade}</div>
{a.edge != null && (
<div className="mono" style={{ fontSize: 10, color: Number(a.edge) >= 0 ? 'var(--g-a)' : 'var(--miss)', marginTop: 3 }}>{Number(a.edge) >= 0 ? '+' : ''}{a.edge}%</div>
)}
</div>
))}
</div>
{/* Quarter-Kelly (Desk) — real probability × the real captured odds. */}
{d.kelly && (
<div className="mono" style={{ marginTop: 10, fontSize: 12, color: 'var(--text-1)' }}>
<span className="label" style={{ fontSize: 9.5, marginRight: 8 }}>QTR-KELLY</span>
<span style={{ color: 'var(--g-a)', fontWeight: 700 }}>{d.kelly.pct}% of bankroll</span>
<span style={{ color: 'var(--text-2)' }}> · at {d.kelly.odds}</span>
</div>
)}
</div>
)}
+1 -1
View File
@@ -106,7 +106,7 @@ export default function ProcessingGrade({ data, replayKey = 0, onShare, onAddToP
</div>
<div style={{ marginLeft: 'auto', textAlign: 'right' }}>
<div className="mono" style={{ fontSize: 30, fontWeight: 800, color: 'var(--g-ap)', textShadow: '0 0 16px rgba(0,255,184,.5)' }}>{pct}%</div>
<div className="mono" style={{ fontSize: 9.5, letterSpacing: '0.12em', color: 'rgba(232,255,244,.5)' }}>WEIGHING 40+ FACTORS</div>
<div className="mono" style={{ fontSize: 9.5, letterSpacing: '0.12em', color: 'rgba(232,255,244,.5)' }}>WEIGHING THE FACTORS</div>
</div>
</div>