major-domo-v2/models/README.md
Cal Corum 758be0f166 CLAUDE: Fix trade system issues and enhance documentation
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>
2025-10-06 16:10:13 -05:00

489 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:
```python
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:
- `Player` model: `id: int = Field(..., description="Player ID from database")`
- `Team` model: `id: int = Field(..., description="Team ID from database")`
## Model Categories
### Core Entities
#### League Structure
- **`team.py`** - Team information, abbreviations, divisions, and organizational affiliates
- **`division.py`** - Division structure and organization
- **`manager.py`** - Team managers and ownership
- **`standings.py`** - Team standings and rankings
#### Player Data
- **`player.py`** - Core player information and identifiers
- **`sbaplayer.py`** - Extended SBA-specific player data
- **`batting_stats.py`** - Batting statistics and performance metrics
- **`pitching_stats.py`** - Pitching statistics and performance metrics
- **`roster.py`** - Team roster assignments and positions
#### Game Operations
- **`game.py`** - Individual game results and scheduling
- **`transaction.py`** - Player transactions (trades, waivers, etc.)
#### Draft System
- **`draft_pick.py`** - Individual draft pick information
- **`draft_data.py`** - Draft round and selection data
- **`draft_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:
```python
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:
```python
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:
```python
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:
```python
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:
```python
# 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:
```python
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:
```python
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
```python
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
```python
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
```python
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
```python
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:
```python
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:
```python
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
```python
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=True` to reduce serialization size
## Development Guidelines
### Adding New Models
1. **Inherit from SBABaseModel** for consistency
2. **Define required fields explicitly** with proper types
3. **Add field descriptions** for documentation
4. **Include validation rules** for data integrity
5. **Provide `from_api_data()` class method** if needed
6. **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):
```python
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
```python
# 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
```python
# 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 `ValueError` if 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:
```python
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
```python
# 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
```python
# 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:
```python
# 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
```python
# 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
```python
# 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_moves` now uses only `moves_giving` to 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:**
1. Review existing model implementations for patterns
2. Understand the validation rules and field constraints
3. Check the service layer integration in `/services`
4. Follow the testing patterns with complete model data
5. Consider the API data format when creating new models