Calculating wpa for plays
This commit is contained in:
parent
d0f635034b
commit
e399fec853
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import discord
|
import discord
|
||||||
|
import pandas as pd
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
@ -13,13 +14,84 @@ from utilities.embeds import image_embed
|
|||||||
from utilities.pages import Pagination
|
from utilities.pages import Pagination
|
||||||
|
|
||||||
|
|
||||||
|
WPA_DF = pd.read_csv(f'storage/wpa_data.csv').set_index('index')
|
||||||
|
|
||||||
|
|
||||||
|
def get_obc(on_first = None, on_second = None, on_third = None) -> int:
|
||||||
|
if on_third is not None:
|
||||||
|
if on_second is not None:
|
||||||
|
if on_first is not None:
|
||||||
|
obc = 7
|
||||||
|
else:
|
||||||
|
obc = 6
|
||||||
|
elif on_first is not None:
|
||||||
|
obc = 5
|
||||||
|
else:
|
||||||
|
obc = 3
|
||||||
|
elif on_second is not None:
|
||||||
|
if on_first is not None:
|
||||||
|
obc = 4
|
||||||
|
else:
|
||||||
|
obc = 2
|
||||||
|
elif on_first is not None:
|
||||||
|
obc = 1
|
||||||
|
else:
|
||||||
|
obc = 0
|
||||||
|
|
||||||
|
return obc
|
||||||
|
|
||||||
|
|
||||||
|
def get_re24(this_play: Play, runs_scored: int, new_obc: int, new_starting_outs: int) -> float:
|
||||||
|
re_data = {
|
||||||
|
0: [0.457, 0.231, 0.077],
|
||||||
|
1: [0.793, 0.438, 0.171],
|
||||||
|
2: [1.064, 0.596, 0.259],
|
||||||
|
4: [1.373, 0.772, 0.351],
|
||||||
|
3: [1.340, 0.874, 0.287],
|
||||||
|
5: [1.687, 1.042, 0.406],
|
||||||
|
6: [1.973, 1.311, 0.448],
|
||||||
|
7: [2.295, 1.440, 0.618]
|
||||||
|
}
|
||||||
|
|
||||||
|
start_re24 = re_data[this_play.on_base_code][this_play.starting_outs]
|
||||||
|
end_re24 = 0 if this_play.starting_outs + this_play.outs > 2 else re_data[new_obc][new_starting_outs]
|
||||||
|
return round(end_re24 - start_re24 + runs_scored, 3)
|
||||||
|
|
||||||
|
|
||||||
|
def get_wpa(old_play: Play, new_play: Play):
|
||||||
|
new_rd = new_play.home_score - new_play.away_score
|
||||||
|
if new_rd > 6:
|
||||||
|
new_rd = 6
|
||||||
|
elif new_rd < -6:
|
||||||
|
new_rd = -6
|
||||||
|
|
||||||
|
old_rd = old_play.home_score - old_play.away_score
|
||||||
|
if old_rd > 6:
|
||||||
|
old_rd = 6
|
||||||
|
elif old_rd < -6:
|
||||||
|
old_rd = -6
|
||||||
|
|
||||||
|
new_win_ex = WPA_DF.loc[f'{new_play.inning_half}_{new_play.inning_num}_{new_play.starting_outs}_out_{new_play.on_base_code}_obc_{new_rd}_home_run_diff'].home_win_ex
|
||||||
|
|
||||||
|
old_win_ex = WPA_DF.loc[f'{old_play.inning_half}_{old_play.inning_num}_{old_play.starting_outs}_out_{old_play.on_base_code}_obc_{old_rd}_home_run_diff'].home_win_ex
|
||||||
|
|
||||||
|
return round(new_win_ex - old_win_ex, 3)
|
||||||
|
|
||||||
|
|
||||||
def complete_play(session:Session, this_play: Play):
|
def complete_play(session:Session, this_play: Play):
|
||||||
nso = this_play.starting_outs + this_play.outs
|
nso = this_play.starting_outs + this_play.outs
|
||||||
|
runs_scored = 0
|
||||||
|
on_first, on_second, on_third = None, None, None
|
||||||
|
|
||||||
if nso >= 3:
|
if nso >= 3:
|
||||||
switch_sides = True
|
switch_sides = True
|
||||||
|
obc = 0
|
||||||
nso = 0
|
nso = 0
|
||||||
|
is_go_ahead = False
|
||||||
nih = 'bot' if this_play.inning_half.lower() == 'top' else 'top'
|
nih = 'bot' if this_play.inning_half.lower() == 'top' else 'top'
|
||||||
|
away_score = this_play.away_score
|
||||||
|
home_score = this_play.home_score
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opponent_play = get_last_team_play(session, this_play.game, this_play.pitcher.team)
|
opponent_play = get_last_team_play(session, this_play.game, this_play.pitcher.team)
|
||||||
nbo = opponent_play.batting_order + 1
|
nbo = opponent_play.batting_order + 1
|
||||||
@ -29,28 +101,78 @@ def complete_play(session:Session, this_play: Play):
|
|||||||
finally:
|
finally:
|
||||||
new_batter_team = this_play.game.away_team if nih == 'top' else this_play.game.home_team
|
new_batter_team = this_play.game.away_team if nih == 'top' else this_play.game.home_team
|
||||||
new_pitcher_team = this_play.game.away_team if nih == 'bot' else this_play.game.home_team
|
new_pitcher_team = this_play.game.away_team if nih == 'bot' else this_play.game.home_team
|
||||||
|
inning = this_play.inning_num if nih == 'bot' else this_play.inning_num + 1
|
||||||
|
|
||||||
else:
|
else:
|
||||||
switch_sides = False
|
switch_sides = False
|
||||||
nbo = this_play.batting_order + 1 if this_play.pa == 1 else this_play.batting_order
|
nbo = this_play.batting_order + 1 if this_play.pa == 1 else this_play.batting_order
|
||||||
nih = this_play.inning_half
|
nih = this_play.inning_half
|
||||||
new_batter_team = this_play.batter.team
|
new_batter_team = this_play.batter.team
|
||||||
new_pitcher_team = this_play.pitcher.team
|
new_pitcher_team = this_play.pitcher.team
|
||||||
|
inning = this_play.inning_num
|
||||||
|
|
||||||
|
for this_runner, runner_dest in [
|
||||||
|
(this_play.batter, this_play.batter_final), (this_play.on_first, this_play.on_first_final), (this_play.on_second, this_play.on_second_final), (this_play.on_third, this_play.on_third_final)
|
||||||
|
]:
|
||||||
|
if runner_dest is not None:
|
||||||
|
if runner_dest == 1:
|
||||||
|
if on_first is not None:
|
||||||
|
log_exception(ValueError, f'Cannot place {this_runner.player.name} on first; {on_first.player.name} is already placed there')
|
||||||
|
on_first = this_runner
|
||||||
|
elif runner_dest == 2:
|
||||||
|
if on_second is not None:
|
||||||
|
log_exception(ValueError, f'Cannot place {this_runner.player.name} on second; {on_second.player.name} is already placed there')
|
||||||
|
on_second = this_runner
|
||||||
|
elif runner_dest == 3:
|
||||||
|
if on_third is not None:
|
||||||
|
log_exception(ValueError, f'Cannot place {this_runner.player.name} on third; {on_third.player.name} is already placed there')
|
||||||
|
on_third = this_runner
|
||||||
|
elif runner_dest == 4:
|
||||||
|
runs_scored += 1
|
||||||
|
|
||||||
|
if this_play.inning_half == 'top':
|
||||||
|
away_score = this_play.away_score + runs_scored
|
||||||
|
home_score = this_play.home_score
|
||||||
|
|
||||||
|
if runs_scored > 0 and this_play.away_score <= this_play.home_score and away_score > home_score:
|
||||||
|
is_go_ahead = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
away_score = this_play.away_score
|
||||||
|
home_score = this_play.home_score + runs_scored
|
||||||
|
|
||||||
|
if runs_scored > 0 and this_play.home_score <= this_play.away_score and home_score > away_score:
|
||||||
|
is_go_ahead = True
|
||||||
|
|
||||||
|
obc = get_obc(on_first, on_second, on_third)
|
||||||
|
|
||||||
|
this_play.re24 = get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso)
|
||||||
|
|
||||||
if nbo > 9:
|
if nbo > 9:
|
||||||
nbo = 1
|
nbo = 1
|
||||||
|
|
||||||
# TODO: Set baserunners
|
new_batter = get_one_lineup(session, this_play.game, new_batter_team, batting_order=nbo)
|
||||||
|
|
||||||
new_play = Play(
|
new_play = Play(
|
||||||
game=this_play.game,
|
game=this_play.game,
|
||||||
play_num=this_play.play_num + 1,
|
play_num=this_play.play_num + 1,
|
||||||
batting_order=nbo,
|
batting_order=nbo,
|
||||||
inning_half=nih,
|
inning_half=nih,
|
||||||
|
inning_num=inning,
|
||||||
starting_outs=nso,
|
starting_outs=nso,
|
||||||
batter=get_one_lineup(session, this_play.game, new_batter_team, batting_order=nbo),
|
on_base_code=obc,
|
||||||
|
away_score=away_score,
|
||||||
|
home_score=home_score,
|
||||||
|
batter=new_batter,
|
||||||
|
batter_pos=new_batter.position,
|
||||||
pitcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='P'),
|
pitcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='P'),
|
||||||
catcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='C'),
|
catcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='C'),
|
||||||
is_new_inning=switch_sides,
|
is_new_inning=switch_sides,
|
||||||
|
is_tied=away_score == home_score,
|
||||||
|
is_go_ahead=is_go_ahead,
|
||||||
|
on_first=on_first,
|
||||||
|
on_second=on_second,
|
||||||
|
on_third=on_third,
|
||||||
managerai=this_play.managerai
|
managerai=this_play.managerai
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -459,11 +581,10 @@ async def flyballs(session: Session, interaction: discord.Interaction, this_game
|
|||||||
await question.delete()
|
await question.delete()
|
||||||
|
|
||||||
elif flyball_type == 'c':
|
elif flyball_type == 'c':
|
||||||
patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1)
|
this_play.pa, this_play.ab, this_play.outs = 1, 1, 1
|
||||||
advance_runners(this_play.id, num_bases=0)
|
|
||||||
|
|
||||||
if comp_play:
|
if comp_play:
|
||||||
complete_play(this_play.id)
|
complete_play(session, this_play)
|
||||||
|
|
||||||
session.refresh(this_play)
|
session.refresh(this_play)
|
||||||
return this_play
|
return this_play
|
||||||
|
|||||||
@ -568,12 +568,12 @@ class PlayBase(SQLModel):
|
|||||||
in_pow: bool = Field(default=False)
|
in_pow: bool = Field(default=False)
|
||||||
|
|
||||||
on_first_id: int | None = Field(default=None, foreign_key='lineup.id')
|
on_first_id: int | None = Field(default=None, foreign_key='lineup.id')
|
||||||
on_first_final: int | None = Field(default=None)
|
on_first_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = no change
|
||||||
on_second_id: int | None = Field(default=None, foreign_key='lineup.id')
|
on_second_id: int | None = Field(default=None, foreign_key='lineup.id')
|
||||||
on_second_final: int | None = Field(default=None)
|
on_second_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = no change
|
||||||
on_third_id: int | None = Field(default=None, foreign_key='lineup.id')
|
on_third_id: int | None = Field(default=None, foreign_key='lineup.id')
|
||||||
on_third_final: int | None = Field(default=None)
|
on_third_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = no change
|
||||||
batter_final: int | None = Field(default=None)
|
batter_final: int | None = Field(default=None) # 99 = out, 1-4 = base, None = out
|
||||||
|
|
||||||
pa: int = Field(default=0, ge=0, le=1)
|
pa: int = Field(default=0, ge=0, le=1)
|
||||||
ab: int = Field(default=0, ge=0, le=1)
|
ab: int = Field(default=0, ge=0, le=1)
|
||||||
|
|||||||
@ -8,3 +8,4 @@ sqlmodel
|
|||||||
alembic
|
alembic
|
||||||
pytest
|
pytest
|
||||||
pytest-asyncio
|
pytest-asyncio
|
||||||
|
pandas
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
from command_logic.logic_gameplay import advance_runners
|
from command_logic.logic_gameplay import advance_runners, get_obc, get_re24, get_wpa
|
||||||
from tests.factory import session_fixture, Game
|
from tests.factory import session_fixture, Game
|
||||||
|
|
||||||
|
|
||||||
@ -15,3 +15,33 @@ def test_advance_runners(session: Session):
|
|||||||
assert play_1.on_third_id is None
|
assert play_1.on_third_id is None
|
||||||
|
|
||||||
# TODO: Test advance runners once "advance play" function is ready
|
# TODO: Test advance runners once "advance play" function is ready
|
||||||
|
|
||||||
|
def test_get_obc():
|
||||||
|
assert get_obc() == 0
|
||||||
|
assert get_obc(on_first=True) == 1
|
||||||
|
assert get_obc(on_second=True) == 2
|
||||||
|
assert get_obc(on_third=True) == 3
|
||||||
|
assert get_obc(on_first=True, on_second=True) == 4
|
||||||
|
assert get_obc(on_first=True, on_third=True) == 5
|
||||||
|
assert get_obc(on_second=True, on_third=True) == 6
|
||||||
|
assert get_obc(on_first=True, on_second=True, on_third=True) == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_re24(session: Session):
|
||||||
|
game_1 = session.get(Game, 1)
|
||||||
|
this_play = game_1.current_play_or_none(session)
|
||||||
|
|
||||||
|
assert this_play is not None
|
||||||
|
assert get_re24(this_play, runs_scored=0, new_obc=0, new_starting_outs=2) == -.154
|
||||||
|
assert get_re24(this_play, runs_scored=1, new_obc=0, new_starting_outs=1) == 1
|
||||||
|
assert get_re24(this_play, runs_scored=0, new_obc=2, new_starting_outs=1) == 0.365
|
||||||
|
assert get_re24(this_play, runs_scored=0, new_obc=6, new_starting_outs=2) == 0.217
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_re24(session: Session):
|
||||||
|
game_1 = session.get(Game, 1)
|
||||||
|
this_play = game_1.current_play_or_none(session)
|
||||||
|
old_play = game_1.plays[0]
|
||||||
|
|
||||||
|
assert old_play.id != this_play
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user