Major fixes and improvements: Trade System Fixes: - Fix duplicate player moves in trade embed Player Exchanges section - Resolve "WVMiL not participating" error for Minor League destinations - Implement organizational authority model for ML/MiL/IL team relationships - Update Trade.cross_team_moves to deduplicate using moves_giving only Team Model Enhancements: - Rewrite roster_type() method using sname as definitive source per spec - Fix edge cases like "BHMIL" (Birmingham IL) vs "BHMMIL" - Update _get_base_abbrev() to use consistent sname-based logic - Add organizational lookup support in trade participation Autocomplete System: - Fix major_league_team_autocomplete invalid roster_type parameter - Implement client-side filtering using Team.roster_type() method - Add comprehensive test coverage for all autocomplete functions - Centralize autocomplete logic to shared utils functions Test Infrastructure: - Add 25 new tests for trade models and trade builder - Add 13 autocomplete function tests with error handling - Fix existing test failures with proper mocking patterns - Update dropadd tests to use shared autocomplete functions Documentation Updates: - Document trade model enhancements and deduplication fix - Add autocomplete function documentation with usage examples - Document organizational authority model and edge case handling - Update README files with recent fixes and implementation notes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| base.py | ||
| batting_stats.py | ||
| current.py | ||
| custom_command.py | ||
| division.py | ||
| draft_data.py | ||
| draft_list.py | ||
| draft_pick.py | ||
| game.py | ||
| manager.py | ||
| pitching_stats.py | ||
| player.py | ||
| README.md | ||
| roster.py | ||
| sbaplayer.py | ||
| standings.py | ||
| team.py | ||
| trade.py | ||
| transaction.py | ||
Models Directory
The models directory contains Pydantic data models for Discord Bot v2.0, providing type-safe representations of all SBA (Strat-o-Matic Baseball Association) entities. All models inherit from SBABaseModel and follow consistent validation patterns.
Architecture
Pydantic Foundation
All models use Pydantic v2 with:
- Automatic validation of field types and constraints
- Serialization/deserialization for API interactions
- Type safety with full IDE support
- JSON schema generation for documentation
- Field validation with custom validators
Base Model (base.py)
The foundation for all SBA models:
class SBABaseModel(BaseModel):
model_config = {
"validate_assignment": True,
"use_enum_values": True,
"arbitrary_types_allowed": True,
"json_encoders": {datetime: lambda v: v.isoformat() if v else None}
}
id: Optional[int] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
Breaking Changes (August 2025)
Database entities now require id fields since they're always fetched from the database:
Playermodel:id: int = Field(..., description="Player ID from database")Teammodel:id: int = Field(..., description="Team ID from database")
Model Categories
Core Entities
League Structure
team.py- Team information, abbreviations, divisions, and organizational affiliatesdivision.py- Division structure and organizationmanager.py- Team managers and ownershipstandings.py- Team standings and rankings
Player Data
player.py- Core player information and identifierssbaplayer.py- Extended SBA-specific player databatting_stats.py- Batting statistics and performance metricspitching_stats.py- Pitching statistics and performance metricsroster.py- Team roster assignments and positions
Game Operations
game.py- Individual game results and schedulingtransaction.py- Player transactions (trades, waivers, etc.)
Draft System
draft_pick.py- Individual draft pick informationdraft_data.py- Draft round and selection datadraft_list.py- Complete draft lists and results
Custom Features
custom_command.py- User-created Discord commands
Trade System
trade.py- Multi-team trade structures and validation
Legacy Models
current.py- Legacy model definitions for backward compatibility
Model Validation Patterns
Required Fields
Models distinguish between required and optional fields:
class Player(SBABaseModel):
id: int = Field(..., description="Player ID from database") # Required
name: str = Field(..., description="Player full name") # Required
team_id: Optional[int] = None # Optional
position: Optional[str] = None # Optional
Field Constraints
Models use Pydantic validators for data integrity:
class BattingStats(SBABaseModel):
at_bats: int = Field(ge=0, description="At bats (non-negative)")
hits: int = Field(ge=0, le=Field('at_bats'), description="Hits (cannot exceed at_bats)")
@field_validator('batting_average')
@classmethod
def validate_batting_average(cls, v):
if v is not None and not 0.0 <= v <= 1.0:
raise ValueError('Batting average must be between 0.0 and 1.0')
return v
Custom Validators
Models implement business logic validation:
class Transaction(SBABaseModel):
transaction_type: str
player_id: int
from_team_id: Optional[int] = None
to_team_id: Optional[int] = None
@model_validator(mode='after')
def validate_team_requirements(self):
if self.transaction_type == 'trade':
if not self.from_team_id or not self.to_team_id:
raise ValueError('Trade transactions require both from_team_id and to_team_id')
return self
API Integration
Data Transformation
Models provide methods for API interaction:
class Player(SBABaseModel):
@classmethod
def from_api_data(cls, data: Dict[str, Any]):
"""Create model instance from API response data."""
if not data:
raise ValueError(f"Cannot create {cls.__name__} from empty data")
return cls(**data)
def to_dict(self, exclude_none: bool = True) -> Dict[str, Any]:
"""Convert model to dictionary for API requests."""
return self.model_dump(exclude_none=exclude_none)
Serialization Examples
Models handle various data formats:
# From API JSON
player_data = {"id": 123, "name": "Player Name", "team_id": 5}
player = Player.from_api_data(player_data)
# To API JSON
api_payload = player.to_dict(exclude_none=True)
# JSON string serialization
json_string = player.model_dump_json()
# From JSON string
player_copy = Player.model_validate_json(json_string)
Testing Requirements
Model Validation Testing
All model tests must provide complete data:
def test_player_creation():
# ✅ Correct - provides required ID field
player = Player(
id=123,
name="Test Player",
team_id=5,
position="1B"
)
assert player.id == 123
def test_incomplete_data():
# ❌ This will fail - missing required ID
with pytest.raises(ValidationError):
Player(name="Test Player") # Missing required id field
Test Data Patterns
Use helper functions for consistent test data:
def create_test_player(**overrides) -> Player:
"""Create a test player with default values."""
defaults = {
"id": 123,
"name": "Test Player",
"team_id": 1,
"position": "1B"
}
defaults.update(overrides)
return Player(**defaults)
def test_player_with_stats():
player = create_test_player(name="Star Player")
assert player.name == "Star Player"
assert player.id == 123 # Default from helper
Field Types and Constraints
Common Field Patterns
Identifiers
id: int = Field(..., description="Database primary key")
player_id: int = Field(..., description="Foreign key to player")
team_id: Optional[int] = Field(None, description="Foreign key to team")
Names and Text
name: str = Field(..., min_length=1, max_length=100)
abbreviation: str = Field(..., min_length=2, max_length=5)
description: Optional[str] = Field(None, max_length=500)
Statistics
games_played: int = Field(ge=0, description="Games played (non-negative)")
batting_average: Optional[float] = Field(None, ge=0.0, le=1.0)
era: Optional[float] = Field(None, ge=0.0, description="Earned run average")
Dates and Times
game_date: Optional[datetime] = None
created_at: Optional[datetime] = None
season_year: int = Field(..., ge=1900, le=2100)
Model Relationships
Foreign Key Patterns
Models reference related entities via ID fields:
class Player(SBABaseModel):
id: int
team_id: Optional[int] = None # References Team.id
class BattingStats(SBABaseModel):
player_id: int # References Player.id
season: int
team_id: int # References Team.id
Nested Objects
Some models contain nested structures:
class CustomCommand(SBABaseModel):
name: str
creator: Manager # Nested Manager object
response: str
class DraftPick(SBABaseModel):
pick_number: int
player: Optional[Player] = None # Optional nested Player
team: Team # Required nested Team
Validation Error Handling
Common Validation Errors
- Missing required fields - Provide all required model fields
- Type mismatches - Ensure field types match model definitions
- Constraint violations - Check field validators and constraints
- Invalid nested objects - Validate all nested model data
Error Examples
try:
player = Player(name="Test") # Missing required id
except ValidationError as e:
print(e.errors())
# [{'type': 'missing', 'loc': ('id',), 'msg': 'Field required'}]
try:
stats = BattingStats(hits=5, at_bats=3) # hits > at_bats
except ValidationError as e:
print(e.errors())
# Constraint violation error
Performance Considerations
Model Instantiation
- Use
model_validate()for external data - Use
model_construct()for trusted internal data (faster) - Cache model instances when possible
- Avoid repeated validation of the same data
Memory Usage
- Models are relatively lightweight
- Nested objects can increase memory footprint
- Consider using
__slots__for high-volume models - Use
exclude_none=Trueto reduce serialization size
Development Guidelines
Adding New Models
- Inherit from SBABaseModel for consistency
- Define required fields explicitly with proper types
- Add field descriptions for documentation
- Include validation rules for data integrity
- Provide
from_api_data()class method if needed - Write comprehensive tests covering edge cases
Team Model Enhancements (January 2025)
Organizational Affiliate Methods
The Team model now includes methods to work with organizational affiliates (Major League, Minor League, and Injured List teams):
class Team(SBABaseModel):
async def major_league_affiliate(self) -> 'Team':
"""Get the major league team for this organization via API call."""
async def minor_league_affiliate(self) -> 'Team':
"""Get the minor league team for this organization via API call."""
async def injured_list_affiliate(self) -> 'Team':
"""Get the injured list team for this organization via API call."""
def is_same_organization(self, other_team: 'Team') -> bool:
"""Check if this team and another team are from the same organization."""
Usage Examples
Organizational Relationships
# Get affiliate teams
por_team = await team_service.get_team_by_abbrev("POR", 12)
por_mil = await por_team.minor_league_affiliate() # Returns "PORMIL" team
por_il = await por_team.injured_list_affiliate() # Returns "PORIL" team
# Check organizational relationships
assert por_team.is_same_organization(por_mil) # True
assert por_team.is_same_organization(por_il) # True
# Different organizations
nyy_team = await team_service.get_team_by_abbrev("NYY", 12)
assert not por_team.is_same_organization(nyy_team) # False
Roster Type Detection
# Determine roster type from team abbreviation
assert por_team.roster_type() == RosterType.MAJOR_LEAGUE # "POR"
assert por_mil.roster_type() == RosterType.MINOR_LEAGUE # "PORMIL"
assert por_il.roster_type() == RosterType.INJURED_LIST # "PORIL"
# Handle edge cases
bhm_il = Team(abbrev="BHMIL") # BHM + IL, not BH + MIL
assert bhm_il.roster_type() == RosterType.INJURED_LIST
Implementation Notes
- API Integration: Affiliate methods make actual API calls to fetch team data
- Error Handling: Methods raise
ValueErrorif affiliate teams cannot be found - Edge Cases: Correctly handles teams like "BHMIL" (Birmingham IL)
- Performance: Base abbreviation extraction is cached internally
Model Evolution
- Backward compatibility - Add optional fields for new features
- Migration patterns - Handle schema changes gracefully
- Version management - Document breaking changes
- API alignment - Keep models synchronized with API
Testing Strategy
- Unit tests for individual model validation
- Integration tests with service layer
- Edge case testing for validation rules
- Performance tests for large data sets
Trade Model Enhancements (January 2025)
Multi-Team Trade Support
The Trade model now supports complex multi-team player exchanges with proper organizational authority handling:
class Trade(SBABaseModel):
def get_participant_by_organization(self, team: Team) -> Optional[TradeParticipant]:
"""Find participant by organization affiliation.
Major League team owners control their entire organization (ML/MiL/IL),
so if a ML team is participating, their MiL and IL teams are also valid.
"""
@property
def cross_team_moves(self) -> List[TradeMove]:
"""Get all moves that cross team boundaries (deduplicated)."""
Key Features
Organizational Authority Model
# ML team owners can trade from/to any affiliate
wv_team = Team(abbrev="WV") # Major League
wv_mil = Team(abbrev="WVMIL") # Minor League
wv_il = Team(abbrev="WVIL") # Injured List
# If WV is participating in trade, WVMIL and WVIL moves are valid
trade.add_participant(wv_team) # Add ML team
# Now can move players to/from WVMIL and WVIL
Deduplication Fix
# Before: Each move appeared twice (giving + receiving perspective)
cross_moves = trade.cross_team_moves # Would show duplicates
# After: Clean single view of each player exchange
cross_moves = trade.cross_team_moves # Shows each move once
Trade Move Descriptions
Enhanced move descriptions with clear team-to-team visualization:
# Team-to-team trade
"🔄 Mike Trout: WV (ML) → NY (ML)"
# Free agency signing
"➕ Mike Trout: FA → WV (ML)"
# Release to free agency
"➖ Mike Trout: WV (ML) → FA"
Usage Examples
Basic Trade Setup
# Create trade
trade = Trade(trade_id="abc123", participants=[], status=TradeStatus.DRAFT)
# Add participating teams
wv_participant = trade.add_participant(wv_team)
ny_participant = trade.add_participant(ny_team)
# Create player moves
move = TradeMove(
player=player,
from_team=wv_team,
to_team=ny_team,
source_team=wv_team,
destination_team=ny_team
)
Organizational Flexibility
# Trade builder allows MiL/IL destinations when ML team participates
builder = TradeBuilder(user_id, wv_team) # WV is participating
builder.add_team(ny_team)
# This now works - can send player to NYMIL
success, error = await builder.add_player_move(
player=player,
from_team=wv_team,
to_team=ny_mil_team, # Minor league affiliate
from_roster=RosterType.MAJOR_LEAGUE,
to_roster=RosterType.MINOR_LEAGUE
)
assert success # ✅ Works due to organizational authority
Implementation Notes
- Deduplication:
cross_team_movesnow uses onlymoves_givingto avoid showing same move twice - Organizational Lookup: Trade participants can be found by any team in the organization
- Validation: Trade balance validation ensures moves are properly matched
- UI Integration: Embeds show clean, deduplicated player exchange lists
Breaking Changes Fixed
- Team Roster Type Detection: Updated logic to handle edge cases like "BHMIL" correctly
- Autocomplete Functions: Fixed invalid parameter passing in team filtering
- Trade Participant Validation: Now properly handles organizational affiliates
Next Steps for AI Agents:
- Review existing model implementations for patterns
- Understand the validation rules and field constraints
- Check the service layer integration in
/services - Follow the testing patterns with complete model data
- Consider the API data format when creating new models