- Add Sippie Swartzel custom batter profile (0.820 OPS, SS/RF, no HR power) - Update Kalin Young profile (0.891 OPS, All-Star rarity) - Update Admiral Ball Traits profile with innings field - Fix S3 cache-busting to include Unix timestamp for same-day updates - Add pd_cards/core/upload.py and scouting.py modules - Add custom card submission scripts and documentation - Add uv.lock for dependency tracking
489 lines
19 KiB
Python
489 lines
19 KiB
Python
"""
|
|
Tony Smehrik - Custom Pitcher Card Preview
|
|
|
|
FIRST PASS - Target specs:
|
|
- Name: Tony Smehrik
|
|
- Hand: Left
|
|
- Position: RP, SP (starter_rating: 5, relief_rating: 5, closer_rating: None)
|
|
- Target Combined OPS: 0.585
|
|
- Target OPS vs L: 0.495 (dominant vs lefties - same-side advantage)
|
|
- K-rate vs L: average (~20%)
|
|
- K-rate vs R: very low (~12%)
|
|
- Flyball rate vs L: average (~35%)
|
|
- Flyball rate vs R: high (~45%)
|
|
- Team: Custom Ballplayers
|
|
- Cardset ID: 29
|
|
|
|
Uses CORRECT pitcher schema with:
|
|
- double_cf (not double_pull)
|
|
- flyout_lf_b, flyout_cf_b, flyout_rf_b (no flyout_a, flyout_bq)
|
|
- groundout_a, groundout_b only (no groundout_c)
|
|
- xcheck fields (P, C, 1B, 2B, 3B, SS, LF, CF, RF) = 29 chances total
|
|
"""
|
|
|
|
import asyncio
|
|
from dataclasses import dataclass
|
|
from creation_helpers import mround, sanitize_chance_output
|
|
|
|
|
|
# Default x-check values (from calcs_pitcher.py)
|
|
DEFAULT_XCHECKS = {
|
|
'xcheck_p': 1.0,
|
|
'xcheck_c': 3.0,
|
|
'xcheck_1b': 2.0,
|
|
'xcheck_2b': 6.0,
|
|
'xcheck_3b': 3.0,
|
|
'xcheck_ss': 7.0,
|
|
'xcheck_lf': 2.0,
|
|
'xcheck_cf': 3.0,
|
|
'xcheck_rf': 2.0,
|
|
}
|
|
TOTAL_XCHECK = sum(DEFAULT_XCHECKS.values()) # 29.0
|
|
|
|
|
|
def calculate_pitcher_rating(
|
|
vs_hand: str,
|
|
pit_hand: str,
|
|
avg: float,
|
|
obp: float,
|
|
slg: float,
|
|
bb_pct: float,
|
|
k_pct: float,
|
|
hr_per_hit: float,
|
|
triple_per_hit: float,
|
|
double_per_hit: float,
|
|
fb_pct: float,
|
|
gb_pct: float,
|
|
hard_pct: float,
|
|
med_pct: float,
|
|
soft_pct: float,
|
|
oppo_pct: float,
|
|
hr_fb_pct: float = 0.10,
|
|
) -> dict:
|
|
"""
|
|
Calculate pitcher card ratings using the correct schema.
|
|
|
|
Total chances = 108:
|
|
- Hits (HR, BP-HR, 3B, 2B, 1B variants)
|
|
- On-base (BB, HBP)
|
|
- Outs (K, flyouts, groundouts)
|
|
- X-checks (29 chances that redirect to pitcher's card)
|
|
"""
|
|
# Calculate base chances (108 total, minus 29 for x-checks = 79 "real" chances)
|
|
# But wait - the x-checks ARE part of the 108. They're outs that redirect.
|
|
# So: hits + ob + outs = 108, where outs includes K, flyouts, groundouts, AND xchecks
|
|
|
|
# From calcs_pitcher.py, total_chances includes xchecks in the out count
|
|
# Let's calculate hits and OB first, then outs fill the rest
|
|
|
|
# Hits allowed (adjusted for the 1.2x offense modifier used in game)
|
|
# Note: calcs_pitcher subtracts 0.05 from AVG for BP results
|
|
all_hits = sanitize_chance_output((avg - 0.05) * 108)
|
|
|
|
# OB (walks + HBP)
|
|
all_other_ob = sanitize_chance_output(min(bb_pct * 108, 0.8 * 108)) # Cap at ~86 chances
|
|
|
|
# Outs (everything else)
|
|
all_outs = mround(108 - all_hits - all_other_ob, base=0.5)
|
|
|
|
# ===== HITS DISTRIBUTION =====
|
|
# Singles
|
|
single_pct = 1.0 - hr_per_hit - triple_per_hit - double_per_hit
|
|
total_singles = sanitize_chance_output(all_hits * single_pct)
|
|
|
|
bp_single = 5.0 if total_singles >= 5 else 0.0
|
|
rem_singles = total_singles - bp_single
|
|
|
|
# Distribute singles based on contact quality
|
|
single_two = sanitize_chance_output(rem_singles / 2) if hard_pct >= 0.2 else 0.0
|
|
rem_singles -= single_two
|
|
|
|
single_one = sanitize_chance_output(rem_singles) if soft_pct >= 0.2 else 0.0
|
|
rem_singles -= single_one
|
|
|
|
single_center = sanitize_chance_output(rem_singles)
|
|
|
|
# XBH
|
|
rem_xbh = all_hits - bp_single - single_two - single_one - single_center
|
|
|
|
# Doubles
|
|
xbh_total = hr_per_hit + triple_per_hit + double_per_hit
|
|
if xbh_total > 0:
|
|
do_rate = double_per_hit / xbh_total
|
|
tr_rate = triple_per_hit / xbh_total
|
|
hr_rate = hr_per_hit / xbh_total
|
|
else:
|
|
do_rate = tr_rate = hr_rate = 0.0
|
|
|
|
raw_doubles = sanitize_chance_output(rem_xbh * do_rate)
|
|
double_two = raw_doubles if soft_pct > 0.2 else 0.0
|
|
double_cf = mround(raw_doubles - double_two)
|
|
double_three = 0.0 # Reserved for special cases
|
|
|
|
rem_xbh -= (double_two + double_cf + double_three)
|
|
|
|
# Triples
|
|
triple = sanitize_chance_output(rem_xbh * tr_rate)
|
|
rem_xbh = mround(rem_xbh - triple)
|
|
|
|
# Home runs
|
|
raw_hr = rem_xbh
|
|
if hr_fb_pct < 0.08:
|
|
bp_homerun = sanitize_chance_output(raw_hr, min_chances=1.0, rounding=1.0)
|
|
homerun = 0.0
|
|
elif hr_fb_pct > 0.28:
|
|
homerun = raw_hr
|
|
bp_homerun = 0.0
|
|
elif hr_fb_pct > 0.18:
|
|
bp_homerun = sanitize_chance_output(raw_hr * 0.4, min_chances=1.0, rounding=1.0)
|
|
homerun = mround(raw_hr - bp_homerun)
|
|
else:
|
|
bp_homerun = sanitize_chance_output(raw_hr * 0.75, min_chances=1.0, rounding=1.0)
|
|
homerun = mround(raw_hr - bp_homerun)
|
|
|
|
# ===== ON-BASE DISTRIBUTION =====
|
|
# Assume 90% walks, 10% HBP
|
|
hbp = mround(all_other_ob * 0.10)
|
|
walk = mround(all_other_ob - hbp)
|
|
|
|
# ===== OUTS DISTRIBUTION =====
|
|
# Strikeouts (adjusted for K%)
|
|
# K rate is K/PA, but we need K/(AB-H) for out distribution
|
|
# Simplified: use K% directly scaled to outs
|
|
raw_so = sanitize_chance_output(all_outs * k_pct * 1.2) # 1.2x modifier from calcs_pitcher
|
|
|
|
# Cap strikeouts to leave room for other outs
|
|
current_total = (homerun + bp_homerun + triple + double_three + double_two + double_cf +
|
|
single_two + single_one + single_center + bp_single + hbp + walk)
|
|
max_so = 108 - current_total - TOTAL_XCHECK - 5 # Leave at least 5 for flyouts/groundouts
|
|
strikeout = min(raw_so, max_so)
|
|
|
|
# Remaining outs (after K and x-checks)
|
|
rem_outs = 108 - current_total - strikeout - TOTAL_XCHECK
|
|
|
|
# Flyouts vs groundouts based on FB%/GB%
|
|
total_batted = fb_pct + gb_pct
|
|
if total_batted > 0:
|
|
fb_share = fb_pct / total_batted
|
|
gb_share = gb_pct / total_batted
|
|
else:
|
|
fb_share = gb_share = 0.5
|
|
|
|
all_flyouts = sanitize_chance_output(rem_outs * fb_share)
|
|
all_groundouts = rem_outs - all_flyouts
|
|
|
|
# Distribute flyouts by field (pitcher hand affects distribution)
|
|
if pit_hand == 'L':
|
|
# Lefty: more fly balls to LF (opposite field for RHB)
|
|
flyout_lf_b = sanitize_chance_output(all_flyouts * oppo_pct)
|
|
flyout_rf_b = sanitize_chance_output(all_flyouts * 0.25)
|
|
else:
|
|
# Righty: more fly balls to RF (opposite field for LHB)
|
|
flyout_rf_b = sanitize_chance_output(all_flyouts * oppo_pct)
|
|
flyout_lf_b = sanitize_chance_output(all_flyouts * 0.25)
|
|
flyout_cf_b = all_flyouts - flyout_lf_b - flyout_rf_b
|
|
|
|
# Distribute groundouts (A = DP potential, B = routine)
|
|
groundout_a = sanitize_chance_output(all_groundouts * soft_pct)
|
|
groundout_b = sanitize_chance_output(all_groundouts - groundout_a)
|
|
|
|
# Build the rating dict
|
|
rating = {
|
|
'pitchingcard_id': 0, # Will be set on submission
|
|
'vs_hand': vs_hand,
|
|
'homerun': mround(homerun),
|
|
'bp_homerun': mround(bp_homerun),
|
|
'triple': mround(triple),
|
|
'double_three': mround(double_three),
|
|
'double_two': mround(double_two),
|
|
'double_cf': mround(double_cf),
|
|
'single_two': mround(single_two),
|
|
'single_one': mround(single_one),
|
|
'single_center': mround(single_center),
|
|
'bp_single': mround(bp_single),
|
|
'hbp': mround(hbp),
|
|
'walk': mround(walk),
|
|
'strikeout': mround(strikeout),
|
|
'flyout_lf_b': mround(flyout_lf_b),
|
|
'flyout_cf_b': mround(flyout_cf_b),
|
|
'flyout_rf_b': mround(flyout_rf_b),
|
|
'groundout_a': mround(groundout_a),
|
|
'groundout_b': mround(groundout_b),
|
|
**DEFAULT_XCHECKS,
|
|
# Calculated stats for display
|
|
'avg': avg,
|
|
'obp': obp,
|
|
'slg': slg,
|
|
}
|
|
|
|
# Verify total and adjust if needed
|
|
total = sum([
|
|
rating['homerun'], rating['bp_homerun'], rating['triple'],
|
|
rating['double_three'], rating['double_two'], rating['double_cf'],
|
|
rating['single_two'], rating['single_one'], rating['single_center'], rating['bp_single'],
|
|
rating['hbp'], rating['walk'], rating['strikeout'],
|
|
rating['flyout_lf_b'], rating['flyout_cf_b'], rating['flyout_rf_b'],
|
|
rating['groundout_a'], rating['groundout_b'],
|
|
rating['xcheck_p'], rating['xcheck_c'], rating['xcheck_1b'], rating['xcheck_2b'],
|
|
rating['xcheck_3b'], rating['xcheck_ss'], rating['xcheck_lf'], rating['xcheck_cf'], rating['xcheck_rf']
|
|
])
|
|
|
|
# Adjust strikeouts to hit exactly 108
|
|
diff = 108 - total
|
|
if abs(diff) > 0.01:
|
|
rating['strikeout'] = mround(rating['strikeout'] + diff)
|
|
|
|
return rating
|
|
|
|
|
|
def preview_pitcher():
|
|
"""Preview Tony Smehrik's ratings without submitting to database."""
|
|
|
|
print("\n" + "="*70)
|
|
print("TONY SMEHRIK - CUSTOM PITCHER PREVIEW")
|
|
print("="*70)
|
|
|
|
# Target: Combined OPS 0.585, OPS vs L 0.495
|
|
# Combined OPS formula for pitchers: (OPS_vR + OPS_vL + max(OPS_vL, OPS_vR)) / 3
|
|
# If OPS_vL = 0.495 and OPS_vR > OPS_vL:
|
|
# 0.585 = (OPS_vR + 0.495 + OPS_vR) / 3
|
|
# 1.755 = 2*OPS_vR + 0.495
|
|
# OPS_vR = (1.755 - 0.495) / 2 = 0.630
|
|
|
|
pit_hand = 'L'
|
|
|
|
# vs LHB (Same-side - Dominant)
|
|
# Target OPS: 0.465 (lowered from 0.495)
|
|
# OBP ~0.240, SLG ~0.225
|
|
print("\nCalculating vs LHB (same-side advantage)...")
|
|
vl = calculate_pitcher_rating(
|
|
vs_hand='L',
|
|
pit_hand=pit_hand,
|
|
avg=0.185, # Lowered from 0.195
|
|
obp=0.240, # Lowered from 0.252
|
|
slg=0.225, # Lowered from 0.243
|
|
bb_pct=0.055, # Slightly lower walks
|
|
k_pct=0.20, # Average K-rate
|
|
hr_per_hit=0.04,
|
|
triple_per_hit=0.02,
|
|
double_per_hit=0.22,
|
|
fb_pct=0.35, # Average flyball
|
|
gb_pct=0.45,
|
|
hard_pct=0.30,
|
|
med_pct=0.50,
|
|
soft_pct=0.20,
|
|
oppo_pct=0.26,
|
|
hr_fb_pct=0.06,
|
|
)
|
|
|
|
# vs RHB (Opposite-side - Weaker)
|
|
# Target OPS: 0.645 (raised from 0.630)
|
|
# OBP ~0.305, SLG ~0.340
|
|
print("Calculating vs RHB (opposite-side)...")
|
|
vr = calculate_pitcher_rating(
|
|
vs_hand='R',
|
|
pit_hand=pit_hand,
|
|
avg=0.245, # Raised from 0.238
|
|
obp=0.305, # Raised from 0.298
|
|
slg=0.340, # Raised from 0.332
|
|
bb_pct=0.075, # Slightly more walks
|
|
k_pct=0.08, # Very low K-rate (~8%)
|
|
hr_per_hit=0.07,
|
|
triple_per_hit=0.02,
|
|
double_per_hit=0.24,
|
|
fb_pct=0.45, # High flyball
|
|
gb_pct=0.35,
|
|
hard_pct=0.34,
|
|
med_pct=0.46,
|
|
soft_pct=0.20,
|
|
oppo_pct=0.26,
|
|
hr_fb_pct=0.09,
|
|
)
|
|
|
|
# ===== MANUAL ADJUSTMENTS =====
|
|
# Set half of ALL on-base chances (hits + BB + HBP) to HR
|
|
# Split 1:1 between HR and BP-HR
|
|
# BP-HR and HBP must be whole numbers
|
|
|
|
# vs LHB: Total OB = Hits + BB + HBP
|
|
vl_total_hits = (vl['homerun'] + vl['bp_homerun'] + vl['triple'] +
|
|
vl['double_three'] + vl['double_two'] + vl['double_cf'] +
|
|
vl['single_two'] + vl['single_one'] + vl['single_center'] + vl['bp_single'])
|
|
vl_total_ob = vl_total_hits + vl['walk'] + vl['hbp']
|
|
vl_hr_total = vl_total_ob / 2 # Half of all OB → HR
|
|
# Split 1:1, BP-HR must be whole number
|
|
vl['bp_homerun'] = round(vl_hr_total / 2) # Whole number
|
|
vl['homerun'] = round((vl_hr_total - vl['bp_homerun']) * 20) / 20
|
|
# Ensure HBP is whole number
|
|
vl['hbp'] = 1.0
|
|
|
|
# vs RHB: Total OB = Hits + BB + HBP
|
|
vr_total_hits = (vr['homerun'] + vr['bp_homerun'] + vr['triple'] +
|
|
vr['double_three'] + vr['double_two'] + vr['double_cf'] +
|
|
vr['single_two'] + vr['single_one'] + vr['single_center'] + vr['bp_single'])
|
|
vr_total_ob = vr_total_hits + vr['walk'] + vr['hbp']
|
|
vr_hr_total = vr_total_ob / 2 # Half of all OB → HR
|
|
# Split 1:1, BP-HR must be whole number
|
|
vr['bp_homerun'] = round(vr_hr_total / 2) # Whole number
|
|
vr['homerun'] = round((vr_hr_total - vr['bp_homerun']) * 20) / 20
|
|
# Ensure HBP is whole number
|
|
vr['hbp'] = 1.0
|
|
|
|
# The HR chances come FROM the existing hit pool, so we need to reduce other hits
|
|
# to keep total hits the same. Reduce singles proportionally.
|
|
vl_new_hr_total = vl['homerun'] + vl['bp_homerun']
|
|
vl_old_hr_total = 0 # Started with 0 HR
|
|
vl_hr_increase = vl_new_hr_total - vl_old_hr_total
|
|
# Reduce singles to compensate - take from single_one, single_two, bp_single
|
|
vl_singles_to_reduce = vl_hr_increase
|
|
# Take from single_one first, then single_two, then bp_single
|
|
reduce_from_one = min(vl['single_one'], vl_singles_to_reduce)
|
|
vl['single_one'] -= reduce_from_one
|
|
vl_singles_to_reduce -= reduce_from_one
|
|
reduce_from_two = min(vl['single_two'], vl_singles_to_reduce)
|
|
vl['single_two'] -= reduce_from_two
|
|
vl_singles_to_reduce -= reduce_from_two
|
|
reduce_from_bp = min(vl['bp_single'], vl_singles_to_reduce)
|
|
vl['bp_single'] -= reduce_from_bp
|
|
vl_singles_to_reduce -= reduce_from_bp
|
|
# If still need to reduce, take from doubles
|
|
if vl_singles_to_reduce > 0:
|
|
vl['double_cf'] = max(0, vl['double_cf'] - vl_singles_to_reduce)
|
|
|
|
vr_new_hr_total = vr['homerun'] + vr['bp_homerun']
|
|
vr_old_hr_total = 0.80 + 1.00 # Had some HR already
|
|
vr_hr_increase = vr_new_hr_total - vr_old_hr_total
|
|
# Reduce singles to compensate
|
|
vr_singles_to_reduce = vr_hr_increase
|
|
reduce_from_one = min(vr['single_one'], vr_singles_to_reduce)
|
|
vr['single_one'] -= reduce_from_one
|
|
vr_singles_to_reduce -= reduce_from_one
|
|
reduce_from_two = min(vr['single_two'], vr_singles_to_reduce)
|
|
vr['single_two'] -= reduce_from_two
|
|
vr_singles_to_reduce -= reduce_from_two
|
|
reduce_from_bp = min(vr['bp_single'], vr_singles_to_reduce)
|
|
vr['bp_single'] -= reduce_from_bp
|
|
vr_singles_to_reduce -= reduce_from_bp
|
|
if vr_singles_to_reduce > 0:
|
|
vr['double_cf'] = max(0, vr['double_cf'] - vr_singles_to_reduce)
|
|
|
|
# Force BP-SI to specific values
|
|
vl['bp_single'] = 5.0
|
|
vr['bp_single'] = 3.0
|
|
|
|
# Recalculate totals and adjust strikeouts to hit 108
|
|
for rating in [vl, vr]:
|
|
total = sum([
|
|
rating['homerun'], rating['bp_homerun'], rating['triple'],
|
|
rating['double_three'], rating['double_two'], rating['double_cf'],
|
|
rating['single_two'], rating['single_one'], rating['single_center'], rating['bp_single'],
|
|
rating['hbp'], rating['walk'], rating['strikeout'],
|
|
rating['flyout_lf_b'], rating['flyout_cf_b'], rating['flyout_rf_b'],
|
|
rating['groundout_a'], rating['groundout_b'],
|
|
rating['xcheck_p'], rating['xcheck_c'], rating['xcheck_1b'], rating['xcheck_2b'],
|
|
rating['xcheck_3b'], rating['xcheck_ss'], rating['xcheck_lf'], rating['xcheck_cf'], rating['xcheck_rf']
|
|
])
|
|
diff = 108 - total
|
|
rating['strikeout'] = round((rating['strikeout'] + diff) * 20) / 20
|
|
|
|
print(f"\nApplied HR adjustments + BP-SI override (vL: 5.0, vR: 3.0):")
|
|
print(f" vs LHB: Total OB was {vl_total_ob:.2f} → Half ({vl_hr_total:.2f}) to HR (HR:{vl['homerun']:.2f} + BP-HR:{vl['bp_homerun']:.0f})")
|
|
print(f" vs RHB: Total OB was {vr_total_ob:.2f} → Half ({vr_hr_total:.2f}) to HR (HR:{vr['homerun']:.2f} + BP-HR:{vr['bp_homerun']:.0f})")
|
|
|
|
# Calculate combined OPS
|
|
ops_vl = vl['obp'] + vl['slg']
|
|
ops_vr = vr['obp'] + vr['slg']
|
|
combined_ops = (ops_vr + ops_vl + max(ops_vl, ops_vr)) / 3
|
|
|
|
# Display results
|
|
print("\n" + "-"*70)
|
|
print("TONY SMEHRIK (L) - SP/RP")
|
|
print("-"*70)
|
|
|
|
print(f"\nTarget OPS: 0.585 combined, 0.495 vs L")
|
|
print(f"Actual OPS: {combined_ops:.3f} combined, {ops_vl:.3f} vs L, {ops_vr:.3f} vs R")
|
|
|
|
for rating in [vl, vr]:
|
|
vs_hand = rating['vs_hand']
|
|
print(f"\n{'='*35}")
|
|
print(f"VS {vs_hand}HB:")
|
|
print(f"{'='*35}")
|
|
|
|
ops = rating['obp'] + rating['slg']
|
|
print(f" AVG: {rating['avg']:.3f} OBP: {rating['obp']:.3f} SLG: {rating['slg']:.3f} OPS: {ops:.3f}")
|
|
|
|
# Show hit distribution
|
|
total_hits = (rating['homerun'] + rating['bp_homerun'] + rating['triple'] +
|
|
rating['double_three'] + rating['double_two'] + rating['double_cf'] +
|
|
rating['single_two'] + rating['single_one'] + rating['single_center'] + rating['bp_single'])
|
|
doubles = rating['double_cf'] + rating['double_two'] + rating['double_three']
|
|
singles = total_hits - rating['homerun'] - rating['bp_homerun'] - rating['triple'] - doubles
|
|
|
|
print(f"\n HITS ALLOWED: {total_hits:.2f}")
|
|
print(f" HR: {rating['homerun']:.2f} + BP-HR: {rating['bp_homerun']:.2f}")
|
|
print(f" 3B: {rating['triple']:.2f}")
|
|
print(f" 2B: {doubles:.2f} (double**: {rating['double_two']:.2f}, double(cf): {rating['double_cf']:.2f})")
|
|
print(f" 1B: {singles:.2f} (1B**: {rating['single_two']:.2f}, 1B*: {rating['single_one']:.2f}, 1B(cf): {rating['single_center']:.2f}, BP-1B: {rating['bp_single']:.2f})")
|
|
|
|
# Show walks/strikeouts
|
|
print(f"\n PLATE DISCIPLINE:")
|
|
print(f" BB: {rating['walk']:.2f} HBP: {rating['hbp']:.2f}")
|
|
print(f" K: {rating['strikeout']:.2f}")
|
|
|
|
# Show batted ball outs
|
|
flyouts = rating['flyout_lf_b'] + rating['flyout_cf_b'] + rating['flyout_rf_b']
|
|
groundouts = rating['groundout_a'] + rating['groundout_b']
|
|
xchecks = sum([rating[f'xcheck_{pos}'] for pos in ['p', 'c', '1b', '2b', '3b', 'ss', 'lf', 'cf', 'rf']])
|
|
total_outs = rating['strikeout'] + flyouts + groundouts + xchecks
|
|
|
|
print(f"\n OUTS: {total_outs:.2f}")
|
|
print(f" Strikeouts: {rating['strikeout']:.2f}")
|
|
print(f" Flyouts: {flyouts:.2f} (LF-B: {rating['flyout_lf_b']:.2f}, CF-B: {rating['flyout_cf_b']:.2f}, RF-B: {rating['flyout_rf_b']:.2f})")
|
|
print(f" Groundouts: {groundouts:.2f} (A: {rating['groundout_a']:.2f}, B: {rating['groundout_b']:.2f})")
|
|
print(f" X-Checks: {xchecks:.2f} (P:{rating['xcheck_p']:.0f} C:{rating['xcheck_c']:.0f} 1B:{rating['xcheck_1b']:.0f} 2B:{rating['xcheck_2b']:.0f} 3B:{rating['xcheck_3b']:.0f} SS:{rating['xcheck_ss']:.0f} LF:{rating['xcheck_lf']:.0f} CF:{rating['xcheck_cf']:.0f} RF:{rating['xcheck_rf']:.0f})")
|
|
|
|
batted_outs = flyouts + groundouts
|
|
if batted_outs > 0:
|
|
fb_rate = flyouts / batted_outs
|
|
gb_rate = groundouts / batted_outs
|
|
print(f" FB%: {fb_rate*100:.1f}% GB%: {gb_rate*100:.1f}%")
|
|
|
|
# Verify total = 108
|
|
total = sum([
|
|
rating['homerun'], rating['bp_homerun'], rating['triple'],
|
|
rating['double_three'], rating['double_two'], rating['double_cf'],
|
|
rating['single_two'], rating['single_one'], rating['single_center'], rating['bp_single'],
|
|
rating['hbp'], rating['walk'], rating['strikeout'],
|
|
rating['flyout_lf_b'], rating['flyout_cf_b'], rating['flyout_rf_b'],
|
|
rating['groundout_a'], rating['groundout_b'],
|
|
rating['xcheck_p'], rating['xcheck_c'], rating['xcheck_1b'], rating['xcheck_2b'],
|
|
rating['xcheck_3b'], rating['xcheck_ss'], rating['xcheck_lf'], rating['xcheck_cf'], rating['xcheck_rf']
|
|
])
|
|
print(f"\n Total chances: {total:.2f} (should be 108.0)")
|
|
|
|
# Summary
|
|
print("\n" + "="*70)
|
|
print("SUMMARY")
|
|
print("="*70)
|
|
print(f"\nName: Tony Smehrik")
|
|
print(f"Hand: L")
|
|
print(f"Positions: SP, RP")
|
|
print(f"Starter Rating: 5 | Relief Rating: 5 | Closer Rating: None")
|
|
print(f"\nCombined OPS Against: {combined_ops:.3f} (target: 0.585)")
|
|
print(f"OPS vs LHB: {ops_vl:.3f} (target: 0.495)")
|
|
print(f"OPS vs RHB: {ops_vr:.3f} (target: ~0.630)")
|
|
print(f"\nK-rate vs L: ~20% (average)")
|
|
print(f"K-rate vs R: ~12% (very low)")
|
|
print(f"\nFB% vs L: ~35% (average)")
|
|
print(f"FB% vs R: ~45% (high)")
|
|
|
|
print("\n" + "-"*70)
|
|
print("Ready to submit? Run: python -m custom_cards.submit_tony_smehrik")
|
|
print("-"*70 + "\n")
|
|
|
|
return vl, vr
|
|
|
|
|
|
if __name__ == "__main__":
|
|
preview_pitcher()
|