Merge Wave 5B (wiring/data): pitcher arsenal via Baseball Savant (D4)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-13 16:04:06 -04:00
9 changed files with 681 additions and 1 deletions
@@ -0,0 +1,27 @@
import { NextRequest, NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3000';
/**
* Pitcher arsenal proxy (Wave 5B). Forwards GET
* /api/stats/pitcher/:name/arsenal to the Express stats route (Baseball Savant
* pitch mix / velo / whiff). Thin pass-through; preserves ?sport=. On any
* upstream failure it returns { found:false } so the PitcherArsenal card
* self-hides honestly rather than showing an error.
*/
export async function GET(req: NextRequest, ctx: { params: Promise<{ name: string }> }) {
const { name } = await ctx.params;
const qs = req.nextUrl.search;
try {
const upstream = await fetch(
`${BACKEND_URL}/api/stats/pitcher/${encodeURIComponent(name)}/arsenal${qs}`,
{ method: 'GET', headers: { Accept: 'application/json' } },
);
const data = await upstream.json().catch(() => ({ found: false }));
return NextResponse.json(data, { status: upstream.ok ? 200 : upstream.status });
} catch {
return NextResponse.json({ found: false }, { status: 200 });
}
}
+3
View File
@@ -1333,6 +1333,9 @@ html[data-font="readable"] .wm::after { opacity: 0.45 !important; }
/* Terminal's multi-column grid stacks on mobile. */
@media (max-width: 768px) {
.terminal-grid { grid-template-columns: 1fr !important; }
/* Pitcher-identity (Wave 5B): identity + arsenal columns stack; the arsenal
table keeps its own internal grid. */
.parsenal-grid { grid-template-columns: 1fr !important; }
}
/* ── Session 59 (work-order 3.2) — overflow containment at 390px ──────── */
+8
View File
@@ -8,6 +8,7 @@ import ArchetypeBadge from '@/components/vyndr/ArchetypeBadge';
import ArchetypeBlend from '@/components/vyndr/ArchetypeBlend';
import ModelRecord from '@/components/vyndr/ModelRecord';
import PlayerStreaks from '@/components/vyndr/PlayerStreaks';
import PitcherArsenal from '@/components/vyndr/PitcherArsenal';
import { archetypeInfo } from '@/lib/archetypes';
import { initials, sportLabel, dnaRows, blendReadout } from '@/lib/playerProfileAdapter';
@@ -201,6 +202,13 @@ export default function PlayerProfilePage() {
</div>
)}
{/* C2. PITCHER IDENTITY (Wave 5B) — Baseball Savant arsenal lens. MLB only;
the component (heading included) SELF-HIDES for non-pitchers / when
Savant has no arsenal. Context, not a graded market value. */}
{p.sport === 'mlb' && (
<PitcherArsenal name={p.player} sport={p.sport} heading pitcher={{ name: p.player, team: p.team }} />
)}
{/* D. ACTIVE PROPS */}
{p.activeProps?.length > 0 && (
<>