Increased target OPS from 0.820 to 0.850 with adjusted stat splits: - vs RHP: .260/.340/.495 (power profile) - vs LHP: .260/.375/.420 (patient/OBP profile) - Cost updated from 85 to 188 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
413 lines
14 KiB
Python
413 lines
14 KiB
Python
"""
|
|
Preview ratings for Sphealthamus Sphealy Spheal Sr custom player.
|
|
Target: 0.850 total OPS
|
|
Power vs RHP (singles/HRs, 62% pull), Patient vs LHP (walks/singles, 28% pull)
|
|
"""
|
|
|
|
from custom_cards.archetype_definitions import BatterArchetype
|
|
from custom_cards.archetype_calculator import BatterRatingCalculator, calculate_total_ops
|
|
import random
|
|
|
|
# Create custom archetype for Sphealthamus Sphealy Spheal Sr
|
|
# Target: Combined OPS = 0.850 with formula: (OPS_vR + OPS_vL + min) / 3
|
|
# Dramatic split: Power vs RHP, Patient vs LHP
|
|
|
|
spheal = BatterArchetype(
|
|
name="Sphealthamus Sphealy Spheal Sr",
|
|
description="Power vs RHP, patience vs LHP, extreme splits",
|
|
|
|
# VS RHP: Target OPS = ~0.850 (power profile - singles + HRs)
|
|
avg_vs_r=0.260, # Boosted to hit OPS target
|
|
obp_vs_r=0.340, # Boosted to hit OPS target
|
|
slg_vs_r=0.495, # Boosted to hit OPS target
|
|
bb_pct_vs_r=0.09, # Moderate walks
|
|
k_pct_vs_r=0.24, # Higher strikeouts (power hitter)
|
|
|
|
# VS LHP: Target OPS = ~0.830 (patient profile - walks + singles)
|
|
avg_vs_l=0.260, # Boosted to hit OPS target
|
|
obp_vs_l=0.375, # Boosted to hit OPS target
|
|
slg_vs_l=0.420, # Boosted to hit OPS target
|
|
bb_pct_vs_l=0.15, # Very high walks
|
|
k_pct_vs_l=0.20, # Fewer strikeouts
|
|
|
|
# Power distribution - high HR rate vs RHP, moderate vs LHP
|
|
hr_per_hit=0.15, # High HR rate
|
|
triple_per_hit=0.00, # NO TRIPLES
|
|
double_per_hit=0.20, # Moderate doubles
|
|
|
|
# Batted ball profile
|
|
gb_pct=0.40, # Some ground balls
|
|
fb_pct=0.38, # Lots of fly balls (power)
|
|
ld_pct=0.22, # Line drives
|
|
|
|
# Batted ball quality - power hitter
|
|
hard_pct=0.40, # Lots of hard contact
|
|
med_pct=0.42, # Medium contact
|
|
soft_pct=0.18, # Some soft contact
|
|
|
|
# Spray chart - will adjust per split
|
|
pull_pct=0.45, # Placeholder
|
|
center_pct=0.32, # Placeholder
|
|
oppo_pct=0.23, # Placeholder
|
|
|
|
# Infield hits
|
|
ifh_pct=0.04, # Low (slow runner)
|
|
|
|
# Specific power metrics
|
|
hr_fb_pct=0.16, # Good HR/FB
|
|
|
|
# Baserunning - poor speed
|
|
speed_rating=3, # Poor speed
|
|
steal_jump=5, # Below average
|
|
xbt_pct=0.42, # Below average
|
|
|
|
# Situational hitting - poor
|
|
hit_run_skill=3, # Poor hit-and-run
|
|
|
|
# Defensive profile
|
|
primary_positions=["1B"],
|
|
defensive_rating=5, # Average defender
|
|
)
|
|
|
|
print("="*70)
|
|
print("SPHEALTHAMUS SPHEALY SPHEAL SR - CUSTOM PLAYER PREVIEW")
|
|
print("="*70)
|
|
print()
|
|
print("Player Info:")
|
|
print(" Name: Sphealthamus Sphealy Spheal Sr")
|
|
print(" Hand: L (Left-handed batter)")
|
|
print(" Primary Position: 1B")
|
|
print()
|
|
|
|
print("Defensive Ratings (per requirements):")
|
|
print(" 1B: Range 5 / Error 3")
|
|
print()
|
|
|
|
print("Target Statistics:")
|
|
print(f" Target Total OPS: 0.850")
|
|
print(f" Pull Rate vs RHP: 62%")
|
|
print(f" Pull Rate vs LHP: 28%")
|
|
print(f" Running: 9")
|
|
print(f" Stealing: 18-13 (0.416666)")
|
|
print(f" Bunting: D")
|
|
print(f" Hit-and-Run: D")
|
|
print(f" vs RHP: Power (singles/HRs, 3:2 BP-HR:HR ratio)")
|
|
print(f" vs LHP: Patient (walks/singles, few HRs)")
|
|
print(f" No triples either side")
|
|
print()
|
|
|
|
# Calculate ratings
|
|
calc = BatterRatingCalculator(spheal)
|
|
ratings = calc.calculate_ratings(battingcard_id=0) # Temp ID
|
|
baserunning = calc.calculate_baserunning()
|
|
|
|
# Override steal rate to 0.416666 (18-13)
|
|
baserunning['steal_jump'] = 0.416666
|
|
baserunning['steal_high'] = 18
|
|
baserunning['steal_low'] = 13
|
|
|
|
# Override running to 9
|
|
baserunning['running'] = 9
|
|
|
|
# Override bunting to D
|
|
baserunning['bunting'] = 'D'
|
|
|
|
# Override hit-and-run to D
|
|
baserunning['hit_and_run'] = 'D'
|
|
|
|
# Apply randomization
|
|
random.seed(43)
|
|
|
|
def round_to_05(value):
|
|
"""Round to nearest 0.05 (Stratomatic standard)."""
|
|
return round(value * 20) / 20
|
|
|
|
def add_variation(base_value, max_variation=0.5):
|
|
"""Add small random variation to a value, then round to 0.05."""
|
|
if base_value == 0.0:
|
|
return 0.0
|
|
variation = random.uniform(-max_variation, max_variation)
|
|
result = max(0.0, base_value + variation)
|
|
return round_to_05(result)
|
|
|
|
# Randomize to make card look natural
|
|
for rating in ratings:
|
|
randomize_fields = [
|
|
'homerun', 'bp_homerun', 'triple', 'double_three', 'double_two', 'double_pull',
|
|
'single_two', 'single_one', 'single_center',
|
|
'walk', 'hbp', 'strikeout', 'lineout', 'popout',
|
|
'flyout_a', 'flyout_bq', 'flyout_lf_b', 'flyout_rf_b',
|
|
'groundout_a', 'groundout_b', 'groundout_c'
|
|
]
|
|
|
|
for field in randomize_fields:
|
|
if rating[field] > 0:
|
|
rating[field] = add_variation(rating[field], 0.5)
|
|
|
|
# Fix BP-HR and HBP to whole numbers, remove triples, adjust pull rates
|
|
print("\n✓ Applying custom adjustments...")
|
|
print(" - Setting BP-HR to whole numbers")
|
|
print(" - Setting HBP to whole numbers")
|
|
print(" - Removing all triples")
|
|
print(" - Adjusting pull rates (62% vR, 28% vL)")
|
|
print(" - Setting BP-HR:HR ratio to 3:2 vs RHP")
|
|
|
|
# vs LHP (ratings[0]) - Patient profile
|
|
old_bphr_vl = ratings[0]['bp_homerun']
|
|
ratings[0]['bp_homerun'] = 1.0 # Fewer BP-HR vs LHP
|
|
ratings[0]['single_center'] += (old_bphr_vl - 1.0)
|
|
|
|
old_hbp_vl = ratings[0]['hbp']
|
|
ratings[0]['hbp'] = 1.0
|
|
ratings[0]['single_center'] += (old_hbp_vl - 1.0)
|
|
|
|
# Remove triples vL
|
|
old_triple_vl = ratings[0]['triple']
|
|
ratings[0]['triple'] = 0.0
|
|
ratings[0]['single_center'] += old_triple_vl
|
|
|
|
# Remove Double2 vL and move to Walks
|
|
old_double2_vl = ratings[0]['double_two']
|
|
ratings[0]['double_two'] = 0.0
|
|
ratings[0]['walk'] += old_double2_vl
|
|
|
|
# Set pull rate to 28% vL
|
|
ratings[0]['pull_rate'] = 0.28
|
|
ratings[0]['center_rate'] = 0.40
|
|
ratings[0]['slap_rate'] = 0.32
|
|
|
|
# vs RHP (ratings[1]) - Power profile
|
|
# Add 2 BP-HR and 1.4 HR
|
|
total_hr_chances = ratings[1]['homerun'] + ratings[1]['bp_homerun']
|
|
ratings[1]['homerun'] = 3.4 # Add 1.4 HR
|
|
ratings[1]['bp_homerun'] = 5.0 # Add 2 BP-HR
|
|
# Redistribute excess
|
|
excess = total_hr_chances - 8.4
|
|
ratings[1]['single_center'] += excess
|
|
|
|
old_hbp_vr = ratings[1]['hbp']
|
|
ratings[1]['hbp'] = 1.0
|
|
ratings[1]['single_center'] += (old_hbp_vr - 1.0)
|
|
|
|
# Remove triples vR
|
|
old_triple_vr = ratings[1]['triple']
|
|
ratings[1]['triple'] = 0.0
|
|
ratings[1]['single_center'] += old_triple_vr
|
|
|
|
# Remove Double2 vR and move to singles
|
|
old_double2_vr = ratings[1]['double_two']
|
|
ratings[1]['double_two'] = 0.0
|
|
ratings[1]['single_center'] += old_double2_vr
|
|
|
|
# Remove Flyout LF-B vR and move to strikeouts
|
|
old_flyout_lf_vr = ratings[1]['flyout_lf_b']
|
|
ratings[1]['flyout_lf_b'] = 0.0
|
|
ratings[1]['strikeout'] += old_flyout_lf_vr
|
|
|
|
# Set pull rate to 62% vR
|
|
ratings[1]['pull_rate'] = 0.62
|
|
ratings[1]['center_rate'] = 0.25
|
|
ratings[1]['slap_rate'] = 0.13
|
|
|
|
# Rebalance each split to exactly 108.00
|
|
def rebalance_to_108(rating_dict):
|
|
"""Adjust to ensure total = 108.00 by tweaking groundout_b."""
|
|
current_total = sum([
|
|
rating_dict['homerun'], rating_dict['bp_homerun'], rating_dict['triple'],
|
|
rating_dict['double_three'], rating_dict['double_two'], rating_dict['double_pull'],
|
|
rating_dict['single_two'], rating_dict['single_one'], rating_dict['single_center'],
|
|
rating_dict['bp_single'], rating_dict['walk'], rating_dict['hbp'],
|
|
rating_dict['strikeout'], rating_dict['lineout'], rating_dict['popout'],
|
|
rating_dict['flyout_a'], rating_dict['flyout_bq'], rating_dict['flyout_lf_b'],
|
|
rating_dict['flyout_rf_b'], rating_dict['groundout_a'], rating_dict['groundout_b'],
|
|
rating_dict['groundout_c']
|
|
])
|
|
diff = 108.0 - current_total
|
|
rating_dict['groundout_b'] = round_to_05(rating_dict['groundout_b'] + diff)
|
|
return rating_dict
|
|
|
|
ratings[0] = rebalance_to_108(ratings[0])
|
|
ratings[1] = rebalance_to_108(ratings[1])
|
|
|
|
# Adjust groundout ratios to 4:2:1 (A:B:C)
|
|
print("\n✓ Adjusting groundout ratios to 4:2:1 (A:B:C)...")
|
|
for rating in ratings:
|
|
total_groundouts = rating['groundout_a'] + rating['groundout_b'] + rating['groundout_c']
|
|
rating['groundout_a'] = round_to_05(total_groundouts * (4/7))
|
|
rating['groundout_b'] = round_to_05(total_groundouts * (2/7))
|
|
rating['groundout_c'] = round_to_05(total_groundouts * (1/7))
|
|
|
|
# Set Flyout A to 1.0 and move removed chances to strikeouts
|
|
print("✓ Setting Flyout A to 1.0 both sides...")
|
|
for rating in ratings:
|
|
old_flyout_a = rating['flyout_a']
|
|
rating['flyout_a'] = 1.0
|
|
rating['strikeout'] += (old_flyout_a - 1.0)
|
|
|
|
# Rebalance again after adjustments
|
|
ratings[0] = rebalance_to_108(ratings[0])
|
|
ratings[1] = rebalance_to_108(ratings[1])
|
|
|
|
# Recalculate rate stats after adjustments
|
|
for rating in ratings:
|
|
total_hits = (
|
|
rating['homerun'] + rating['bp_homerun'] * 0.5 + rating['triple'] +
|
|
rating['double_three'] + rating['double_two'] + rating['double_pull'] +
|
|
rating['single_two'] + rating['single_one'] + rating['single_center'] + rating['bp_single'] * 0.5
|
|
)
|
|
rating['avg'] = round(total_hits / 108, 5)
|
|
rating['obp'] = round((total_hits + rating['hbp'] + rating['walk']) / 108, 5)
|
|
rating['slg'] = round((
|
|
rating['homerun'] * 4 + rating['bp_homerun'] * 2 +
|
|
rating['triple'] * 3 +
|
|
(rating['double_two'] + rating['double_three'] + rating['double_pull']) * 2 +
|
|
rating['single_center'] + rating['single_two'] + rating['single_one'] + rating['bp_single']
|
|
) / 108, 5)
|
|
|
|
# Display results
|
|
print()
|
|
print("-"*70)
|
|
print("CALCULATED BATTING RATINGS")
|
|
print("-"*70)
|
|
|
|
for rating in ratings:
|
|
vs_hand = rating['vs_hand']
|
|
print(f"\nVS {vs_hand}HP:")
|
|
print(f" AVG: {rating['avg']:.3f} OBP: {rating['obp']:.3f} SLG: {rating['slg']:.3f} OPS: {rating['obp']+rating['slg']:.3f}")
|
|
|
|
# Hit breakdown
|
|
total_hits = (rating['homerun'] + rating['bp_homerun'] + rating['triple'] +
|
|
rating['double_three'] + rating['double_two'] + rating['double_pull'] +
|
|
rating['single_two'] + rating['single_one'] + rating['single_center'] + rating['bp_single'])
|
|
|
|
print(f"\n Power:")
|
|
print(f" HR: {rating['homerun']:.1f}")
|
|
print(f" BP-HR: {rating['bp_homerun']:.1f}")
|
|
if vs_hand == 'R':
|
|
print(f" BP-HR:HR Ratio: {rating['bp_homerun']:.0f}:{rating['homerun']:.0f}")
|
|
|
|
print(f"\n On-Base:")
|
|
print(f" Walks: {rating['walk']:.1f}")
|
|
print(f" HBP: {rating['hbp']:.1f}")
|
|
|
|
# Verify total = 108
|
|
total_chances = sum([
|
|
rating['homerun'], rating['bp_homerun'], rating['triple'],
|
|
rating['double_three'], rating['double_two'], rating['double_pull'],
|
|
rating['single_two'], rating['single_one'], rating['single_center'], rating['bp_single'],
|
|
rating['hbp'], rating['walk'], rating['strikeout'],
|
|
rating['lineout'], rating['popout'],
|
|
rating['flyout_a'], rating['flyout_bq'], rating['flyout_lf_b'], rating['flyout_rf_b'],
|
|
rating['groundout_a'], rating['groundout_b'], rating['groundout_c']
|
|
])
|
|
print(f"\n Total Chances: {total_chances:.2f} (must be 108.0)")
|
|
|
|
# Calculate and display total OPS
|
|
total_ops = calculate_total_ops(ratings[0], ratings[1], is_pitcher=False)
|
|
print()
|
|
print("="*70)
|
|
print(f"TOTAL OPS: {total_ops:.3f} (Target: 0.850)")
|
|
if abs(total_ops - 0.850) <= 0.005:
|
|
print("✓ Within target range!")
|
|
elif abs(total_ops - 0.850) <= 0.015:
|
|
print("~ Close to target (can tweak if needed)")
|
|
else:
|
|
print("⚠ Outside target range - needs adjustment")
|
|
print("="*70)
|
|
|
|
# Show baserunning
|
|
print()
|
|
print("Baserunning Ratings:")
|
|
print(f" Steal Range: {baserunning['steal_low']}-{baserunning['steal_high']}")
|
|
print(f" Steal Jump: {baserunning['steal_jump']:.6f}")
|
|
print(f" Running: {baserunning['running']}")
|
|
print(f" Hit-and-Run: {baserunning['hit_and_run']}")
|
|
print(f" Bunting: {baserunning['bunting']}")
|
|
|
|
print()
|
|
print("="*70)
|
|
print("DETAILED D20 RATINGS (Side-by-Side Comparison)")
|
|
print("="*70)
|
|
print()
|
|
|
|
# Create table header
|
|
print(f"{'Rating':<25} {'vs LHP':>10} {'vs RHP':>10}")
|
|
print("-" * 70)
|
|
|
|
# Extract ratings for easier access
|
|
vl = ratings[0] # vs LHP
|
|
vr = ratings[1] # vs RHP
|
|
|
|
# Display all ratings in table format
|
|
rating_pairs = [
|
|
("Homerun", 'homerun'),
|
|
("BP Homerun", 'bp_homerun'),
|
|
("Triple", 'triple'),
|
|
("Double (3-zone)", 'double_three'),
|
|
("Double (2-zone)", 'double_two'),
|
|
("Double (Pull)", 'double_pull'),
|
|
("Single (2-zone)", 'single_two'),
|
|
("Single (1-zone)", 'single_one'),
|
|
("Single (Center)", 'single_center'),
|
|
("BP Single", 'bp_single'),
|
|
("Walk", 'walk'),
|
|
("HBP", 'hbp'),
|
|
("Strikeout", 'strikeout'),
|
|
("Lineout", 'lineout'),
|
|
("Popout", 'popout'),
|
|
("Flyout A", 'flyout_a'),
|
|
("Flyout BQ", 'flyout_bq'),
|
|
("Flyout LF-B", 'flyout_lf_b'),
|
|
("Flyout RF-B", 'flyout_rf_b'),
|
|
("Groundout A", 'groundout_a'),
|
|
("Groundout B", 'groundout_b'),
|
|
("Groundout C", 'groundout_c'),
|
|
]
|
|
|
|
for label, key in rating_pairs:
|
|
print(f"{label:<25} {vl[key]:>10.2f} {vr[key]:>10.2f}")
|
|
|
|
# Show totals
|
|
vl_total = sum([vl[key] for _, key in rating_pairs])
|
|
vr_total = sum([vr[key] for _, key in rating_pairs])
|
|
print("-" * 70)
|
|
print(f"{'TOTAL CHANCES':<25} {vl_total:>10.2f} {vr_total:>10.2f}")
|
|
print(f"{'(must be 108.0)':<25} {'':>10} {'':>10}")
|
|
|
|
# Show spray chart percentages
|
|
print()
|
|
print("="*70)
|
|
print("SPRAY CHART VERIFICATION")
|
|
print("="*70)
|
|
print(f"vs LHP - Pull: {vl['pull_rate']*100:.1f}% Center: {vl['center_rate']*100:.1f}% Opposite: {vl['slap_rate']*100:.1f}%")
|
|
print(f"vs RHP - Pull: {vr['pull_rate']*100:.1f}% Center: {vr['center_rate']*100:.1f}% Opposite: {vr['slap_rate']*100:.1f}%")
|
|
print(f"\nTarget Pull Rates: 28% vL ({'✓ MATCH' if abs(vl['pull_rate'] - 0.28) < 0.01 else '⚠ ADJUST'}), 62% vR ({'✓ MATCH' if abs(vr['pull_rate'] - 0.62) < 0.01 else '⚠ ADJUST'})")
|
|
|
|
# Show BP-HR:HR ratio
|
|
print()
|
|
print("="*70)
|
|
print("BP-HR:HR RATIO VERIFICATION")
|
|
print("="*70)
|
|
print(f"vs RHP - BP-HR: {vr['bp_homerun']:.0f}, HR: {vr['homerun']:.0f}")
|
|
print(f"Ratio: {vr['bp_homerun']:.0f}:{vr['homerun']:.0f} (Target: 3:2) {'✓ MATCH' if vr['bp_homerun'] == 3.0 and vr['homerun'] == 2.0 else '⚠ ADJUST'}")
|
|
|
|
print()
|
|
print("="*70)
|
|
print("SUMMARY")
|
|
print("="*70)
|
|
print()
|
|
print("✓ Ratings calculated successfully")
|
|
print(f"✓ Total OPS: {total_ops:.3f} (Target: 0.850)")
|
|
print(f"✓ Pull Rates: {vl['pull_rate']*100:.0f}% vL, {vr['pull_rate']*100:.0f}% vR (Target: 28% vL, 62% vR)")
|
|
print(f"✓ BP-HR:HR Ratio vR: {vr['bp_homerun']:.0f}:{vr['homerun']:.0f} (Target: 3:2)")
|
|
print(f"✓ Triples: {vl['triple']:.0f} vL, {vr['triple']:.0f} vR (Target: 0 both)")
|
|
print(f"✓ Running: {baserunning['running']} (Target: 9)")
|
|
print(f"✓ Stealing: {baserunning['steal_low']}-{baserunning['steal_high']} (Target: 18-13)")
|
|
print()
|
|
print("Next Steps:")
|
|
print("1. Review the ratings above")
|
|
print("2. If approved, run create_sphealthamus_spheal.py to update the database records")
|
|
print()
|
|
print("⚠️ NOT POSTED TO DATABASE - PREVIEW ONLY")
|
|
print("="*70)
|