Codex commited on
Commit ·
9d4572d
1
Parent(s): 9a27dc9
Fix hitter chart profile scoring
Browse files- src/matchups.js +47 -4
src/matchups.js
CHANGED
|
@@ -2565,15 +2565,20 @@ export class MatchupService {
|
|
| 2565 |
throw new Error('No hosted hitter slate was available in the fallback window.');
|
| 2566 |
}
|
| 2567 |
|
| 2568 |
-
const cacheKey = this.buildChartCacheKey('hitter-chart-base', { date: resolvedDate });
|
| 2569 |
const base = await this.getCachedChartValue(cacheKey, async () => {
|
| 2570 |
-
const [slateRows, hitterRows, pitcherRows, rosterRows] = await Promise.all([
|
| 2571 |
this.hosted.readDailyFile(resolvedDate, 'slate.parquet', SLATE_COLUMNS),
|
| 2572 |
this.hosted.readDailyFile(resolvedDate, 'daily_hitter_metrics.parquet', HITTER_COLUMNS),
|
| 2573 |
this.hosted.readDailyFile(resolvedDate, 'daily_pitcher_metrics.parquet', PITCHER_COLUMNS),
|
|
|
|
| 2574 |
this.hosted.readDailyFile(resolvedDate, 'rosters.parquet', ROSTER_COLUMNS).catch(() => []),
|
|
|
|
|
|
|
| 2575 |
]);
|
| 2576 |
const rosterLookup = buildRosterLookup(rosterRows);
|
|
|
|
|
|
|
| 2577 |
const pitcherLookup = new Map(
|
| 2578 |
pitcherRows
|
| 2579 |
.filter((row) => keepRowForDefaults(row))
|
|
@@ -2586,12 +2591,50 @@ export class MatchupService {
|
|
| 2586 |
slateLookup.set(team, { ...slate, opposingPitcherHand: pitcher.p_throws });
|
| 2587 |
}
|
| 2588 |
}
|
| 2589 |
-
|
|
|
|
| 2590 |
.filter((row) => keepRowForDefaults(row))
|
| 2591 |
.map((row) => withDefaults(row, slateLookup, { rosterLookup, playerType: 'hitter' }));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2592 |
});
|
| 2593 |
|
| 2594 |
-
const hitterMatch = findBestPlayerMatch(base, 'hitter_name', playerName)
|
|
|
|
| 2595 |
if (!hitterMatch) {
|
| 2596 |
throw new Error(`No hitter matchup profile matched "${playerName}".`);
|
| 2597 |
}
|
|
|
|
| 2565 |
throw new Error('No hosted hitter slate was available in the fallback window.');
|
| 2566 |
}
|
| 2567 |
|
| 2568 |
+
const cacheKey = this.buildChartCacheKey('hitter-chart-base', { date: resolvedDate, player: playerName });
|
| 2569 |
const base = await this.getCachedChartValue(cacheKey, async () => {
|
| 2570 |
+
const [slateRows, hitterRows, pitcherRows, exclusionRows, rosterRows, batterZoneRows, pitcherZoneRows] = await Promise.all([
|
| 2571 |
this.hosted.readDailyFile(resolvedDate, 'slate.parquet', SLATE_COLUMNS),
|
| 2572 |
this.hosted.readDailyFile(resolvedDate, 'daily_hitter_metrics.parquet', HITTER_COLUMNS),
|
| 2573 |
this.hosted.readDailyFile(resolvedDate, 'daily_pitcher_metrics.parquet', PITCHER_COLUMNS),
|
| 2574 |
+
this.hosted.readDailyFile(resolvedDate, 'hitter_pitcher_exclusions.parquet', EXCLUSION_COLUMNS).catch(() => []),
|
| 2575 |
this.hosted.readDailyFile(resolvedDate, 'rosters.parquet', ROSTER_COLUMNS).catch(() => []),
|
| 2576 |
+
this.hosted.readDailyFile(resolvedDate, 'daily_batter_zone_profiles.parquet', BATTER_ZONE_COLUMNS).catch(() => []),
|
| 2577 |
+
this.hosted.readDailyFile(resolvedDate, 'daily_pitcher_zone_profiles.parquet', PITCHER_ZONE_COLUMNS).catch(() => []),
|
| 2578 |
]);
|
| 2579 |
const rosterLookup = buildRosterLookup(rosterRows);
|
| 2580 |
+
const rosterIdsByTeam = buildRosterIdsByTeam(rosterRows);
|
| 2581 |
+
const exclusionSet = buildExclusionSet(exclusionRows);
|
| 2582 |
const pitcherLookup = new Map(
|
| 2583 |
pitcherRows
|
| 2584 |
.filter((row) => keepRowForDefaults(row))
|
|
|
|
| 2591 |
slateLookup.set(team, { ...slate, opposingPitcherHand: pitcher.p_throws });
|
| 2592 |
}
|
| 2593 |
}
|
| 2594 |
+
|
| 2595 |
+
const preparedRows = hitterRows
|
| 2596 |
.filter((row) => keepRowForDefaults(row))
|
| 2597 |
.map((row) => withDefaults(row, slateLookup, { rosterLookup, playerType: 'hitter' }));
|
| 2598 |
+
|
| 2599 |
+
const matchedPlayer = findBestPlayerMatch(preparedRows, 'hitter_name', playerName);
|
| 2600 |
+
if (!matchedPlayer) {
|
| 2601 |
+
return {
|
| 2602 |
+
resolvedDate,
|
| 2603 |
+
rows: [],
|
| 2604 |
+
scoredRows: [],
|
| 2605 |
+
batterZoneRows,
|
| 2606 |
+
pitcherZoneRows,
|
| 2607 |
+
};
|
| 2608 |
+
}
|
| 2609 |
+
|
| 2610 |
+
const normalizedTeam = normalizeTeam(matchedPlayer.team);
|
| 2611 |
+
const effectiveSplit = matchedPlayer.opposing_pitcher_hand === 'R'
|
| 2612 |
+
? 'vs_rhp'
|
| 2613 |
+
: matchedPlayer.opposing_pitcher_hand === 'L'
|
| 2614 |
+
? 'vs_lhp'
|
| 2615 |
+
: DEFAULT_SPLIT_KEY;
|
| 2616 |
+
|
| 2617 |
+
const teamRows = hitterRows
|
| 2618 |
+
.filter((row) => normalizeTeam(row.team) === normalizedTeam)
|
| 2619 |
+
.filter((row) => String(row.split_key ?? DEFAULT_SPLIT_KEY) === effectiveSplit)
|
| 2620 |
+
.filter((row) => String(row.recent_window ?? DEFAULT_RECENT_WINDOW) === DEFAULT_RECENT_WINDOW)
|
| 2621 |
+
.filter((row) => String(row.weighted_mode ?? DEFAULT_WEIGHTED_MODE) === DEFAULT_WEIGHTED_MODE)
|
| 2622 |
+
.filter((row) => {
|
| 2623 |
+
const rosterPlayerIds = rosterIdsByTeam.get(normalizedTeam) ?? null;
|
| 2624 |
+
return !rosterPlayerIds || rosterPlayerIds.has(String(row.batter ?? row.player_id ?? '').trim());
|
| 2625 |
+
})
|
| 2626 |
+
.filter((row) => !exclusionSet.has(String(row.batter ?? row.player_id ?? '').trim()))
|
| 2627 |
+
.map((row) => withDefaults(row, slateLookup, { rosterLookup, playerType: 'hitter' }));
|
| 2628 |
+
|
| 2629 |
+
return {
|
| 2630 |
+
resolvedDate,
|
| 2631 |
+
rows: preparedRows,
|
| 2632 |
+
scoredRows: addHitterMatchupScore(teamRows, batterZoneRows, pitcherZoneRows),
|
| 2633 |
+
};
|
| 2634 |
});
|
| 2635 |
|
| 2636 |
+
const hitterMatch = findBestPlayerMatch(base.scoredRows ?? [], 'hitter_name', playerName)
|
| 2637 |
+
?? findBestPlayerMatch(base.rows ?? [], 'hitter_name', playerName);
|
| 2638 |
if (!hitterMatch) {
|
| 2639 |
throw new Error(`No hitter matchup profile matched "${playerName}".`);
|
| 2640 |
}
|