Session 50: Complete Parlay Lab (2215 tests)

Correlation-aware combined parlay grading — the Desk-tier differentiator.

- Correlation model (parlayService.js, added to S28 funcs): correlationScore
  (game-aware 0.7/0.4/0.2/0.0), combinedGrade (avg penalized by avgCorr*0.5),
  estimatedPayout (fair-odds product * (1-avgCorr) discount), correlationWarning,
  gradeParlay.
- POST /api/parlay/grade (public, 2-6 legs) -> {combined,correlation,payout,legs}.
  Fixed the Next proxy (was forwarding to /api/scan/parlay).
- ParlayContext: legs gained team/game/archetype; tier-aware maxLegs; auto-grades
  the slip (debounced) when legs>=2 -> live combined/correlation/payout; hasLeg/
  legKey/atCap.
- "+" button on every graded prop: StatStrip onAddLeg/isLegActive, wired by
  vyndr/GameCard via useParlay (builds leg w/ team + game). GradeResultCard feeds
  the same context from the scan page.
- ParlayPanel (replaces legacy ParlayTray): bottom slide-up w/ legs, combined
  grade, correlation warning, est payout, CLEAR ALL + floating leg-count badge.
  Tier-gated: free 2 legs (payout blurred -> Desk upsell), Analyst 4, Desk 6.

Backend 2185 -> 2215 tests (+30), 187 suites. Web build clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-19 11:25:14 -04:00
parent 3b47b783dc
commit f1956dc953
14 changed files with 706 additions and 48 deletions
+3 -2
View File
@@ -9,7 +9,8 @@ import Footer from '@/components/Footer';
import AuthGate from '@/components/AuthGate';
import HashRedirect from '@/components/vyndr/HashRedirect';
import GlobalHosts from '@/components/vyndr/GlobalHosts';
import ParlayTray from '@/components/ParlayTray';
// Session 50 — the Parlay Lab supersedes the legacy ParlayTray.
import ParlayPanel from '@/components/vyndr/ParlayPanel';
import BottomTabBar from '@/components/BottomTabBar';
import InstallPrompt from '@/components/InstallPrompt';
import PushPrompt from '@/components/PushPrompt';
@@ -141,7 +142,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<main style={{ paddingTop: 124, minHeight: '100vh', paddingBottom: 80 }}>{children}</main>
</AuthGate>
<Footer />
<ParlayTray />
<ParlayPanel />
<BottomTabBar />
<InstallPrompt />
<PushPrompt />