major-domo-v2/models
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
..
__init__.py CLAUDE: Initial commit for discord-app-v2 rebuild 2025-08-15 00:04:50 -05:00
base.py CLAUDE: Initial commit for discord-app-v2 rebuild 2025-08-15 00:04:50 -05:00
batting_stats.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
current.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
custom_command.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
division.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
draft_data.py CLAUDE: Initial commit for discord-app-v2 rebuild 2025-08-15 00:04:50 -05:00
draft_list.py CLAUDE: Initial commit for discord-app-v2 rebuild 2025-08-15 00:04:50 -05:00
draft_pick.py CLAUDE: Initial commit for discord-app-v2 rebuild 2025-08-15 00:04:50 -05:00
game.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
manager.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
pitching_stats.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
player.py Transactions cog in place 2025-09-24 09:32:04 -05:00
README.md CLAUDE: Fix trade system issues and enhance documentation 2025-10-06 16:10:13 -05:00
roster.py CLAUDE: Comprehensive bot improvements and test infrastructure 2025-10-02 11:35:26 -05:00
sbaplayer.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
standings.py CLAUDE: Major bot enhancements - Admin commands, player stats, standings, schedules 2025-08-28 15:32:38 -05:00
team.py CLAUDE: Fix trade system issues and enhance documentation 2025-10-06 16:10:13 -05:00
trade.py CLAUDE: Fix trade system issues and enhance documentation 2025-10-06 16:10:13 -05:00
transaction.py CLAUDE: Fix trade system issues and enhance documentation 2025-10-06 16:10:13 -05:00

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:

  • 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:

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=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):

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 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:

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_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