diff --git a/batters/creation.py b/batters/creation.py index a6e81a1..9f81dfc 100644 --- a/batters/creation.py +++ b/batters/creation.py @@ -6,7 +6,7 @@ import numpy as np from creation_helpers import ( get_all_pybaseball_ids, sanitize_name, CLUB_LIST, FRANCHISE_LIST, pd_players_df, mlbteam_and_franchise, get_hand, NEW_PLAYER_COST, RARITY_BASE_COSTS, - should_update_player_description + should_update_player_description, calculate_rarity_cost_adjustment ) from db_calls import db_post, db_get, db_put, db_patch from . import calcs_batter as cba @@ -353,80 +353,13 @@ async def post_player_updates( ]) elif df_data['rarity'] != df_data['new_rarity_id']: - old_rarity = df_data['rarity'] - new_rarity = df_data['new_rarity_id'] - old_cost = df_data['cost'] - new_cost = 0 - - if old_rarity == 1: - if new_rarity == 2: - new_cost = max(old_cost - 540, 100) - elif new_rarity == 3: - new_cost = max(old_cost - 720, 50) - elif new_rarity == 4: - new_cost = max(old_cost - 780, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 800, 5) - elif new_rarity == 99: - new_cost = old_cost + 1600 - elif old_rarity == 2: - if new_rarity == 1: - new_cost = old_cost + 540 - elif new_rarity == 3: - new_cost = max(old_cost - 180, 50) - elif new_rarity == 4: - new_cost = max(old_cost - 240, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 260, 5) - elif new_rarity == 99: - new_cost = old_cost + 2140 - elif old_rarity == 3: - if new_rarity == 1: - new_cost = old_cost + 720 - elif new_rarity == 2: - new_cost = old_cost + 180 - elif new_rarity == 4: - new_cost = max(old_cost - 60, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 80, 5) - elif new_rarity == 99: - new_cost = old_cost + 2320 - elif old_rarity == 4: - if new_rarity == 1: - new_cost = old_cost + 780 - elif new_rarity == 2: - new_cost = old_cost + 240 - elif new_rarity == 3: - new_cost = old_cost + 60 - elif new_rarity == 5: - new_cost = max(old_cost - 20, 5) - elif new_rarity == 99: - new_cost = old_cost + 2380 - elif old_rarity == 5: - if new_rarity == 1: - new_cost = old_cost + 800 - elif new_rarity == 2: - new_cost = old_cost + 260 - elif new_rarity == 3: - new_cost = old_cost + 80 - elif new_rarity == 4: - new_cost = old_cost + 20 - elif new_rarity == 99: - new_cost = old_cost + 2400 - elif old_rarity == 99: - if new_rarity == 1: - new_cost = max(old_cost - 1600, 800) - elif new_rarity == 2: - new_cost = max(old_cost - 2140, 100) - elif new_rarity == 3: - new_cost = max(old_cost - 2320, 50) - elif new_rarity == 4: - new_cost = max(old_cost - 2380, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 2400, 5) - - if new_cost != 0: - params.extend([('cost', new_cost), ('rarity_id', new_rarity)]) + # Calculate adjusted cost for rarity change using lookup table + new_cost = calculate_rarity_cost_adjustment( + old_rarity=df_data['rarity'], + new_rarity=df_data['new_rarity_id'], + old_cost=df_data['cost'] + ) + params.extend([('cost', new_cost), ('rarity_id', df_data['new_rarity_id'])]) if len(params) > 0: if df_data.player_id not in player_updates.keys(): diff --git a/creation_helpers.py b/creation_helpers.py index 615f05f..9e93081 100644 --- a/creation_helpers.py +++ b/creation_helpers.py @@ -25,6 +25,54 @@ RARITY_BASE_COSTS = { 99: 2400 # Special/Legend } +# Rarity Cost Adjustments +# Maps (old_rarity, new_rarity) -> (cost_adjustment, minimum_cost) +# When a player's rarity changes, adjust their cost by the specified amount +# and enforce minimum cost if specified (None = no minimum) +RARITY_COST_ADJUSTMENTS = { + # From Diamond (1) + (1, 2): (-540, 100), + (1, 3): (-720, 50), + (1, 4): (-780, 15), + (1, 5): (-800, 5), + (1, 99): (1600, None), + + # From Gold (2) + (2, 1): (540, None), + (2, 3): (-180, 50), + (2, 4): (-240, 15), + (2, 5): (-260, 5), + (2, 99): (2140, None), + + # From Silver (3) + (3, 1): (720, None), + (3, 2): (180, None), + (3, 4): (-60, 15), + (3, 5): (-80, 5), + (3, 99): (2320, None), + + # From Bronze (4) + (4, 1): (780, None), + (4, 2): (240, None), + (4, 3): (60, None), + (4, 5): (-20, 5), + (4, 99): (2380, None), + + # From Common (5) + (5, 1): (800, None), + (5, 2): (260, None), + (5, 3): (80, None), + (5, 4): (20, None), + (5, 99): (2400, None), + + # From Special/Legend (99) + (99, 1): (-1600, 800), + (99, 2): (-2140, 100), + (99, 3): (-2320, 50), + (99, 4): (-2380, 15), + (99, 5): (-2400, 5), +} + D20_CHANCES = { '2': { 'chances': 1, @@ -585,6 +633,61 @@ def should_update_player_description( return is_different and not is_potm +def calculate_rarity_cost_adjustment(old_rarity: int, new_rarity: int, old_cost: int) -> int: + """ + Calculate new cost when a player's rarity changes. + + Uses the RARITY_COST_ADJUSTMENTS lookup table to determine the cost adjustment + and minimum cost when a player moves between rarity tiers. + + Args: + old_rarity: Current rarity tier (1-5, 99) + new_rarity: New rarity tier (1-5, 99) + old_cost: Current player cost + + Returns: + New cost after adjustment (with minimum enforced if applicable) + + Examples: + >>> calculate_rarity_cost_adjustment(1, 2, 1000) + 460 # Diamond to Gold: 1000 - 540 = 460, min 100 → 460 + + >>> calculate_rarity_cost_adjustment(1, 5, 100) + 5 # Diamond to Common: 100 - 800 = -700, min 5 → 5 + + >>> calculate_rarity_cost_adjustment(5, 1, 50) + 850 # Common to Diamond: 50 + 800 = 850, no min → 850 + + >>> calculate_rarity_cost_adjustment(3, 3, 100) + 100 # No change: same rarity returns same cost + """ + # No change if rarity stays the same + if old_rarity == new_rarity: + return old_cost + + # Look up the adjustment and minimum cost + adjustment_data = RARITY_COST_ADJUSTMENTS.get((old_rarity, new_rarity)) + + if adjustment_data is None: + # No defined adjustment for this transition - return old cost + logger.warning( + f"creation_helpers.calculate_rarity_cost_adjustment - No cost adjustment defined for " + f"rarity change {old_rarity} → {new_rarity}. Keeping cost at {old_cost}." + ) + return old_cost + + cost_adjustment, min_cost = adjustment_data + + # Calculate new cost + new_cost = old_cost + cost_adjustment + + # Apply minimum cost if specified + if min_cost is not None: + new_cost = max(new_cost, min_cost) + + return new_cost + + async def pd_players_df(cardset_id: int): p_query = await db_get( 'players', diff --git a/pitchers/creation.py b/pitchers/creation.py index 4678ec2..84b89bf 100644 --- a/pitchers/creation.py +++ b/pitchers/creation.py @@ -5,7 +5,7 @@ import pandas as pd from creation_helpers import ( get_all_pybaseball_ids, sanitize_name, CLUB_LIST, FRANCHISE_LIST, pd_players_df, mlbteam_and_franchise, NEW_PLAYER_COST, RARITY_BASE_COSTS, - should_update_player_description + should_update_player_description, calculate_rarity_cost_adjustment ) from db_calls import db_post, db_get, db_put, db_patch from defenders import calcs_defense as cde @@ -435,80 +435,13 @@ async def post_player_updates( ]) elif df_data['rarity'] != df_data['new_rarity_id']: - old_rarity = df_data['rarity'] - new_rarity = df_data['new_rarity_id'] - old_cost = df_data['cost'] - new_cost = 0 - - if old_rarity == 1: - if new_rarity == 2: - new_cost = max(old_cost - 540, 100) - elif new_rarity == 3: - new_cost = max(old_cost - 720, 50) - elif new_rarity == 4: - new_cost = max(old_cost - 780, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 800, 5) - elif new_rarity == 99: - new_cost = old_cost + 1600 - elif old_rarity == 2: - if new_rarity == 1: - new_cost = old_cost + 540 - elif new_rarity == 3: - new_cost = max(old_cost - 180, 50) - elif new_rarity == 4: - new_cost = max(old_cost - 240, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 260, 5) - elif new_rarity == 99: - new_cost = old_cost + 2140 - elif old_rarity == 3: - if new_rarity == 1: - new_cost = old_cost + 720 - elif new_rarity == 2: - new_cost = old_cost + 180 - elif new_rarity == 4: - new_cost = max(old_cost - 60, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 80, 5) - elif new_rarity == 99: - new_cost = old_cost + 2320 - elif old_rarity == 4: - if new_rarity == 1: - new_cost = old_cost + 780 - elif new_rarity == 2: - new_cost = old_cost + 240 - elif new_rarity == 3: - new_cost = old_cost + 60 - elif new_rarity == 5: - new_cost = max(old_cost - 20, 5) - elif new_rarity == 99: - new_cost = old_cost + 2380 - elif old_rarity == 5: - if new_rarity == 1: - new_cost = old_cost + 800 - elif new_rarity == 2: - new_cost = old_cost + 260 - elif new_rarity == 3: - new_cost = old_cost + 80 - elif new_rarity == 4: - new_cost = old_cost + 20 - elif new_rarity == 99: - new_cost = old_cost + 2400 - elif old_rarity == 99: - if new_rarity == 1: - new_cost = max(old_cost - 1600, 800) - elif new_rarity == 2: - new_cost = max(old_cost - 2140, 100) - elif new_rarity == 3: - new_cost = max(old_cost - 2320, 50) - elif new_rarity == 4: - new_cost = max(old_cost - 2380, 15) - elif new_rarity == 5: - new_cost = max(old_cost - 2400, 5) - - if new_cost != 0: - params.extend([('cost', new_cost), ('rarity_id', new_rarity)]) + # Calculate adjusted cost for rarity change using lookup table + new_cost = calculate_rarity_cost_adjustment( + old_rarity=df_data['rarity'], + new_rarity=df_data['new_rarity_id'], + old_cost=df_data['cost'] + ) + params.extend([('cost', new_cost), ('rarity_id', df_data['new_rarity_id'])]) if len(params) > 0: if df_data.player_id not in player_updates.keys(): diff --git a/tests/test_rarity_cost_adjustments.py b/tests/test_rarity_cost_adjustments.py new file mode 100644 index 0000000..fa4d51e --- /dev/null +++ b/tests/test_rarity_cost_adjustments.py @@ -0,0 +1,184 @@ +""" +Tests for rarity cost adjustment logic. + +This test verifies that calculate_rarity_cost_adjustment() correctly: +1. Adjusts costs when rarity changes +2. Enforces minimum costs where specified +3. Handles all rarity transitions (1-5, 99) +4. Returns original cost when rarity doesn't change +""" +import pytest +from creation_helpers import calculate_rarity_cost_adjustment + + +class TestRarityCostAdjustments: + """Test suite for rarity cost adjustment calculations.""" + + def test_no_change_same_rarity(self): + """Cost should not change if rarity stays the same.""" + assert calculate_rarity_cost_adjustment(1, 1, 1000) == 1000 + assert calculate_rarity_cost_adjustment(5, 5, 50) == 50 + assert calculate_rarity_cost_adjustment(99, 99, 2400) == 2400 + + def test_diamond_to_gold(self): + """Diamond (1) to Gold (2): -540, min 100.""" + assert calculate_rarity_cost_adjustment(1, 2, 1000) == 460 + assert calculate_rarity_cost_adjustment(1, 2, 500) == 100 # min enforced + assert calculate_rarity_cost_adjustment(1, 2, 50) == 100 # min enforced + + def test_diamond_to_silver(self): + """Diamond (1) to Silver (3): -720, min 50.""" + assert calculate_rarity_cost_adjustment(1, 3, 1000) == 280 + assert calculate_rarity_cost_adjustment(1, 3, 500) == 50 # min enforced + assert calculate_rarity_cost_adjustment(1, 3, 100) == 50 # min enforced + + def test_diamond_to_bronze(self): + """Diamond (1) to Bronze (4): -780, min 15.""" + assert calculate_rarity_cost_adjustment(1, 4, 1000) == 220 + assert calculate_rarity_cost_adjustment(1, 4, 500) == 15 # min enforced + assert calculate_rarity_cost_adjustment(1, 4, 50) == 15 # min enforced + + def test_diamond_to_common(self): + """Diamond (1) to Common (5): -800, min 5.""" + assert calculate_rarity_cost_adjustment(1, 5, 1000) == 200 + assert calculate_rarity_cost_adjustment(1, 5, 500) == 5 # min enforced + assert calculate_rarity_cost_adjustment(1, 5, 100) == 5 # min enforced + + def test_diamond_to_special(self): + """Diamond (1) to Special (99): +1600, no min.""" + assert calculate_rarity_cost_adjustment(1, 99, 1000) == 2600 + assert calculate_rarity_cost_adjustment(1, 99, 100) == 1700 + + def test_gold_to_diamond(self): + """Gold (2) to Diamond (1): +540, no min.""" + assert calculate_rarity_cost_adjustment(2, 1, 200) == 740 + assert calculate_rarity_cost_adjustment(2, 1, 100) == 640 + + def test_gold_to_silver(self): + """Gold (2) to Silver (3): -180, min 50.""" + assert calculate_rarity_cost_adjustment(2, 3, 300) == 120 + assert calculate_rarity_cost_adjustment(2, 3, 100) == 50 # min enforced + + def test_silver_to_gold(self): + """Silver (3) to Gold (2): +180, no min.""" + assert calculate_rarity_cost_adjustment(3, 2, 100) == 280 + assert calculate_rarity_cost_adjustment(3, 2, 50) == 230 + + def test_silver_to_bronze(self): + """Silver (3) to Bronze (4): -60, min 15.""" + assert calculate_rarity_cost_adjustment(3, 4, 100) == 40 + assert calculate_rarity_cost_adjustment(3, 4, 50) == 15 # min enforced + + def test_bronze_to_common(self): + """Bronze (4) to Common (5): -20, min 5.""" + assert calculate_rarity_cost_adjustment(4, 5, 50) == 30 + assert calculate_rarity_cost_adjustment(4, 5, 20) == 5 # min enforced + + def test_common_to_bronze(self): + """Common (5) to Bronze (4): +20, no min.""" + assert calculate_rarity_cost_adjustment(5, 4, 10) == 30 + assert calculate_rarity_cost_adjustment(5, 4, 50) == 70 + + def test_common_to_diamond(self): + """Common (5) to Diamond (1): +800, no min.""" + assert calculate_rarity_cost_adjustment(5, 1, 10) == 810 + assert calculate_rarity_cost_adjustment(5, 1, 50) == 850 + + def test_special_to_diamond(self): + """Special (99) to Diamond (1): -1600, min 800.""" + assert calculate_rarity_cost_adjustment(99, 1, 2400) == 800 + assert calculate_rarity_cost_adjustment(99, 1, 2000) == 800 # min enforced + assert calculate_rarity_cost_adjustment(99, 1, 3000) == 1400 + + def test_special_to_gold(self): + """Special (99) to Gold (2): -2140, min 100.""" + assert calculate_rarity_cost_adjustment(99, 2, 2400) == 260 + assert calculate_rarity_cost_adjustment(99, 2, 2000) == 100 # min enforced + + def test_special_to_common(self): + """Special (99) to Common (5): -2400, min 5.""" + assert calculate_rarity_cost_adjustment(99, 5, 2400) == 5 # min enforced + assert calculate_rarity_cost_adjustment(99, 5, 3000) == 600 + + def test_all_upward_transitions(self): + """Test all transitions that increase rarity (decrease number).""" + # Common (5) moving up + assert calculate_rarity_cost_adjustment(5, 4, 10) == 30 # +20 + assert calculate_rarity_cost_adjustment(5, 3, 10) == 90 # +80 + assert calculate_rarity_cost_adjustment(5, 2, 10) == 270 # +260 + assert calculate_rarity_cost_adjustment(5, 1, 10) == 810 # +800 + + # Bronze (4) moving up + assert calculate_rarity_cost_adjustment(4, 3, 30) == 90 # +60 + assert calculate_rarity_cost_adjustment(4, 2, 30) == 270 # +240 + assert calculate_rarity_cost_adjustment(4, 1, 30) == 810 # +780 + + # Silver (3) moving up + assert calculate_rarity_cost_adjustment(3, 2, 90) == 270 # +180 + assert calculate_rarity_cost_adjustment(3, 1, 90) == 810 # +720 + + # Gold (2) moving up + assert calculate_rarity_cost_adjustment(2, 1, 270) == 810 # +540 + + def test_all_downward_transitions_with_minimums(self): + """Test all transitions that decrease rarity (increase number) with minimum enforcement.""" + # Diamond (1) moving down - all have minimums + assert calculate_rarity_cost_adjustment(1, 2, 100) == 100 # would be -440, min 100 + assert calculate_rarity_cost_adjustment(1, 3, 100) == 50 # would be -620, min 50 + assert calculate_rarity_cost_adjustment(1, 4, 100) == 15 # would be -680, min 15 + assert calculate_rarity_cost_adjustment(1, 5, 100) == 5 # would be -700, min 5 + + # Gold (2) moving down - all have minimums + assert calculate_rarity_cost_adjustment(2, 3, 100) == 50 # would be -80, min 50 + assert calculate_rarity_cost_adjustment(2, 4, 100) == 15 # would be -140, min 15 + assert calculate_rarity_cost_adjustment(2, 5, 100) == 5 # would be -160, min 5 + + # Silver (3) moving down - all have minimums + assert calculate_rarity_cost_adjustment(3, 4, 50) == 15 # would be -10, min 15 + assert calculate_rarity_cost_adjustment(3, 5, 50) == 5 # would be -30, min 5 + + def test_edge_cases(self): + """Test edge cases: zero cost, very high cost, etc.""" + # Zero cost + assert calculate_rarity_cost_adjustment(5, 1, 0) == 800 + assert calculate_rarity_cost_adjustment(1, 5, 0) == 5 # min enforced + + # Very high cost + assert calculate_rarity_cost_adjustment(5, 1, 10000) == 10800 + assert calculate_rarity_cost_adjustment(99, 1, 10000) == 8400 + + def test_symmetry(self): + """Test that adjustments are symmetric (up then down returns close to original).""" + # Diamond to Common and back (won't be exact due to minimums, but should be logical) + original = 810 + after_down = calculate_rarity_cost_adjustment(1, 5, original) # 810 - 800 = 10, min 5 → 5 + after_up = calculate_rarity_cost_adjustment(5, 1, after_down) # 5 + 800 = 805 + assert after_down == 10 + assert after_up == 810 + + # Gold to Bronze and back + original = 270 + after_down = calculate_rarity_cost_adjustment(2, 4, original) # 270 - 240 = 30 + after_up = calculate_rarity_cost_adjustment(4, 2, after_down) # 30 + 240 = 270 + assert after_down == 30 + assert after_up == 270 + + +class TestRarityCostAdjustmentEdgeCases: + """Test edge cases and error handling.""" + + def test_undefined_transition(self): + """Test that undefined transitions are handled gracefully.""" + # There's no transition from 1 to 1 (same), but it's handled + result = calculate_rarity_cost_adjustment(1, 1, 500) + assert result == 500 + + # There's no transition from rarity 10 (doesn't exist) but should return old cost + result = calculate_rarity_cost_adjustment(10, 5, 500) + assert result == 500 # Falls back to old cost + + def test_negative_costs(self): + """Test behavior with negative costs (shouldn't happen, but test it).""" + # Negative costs should still get adjusted + result = calculate_rarity_cost_adjustment(5, 1, -100) + assert result == 700 # -100 + 800 = 700