diff --git a/VERSION b/VERSION index 10f67f4..28e8154 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.25.5 +2.25.6 diff --git a/services/transaction_builder.py b/services/transaction_builder.py index 7d7d1f4..2993f7c 100644 --- a/services/transaction_builder.py +++ b/services/transaction_builder.py @@ -87,6 +87,7 @@ class RosterValidationResult: minor_league_limit: int = 6 major_league_swar: float = 0.0 minor_league_swar: float = 0.0 + major_league_swar_cap: float = 32.0 # Team's sWAR cap limit pre_existing_ml_swar_change: float = 0.0 pre_existing_mil_swar_change: float = 0.0 pre_existing_transaction_count: int = 0 @@ -110,7 +111,10 @@ class RosterValidationResult: @property def major_league_swar_status(self) -> str: """Status string for major league sWAR.""" - return f"📊 Major League sWAR: {self.major_league_swar:.2f}" + if self.major_league_swar > self.major_league_swar_cap: + return f"❌ Major League sWAR: {self.major_league_swar:.2f}/{self.major_league_swar_cap:.1f} (Over cap!)" + else: + return f"✅ Major League sWAR: {self.major_league_swar:.2f}/{self.major_league_swar_cap:.1f}" @property def minor_league_swar_status(self) -> str: @@ -447,9 +451,18 @@ class TransactionBuilder: errors.append(f"Minor League roster would have {projected_mil_size} players (limit: {mil_limit})") suggestions.append(f"Drop {projected_mil_size - mil_limit} MiL player(s) to make roster legal") elif projected_mil_size < 0: - is_legal = False + is_legal = False errors.append("Cannot have negative players on Minor League roster") - + + # Validate sWAR cap + # Use team-specific cap if set, otherwise fall back to config default + team_swar_cap = self.team.salary_cap if self.team.salary_cap is not None else config.swar_cap_limit + if projected_ml_swar > team_swar_cap: + is_legal = False + errors.append(f"Major League sWAR would be {projected_ml_swar:.2f} (cap: {team_swar_cap:.1f})") + over_cap = projected_ml_swar - team_swar_cap + suggestions.append(f"Remove {over_cap:.2f} sWAR from ML roster to be under cap") + # Add suggestions for empty transaction if not self.moves: suggestions.append("Add player moves to build your transaction") @@ -465,6 +478,7 @@ class TransactionBuilder: minor_league_limit=mil_limit, major_league_swar=projected_ml_swar, minor_league_swar=projected_mil_swar, + major_league_swar_cap=team_swar_cap, pre_existing_ml_swar_change=pre_existing_ml_swar_change, pre_existing_mil_swar_change=pre_existing_mil_swar_change, pre_existing_transaction_count=pre_existing_count diff --git a/tests/test_services_transaction_builder.py b/tests/test_services_transaction_builder.py index 76abd7d..e33acb0 100644 --- a/tests/test_services_transaction_builder.py +++ b/tests/test_services_transaction_builder.py @@ -24,7 +24,7 @@ from tests.factories import PlayerFactory, TeamFactory class TestTransactionBuilder: """Test TransactionBuilder core functionality.""" - + @pytest.fixture def mock_team(self): """Create a mock team for testing.""" @@ -33,9 +33,10 @@ class TestTransactionBuilder: abbrev='WV', sname='Black Bears', lname='West Virginia Black Bears', - season=12 + season=12, + salary_cap=50.0 # Set high cap to avoid sWAR validation failures in roster tests ) - + @pytest.fixture def mock_player(self): """Create a mock player for testing.""" @@ -410,7 +411,83 @@ class TestTransactionBuilder: assert validation.major_league_count == 24 # No changes assert len(validation.suggestions) == 1 assert "Add player moves" in validation.suggestions[0] - + + @pytest.mark.asyncio + async def test_validate_transaction_over_swar_cap(self, mock_roster): + """Test validation fails when sWAR exceeds team cap.""" + # Create a team with a low salary cap (30.0) + low_cap_team = Team( + id=499, + abbrev='WV', + sname='Black Bears', + lname='West Virginia Black Bears', + season=12, + salary_cap=30.0 # Low cap - roster has 24 * 1.5 = 36.0 sWAR + ) + builder = TransactionBuilder(low_cap_team, user_id=12345) + + with patch.object(builder, '_current_roster', mock_roster): + with patch.object(builder, '_roster_loaded', True): + validation = await builder.validate_transaction() + + # Should fail due to sWAR cap (36.0 > 30.0) + assert validation.is_legal is False + assert validation.major_league_swar == 36.0 # 24 players * 1.5 wara + assert validation.major_league_swar_cap == 30.0 + assert any("sWAR would be 36.00 (cap: 30.0)" in error for error in validation.errors) + assert any("Remove 6.00 sWAR" in suggestion for suggestion in validation.suggestions) + + @pytest.mark.asyncio + async def test_validate_transaction_under_swar_cap(self, mock_team, mock_roster): + """Test validation passes when sWAR is under team cap.""" + # mock_team has salary_cap=50.0, roster has 36.0 sWAR + builder = TransactionBuilder(mock_team, user_id=12345) + + with patch.object(builder, '_current_roster', mock_roster): + with patch.object(builder, '_roster_loaded', True): + validation = await builder.validate_transaction() + + # Should pass - 36.0 < 50.0 + assert validation.is_legal is True + assert validation.major_league_swar == 36.0 + assert validation.major_league_swar_cap == 50.0 + + @pytest.mark.asyncio + async def test_swar_status_display_over_cap(self): + """Test sWAR status display shows correct format when over cap.""" + validation = RosterValidationResult( + is_legal=False, + major_league_count=26, + minor_league_count=6, + warnings=[], + errors=[], + suggestions=[], + major_league_swar=35.5, + minor_league_swar=3.0, + major_league_swar_cap=33.5 + ) + assert "❌" in validation.major_league_swar_status + assert "35.50/33.5" in validation.major_league_swar_status + assert "Over cap!" in validation.major_league_swar_status + + @pytest.mark.asyncio + async def test_swar_status_display_under_cap(self): + """Test sWAR status display shows correct format when under cap.""" + validation = RosterValidationResult( + is_legal=True, + major_league_count=26, + minor_league_count=6, + warnings=[], + errors=[], + suggestions=[], + major_league_swar=31.0, + minor_league_swar=3.0, + major_league_swar_cap=33.5 + ) + assert "✅" in validation.major_league_swar_status + assert "31.00/33.5" in validation.major_league_swar_status + assert "Over cap!" not in validation.major_league_swar_status + @pytest.mark.asyncio async def test_submit_transaction_empty(self, builder): """Test submitting empty transaction fails."""