763 lines
23 KiB
Python
763 lines
23 KiB
Python
import random
|
|
|
|
import pydantic
|
|
|
|
from creation_helpers import mround, sanitize_chance_output
|
|
from typing import List, Literal
|
|
from decimal import Decimal
|
|
from exceptions import logger
|
|
|
|
|
|
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 * 0.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, 0.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 * 0.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 < 0.2:
|
|
return 0
|
|
elif hard_rate > 0.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 < 0.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 > 0.2:
|
|
return sanitize_chance_output(all_hr * 0.6)
|
|
else:
|
|
return sanitize_chance_output(all_hr * 0.25)
|
|
|
|
|
|
def bp_homeruns(all_hr, hr_rate):
|
|
if all_hr == 0 or hr_rate == 0:
|
|
return mround(0)
|
|
elif hr_rate > 0.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 > 0.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 < 0.4:
|
|
return mround(0)
|
|
else:
|
|
return mround(1.0)
|
|
|
|
|
|
def flyout_bq(rem_flyouts, soft_rate):
|
|
if rem_flyouts == 0 or soft_rate < 0.1:
|
|
return mround(0)
|
|
else:
|
|
return sanitize_chance_output(rem_flyouts * min(soft_rate * 3, mround(0.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 < 0.4:
|
|
return mround(0)
|
|
elif med_rate > 0.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
|
|
):
|
|
if chances == 0 or sb2s + cs2s == 0:
|
|
return 0, 0, False, 0
|
|
|
|
total_attempts = sb2s + cs2s + sb3s + cs3s
|
|
attempt_pct = total_attempts / chances
|
|
|
|
if attempt_pct >= 0.08:
|
|
st_auto = True
|
|
else:
|
|
st_auto = False
|
|
|
|
# chance_odds = [x / 36 for x in range(1, 36)]
|
|
if attempt_pct * 1.5 >= 1.0:
|
|
st_jump = 1.0
|
|
else:
|
|
st_jump = 0
|
|
for x in range(1, 37):
|
|
if attempt_pct * 1.5 <= x / 36:
|
|
st_jump = x / 36
|
|
break
|
|
|
|
st_high = mround(20 * (sb2s / (sb2s + cs2s + cs2s)))
|
|
if st_high <= 10:
|
|
st_auto = False
|
|
|
|
if sb3s + cs3s < max((3 * season_pct), 1):
|
|
st_low = 3
|
|
else:
|
|
st_low = mround(16 * ((sb2s + sb3s) / (sb2s + sb3s + cs2s * 2 + cs3s * 2)))
|
|
if not st_auto:
|
|
st_low = min(st_low, 10)
|
|
|
|
if st_low >= st_high - 3:
|
|
if st_high == 0:
|
|
st_low = 0
|
|
st_jump = 0
|
|
elif st_high <= 3:
|
|
st_high = 4
|
|
st_low = 1
|
|
else:
|
|
st_low = st_high - 3
|
|
|
|
# if ((st_high - 7) > st_low) and st_high > 7:
|
|
# st_low = st_high - 7
|
|
|
|
return round(st_low), round(st_high), st_auto, st_jump
|
|
|
|
|
|
def stealing_line(steal_data: dict):
|
|
sd = steal_data
|
|
jump_chances = round(sd[3] * 36)
|
|
|
|
if jump_chances == 0:
|
|
good_jump = "-"
|
|
elif jump_chances <= 6:
|
|
if jump_chances == 6:
|
|
good_jump = 7
|
|
elif jump_chances == 5:
|
|
good_jump = 6
|
|
elif jump_chances == 4:
|
|
good_jump = 5
|
|
elif jump_chances == 3:
|
|
good_jump = 4
|
|
elif jump_chances == 2:
|
|
good_jump = 3
|
|
elif jump_chances == 1:
|
|
good_jump = 2
|
|
elif jump_chances == 7:
|
|
good_jump = "4,5"
|
|
elif jump_chances == 8:
|
|
good_jump = "4,6"
|
|
elif jump_chances == 9:
|
|
good_jump = "3-5"
|
|
elif jump_chances == 10:
|
|
good_jump = "2-5"
|
|
elif jump_chances == 11:
|
|
good_jump = "6,7"
|
|
elif jump_chances == 12:
|
|
good_jump = "4-6"
|
|
elif jump_chances == 13:
|
|
good_jump = "2,4-6"
|
|
elif jump_chances == 14:
|
|
good_jump = "3-6"
|
|
elif jump_chances == 15:
|
|
good_jump = "2-6"
|
|
elif jump_chances == 16:
|
|
good_jump = "2,5-6"
|
|
elif jump_chances == 17:
|
|
good_jump = "3,5-6"
|
|
elif jump_chances == 18:
|
|
good_jump = "4-6"
|
|
elif jump_chances == 19:
|
|
good_jump = "2,4-7"
|
|
elif jump_chances == 20:
|
|
good_jump = "3-7"
|
|
elif jump_chances == 21:
|
|
good_jump = "2-7"
|
|
elif jump_chances == 22:
|
|
good_jump = "2-7,12"
|
|
elif jump_chances == 23:
|
|
good_jump = "2-7,11"
|
|
elif jump_chances == 24:
|
|
good_jump = "2,4-8"
|
|
elif jump_chances == 25:
|
|
good_jump = "3-8"
|
|
elif jump_chances == 26:
|
|
good_jump = "2-8"
|
|
elif jump_chances == 27:
|
|
good_jump = "2-8,12"
|
|
elif jump_chances == 28:
|
|
good_jump = "2-8,11"
|
|
elif jump_chances == 29:
|
|
good_jump = "3-9"
|
|
elif jump_chances == 30:
|
|
good_jump = "2-9"
|
|
elif jump_chances == 31:
|
|
good_jump = "2-9,12"
|
|
elif jump_chances == 32:
|
|
good_jump = "2-9,11"
|
|
elif jump_chances == 33:
|
|
good_jump = "2-10"
|
|
elif jump_chances == 34:
|
|
good_jump = "3-11"
|
|
elif jump_chances == 35:
|
|
good_jump = "2-11"
|
|
else:
|
|
good_jump = "2-12"
|
|
|
|
return f"{'*' if sd[2] else ''}{good_jump}/- ({sd[1] if sd[1] else '-'}-{sd[0] if sd[0] else '-'})"
|
|
|
|
|
|
def running(extra_base_pct: str):
|
|
if extra_base_pct == "":
|
|
return 8
|
|
try:
|
|
xb_pct = float(extra_base_pct.strip("%")) / 80
|
|
except Exception as e:
|
|
logger.error(f"calcs_batter running - {e}")
|
|
return 8
|
|
|
|
return max(min(round(6 + (10 * xb_pct)), 17), 8)
|
|
|
|
|
|
def bunting(num_bunts: int, season_pct: float):
|
|
if num_bunts > max(round(10 * season_pct), 4):
|
|
return "A"
|
|
elif num_bunts > max(round(5 * season_pct), 2):
|
|
return "B"
|
|
elif num_bunts > 1:
|
|
return "C"
|
|
else:
|
|
return "D"
|
|
|
|
|
|
def hit_and_run(
|
|
ab_vl: int,
|
|
ab_vr: int,
|
|
hits_vl: int,
|
|
hits_vr: int,
|
|
hr_vl: int,
|
|
hr_vr: int,
|
|
so_vl: int,
|
|
so_vr: int,
|
|
):
|
|
babip = (hits_vr + hits_vl - hr_vl - hr_vr) / max(
|
|
ab_vl + ab_vr - so_vl - so_vr - hr_vl - hr_vl, 1
|
|
)
|
|
if babip >= 0.35:
|
|
return "A"
|
|
elif babip >= 0.3:
|
|
return "B"
|
|
elif babip >= 0.25:
|
|
return "C"
|
|
else:
|
|
return "D"
|
|
|
|
|
|
def get_batter_ratings(df_data) -> List[dict]:
|
|
# Consider a sliding offense_mod based on OPS; floor of 1x and ceiling of 1.5x ?
|
|
offense_mod = 1.2
|
|
vl = BattingCardRatingsModel(
|
|
battingcard_id=df_data.battingcard_id,
|
|
bat_hand=df_data["bat_hand"],
|
|
vs_hand="L",
|
|
all_hits=sanitize_chance_output(108 * offense_mod * df_data["AVG_vL"]),
|
|
all_other_ob=sanitize_chance_output(
|
|
108
|
|
* offense_mod
|
|
* ((df_data["BB_vL"] + df_data["HBP_vL"]) / df_data["PA_vL"])
|
|
),
|
|
hard_rate=df_data["Hard%_vL"],
|
|
med_rate=df_data["Med%_vL"],
|
|
soft_rate=df_data["Soft%_vL"],
|
|
pull_rate=df_data["Pull%_vL"],
|
|
center_rate=df_data["Cent%_vL"],
|
|
slap_rate=df_data["Oppo%_vL"],
|
|
)
|
|
vr = BattingCardRatingsModel(
|
|
battingcard_id=df_data.battingcard_id,
|
|
bat_hand=df_data["bat_hand"],
|
|
vs_hand="R",
|
|
all_hits=sanitize_chance_output(108 * offense_mod * df_data["AVG_vR"]),
|
|
all_other_ob=sanitize_chance_output(
|
|
108
|
|
* offense_mod
|
|
* ((df_data["BB_vR"] + df_data["HBP_vR"]) / df_data["PA_vR"])
|
|
),
|
|
hard_rate=df_data["Hard%_vR"],
|
|
med_rate=df_data["Med%_vR"],
|
|
soft_rate=df_data["Soft%_vR"],
|
|
pull_rate=df_data["Pull%_vR"],
|
|
center_rate=df_data["Cent%_vR"],
|
|
slap_rate=df_data["Oppo%_vR"],
|
|
)
|
|
vl.all_outs = mround(
|
|
108 - vl.all_hits - vl.all_other_ob
|
|
) # .quantize(Decimal("0.05"))
|
|
vr.all_outs = mround(
|
|
108 - vr.all_hits - vr.all_other_ob
|
|
) # .quantize(Decimal("0.05"))
|
|
|
|
vl.calculate_singles(df_data["1B_vL"], df_data["H_vL"], mround(df_data["IFH%_vL"]))
|
|
vr.calculate_singles(df_data["1B_vR"], df_data["H_vR"], mround(df_data["IFH%_vR"]))
|
|
|
|
logger.debug(
|
|
f"vL - All Hits: {vl.all_hits} / Other OB: {vl.all_other_ob} / All Outs: {vl.all_outs} "
|
|
f"/ Total: {vl.all_hits + vl.all_other_ob + vl.all_outs}"
|
|
)
|
|
logger.debug(
|
|
f"vR - All Hits: {vr.all_hits} / Other OB: {vr.all_other_ob} / All Outs: {vr.all_outs} "
|
|
f"/ Total: {vr.all_hits + vr.all_other_ob + vr.all_outs}"
|
|
)
|
|
|
|
vl.calculate_xbh(
|
|
df_data["3B_vL"], df_data["2B_vL"], df_data["HR_vL"], df_data["HR/FB_vL"]
|
|
)
|
|
vr.calculate_xbh(
|
|
df_data["3B_vR"], df_data["2B_vR"], df_data["HR_vR"], df_data["HR/FB_vR"]
|
|
)
|
|
|
|
logger.debug(f"all_hits: {vl.all_hits} / sum of hits: {vl.total_chances()}")
|
|
logger.debug(f"all_hits: {vr.all_hits} / sum of hits: {vr.total_chances()}")
|
|
|
|
vl.calculate_other_ob(df_data["BB_vL"], df_data["HBP_vL"])
|
|
vr.calculate_other_ob(df_data["BB_vR"], df_data["HBP_vR"])
|
|
|
|
logger.debug(
|
|
f"all on base: {vl.hbp + vl.walk + vl.total_hits()} / all chances: {vl.total_chances()}"
|
|
f"{'*******ERROR ABOVE*******' if vl.hbp + vl.walk + vl.total_hits() != vl.total_chances() else ''}"
|
|
)
|
|
logger.debug(
|
|
f"all on base: {vr.hbp + vr.walk + vr.total_hits()} / all chances: {vr.total_chances()}"
|
|
f"{'*******ERROR ABOVE*******' if vr.hbp + vr.walk + vr.total_hits() != vr.total_chances() else ''}"
|
|
)
|
|
|
|
vl.calculate_strikeouts(df_data["SO_vL"], df_data["AB_vL"], df_data["H_vL"])
|
|
vr.calculate_strikeouts(df_data["SO_vR"], df_data["AB_vR"], df_data["H_vR"])
|
|
|
|
logger.debug(
|
|
f"K rate vL: {round(vl.strikeout / vl.all_outs, 2)} / "
|
|
f"K rate vR: {round(vr.strikeout / vr.all_outs, 2)}"
|
|
)
|
|
|
|
vl.calculate_other_outs(
|
|
df_data["FB%_vL"],
|
|
df_data["LD%_vL"],
|
|
df_data["GB%_vL"],
|
|
df_data["GDP_vL"],
|
|
df_data["AB_vL"],
|
|
)
|
|
vr.calculate_other_outs(
|
|
df_data["FB%_vR"],
|
|
df_data["LD%_vR"],
|
|
df_data["GB%_vR"],
|
|
df_data["GDP_vR"],
|
|
df_data["AB_vR"],
|
|
)
|
|
|
|
# Correct total chance errors
|
|
for x in [vl, vr]:
|
|
if x.total_chances() < 108:
|
|
diff = mround(108) - x.total_chances()
|
|
logger.error(f"Adding {diff} strikeouts to close gap")
|
|
x.strikeout += diff
|
|
elif x.total_chances() > 108:
|
|
diff = x.total_chances() - mround(108)
|
|
logger.error(f"Have surplus of {diff} chances")
|
|
if x.strikeout + 1 > diff:
|
|
logger.error(f"Subtracting {diff} strikeouts to close gap")
|
|
x.strikeout -= diff
|
|
elif x.lineout + 1 > diff:
|
|
logger.error(f"Subtracting {diff} lineouts to close gap")
|
|
x.lineout -= diff
|
|
elif x.groundout_a + 1 > diff:
|
|
logger.error(f"Subtracting {diff} gbA to close gap")
|
|
x.groundout_a -= diff
|
|
elif x.groundout_b + 1 > diff:
|
|
logger.error(f"Subtracting {diff} gbB to close gap")
|
|
x.groundout_b -= diff
|
|
elif x.groundout_c + 1 > diff:
|
|
logger.error(f"Subtracting {diff} gbC to close gap")
|
|
x.groundout_c -= diff
|
|
|
|
vl_total_chances = vl.total_chances()
|
|
vr_total_chances = vr.total_chances()
|
|
if vl_total_chances != 108:
|
|
logger.error(f"total chances for {df_data.name} come to {vl_total_chances}")
|
|
else:
|
|
logger.debug(f"total chances: {vl_total_chances}")
|
|
if vr_total_chances != 108:
|
|
logger.error(f"total chances for {df_data.name} come to {vr_total_chances}")
|
|
else:
|
|
logger.debug(f"total chances: {vr_total_chances}")
|
|
|
|
return [vl.custom_to_dict(), vr.custom_to_dict()]
|