Files
monorepo/baseball/src/lib/baseball-stats.ts
T
2026-05-06 18:31:27 +00:00

118 lines
3.1 KiB
TypeScript

import type { PlayerStatsInput } from "@/lib/types";
type StatValue = number | null | undefined;
function stat(value: StatValue) {
return value ?? 0;
}
function hasValue(value: StatValue) {
return value !== null && value !== undefined;
}
function formatRate(value: number | null, digits = 3) {
if (value === null || Number.isNaN(value)) {
return null;
}
return value.toFixed(digits).replace(/^0(?=\.)/, "");
}
function formatDecimal(value: number | null, digits = 2) {
if (value === null || Number.isNaN(value)) {
return null;
}
return value.toFixed(digits);
}
export function formatInningsPitched(outs: StatValue) {
const totalOuts = stat(outs);
if (totalOuts <= 0) {
return null;
}
const wholeInnings = Math.floor(totalOuts / 3);
const remainder = totalOuts % 3;
return `${wholeInnings}.${remainder}`;
}
export function hasBattingStats(stats?: PlayerStatsInput | null) {
if (!stats) return false;
return [
stats.atBats,
stats.runs,
stats.hits,
stats.doubles,
stats.triples,
stats.homeRuns,
stats.runsBattedIn,
stats.walks,
stats.strikeouts,
stats.stolenBases
].some(hasValue);
}
export function hasPitchingStats(stats?: PlayerStatsInput | null) {
if (!stats) return false;
return [
stats.inningsPitchedOuts,
stats.earnedRunsAllowed,
stats.hitsAllowed,
stats.walksAllowed,
stats.strikeoutsPitched
].some(hasValue);
}
export function getBattingSummary(stats?: PlayerStatsInput | null) {
if (!hasBattingStats(stats)) {
return [];
}
const atBats = stat(stats?.atBats);
const hits = stat(stats?.hits);
const walks = stat(stats?.walks);
const doubles = stat(stats?.doubles);
const triples = stat(stats?.triples);
const homeRuns = stat(stats?.homeRuns);
const singles = Math.max(hits - doubles - triples - homeRuns, 0);
const totalBases = singles + doubles * 2 + triples * 3 + homeRuns * 4;
const average = atBats > 0 ? hits / atBats : null;
const onBase = atBats + walks > 0 ? (hits + walks) / (atBats + walks) : null;
const slugging = atBats > 0 ? totalBases / atBats : null;
const ops = onBase !== null || slugging !== null ? (onBase ?? 0) + (slugging ?? 0) : null;
return [
["AVG", formatRate(average)],
["OBP", formatRate(onBase)],
["SLG", formatRate(slugging)],
["OPS", formatRate(ops)]
] as const;
}
export function getPitchingSummary(stats?: PlayerStatsInput | null) {
if (!hasPitchingStats(stats)) {
return [];
}
const outs = stat(stats?.inningsPitchedOuts);
const innings = outs / 3;
const earnedRuns = stat(stats?.earnedRunsAllowed);
const hitsAllowed = stat(stats?.hitsAllowed);
const walksAllowed = stat(stats?.walksAllowed);
const strikeoutsPitched = stat(stats?.strikeoutsPitched);
const era = outs > 0 ? (earnedRuns * 27) / outs : null;
const whip = innings > 0 ? (hitsAllowed + walksAllowed) / innings : null;
return [
["IP", formatInningsPitched(outs)],
["ERA", formatDecimal(era)],
["WHIP", formatDecimal(whip)],
["K", strikeoutsPitched > 0 || hasValue(stats?.strikeoutsPitched) ? String(strikeoutsPitched) : null]
] as const;
}