Extract BattingCardRatingsModel and PitchingCardRatingsModel into models.py files
Move each ratings model class (and, for batters, the helper functions it depends on) into a dedicated models.py so that calcs_*.py can import from card_builder.py at module level without circular imports. - batters/models.py: BattingCardRatingsModel + bp_singles, wh_singles, one_singles, bp_homeruns, triples, two_doubles, hit_by_pitch, strikeouts, flyout_a, flyout_bq, flyout_b, groundball_a, groundball_c - pitchers/models.py: PitchingCardRatingsModel (no helper deps needed) - batters/calcs_batter.py: imports model + build_batter_full_cards at top - pitchers/calcs_pitcher.py: imports model + build_pitcher_full_cards at top - batters/card_builder.py: imports from batters.models - pitchers/card_builder.py: imports from pitchers.models - tests/test_batter_calcs.py: import bp_singles, wh_singles from batters.models Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
a72abc01a3
commit
39c652e55c
@ -1,349 +1,12 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
import pydantic
|
|
||||||
|
|
||||||
from creation_helpers import mround, sanitize_chance_output
|
from creation_helpers import mround, sanitize_chance_output
|
||||||
from typing import List, Literal
|
from typing import List
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from exceptions import logger
|
from exceptions import logger
|
||||||
|
|
||||||
|
from batters.models import BattingCardRatingsModel
|
||||||
class BattingCardRatingsModel(pydantic.BaseModel):
|
from batters.card_builder import build_batter_full_cards
|
||||||
battingcard_id: int
|
|
||||||
bat_hand: Literal['R', 'L', 'S']
|
|
||||||
vs_hand: Literal['R', 'L']
|
|
||||||
all_hits: float = 0.0
|
|
||||||
all_other_ob: float = 0.0
|
|
||||||
all_outs: float = 0.0
|
|
||||||
rem_singles: float = 0.0
|
|
||||||
rem_xbh: float = 0.0
|
|
||||||
rem_hr: float = 0.0
|
|
||||||
rem_doubles: float = 0.0
|
|
||||||
hard_rate: float
|
|
||||||
med_rate: float
|
|
||||||
soft_rate: float
|
|
||||||
pull_rate: float
|
|
||||||
center_rate: float
|
|
||||||
slap_rate: float
|
|
||||||
homerun: float = 0.0
|
|
||||||
bp_homerun: float = 0.0
|
|
||||||
triple: float = 0.0
|
|
||||||
double_three: float = 0.0
|
|
||||||
double_two: float = 0.0
|
|
||||||
double_pull: float = 0.0
|
|
||||||
single_two: float = 0.0
|
|
||||||
single_one: float = 0.0
|
|
||||||
single_center: float = 0.0
|
|
||||||
bp_single: float = 0.0
|
|
||||||
hbp: float = 0.0
|
|
||||||
walk: float = 0.0
|
|
||||||
strikeout: float = 0.0
|
|
||||||
lineout: float = 0.0
|
|
||||||
popout: float = 0.0
|
|
||||||
rem_flyballs: float = 0.0
|
|
||||||
flyout_a: float = 0.0
|
|
||||||
flyout_bq: float = 0.0
|
|
||||||
flyout_lf_b: float = 0.0
|
|
||||||
flyout_rf_b: float = 0.0
|
|
||||||
rem_groundballs: float = 0.0
|
|
||||||
groundout_a: float = 0.0
|
|
||||||
groundout_b: float = 0.0
|
|
||||||
groundout_c: float = 0.0
|
|
||||||
avg: float = 0.0
|
|
||||||
obp: float = 0.0
|
|
||||||
slg: float = 0.0
|
|
||||||
|
|
||||||
def total_chances(self):
|
|
||||||
return mround(sum([
|
|
||||||
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_pull,
|
|
||||||
self.single_two, self.single_one, self.single_center, self.bp_single, self.hbp, self.walk, self.strikeout,
|
|
||||||
self.lineout, self.popout, self.flyout_a, self.flyout_bq, self.flyout_lf_b, self.flyout_rf_b,
|
|
||||||
self.groundout_a, self.groundout_b, self.groundout_c
|
|
||||||
]))
|
|
||||||
|
|
||||||
def total_hits(self):
|
|
||||||
return mround(sum([
|
|
||||||
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_pull,
|
|
||||||
self.single_two, self.single_one, self.single_center, self.bp_single
|
|
||||||
]))
|
|
||||||
|
|
||||||
def rem_hits(self):
|
|
||||||
return (self.all_hits -
|
|
||||||
sum([
|
|
||||||
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_pull,
|
|
||||||
self.single_two, self.single_one, self.single_center, self.bp_single
|
|
||||||
]))
|
|
||||||
|
|
||||||
def rem_outs(self):
|
|
||||||
return mround(self.all_outs -
|
|
||||||
sum([
|
|
||||||
self.strikeout, self.lineout, self.popout, self.flyout_a, self.flyout_bq, self.flyout_lf_b,
|
|
||||||
self.flyout_rf_b, self.groundout_a, self.groundout_b, self.groundout_c
|
|
||||||
]))
|
|
||||||
|
|
||||||
def rem_other_ob(self):
|
|
||||||
return self.all_other_ob - self.hbp - self.walk
|
|
||||||
|
|
||||||
def calculate_singles(self, szn_singles, szn_hits, ifh_rate: Decimal):
|
|
||||||
tot = sanitize_chance_output(self.all_hits * mround((szn_singles * .8) / max(szn_hits, 1)))
|
|
||||||
logger.debug(f'tot: {tot}')
|
|
||||||
self.rem_singles = tot
|
|
||||||
|
|
||||||
self.bp_single = bp_singles(self.rem_singles)
|
|
||||||
self.rem_singles -= self.bp_single
|
|
||||||
|
|
||||||
self.single_two = wh_singles(self.rem_singles, self.hard_rate)
|
|
||||||
self.rem_singles -= self.single_two
|
|
||||||
|
|
||||||
self.single_one = one_singles(self.rem_singles, ifh_rate)
|
|
||||||
self.rem_singles -= self.single_one
|
|
||||||
|
|
||||||
self.single_center = sanitize_chance_output(self.rem_singles)
|
|
||||||
self.rem_singles -= self.single_center
|
|
||||||
|
|
||||||
self.rem_xbh = self.all_hits - self.bp_single - self.single_two - self.single_one - self.single_center
|
|
||||||
|
|
||||||
def calculate_xbh(self, szn_triples, szn_doubles, szn_hr, hr_per_fb: Decimal):
|
|
||||||
self.triple = triples(self.rem_xbh, szn_triples, szn_doubles + szn_hr)
|
|
||||||
self.rem_xbh -= self.triple
|
|
||||||
|
|
||||||
tot_doubles = sanitize_chance_output(self.rem_xbh * mround(szn_doubles / max(szn_hr + szn_doubles, 1)))
|
|
||||||
self.double_two = two_doubles(tot_doubles, self.soft_rate)
|
|
||||||
self.double_pull = sanitize_chance_output(tot_doubles - self.double_two)
|
|
||||||
self.rem_xbh -= mround(self.double_two + self.double_pull)
|
|
||||||
|
|
||||||
if (self.rem_xbh > mround(0)) and szn_hr > 0:
|
|
||||||
self.bp_homerun = bp_homeruns(self.rem_xbh, hr_per_fb)
|
|
||||||
self.homerun = sanitize_chance_output(self.rem_xbh - self.bp_homerun, min_chances=0.5)
|
|
||||||
self.rem_xbh -= mround(self.bp_homerun + self.homerun)
|
|
||||||
|
|
||||||
if szn_triples > 0 and self.rem_xbh > 0:
|
|
||||||
logger.error(f'Adding {self.rem_xbh} results to triples')
|
|
||||||
self.triple += sanitize_chance_output(self.rem_xbh, min_chances=0.5)
|
|
||||||
elif self.rem_xbh > 0:
|
|
||||||
logger.error(f'Adding {self.rem_xbh} results to all other ob')
|
|
||||||
# print(self)
|
|
||||||
self.all_other_ob += self.rem_xbh
|
|
||||||
|
|
||||||
def calculate_other_ob(self, szn_bb, szn_hbp):
|
|
||||||
self.hbp = hit_by_pitch(self.all_other_ob, szn_hbp, szn_bb)
|
|
||||||
self.walk = sanitize_chance_output(self.all_other_ob - self.hbp)
|
|
||||||
|
|
||||||
if self.walk + self.hbp < self.all_other_ob:
|
|
||||||
rem = self.all_other_ob - self.walk - self.hbp
|
|
||||||
logger.error(f'Adding {rem} chances to all_outs')
|
|
||||||
# print(self)
|
|
||||||
self.all_outs += mround(rem)
|
|
||||||
|
|
||||||
def calculate_strikeouts(self, szn_so, szn_ab, szn_hits):
|
|
||||||
self.strikeout = strikeouts(self.all_outs, (szn_so / max(szn_ab - szn_hits, 1)))
|
|
||||||
|
|
||||||
def calculate_other_outs(self, fb_rate, ld_rate, gb_rate, szn_gidp, szn_ab):
|
|
||||||
self.rem_flyballs = sanitize_chance_output(self.rem_outs() * mround(fb_rate))
|
|
||||||
self.flyout_a = flyout_a(self.rem_flyballs, self.hard_rate)
|
|
||||||
self.rem_flyballs -= self.flyout_a
|
|
||||||
|
|
||||||
self.flyout_bq = flyout_bq(self.rem_flyballs, self.soft_rate)
|
|
||||||
self.rem_flyballs -= self.flyout_bq
|
|
||||||
|
|
||||||
self.flyout_lf_b = flyout_b(
|
|
||||||
self.rem_flyballs,
|
|
||||||
pull_rate=self.pull_rate if self.bat_hand == 'R' else self.slap_rate,
|
|
||||||
cent_rate=self.center_rate
|
|
||||||
)
|
|
||||||
self.rem_flyballs -= self.flyout_lf_b
|
|
||||||
self.flyout_rf_b = sanitize_chance_output(self.rem_flyballs)
|
|
||||||
self.rem_flyballs -= self.flyout_rf_b
|
|
||||||
|
|
||||||
if self.rem_flyballs > 0:
|
|
||||||
logger.debug(f'Adding {self.rem_flyballs} chances to lineouts')
|
|
||||||
|
|
||||||
tot_oneouts = sanitize_chance_output(self.rem_outs() * mround(ld_rate / max(ld_rate + gb_rate, .01)))
|
|
||||||
self.lineout = sanitize_chance_output(mround(random.random()) * tot_oneouts)
|
|
||||||
self.popout = sanitize_chance_output(tot_oneouts - self.lineout)
|
|
||||||
|
|
||||||
self.groundout_a = groundball_a(self.rem_outs(), szn_gidp, szn_ab)
|
|
||||||
self.groundout_c = groundball_c(self.rem_outs(), self.med_rate)
|
|
||||||
self.groundout_b = self.rem_outs()
|
|
||||||
|
|
||||||
def calculate_rate_stats(self):
|
|
||||||
self.avg = mround(self.total_hits() / 108, prec=5, base=0.00001)
|
|
||||||
self.obp = mround((self.total_hits() + self.hbp + self.walk) / 108, prec=5, base=0.00001)
|
|
||||||
self.slg = mround((
|
|
||||||
self.homerun * 4 + self.triple * 3 + self.single_center + self.single_two + self.single_two +
|
|
||||||
(self.double_two + self.double_three + self.double_two + self.bp_homerun) * 2 + self.bp_single / 2) / 108, prec=5, base=0.00001)
|
|
||||||
|
|
||||||
def custom_to_dict(self):
|
|
||||||
self.calculate_rate_stats()
|
|
||||||
return {
|
|
||||||
'battingcard_id': self.battingcard_id,
|
|
||||||
'vs_hand': self.vs_hand,
|
|
||||||
'homerun': self.homerun,
|
|
||||||
'bp_homerun': self.bp_homerun,
|
|
||||||
'triple': self.triple,
|
|
||||||
'double_three': self.double_three,
|
|
||||||
'double_two': self.double_two,
|
|
||||||
'double_pull': self.double_pull,
|
|
||||||
'single_two': self.single_two,
|
|
||||||
'single_one': self.single_one,
|
|
||||||
'single_center': self.single_center,
|
|
||||||
'bp_single': self.bp_single,
|
|
||||||
'hbp': self.hbp,
|
|
||||||
'walk': self.walk,
|
|
||||||
'strikeout': mround(self.strikeout),
|
|
||||||
'lineout': self.lineout,
|
|
||||||
'popout': self.popout,
|
|
||||||
'flyout_a': self.flyout_a,
|
|
||||||
'flyout_bq': self.flyout_bq,
|
|
||||||
'flyout_lf_b': self.flyout_lf_b,
|
|
||||||
'flyout_rf_b': self.flyout_rf_b,
|
|
||||||
'groundout_a': self.groundout_a,
|
|
||||||
'groundout_b': self.groundout_b,
|
|
||||||
'groundout_c': self.groundout_c,
|
|
||||||
'pull_rate': self.pull_rate,
|
|
||||||
'center_rate': self.center_rate,
|
|
||||||
'slap_rate': self.slap_rate,
|
|
||||||
'avg': self.avg,
|
|
||||||
'obp': self.obp,
|
|
||||||
'slg': self.slg
|
|
||||||
}
|
|
||||||
|
|
||||||
# def total_chances(chance_data):
|
|
||||||
# sum_chances = 0
|
|
||||||
# for key in chance_data:
|
|
||||||
# if key not in ['id', 'player_id', 'cardset_id', 'vs_hand', 'is_prep']:
|
|
||||||
# sum_chances += chance_data[key]
|
|
||||||
#
|
|
||||||
# return mround(sum_chances)
|
|
||||||
|
|
||||||
|
|
||||||
def total_singles(all_hits, szn_singles, szn_hits):
|
|
||||||
return sanitize_chance_output(all_hits * ((szn_singles * .8) / max(szn_hits, 1)))
|
|
||||||
|
|
||||||
|
|
||||||
def bp_singles(all_singles):
|
|
||||||
if all_singles < 6:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return mround(5)
|
|
||||||
|
|
||||||
|
|
||||||
def wh_singles(rem_singles, hard_rate):
|
|
||||||
if rem_singles == 0 or hard_rate < .2:
|
|
||||||
return 0
|
|
||||||
elif hard_rate > .4:
|
|
||||||
return sanitize_chance_output(rem_singles * 2 / 3, min_chances=2)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(rem_singles / 3, min_chances=2)
|
|
||||||
|
|
||||||
|
|
||||||
def one_singles(rem_singles, ifh_rate, force_rem=False):
|
|
||||||
if force_rem:
|
|
||||||
return mround(rem_singles)
|
|
||||||
elif rem_singles == 0 or ifh_rate < .05:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(rem_singles * min(ifh_rate * mround(3), 0.75), min_chances=2)
|
|
||||||
|
|
||||||
|
|
||||||
def all_homeruns(rem_hits, all_hits, hrs, hits, singles):
|
|
||||||
if rem_hits == 0 or all_hits == 0 or hrs == 0 or hits - singles == 0:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return mround(min(rem_hits, all_hits * ((hrs * 1.15) / max(hits, 1))))
|
|
||||||
|
|
||||||
|
|
||||||
def nd_homeruns(all_hr, hr_rate):
|
|
||||||
if all_hr == 0 or hr_rate == 0:
|
|
||||||
return mround(0)
|
|
||||||
elif hr_rate > .2:
|
|
||||||
return sanitize_chance_output(all_hr * .6)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(all_hr * .25)
|
|
||||||
|
|
||||||
|
|
||||||
def bp_homeruns(all_hr, hr_rate):
|
|
||||||
if all_hr == 0 or hr_rate == 0:
|
|
||||||
return mround(0)
|
|
||||||
elif hr_rate > .2:
|
|
||||||
return mround(all_hr * 0.4, base=1.0)
|
|
||||||
else:
|
|
||||||
return mround(all_hr * 0.8, base=1.0)
|
|
||||||
|
|
||||||
|
|
||||||
def triples(all_xbh, tr_count, do_count):
|
|
||||||
if all_xbh == mround(0) or tr_count == mround(0):
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(all_xbh * mround(tr_count / max(tr_count + do_count, 1)), min_chances=1)
|
|
||||||
|
|
||||||
|
|
||||||
def two_doubles(all_doubles, soft_rate):
|
|
||||||
if all_doubles == 0 or soft_rate == 0:
|
|
||||||
return mround(0)
|
|
||||||
elif soft_rate > .2:
|
|
||||||
return sanitize_chance_output(all_doubles / 2)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(all_doubles / 4)
|
|
||||||
|
|
||||||
|
|
||||||
def hit_by_pitch(other_ob, hbps, walks):
|
|
||||||
if hbps == 0 or other_ob * mround(hbps / max(hbps + walks, 1)) < 1:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(other_ob * mround(hbps / max(hbps + walks, 1)), rounding=1.0)
|
|
||||||
|
|
||||||
|
|
||||||
def strikeouts(all_outs, k_rate):
|
|
||||||
if all_outs == 0 or k_rate == 0:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(all_outs * k_rate)
|
|
||||||
|
|
||||||
|
|
||||||
def flyout_a(all_flyouts, hard_rate):
|
|
||||||
if all_flyouts == 0 or hard_rate < .4:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return mround(1.0)
|
|
||||||
|
|
||||||
|
|
||||||
def flyout_bq(rem_flyouts, soft_rate):
|
|
||||||
if rem_flyouts == 0 or soft_rate < .1:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(rem_flyouts * min(soft_rate * 3, mround(.75)))
|
|
||||||
|
|
||||||
|
|
||||||
def flyout_b(rem_flyouts, pull_rate, cent_rate):
|
|
||||||
if rem_flyouts == 0 or pull_rate == 0:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(rem_flyouts * (pull_rate + cent_rate / 2))
|
|
||||||
|
|
||||||
|
|
||||||
def popouts(rem_outs, iffb_rate):
|
|
||||||
if rem_outs == 0 or iffb_rate * rem_outs < 1:
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
return mround(rem_outs * iffb_rate)
|
|
||||||
|
|
||||||
|
|
||||||
def groundball_a(all_groundouts, gidps, abs):
|
|
||||||
if all_groundouts == 0 or gidps == 0:
|
|
||||||
return mround(0)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(mround(min(gidps ** 2.5, abs) / max(abs, 1)) * all_groundouts)
|
|
||||||
|
|
||||||
|
|
||||||
def groundball_c(rem_groundouts, med_rate):
|
|
||||||
if rem_groundouts == 0 or med_rate < .4:
|
|
||||||
return mround(0)
|
|
||||||
elif med_rate > .6:
|
|
||||||
return sanitize_chance_output(rem_groundouts)
|
|
||||||
else:
|
|
||||||
return sanitize_chance_output(rem_groundouts * med_rate)
|
|
||||||
|
|
||||||
|
|
||||||
def stealing(chances: int, sb2s: int, cs2s: int, sb3s: int, cs3s: int, season_pct: float):
|
def stealing(chances: int, sb2s: int, cs2s: int, sb3s: int, cs3s: int, season_pct: float):
|
||||||
if chances == 0 or sb2s + cs2s == 0:
|
if chances == 0 or sb2s + cs2s == 0:
|
||||||
@ -626,7 +289,6 @@ def get_batter_ratings(df_data) -> List[dict]:
|
|||||||
vr_dict = vr.custom_to_dict()
|
vr_dict = vr.custom_to_dict()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from batters.card_builder import build_batter_full_cards
|
|
||||||
vl_card, vr_card = build_batter_full_cards(
|
vl_card, vr_card = build_batter_full_cards(
|
||||||
vl, vr, int(df_data['offense_col']), int(df_data['player_id']), df_data['bat_hand']
|
vl, vr, int(df_data['offense_col']), int(df_data['player_id']), df_data['bat_hand']
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import logging
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from card_layout import FullBattingCard, PLAY_RESULTS, PlayResult, EXACT_CHANCES, get_chances
|
from card_layout import FullBattingCard, PLAY_RESULTS, PlayResult, EXACT_CHANCES, get_chances
|
||||||
from batters.calcs_batter import BattingCardRatingsModel
|
from batters.models import BattingCardRatingsModel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
308
batters/models.py
Normal file
308
batters/models.py
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from creation_helpers import mround, sanitize_chance_output
|
||||||
|
from typing import Literal
|
||||||
|
from decimal import Decimal
|
||||||
|
from exceptions import logger
|
||||||
|
|
||||||
|
|
||||||
|
def bp_singles(all_singles):
|
||||||
|
if all_singles < 6:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return mround(5)
|
||||||
|
|
||||||
|
|
||||||
|
def wh_singles(rem_singles, hard_rate):
|
||||||
|
if rem_singles == 0 or hard_rate < .2:
|
||||||
|
return 0
|
||||||
|
elif hard_rate > .4:
|
||||||
|
return sanitize_chance_output(rem_singles * 2 / 3, min_chances=2)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(rem_singles / 3, min_chances=2)
|
||||||
|
|
||||||
|
|
||||||
|
def one_singles(rem_singles, ifh_rate, force_rem=False):
|
||||||
|
if force_rem:
|
||||||
|
return mround(rem_singles)
|
||||||
|
elif rem_singles == 0 or ifh_rate < .05:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(rem_singles * min(ifh_rate * mround(3), 0.75), min_chances=2)
|
||||||
|
|
||||||
|
|
||||||
|
def bp_homeruns(all_hr, hr_rate):
|
||||||
|
if all_hr == 0 or hr_rate == 0:
|
||||||
|
return mround(0)
|
||||||
|
elif hr_rate > .2:
|
||||||
|
return mround(all_hr * 0.4, base=1.0)
|
||||||
|
else:
|
||||||
|
return mround(all_hr * 0.8, base=1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def triples(all_xbh, tr_count, do_count):
|
||||||
|
if all_xbh == mround(0) or tr_count == mround(0):
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(all_xbh * mround(tr_count / max(tr_count + do_count, 1)), min_chances=1)
|
||||||
|
|
||||||
|
|
||||||
|
def two_doubles(all_doubles, soft_rate):
|
||||||
|
if all_doubles == 0 or soft_rate == 0:
|
||||||
|
return mround(0)
|
||||||
|
elif soft_rate > .2:
|
||||||
|
return sanitize_chance_output(all_doubles / 2)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(all_doubles / 4)
|
||||||
|
|
||||||
|
|
||||||
|
def hit_by_pitch(other_ob, hbps, walks):
|
||||||
|
if hbps == 0 or other_ob * mround(hbps / max(hbps + walks, 1)) < 1:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(other_ob * mround(hbps / max(hbps + walks, 1)), rounding=1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def strikeouts(all_outs, k_rate):
|
||||||
|
if all_outs == 0 or k_rate == 0:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(all_outs * k_rate)
|
||||||
|
|
||||||
|
|
||||||
|
def flyout_a(all_flyouts, hard_rate):
|
||||||
|
if all_flyouts == 0 or hard_rate < .4:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return mround(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def flyout_bq(rem_flyouts, soft_rate):
|
||||||
|
if rem_flyouts == 0 or soft_rate < .1:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(rem_flyouts * min(soft_rate * 3, mround(.75)))
|
||||||
|
|
||||||
|
|
||||||
|
def flyout_b(rem_flyouts, pull_rate, cent_rate):
|
||||||
|
if rem_flyouts == 0 or pull_rate == 0:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(rem_flyouts * (pull_rate + cent_rate / 2))
|
||||||
|
|
||||||
|
|
||||||
|
def groundball_a(all_groundouts, gidps, abs):
|
||||||
|
if all_groundouts == 0 or gidps == 0:
|
||||||
|
return mround(0)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(mround(min(gidps ** 2.5, abs) / max(abs, 1)) * all_groundouts)
|
||||||
|
|
||||||
|
|
||||||
|
def groundball_c(rem_groundouts, med_rate):
|
||||||
|
if rem_groundouts == 0 or med_rate < .4:
|
||||||
|
return mround(0)
|
||||||
|
elif med_rate > .6:
|
||||||
|
return sanitize_chance_output(rem_groundouts)
|
||||||
|
else:
|
||||||
|
return sanitize_chance_output(rem_groundouts * med_rate)
|
||||||
|
|
||||||
|
|
||||||
|
class BattingCardRatingsModel(pydantic.BaseModel):
|
||||||
|
battingcard_id: int
|
||||||
|
bat_hand: Literal['R', 'L', 'S']
|
||||||
|
vs_hand: Literal['R', 'L']
|
||||||
|
all_hits: float = 0.0
|
||||||
|
all_other_ob: float = 0.0
|
||||||
|
all_outs: float = 0.0
|
||||||
|
rem_singles: float = 0.0
|
||||||
|
rem_xbh: float = 0.0
|
||||||
|
rem_hr: float = 0.0
|
||||||
|
rem_doubles: float = 0.0
|
||||||
|
hard_rate: float
|
||||||
|
med_rate: float
|
||||||
|
soft_rate: float
|
||||||
|
pull_rate: float
|
||||||
|
center_rate: float
|
||||||
|
slap_rate: float
|
||||||
|
homerun: float = 0.0
|
||||||
|
bp_homerun: float = 0.0
|
||||||
|
triple: float = 0.0
|
||||||
|
double_three: float = 0.0
|
||||||
|
double_two: float = 0.0
|
||||||
|
double_pull: float = 0.0
|
||||||
|
single_two: float = 0.0
|
||||||
|
single_one: float = 0.0
|
||||||
|
single_center: float = 0.0
|
||||||
|
bp_single: float = 0.0
|
||||||
|
hbp: float = 0.0
|
||||||
|
walk: float = 0.0
|
||||||
|
strikeout: float = 0.0
|
||||||
|
lineout: float = 0.0
|
||||||
|
popout: float = 0.0
|
||||||
|
rem_flyballs: float = 0.0
|
||||||
|
flyout_a: float = 0.0
|
||||||
|
flyout_bq: float = 0.0
|
||||||
|
flyout_lf_b: float = 0.0
|
||||||
|
flyout_rf_b: float = 0.0
|
||||||
|
rem_groundballs: float = 0.0
|
||||||
|
groundout_a: float = 0.0
|
||||||
|
groundout_b: float = 0.0
|
||||||
|
groundout_c: float = 0.0
|
||||||
|
avg: float = 0.0
|
||||||
|
obp: float = 0.0
|
||||||
|
slg: float = 0.0
|
||||||
|
|
||||||
|
def total_chances(self):
|
||||||
|
return mround(sum([
|
||||||
|
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_pull,
|
||||||
|
self.single_two, self.single_one, self.single_center, self.bp_single, self.hbp, self.walk, self.strikeout,
|
||||||
|
self.lineout, self.popout, self.flyout_a, self.flyout_bq, self.flyout_lf_b, self.flyout_rf_b,
|
||||||
|
self.groundout_a, self.groundout_b, self.groundout_c
|
||||||
|
]))
|
||||||
|
|
||||||
|
def total_hits(self):
|
||||||
|
return mround(sum([
|
||||||
|
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_pull,
|
||||||
|
self.single_two, self.single_one, self.single_center, self.bp_single
|
||||||
|
]))
|
||||||
|
|
||||||
|
def rem_hits(self):
|
||||||
|
return (self.all_hits -
|
||||||
|
sum([
|
||||||
|
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_pull,
|
||||||
|
self.single_two, self.single_one, self.single_center, self.bp_single
|
||||||
|
]))
|
||||||
|
|
||||||
|
def rem_outs(self):
|
||||||
|
return mround(self.all_outs -
|
||||||
|
sum([
|
||||||
|
self.strikeout, self.lineout, self.popout, self.flyout_a, self.flyout_bq, self.flyout_lf_b,
|
||||||
|
self.flyout_rf_b, self.groundout_a, self.groundout_b, self.groundout_c
|
||||||
|
]))
|
||||||
|
|
||||||
|
def rem_other_ob(self):
|
||||||
|
return self.all_other_ob - self.hbp - self.walk
|
||||||
|
|
||||||
|
def calculate_singles(self, szn_singles, szn_hits, ifh_rate: Decimal):
|
||||||
|
tot = sanitize_chance_output(self.all_hits * mround((szn_singles * .8) / max(szn_hits, 1)))
|
||||||
|
logger.debug(f'tot: {tot}')
|
||||||
|
self.rem_singles = tot
|
||||||
|
|
||||||
|
self.bp_single = bp_singles(self.rem_singles)
|
||||||
|
self.rem_singles -= self.bp_single
|
||||||
|
|
||||||
|
self.single_two = wh_singles(self.rem_singles, self.hard_rate)
|
||||||
|
self.rem_singles -= self.single_two
|
||||||
|
|
||||||
|
self.single_one = one_singles(self.rem_singles, ifh_rate)
|
||||||
|
self.rem_singles -= self.single_one
|
||||||
|
|
||||||
|
self.single_center = sanitize_chance_output(self.rem_singles)
|
||||||
|
self.rem_singles -= self.single_center
|
||||||
|
|
||||||
|
self.rem_xbh = self.all_hits - self.bp_single - self.single_two - self.single_one - self.single_center
|
||||||
|
|
||||||
|
def calculate_xbh(self, szn_triples, szn_doubles, szn_hr, hr_per_fb: Decimal):
|
||||||
|
self.triple = triples(self.rem_xbh, szn_triples, szn_doubles + szn_hr)
|
||||||
|
self.rem_xbh -= self.triple
|
||||||
|
|
||||||
|
tot_doubles = sanitize_chance_output(self.rem_xbh * mround(szn_doubles / max(szn_hr + szn_doubles, 1)))
|
||||||
|
self.double_two = two_doubles(tot_doubles, self.soft_rate)
|
||||||
|
self.double_pull = sanitize_chance_output(tot_doubles - self.double_two)
|
||||||
|
self.rem_xbh -= mround(self.double_two + self.double_pull)
|
||||||
|
|
||||||
|
if (self.rem_xbh > mround(0)) and szn_hr > 0:
|
||||||
|
self.bp_homerun = bp_homeruns(self.rem_xbh, hr_per_fb)
|
||||||
|
self.homerun = sanitize_chance_output(self.rem_xbh - self.bp_homerun, min_chances=0.5)
|
||||||
|
self.rem_xbh -= mround(self.bp_homerun + self.homerun)
|
||||||
|
|
||||||
|
if szn_triples > 0 and self.rem_xbh > 0:
|
||||||
|
logger.error(f'Adding {self.rem_xbh} results to triples')
|
||||||
|
self.triple += sanitize_chance_output(self.rem_xbh, min_chances=0.5)
|
||||||
|
elif self.rem_xbh > 0:
|
||||||
|
logger.error(f'Adding {self.rem_xbh} results to all other ob')
|
||||||
|
self.all_other_ob += self.rem_xbh
|
||||||
|
|
||||||
|
def calculate_other_ob(self, szn_bb, szn_hbp):
|
||||||
|
self.hbp = hit_by_pitch(self.all_other_ob, szn_hbp, szn_bb)
|
||||||
|
self.walk = sanitize_chance_output(self.all_other_ob - self.hbp)
|
||||||
|
|
||||||
|
if self.walk + self.hbp < self.all_other_ob:
|
||||||
|
rem = self.all_other_ob - self.walk - self.hbp
|
||||||
|
logger.error(f'Adding {rem} chances to all_outs')
|
||||||
|
self.all_outs += mround(rem)
|
||||||
|
|
||||||
|
def calculate_strikeouts(self, szn_so, szn_ab, szn_hits):
|
||||||
|
self.strikeout = strikeouts(self.all_outs, (szn_so / max(szn_ab - szn_hits, 1)))
|
||||||
|
|
||||||
|
def calculate_other_outs(self, fb_rate, ld_rate, gb_rate, szn_gidp, szn_ab):
|
||||||
|
self.rem_flyballs = sanitize_chance_output(self.rem_outs() * mround(fb_rate))
|
||||||
|
self.flyout_a = flyout_a(self.rem_flyballs, self.hard_rate)
|
||||||
|
self.rem_flyballs -= self.flyout_a
|
||||||
|
|
||||||
|
self.flyout_bq = flyout_bq(self.rem_flyballs, self.soft_rate)
|
||||||
|
self.rem_flyballs -= self.flyout_bq
|
||||||
|
|
||||||
|
self.flyout_lf_b = flyout_b(
|
||||||
|
self.rem_flyballs,
|
||||||
|
pull_rate=self.pull_rate if self.bat_hand == 'R' else self.slap_rate,
|
||||||
|
cent_rate=self.center_rate
|
||||||
|
)
|
||||||
|
self.rem_flyballs -= self.flyout_lf_b
|
||||||
|
self.flyout_rf_b = sanitize_chance_output(self.rem_flyballs)
|
||||||
|
self.rem_flyballs -= self.flyout_rf_b
|
||||||
|
|
||||||
|
if self.rem_flyballs > 0:
|
||||||
|
logger.debug(f'Adding {self.rem_flyballs} chances to lineouts')
|
||||||
|
|
||||||
|
tot_oneouts = sanitize_chance_output(self.rem_outs() * mround(ld_rate / max(ld_rate + gb_rate, .01)))
|
||||||
|
self.lineout = sanitize_chance_output(mround(random.random()) * tot_oneouts)
|
||||||
|
self.popout = sanitize_chance_output(tot_oneouts - self.lineout)
|
||||||
|
|
||||||
|
self.groundout_a = groundball_a(self.rem_outs(), szn_gidp, szn_ab)
|
||||||
|
self.groundout_c = groundball_c(self.rem_outs(), self.med_rate)
|
||||||
|
self.groundout_b = self.rem_outs()
|
||||||
|
|
||||||
|
def calculate_rate_stats(self):
|
||||||
|
self.avg = mround(self.total_hits() / 108, prec=5, base=0.00001)
|
||||||
|
self.obp = mround((self.total_hits() + self.hbp + self.walk) / 108, prec=5, base=0.00001)
|
||||||
|
self.slg = mround((
|
||||||
|
self.homerun * 4 + self.triple * 3 + self.single_center + self.single_two + self.single_two +
|
||||||
|
(self.double_two + self.double_three + self.double_two + self.bp_homerun) * 2 + self.bp_single / 2) / 108, prec=5, base=0.00001)
|
||||||
|
|
||||||
|
def custom_to_dict(self):
|
||||||
|
self.calculate_rate_stats()
|
||||||
|
return {
|
||||||
|
'battingcard_id': self.battingcard_id,
|
||||||
|
'vs_hand': self.vs_hand,
|
||||||
|
'homerun': self.homerun,
|
||||||
|
'bp_homerun': self.bp_homerun,
|
||||||
|
'triple': self.triple,
|
||||||
|
'double_three': self.double_three,
|
||||||
|
'double_two': self.double_two,
|
||||||
|
'double_pull': self.double_pull,
|
||||||
|
'single_two': self.single_two,
|
||||||
|
'single_one': self.single_one,
|
||||||
|
'single_center': self.single_center,
|
||||||
|
'bp_single': self.bp_single,
|
||||||
|
'hbp': self.hbp,
|
||||||
|
'walk': self.walk,
|
||||||
|
'strikeout': mround(self.strikeout),
|
||||||
|
'lineout': self.lineout,
|
||||||
|
'popout': self.popout,
|
||||||
|
'flyout_a': self.flyout_a,
|
||||||
|
'flyout_bq': self.flyout_bq,
|
||||||
|
'flyout_lf_b': self.flyout_lf_b,
|
||||||
|
'flyout_rf_b': self.flyout_rf_b,
|
||||||
|
'groundout_a': self.groundout_a,
|
||||||
|
'groundout_b': self.groundout_b,
|
||||||
|
'groundout_c': self.groundout_c,
|
||||||
|
'pull_rate': self.pull_rate,
|
||||||
|
'center_rate': self.center_rate,
|
||||||
|
'slap_rate': self.slap_rate,
|
||||||
|
'avg': self.avg,
|
||||||
|
'obp': self.obp,
|
||||||
|
'slg': self.slg
|
||||||
|
}
|
||||||
@ -1,304 +1,11 @@
|
|||||||
import math
|
import math
|
||||||
|
|
||||||
import pydantic
|
|
||||||
|
|
||||||
from creation_helpers import mround, sanitize_chance_output
|
from creation_helpers import mround, sanitize_chance_output
|
||||||
from typing import List, Literal
|
from typing import List
|
||||||
from exceptions import logger
|
from exceptions import logger
|
||||||
|
|
||||||
|
from pitchers.models import PitchingCardRatingsModel
|
||||||
class PitchingCardRatingsModel(pydantic.BaseModel):
|
from pitchers.card_builder import build_pitcher_full_cards
|
||||||
pitchingcard_id: int
|
|
||||||
pit_hand: Literal['R', 'L']
|
|
||||||
vs_hand: Literal['R', 'L']
|
|
||||||
all_hits: float = 0.0
|
|
||||||
all_other_ob: float = 0.0
|
|
||||||
all_outs: float = 0.0
|
|
||||||
rem_singles: float = 0.0
|
|
||||||
rem_xbh: float = 0.0
|
|
||||||
rem_hr: float = 0.0
|
|
||||||
rem_doubles: float = 0.0
|
|
||||||
hard_rate: float
|
|
||||||
med_rate: float
|
|
||||||
soft_rate: float
|
|
||||||
# pull_rate: float
|
|
||||||
# center_rate: float
|
|
||||||
# slap_rate: float
|
|
||||||
homerun: float = 0.0
|
|
||||||
bp_homerun: float = 0.0
|
|
||||||
triple: float = 0.0
|
|
||||||
double_three: float = 0.0
|
|
||||||
double_two: float = 0.0
|
|
||||||
double_cf: float = 0.0
|
|
||||||
single_two: float = 0.0
|
|
||||||
single_one: float = 0.0
|
|
||||||
single_center: float = 0.0
|
|
||||||
bp_single: float = 0.0
|
|
||||||
hbp: float = 0.0
|
|
||||||
walk: float = 0.0
|
|
||||||
strikeout: float = 0.0
|
|
||||||
rem_flyballs: float = 0.0
|
|
||||||
flyout_lf_b: float = 0.0
|
|
||||||
flyout_cf_b: float = 0.0
|
|
||||||
flyout_rf_b: float = 0.0
|
|
||||||
rem_groundballs: float = 0.0
|
|
||||||
groundout_a: float = 0.0
|
|
||||||
groundout_b: float = 0.0
|
|
||||||
xcheck_p: float = float(1.0)
|
|
||||||
xcheck_c: float = float(3.0)
|
|
||||||
xcheck_1b: float = float(2.0)
|
|
||||||
xcheck_2b: float = float(6.0)
|
|
||||||
xcheck_3b: float = float(3.0)
|
|
||||||
xcheck_ss: float = float(7.0)
|
|
||||||
xcheck_lf: float = float(2.0)
|
|
||||||
xcheck_cf: float = float(3.0)
|
|
||||||
xcheck_rf: float = float(2.0)
|
|
||||||
avg: float = 0.0
|
|
||||||
obp: float = 0.0
|
|
||||||
slg: float = 0.0
|
|
||||||
|
|
||||||
def total_chances(self):
|
|
||||||
return mround(sum([
|
|
||||||
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_cf,
|
|
||||||
self.single_two, self.single_one, self.single_center, self.bp_single, self.hbp, self.walk, self.strikeout,
|
|
||||||
self.flyout_lf_b, self.flyout_cf_b, self.flyout_rf_b, self.groundout_a, self.groundout_b, self.xcheck_p,
|
|
||||||
self.xcheck_c, self.xcheck_1b, self.xcheck_2b, self.xcheck_3b, self.xcheck_ss, self.xcheck_lf,
|
|
||||||
self.xcheck_cf, self.xcheck_rf
|
|
||||||
]))
|
|
||||||
|
|
||||||
def total_hits(self):
|
|
||||||
return mround(sum([
|
|
||||||
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_cf,
|
|
||||||
self.single_two, self.single_one, self.single_center, self.bp_single
|
|
||||||
]))
|
|
||||||
|
|
||||||
def total_ob(self):
|
|
||||||
return mround(sum([
|
|
||||||
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_cf,
|
|
||||||
self.single_two, self.single_one, self.single_center, self.bp_single, self.hbp, self.walk
|
|
||||||
]))
|
|
||||||
|
|
||||||
def total_outs(self):
|
|
||||||
return mround(sum([
|
|
||||||
self.strikeout, self.flyout_lf_b, self.flyout_cf_b, self.flyout_rf_b, self.groundout_a, self.groundout_b,
|
|
||||||
self.xcheck_p, self.xcheck_c, self.xcheck_1b, self.xcheck_2b, self.xcheck_3b, self.xcheck_ss,
|
|
||||||
self.xcheck_lf, self.xcheck_cf, self.xcheck_rf
|
|
||||||
]))
|
|
||||||
|
|
||||||
def calculate_rate_stats(self):
|
|
||||||
self.avg = mround(self.total_hits() / 108, prec=5, base=0.00001)
|
|
||||||
self.obp = mround((self.total_hits() + self.hbp + self.walk) / 108, prec=5, base=0.00001)
|
|
||||||
self.slg = mround((
|
|
||||||
self.homerun * 4 + self.triple * 3 + self.single_center + self.single_two + self.single_two +
|
|
||||||
(self.double_two + self.double_three + self.double_two + self.bp_homerun) * 2 + self.bp_single / 2) / 108, prec=5, base=0.00001)
|
|
||||||
|
|
||||||
def custom_to_dict(self):
|
|
||||||
self.calculate_rate_stats()
|
|
||||||
return {
|
|
||||||
'pitchingcard_id': self.pitchingcard_id,
|
|
||||||
'vs_hand': self.vs_hand,
|
|
||||||
'homerun': self.homerun,
|
|
||||||
'bp_homerun': self.bp_homerun,
|
|
||||||
'triple': self.triple,
|
|
||||||
'double_three': self.double_three,
|
|
||||||
'double_two': self.double_two,
|
|
||||||
'double_cf': self.double_cf,
|
|
||||||
'single_two': self.single_two,
|
|
||||||
'single_one': self.single_one,
|
|
||||||
'single_center': self.single_center,
|
|
||||||
'bp_single': self.bp_single,
|
|
||||||
'hbp': self.hbp,
|
|
||||||
'walk': self.walk,
|
|
||||||
'strikeout': self.strikeout,
|
|
||||||
'flyout_lf_b': self.flyout_lf_b,
|
|
||||||
'flyout_cf_b': self.flyout_cf_b,
|
|
||||||
'flyout_rf_b': self.flyout_rf_b,
|
|
||||||
'groundout_a': self.groundout_a,
|
|
||||||
'groundout_b': self.groundout_b,
|
|
||||||
'xcheck_p': self.xcheck_p,
|
|
||||||
'xcheck_c': self.xcheck_c,
|
|
||||||
'xcheck_1b': self.xcheck_1b,
|
|
||||||
'xcheck_2b': self.xcheck_2b,
|
|
||||||
'xcheck_3b': self.xcheck_3b,
|
|
||||||
'xcheck_ss': self.xcheck_ss,
|
|
||||||
'xcheck_lf': self.xcheck_lf,
|
|
||||||
'xcheck_cf': self.xcheck_cf,
|
|
||||||
'xcheck_rf': self.xcheck_rf,
|
|
||||||
'avg': self.avg,
|
|
||||||
'obp': self.obp,
|
|
||||||
'slg': self.slg
|
|
||||||
}
|
|
||||||
|
|
||||||
def calculate_singles(self, szn_hits, szn_singles):
|
|
||||||
if szn_hits == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
tot = sanitize_chance_output(self.all_hits * (szn_singles / szn_hits))
|
|
||||||
logger.debug(f'total singles: {tot}')
|
|
||||||
self.rem_singles = tot
|
|
||||||
|
|
||||||
self.bp_single = 5.0 if self.rem_singles >= 5 else 0.0
|
|
||||||
self.rem_singles -= self.bp_single
|
|
||||||
|
|
||||||
self.single_two = sanitize_chance_output(self.rem_singles / 2) if self.hard_rate >= 0.2 else 0.0
|
|
||||||
self.rem_singles -= self.single_two
|
|
||||||
|
|
||||||
self.single_one = sanitize_chance_output(self.rem_singles) if self.soft_rate >= .2 else 0.0
|
|
||||||
self.rem_singles -= self.single_one
|
|
||||||
|
|
||||||
self.single_center = sanitize_chance_output(self.rem_singles)
|
|
||||||
self.rem_singles -= self.single_center
|
|
||||||
|
|
||||||
self.rem_xbh = self.all_hits - self.single_center - self.single_one - self.single_two - self.bp_single
|
|
||||||
logger.info(f'remaining singles: {self.rem_singles} / total xbh: {self.rem_xbh}')
|
|
||||||
|
|
||||||
def calculate_xbh(self, szn_doubles, szn_triples, szn_homeruns, hr_per_fb_rate):
|
|
||||||
szn_xbh = szn_doubles + szn_triples + szn_homeruns
|
|
||||||
if szn_xbh == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
hr_rate = mround(szn_homeruns / szn_xbh)
|
|
||||||
tr_rate = mround(szn_triples / szn_xbh)
|
|
||||||
do_rate = mround(szn_doubles / szn_xbh)
|
|
||||||
logger.info(f'hr%: {hr_rate:.2f} / tr%: {tr_rate:.2f} / do%: {do_rate:.2f}')
|
|
||||||
|
|
||||||
raw_do_chances = sanitize_chance_output(self.rem_xbh * do_rate)
|
|
||||||
logger.info(f'raw do chances: {raw_do_chances}')
|
|
||||||
self.double_two = raw_do_chances if self.soft_rate > .2 else 0.0
|
|
||||||
self.double_cf = mround(raw_do_chances - self.double_two)
|
|
||||||
self.rem_xbh -= mround(self.double_two + self.double_cf + self.double_three)
|
|
||||||
logger.info(f'Double**: {self.double_two} / Double(cf): {self.double_cf} / rem xbh: {self.rem_xbh}')
|
|
||||||
|
|
||||||
self.triple = sanitize_chance_output(self.rem_xbh * tr_rate)
|
|
||||||
self.rem_xbh = mround(self.rem_xbh - self.triple)
|
|
||||||
logger.info(f'Triple: {self.triple} / rem xbh: {self.rem_xbh}')
|
|
||||||
|
|
||||||
raw_hr_chances = self.rem_xbh
|
|
||||||
logger.info(f'raw hr chances: {raw_hr_chances}')
|
|
||||||
|
|
||||||
if hr_per_fb_rate < .08:
|
|
||||||
self.bp_homerun = sanitize_chance_output(raw_hr_chances, min_chances=1.0, rounding=1.0)
|
|
||||||
elif hr_per_fb_rate > .28:
|
|
||||||
self.homerun = raw_hr_chances
|
|
||||||
elif hr_per_fb_rate > .18:
|
|
||||||
self.bp_homerun = sanitize_chance_output(raw_hr_chances * 0.4, min_chances=1.0, rounding=1.0)
|
|
||||||
self.homerun = self.rem_xbh - self.bp_homerun
|
|
||||||
else:
|
|
||||||
self.bp_homerun = sanitize_chance_output(raw_hr_chances * .75, min_chances=1.0, rounding=1.0)
|
|
||||||
self.homerun = mround(self.rem_xbh - self.bp_homerun)
|
|
||||||
logger.info(f'BP HR: {self.bp_homerun} / ND HR: {self.homerun}')
|
|
||||||
|
|
||||||
self.rem_xbh -= (self.bp_homerun + self.homerun)
|
|
||||||
logger.info(f'excess xbh: {self.rem_xbh}')
|
|
||||||
|
|
||||||
if self.rem_xbh > 0:
|
|
||||||
if self.triple > 1:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to triple')
|
|
||||||
self.triple += self.rem_xbh
|
|
||||||
self.rem_xbh = 0.0
|
|
||||||
elif self.double_cf > 1:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to double(cf)')
|
|
||||||
self.double_cf += self.rem_xbh
|
|
||||||
self.rem_xbh = 0.0
|
|
||||||
elif self.double_two > 1:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to double**')
|
|
||||||
self.double_two += self.rem_xbh
|
|
||||||
self.rem_xbh = 0.0
|
|
||||||
elif self.single_two > 1:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to single**')
|
|
||||||
self.single_two += self.rem_xbh
|
|
||||||
self.rem_xbh = 0.0
|
|
||||||
elif self.single_center > 1:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to single(cf)')
|
|
||||||
self.single_center += self.rem_xbh
|
|
||||||
self.rem_xbh = 0.0
|
|
||||||
elif self.single_one > 1:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to single*')
|
|
||||||
self.single_one += self.rem_xbh
|
|
||||||
self.rem_xbh = 0.0
|
|
||||||
else:
|
|
||||||
logger.info(f'Passing {self.rem_xbh} xbh to other_ob')
|
|
||||||
self.all_other_ob += self.rem_xbh
|
|
||||||
|
|
||||||
def calculate_other_ob(self, szn_walks, szn_hbp):
|
|
||||||
if szn_walks + szn_hbp == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
this_hbp = sanitize_chance_output(self.all_other_ob * szn_hbp / (szn_walks + szn_hbp), rounding=1.0)
|
|
||||||
logger.info(f'hbp value candidate: {this_hbp} / all_other_ob: {self.all_other_ob}')
|
|
||||||
self.hbp = max(min(this_hbp, self.all_other_ob), 0)
|
|
||||||
self.walk = mround(self.all_other_ob - self.hbp)
|
|
||||||
logger.info(f'self.hbp: {self.hbp} / self.walk: {self.walk}')
|
|
||||||
|
|
||||||
def calculate_strikouts(self, szn_strikeouts, szn_ab, szn_hits):
|
|
||||||
denom = max(szn_ab - szn_hits, 1)
|
|
||||||
raw_so = sanitize_chance_output(self.all_outs * (szn_strikeouts * 1.2) / denom)
|
|
||||||
sum_bb_so = self.walk + raw_so
|
|
||||||
excess = sum_bb_so - mround(math.floor(sum_bb_so))
|
|
||||||
logger.info(f'raw_so: {raw_so} / sum_bb_so: {sum_bb_so} / excess: {excess}')
|
|
||||||
|
|
||||||
self.strikeout = max(raw_so - excess - .05, 0.0)
|
|
||||||
if self.strikeout < 0:
|
|
||||||
logger.error(f'Strikeouts are less than zero :confusedpsyduck:')
|
|
||||||
|
|
||||||
def calculate_other_outs(self, fb_pct, gb_pct, oppo_pct):
|
|
||||||
rem_outs = 108 - self.total_chances()
|
|
||||||
|
|
||||||
all_fo = sanitize_chance_output(rem_outs * fb_pct)
|
|
||||||
if self.pit_hand == 'L':
|
|
||||||
self.flyout_lf_b = sanitize_chance_output(all_fo * oppo_pct)
|
|
||||||
else:
|
|
||||||
self.flyout_rf_b = sanitize_chance_output(all_fo * oppo_pct)
|
|
||||||
self.flyout_cf_b = all_fo - self.flyout_lf_b - self.flyout_rf_b
|
|
||||||
rem_outs -= (self.flyout_lf_b + self.flyout_cf_b + self.flyout_rf_b)
|
|
||||||
|
|
||||||
all_gb = rem_outs
|
|
||||||
self.groundout_a = sanitize_chance_output(all_gb * self.soft_rate)
|
|
||||||
self.groundout_b = sanitize_chance_output(all_gb - self.groundout_a)
|
|
||||||
|
|
||||||
rem_chances = 108 - self.total_chances()
|
|
||||||
logger.info(f'Remaining outs: {rem_chances}')
|
|
||||||
|
|
||||||
if self.strikeout > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to strikeouts')
|
|
||||||
self.strikeout += rem_chances
|
|
||||||
elif self.flyout_cf_b > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to fly(cf)')
|
|
||||||
self.flyout_cf_b += rem_chances
|
|
||||||
elif self.flyout_rf_b > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to fly(rf)')
|
|
||||||
self.flyout_rf_b += rem_chances
|
|
||||||
elif self.flyout_lf_b > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to fly(lf)')
|
|
||||||
self.flyout_lf_b += rem_chances
|
|
||||||
elif self.groundout_a > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to gbA')
|
|
||||||
self.groundout_a += rem_chances
|
|
||||||
elif self.single_one > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to single*')
|
|
||||||
self.single_one += rem_chances
|
|
||||||
elif self.single_center > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to single(cf)')
|
|
||||||
self.single_center += rem_chances
|
|
||||||
elif self.single_two > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to single**')
|
|
||||||
self.single_two += rem_chances
|
|
||||||
elif self.double_two > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to double**')
|
|
||||||
self.double_two += rem_chances
|
|
||||||
elif self.double_cf > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to double(cf)')
|
|
||||||
self.double_cf += rem_chances
|
|
||||||
elif self.triple > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to triple')
|
|
||||||
self.triple += rem_chances
|
|
||||||
elif self.homerun > 1:
|
|
||||||
logger.info(f'Passing {rem_chances} outs to homerun')
|
|
||||||
self.homerun += rem_chances
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Could not complete card')
|
|
||||||
|
|
||||||
|
|
||||||
def get_pitcher_ratings(df_data) -> List[dict]:
|
def get_pitcher_ratings(df_data) -> List[dict]:
|
||||||
# Calculate OB values with min cap (ensure scalar values for comparison)
|
# Calculate OB values with min cap (ensure scalar values for comparison)
|
||||||
ob_vl = float(108 * (df_data['BB_vL'] + df_data['HBP_vL']) / df_data['TBF_vL'])
|
ob_vl = float(108 * (df_data['BB_vL'] + df_data['HBP_vL']) / df_data['TBF_vL'])
|
||||||
@ -380,7 +87,6 @@ def get_pitcher_ratings(df_data) -> List[dict]:
|
|||||||
vr_dict = vr.custom_to_dict()
|
vr_dict = vr.custom_to_dict()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pitchers.card_builder import build_pitcher_full_cards
|
|
||||||
vl_card, vr_card = build_pitcher_full_cards(
|
vl_card, vr_card = build_pitcher_full_cards(
|
||||||
vl, vr, int(df_data['offense_col']), int(df_data['player_id']), df_data['pitch_hand']
|
vl, vr, int(df_data['offense_col']), int(df_data['player_id']), df_data['pitch_hand']
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import logging
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
from card_layout import FullPitchingCard, PLAY_RESULTS, PlayResult, EXACT_CHANCES, get_chances
|
from card_layout import FullPitchingCard, PLAY_RESULTS, PlayResult, EXACT_CHANCES, get_chances
|
||||||
from pitchers.calcs_pitcher import PitchingCardRatingsModel
|
from pitchers.models import PitchingCardRatingsModel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
299
pitchers/models.py
Normal file
299
pitchers/models.py
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
|
||||||
|
from creation_helpers import mround, sanitize_chance_output
|
||||||
|
from typing import Literal
|
||||||
|
from exceptions import logger
|
||||||
|
|
||||||
|
|
||||||
|
class PitchingCardRatingsModel(pydantic.BaseModel):
|
||||||
|
pitchingcard_id: int
|
||||||
|
pit_hand: Literal['R', 'L']
|
||||||
|
vs_hand: Literal['R', 'L']
|
||||||
|
all_hits: float = 0.0
|
||||||
|
all_other_ob: float = 0.0
|
||||||
|
all_outs: float = 0.0
|
||||||
|
rem_singles: float = 0.0
|
||||||
|
rem_xbh: float = 0.0
|
||||||
|
rem_hr: float = 0.0
|
||||||
|
rem_doubles: float = 0.0
|
||||||
|
hard_rate: float
|
||||||
|
med_rate: float
|
||||||
|
soft_rate: float
|
||||||
|
# pull_rate: float
|
||||||
|
# center_rate: float
|
||||||
|
# slap_rate: float
|
||||||
|
homerun: float = 0.0
|
||||||
|
bp_homerun: float = 0.0
|
||||||
|
triple: float = 0.0
|
||||||
|
double_three: float = 0.0
|
||||||
|
double_two: float = 0.0
|
||||||
|
double_cf: float = 0.0
|
||||||
|
single_two: float = 0.0
|
||||||
|
single_one: float = 0.0
|
||||||
|
single_center: float = 0.0
|
||||||
|
bp_single: float = 0.0
|
||||||
|
hbp: float = 0.0
|
||||||
|
walk: float = 0.0
|
||||||
|
strikeout: float = 0.0
|
||||||
|
rem_flyballs: float = 0.0
|
||||||
|
flyout_lf_b: float = 0.0
|
||||||
|
flyout_cf_b: float = 0.0
|
||||||
|
flyout_rf_b: float = 0.0
|
||||||
|
rem_groundballs: float = 0.0
|
||||||
|
groundout_a: float = 0.0
|
||||||
|
groundout_b: float = 0.0
|
||||||
|
xcheck_p: float = float(1.0)
|
||||||
|
xcheck_c: float = float(3.0)
|
||||||
|
xcheck_1b: float = float(2.0)
|
||||||
|
xcheck_2b: float = float(6.0)
|
||||||
|
xcheck_3b: float = float(3.0)
|
||||||
|
xcheck_ss: float = float(7.0)
|
||||||
|
xcheck_lf: float = float(2.0)
|
||||||
|
xcheck_cf: float = float(3.0)
|
||||||
|
xcheck_rf: float = float(2.0)
|
||||||
|
avg: float = 0.0
|
||||||
|
obp: float = 0.0
|
||||||
|
slg: float = 0.0
|
||||||
|
|
||||||
|
def total_chances(self):
|
||||||
|
return mround(sum([
|
||||||
|
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_cf,
|
||||||
|
self.single_two, self.single_one, self.single_center, self.bp_single, self.hbp, self.walk, self.strikeout,
|
||||||
|
self.flyout_lf_b, self.flyout_cf_b, self.flyout_rf_b, self.groundout_a, self.groundout_b, self.xcheck_p,
|
||||||
|
self.xcheck_c, self.xcheck_1b, self.xcheck_2b, self.xcheck_3b, self.xcheck_ss, self.xcheck_lf,
|
||||||
|
self.xcheck_cf, self.xcheck_rf
|
||||||
|
]))
|
||||||
|
|
||||||
|
def total_hits(self):
|
||||||
|
return mround(sum([
|
||||||
|
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_cf,
|
||||||
|
self.single_two, self.single_one, self.single_center, self.bp_single
|
||||||
|
]))
|
||||||
|
|
||||||
|
def total_ob(self):
|
||||||
|
return mround(sum([
|
||||||
|
self.homerun, self.bp_homerun, self.triple, self.double_three, self.double_two, self.double_cf,
|
||||||
|
self.single_two, self.single_one, self.single_center, self.bp_single, self.hbp, self.walk
|
||||||
|
]))
|
||||||
|
|
||||||
|
def total_outs(self):
|
||||||
|
return mround(sum([
|
||||||
|
self.strikeout, self.flyout_lf_b, self.flyout_cf_b, self.flyout_rf_b, self.groundout_a, self.groundout_b,
|
||||||
|
self.xcheck_p, self.xcheck_c, self.xcheck_1b, self.xcheck_2b, self.xcheck_3b, self.xcheck_ss,
|
||||||
|
self.xcheck_lf, self.xcheck_cf, self.xcheck_rf
|
||||||
|
]))
|
||||||
|
|
||||||
|
def calculate_rate_stats(self):
|
||||||
|
self.avg = mround(self.total_hits() / 108, prec=5, base=0.00001)
|
||||||
|
self.obp = mround((self.total_hits() + self.hbp + self.walk) / 108, prec=5, base=0.00001)
|
||||||
|
self.slg = mround((
|
||||||
|
self.homerun * 4 + self.triple * 3 + self.single_center + self.single_two + self.single_two +
|
||||||
|
(self.double_two + self.double_three + self.double_two + self.bp_homerun) * 2 + self.bp_single / 2) / 108, prec=5, base=0.00001)
|
||||||
|
|
||||||
|
def custom_to_dict(self):
|
||||||
|
self.calculate_rate_stats()
|
||||||
|
return {
|
||||||
|
'pitchingcard_id': self.pitchingcard_id,
|
||||||
|
'vs_hand': self.vs_hand,
|
||||||
|
'homerun': self.homerun,
|
||||||
|
'bp_homerun': self.bp_homerun,
|
||||||
|
'triple': self.triple,
|
||||||
|
'double_three': self.double_three,
|
||||||
|
'double_two': self.double_two,
|
||||||
|
'double_cf': self.double_cf,
|
||||||
|
'single_two': self.single_two,
|
||||||
|
'single_one': self.single_one,
|
||||||
|
'single_center': self.single_center,
|
||||||
|
'bp_single': self.bp_single,
|
||||||
|
'hbp': self.hbp,
|
||||||
|
'walk': self.walk,
|
||||||
|
'strikeout': self.strikeout,
|
||||||
|
'flyout_lf_b': self.flyout_lf_b,
|
||||||
|
'flyout_cf_b': self.flyout_cf_b,
|
||||||
|
'flyout_rf_b': self.flyout_rf_b,
|
||||||
|
'groundout_a': self.groundout_a,
|
||||||
|
'groundout_b': self.groundout_b,
|
||||||
|
'xcheck_p': self.xcheck_p,
|
||||||
|
'xcheck_c': self.xcheck_c,
|
||||||
|
'xcheck_1b': self.xcheck_1b,
|
||||||
|
'xcheck_2b': self.xcheck_2b,
|
||||||
|
'xcheck_3b': self.xcheck_3b,
|
||||||
|
'xcheck_ss': self.xcheck_ss,
|
||||||
|
'xcheck_lf': self.xcheck_lf,
|
||||||
|
'xcheck_cf': self.xcheck_cf,
|
||||||
|
'xcheck_rf': self.xcheck_rf,
|
||||||
|
'avg': self.avg,
|
||||||
|
'obp': self.obp,
|
||||||
|
'slg': self.slg
|
||||||
|
}
|
||||||
|
|
||||||
|
def calculate_singles(self, szn_hits, szn_singles):
|
||||||
|
if szn_hits == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
tot = sanitize_chance_output(self.all_hits * (szn_singles / szn_hits))
|
||||||
|
logger.debug(f'total singles: {tot}')
|
||||||
|
self.rem_singles = tot
|
||||||
|
|
||||||
|
self.bp_single = 5.0 if self.rem_singles >= 5 else 0.0
|
||||||
|
self.rem_singles -= self.bp_single
|
||||||
|
|
||||||
|
self.single_two = sanitize_chance_output(self.rem_singles / 2) if self.hard_rate >= 0.2 else 0.0
|
||||||
|
self.rem_singles -= self.single_two
|
||||||
|
|
||||||
|
self.single_one = sanitize_chance_output(self.rem_singles) if self.soft_rate >= .2 else 0.0
|
||||||
|
self.rem_singles -= self.single_one
|
||||||
|
|
||||||
|
self.single_center = sanitize_chance_output(self.rem_singles)
|
||||||
|
self.rem_singles -= self.single_center
|
||||||
|
|
||||||
|
self.rem_xbh = self.all_hits - self.single_center - self.single_one - self.single_two - self.bp_single
|
||||||
|
logger.info(f'remaining singles: {self.rem_singles} / total xbh: {self.rem_xbh}')
|
||||||
|
|
||||||
|
def calculate_xbh(self, szn_doubles, szn_triples, szn_homeruns, hr_per_fb_rate):
|
||||||
|
szn_xbh = szn_doubles + szn_triples + szn_homeruns
|
||||||
|
if szn_xbh == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
hr_rate = mround(szn_homeruns / szn_xbh)
|
||||||
|
tr_rate = mround(szn_triples / szn_xbh)
|
||||||
|
do_rate = mround(szn_doubles / szn_xbh)
|
||||||
|
logger.info(f'hr%: {hr_rate:.2f} / tr%: {tr_rate:.2f} / do%: {do_rate:.2f}')
|
||||||
|
|
||||||
|
raw_do_chances = sanitize_chance_output(self.rem_xbh * do_rate)
|
||||||
|
logger.info(f'raw do chances: {raw_do_chances}')
|
||||||
|
self.double_two = raw_do_chances if self.soft_rate > .2 else 0.0
|
||||||
|
self.double_cf = mround(raw_do_chances - self.double_two)
|
||||||
|
self.rem_xbh -= mround(self.double_two + self.double_cf + self.double_three)
|
||||||
|
logger.info(f'Double**: {self.double_two} / Double(cf): {self.double_cf} / rem xbh: {self.rem_xbh}')
|
||||||
|
|
||||||
|
self.triple = sanitize_chance_output(self.rem_xbh * tr_rate)
|
||||||
|
self.rem_xbh = mround(self.rem_xbh - self.triple)
|
||||||
|
logger.info(f'Triple: {self.triple} / rem xbh: {self.rem_xbh}')
|
||||||
|
|
||||||
|
raw_hr_chances = self.rem_xbh
|
||||||
|
logger.info(f'raw hr chances: {raw_hr_chances}')
|
||||||
|
|
||||||
|
if hr_per_fb_rate < .08:
|
||||||
|
self.bp_homerun = sanitize_chance_output(raw_hr_chances, min_chances=1.0, rounding=1.0)
|
||||||
|
elif hr_per_fb_rate > .28:
|
||||||
|
self.homerun = raw_hr_chances
|
||||||
|
elif hr_per_fb_rate > .18:
|
||||||
|
self.bp_homerun = sanitize_chance_output(raw_hr_chances * 0.4, min_chances=1.0, rounding=1.0)
|
||||||
|
self.homerun = self.rem_xbh - self.bp_homerun
|
||||||
|
else:
|
||||||
|
self.bp_homerun = sanitize_chance_output(raw_hr_chances * .75, min_chances=1.0, rounding=1.0)
|
||||||
|
self.homerun = mround(self.rem_xbh - self.bp_homerun)
|
||||||
|
logger.info(f'BP HR: {self.bp_homerun} / ND HR: {self.homerun}')
|
||||||
|
|
||||||
|
self.rem_xbh -= (self.bp_homerun + self.homerun)
|
||||||
|
logger.info(f'excess xbh: {self.rem_xbh}')
|
||||||
|
|
||||||
|
if self.rem_xbh > 0:
|
||||||
|
if self.triple > 1:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to triple')
|
||||||
|
self.triple += self.rem_xbh
|
||||||
|
self.rem_xbh = 0.0
|
||||||
|
elif self.double_cf > 1:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to double(cf)')
|
||||||
|
self.double_cf += self.rem_xbh
|
||||||
|
self.rem_xbh = 0.0
|
||||||
|
elif self.double_two > 1:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to double**')
|
||||||
|
self.double_two += self.rem_xbh
|
||||||
|
self.rem_xbh = 0.0
|
||||||
|
elif self.single_two > 1:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to single**')
|
||||||
|
self.single_two += self.rem_xbh
|
||||||
|
self.rem_xbh = 0.0
|
||||||
|
elif self.single_center > 1:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to single(cf)')
|
||||||
|
self.single_center += self.rem_xbh
|
||||||
|
self.rem_xbh = 0.0
|
||||||
|
elif self.single_one > 1:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to single*')
|
||||||
|
self.single_one += self.rem_xbh
|
||||||
|
self.rem_xbh = 0.0
|
||||||
|
else:
|
||||||
|
logger.info(f'Passing {self.rem_xbh} xbh to other_ob')
|
||||||
|
self.all_other_ob += self.rem_xbh
|
||||||
|
|
||||||
|
def calculate_other_ob(self, szn_walks, szn_hbp):
|
||||||
|
if szn_walks + szn_hbp == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
this_hbp = sanitize_chance_output(self.all_other_ob * szn_hbp / (szn_walks + szn_hbp), rounding=1.0)
|
||||||
|
logger.info(f'hbp value candidate: {this_hbp} / all_other_ob: {self.all_other_ob}')
|
||||||
|
self.hbp = max(min(this_hbp, self.all_other_ob), 0)
|
||||||
|
self.walk = mround(self.all_other_ob - self.hbp)
|
||||||
|
logger.info(f'self.hbp: {self.hbp} / self.walk: {self.walk}')
|
||||||
|
|
||||||
|
def calculate_strikouts(self, szn_strikeouts, szn_ab, szn_hits):
|
||||||
|
denom = max(szn_ab - szn_hits, 1)
|
||||||
|
raw_so = sanitize_chance_output(self.all_outs * (szn_strikeouts * 1.2) / denom)
|
||||||
|
sum_bb_so = self.walk + raw_so
|
||||||
|
excess = sum_bb_so - mround(math.floor(sum_bb_so))
|
||||||
|
logger.info(f'raw_so: {raw_so} / sum_bb_so: {sum_bb_so} / excess: {excess}')
|
||||||
|
|
||||||
|
self.strikeout = max(raw_so - excess - .05, 0.0)
|
||||||
|
if self.strikeout < 0:
|
||||||
|
logger.error(f'Strikeouts are less than zero :confusedpsyduck:')
|
||||||
|
|
||||||
|
def calculate_other_outs(self, fb_pct, gb_pct, oppo_pct):
|
||||||
|
rem_outs = 108 - self.total_chances()
|
||||||
|
|
||||||
|
all_fo = sanitize_chance_output(rem_outs * fb_pct)
|
||||||
|
if self.pit_hand == 'L':
|
||||||
|
self.flyout_lf_b = sanitize_chance_output(all_fo * oppo_pct)
|
||||||
|
else:
|
||||||
|
self.flyout_rf_b = sanitize_chance_output(all_fo * oppo_pct)
|
||||||
|
self.flyout_cf_b = all_fo - self.flyout_lf_b - self.flyout_rf_b
|
||||||
|
rem_outs -= (self.flyout_lf_b + self.flyout_cf_b + self.flyout_rf_b)
|
||||||
|
|
||||||
|
all_gb = rem_outs
|
||||||
|
self.groundout_a = sanitize_chance_output(all_gb * self.soft_rate)
|
||||||
|
self.groundout_b = sanitize_chance_output(all_gb - self.groundout_a)
|
||||||
|
|
||||||
|
rem_chances = 108 - self.total_chances()
|
||||||
|
logger.info(f'Remaining outs: {rem_chances}')
|
||||||
|
|
||||||
|
if self.strikeout > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to strikeouts')
|
||||||
|
self.strikeout += rem_chances
|
||||||
|
elif self.flyout_cf_b > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to fly(cf)')
|
||||||
|
self.flyout_cf_b += rem_chances
|
||||||
|
elif self.flyout_rf_b > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to fly(rf)')
|
||||||
|
self.flyout_rf_b += rem_chances
|
||||||
|
elif self.flyout_lf_b > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to fly(lf)')
|
||||||
|
self.flyout_lf_b += rem_chances
|
||||||
|
elif self.groundout_a > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to gbA')
|
||||||
|
self.groundout_a += rem_chances
|
||||||
|
elif self.single_one > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to single*')
|
||||||
|
self.single_one += rem_chances
|
||||||
|
elif self.single_center > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to single(cf)')
|
||||||
|
self.single_center += rem_chances
|
||||||
|
elif self.single_two > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to single**')
|
||||||
|
self.single_two += rem_chances
|
||||||
|
elif self.double_two > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to double**')
|
||||||
|
self.double_two += rem_chances
|
||||||
|
elif self.double_cf > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to double(cf)')
|
||||||
|
self.double_cf += rem_chances
|
||||||
|
elif self.triple > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to triple')
|
||||||
|
self.triple += rem_chances
|
||||||
|
elif self.homerun > 1:
|
||||||
|
logger.info(f'Passing {rem_chances} outs to homerun')
|
||||||
|
self.homerun += rem_chances
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Could not complete card')
|
||||||
@ -1,7 +1,7 @@
|
|||||||
from decimal import ROUND_HALF_EVEN, Decimal
|
from decimal import ROUND_HALF_EVEN, Decimal
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from batters.calcs_batter import bp_singles, wh_singles
|
from batters.models import bp_singles, wh_singles
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user