Adding 17 CLAUDE.md files across the project to provide detailed context and implementation guidelines for AI development agents: Root Documentation: - CLAUDE.md - Main project guide with Git workflow requirements Component Documentation: - commands/CLAUDE.md - Command architecture and patterns - models/CLAUDE.md - Pydantic models and validation - services/CLAUDE.md - Service layer and API interactions - tasks/CLAUDE.md - Background tasks and automation - tests/CLAUDE.md - Testing strategies and patterns - utils/CLAUDE.md - Utility functions and decorators - views/CLAUDE.md - Discord UI components and embeds Command Package Documentation: - commands/help/CLAUDE.md - Help system implementation - commands/injuries/CLAUDE.md - Injury management commands - commands/league/CLAUDE.md - League-wide commands - commands/players/CLAUDE.md - Player information commands - commands/profile/CLAUDE.md - User profile commands - commands/teams/CLAUDE.md - Team information commands - commands/transactions/CLAUDE.md - Transaction management - commands/utilities/CLAUDE.md - Utility commands - commands/voice/CLAUDE.md - Voice channel management Key Updates: - Updated .gitignore to track CLAUDE.md files in version control - Added Git Workflow section requiring branch-based development - Documented all architectural patterns and best practices - Included comprehensive command/service implementation guides These files provide essential context for AI agents working on the codebase, ensuring consistent patterns, proper error handling, and maintainable code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
691 lines
22 KiB
Markdown
691 lines
22 KiB
Markdown
# 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")`
|
||
|
||
### Game Submission Models (January 2025)
|
||
|
||
New models for comprehensive game data submission from Google Sheets scorecards:
|
||
|
||
#### Play Model (`play.py`)
|
||
Represents a single play in a baseball game with complete statistics and game state.
|
||
|
||
**Key Features:**
|
||
- **92 total fields** supporting comprehensive play-by-play tracking
|
||
- **68 fields from scorecard**: All data read from Google Sheets Playtable
|
||
- **Required fields**: game_id, play_num, pitcher_id, on_base_code, inning details, outs, scores
|
||
- **Base running**: Tracks up to 3 runners with starting and ending positions
|
||
- **Statistics**: PA, AB, H, HR, RBI, BB, SO, SB, CS, errors, and 20+ more
|
||
- **Advanced metrics**: WPA, RE24, ballpark effects
|
||
- **Descriptive text generation**: Automatic play descriptions for key plays display
|
||
|
||
**Field Validators:**
|
||
```python
|
||
@field_validator('on_first_final')
|
||
@classmethod
|
||
def no_final_if_no_runner_one(cls, v, info):
|
||
"""Ensure on_first_final is None if no runner on first."""
|
||
if info.data.get('on_first_id') is None:
|
||
return None
|
||
return v
|
||
```
|
||
|
||
**Usage Example:**
|
||
```python
|
||
play = Play(
|
||
id=1234,
|
||
game_id=567,
|
||
play_num=1,
|
||
pitcher_id=100,
|
||
batter_id=101,
|
||
on_base_code="000",
|
||
inning_half="top",
|
||
inning_num=1,
|
||
batting_order=1,
|
||
starting_outs=0,
|
||
away_score=0,
|
||
home_score=0,
|
||
homerun=1,
|
||
rbi=1,
|
||
wpa=0.15
|
||
)
|
||
|
||
# Generate human-readable description
|
||
description = play.descriptive_text(away_team, home_team)
|
||
# Output: "Top 1: (NYY) homers"
|
||
```
|
||
|
||
**Field Categories:**
|
||
- **Game Context**: game_id, play_num, inning_half, inning_num, starting_outs
|
||
- **Players**: batter_id, pitcher_id, catcher_id, defender_id, runner_id
|
||
- **Base Runners**: on_first_id, on_second_id, on_third_id (with _final positions)
|
||
- **Offensive Stats**: pa, ab, hit, rbi, double, triple, homerun, bb, so, hbp, sac
|
||
- **Defensive Stats**: outs, error, wild_pitch, passed_ball, pick_off, balk
|
||
- **Advanced**: wpa, re24_primary, re24_running, ballpark effects (bphr, bpfo, bp1b, bplo)
|
||
- **Pitching**: pitcher_rest_outs, inherited_runners, inherited_scored, on_hook_for_loss
|
||
|
||
**API-Populated Nested Objects:**
|
||
|
||
The Play model includes optional nested object fields for all ID references. These are populated by the API endpoint to provide complete context without additional lookups:
|
||
|
||
```python
|
||
class Play(SBABaseModel):
|
||
# ID field with corresponding optional object
|
||
game_id: int = Field(..., description="Game ID this play belongs to")
|
||
game: Optional[Game] = Field(None, description="Game object (API-populated)")
|
||
|
||
pitcher_id: int = Field(..., description="Pitcher ID")
|
||
pitcher: Optional[Player] = Field(None, description="Pitcher object (API-populated)")
|
||
|
||
batter_id: Optional[int] = Field(None, description="Batter ID")
|
||
batter: Optional[Player] = Field(None, description="Batter object (API-populated)")
|
||
|
||
# ... and so on for all player/team IDs
|
||
```
|
||
|
||
**Pattern Details:**
|
||
- **Placement**: Optional object field immediately follows its corresponding ID field
|
||
- **Naming**: Object field uses singular form of ID field name (e.g., `batter_id` → `batter`)
|
||
- **API Population**: Database endpoint includes nested objects in response
|
||
- **Future Enhancement**: Validators could ensure consistency between ID and object fields
|
||
|
||
**ID Fields with Nested Objects:**
|
||
- `game_id` → `game: Optional[Game]`
|
||
- `pitcher_id` → `pitcher: Optional[Player]`
|
||
- `batter_id` → `batter: Optional[Player]`
|
||
- `batter_team_id` → `batter_team: Optional[Team]`
|
||
- `pitcher_team_id` → `pitcher_team: Optional[Team]`
|
||
- `on_first_id` → `on_first: Optional[Player]`
|
||
- `on_second_id` → `on_second: Optional[Player]`
|
||
- `on_third_id` → `on_third: Optional[Player]`
|
||
- `catcher_id` → `catcher: Optional[Player]`
|
||
- `catcher_team_id` → `catcher_team: Optional[Team]`
|
||
- `defender_id` → `defender: Optional[Player]`
|
||
- `defender_team_id` → `defender_team: Optional[Team]`
|
||
- `runner_id` → `runner: Optional[Player]`
|
||
- `runner_team_id` → `runner_team: Optional[Team]`
|
||
|
||
**Usage Example:**
|
||
```python
|
||
# API returns play with nested objects populated
|
||
play = await play_service.get_play(play_id=123)
|
||
|
||
# Access nested objects directly without additional lookups
|
||
if play.batter:
|
||
print(f"Batter: {play.batter.name}")
|
||
if play.pitcher:
|
||
print(f"Pitcher: {play.pitcher.name}")
|
||
if play.game:
|
||
print(f"Game: {play.game.matchup_display}")
|
||
```
|
||
|
||
#### Decision Model (`decision.py`)
|
||
Tracks pitching decisions (wins, losses, saves, holds) for game results.
|
||
|
||
**Key Features:**
|
||
- **Pitching decisions**: Win, Loss, Save, Hold, Blown Save flags
|
||
- **Game metadata**: game_id, season, week, game_num
|
||
- **Pitcher workload**: rest_ip, rest_required, inherited runners
|
||
- **Human-readable repr**: Shows decision type (W/L/SV/HLD/BS)
|
||
|
||
**Usage Example:**
|
||
```python
|
||
decision = Decision(
|
||
id=456,
|
||
game_id=567,
|
||
season=12,
|
||
week=5,
|
||
game_num=2,
|
||
pitcher_id=200,
|
||
team_id=10,
|
||
win=1, # Winning pitcher
|
||
is_start=True,
|
||
rest_ip=7.0,
|
||
rest_required=4
|
||
)
|
||
|
||
print(decision)
|
||
# Output: Decision(pitcher_id=200, game_id=567, type=W)
|
||
```
|
||
|
||
**Field Categories:**
|
||
- **Game Context**: game_id, season, week, game_num
|
||
- **Pitcher**: pitcher_id, team_id
|
||
- **Decisions**: win, loss, hold, is_save, b_save (all 0 or 1)
|
||
- **Workload**: is_start, irunners, irunners_scored, rest_ip, rest_required
|
||
|
||
**Data Pipeline:**
|
||
```
|
||
Google Sheets Scorecard
|
||
↓
|
||
SheetsService.read_playtable_data() → 68 fields per play
|
||
↓
|
||
PlayService.create_plays_batch() → Validate with Play model
|
||
↓
|
||
Database API /plays endpoint
|
||
↓
|
||
PlayService.get_top_plays_by_wpa() → Return Play objects
|
||
↓
|
||
Play.descriptive_text() → Human-readable descriptions
|
||
```
|
||
|
||
## 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
|
||
- **`play.py`** (NEW - January 2025) - Play-by-play data for game submissions
|
||
- **`decision.py`** (NEW - January 2025) - Pitching decisions and game results
|
||
- **`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
|
||
|
||
### Custom Command Model (October 2025)
|
||
|
||
**Optional Creator ID Field**
|
||
|
||
The `CustomCommand` model now has an optional `creator_id` field to support different API endpoints:
|
||
|
||
```python
|
||
class CustomCommand(SBABaseModel):
|
||
id: int = Field(..., description="Database ID")
|
||
name: str = Field(..., description="Command name (unique)")
|
||
content: str = Field(..., description="Command response content")
|
||
creator_id: Optional[int] = Field(None, description="ID of the creator (may be missing from execute endpoint)")
|
||
creator: Optional[CustomCommandCreator] = Field(None, description="Creator details")
|
||
```
|
||
|
||
**Why Optional:**
|
||
- The `/custom_commands/by_name/{name}/execute` API endpoint returns command data **without** `creator_id`
|
||
- This endpoint only needs to return the command content for execution
|
||
- Other endpoints (get, create, update, delete) include full creator information
|
||
|
||
**Usage Pattern:**
|
||
```python
|
||
# Execute endpoint - creator_id may be None
|
||
command, content = await custom_commands_service.execute_command("BPFO")
|
||
# command.creator_id might be None, but command.content is always present
|
||
|
||
# Get endpoint - creator_id is always present
|
||
command = await custom_commands_service.get_command_by_name("BPFO")
|
||
# command.creator_id is populated, command.creator has full details
|
||
```
|
||
|
||
**Important Note:**
|
||
This change maintains backward compatibility while fixing validation errors when executing custom commands. Permission checks (edit/delete) use endpoints that return complete creator information.
|
||
|
||
#### 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 |