Session 7d: Audit fixes - rate limiting, error leak, parallel parlays, analyze cache, bundle analyzer
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
// DEPRECATED — Session 7c audit flagged this for unification with the
|
||||
// new Engine 1 (src/services/intelligence/engine1.js). Session 7d deferred
|
||||
// the rewire because the output shapes are incompatible:
|
||||
// - Legacy: 4-letter grade (A|B|C|D), 0-100 confidence, kill_conditions,
|
||||
// reasoning.steps. Consumed by /api/analyze, /api/scan, /api/bets
|
||||
// and the frontend GradeCard + DemoScan components.
|
||||
// - New: 11-step grade (F..A+), 0-1 confidence, factors array. Consumed
|
||||
// by /api/grading/pipeline only.
|
||||
// Migration plan in docs/SYSTEM-MANIFEST.md §8 ARCH-1. Do not extend this
|
||||
// file — new features land in engine1.js. Remove this file when the legacy
|
||||
// route set retires.
|
||||
function computeGrade(stepResults) {
|
||||
const {
|
||||
seasonDelta,
|
||||
|
||||
@@ -31,6 +31,12 @@ function inactive(reason) {
|
||||
// Normalize player names for matching across data sources. ParlayAPI may
|
||||
// emit "Brunson, Jalen" while ESPN emits "Jalen Brunson" — strip case,
|
||||
// punctuation, suffixes, and collapse whitespace so equivalence works.
|
||||
//
|
||||
// DUP-1 (Session 7c): a near-identical implementation lives in
|
||||
// scripts/populate-player-ids.js. The script's variant keeps digits
|
||||
// (some legacy roster fields encode jersey numbers); this one strips
|
||||
// them because trap matches go by player name only. If the script
|
||||
// stops needing digits, consolidate to a shared util.
|
||||
function normalizeName(name) {
|
||||
if (!name) return '';
|
||||
return String(name)
|
||||
|
||||
@@ -23,12 +23,22 @@ async function scanParlay(user, legs) {
|
||||
}
|
||||
}
|
||||
|
||||
// Analyze all legs
|
||||
const legResults = [];
|
||||
for (const leg of legs) {
|
||||
const result = await analyzeProp(leg);
|
||||
legResults.push(result);
|
||||
}
|
||||
// PERF-2 (Session 7d): analyze legs in parallel. Each call is an
|
||||
// independent upstream lookup, so a 6-leg parlay is ~6x faster here
|
||||
// than the old sequential loop. allSettled preserves leg order and
|
||||
// lets a single failed leg surface as an error stub instead of
|
||||
// crashing the whole parlay.
|
||||
const settled = await Promise.allSettled(legs.map((leg) => analyzeProp(leg)));
|
||||
const legResults = settled.map((s, i) => {
|
||||
if (s.status === 'fulfilled') return s.value;
|
||||
return {
|
||||
...legs[i],
|
||||
error: s.reason?.message || 'analysis_failed',
|
||||
grade: 'F',
|
||||
confidence: 0,
|
||||
reasoning: { summary: 'Analysis failed for this leg.' },
|
||||
};
|
||||
});
|
||||
|
||||
// Fetch odds data for correlation detection (spreads, game context)
|
||||
let spreads = [];
|
||||
@@ -81,28 +91,30 @@ async function scanParlay(user, legs) {
|
||||
correlationFlags
|
||||
);
|
||||
|
||||
// Write to database
|
||||
const pickIds = [];
|
||||
for (const leg of legResults) {
|
||||
const { data: pick, error } = await supabase
|
||||
// PERF-2 (Session 7d): one batched insert for every leg's pick row
|
||||
// instead of N sequential inserts. Supabase preserves insert order in
|
||||
// the returned data array so pickIds line up with legResults.
|
||||
const pickRows = legResults.map((leg) => ({
|
||||
user_id: user.id,
|
||||
player: leg.player,
|
||||
stat_type: leg.stat_type,
|
||||
line: leg.line,
|
||||
book: leg.book || 'unknown',
|
||||
direction: leg.direction,
|
||||
grade: leg.grade,
|
||||
edge_pct: leg.edge_pct,
|
||||
reasoning: leg.reasoning?.summary || '',
|
||||
kill_conditions: (leg.kill_conditions_triggered || []).map((k) => k.code),
|
||||
confidence: leg.confidence,
|
||||
}));
|
||||
let pickIds = [];
|
||||
if (pickRows.length > 0) {
|
||||
const { data: picksData, error: picksErr } = await supabase
|
||||
.from('picks')
|
||||
.insert({
|
||||
user_id: user.id,
|
||||
player: leg.player,
|
||||
stat_type: leg.stat_type,
|
||||
line: leg.line,
|
||||
book: leg.book || 'unknown',
|
||||
direction: leg.direction,
|
||||
grade: leg.grade,
|
||||
edge_pct: leg.edge_pct,
|
||||
reasoning: leg.reasoning?.summary || '',
|
||||
kill_conditions: (leg.kill_conditions_triggered || []).map((k) => k.code),
|
||||
confidence: leg.confidence,
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (pick) pickIds.push(pick.id);
|
||||
.insert(pickRows)
|
||||
.select('id');
|
||||
if (picksErr) console.warn('[VYNDR] picks batch insert failed:', picksErr.message);
|
||||
pickIds = (picksData || []).map((p) => p.id);
|
||||
}
|
||||
|
||||
// Write scan session
|
||||
|
||||
Reference in New Issue
Block a user