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:
Kev
2026-06-18 15:38:06 -04:00
parent 8bc79f3c38
commit 80683e71b4
19 changed files with 940 additions and 18 deletions
@@ -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,
},
};