Session 43: Data pipeline + audit fixes + depth chart foundation (2045 tests)
P0 fixes + wiring real data into the S42 Player Intelligence architecture. - P0 dropdown z-index: the nav's backdrop-filter stacking context let the Ticker/HeartbeatBar paint over the avatar/More dropdowns and eat clicks. nav now position:relative zIndex:2; menus zIndex:100. Avatar Settings -> /settings. - Real MLB stats: mlbStatsAdapter.searchPlayer + getPlayerStats (name->id-> season+gamelog). playerIntelService.resolvePlayerStats normalizes into the archetype classifier; getPlayerIntel returns found:true + real season + archetype classified from real stats. NBA via nbaStatsClient (degrades). - Game cards: slateAdapter.groupPropsByPlayer (playerStrips, name once) + mapPitchers (MLB probables), folded into mapScheduleToGameCards. Legacy GameCard line grid renders BookChip (brand colors) not grey text. - Grade card intel: analyzeViaEngine1.buildIntelFields computes stat-context + form/usage/matchup/rest from the existing feature vector (zero extra I/O); gradeAdapter lights up the card sections. Archetype deferred (needs season line at grade time). - Depth chart foundation: depthChartService (getLineup/getDepthChart/ getCascadeProjection) + /api/stats/lineup|depth|cascade, graceful + injectable. - Mobile: player hero name overflow-wrap + 24px on <=640px (was clipping). Backend 2011 -> 2045 tests (+34), 163 suites. Web build clean (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -265,6 +265,55 @@ function fallbackLegacyResult(rawProp, errors) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Form score (0..100) from recent-vs-baseline averages (Session 43). Hot
|
||||
* (l5 > l20) trends above 70; cold below. undefined when there's no recent avg.
|
||||
*/
|
||||
function computeFormScore(features = {}) {
|
||||
const l5 = features.l5_avg;
|
||||
if (!Number.isFinite(l5)) return undefined;
|
||||
const base = Number.isFinite(features.l20_avg) ? features.l20_avg
|
||||
: Number.isFinite(features.l10_avg) ? features.l10_avg : null;
|
||||
if (base == null || base === 0) return 75;
|
||||
const ratio = l5 / base;
|
||||
return Math.round(Math.max(40, Math.min(99, 70 + (ratio - 1) * 60)));
|
||||
}
|
||||
|
||||
function matchupGradeFromRank(rank) {
|
||||
if (!Number.isFinite(rank)) return undefined;
|
||||
if (rank >= 0.66) return 'A';
|
||||
if (rank >= 0.5) return 'B+';
|
||||
if (rank >= 0.33) return 'B';
|
||||
return 'C';
|
||||
}
|
||||
|
||||
/**
|
||||
* Intelligence fields for the grade card (Session 43) — STAT CONTEXT + VYNDR
|
||||
* INTELLIGENCE. Computed ONLY from the already-built feature vector (no extra
|
||||
* I/O), so every field is optional and self-hides on the card when absent.
|
||||
* NOTE: archetype is intentionally NOT set here — the per-prop feature vector
|
||||
* doesn't carry a full multi-stat season line, so classifying it would just
|
||||
* yield the fallback. The archetype strip lights up once the snapshot pipeline
|
||||
* (Session 44) feeds per-player season lines into the grade response.
|
||||
*/
|
||||
function buildIntelFields(features = {}) {
|
||||
const out = {};
|
||||
const round1 = (n) => Math.round(n * 10) / 10;
|
||||
if (Number.isFinite(features.l20_avg)) out.season_avg = round1(features.l20_avg);
|
||||
else if (Number.isFinite(features.season_avg)) out.season_avg = round1(features.season_avg);
|
||||
if (Number.isFinite(features.l10_avg)) out.last10_avg = round1(features.l10_avg);
|
||||
else if (Number.isFinite(features.l5_avg)) out.last10_avg = round1(features.l5_avg);
|
||||
|
||||
const form = computeFormScore(features);
|
||||
if (form != null) out.form = form;
|
||||
if (Number.isFinite(features.usage_rate)) out.usage = `${round1(features.usage_rate)}%`;
|
||||
else if (Number.isFinite(features.minutes_per_game)) out.usage = `${Math.round(features.minutes_per_game)} min`;
|
||||
const matchup = matchupGradeFromRank(features.opp_rank_stat);
|
||||
if (matchup) out.matchup_grade = matchup;
|
||||
if (Number.isFinite(features.rest_days)) out.rest = `${features.rest_days}d rest`;
|
||||
return out;
|
||||
}
|
||||
|
||||
async function analyzeViaEngine1(rawProp = {}) {
|
||||
const featureResult = await computeFeaturesForProp(rawProp);
|
||||
const { features, trap, consistency, prop, meta } = featureResult;
|
||||
@@ -308,6 +357,10 @@ async function analyzeViaEngine1(rawProp = {}) {
|
||||
line: prop.line,
|
||||
});
|
||||
|
||||
// Session 43 — attach grade-card intelligence fields (stat context + VYNDR
|
||||
// intelligence). Optional + self-hiding on the card; zero extra I/O.
|
||||
Object.assign(legacy, buildIntelFields(features));
|
||||
|
||||
return legacy;
|
||||
}
|
||||
|
||||
@@ -319,5 +372,8 @@ module.exports = {
|
||||
fallbackLegacyResult,
|
||||
explainErrors,
|
||||
ERROR_EXPLANATIONS,
|
||||
buildIntelFields,
|
||||
computeFormScore,
|
||||
matchupGradeFromRank,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user