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:
Cal Corum 2026-02-25 16:42:51 -06:00
parent a72abc01a3
commit 39c652e55c
7 changed files with 616 additions and 641 deletions

View File

@ -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']
) )

View File

@ -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
View 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
}

View File

@ -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']
) )

View File

@ -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
View 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')

View File

@ -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