Basic fielding stats leaders - needs filter on position specific x-check count

This commit is contained in:
Peter 2023-12-26 12:39:49 -05:00
parent 69a59c7d72
commit 4bb2795929
2 changed files with 155 additions and 15 deletions

View File

@ -89,6 +89,25 @@ export async function fetchFieldingStatsBySeasonAndTeamId(seasonNumber: number,
return fieldingStatsResponse.stats.map(normalizeFieldingStat)
}
export async function fetchFieldingStatsBySeason(seasonNumber: number, isRegularSeason: boolean): Promise<FieldingStat[]> {
// different endpoint for pre-modern stats (/plays) era
if (seasonNumber < MODERN_STAT_ERA_START) {
return []
}
// TODO might want to make this playerpositionteam grouping (currently does not exist)
const response = await fetch(`${SITE_URL}/api/v3/plays/fielding?season=${seasonNumber}&limit=10000&group_by=playerposition&s_type=${isRegularSeason ? 'regular' : 'post'}`)
const fieldingStatsResponse: {
count: number
stats: FieldingStatRaw[]
} = await response.json()
if (fieldingStatsResponse.count === 0) return []
return fieldingStatsResponse.stats.map(normalizeFieldingStat)
}
async function fetchLegacyFieldingStatsBySeasonAndPlayerId(seasonNumber: number, playerId: number, isRegularSeason: boolean): Promise<FieldingStat[]> {
const response = await fetch(`${SITE_URL}/api/v3/fieldingstats/totals?season=${seasonNumber}&player_id=${playerId}&s_type=${isRegularSeason ? 'regular' : 'post'}`)

View File

@ -56,10 +56,12 @@
</template>
<script lang="ts">
import type { Team } from '@/services/apiResponseTypes'
import { fetchBattingStatsBySeason, type BattingStat } from '@/services/battingStatsService'
import { type LeagueInfo, fetchCurrentLeagueInfo } from '@/services/currentService'
import type { FieldingStat } from '@/services/fieldingStatsService'
import { fetchFieldingStatsBySeason, type FieldingStat } from '@/services/fieldingStatsService'
import { fetchPitchingStatsBySeason, type PitchingStat } from '@/services/pitchingStatsService'
import type { Player } from '@/services/playersService'
import { CURRENT_SEASON, GAMES_PER_WEEK, WEEKS_PER_SEASON } from '@/services/utilities'
interface LeaderboardTableData<T> {
@ -69,12 +71,34 @@ interface LeaderboardTableData<T> {
precision?: number
}
interface FlatFieldingStat {
player: Player
team: Team
xCheckCount: number
hit: number
error: number
stolenBaseCheckCount: number
stolenBaseCount: number
caughtStealingCount: number
caughtStealingPercent: number
passedBallCount: number
weightedFieldingPercentPitcher: number
weightedFieldingPercentCatcher: number
weightedFieldingPercentFirstBase: number
weightedFieldingPercentSecondBase: number
weightedFieldingPercentThirdBase: number
weightedFieldingPercentShortstop: number
weightedFieldingPercentLeftField: number
weightedFieldingPercentCenterField: number
weightedFieldingPercentRightField: number
}
export default {
name: 'LeaderboardView',
data() {
return {
allPlayersBattingStats: [] as BattingStat[],
allPlayersFieldingStats: [] as FieldingStat[],
allPlayersFieldingStats: [] as FlatFieldingStat[],
allPlayersPitchingStats: [] as PitchingStat[],
currentSeasonNumber: CURRENT_SEASON,
seasonNumber: CURRENT_SEASON,
@ -110,7 +134,7 @@ export default {
qualifyingPitchingStats(): PitchingStat[] {
return this.allPlayersPitchingStats.filter(ps => ps.ip >= this.inningsPitchedMinimum)
},
leaderboardTableData(): LeaderboardTableData<BattingStat>[] | LeaderboardTableData<PitchingStat>[] | LeaderboardTableData<FieldingStat>[] {
leaderboardTableData(): LeaderboardTableData<BattingStat>[] | LeaderboardTableData<PitchingStat>[] | LeaderboardTableData<FlatFieldingStat>[] {
if (this.statType == 'Batting') {
return this.battingLeaderboardTableData
}
@ -181,8 +205,28 @@ export default {
{ title: 'Inherited Runners Scored', columnName: 'IRS', statPropertyName: 'ir_sc' }
]
},
fieldingLeaderboardTableData(): LeaderboardTableData<FieldingStat>[] {
return []
fieldingLeaderboardTableData(): LeaderboardTableData<FlatFieldingStat>[] {
return [
{ title: 'Fielding Chances', columnName: 'X-Ch', statPropertyName: 'xCheckCount' },
{ title: 'Weighted Fielding % (RF)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentRightField', precision: 3 },
{ title: 'Weighted Fielding % (CF)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentCenterField', precision: 3 },
{ title: 'Weighted Fielding % (LF)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentLeftField', precision: 3 },
{ title: 'Weighted Fielding % (SS)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentShortstop', precision: 3 },
{ title: 'Weighted Fielding % (3B)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentThirdBase', precision: 3 },
{ title: 'Weighted Fielding % (2B)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentSecondBase', precision: 3 },
{ title: 'Weighted Fielding % (1B)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentFirstBase', precision: 3 },
{ title: 'Weighted Fielding % (C)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentCatcher', precision: 3 },
{ title: 'Weighted Fielding % (P)', columnName: 'wF%', statPropertyName: 'weightedFieldingPercentPitcher', precision: 3 },
{ title: 'Errors', columnName: 'E', statPropertyName: 'error' },
{ title: 'Hits Allowed', columnName: 'X-Hit', statPropertyName: 'hit' },
{ title: 'Passed Balls', columnName: 'PB', statPropertyName: 'passedBallCount' },
{ title: 'Steal Attempts Against', columnName: 'SBa', statPropertyName: 'stolenBaseCheckCount' },
{ title: 'Runners Thrown Out (C)', columnName: 'CSc', statPropertyName: 'caughtStealingCount' },
{ title: 'Caught Stealing % (C)', columnName: 'CS%', statPropertyName: 'caughtStealingPercent', precision: 1 },
]
}
},
created() {
@ -199,7 +243,7 @@ export default {
}
},
methods: {
getTop10StatByCategory(stat: keyof BattingStat | keyof PitchingStat | keyof FieldingStat): (BattingStat | PitchingStat | FieldingStat)[] {
getTop10StatByCategory(stat: keyof BattingStat | keyof PitchingStat | keyof FlatFieldingStat): (BattingStat | PitchingStat | FlatFieldingStat)[] {
if (this.statType == 'Batting') {
return this.getTop10BattingStatByCategory(stat as keyof BattingStat)
}
@ -207,7 +251,7 @@ export default {
return this.getTop10PitchingStatByCategory(stat as keyof PitchingStat)
}
return [] // this.getTop10FieldingStatByCategory(stat as keyof FieldingStat)
return this.getTop10FieldingStatByCategory(stat as keyof FlatFieldingStat)
},
getTop10BattingStatByCategory(stat: keyof BattingStat): BattingStat[] {
// concat an empty array so that the existing array is not sorted as a side effect
@ -225,19 +269,28 @@ export default {
return statBase.sort((a, b) => sortMultiplier * ((b[stat] as number) - (a[stat] as number))).slice(0, 10)
},
// getTop10FieldingStatByCategory(stat: keyof FieldingStat): FieldingStat[] {
// // concat an empty array so that the existing array is not sorted as a side effect
getTop10FieldingStatByCategory(stat: keyof FlatFieldingStat): FlatFieldingStat[] {
// High: 2B/SS - 2 XCh/wk
// Med: 1B/3B/CF - 1 XCh/wk
// Low: LF/RF/P/C - 0.5 XCh/wk
const highXCheckGroup: (keyof FlatFieldingStat)[] = ['weightedFieldingPercentSecondBase', 'weightedFieldingPercentShortstop']
const mediumXCheckGroup: (keyof FlatFieldingStat)[] = ['weightedFieldingPercentFirstBase', 'weightedFieldingPercentThirdBase', 'weightedFieldingPercentCenterField']
const lowXCheckGroup: (keyof FlatFieldingStat)[] = ['weightedFieldingPercentLeftField', 'weightedFieldingPercentRightField', 'weightedFieldingPercentCatcher', 'weightedFieldingPercentCatcher', 'weightedFieldingPercentPitcher']
// return []//this.qualifyingBattingStats.concat([]).sort((a, b) => (b[stat] as number) - (a[stat] as number)).slice(0, 10)
// },
formatNumericalStat(stat: BattingStat | PitchingStat | FieldingStat, property: keyof BattingStat | keyof PitchingStat | keyof FieldingStat, precision: number): number | string {
const xCheckCountPerWeek = highXCheckGroup.includes(stat) ? 2 : mediumXCheckGroup.includes(stat) ? 1 : lowXCheckGroup.includes(stat) ? 0.5 : 0
return this.allPlayersFieldingStats
.filter(stat => stat.xCheckCount >= xCheckCountPerWeek * this.weekNumberForCalcs)
.sort((a, b) => ((b[stat] as number) - (a[stat] as number))).slice(0, 10)
},
formatNumericalStat(stat: BattingStat | PitchingStat | FlatFieldingStat, property: keyof BattingStat | keyof PitchingStat | keyof FlatFieldingStat, precision: number): number | string {
let numericalStat: number = 0
if (this.statType == 'Batting') numericalStat = (stat as BattingStat)[property as keyof BattingStat] as number
if (this.statType == 'Pitching') numericalStat = (stat as PitchingStat)[property as keyof PitchingStat] as number
if (this.statType == 'Fielding') numericalStat = (stat as FieldingStat)[property as keyof FieldingStat] as number
if (this.statType == 'Fielding') numericalStat = (stat as FlatFieldingStat)[property as keyof FlatFieldingStat] as number
if (Number.isInteger(numericalStat)) return numericalStat
if (Number.isInteger(numericalStat) && !precision) return numericalStat
return numericalStat.toFixed(precision)
},
@ -252,7 +305,75 @@ export default {
this.allPlayersPitchingStats = await fetchPitchingStatsBySeason(this.seasonNumber, true)
}
if (this.statType == 'Fielding') {
this.allPlayersFieldingStats = []
const allPlayerPositionFieldingStats: FieldingStat[] = await fetchFieldingStatsBySeason(this.seasonNumber, true)
const aggregatedFieldingStatsByPlayer: { [playerName: string]: FlatFieldingStat } = {}
allPlayerPositionFieldingStats.forEach(stat => {
if (!aggregatedFieldingStatsByPlayer[stat.player.name]) {
aggregatedFieldingStatsByPlayer[stat.player.name] = {
player: stat.player,
team: stat.team,
xCheckCount: 0,
hit: 0,
error: 0,
stolenBaseCheckCount: 0,
stolenBaseCount: 0,
caughtStealingCount: 0,
caughtStealingPercent: 0,
passedBallCount: 0,
weightedFieldingPercentPitcher: 0,
weightedFieldingPercentCatcher: 0,
weightedFieldingPercentFirstBase: 0,
weightedFieldingPercentSecondBase: 0,
weightedFieldingPercentThirdBase: 0,
weightedFieldingPercentShortstop: 0,
weightedFieldingPercentLeftField: 0,
weightedFieldingPercentCenterField: 0,
weightedFieldingPercentRightField: 0
}
}
aggregatedFieldingStatsByPlayer[stat.player.name].xCheckCount += stat.xCheckCount
aggregatedFieldingStatsByPlayer[stat.player.name].hit += stat.hit
aggregatedFieldingStatsByPlayer[stat.player.name].error += stat.error
if (stat.pos === 'P') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentPitcher = stat.weightedFieldingPercent
}
else if (stat.pos === 'C') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentCatcher = stat.weightedFieldingPercent
// catchers have extra stats
aggregatedFieldingStatsByPlayer[stat.player.name].stolenBaseCheckCount = stat.stolenBaseCheckCount
aggregatedFieldingStatsByPlayer[stat.player.name].stolenBaseCount = stat.stolenBaseCount
aggregatedFieldingStatsByPlayer[stat.player.name].caughtStealingCount = stat.caughtStealingCount
aggregatedFieldingStatsByPlayer[stat.player.name].caughtStealingPercent = stat.caughtStealingPercent
aggregatedFieldingStatsByPlayer[stat.player.name].passedBallCount = stat.passedBallCount
}
else if (stat.pos === '1B') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentFirstBase = stat.weightedFieldingPercent
}
else if (stat.pos === '2B') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentSecondBase = stat.weightedFieldingPercent
}
else if (stat.pos === '3B') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentThirdBase = stat.weightedFieldingPercent
}
else if (stat.pos === 'SS') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentShortstop = stat.weightedFieldingPercent
}
else if (stat.pos === 'LF') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentLeftField = stat.weightedFieldingPercent
}
else if (stat.pos === 'CF') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentCenterField = stat.weightedFieldingPercent
}
else if (stat.pos === 'RF') {
aggregatedFieldingStatsByPlayer[stat.player.name].weightedFieldingPercentRightField = stat.weightedFieldingPercent
}
})
this.allPlayersFieldingStats = Object.values(aggregatedFieldingStatsByPlayer)
}
}
}