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
+22 -1
View File
@@ -2,7 +2,8 @@ const express = require('express');
const { getSupabaseServiceClient } = require('../utils/supabase');
const { getStatFilters } = require('../config/statFilters');
const { createRateLimit } = require('../middleware/rateLimit');
const { getPlayerIntel, getLeaders } = require('../services/playerIntelService');
const { getPlayerIntel, getLeaders, sanitizePlayerName } = require('../services/playerIntelService');
const { getPitcherArsenal } = require('../services/adapters/savantAdapter');
const depthChart = require('../services/depthChartService');
const router = express.Router();
@@ -139,6 +140,26 @@ router.get('/player/:name', intelLimit, async (req, res) => {
}
});
// GET /pitcher/:name/arsenal?sport=mlb — Baseball Savant pitch arsenal (Wave 5B).
// Pitch mix % + velo + whiff% (the mockup's PITCHER IDENTITY lens). FREE Statcast
// source; the adapter resolves name→MLBAM id + caches 24h. MLB only — any other
// sport (or a miss) returns { found:false } and the card self-hides. Never fabricates.
router.get('/pitcher/:name/arsenal', intelLimit, async (req, res) => {
try {
const sport = String(req.query.sport || 'mlb').toLowerCase();
if (sport !== 'mlb') {
return res.set(MISSION_HEADER).json({ found: false, reason: 'arsenal is MLB-only' });
}
const name = sanitizePlayerName(req.params.name);
const arsenal = await getPitcherArsenal(name);
res.set(MISSION_HEADER).json(arsenal || { found: false });
} catch (err) {
console.error('[stats/pitcher/arsenal]', err.message);
// Honesty: an error is an ABSENT arsenal, not a fabricated one. Card self-hides.
res.set(MISSION_HEADER).json({ found: false });
}
});
// GET /leaders?sport=mlb&stat=hits&limit=10 — tonight's stat leaders (top
// graded props by confidence) for the Terminal / Stats Explorer.
router.get('/leaders', intelLimit, async (req, res) => {