Closes #57 - Add RARITY_LADDER and rarity_is_downgrade() to rarity_thresholds.py - Add get_fully_evolved_players() to db_calls.py — queries a to-be-created database endpoint; returns empty set safely if endpoint is unavailable - In batters/creation.py post_player_updates(): pre-flight check identifies players where OPS rarity would downgrade, then guards the rarity write to skip any downgrade for fully-evolved (T4) cards - Same guard added to pitchers/creation.py post_player_updates() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
169 lines
5.0 KiB
Python
169 lines
5.0 KiB
Python
"""
|
|
Rarity threshold configurations for card creation.
|
|
|
|
This module defines OPS thresholds for assigning rarity tiers to player cards.
|
|
Different seasons may have different thresholds based on league-wide performance.
|
|
"""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
@dataclass
|
|
class PitcherRarityThresholds:
|
|
"""OPS-against thresholds for pitcher rarity assignment."""
|
|
|
|
# Starter thresholds (OPS-against, lower is better)
|
|
starter_hof: float # Hall of Fame (99)
|
|
starter_diamond: float # Diamond (1)
|
|
starter_gold: float # Gold (2)
|
|
starter_silver: float # Silver (3)
|
|
starter_bronze: float # Bronze (4)
|
|
|
|
# Reliever thresholds (OPS-against, lower is better)
|
|
reliever_hof: float # Hall of Fame (99)
|
|
reliever_diamond: float # Diamond (1)
|
|
reliever_gold: float # Gold (2)
|
|
reliever_silver: float # Silver (3)
|
|
reliever_bronze: float # Bronze (4)
|
|
|
|
def get_rarity_for_starter(self, total_ops: float) -> int:
|
|
"""Returns rarity ID for a starting pitcher based on OPS-against."""
|
|
if total_ops <= self.starter_hof:
|
|
return 99
|
|
elif total_ops <= self.starter_diamond:
|
|
return 1
|
|
elif total_ops <= self.starter_gold:
|
|
return 2
|
|
elif total_ops <= self.starter_silver:
|
|
return 3
|
|
elif total_ops <= self.starter_bronze:
|
|
return 4
|
|
else:
|
|
return 5 # Common
|
|
|
|
def get_rarity_for_reliever(self, total_ops: float) -> int:
|
|
"""Returns rarity ID for a relief pitcher based on OPS-against."""
|
|
if total_ops <= self.reliever_hof:
|
|
return 99
|
|
elif total_ops <= self.reliever_diamond:
|
|
return 1
|
|
elif total_ops <= self.reliever_gold:
|
|
return 2
|
|
elif total_ops <= self.reliever_silver:
|
|
return 3
|
|
elif total_ops <= self.reliever_bronze:
|
|
return 4
|
|
else:
|
|
return 5 # Common
|
|
|
|
|
|
@dataclass
|
|
class BatterRarityThresholds:
|
|
"""OPS thresholds for batter rarity assignment."""
|
|
|
|
# Batter thresholds (OPS, higher is better)
|
|
hof: float # Hall of Fame (99)
|
|
diamond: float # Diamond (1)
|
|
gold: float # Gold (2)
|
|
silver: float # Silver (3)
|
|
bronze: float # Bronze (4)
|
|
|
|
def get_rarity(self, total_ops: float) -> int:
|
|
"""Returns rarity ID for a batter based on OPS."""
|
|
# For batters, higher OPS is better, so we check >= instead of <=
|
|
if total_ops >= self.hof:
|
|
return 99
|
|
elif total_ops >= self.diamond:
|
|
return 1
|
|
elif total_ops >= self.gold:
|
|
return 2
|
|
elif total_ops >= self.silver:
|
|
return 3
|
|
elif total_ops >= self.bronze:
|
|
return 4
|
|
else:
|
|
return 5 # Common
|
|
|
|
|
|
# 2024 Season Thresholds (Original values)
|
|
PITCHER_THRESHOLDS_2024 = PitcherRarityThresholds(
|
|
starter_hof=0.4,
|
|
starter_diamond=0.475,
|
|
starter_gold=0.53,
|
|
starter_silver=0.6,
|
|
starter_bronze=0.675,
|
|
reliever_hof=0.325,
|
|
reliever_diamond=0.4,
|
|
reliever_gold=0.475,
|
|
reliever_silver=0.55,
|
|
reliever_bronze=0.625,
|
|
)
|
|
|
|
BATTER_THRESHOLDS_2024 = BatterRarityThresholds(
|
|
hof=1.2,
|
|
diamond=1.0,
|
|
gold=0.9,
|
|
silver=0.8,
|
|
bronze=0.7,
|
|
)
|
|
|
|
# 2025 Season Thresholds (Adjusted based on data analysis)
|
|
PITCHER_THRESHOLDS_2025 = PitcherRarityThresholds(
|
|
starter_hof=0.300, # Top 5%
|
|
starter_diamond=0.354, # Top 15%
|
|
starter_gold=0.384, # Top 30%
|
|
starter_silver=0.441, # Top 50%
|
|
starter_bronze=0.487, # Top 70%
|
|
reliever_hof=0.270, # Top 5%
|
|
reliever_diamond=0.319, # Top 15%
|
|
reliever_gold=0.370, # Top 30%
|
|
reliever_silver=0.436, # Top 50%
|
|
reliever_bronze=0.503, # Top 70%
|
|
)
|
|
|
|
BATTER_THRESHOLDS_2025 = BatterRarityThresholds(
|
|
hof=1.2,
|
|
diamond=1.0,
|
|
gold=0.9,
|
|
silver=0.8,
|
|
bronze=0.7,
|
|
)
|
|
|
|
|
|
def get_pitcher_thresholds(season: int) -> PitcherRarityThresholds:
|
|
"""Get pitcher rarity thresholds for a specific season."""
|
|
if season >= 2025:
|
|
return PITCHER_THRESHOLDS_2025
|
|
else:
|
|
return PITCHER_THRESHOLDS_2024
|
|
|
|
|
|
def get_batter_thresholds(season: int) -> BatterRarityThresholds:
|
|
"""Get batter rarity thresholds for a specific season."""
|
|
if season >= 2025:
|
|
return BATTER_THRESHOLDS_2025
|
|
else:
|
|
return BATTER_THRESHOLDS_2024
|
|
|
|
|
|
# Ordered from worst to best rarity. Used for ladder comparisons such as the
|
|
# T4 refractor guard — do not change the order.
|
|
RARITY_LADDER = [5, 4, 3, 2, 1, 99] # Common → Bronze → Silver → Gold → Diamond → HoF
|
|
|
|
|
|
def rarity_is_downgrade(current_rarity_id: int, new_rarity_id: int) -> bool:
|
|
"""Return True if new_rarity_id is a less prestigious tier than current_rarity_id.
|
|
|
|
Uses the RARITY_LADDER ordering. Unknown IDs are treated as position 0
|
|
(worst), so an unknown current rarity will never trigger a downgrade guard.
|
|
"""
|
|
try:
|
|
current_pos = RARITY_LADDER.index(current_rarity_id)
|
|
except ValueError:
|
|
return False
|
|
try:
|
|
new_pos = RARITY_LADDER.index(new_rarity_id)
|
|
except ValueError:
|
|
return False
|
|
return current_pos > new_pos
|