Add offense_col resolver for retrosheet pipeline to fix 883 silent KeyErrors
The FullCard migration requires offense_col and player_id on each player's DataFrame row. The retrosheet pipeline calculates ratings before posting, so both fields were missing — causing silent card layout builder failures. Adds a three-tier resolution: CSV cache → API bulk fetch → deterministic hash fallback. Also includes player_id fallback in both calcs modules. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
32cadb1c49
commit
db3822565c
@ -8,14 +8,17 @@ from exceptions import logger
|
||||
from batters.models import BattingCardRatingsModel
|
||||
from batters.card_builder import build_batter_full_cards
|
||||
|
||||
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:
|
||||
return 0, 0, False, 0
|
||||
|
||||
total_attempts = sb2s + cs2s + sb3s + cs3s
|
||||
attempt_pct = total_attempts / chances
|
||||
|
||||
if attempt_pct >= .08:
|
||||
if attempt_pct >= 0.08:
|
||||
st_auto = True
|
||||
else:
|
||||
st_auto = False
|
||||
@ -62,7 +65,7 @@ def stealing_line(steal_data: dict):
|
||||
jump_chances = round(sd[3] * 36)
|
||||
|
||||
if jump_chances == 0:
|
||||
good_jump = '-'
|
||||
good_jump = "-"
|
||||
elif jump_chances <= 6:
|
||||
if jump_chances == 6:
|
||||
good_jump = 7
|
||||
@ -77,76 +80,76 @@ def stealing_line(steal_data: dict):
|
||||
elif jump_chances == 1:
|
||||
good_jump = 2
|
||||
elif jump_chances == 7:
|
||||
good_jump = '4,5'
|
||||
good_jump = "4,5"
|
||||
elif jump_chances == 8:
|
||||
good_jump = '4,6'
|
||||
good_jump = "4,6"
|
||||
elif jump_chances == 9:
|
||||
good_jump = '3-5'
|
||||
good_jump = "3-5"
|
||||
elif jump_chances == 10:
|
||||
good_jump = '2-5'
|
||||
good_jump = "2-5"
|
||||
elif jump_chances == 11:
|
||||
good_jump = '6,7'
|
||||
good_jump = "6,7"
|
||||
elif jump_chances == 12:
|
||||
good_jump = '4-6'
|
||||
good_jump = "4-6"
|
||||
elif jump_chances == 13:
|
||||
good_jump = '2,4-6'
|
||||
good_jump = "2,4-6"
|
||||
elif jump_chances == 14:
|
||||
good_jump = '3-6'
|
||||
good_jump = "3-6"
|
||||
elif jump_chances == 15:
|
||||
good_jump = '2-6'
|
||||
good_jump = "2-6"
|
||||
elif jump_chances == 16:
|
||||
good_jump = '2,5-6'
|
||||
good_jump = "2,5-6"
|
||||
elif jump_chances == 17:
|
||||
good_jump = '3,5-6'
|
||||
good_jump = "3,5-6"
|
||||
elif jump_chances == 18:
|
||||
good_jump = '4-6'
|
||||
good_jump = "4-6"
|
||||
elif jump_chances == 19:
|
||||
good_jump = '2,4-7'
|
||||
good_jump = "2,4-7"
|
||||
elif jump_chances == 20:
|
||||
good_jump = '3-7'
|
||||
good_jump = "3-7"
|
||||
elif jump_chances == 21:
|
||||
good_jump = '2-7'
|
||||
good_jump = "2-7"
|
||||
elif jump_chances == 22:
|
||||
good_jump = '2-7,12'
|
||||
good_jump = "2-7,12"
|
||||
elif jump_chances == 23:
|
||||
good_jump = '2-7,11'
|
||||
good_jump = "2-7,11"
|
||||
elif jump_chances == 24:
|
||||
good_jump = '2,4-8'
|
||||
good_jump = "2,4-8"
|
||||
elif jump_chances == 25:
|
||||
good_jump = '3-8'
|
||||
good_jump = "3-8"
|
||||
elif jump_chances == 26:
|
||||
good_jump = '2-8'
|
||||
good_jump = "2-8"
|
||||
elif jump_chances == 27:
|
||||
good_jump = '2-8,12'
|
||||
good_jump = "2-8,12"
|
||||
elif jump_chances == 28:
|
||||
good_jump = '2-8,11'
|
||||
good_jump = "2-8,11"
|
||||
elif jump_chances == 29:
|
||||
good_jump = '3-9'
|
||||
good_jump = "3-9"
|
||||
elif jump_chances == 30:
|
||||
good_jump = '2-9'
|
||||
good_jump = "2-9"
|
||||
elif jump_chances == 31:
|
||||
good_jump = '2-9,12'
|
||||
good_jump = "2-9,12"
|
||||
elif jump_chances == 32:
|
||||
good_jump = '2-9,11'
|
||||
good_jump = "2-9,11"
|
||||
elif jump_chances == 33:
|
||||
good_jump = '2-10'
|
||||
good_jump = "2-10"
|
||||
elif jump_chances == 34:
|
||||
good_jump = '3-11'
|
||||
good_jump = "3-11"
|
||||
elif jump_chances == 35:
|
||||
good_jump = '2-11'
|
||||
good_jump = "2-11"
|
||||
else:
|
||||
good_jump = '2-12'
|
||||
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 == '':
|
||||
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}')
|
||||
logger.error(f"calcs_batter running - {e}")
|
||||
xb_pct = 20
|
||||
|
||||
return max(min(round(6 + (10 * xb_pct)), 17), 8)
|
||||
@ -154,26 +157,36 @@ def running(extra_base_pct: str):
|
||||
|
||||
def bunting(num_bunts: int, season_pct: float):
|
||||
if num_bunts > max(round(10 * season_pct), 4):
|
||||
return 'A'
|
||||
return "A"
|
||||
elif num_bunts > max(round(5 * season_pct), 2):
|
||||
return 'B'
|
||||
return "B"
|
||||
elif num_bunts > 1:
|
||||
return 'C'
|
||||
return "C"
|
||||
else:
|
||||
return 'D'
|
||||
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 >= .35:
|
||||
return 'A'
|
||||
elif babip >= .3:
|
||||
return 'B'
|
||||
elif babip >= .25:
|
||||
return 'C'
|
||||
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'
|
||||
return "D"
|
||||
|
||||
|
||||
def get_batter_ratings(df_data) -> List[dict]:
|
||||
@ -181,120 +194,154 @@ def get_batter_ratings(df_data) -> List[dict]:
|
||||
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']
|
||||
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']
|
||||
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.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']))
|
||||
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}'
|
||||
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}'
|
||||
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'])
|
||||
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()}')
|
||||
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'])
|
||||
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 ""}')
|
||||
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'])
|
||||
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)}')
|
||||
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']
|
||||
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']
|
||||
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')
|
||||
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')
|
||||
logger.error(f"Have surplus of {diff} chances")
|
||||
if x.strikeout + 1 > diff:
|
||||
logger.error(f'Subtracting {diff} strikeouts to close gap')
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
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}')
|
||||
logger.error(f"total chances for {df_data.name} come to {vl_total_chances}")
|
||||
else:
|
||||
logger.debug(f'total chances: {vl_total_chances}')
|
||||
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}')
|
||||
logger.error(f"total chances for {df_data.name} come to {vr_total_chances}")
|
||||
else:
|
||||
logger.debug(f'total chances: {vr_total_chances}')
|
||||
logger.debug(f"total chances: {vr_total_chances}")
|
||||
|
||||
vl_dict = vl.custom_to_dict()
|
||||
vr_dict = vr.custom_to_dict()
|
||||
|
||||
try:
|
||||
offense_col = int(df_data["offense_col"]) if "offense_col" in df_data else 1
|
||||
player_id = (
|
||||
int(df_data["player_id"])
|
||||
if "player_id" in df_data
|
||||
else abs(hash(df_data["key_bbref"])) % 10000
|
||||
)
|
||||
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, offense_col, player_id, df_data["bat_hand"]
|
||||
)
|
||||
vl_dict.update(vl_card.card_output())
|
||||
vr_dict.update(vr_card.card_output())
|
||||
except Exception as e:
|
||||
logger.warning(f'Card layout builder failed for {df_data.name}: {e}')
|
||||
logger.warning(f"Card layout builder failed for {df_data.name}: {e}")
|
||||
|
||||
return [vl_dict, vr_dict]
|
||||
|
||||
102
offense_col_resolver.py
Normal file
102
offense_col_resolver.py
Normal file
@ -0,0 +1,102 @@
|
||||
"""Resolve offense_col for players in the retrosheet pipeline.
|
||||
|
||||
Three-tier resolution:
|
||||
1. Cache hit → stored value from data-input/offense_col_cache.csv
|
||||
2. API pre-fetch → bulk-fetch all MlbPlayers, merge new entries into cache
|
||||
3. Hash fallback → deterministic hash(player_name) % 3 + 1
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from db_calls import db_get
|
||||
from exceptions import logger
|
||||
|
||||
CACHE_PATH = "data-input/offense_col_cache.csv"
|
||||
|
||||
|
||||
def hash_offense_col(player_name: str) -> int:
|
||||
"""Deterministic offense_col from player name. Returns 1, 2, or 3."""
|
||||
normalized = player_name.strip().lower()
|
||||
digest = hashlib.md5(normalized.encode()).hexdigest()
|
||||
return int(digest, 16) % 3 + 1
|
||||
|
||||
|
||||
def load_cache(path: str = CACHE_PATH) -> dict[str, int]:
|
||||
"""Load {key_bbref: offense_col} from CSV cache."""
|
||||
if not os.path.exists(path):
|
||||
return {}
|
||||
df = pd.read_csv(path, dtype={"key_bbref": str, "offense_col": int})
|
||||
return dict(zip(df["key_bbref"], df["offense_col"]))
|
||||
|
||||
|
||||
def save_cache(cache: dict[str, tuple[str, int]], path: str = CACHE_PATH):
|
||||
"""Write cache to CSV. cache values are (player_name, offense_col)."""
|
||||
rows = sorted(
|
||||
[
|
||||
{"key_bbref": k, "player_name": v[0], "offense_col": v[1]}
|
||||
for k, v in cache.items()
|
||||
],
|
||||
key=lambda r: r["key_bbref"],
|
||||
)
|
||||
pd.DataFrame(rows).to_csv(path, index=False)
|
||||
|
||||
|
||||
async def resolve_offense_cols(
|
||||
df: pd.DataFrame, api_available: bool = True
|
||||
) -> pd.DataFrame:
|
||||
"""Add offense_col column to a stats DataFrame.
|
||||
|
||||
Args:
|
||||
df: DataFrame with key_bbref, use_name, last_name columns.
|
||||
api_available: If True, fetch from API to refresh cache.
|
||||
|
||||
Returns:
|
||||
df with offense_col column added.
|
||||
"""
|
||||
cache = load_cache()
|
||||
full_cache: dict[str, tuple[str, int]] = {}
|
||||
|
||||
# Seed full_cache from existing file cache
|
||||
for bbref, oc in cache.items():
|
||||
full_cache[bbref] = ("", oc)
|
||||
|
||||
# Refresh from API if available
|
||||
if api_available:
|
||||
try:
|
||||
result = await db_get("mlbplayers")
|
||||
if result and "players" in result:
|
||||
api_count = 0
|
||||
for p in result["players"]:
|
||||
bbref = p.get("key_bbref")
|
||||
oc = p.get("offense_col")
|
||||
name = f'{p.get("first_name", "")} {p.get("last_name", "")}'.strip()
|
||||
if bbref and oc:
|
||||
full_cache[bbref] = (name, int(oc))
|
||||
api_count += 1
|
||||
logger.info(
|
||||
f"offense_col_resolver: loaded {api_count} entries from API"
|
||||
)
|
||||
save_cache(full_cache)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"offense_col_resolver: API fetch failed, using cache only: {e}"
|
||||
)
|
||||
|
||||
# Build lookup from full_cache
|
||||
lookup = {k: v[1] for k, v in full_cache.items()}
|
||||
|
||||
# Resolve for each row
|
||||
def resolve_row(row):
|
||||
bbref = row.get("key_bbref", "")
|
||||
if bbref in lookup:
|
||||
return lookup[bbref]
|
||||
name = f'{row.get("use_name", "")} {row.get("last_name", "")}'.strip()
|
||||
oc = hash_offense_col(name)
|
||||
logger.debug(f"offense_col_resolver: hash fallback for {name} ({bbref}) → {oc}")
|
||||
return oc
|
||||
|
||||
df["offense_col"] = df.apply(resolve_row, axis=1)
|
||||
return df
|
||||
@ -6,94 +6,138 @@ from exceptions import logger
|
||||
|
||||
from pitchers.models import PitchingCardRatingsModel
|
||||
from pitchers.card_builder import build_pitcher_full_cards
|
||||
|
||||
|
||||
def get_pitcher_ratings(df_data) -> List[dict]:
|
||||
# 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_vr = float(108 * (df_data['BB_vR'] + df_data['HBP_vR']) / df_data['TBF_vR'])
|
||||
ob_vl = float(108 * (df_data["BB_vL"] + df_data["HBP_vL"]) / df_data["TBF_vL"])
|
||||
ob_vr = float(108 * (df_data["BB_vR"] + df_data["HBP_vR"]) / df_data["TBF_vR"])
|
||||
|
||||
vl = PitchingCardRatingsModel(
|
||||
pitchingcard_id=df_data.pitchingcard_id,
|
||||
pit_hand=df_data.pitch_hand,
|
||||
vs_hand='L',
|
||||
all_hits=sanitize_chance_output((df_data['AVG_vL'] - 0.05) * 108), # Subtracting chances from BP results
|
||||
vs_hand="L",
|
||||
all_hits=sanitize_chance_output(
|
||||
(df_data["AVG_vL"] - 0.05) * 108
|
||||
), # Subtracting chances from BP results
|
||||
all_other_ob=sanitize_chance_output(min(ob_vl, 0.8)),
|
||||
hard_rate=df_data['Hard%_vL'],
|
||||
med_rate=df_data['Med%_vL'],
|
||||
soft_rate=df_data['Soft%_vL']
|
||||
hard_rate=df_data["Hard%_vL"],
|
||||
med_rate=df_data["Med%_vL"],
|
||||
soft_rate=df_data["Soft%_vL"],
|
||||
)
|
||||
vr = PitchingCardRatingsModel(
|
||||
pitchingcard_id=df_data.pitchingcard_id,
|
||||
pit_hand=df_data.pitch_hand,
|
||||
vs_hand='R',
|
||||
all_hits=sanitize_chance_output((df_data['AVG_vR'] - 0.05) * 108), # Subtracting chances from BP results
|
||||
vs_hand="R",
|
||||
all_hits=sanitize_chance_output(
|
||||
(df_data["AVG_vR"] - 0.05) * 108
|
||||
), # Subtracting chances from BP results
|
||||
all_other_ob=sanitize_chance_output(min(ob_vr, 0.8)),
|
||||
hard_rate=df_data['Hard%_vR'],
|
||||
med_rate=df_data['Med%_vR'],
|
||||
soft_rate=df_data['Soft%_vR']
|
||||
hard_rate=df_data["Hard%_vR"],
|
||||
med_rate=df_data["Med%_vR"],
|
||||
soft_rate=df_data["Soft%_vR"],
|
||||
)
|
||||
vl.all_outs = mround(108 - vl.all_hits - vl.all_other_ob, base=0.5)
|
||||
vr.all_outs = mround(108 - vr.all_hits - vr.all_other_ob, base=0.5)
|
||||
|
||||
logger.info(
|
||||
f'vL - All Hits: {vl.all_hits} / Other OB: {vl.all_other_ob} / All Outs: {vl.all_outs} '
|
||||
f'/ Total: {vl.total_chances()}'
|
||||
f"vL - All Hits: {vl.all_hits} / Other OB: {vl.all_other_ob} / All Outs: {vl.all_outs} "
|
||||
f"/ Total: {vl.total_chances()}"
|
||||
)
|
||||
logger.info(
|
||||
f'vR - All Hits: {vr.all_hits} / Other OB: {vr.all_other_ob} / All Outs: {vr.all_outs} '
|
||||
f'/ Total: {vr.total_chances()}'
|
||||
f"vR - All Hits: {vr.all_hits} / Other OB: {vr.all_other_ob} / All Outs: {vr.all_outs} "
|
||||
f"/ Total: {vr.total_chances()}"
|
||||
)
|
||||
|
||||
vl.calculate_singles(df_data['H_vL'], df_data['H_vL'] - df_data['2B_vL'] - df_data['3B_vL'] - df_data['HR_vL'])
|
||||
vr.calculate_singles(df_data['H_vR'], df_data['H_vR'] - df_data['2B_vR'] - df_data['3B_vR'] - df_data['HR_vR'])
|
||||
vl.calculate_singles(
|
||||
df_data["H_vL"],
|
||||
df_data["H_vL"] - df_data["2B_vL"] - df_data["3B_vL"] - df_data["HR_vL"],
|
||||
)
|
||||
vr.calculate_singles(
|
||||
df_data["H_vR"],
|
||||
df_data["H_vR"] - df_data["2B_vR"] - df_data["3B_vR"] - df_data["HR_vR"],
|
||||
)
|
||||
|
||||
logger.info(f'vL: All Hits: {vl.all_hits} / BP Singles: {vl.bp_single} / Single 2: {vl.single_two} / '
|
||||
f'Single 1: {vl.single_one} / Single CF: {vl.single_center}')
|
||||
logger.info(f'vR: All Hits: {vr.all_hits} / BP Singles: {vr.bp_single} / Single 2: {vr.single_two} / '
|
||||
f'Single 1: {vr.single_one} / Single CF: {vr.single_center}')
|
||||
logger.info(
|
||||
f"vL: All Hits: {vl.all_hits} / BP Singles: {vl.bp_single} / Single 2: {vl.single_two} / "
|
||||
f"Single 1: {vl.single_one} / Single CF: {vl.single_center}"
|
||||
)
|
||||
logger.info(
|
||||
f"vR: All Hits: {vr.all_hits} / BP Singles: {vr.bp_single} / Single 2: {vr.single_two} / "
|
||||
f"Single 1: {vr.single_one} / Single CF: {vr.single_center}"
|
||||
)
|
||||
|
||||
vl.calculate_xbh(df_data['2B_vL'], df_data['3B_vL'], df_data['HR_vL'], df_data['HR/FB_vL'])
|
||||
vr.calculate_xbh(df_data['2B_vR'], df_data['3B_vR'], df_data['HR_vR'], df_data['HR/FB_vR'])
|
||||
vl.calculate_xbh(
|
||||
df_data["2B_vL"], df_data["3B_vL"], df_data["HR_vL"], df_data["HR/FB_vL"]
|
||||
)
|
||||
vr.calculate_xbh(
|
||||
df_data["2B_vR"], df_data["3B_vR"], df_data["HR_vR"], df_data["HR/FB_vR"]
|
||||
)
|
||||
|
||||
logger.debug(f'vL: All XBH: {vl.all_hits - vl.single_one - vl.single_two - vl.single_center - vl.bp_single} / '
|
||||
f'Double**: {vl.double_two} / Double(cf): {vl.double_cf} / Triple: {vl.triple} / '
|
||||
f'BP HR: {vl.bp_homerun} / ND HR: {vl.homerun}')
|
||||
logger.debug(f'vR: All XBH: {vr.all_hits - vr.single_one - vr.single_two - vr.single_center - vr.bp_single} / '
|
||||
f'Double**: {vr.double_two} / Double(cf): {vr.double_cf} / Triple: {vr.triple} / '
|
||||
f'BP HR: {vr.bp_homerun} / ND HR: {vr.homerun}')
|
||||
logger.debug(
|
||||
f"vL: All XBH: {vl.all_hits - vl.single_one - vl.single_two - vl.single_center - vl.bp_single} / "
|
||||
f"Double**: {vl.double_two} / Double(cf): {vl.double_cf} / Triple: {vl.triple} / "
|
||||
f"BP HR: {vl.bp_homerun} / ND HR: {vl.homerun}"
|
||||
)
|
||||
logger.debug(
|
||||
f"vR: All XBH: {vr.all_hits - vr.single_one - vr.single_two - vr.single_center - vr.bp_single} / "
|
||||
f"Double**: {vr.double_two} / Double(cf): {vr.double_cf} / Triple: {vr.triple} / "
|
||||
f"BP HR: {vr.bp_homerun} / ND HR: {vr.homerun}"
|
||||
)
|
||||
|
||||
vl.calculate_other_ob(df_data['BB_vL'], df_data['HBP_vL'])
|
||||
vr.calculate_other_ob(df_data['BB_vR'], df_data['HBP_vR'])
|
||||
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.info(f'vL: All other OB: {vl.all_other_ob} / HBP: {vl.hbp} / BB: {vl.walk} / '
|
||||
f'Total Chances: {vl.total_chances()}')
|
||||
logger.info(f'vR: All other OB: {vr.all_other_ob} / HBP: {vr.hbp} / BB: {vr.walk} / '
|
||||
f'Total Chances: {vr.total_chances()}')
|
||||
logger.info(
|
||||
f"vL: All other OB: {vl.all_other_ob} / HBP: {vl.hbp} / BB: {vl.walk} / "
|
||||
f"Total Chances: {vl.total_chances()}"
|
||||
)
|
||||
logger.info(
|
||||
f"vR: All other OB: {vr.all_other_ob} / HBP: {vr.hbp} / BB: {vr.walk} / "
|
||||
f"Total Chances: {vr.total_chances()}"
|
||||
)
|
||||
|
||||
vl.calculate_strikouts(
|
||||
df_data['SO_vL'], df_data['TBF_vL'] - df_data['BB_vL'] - df_data['IBB_vL'] - df_data['HBP_vL'], df_data['H_vL'])
|
||||
df_data["SO_vL"],
|
||||
df_data["TBF_vL"] - df_data["BB_vL"] - df_data["IBB_vL"] - df_data["HBP_vL"],
|
||||
df_data["H_vL"],
|
||||
)
|
||||
vr.calculate_strikouts(
|
||||
df_data['SO_vR'], df_data['TBF_vR'] - df_data['BB_vR'] - df_data['IBB_vR'] - df_data['HBP_vR'], df_data['H_vR'])
|
||||
df_data["SO_vR"],
|
||||
df_data["TBF_vR"] - df_data["BB_vR"] - df_data["IBB_vR"] - df_data["HBP_vR"],
|
||||
df_data["H_vR"],
|
||||
)
|
||||
|
||||
logger.info(f'vL: All Outs: {vl.all_outs} / Ks: {vl.strikeout} / Current Outs: {vl.total_outs()}')
|
||||
logger.info(f'vR: All Outs: {vr.all_outs} / Ks: {vr.strikeout} / Current Outs: {vr.total_outs()}')
|
||||
logger.info(
|
||||
f"vL: All Outs: {vl.all_outs} / Ks: {vl.strikeout} / Current Outs: {vl.total_outs()}"
|
||||
)
|
||||
logger.info(
|
||||
f"vR: All Outs: {vr.all_outs} / Ks: {vr.strikeout} / Current Outs: {vr.total_outs()}"
|
||||
)
|
||||
|
||||
vl.calculate_other_outs(df_data['FB%_vL'], df_data['GB%_vL'], df_data['Oppo%_vL'])
|
||||
vr.calculate_other_outs(df_data['FB%_vR'], df_data['GB%_vR'], df_data['Oppo%_vR'])
|
||||
vl.calculate_other_outs(df_data["FB%_vL"], df_data["GB%_vL"], df_data["Oppo%_vL"])
|
||||
vr.calculate_other_outs(df_data["FB%_vR"], df_data["GB%_vR"], df_data["Oppo%_vR"])
|
||||
|
||||
logger.info(f'vL: Total chances: {vl.total_chances()}')
|
||||
logger.info(f'vR: Total chances: {vr.total_chances()}')
|
||||
logger.info(f"vL: Total chances: {vl.total_chances()}")
|
||||
logger.info(f"vR: Total chances: {vr.total_chances()}")
|
||||
|
||||
vl_dict = vl.custom_to_dict()
|
||||
vr_dict = vr.custom_to_dict()
|
||||
|
||||
try:
|
||||
offense_col = int(df_data["offense_col"]) if "offense_col" in df_data else 1
|
||||
player_id = (
|
||||
int(df_data["player_id"])
|
||||
if "player_id" in df_data
|
||||
else abs(hash(df_data["key_bbref"])) % 10000
|
||||
)
|
||||
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, offense_col, player_id, df_data["pitch_hand"]
|
||||
)
|
||||
vl_dict.update(vl_card.card_output())
|
||||
vr_dict.update(vr_card.card_output())
|
||||
except Exception as e:
|
||||
logger.warning(f'Card layout builder failed for {df_data.name}: {e}')
|
||||
logger.warning(f"Card layout builder failed for {df_data.name}: {e}")
|
||||
|
||||
return [vl_dict, vr_dict]
|
||||
|
||||
@ -101,59 +145,61 @@ def get_pitcher_ratings(df_data) -> List[dict]:
|
||||
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']:
|
||||
if key not in ["id", "player_id", "cardset_id", "vs_hand", "is_prep"]:
|
||||
sum_chances += chance_data[key]
|
||||
|
||||
return sum_chances
|
||||
|
||||
|
||||
def soft_rate(pct):
|
||||
if pct > .2:
|
||||
return 'high'
|
||||
elif pct < .1:
|
||||
return 'low'
|
||||
if pct > 0.2:
|
||||
return "high"
|
||||
elif pct < 0.1:
|
||||
return "low"
|
||||
else:
|
||||
return 'avg'
|
||||
return "avg"
|
||||
|
||||
|
||||
def med_rate(pct):
|
||||
if pct > .65:
|
||||
return 'high'
|
||||
elif pct < .4:
|
||||
return 'low'
|
||||
if pct > 0.65:
|
||||
return "high"
|
||||
elif pct < 0.4:
|
||||
return "low"
|
||||
else:
|
||||
return 'avg'
|
||||
return "avg"
|
||||
|
||||
|
||||
def hard_rate(pct):
|
||||
if pct > .4:
|
||||
return 'high'
|
||||
elif pct < .2:
|
||||
return 'low'
|
||||
if pct > 0.4:
|
||||
return "high"
|
||||
elif pct < 0.2:
|
||||
return "low"
|
||||
else:
|
||||
return 'avg'
|
||||
return "avg"
|
||||
|
||||
|
||||
def hr_per_fb_rate(pct):
|
||||
if pct > .18:
|
||||
return 'high'
|
||||
elif pct < .08:
|
||||
return 'low'
|
||||
if pct > 0.18:
|
||||
return "high"
|
||||
elif pct < 0.08:
|
||||
return "low"
|
||||
else:
|
||||
return 'avg'
|
||||
return "avg"
|
||||
|
||||
|
||||
def all_singles(row, hits_vl, hits_vr):
|
||||
if int(row[7]) == 0:
|
||||
tot_singles_vl = 0
|
||||
else:
|
||||
tot_singles_vl = hits_vl * ((int(row[7]) - int(row[8]) - int(row[9]) - int(row[12]))
|
||||
/ int(row[7]))
|
||||
tot_singles_vl = hits_vl * (
|
||||
(int(row[7]) - int(row[8]) - int(row[9]) - int(row[12])) / int(row[7])
|
||||
)
|
||||
if int(row[40]) == 0:
|
||||
tot_singles_vr = 0
|
||||
else:
|
||||
tot_singles_vr = hits_vr * ((int(row[40]) - int(row[41]) - int(row[42]) - int(row[45]))
|
||||
/ int(row[40]))
|
||||
tot_singles_vr = hits_vr * (
|
||||
(int(row[40]) - int(row[41]) - int(row[42]) - int(row[45])) / int(row[40])
|
||||
)
|
||||
|
||||
return mround(tot_singles_vl), mround(tot_singles_vr)
|
||||
|
||||
@ -166,12 +212,12 @@ def bp_singles(singles_vl, singles_vr):
|
||||
|
||||
|
||||
def wh_singles(rem_si_vl, rem_si_vr, hard_rate_vl, hard_rate_vr):
|
||||
if hard_rate_vl == 'low':
|
||||
if hard_rate_vl == "low":
|
||||
whs_vl = 0
|
||||
else:
|
||||
whs_vl = rem_si_vl / 2
|
||||
|
||||
if hard_rate_vr == 'low':
|
||||
if hard_rate_vr == "low":
|
||||
whs_vr = 0
|
||||
else:
|
||||
whs_vr = rem_si_vr / 2
|
||||
@ -180,12 +226,12 @@ def wh_singles(rem_si_vl, rem_si_vr, hard_rate_vl, hard_rate_vr):
|
||||
|
||||
|
||||
def one_singles(rem_si_vl, rem_si_vr, soft_rate_vl, soft_rate_vr):
|
||||
if soft_rate_vl == 'high':
|
||||
if soft_rate_vl == "high":
|
||||
oss_vl = rem_si_vl
|
||||
else:
|
||||
oss_vl = 0
|
||||
|
||||
if soft_rate_vr == 'high':
|
||||
if soft_rate_vr == "high":
|
||||
oss_vr = rem_si_vr
|
||||
else:
|
||||
oss_vr = 0
|
||||
@ -194,19 +240,19 @@ def one_singles(rem_si_vl, rem_si_vr, soft_rate_vl, soft_rate_vr):
|
||||
|
||||
|
||||
def bp_homerun(hr_vl, hr_vr, hr_rate_vl, hr_rate_vr):
|
||||
if hr_rate_vl == 'low':
|
||||
if hr_rate_vl == "low":
|
||||
bphr_vl = hr_vl
|
||||
elif hr_rate_vl == 'avg':
|
||||
bphr_vl = hr_vl * .75
|
||||
elif hr_rate_vl == "avg":
|
||||
bphr_vl = hr_vl * 0.75
|
||||
else:
|
||||
bphr_vl = hr_vl * .4
|
||||
bphr_vl = hr_vl * 0.4
|
||||
|
||||
if hr_rate_vr == 'low':
|
||||
if hr_rate_vr == "low":
|
||||
bphr_vr = hr_vr
|
||||
elif hr_rate_vr == 'avg':
|
||||
bphr_vr = hr_vr * .75
|
||||
elif hr_rate_vr == "avg":
|
||||
bphr_vr = hr_vr * 0.75
|
||||
else:
|
||||
bphr_vr = hr_vr * .4
|
||||
bphr_vr = hr_vr * 0.4
|
||||
|
||||
return mround(bphr_vl), mround(bphr_vr)
|
||||
|
||||
@ -219,8 +265,8 @@ def triples(all_xbh_vl, all_xbh_vr, triple_rate_vl, triple_rate_vr):
|
||||
|
||||
|
||||
def two_doubles(all_doubles_vl, all_doubles_vr, soft_rate_vl, soft_rate_vr):
|
||||
two_doubles_vl = all_doubles_vl if soft_rate_vl == 'high' else 0
|
||||
two_doubles_vr = all_doubles_vr if soft_rate_vr == 'high' else 0
|
||||
two_doubles_vl = all_doubles_vl if soft_rate_vl == "high" else 0
|
||||
two_doubles_vr = all_doubles_vr if soft_rate_vr == "high" else 0
|
||||
|
||||
return mround(two_doubles_vl), mround(two_doubles_vr)
|
||||
|
||||
@ -242,21 +288,21 @@ def hbps(all_ob, this_hbp_rate):
|
||||
|
||||
|
||||
def xchecks(pos, all_chances=True):
|
||||
if pos.lower() == 'p':
|
||||
if pos.lower() == "p":
|
||||
return 1 if all_chances else 0
|
||||
elif pos.lower() == 'c':
|
||||
elif pos.lower() == "c":
|
||||
return 3 if all_chances else 2
|
||||
elif pos.lower() == '1b':
|
||||
elif pos.lower() == "1b":
|
||||
return 2 if all_chances else 1
|
||||
elif pos.lower() == '2b':
|
||||
elif pos.lower() == "2b":
|
||||
return 6 if all_chances else 5
|
||||
elif pos.lower() == '3b':
|
||||
elif pos.lower() == "3b":
|
||||
return 3 if all_chances else 2
|
||||
elif pos.lower() == 'ss':
|
||||
elif pos.lower() == "ss":
|
||||
return 7 if all_chances else 6
|
||||
elif pos.lower() == 'lf':
|
||||
elif pos.lower() == "lf":
|
||||
return 2 if all_chances else 1
|
||||
elif pos.lower() == 'cf':
|
||||
elif pos.lower() == "cf":
|
||||
return 3 if all_chances else 2
|
||||
else:
|
||||
return 2 if all_chances else 1
|
||||
@ -272,7 +318,7 @@ def oppo_fly(all_fly, oppo_rate):
|
||||
def groundball_a(all_gb, dp_rate):
|
||||
if all_gb == 0 or dp_rate == 0:
|
||||
return 0
|
||||
elif dp_rate > .6:
|
||||
elif dp_rate > 0.6:
|
||||
return all_gb
|
||||
else:
|
||||
return mround(all_gb * (dp_rate * 1.5))
|
||||
@ -282,20 +328,22 @@ def balks(total_balks: int, innings: float, season_pct):
|
||||
try:
|
||||
total_balks = int(total_balks)
|
||||
except ValueError:
|
||||
logger.error(f'Could not read balks: {total_balks} / setting to 0')
|
||||
logger.error(f"Could not read balks: {total_balks} / setting to 0")
|
||||
total_balks = 0
|
||||
|
||||
|
||||
try:
|
||||
innings = float(innings)
|
||||
except ValueError:
|
||||
logger.error(f'Could not read innings: {innings} / setting to 0')
|
||||
logger.error(f"Could not read innings: {innings} / setting to 0")
|
||||
innings = 0
|
||||
|
||||
if innings == 0:
|
||||
return 0
|
||||
|
||||
numerator = (total_balks * 290 * season_pct)
|
||||
logger.info(f'total_balks: {total_balks} / season_pct {season_pct} / innings: {innings} / numerator: {numerator}')
|
||||
|
||||
numerator = total_balks * 290 * season_pct
|
||||
logger.info(
|
||||
f"total_balks: {total_balks} / season_pct {season_pct} / innings: {innings} / numerator: {numerator}"
|
||||
)
|
||||
|
||||
return min(round(numerator / innings), 20)
|
||||
|
||||
@ -311,19 +359,19 @@ def closer_rating(gf: int, saves: int, games: int):
|
||||
if gf == 0 or games == 0 or saves == 0:
|
||||
return None
|
||||
|
||||
if gf / games >= .875:
|
||||
if gf / games >= 0.875:
|
||||
return 6
|
||||
elif gf / games >= .8:
|
||||
elif gf / games >= 0.8:
|
||||
return 5
|
||||
elif gf / games >= .7:
|
||||
elif gf / games >= 0.7:
|
||||
return 4
|
||||
elif gf / games >= .55:
|
||||
elif gf / games >= 0.55:
|
||||
return 3
|
||||
elif gf / games >= .4:
|
||||
elif gf / games >= 0.4:
|
||||
return 2
|
||||
elif gf / games >= .25:
|
||||
elif gf / games >= 0.25:
|
||||
return 1
|
||||
elif gf / games >= .1:
|
||||
elif gf / games >= 0.1:
|
||||
return 0
|
||||
else:
|
||||
return None
|
||||
|
||||
2540
retrosheet_data.py
2540
retrosheet_data.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user