Refactor batting stats, services, adding types for pitching stats
This commit is contained in:
parent
a778ddf361
commit
7c17166174
3
components.d.ts
vendored
3
components.d.ts
vendored
@ -9,11 +9,14 @@ export {}
|
|||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
|
BattingSummaryTable: typeof import('./src/components/BattingSummaryTable.vue')['default']
|
||||||
|
CareerBattingTable: typeof import('./src/components/CareerBattingTable.vue')['default']
|
||||||
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
|
IconCommunity: typeof import('./src/components/icons/IconCommunity.vue')['default']
|
||||||
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
|
IconDocumentation: typeof import('./src/components/icons/IconDocumentation.vue')['default']
|
||||||
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
|
IconEcosystem: typeof import('./src/components/icons/IconEcosystem.vue')['default']
|
||||||
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
|
IconSupport: typeof import('./src/components/icons/IconSupport.vue')['default']
|
||||||
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
|
IconTooling: typeof import('./src/components/icons/IconTooling.vue')['default']
|
||||||
|
LastFourGamesBattingTable: typeof import('./src/components/LastFourGamesBattingTable.vue')['default']
|
||||||
NavBar: typeof import('./src/components/NavBar.vue')['default']
|
NavBar: typeof import('./src/components/NavBar.vue')['default']
|
||||||
NewsPreview: typeof import('./src/components/NewsPreview.vue')['default']
|
NewsPreview: typeof import('./src/components/NewsPreview.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
|
|||||||
94
src/components/BattingSummaryTable.vue
Normal file
94
src/components/BattingSummaryTable.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="table-responsive-xl" style="max-width:45rem">
|
||||||
|
<table class="table table-sm table-striped">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Summary</th>
|
||||||
|
<th>AB</th>
|
||||||
|
<th>H</th>
|
||||||
|
<th>HR</th>
|
||||||
|
<th>BA</th>
|
||||||
|
<th>R</th>
|
||||||
|
<th>RBI</th>
|
||||||
|
<th>SB</th>
|
||||||
|
<th>OBP</th>
|
||||||
|
<th>SLG</th>
|
||||||
|
<th>OPS</th>
|
||||||
|
<th>wOBA</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="batter-summary-table">
|
||||||
|
<tr v-if="currentSeasonBatting">
|
||||||
|
<td>Season {{ currentSeasonBatting.player.season }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.ab }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.hit }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.hr }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.avg.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.run }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.rbi }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.sb }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.obp.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.slg.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.ops.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentSeasonBatting.woba.toFixed(3) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-if="currentPostSeasonBatting">
|
||||||
|
<td>S{{ currentPostSeasonBatting.player.season }} / Playoffs</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.ab }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.hit }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.hr }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.avg.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.run }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.rbi }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.sb }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.obp.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.slg.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.ops.toFixed(3) }}</td>
|
||||||
|
<td>{{ currentPostSeasonBatting.woba.toFixed(3) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot v-if="careerBattingStat" id="batter-summary-footer">
|
||||||
|
<tr>
|
||||||
|
<th>Career</th>
|
||||||
|
<th>{{ careerBattingStat.ab }}</th>
|
||||||
|
<th>{{ careerBattingStat.hit }}</th>
|
||||||
|
<th>{{ careerBattingStat.hr }}</th>
|
||||||
|
<th>{{ careerBattingStat.avg.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.run }}</th>
|
||||||
|
<th>{{ careerBattingStat.rbi }}</th>
|
||||||
|
<th>{{ careerBattingStat.sb }}</th>
|
||||||
|
<th>{{ careerBattingStat.obp.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.slg.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.ops.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.woba.toFixed(3) }}</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { aggregateBattingStats, type BattingStat } from '@/services/battingStatsService'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "BattingSummaryTable",
|
||||||
|
props: {
|
||||||
|
currentSeasonBatting: { type: Object as PropType<BattingStat>, required: false },
|
||||||
|
currentPostSeasonBatting: { type: Object as PropType<BattingStat>, required: false },
|
||||||
|
regularSeasonBattingStats: { type: Array<BattingStat>, required: true }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
careerBattingStat(): BattingStat | undefined {
|
||||||
|
if (this.regularSeasonBattingStats.length > 0) {
|
||||||
|
// old site behavior just summed regular season stats for the career line total
|
||||||
|
return aggregateBattingStats(this.regularSeasonBattingStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
168
src/components/CareerBattingTable.vue
Normal file
168
src/components/CareerBattingTable.vue
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<div class="row" id="career-batting-row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h3>Batting Stats</h3>
|
||||||
|
<div class="table-responsive-xl">
|
||||||
|
<table class="table table-sm table-striped" id="career-batting">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Season</th>
|
||||||
|
<th>PA</th>
|
||||||
|
<th>AB</th>
|
||||||
|
<th>R</th>
|
||||||
|
<th>H</th>
|
||||||
|
<th>2B</th>
|
||||||
|
<th>3B</th>
|
||||||
|
<th>HR</th>
|
||||||
|
<th>RBI</th>
|
||||||
|
<th>SB</th>
|
||||||
|
<th>CS</th>
|
||||||
|
<th>BB</th>
|
||||||
|
<th>SO</th>
|
||||||
|
<th>BA</th>
|
||||||
|
<th>OBP</th>
|
||||||
|
<th>SLG</th>
|
||||||
|
<th>OPS</th>
|
||||||
|
<th>wOBA</th>
|
||||||
|
<th>K%</th>
|
||||||
|
<th>BPHR</th>
|
||||||
|
<th>BPFO</th>
|
||||||
|
<th>BP1B</th>
|
||||||
|
<th>BPLO</th>
|
||||||
|
<th>GIDP</th>
|
||||||
|
<th>HBP</th>
|
||||||
|
<th>SAC</th>
|
||||||
|
<th>IBB</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="career-batting-table">
|
||||||
|
<tr v-for="stat in sortedRegularAndPostSeasonBatting">
|
||||||
|
<td>S{{ stat.seasonNumber }}{{ stat.isRegularSeason ? '' : ' / Playoffs' }}</td>
|
||||||
|
<td>{{ stat.pa }}</td>
|
||||||
|
<td>{{ stat.ab }}</td>
|
||||||
|
<td>{{ stat.run }}</td>
|
||||||
|
<td>{{ stat.hit }}</td>
|
||||||
|
<td>{{ stat.double }}</td>
|
||||||
|
<td>{{ stat.triple }}</td>
|
||||||
|
<td>{{ stat.hr }}</td>
|
||||||
|
<td>{{ stat.rbi }}</td>
|
||||||
|
<td>{{ stat.sb }}</td>
|
||||||
|
<td>{{ stat.cs }}</td>
|
||||||
|
<td>{{ stat.bb }}</td>
|
||||||
|
<td>{{ stat.so }}</td>
|
||||||
|
<td>{{ stat.avg.toFixed(3) }}</td>
|
||||||
|
<td>{{ stat.obp.toFixed(3) }}</td>
|
||||||
|
<td>{{ stat.slg.toFixed(3) }}</td>
|
||||||
|
<td>{{ stat.ops.toFixed(3) }}</td>
|
||||||
|
<td>{{ stat.woba.toFixed(3) }}</td>
|
||||||
|
<td>{{ calculateStrikeoutPercent(stat) }}</td>
|
||||||
|
<td>{{ stat.bphr }}</td>
|
||||||
|
<td>{{ stat.bpfo }}</td>
|
||||||
|
<td>{{ stat.bp1b }}</td>
|
||||||
|
<td>{{ stat.bplo }}</td>
|
||||||
|
<td>{{ stat.gidp }}</td>
|
||||||
|
<td>{{ stat.hbp }}</td>
|
||||||
|
<td>{{ stat.sac }}</td>
|
||||||
|
<td>{{ stat.ibb }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr v-if="careerBattingStat" id="career-batting-footer">
|
||||||
|
<th>Career</th>
|
||||||
|
<th>{{ careerBattingStat.pa }}</th>
|
||||||
|
<th>{{ careerBattingStat.ab }}</th>
|
||||||
|
<th>{{ careerBattingStat.run }}</th>
|
||||||
|
<th>{{ careerBattingStat.hit }}</th>
|
||||||
|
<th>{{ careerBattingStat.double }}</th>
|
||||||
|
<th>{{ careerBattingStat.triple }}</th>
|
||||||
|
<th>{{ careerBattingStat.hr }}</th>
|
||||||
|
<th>{{ careerBattingStat.rbi }}</th>
|
||||||
|
<th>{{ careerBattingStat.sb }}</th>
|
||||||
|
<th>{{ careerBattingStat.cs }}</th>
|
||||||
|
<th>{{ careerBattingStat.bb }}</th>
|
||||||
|
<th>{{ careerBattingStat.so }}</th>
|
||||||
|
<th>{{ careerBattingStat.avg.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.obp.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.slg.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.ops.toFixed(3) }}</th>
|
||||||
|
<th>{{ careerBattingStat.woba.toFixed(3) }}</th>
|
||||||
|
<th>{{ calculateStrikeoutPercent(careerBattingStat) }}</th>
|
||||||
|
<th>{{ careerBattingStat.bphr }}</th>
|
||||||
|
<th>{{ careerBattingStat.bpfo }}</th>
|
||||||
|
<th>{{ careerBattingStat.bp1b }}</th>
|
||||||
|
<th>{{ careerBattingStat.bplo }}</th>
|
||||||
|
<th>{{ careerBattingStat.gidp }}</th>
|
||||||
|
<th>{{ careerBattingStat.hbp }}</th>
|
||||||
|
<th>{{ careerBattingStat.sac }}</th>
|
||||||
|
<th>{{ careerBattingStat.ibb }}</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { aggregateBattingStats, type BattingStat } from '@/services/battingStatsService'
|
||||||
|
|
||||||
|
interface BattingStatWithSeason extends BattingStat {
|
||||||
|
seasonNumber: number
|
||||||
|
isRegularSeason: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "CareerBattingTable",
|
||||||
|
props: {
|
||||||
|
regularSeasonBattingStats: { type: Array<BattingStat>, required: true },
|
||||||
|
postSeasonBattingStats: { type: Array<BattingStat>, required: true }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
careerBattingStat(): BattingStat | undefined {
|
||||||
|
if (this.regularSeasonBattingStats.length > 0) {
|
||||||
|
// old site behavior just summed regular season stats for the career line total
|
||||||
|
return aggregateBattingStats(this.regularSeasonBattingStats)
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
sortedRegularAndPostSeasonBatting(): BattingStatWithSeason[] {
|
||||||
|
let seasonStats: BattingStatWithSeason[] = []
|
||||||
|
|
||||||
|
if (this.regularSeasonBattingStats?.length) {
|
||||||
|
seasonStats = seasonStats.concat(this.regularSeasonBattingStats.map(stat => {
|
||||||
|
return {
|
||||||
|
...stat,
|
||||||
|
seasonNumber: stat.player.season,
|
||||||
|
isRegularSeason: true
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
// TODO: here would be where you could filter out postseason stats if desired (like Josef requested)
|
||||||
|
if (this.postSeasonBattingStats?.length) {
|
||||||
|
seasonStats = seasonStats.concat(this.postSeasonBattingStats.map(stat => {
|
||||||
|
return {
|
||||||
|
...stat,
|
||||||
|
seasonNumber: stat.player.season,
|
||||||
|
isRegularSeason: false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
return seasonStats.sort((s1, s2) => {
|
||||||
|
return s1.seasonNumber - s2.seasonNumber === 0
|
||||||
|
? s1.isRegularSeason
|
||||||
|
? -1
|
||||||
|
: 1
|
||||||
|
: s1.seasonNumber - s2.seasonNumber
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
calculateStrikeoutPercent(stat: BattingStat): string {
|
||||||
|
if (!stat.pa) return 'N/A'
|
||||||
|
return (stat.so * 100 / stat.pa).toFixed(1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
77
src/components/LastFourGamesBattingTable.vue
Normal file
77
src/components/LastFourGamesBattingTable.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<div class="col-small-12">
|
||||||
|
<h3>Last 4 Games</h3>
|
||||||
|
<div class="table-responsive-xl">
|
||||||
|
<table class="table table-sm table-striped">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Game</th>
|
||||||
|
<th>PA</th>
|
||||||
|
<th>AB</th>
|
||||||
|
<th>R</th>
|
||||||
|
<th>H</th>
|
||||||
|
<th>2B</th>
|
||||||
|
<th>3B</th>
|
||||||
|
<th>HR</th>
|
||||||
|
<th>RBI</th>
|
||||||
|
<th>SB</th>
|
||||||
|
<th>CS</th>
|
||||||
|
<th>BB</th>
|
||||||
|
<th>SO</th>
|
||||||
|
<th>BPHR</th>
|
||||||
|
<th>BPFO</th>
|
||||||
|
<th>BP1B</th>
|
||||||
|
<th>BPLO</th>
|
||||||
|
<th>GIDP</th>
|
||||||
|
<th>HBP</th>
|
||||||
|
<th>SAC</th>
|
||||||
|
<th>IBB</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="last4-batting">
|
||||||
|
<tr v-for="gameStat in last4GamesBatting">
|
||||||
|
<td>{{ makeWxGyFromGame(gameStat.game) }}</td>
|
||||||
|
<td>{{ gameStat.pa }}</td>
|
||||||
|
<td>{{ gameStat.ab }}</td>
|
||||||
|
<td>{{ gameStat.run }}</td>
|
||||||
|
<td>{{ gameStat.hit }}</td>
|
||||||
|
<td>{{ gameStat.double }}</td>
|
||||||
|
<td>{{ gameStat.triple }}</td>
|
||||||
|
<td>{{ gameStat.hr }}</td>
|
||||||
|
<td>{{ gameStat.rbi }}</td>
|
||||||
|
<td>{{ gameStat.sb }}</td>
|
||||||
|
<td>{{ gameStat.cs }}</td>
|
||||||
|
<td>{{ gameStat.bb }}</td>
|
||||||
|
<td>{{ gameStat.so }}</td>
|
||||||
|
<td>{{ gameStat.bphr }}</td>
|
||||||
|
<td>{{ gameStat.bpfo }}</td>
|
||||||
|
<td>{{ gameStat.bp1b }}</td>
|
||||||
|
<td>{{ gameStat.bplo }}</td>
|
||||||
|
<td>{{ gameStat.gidp }}</td>
|
||||||
|
<td>{{ gameStat.hbp }}</td>
|
||||||
|
<td>{{ gameStat.sac }}</td>
|
||||||
|
<td>{{ gameStat.ibb }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Game } from '@/services/apiResponseTypes'
|
||||||
|
import type { BattingStat } from '@/services/battingStatsService'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "LastFourGamesBattingTable",
|
||||||
|
props: {
|
||||||
|
last4GamesBatting: { type: Array<BattingStat>, required: true }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
makeWxGyFromGame(game: Game | 'TOT'): string {
|
||||||
|
if (game === 'TOT') return 'TOT'
|
||||||
|
return `w${game.week}g${game.game_num}`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { Game, Team } from './apiResponseTypes'
|
import type { Game, Team } from './apiResponseTypes'
|
||||||
import type { Player } from './playersService'
|
import type { Player } from './playersService'
|
||||||
import { MODERN_STAT_ERA_START, SITE_URL } from './utilities'
|
import { MODERN_STAT_ERA_START, SITE_URL, avg, obp, ops, slg, woba } from './utilities'
|
||||||
|
|
||||||
// TODO make a stats object that has properties for current regular season, current post season,
|
// TODO make a stats object that has properties for current regular season, current post season,
|
||||||
// last 4 games, historical seasons, career totals
|
// last 4 games, historical seasons, career totals
|
||||||
@ -64,56 +64,6 @@ interface LegacyBattingStat {
|
|||||||
bplo: number
|
bplo: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PitchingStat {
|
|
||||||
player: Player
|
|
||||||
team: Team
|
|
||||||
game: Game | 'TOT'
|
|
||||||
// stats below
|
|
||||||
tbf: number
|
|
||||||
outs: number
|
|
||||||
games: number
|
|
||||||
gs: number
|
|
||||||
win: number
|
|
||||||
loss: number
|
|
||||||
hold: number
|
|
||||||
save: number
|
|
||||||
bsave: number
|
|
||||||
ir: number
|
|
||||||
ir_sc: number
|
|
||||||
ab: number
|
|
||||||
run: number
|
|
||||||
e_run: number
|
|
||||||
hits: number
|
|
||||||
double: number
|
|
||||||
triple: number
|
|
||||||
hr: number
|
|
||||||
bb: number
|
|
||||||
so: number
|
|
||||||
hbp: number
|
|
||||||
sac: number
|
|
||||||
ibb: number
|
|
||||||
gidp: number
|
|
||||||
sb: number
|
|
||||||
cs: number
|
|
||||||
bphr: number
|
|
||||||
bpfo: number
|
|
||||||
bp1b: number
|
|
||||||
bplo: number
|
|
||||||
wp: number
|
|
||||||
balk: number
|
|
||||||
wpa: number
|
|
||||||
era: number
|
|
||||||
whip: number
|
|
||||||
avg: number
|
|
||||||
obp: number
|
|
||||||
slg: number
|
|
||||||
ops: number
|
|
||||||
woba: number
|
|
||||||
"k/9": number
|
|
||||||
"bb/9": number
|
|
||||||
"k/bb": number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FieldingStat {
|
export interface FieldingStat {
|
||||||
player: Player
|
player: Player
|
||||||
team: Team
|
team: Team
|
||||||
@ -162,7 +112,7 @@ export async function fetchBattingStatsBySeasonAndPlayerId(seasonNumber: number,
|
|||||||
if (battingStatsResponse.count === 0) return undefined
|
if (battingStatsResponse.count === 0) return undefined
|
||||||
|
|
||||||
if (battingStatsResponse.count > 1) {
|
if (battingStatsResponse.count > 1) {
|
||||||
throw new Error('statsService.fetchBattingStatsBySeasonAndPlayerId - Expected one stat line for season/player, return contained many')
|
throw new Error('battingStatsService.fetchBattingStatsBySeasonAndPlayerId - Expected one stat line for season/player, return contained many')
|
||||||
}
|
}
|
||||||
|
|
||||||
return battingStatsResponse.stats[0]
|
return battingStatsResponse.stats[0]
|
||||||
@ -179,7 +129,7 @@ async function fetchLegacyBattingStatsBySeasonAndPlayerId(seasonNumber: number,
|
|||||||
if (legacyBattingStatsResponse.count === 0) return undefined
|
if (legacyBattingStatsResponse.count === 0) return undefined
|
||||||
|
|
||||||
if (legacyBattingStatsResponse.count > 1) {
|
if (legacyBattingStatsResponse.count > 1) {
|
||||||
throw new Error('statsService.fetchLegacyBattingStatsBySeasonAndPlayerId - Expected one stat line for season/player, return contained many')
|
throw new Error('battingStatsService.fetchLegacyBattingStatsBySeasonAndPlayerId - Expected one stat line for season/player, return contained many')
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeModernBattingStatFromLegacy(legacyBattingStatsResponse.stats[0])
|
return makeModernBattingStatFromLegacy(legacyBattingStatsResponse.stats[0])
|
||||||
@ -194,7 +144,7 @@ export async function fetchBattingStatsForLastFourGamesBySeasonAndPlayerId(seaso
|
|||||||
} = await response.json()
|
} = await response.json()
|
||||||
|
|
||||||
if (battingStatsResponse.count > 4) {
|
if (battingStatsResponse.count > 4) {
|
||||||
throw new Error(`statsService.fetchBattingStatsForLastFourBySeasonAndPlayerId - Expected at most 4 games, return contained ${battingStatsResponse.count}`)
|
throw new Error(`battingStatsService.fetchBattingStatsForLastFourBySeasonAndPlayerId - Expected at most 4 games, return contained ${battingStatsResponse.count}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return battingStatsResponse.stats
|
return battingStatsResponse.stats
|
||||||
@ -301,31 +251,3 @@ function makeModernBattingStatFromLegacy(legacyStat: LegacyBattingStat): Batting
|
|||||||
woba: woba(legacyStat)
|
woba: woba(legacyStat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function avg(stat: LegacyBattingStat): number {
|
|
||||||
if (stat.ab === 0) return 0
|
|
||||||
|
|
||||||
return stat.hit / stat.ab
|
|
||||||
}
|
|
||||||
|
|
||||||
function obp(stat: LegacyBattingStat): number {
|
|
||||||
if (stat.pa === 0) return 0
|
|
||||||
|
|
||||||
return (stat.hit + stat.bb + stat.ibb + stat.hbp) / stat.pa
|
|
||||||
}
|
|
||||||
function slg(stat: LegacyBattingStat): number {
|
|
||||||
if (stat.ab === 0) return 0
|
|
||||||
|
|
||||||
return (stat.hit + stat.double + (stat.triple * 2) + (stat.hr * 3)) / stat.ab
|
|
||||||
}
|
|
||||||
function ops(stat: LegacyBattingStat): number {
|
|
||||||
return obp(stat) + slg(stat)
|
|
||||||
}
|
|
||||||
function woba(stat: LegacyBattingStat): number {
|
|
||||||
const numerator = (.69 * stat.bb) + (.72 * stat.hbp) + (.89 * (stat.hit - stat.double - stat.triple - stat.hr)) + (1.27 * stat.double) + (1.62 * stat.triple) + (2.1 * stat.hr)
|
|
||||||
const denominator = stat.ab + stat.bb - stat.ibb + stat.sac + stat.hbp
|
|
||||||
|
|
||||||
if (denominator === 0) return 0
|
|
||||||
|
|
||||||
return numerator / denominator
|
|
||||||
}
|
|
||||||
425
src/services/pitchingStatsService.ts
Normal file
425
src/services/pitchingStatsService.ts
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
import type { Game, Team } from './apiResponseTypes'
|
||||||
|
import type { Player } from './playersService'
|
||||||
|
import { MODERN_STAT_ERA_START, SITE_URL, avg, obp, ops, slg, woba } from './utilities'
|
||||||
|
|
||||||
|
// TODO make a stats object that has properties for current regular season, current post season,
|
||||||
|
// last 4 games, historical seasons, career totals
|
||||||
|
// could split into batting/pitching/fielding so only necessary ones are called to save time
|
||||||
|
|
||||||
|
export interface PitchingStat {
|
||||||
|
player: Player
|
||||||
|
team: Team
|
||||||
|
game: Game | 'TOT'
|
||||||
|
// stats below
|
||||||
|
tbf: number
|
||||||
|
outs: number
|
||||||
|
games: number
|
||||||
|
gs: number
|
||||||
|
win: number
|
||||||
|
loss: number
|
||||||
|
hold: number
|
||||||
|
save: number
|
||||||
|
bsave: number
|
||||||
|
ir: number
|
||||||
|
ir_sc: number
|
||||||
|
ab: number
|
||||||
|
run: number
|
||||||
|
e_run: number
|
||||||
|
hits: number
|
||||||
|
double: number
|
||||||
|
triple: number
|
||||||
|
hr: number
|
||||||
|
bb: number
|
||||||
|
so: number
|
||||||
|
hbp: number
|
||||||
|
sac: number
|
||||||
|
ibb: number
|
||||||
|
gidp: number
|
||||||
|
sb: number
|
||||||
|
cs: number
|
||||||
|
bphr: number
|
||||||
|
bpfo: number
|
||||||
|
bp1b: number
|
||||||
|
bplo: number
|
||||||
|
wp: number
|
||||||
|
balk: number
|
||||||
|
wpa: number
|
||||||
|
era: number
|
||||||
|
whip: number
|
||||||
|
avg: number
|
||||||
|
obp: number
|
||||||
|
slg: number
|
||||||
|
ops: number
|
||||||
|
woba: number
|
||||||
|
kPer9: number
|
||||||
|
bbPer9: number
|
||||||
|
kPerBB: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PitchingStatRaw {
|
||||||
|
player: Player
|
||||||
|
team: Team
|
||||||
|
game: Game | 'TOT'
|
||||||
|
// stats below
|
||||||
|
tbf: number
|
||||||
|
outs: number
|
||||||
|
games: number
|
||||||
|
gs: number
|
||||||
|
win: number
|
||||||
|
loss: number
|
||||||
|
hold: number
|
||||||
|
save: number
|
||||||
|
bsave: number
|
||||||
|
ir: number
|
||||||
|
ir_sc: number
|
||||||
|
ab: number
|
||||||
|
run: number
|
||||||
|
e_run: number
|
||||||
|
hits: number
|
||||||
|
double: number
|
||||||
|
triple: number
|
||||||
|
hr: number
|
||||||
|
bb: number
|
||||||
|
so: number
|
||||||
|
hbp: number
|
||||||
|
sac: number
|
||||||
|
ibb: number
|
||||||
|
gidp: number
|
||||||
|
sb: number
|
||||||
|
cs: number
|
||||||
|
bphr: number
|
||||||
|
bpfo: number
|
||||||
|
bp1b: number
|
||||||
|
bplo: number
|
||||||
|
wp: number
|
||||||
|
balk: number
|
||||||
|
wpa: number
|
||||||
|
era: number
|
||||||
|
whip: number
|
||||||
|
avg: number
|
||||||
|
obp: number
|
||||||
|
slg: number
|
||||||
|
ops: number
|
||||||
|
woba: number
|
||||||
|
"k/9": number
|
||||||
|
"bb/9": number
|
||||||
|
"k/bb": number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LegacyPitchingStat {
|
||||||
|
player: Player
|
||||||
|
team: Team
|
||||||
|
ip: number,
|
||||||
|
hit: number,
|
||||||
|
run: number,
|
||||||
|
erun: number,
|
||||||
|
so: number,
|
||||||
|
bb: number,
|
||||||
|
hbp: number,
|
||||||
|
wp: number,
|
||||||
|
balk: number,
|
||||||
|
hr: number,
|
||||||
|
ir: number,
|
||||||
|
irs: number,
|
||||||
|
gs: number,
|
||||||
|
games: number,
|
||||||
|
win: number,
|
||||||
|
loss: number,
|
||||||
|
hold: number,
|
||||||
|
sv: number,
|
||||||
|
bsv: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchPitchingStatsBySeasonAndPlayerId(seasonNumber: number, playerId: number, isRegularSeason: boolean): Promise<PitchingStat | undefined> {
|
||||||
|
// different endpoint for pre-modern stats (/plays) era
|
||||||
|
if (seasonNumber < MODERN_STAT_ERA_START) {
|
||||||
|
return await fetchLegacyPitchingStatsBySeasonAndPlayerId(seasonNumber, playerId, isRegularSeason)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${SITE_URL}/api/v3/plays/pitching?season=${seasonNumber}&player_id=${playerId}&s_type=${isRegularSeason ? 'regular' : 'post'}`)
|
||||||
|
|
||||||
|
const pitchingStatsResponse: {
|
||||||
|
count: number
|
||||||
|
stats: PitchingStat[]
|
||||||
|
} = await response.json()
|
||||||
|
|
||||||
|
if (pitchingStatsResponse.count === 0) return undefined
|
||||||
|
|
||||||
|
if (pitchingStatsResponse.count > 1) {
|
||||||
|
throw new Error('pitchingStatsService.fetchPitchingStatsBySeasonAndPlayerId - Expected one stat line for season/player, return contained many')
|
||||||
|
}
|
||||||
|
|
||||||
|
return pitchingStatsResponse.stats[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLegacyPitchingStatsBySeasonAndPlayerId(seasonNumber: number, playerId: number, isRegularSeason: boolean): Promise<PitchingStat | undefined> {
|
||||||
|
const response = await fetch(`${SITE_URL}/api/v3/pitchingstats/totals?season=${seasonNumber}&player_id=${playerId}&s_type=${isRegularSeason ? 'regular' : 'post'}`)
|
||||||
|
|
||||||
|
const legacyPitchingStatsResponse: {
|
||||||
|
count: number
|
||||||
|
stats: LegacyPitchingStat[]
|
||||||
|
} = await response.json()
|
||||||
|
|
||||||
|
if (legacyPitchingStatsResponse.count === 0) return undefined
|
||||||
|
|
||||||
|
if (legacyPitchingStatsResponse.count > 1) {
|
||||||
|
throw new Error('pitchingStatsService.fetchLegacyPitchingStatsBySeasonAndPlayerId - Expected one stat line for season/player, return contained many')
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeModernPitchingStatFromLegacy(legacyPitchingStatsResponse.stats[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchPitchingStatsForLastFourGamesBySeasonAndPlayerId(seasonNumber: number, playerId: number): Promise<PitchingStat[]> {
|
||||||
|
const response = await fetch(`${SITE_URL}/api/v3/plays/pitching?season=${seasonNumber}&player_id=${playerId}&group_by=playergame&limit=4&sort=newest`)
|
||||||
|
|
||||||
|
const pitchingStatsResponse: {
|
||||||
|
count: number
|
||||||
|
stats: PitchingStat[]
|
||||||
|
} = await response.json()
|
||||||
|
|
||||||
|
if (pitchingStatsResponse.count > 4) {
|
||||||
|
throw new Error(`pitchingStatsService.fetchPitchingStatsForLastFourBySeasonAndPlayerId - Expected at most 4 games, return contained ${pitchingStatsResponse.count}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pitchingStatsResponse.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
export function aggregatePitchingStats(pitchingStats: PitchingStat[]): PitchingStat {
|
||||||
|
let totalStat: PitchingStat = {
|
||||||
|
player: pitchingStats[0].player,
|
||||||
|
team: pitchingStats[0].team,
|
||||||
|
game: 'TOT',
|
||||||
|
// stats below
|
||||||
|
tbf: 0,
|
||||||
|
outs: 0,
|
||||||
|
games: 0,
|
||||||
|
gs: 0,
|
||||||
|
win: 0,
|
||||||
|
loss: 0,
|
||||||
|
hold: 0,
|
||||||
|
save: 0,
|
||||||
|
bsave: 0,
|
||||||
|
ir: 0,
|
||||||
|
ir_sc: 0,
|
||||||
|
ab: 0,
|
||||||
|
run: 0,
|
||||||
|
e_run: 0,
|
||||||
|
hits: 0,
|
||||||
|
double: 0,
|
||||||
|
triple: 0,
|
||||||
|
hr: 0,
|
||||||
|
bb: 0,
|
||||||
|
so: 0,
|
||||||
|
hbp: 0,
|
||||||
|
sac: 0,
|
||||||
|
ibb: 0,
|
||||||
|
gidp: 0,
|
||||||
|
sb: 0,
|
||||||
|
cs: 0,
|
||||||
|
bphr: 0,
|
||||||
|
bpfo: 0,
|
||||||
|
bp1b: 0,
|
||||||
|
bplo: 0,
|
||||||
|
wp: 0,
|
||||||
|
balk: 0,
|
||||||
|
wpa: 0,
|
||||||
|
era: 0,
|
||||||
|
whip: 0,
|
||||||
|
avg: 0,
|
||||||
|
obp: 0,
|
||||||
|
slg: 0,
|
||||||
|
ops: 0,
|
||||||
|
woba: 0,
|
||||||
|
kPer9: 0,
|
||||||
|
bbPer9: 0,
|
||||||
|
kPerBB: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pitchingStats.forEach(stat => {
|
||||||
|
totalStat.tbf += stat.tbf,
|
||||||
|
totalStat.outs += stat.outs,
|
||||||
|
totalStat.games += stat.games,
|
||||||
|
totalStat.gs += stat.gs,
|
||||||
|
totalStat.win += stat.win,
|
||||||
|
totalStat.loss += stat.loss,
|
||||||
|
totalStat.hold += stat.hold,
|
||||||
|
totalStat.save += stat.save,
|
||||||
|
totalStat.bsave += stat.bsave,
|
||||||
|
totalStat.ir += stat.ir,
|
||||||
|
totalStat.ir_sc += stat.ir_sc,
|
||||||
|
totalStat.ab += stat.ab,
|
||||||
|
totalStat.run += stat.run,
|
||||||
|
totalStat.e_run += stat.e_run,
|
||||||
|
totalStat.hits += stat.hits,
|
||||||
|
totalStat.double += stat.double,
|
||||||
|
totalStat.triple += stat.triple,
|
||||||
|
totalStat.hr += stat.hr,
|
||||||
|
totalStat.bb += stat.bb,
|
||||||
|
totalStat.so += stat.so,
|
||||||
|
totalStat.hbp += stat.hbp,
|
||||||
|
totalStat.sac += stat.sac,
|
||||||
|
totalStat.ibb += stat.ibb,
|
||||||
|
totalStat.gidp += stat.gidp,
|
||||||
|
totalStat.sb += stat.sb,
|
||||||
|
totalStat.cs += stat.cs,
|
||||||
|
totalStat.bphr += stat.bphr,
|
||||||
|
totalStat.bpfo += stat.bpfo,
|
||||||
|
totalStat.bp1b += stat.bp1b,
|
||||||
|
totalStat.bplo += stat.bplo,
|
||||||
|
totalStat.wp += stat.wp,
|
||||||
|
totalStat.balk += stat.balk,
|
||||||
|
totalStat.wpa += stat.wpa
|
||||||
|
})
|
||||||
|
|
||||||
|
// a shim since batting stat has "hit" and pitching stat has "hits" and I want to share functions
|
||||||
|
// TODO will need to figure out if there are pas for pitching or if there is an easy way to back into it
|
||||||
|
const totalStatWithHit = { ...totalStat, hit: totalStat.hits }
|
||||||
|
|
||||||
|
return {
|
||||||
|
...totalStat,
|
||||||
|
avg: avg(totalStatWithHit),
|
||||||
|
obp: 0, //obp({pa: totalStat.}),
|
||||||
|
slg: slg(totalStatWithHit),
|
||||||
|
ops: 0, //ops(totalStat),
|
||||||
|
woba: woba(totalStatWithHit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeModernPitchingStatFromLegacy(legacyStat: LegacyPitchingStat): PitchingStat {
|
||||||
|
return {
|
||||||
|
player: legacyStat.player,
|
||||||
|
team: legacyStat.team,
|
||||||
|
game: 'TOT',
|
||||||
|
// stats below
|
||||||
|
outs: Math.round(legacyStat.ip * 3),
|
||||||
|
games: legacyStat.games,
|
||||||
|
gs: legacyStat.gs,
|
||||||
|
win: legacyStat.win,
|
||||||
|
loss: legacyStat.loss,
|
||||||
|
hold: legacyStat.hold,
|
||||||
|
save: legacyStat.sv,
|
||||||
|
bsave: legacyStat.bsv,
|
||||||
|
ir: legacyStat.ir,
|
||||||
|
ir_sc: legacyStat.irs,
|
||||||
|
run: legacyStat.run,
|
||||||
|
e_run: legacyStat.erun,
|
||||||
|
hits: legacyStat.hit,
|
||||||
|
hr: legacyStat.hr,
|
||||||
|
bb: legacyStat.bb,
|
||||||
|
so: legacyStat.so,
|
||||||
|
hbp: legacyStat.hbp,
|
||||||
|
wp: legacyStat.wp,
|
||||||
|
balk: legacyStat.balk,
|
||||||
|
|
||||||
|
// calculated stats
|
||||||
|
era: era({ ip: legacyStat.ip, e_run: legacyStat.erun }),
|
||||||
|
whip: whip({ ip: legacyStat.ip, bb: legacyStat.bb, hits: legacyStat.hit }),
|
||||||
|
kPer9: kPer9(legacyStat),
|
||||||
|
bbPer9: bbPer9(legacyStat),
|
||||||
|
kPerBB: kPerBB(legacyStat),
|
||||||
|
|
||||||
|
// no easy way to get below values from legacy stats afaik
|
||||||
|
tbf: 0,
|
||||||
|
ab: 0,
|
||||||
|
double: 0,
|
||||||
|
triple: 0,
|
||||||
|
sac: 0,
|
||||||
|
ibb: 0,
|
||||||
|
gidp: 0,
|
||||||
|
sb: 0,
|
||||||
|
cs: 0,
|
||||||
|
bphr: 0,
|
||||||
|
bpfo: 0,
|
||||||
|
bp1b: 0,
|
||||||
|
bplo: 0,
|
||||||
|
wpa: 0,
|
||||||
|
avg: 0,
|
||||||
|
obp: 0,
|
||||||
|
slg: 0,
|
||||||
|
ops: 0,
|
||||||
|
woba: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePitchingStat(rawStat: PitchingStatRaw): PitchingStat {
|
||||||
|
return {
|
||||||
|
player: rawStat.player,
|
||||||
|
team: rawStat.team,
|
||||||
|
game: rawStat.game,
|
||||||
|
// stats below
|
||||||
|
tbf: rawStat.tbf,
|
||||||
|
outs: rawStat.outs,
|
||||||
|
games: rawStat.games,
|
||||||
|
gs: rawStat.gs,
|
||||||
|
win: rawStat.win,
|
||||||
|
loss: rawStat.loss,
|
||||||
|
hold: rawStat.hold,
|
||||||
|
save: rawStat.save,
|
||||||
|
bsave: rawStat.bsave,
|
||||||
|
ir: rawStat.ir,
|
||||||
|
ir_sc: rawStat.ir_sc,
|
||||||
|
ab: rawStat.ab,
|
||||||
|
run: rawStat.run,
|
||||||
|
e_run: rawStat.e_run,
|
||||||
|
hits: rawStat.hits,
|
||||||
|
double: rawStat.double,
|
||||||
|
triple: rawStat.triple,
|
||||||
|
hr: rawStat.hr,
|
||||||
|
bb: rawStat.bb,
|
||||||
|
so: rawStat.so,
|
||||||
|
hbp: rawStat.hbp,
|
||||||
|
sac: rawStat.sac,
|
||||||
|
ibb: rawStat.ibb,
|
||||||
|
gidp: rawStat.gidp,
|
||||||
|
sb: rawStat.sb,
|
||||||
|
cs: rawStat.cs,
|
||||||
|
bphr: rawStat.bphr,
|
||||||
|
bpfo: rawStat.bpfo,
|
||||||
|
bp1b: rawStat.bp1b,
|
||||||
|
bplo: rawStat.bplo,
|
||||||
|
wp: rawStat.wp,
|
||||||
|
balk: rawStat.balk,
|
||||||
|
wpa: rawStat.wpa,
|
||||||
|
era: rawStat.era,
|
||||||
|
whip: rawStat.whip,
|
||||||
|
avg: rawStat.avg,
|
||||||
|
obp: rawStat.obp,
|
||||||
|
slg: rawStat.slg,
|
||||||
|
ops: rawStat.ops,
|
||||||
|
woba: rawStat.woba,
|
||||||
|
kPer9: rawStat["k/9"],
|
||||||
|
bbPer9: rawStat["bb/9"],
|
||||||
|
kPerBB: rawStat["k/bb"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function era(stat: { e_run: number, ip: number }): number {
|
||||||
|
if (stat.ip === 0) return stat.e_run > 0 ? NaN : 0
|
||||||
|
|
||||||
|
return stat.e_run * 9 / stat.ip
|
||||||
|
}
|
||||||
|
|
||||||
|
function whip(stat: { bb: number, hits: number, ip: number }): number {
|
||||||
|
if (stat.ip === 0) return 0
|
||||||
|
|
||||||
|
return (stat.bb + stat.hits) / stat.ip
|
||||||
|
}
|
||||||
|
|
||||||
|
function kPer9(stat: { so: number, ip: number }): number {
|
||||||
|
if (stat.ip === 0) return 0
|
||||||
|
|
||||||
|
return stat.so * 9 / stat.ip
|
||||||
|
}
|
||||||
|
|
||||||
|
function bbPer9(stat: { bb: number, ip: number }): number {
|
||||||
|
if (stat.ip === 0) return 0
|
||||||
|
|
||||||
|
return stat.bb * 9 / stat.ip
|
||||||
|
}
|
||||||
|
|
||||||
|
function kPerBB(stat: { so: number, bb: number }): number {
|
||||||
|
if (stat.bb === 0) return 0
|
||||||
|
|
||||||
|
return stat.so / stat.bb
|
||||||
|
}
|
||||||
|
|
||||||
@ -7,3 +7,34 @@ export const MODERN_STAT_ERA_START = 8
|
|||||||
// a type guard to tell typescript that undefined has been filtered and to only consider an array
|
// a type guard to tell typescript that undefined has been filtered and to only consider an array
|
||||||
// of the expected types (no undefineds) after filtering
|
// of the expected types (no undefineds) after filtering
|
||||||
export const isNotUndefined = <S>(value: S | undefined): value is S => value != undefined
|
export const isNotUndefined = <S>(value: S | undefined): value is S => value != undefined
|
||||||
|
|
||||||
|
export function avg(stat: { ab: number, hit: number }): number {
|
||||||
|
if (stat.ab === 0) return 0
|
||||||
|
|
||||||
|
return stat.hit / stat.ab
|
||||||
|
}
|
||||||
|
|
||||||
|
export function obp(stat: { pa: number, hit: number, bb: number, ibb: number, hbp: number }): number {
|
||||||
|
if (stat.pa === 0) return 0
|
||||||
|
|
||||||
|
return (stat.hit + stat.bb + stat.ibb + stat.hbp) / stat.pa
|
||||||
|
}
|
||||||
|
|
||||||
|
export function slg(stat: { ab: number, hit: number, double: number, triple: number, hr: number }): number {
|
||||||
|
if (stat.ab === 0) return 0
|
||||||
|
|
||||||
|
return (stat.hit + stat.double + (stat.triple * 2) + (stat.hr * 3)) / stat.ab
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ops(stat: { pa: number, hit: number, bb: number, ibb: number, hbp: number, ab: number, double: number, triple: number, hr: number }): number {
|
||||||
|
return obp(stat) + slg(stat)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function woba(stat: { bb: number, hbp: number, hit: number, double: number, triple: number, hr: number, ab: number, ibb: number, sac: number }): number {
|
||||||
|
const numerator = (.69 * stat.bb) + (.72 * stat.hbp) + (.89 * (stat.hit - stat.double - stat.triple - stat.hr)) + (1.27 * stat.double) + (1.62 * stat.triple) + (2.1 * stat.hr)
|
||||||
|
const denominator = stat.ab + stat.bb - stat.ibb + stat.sac + stat.hbp
|
||||||
|
|
||||||
|
if (denominator === 0) return 0
|
||||||
|
|
||||||
|
return numerator / denominator
|
||||||
|
}
|
||||||
|
|||||||
@ -31,79 +31,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Batter Summary -->
|
<!-- Summary -->
|
||||||
<div v-if="currentSeasonBatting" class="row" id="batter-summary">
|
<div v-if="isCurrentPlayer" class="row" id="batter-summary">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<h3>Summary</h3>
|
<h3>Summary</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-8">
|
<!-- Batting Summary -->
|
||||||
<div class="table-responsive-xl" style="max-width:45rem">
|
<BattingSummaryTable :current-season-batting="currentSeasonBatting"
|
||||||
<table class="table table-sm table-striped">
|
:current-post-season-batting="currentPostSeasonBatting"
|
||||||
<thead class="thead-dark">
|
:regular-season-batting-stats="regularSeasonBattingStats" />
|
||||||
<tr>
|
|
||||||
<th>Summary</th>
|
|
||||||
<th>AB</th>
|
|
||||||
<th>H</th>
|
|
||||||
<th>HR</th>
|
|
||||||
<th>BA</th>
|
|
||||||
<th>R</th>
|
|
||||||
<th>RBI</th>
|
|
||||||
<th>SB</th>
|
|
||||||
<th>OBP</th>
|
|
||||||
<th>SLG</th>
|
|
||||||
<th>OPS</th>
|
|
||||||
<th>wOBA</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="batter-summary-table">
|
|
||||||
<tr>
|
|
||||||
<td>Season {{ currentSeasonBatting.player.season }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.ab }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.hit }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.hr }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.avg.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.run }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.rbi }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.sb }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.obp.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.slg.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.ops.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentSeasonBatting.woba.toFixed(3) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr v-if="currentPostSeasonBatting">
|
|
||||||
<td>S{{ currentPostSeasonBatting.player.season }} / Playoffs</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.ab }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.hit }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.hr }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.avg.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.run }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.rbi }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.sb }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.obp.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.slg.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.ops.toFixed(3) }}</td>
|
|
||||||
<td>{{ currentPostSeasonBatting.woba.toFixed(3) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot v-if="careerBattingStat" id="batter-summary-footer">
|
|
||||||
<tr>
|
|
||||||
<th>Career</th>
|
|
||||||
<th>{{ careerBattingStat.ab }}</th>
|
|
||||||
<th>{{ careerBattingStat.hit }}</th>
|
|
||||||
<th>{{ careerBattingStat.hr }}</th>
|
|
||||||
<th>{{ careerBattingStat.avg.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.run }}</th>
|
|
||||||
<th>{{ careerBattingStat.rbi }}</th>
|
|
||||||
<th>{{ careerBattingStat.sb }}</th>
|
|
||||||
<th>{{ careerBattingStat.obp.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.slg.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.ops.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.woba.toFixed(3) }}</th>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="table-responsive-xl" style="max-width:20rem">
|
<div class="table-responsive-xl" style="max-width:20rem">
|
||||||
<table class="table table-sm table-striped">
|
<table class="table table-sm table-striped">
|
||||||
@ -125,184 +61,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Last 4 Games -->
|
<!-- Last 4 Games -->
|
||||||
<div class="col-small-12">
|
<LastFourGamesBattingTable :last4-games-batting="last4Games" />
|
||||||
<h3>Last 4 Games</h3>
|
|
||||||
<div class="table-responsive-xl">
|
|
||||||
<table class="table table-sm table-striped">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th>Game</th>
|
|
||||||
<th>PA</th>
|
|
||||||
<th>AB</th>
|
|
||||||
<th>R</th>
|
|
||||||
<th>H</th>
|
|
||||||
<th>2B</th>
|
|
||||||
<th>3B</th>
|
|
||||||
<th>HR</th>
|
|
||||||
<th>RBI</th>
|
|
||||||
<th>SB</th>
|
|
||||||
<th>CS</th>
|
|
||||||
<th>BB</th>
|
|
||||||
<th>SO</th>
|
|
||||||
<th>BPHR</th>
|
|
||||||
<th>BPFO</th>
|
|
||||||
<th>BP1B</th>
|
|
||||||
<th>BPLO</th>
|
|
||||||
<th>GIDP</th>
|
|
||||||
<th>HBP</th>
|
|
||||||
<th>SAC</th>
|
|
||||||
<th>IBB</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="last4-batting">
|
|
||||||
<tr v-for="gameStat in last4Games">
|
|
||||||
<td>{{ makeWxGyFromGame(gameStat.game) }}</td>
|
|
||||||
<td>{{ gameStat.pa }}</td>
|
|
||||||
<td>{{ gameStat.ab }}</td>
|
|
||||||
<td>{{ gameStat.run }}</td>
|
|
||||||
<td>{{ gameStat.hit }}</td>
|
|
||||||
<td>{{ gameStat.double }}</td>
|
|
||||||
<td>{{ gameStat.triple }}</td>
|
|
||||||
<td>{{ gameStat.hr }}</td>
|
|
||||||
<td>{{ gameStat.rbi }}</td>
|
|
||||||
<td>{{ gameStat.sb }}</td>
|
|
||||||
<td>{{ gameStat.cs }}</td>
|
|
||||||
<td>{{ gameStat.bb }}</td>
|
|
||||||
<td>{{ gameStat.so }}</td>
|
|
||||||
<td>{{ gameStat.bphr }}</td>
|
|
||||||
<td>{{ gameStat.bpfo }}</td>
|
|
||||||
<td>{{ gameStat.bp1b }}</td>
|
|
||||||
<td>{{ gameStat.bplo }}</td>
|
|
||||||
<td>{{ gameStat.gidp }}</td>
|
|
||||||
<td>{{ gameStat.hbp }}</td>
|
|
||||||
<td>{{ gameStat.sac }}</td>
|
|
||||||
<td>{{ gameStat.ibb }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Career Batting -->
|
<!-- Career Batting -->
|
||||||
<div class="row" id="career-batting-row">
|
<CareerBattingTable :regular-season-batting-stats="regularSeasonBattingStats"
|
||||||
<div class="col-sm-12">
|
:post-season-batting-stats="postSeasonBattingStats" />
|
||||||
<h3>Batting Stats</h3>
|
|
||||||
<div class="table-responsive-xl">
|
|
||||||
<table class="table table-sm table-striped" id="career-batting">
|
|
||||||
<thead class="thead-dark">
|
|
||||||
<tr>
|
|
||||||
<th>Season</th>
|
|
||||||
<th>PA</th>
|
|
||||||
<th>AB</th>
|
|
||||||
<th>R</th>
|
|
||||||
<th>H</th>
|
|
||||||
<th>2B</th>
|
|
||||||
<th>3B</th>
|
|
||||||
<th>HR</th>
|
|
||||||
<th>RBI</th>
|
|
||||||
<th>SB</th>
|
|
||||||
<th>CS</th>
|
|
||||||
<th>BB</th>
|
|
||||||
<th>SO</th>
|
|
||||||
<th>BA</th>
|
|
||||||
<th>OBP</th>
|
|
||||||
<th>SLG</th>
|
|
||||||
<th>OPS</th>
|
|
||||||
<th>wOBA</th>
|
|
||||||
<th>K%</th>
|
|
||||||
<th>BPHR</th>
|
|
||||||
<th>BPFO</th>
|
|
||||||
<th>BP1B</th>
|
|
||||||
<th>BPLO</th>
|
|
||||||
<th>GIDP</th>
|
|
||||||
<th>HBP</th>
|
|
||||||
<th>SAC</th>
|
|
||||||
<th>IBB</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="career-batting-table">
|
|
||||||
<tr v-for="stat in sortedRegularAndPostSeasonBatting">
|
|
||||||
<td>S{{ stat.seasonNumber }}{{ stat.isRegularSeason ? '' : ' / Playoffs' }}</td>
|
|
||||||
<td>{{ stat.pa }}</td>
|
|
||||||
<td>{{ stat.ab }}</td>
|
|
||||||
<td>{{ stat.run }}</td>
|
|
||||||
<td>{{ stat.hit }}</td>
|
|
||||||
<td>{{ stat.double }}</td>
|
|
||||||
<td>{{ stat.triple }}</td>
|
|
||||||
<td>{{ stat.hr }}</td>
|
|
||||||
<td>{{ stat.rbi }}</td>
|
|
||||||
<td>{{ stat.sb }}</td>
|
|
||||||
<td>{{ stat.cs }}</td>
|
|
||||||
<td>{{ stat.bb }}</td>
|
|
||||||
<td>{{ stat.so }}</td>
|
|
||||||
<td>{{ stat.avg.toFixed(3) }}</td>
|
|
||||||
<td>{{ stat.obp.toFixed(3) }}</td>
|
|
||||||
<td>{{ stat.slg.toFixed(3) }}</td>
|
|
||||||
<td>{{ stat.ops.toFixed(3) }}</td>
|
|
||||||
<td>{{ stat.woba.toFixed(3) }}</td>
|
|
||||||
<td>{{ calculateStrikeoutPercent(stat) }}</td>
|
|
||||||
<td>{{ stat.bphr }}</td>
|
|
||||||
<td>{{ stat.bpfo }}</td>
|
|
||||||
<td>{{ stat.bp1b }}</td>
|
|
||||||
<td>{{ stat.bplo }}</td>
|
|
||||||
<td>{{ stat.gidp }}</td>
|
|
||||||
<td>{{ stat.hbp }}</td>
|
|
||||||
<td>{{ stat.sac }}</td>
|
|
||||||
<td>{{ stat.ibb }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tfoot>
|
|
||||||
<tr v-if="careerBattingStat" id="career-batting-footer">
|
|
||||||
<th>Career</th>
|
|
||||||
<th>{{ careerBattingStat.pa }}</th>
|
|
||||||
<th>{{ careerBattingStat.ab }}</th>
|
|
||||||
<th>{{ careerBattingStat.run }}</th>
|
|
||||||
<th>{{ careerBattingStat.hit }}</th>
|
|
||||||
<th>{{ careerBattingStat.double }}</th>
|
|
||||||
<th>{{ careerBattingStat.triple }}</th>
|
|
||||||
<th>{{ careerBattingStat.hr }}</th>
|
|
||||||
<th>{{ careerBattingStat.rbi }}</th>
|
|
||||||
<th>{{ careerBattingStat.sb }}</th>
|
|
||||||
<th>{{ careerBattingStat.cs }}</th>
|
|
||||||
<th>{{ careerBattingStat.bb }}</th>
|
|
||||||
<th>{{ careerBattingStat.so }}</th>
|
|
||||||
<th>{{ careerBattingStat.avg.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.obp.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.slg.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.ops.toFixed(3) }}</th>
|
|
||||||
<th>{{ careerBattingStat.woba.toFixed(3) }}</th>
|
|
||||||
<th>{{ calculateStrikeoutPercent(careerBattingStat) }}</th>
|
|
||||||
<th>{{ careerBattingStat.bphr }}</th>
|
|
||||||
<th>{{ careerBattingStat.bpfo }}</th>
|
|
||||||
<th>{{ careerBattingStat.bp1b }}</th>
|
|
||||||
<th>{{ careerBattingStat.bplo }}</th>
|
|
||||||
<th>{{ careerBattingStat.gidp }}</th>
|
|
||||||
<th>{{ careerBattingStat.hbp }}</th>
|
|
||||||
<th>{{ careerBattingStat.sac }}</th>
|
|
||||||
<th>{{ careerBattingStat.ibb }}</th>
|
|
||||||
</tr>
|
|
||||||
</tfoot>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Game } from '@/services/apiResponseTypes'
|
|
||||||
import { isDiscordAuthenticated } from '@/services/authenticationService'
|
import { isDiscordAuthenticated } from '@/services/authenticationService'
|
||||||
import { fetchLast2DecisionsByPlayerId, type Decision } from '@/services/decisionsService'
|
import { fetchLast2DecisionsByPlayerId, type Decision } from '@/services/decisionsService'
|
||||||
import { type Player, fetchPlayerByName } from '@/services/playersService'
|
import { type Player, fetchPlayerByName } from '@/services/playersService'
|
||||||
import { fetchBattingStatsBySeasonAndPlayerId, fetchBattingStatsForLastFourGamesBySeasonAndPlayerId, aggregateBattingStats, type BattingStat } from '@/services/statsService'
|
import { fetchBattingStatsBySeasonAndPlayerId, fetchBattingStatsForLastFourGamesBySeasonAndPlayerId, type BattingStat } from '@/services/battingStatsService'
|
||||||
import { CURRENT_SEASON, isNotUndefined } from '@/services/utilities'
|
import { CURRENT_SEASON, isNotUndefined } from '@/services/utilities'
|
||||||
|
import LastFourGamesBattingTable from '@/components/LastFourGamesBattingTable.vue'
|
||||||
interface BattingStatWithSeason extends BattingStat {
|
import CareerBattingTable from '@/components/CareerBattingTable.vue'
|
||||||
seasonNumber: number
|
import BattingSummaryTable from '@/components/BattingSummaryTable.vue'
|
||||||
isRegularSeason: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PlayerView",
|
name: "PlayerView",
|
||||||
@ -338,7 +115,8 @@ export default {
|
|||||||
return this.player?.team?.thumbnail
|
return this.player?.team?.thumbnail
|
||||||
},
|
},
|
||||||
baseballReferenceUrl(): string | undefined {
|
baseballReferenceUrl(): string | undefined {
|
||||||
if (!this.player?.bbref_id) return undefined
|
if (!this.player?.bbref_id)
|
||||||
|
return undefined
|
||||||
const firstChar = this.player.bbref_id.slice(0, 1)
|
const firstChar = this.player.bbref_id.slice(0, 1)
|
||||||
return `https://www.baseball-reference.com/players/${firstChar}/${this.player.bbref_id}.shtml`
|
return `https://www.baseball-reference.com/players/${firstChar}/${this.player.bbref_id}.shtml`
|
||||||
},
|
},
|
||||||
@ -346,71 +124,45 @@ export default {
|
|||||||
return this.player?.vanity_card ?? this.teamThumbnail
|
return this.player?.vanity_card ?? this.teamThumbnail
|
||||||
},
|
},
|
||||||
playerCardImage1Url(): string | undefined {
|
playerCardImage1Url(): string | undefined {
|
||||||
if (!this.isAuthenticated) return undefined
|
if (!this.isAuthenticated)
|
||||||
|
return undefined
|
||||||
return this.player?.image
|
return this.player?.image
|
||||||
},
|
},
|
||||||
playerCardImage2Url(): string | undefined {
|
playerCardImage2Url(): string | undefined {
|
||||||
if (!this.isAuthenticated) return undefined
|
if (!this.isAuthenticated)
|
||||||
|
return undefined
|
||||||
return this.player?.image2
|
return this.player?.image2
|
||||||
},
|
},
|
||||||
injuryReturnDate(): string | undefined {
|
injuryReturnDate(): string | undefined {
|
||||||
if (!this.player?.il_return) return undefined
|
if (!this.player?.il_return)
|
||||||
|
return undefined
|
||||||
return ` 🏥 (${this.player.il_return})`
|
return ` 🏥 (${this.player.il_return})`
|
||||||
},
|
},
|
||||||
injuryRating(): string | undefined {
|
injuryRating(): string | undefined {
|
||||||
return this.player?.injury_rating
|
return this.player?.injury_rating
|
||||||
},
|
},
|
||||||
currentSeasonBatting(): BattingStat | undefined {
|
currentSeasonBatting(): BattingStat | undefined {
|
||||||
if (!this.regularSeasonBattingStats.length) return undefined
|
if (!this.regularSeasonBattingStats.length)
|
||||||
|
return undefined
|
||||||
return this.regularSeasonBattingStats.find(stat => stat.player.season === CURRENT_SEASON)
|
return this.regularSeasonBattingStats.find(stat => stat.player.season === CURRENT_SEASON)
|
||||||
},
|
},
|
||||||
currentPostSeasonBatting(): BattingStat | undefined {
|
currentPostSeasonBatting(): BattingStat | undefined {
|
||||||
if (!this.postSeasonBattingStats.length) return undefined
|
if (!this.postSeasonBattingStats.length)
|
||||||
|
return undefined
|
||||||
return this.postSeasonBattingStats.find(stat => stat.player.season === CURRENT_SEASON)
|
return this.postSeasonBattingStats.find(stat => stat.player.season === CURRENT_SEASON)
|
||||||
},
|
},
|
||||||
careerBattingStat(): BattingStat | undefined {
|
|
||||||
if (this.regularSeasonBattingStats.length > 0) {
|
|
||||||
// old site behavior just summed regular season stats for the career line total
|
|
||||||
return aggregateBattingStats(this.regularSeasonBattingStats)
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined
|
|
||||||
},
|
|
||||||
sortedRegularAndPostSeasonBatting(): BattingStatWithSeason[] {
|
|
||||||
let seasonStats: BattingStatWithSeason[] = this.regularSeasonBattingStats.map(stat => {
|
|
||||||
return {
|
|
||||||
...stat,
|
|
||||||
seasonNumber: stat.player.season,
|
|
||||||
isRegularSeason: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// TODO: here would be where you could filter out postseason stats if desired (like Josef requested)
|
|
||||||
seasonStats = seasonStats.concat(this.postSeasonBattingStats.map(stat => {
|
|
||||||
return {
|
|
||||||
...stat,
|
|
||||||
seasonNumber: stat.player.season,
|
|
||||||
isRegularSeason: false
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
return seasonStats.sort((s1, s2) => {
|
|
||||||
return s1.seasonNumber - s2.seasonNumber === 0
|
|
||||||
? s1.isRegularSeason
|
|
||||||
? -1
|
|
||||||
: 1
|
|
||||||
: s1.seasonNumber - s2.seasonNumber
|
|
||||||
})
|
|
||||||
},
|
|
||||||
lastAppearance(): string | undefined {
|
lastAppearance(): string | undefined {
|
||||||
if (!this.last2Decisions?.length) return undefined
|
if (!this.last2Decisions?.length)
|
||||||
if (this.last2Decisions.length <= 0) return undefined
|
return undefined
|
||||||
|
if (this.last2Decisions.length <= 0)
|
||||||
|
return undefined
|
||||||
return this.formatDecisionToAppearance(this.last2Decisions[0])
|
return this.formatDecisionToAppearance(this.last2Decisions[0])
|
||||||
},
|
},
|
||||||
secondLastAppearance(): string | undefined {
|
secondLastAppearance(): string | undefined {
|
||||||
if (!this.last2Decisions?.length) return undefined
|
if (!this.last2Decisions?.length)
|
||||||
if (this.last2Decisions.length <= 1) return '-'
|
return undefined
|
||||||
|
if (this.last2Decisions.length <= 1)
|
||||||
|
return '-'
|
||||||
return this.formatDecisionToAppearance(this.last2Decisions[1])
|
return this.formatDecisionToAppearance(this.last2Decisions[1])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -419,21 +171,22 @@ export default {
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
seasonNumber(newValue, oldValue) {
|
seasonNumber(newValue, oldValue) {
|
||||||
if (newValue !== oldValue) this.fetchData()
|
if (newValue !== oldValue)
|
||||||
|
this.fetchData()
|
||||||
},
|
},
|
||||||
playerName(newName, oldName) {
|
playerName(newName, oldName) {
|
||||||
if (newName !== oldName) this.fetchData()
|
if (newName !== oldName)
|
||||||
|
this.fetchData()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async fetchData(): Promise<void> {
|
async fetchData(): Promise<void> {
|
||||||
this.isAuthenticated = await isDiscordAuthenticated()
|
this.isAuthenticated = await isDiscordAuthenticated()
|
||||||
this.player = await this.tryFetchPlayerByNameForAnySeason(this.seasonNumber, this.playerName)
|
this.player = await this.tryFetchPlayerByNameForAnySeason(this.seasonNumber, this.playerName)
|
||||||
if (!this.player) return
|
if (!this.player)
|
||||||
|
return
|
||||||
this.last2Decisions = await fetchLast2DecisionsByPlayerId(this.seasonNumber, this.player.id)
|
this.last2Decisions = await fetchLast2DecisionsByPlayerId(this.seasonNumber, this.player.id)
|
||||||
this.last4Games = await fetchBattingStatsForLastFourGamesBySeasonAndPlayerId(this.seasonNumber, this.player.id)
|
this.last4Games = await fetchBattingStatsForLastFourGamesBySeasonAndPlayerId(this.seasonNumber, this.player.id)
|
||||||
|
|
||||||
// TODO: this should change, either with an api that can take a player name for every season, a way
|
// TODO: this should change, either with an api that can take a player name for every season, a way
|
||||||
// to get multiple seasons stats at once, or a players ids across all seasons at once
|
// to get multiple seasons stats at once, or a players ids across all seasons at once
|
||||||
const playerSeasons = await Promise.all(Array.from(Array(CURRENT_SEASON), (element, index) => index + 1).map(seasonNumber => fetchPlayerByName(seasonNumber, this.player!.name)))
|
const playerSeasons = await Promise.all(Array.from(Array(CURRENT_SEASON), (element, index) => index + 1).map(seasonNumber => fetchPlayerByName(seasonNumber, this.player!.name)))
|
||||||
@ -443,20 +196,13 @@ export default {
|
|||||||
async tryFetchPlayerByNameForAnySeason(seasonNumber: number, playerName: string): Promise<Player | undefined> {
|
async tryFetchPlayerByNameForAnySeason(seasonNumber: number, playerName: string): Promise<Player | undefined> {
|
||||||
do {
|
do {
|
||||||
const player: Player | undefined = await fetchPlayerByName(seasonNumber, playerName)
|
const player: Player | undefined = await fetchPlayerByName(seasonNumber, playerName)
|
||||||
if (player) return player
|
if (player)
|
||||||
|
return player
|
||||||
seasonNumber--
|
seasonNumber--
|
||||||
} while (seasonNumber > 0)
|
} while (seasonNumber > 0)
|
||||||
},
|
},
|
||||||
formatDecisionToAppearance(decision: Decision): string {
|
formatDecisionToAppearance(decision: Decision): string {
|
||||||
return `${decision.rest_ip.toFixed(1)}IP w${decision.week}g${decision.game_num}`
|
return `${decision.rest_ip.toFixed(1)}IP w${decision.week}g${decision.game_num}`
|
||||||
},
|
|
||||||
makeWxGyFromGame(game: Game | 'TOT'): string {
|
|
||||||
if (game === 'TOT') return 'TOT'
|
|
||||||
return `w${game.week}g${game.game_num}`
|
|
||||||
},
|
|
||||||
calculateStrikeoutPercent(stat: BattingStat): string {
|
|
||||||
if (!stat.pa) return 'N/A'
|
|
||||||
return (stat.so * 100 / stat.pa).toFixed(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user