Session 44: Make it visible — VYNDR archetype names, grade intel, schedule fix, landing page (2061 tests)
Frontend + wiring only. Wires existing backend into the pages users see. - VYNDR Original archetype rename (41) across archetypeService.js + lib/ archetypes.js + ArchetypeBadge, each keeping legacyName (resolves stale data). Judge -> BOMBER. Old POWER PULL slot -> WHIFF strikeout-artist pitcher. - BACKEND_HANDOFF.md: canonical frontend<->backend data contract. - Grade card intel: scan/page.tsx now forwards the engine's intel fields (season_avg/form/usage/matchup_grade/archetype/...) into mapScanToGradeResult -> STAT CONTEXT + VYNDR INTELLIGENCE sections populate. The chain already preserved them (tierGating + /api/scan spread); the page was dropping them. - Schedule freshness: slateAdapter.isRelevantGame drops completed games >24h old; Slate.filteredGames applies it. (TTL already 60s.) - Landing: Features.tsx rewritten to user-facing copy (no Point-biserial/Zone 14/ABS/Phi-coefficient). - Depth chart Next proxies added (/api/stats/lineup|depth|cascade) - were 404. - GameCard swap DEFERRED (Kev): legacy on-demand card stays as a bridge until the snapshot pipeline populates the grades cache; vyndr/GameCard swaps in then. Backend 2045 -> 2061 tests (+16), 167 suites. Web build clean (exit 0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+58
-44
@@ -33,60 +33,71 @@ const GLYPHS = {
|
||||
postup: '<rect x="6.4" y="2.6" width="3.2" height="11" rx="1.6" fill="currentColor"/><circle cx="12" cy="5.2" r="1.7" fill="currentColor"/>',
|
||||
};
|
||||
|
||||
/* name → { color, glyph, desc } — the design's MAP. */
|
||||
/* name → { color, glyph, desc, legacy } — VYNDR Originals (Session 44).
|
||||
Keys/colors/glyphs MUST match src/services/archetypeService.js ARCHETYPES.
|
||||
`legacy` is the old descriptive label, kept for reference (never displayed). */
|
||||
const ARCHETYPE_MAP = {
|
||||
// NBA
|
||||
'VOLUME SCORER': { c: '#FF6B4A', d: 'High usage, shot-dependent scorer', g: 'triangle' },
|
||||
'FLOOR GENERAL': { c: '#4A9EFF', d: 'Assist-heavy playmaker', g: 'node' },
|
||||
'TWO-WAY ANCHOR': { c: '#A78BFA', d: 'Defense, rebounds, blocks', g: 'shield' },
|
||||
'STRETCH BIG': { c: '#2DD4BF', d: 'Floor-spacing shooting big', g: 'target' },
|
||||
'USAGE SPONGE': { c: '#FFB347', d: 'Usage spikes when stars sit', g: 'uparrow' },
|
||||
'COMBO GUARD': { c: '#00D4A0', d: 'Scoring + playmaking hybrid', g: 'twin' },
|
||||
'ROLE GLUE': { c: '#9499A8', d: 'Low-usage specialist', g: 'chain' },
|
||||
'TRANSITION ENGINE': { c: '#22D3EE', d: 'Pace-pushing fast-break threat', g: 'chevrons' },
|
||||
'POST SCORER': { c: '#FF5C5C', d: 'Back-to-basket interior scorer', g: 'postup' },
|
||||
'DEFENSIVE SPECIALIST': { c: '#6366F1', d: 'Perimeter stopper, low usage', g: 'shieldCheck' },
|
||||
'POINT FORWARD': { c: '#38BDF8', d: 'Oversized primary creator', g: 'half' },
|
||||
SLASHER: { c: '#FB923C', d: 'Rim-attacking, foul-drawing driver', g: 'slash' },
|
||||
'RIM RUNNER': { c: '#F472B6', d: 'Lob and putback finisher', g: 'arc' },
|
||||
'3-AND-D': { c: '#818CF8', d: 'Catch-and-shoot plus defense', g: 'crosshair' },
|
||||
'SIXTH MAN': { c: '#FACC15', d: 'Bench scoring spark', g: 'bolt' },
|
||||
TORCH: { c: '#FF6B4A', d: 'High usage, shot-dependent scorer', g: 'triangle', legacy: 'VOLUME SCORER' },
|
||||
CONDUCTOR: { c: '#4A9EFF', d: 'Assist-heavy playmaker', g: 'node', legacy: 'FLOOR GENERAL' },
|
||||
FORTRESS: { c: '#A78BFA', d: 'Defense, rebounds, blocks', g: 'shield', legacy: 'TWO-WAY ANCHOR' },
|
||||
ARTILLERY: { c: '#2DD4BF', d: 'Floor-spacing shooting big', g: 'target', legacy: 'STRETCH BIG' },
|
||||
SURGE: { c: '#FFB347', d: 'Usage spikes when stars sit', g: 'uparrow', legacy: 'USAGE SPONGE' },
|
||||
'DUAL THREAT': { c: '#00D4A0', d: 'Scoring + playmaking hybrid', g: 'twin', legacy: 'COMBO GUARD' },
|
||||
CONNECTOR: { c: '#9499A8', d: 'Low-usage specialist', g: 'chain', legacy: 'ROLE GLUE' },
|
||||
FASTBREAK: { c: '#22D3EE', d: 'Pace-pushing fast-break threat', g: 'chevrons', legacy: 'TRANSITION ENGINE' },
|
||||
'PAINT BOSS': { c: '#FF5C5C', d: 'Back-to-basket interior scorer', g: 'postup', legacy: 'POST SCORER' },
|
||||
LOCKDOWN: { c: '#6366F1', d: 'Perimeter stopper, low usage', g: 'shieldCheck', legacy: 'DEFENSIVE SPECIALIST' },
|
||||
SWITCHBOARD: { c: '#38BDF8', d: 'Oversized primary creator', g: 'half', legacy: 'POINT FORWARD' },
|
||||
ARCHITECT: { c: '#FB923C', d: 'Self-created shot maker, rim-attacking', g: 'slash', legacy: 'SLASHER' },
|
||||
PISTON: { c: '#F472B6', d: 'Lob and putback finisher', g: 'arc', legacy: 'RIM RUNNER' },
|
||||
SENTINEL: { c: '#818CF8', d: 'Catch-and-shoot plus defense', g: 'crosshair', legacy: '3-AND-D' },
|
||||
IGNITER: { c: '#FACC15', d: 'Bench scoring spark', g: 'bolt', legacy: 'SIXTH MAN' },
|
||||
// WNBA-unique
|
||||
'POST FACILITATOR': { c: '#C084FC', d: 'Playmaking hub from the post', g: 'node' },
|
||||
'TWO-WAY WING': { c: '#A78BFA', d: 'Two-way perimeter wing', g: 'shieldCheck' },
|
||||
'STRETCH FORWARD': { c: '#2DD4BF', d: 'Floor-spacing forward', g: 'target' },
|
||||
'SLASHING GUARD': { c: '#FB923C', d: 'Downhill driving guard', g: 'slash' },
|
||||
'INTERIOR ANCHOR': { c: '#6366F1', d: 'Paint defender and rebounder', g: 'shield' },
|
||||
DISTRIBUTOR: { c: '#C084FC', d: 'Playmaking hub from the post', g: 'node', legacy: 'POST FACILITATOR' },
|
||||
SHIELD: { c: '#A78BFA', d: 'Two-way perimeter forward', g: 'shieldCheck', legacy: 'TWO-WAY WING' },
|
||||
RANGE: { c: '#2DD4BF', d: 'Floor-spacing forward', g: 'target', legacy: 'STRETCH FORWARD' },
|
||||
SPARK: { c: '#FB923C', d: 'Downhill scoring guard', g: 'slash', legacy: 'SLASHING GUARD' },
|
||||
ANCHOR: { c: '#6366F1', d: 'Dominant paint defender and rebounder', g: 'shield', legacy: 'INTERIOR ANCHOR' },
|
||||
// MLB
|
||||
'POWER PULL': { c: '#FF5C5C', d: 'HR-dependent, high strikeout power', g: 'batball' },
|
||||
CONTACT: { c: '#3DDC84', d: 'High average, low strikeout', g: 'crosshair' },
|
||||
'RUN PRODUCER': { c: '#4A9EFF', d: 'RBI-dependent, lineup context', g: 'diamond' },
|
||||
ACE: { c: '#A78BFA', d: 'High K/9, low WHIP, deep games', g: 'star' },
|
||||
'BULLPEN ARM': { c: '#FFB347', d: 'Short outings, high leverage', g: 'bolt' },
|
||||
'SPEED THREAT': { c: '#2DD4BF', d: 'Stolen bases, speed score', g: 'chevrons' },
|
||||
'TWO-WAY PLAYER': { c: '#F472B6', d: 'Bats and pitches at elite level', g: 'half' },
|
||||
'UTILITY PLAYER': { c: '#22D3EE', d: 'Multi-position lineup flex', g: 'plus' },
|
||||
'INNINGS EATER': { c: '#818CF8', d: 'Durable, deep-start workhorse', g: 'clock' },
|
||||
'POWER SLUGGER': { c: '#FF6B4A', d: 'All-fields power producer', g: 'triangle' },
|
||||
'TABLE SETTER': { c: '#38BDF8', d: 'On-base leadoff catalyst', g: 'diamondLine' },
|
||||
'GAP HITTER': { c: '#34D399', d: 'Doubles and extra-base gaps', g: 'uparrow' },
|
||||
CLOSER: { c: '#FB7185', d: 'Ninth-inning save specialist', g: 'lock' },
|
||||
SWINGMAN: { c: '#FBBF24', d: 'Spot starter and long relief', g: 'swap' },
|
||||
'DEFENSIVE WIZARD': { c: '#6366F1', d: 'Glove-first defensive value', g: 'shieldCheck' },
|
||||
BOMBER: { c: '#FF6B4A', d: 'Middle-of-the-order power producer', g: 'triangle', legacy: 'POWER SLUGGER' },
|
||||
BRUSH: { c: '#3DDC84', d: 'High average, low strikeout', g: 'crosshair', legacy: 'CONTACT' },
|
||||
DRIVER: { c: '#4A9EFF', d: 'RBI-dependent, lineup context', g: 'diamond', legacy: 'RUN PRODUCER' },
|
||||
ALPHA: { c: '#A78BFA', d: 'High K/9, low WHIP, deep games', g: 'star', legacy: 'ACE' },
|
||||
WHIFF: { c: '#FFB347', d: 'Bat-missing arm, high K with traffic', g: 'bolt', legacy: 'STRIKEOUT ARTIST' },
|
||||
GHOST: { c: '#2DD4BF', d: 'Stolen bases, speed score', g: 'chevrons', legacy: 'SPEED THREAT' },
|
||||
HYBRID: { c: '#F472B6', d: 'Bats and pitches at elite level', g: 'half', legacy: 'TWO-WAY PLAYER' },
|
||||
FLEX: { c: '#22D3EE', d: 'Multi-position lineup flex', g: 'plus', legacy: 'UTILITY PLAYER' },
|
||||
WORKHORSE: { c: '#818CF8', d: 'Durable, deep-start arm', g: 'clock', legacy: 'INNINGS EATER' },
|
||||
CATALYST: { c: '#38BDF8', d: 'On-base leadoff catalyst', g: 'diamondLine', legacy: 'TABLE SETTER' },
|
||||
MIRROR: { c: '#34D399', d: 'Gap-to-gap line-drive bat', g: 'uparrow', legacy: 'GAP HITTER' },
|
||||
HAMMER: { c: '#FB7185', d: 'Ninth-inning save specialist', g: 'lock', legacy: 'CLOSER' },
|
||||
SINKER: { c: '#FBBF24', d: 'Groundball spot-starter / long relief', g: 'swap', legacy: 'SWINGMAN' },
|
||||
BRIDGE: { c: '#6366F1', d: 'Setup / high-leverage middle relief', g: 'shieldCheck', legacy: 'BULLPEN ARM' },
|
||||
SWITCH: { c: '#FF5C5C', d: 'Platoon-leveraged, glove-first bat', g: 'batball', legacy: 'DEFENSIVE WIZARD' },
|
||||
// Soccer
|
||||
POACHER: { c: '#FF5C5C', d: 'Penalty-box finisher', g: 'crosshair' },
|
||||
CREATOR: { c: '#4A9EFF', d: 'Chance-creating playmaker', g: 'node' },
|
||||
'TARGET MAN': { c: '#FF6B4A', d: 'Hold-up aerial striker', g: 'triangle' },
|
||||
'BOX-TO-BOX': { c: '#00D4A0', d: 'All-action central midfielder', g: 'chevrons' },
|
||||
'WING WIZARD': { c: '#2DD4BF', d: 'Dribbling wide threat', g: 'slash' },
|
||||
'SWEEPER KEEPER': { c: '#A78BFA', d: 'Distributing goalkeeper', g: 'shield' },
|
||||
FINISHER: { c: '#FF5C5C', d: 'Penalty-box finisher', g: 'crosshair', legacy: 'POACHER' },
|
||||
MAESTRO: { c: '#4A9EFF', d: 'Chance-creating playmaker', g: 'node', legacy: 'CREATOR' },
|
||||
TOWER: { c: '#FF6B4A', d: 'Hold-up aerial striker', g: 'triangle', legacy: 'TARGET MAN' },
|
||||
MOTOR: { c: '#00D4A0', d: 'All-action central midfielder', g: 'chevrons', legacy: 'BOX-TO-BOX' },
|
||||
BLADE: { c: '#2DD4BF', d: 'Dribbling wide threat', g: 'slash', legacy: 'WING WIZARD' },
|
||||
WALL: { c: '#A78BFA', d: 'Distributing goalkeeper', g: 'shield', legacy: 'SWEEPER KEEPER' },
|
||||
};
|
||||
|
||||
const FALLBACK = { c: '#9499A8', d: '', g: '' };
|
||||
|
||||
// Reverse index so an old legacy name (e.g. "POWER SLUGGER") still resolves to
|
||||
// its VYNDR Original (BOMBER) — defends any stale cached data post-rename.
|
||||
const LEGACY_INDEX = {};
|
||||
for (const [k, v] of Object.entries(ARCHETYPE_MAP)) {
|
||||
if (v.legacy) LEGACY_INDEX[v.legacy.toUpperCase()] = k;
|
||||
}
|
||||
|
||||
function archetypeInfo(name) {
|
||||
const key = (name == null ? '' : String(name)).toUpperCase();
|
||||
return ARCHETYPE_MAP[key] || FALLBACK;
|
||||
if (ARCHETYPE_MAP[key]) return ARCHETYPE_MAP[key];
|
||||
if (LEGACY_INDEX[key]) return ARCHETYPE_MAP[LEGACY_INDEX[key]];
|
||||
return FALLBACK;
|
||||
}
|
||||
|
||||
function archetypeColor(name) {
|
||||
@@ -115,8 +126,11 @@ function badgeStyle(name, variant = 'tint', size = 'sm') {
|
||||
} else {
|
||||
textColor = info.c; bg = info.c + '1F'; borderColor = info.c + '52'; glyphColor = info.c;
|
||||
}
|
||||
// Display the canonical VYNDR name even if a legacy name was passed.
|
||||
const upper = (name == null ? '' : String(name)).toUpperCase();
|
||||
const canonical = ARCHETYPE_MAP[upper] ? upper : LEGACY_INDEX[upper] || upper;
|
||||
return {
|
||||
name: (name == null ? '' : String(name)).toUpperCase(),
|
||||
name: canonical,
|
||||
desc: info.d,
|
||||
glyph: info.g,
|
||||
color: info.c,
|
||||
|
||||
@@ -175,6 +175,23 @@ function mapPitchers(game) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this game still show on the slate (Session 44)? Upcoming + live games
|
||||
* always show; a COMPLETED game is dropped once it's more than 24h old, so a
|
||||
* 5-day-old FINAL never lingers on the dashboard. Unknown/missing date → keep
|
||||
* (degrade open). `now` is injectable for tests.
|
||||
*/
|
||||
function isRelevantGame(game, now = Date.now()) {
|
||||
if (!game) return false;
|
||||
const state = String(game.state || game.status || '').toLowerCase();
|
||||
const isFinal = state === 'final' || state === 'post' || state === 'closed' || state === 'complete';
|
||||
if (!isFinal) return true;
|
||||
const raw = game.date || game.gameTime || game.commence_time || game.startTime;
|
||||
const t = raw ? new Date(raw).getTime() : NaN;
|
||||
if (Number.isNaN(t)) return true; // no parseable date → don't hide
|
||||
return (now - t) / 3_600_000 < 24;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseAmericanOdds,
|
||||
detectBestLines,
|
||||
@@ -183,4 +200,5 @@ module.exports = {
|
||||
formatGameTime,
|
||||
groupPropsByPlayer,
|
||||
mapPitchers,
|
||||
isRelevantGame,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user