This commit includes cleanup from model refactoring and terminal client
modularization for better code organization and maintainability.
## Game Models Refactor
**Removed RunnerState class:**
- Eliminated separate RunnerState model (was redundant)
- Replaced runners: List[RunnerState] with direct base references:
- on_first: Optional[LineupPlayerState]
- on_second: Optional[LineupPlayerState]
- on_third: Optional[LineupPlayerState]
- Updated helper methods:
- get_runner_at_base() now returns LineupPlayerState directly
- get_all_runners() returns List[Tuple[int, LineupPlayerState]]
- is_runner_on_X() simplified to direct None checks
**Benefits:**
- Matches database structure (plays table has on_first_id, etc.)
- Simpler state management (direct references vs list management)
- Better type safety (LineupPlayerState vs generic runner)
- Easier to work with in game engine logic
**Updated files:**
- app/models/game_models.py - Removed RunnerState, updated GameState
- app/core/play_resolver.py - Use get_all_runners() instead of state.runners
- app/core/validators.py - Updated runner access patterns
- tests/unit/models/test_game_models.py - Updated test assertions
- tests/unit/core/test_play_resolver.py - Updated test data
- tests/unit/core/test_validators.py - Updated test data
## Terminal Client Refactor
**Modularization (DRY principle):**
Created separate modules for better code organization:
1. **terminal_client/commands.py** (10,243 bytes)
- Shared command functions for game operations
- Used by both CLI (main.py) and REPL (repl.py)
- Functions: submit_defensive_decision, submit_offensive_decision,
resolve_play, quick_play_sequence
- Single source of truth for command logic
2. **terminal_client/arg_parser.py** (7,280 bytes)
- Centralized argument parsing and validation
- Handles defensive/offensive decision arguments
- Validates formats (alignment, depths, hold runners, steal attempts)
3. **terminal_client/completions.py** (10,357 bytes)
- TAB completion support for REPL mode
- Command completions, option completions, dynamic completions
- Game ID completions, defensive/offensive option suggestions
4. **terminal_client/help_text.py** (10,839 bytes)
- Centralized help text and command documentation
- Detailed command descriptions
- Usage examples for all commands
**Updated main modules:**
- terminal_client/main.py - Simplified by using shared commands module
- terminal_client/repl.py - Cleaner with shared functions and completions
**Benefits:**
- DRY: Behavior consistent between CLI and REPL modes
- Maintainability: Changes in one place affect both interfaces
- Testability: Can test commands module independently
- Organization: Clear separation of concerns
## Documentation
**New files:**
- app/models/visual_model_relationships.md
- Visual documentation of model relationships
- Helps understand data flow between models
- terminal_client/update_docs/ (6 phase documentation files)
- Phased documentation for terminal client evolution
- Historical context for implementation decisions
## Tests
**New test files:**
- tests/unit/terminal_client/__init__.py
- tests/unit/terminal_client/test_arg_parser.py
- tests/unit/terminal_client/test_commands.py
- tests/unit/terminal_client/test_completions.py
- tests/unit/terminal_client/test_help_text.py
**Updated tests:**
- Integration tests updated for new runner model
- Unit tests updated for model changes
- All tests passing with new structure
## Summary
- ✅ Simplified game state model (removed RunnerState)
- ✅ Better alignment with database structure
- ✅ Modularized terminal client (DRY principle)
- ✅ Shared command logic between CLI and REPL
- ✅ Comprehensive test coverage
- ✅ Improved documentation
Total changes: 26 files modified/created
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
309 lines
20 KiB
Markdown
309 lines
20 KiB
Markdown
════════════════════════════════════════════════════════════════════════════════
|
|
BACKEND MODEL RELATIONSHIPS
|
|
════════════════════════════════════════════════════════════════════════════════
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ DATABASE MODELS (db_models.py) │
|
|
│ SQLAlchemy ORM - PostgreSQL Tables │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌─────────────┐
|
|
│ GAME │ (games table)
|
|
│ PK: id(UUID)│
|
|
│ │
|
|
│ league_id │ ('sba' or 'pd')
|
|
│ home_team_id│
|
|
│ away_team_id│
|
|
│ status │
|
|
│ game_mode │
|
|
│ inning/half │
|
|
│ scores │
|
|
│ │
|
|
│ AI Support: │
|
|
│ - home_is_ai│
|
|
│ - away_is_ai│
|
|
│ - difficulty│
|
|
└──────┬──────┘
|
|
│
|
|
┌────────────────────┼────────────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
│ PLAY │ │ LINEUP │ │ SESSION │
|
|
│ (plays) │ │(lineups) │ │(sessions)│
|
|
└──────────┘ └──────────┘ └──────────┘
|
|
│ │ │
|
|
│ │ (WebSocket state)
|
|
│ │
|
|
┌────────────┼────────────┐ │
|
|
│ │ │ │
|
|
▼ ▼ ▼ ▼
|
|
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
|
|
│ batter │ │pitcher │ │catcher │ │on_first│
|
|
│ (FK) │ │ (FK) │ │ (FK) │ │ (FK) │
|
|
└────────┘ └────────┘ └────────┘ └────────┘
|
|
│ │ │ │
|
|
└────────────┴────────────┴───────┘
|
|
│
|
|
All FK to LINEUP
|
|
|
|
|
|
┌──────────────────────────────┐
|
|
│ GAME RELATIONSHIPS │
|
|
│ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ GameCardsetLink │ (PD) │
|
|
│ │ - game_id (FK) │ │
|
|
│ │ - cardset_id │ │
|
|
│ │ - priority │ │
|
|
│ └─────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ RosterLink │ │
|
|
│ │ - game_id (FK) │ │
|
|
│ │ - team_id │ │
|
|
│ │ - card_id 🅿 │(PD) │
|
|
│ │ - player_id 🆂 │(SBA) │
|
|
│ │ XOR constraint │ │
|
|
│ └─────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ Roll │ │
|
|
│ │ - roll_id (PK) │ │
|
|
│ │ - game_id (FK) │ │
|
|
│ │ - roll_type │ │
|
|
│ │ - roll_data │ │
|
|
│ │ - context │ │
|
|
│ └─────────────────┘ │
|
|
└──────────────────────────────┘
|
|
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ IN-MEMORY STATE MODELS (game_models.py) │
|
|
│ Pydantic v2 - Fast Validation & Serialization │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────┐
|
|
│ GameState │ (Core in-memory state)
|
|
│ │
|
|
│ game_id: UUID │
|
|
│ league_id: str │
|
|
│ home_team_id │
|
|
│ away_team_id │
|
|
│ home/away_is_ai │
|
|
│ │
|
|
│ Game State: │
|
|
│ - status │
|
|
│ - inning/half │
|
|
│ - outs │
|
|
│ - scores │
|
|
│ │
|
|
│ Current Play: │
|
|
│ - batter_id │
|
|
│ - pitcher_id │
|
|
│ - catcher_id │
|
|
│ - on_base_code │
|
|
│ │
|
|
│ Decision Track: │
|
|
│ - pending_dec │
|
|
│ - decisions_dict │
|
|
│ │
|
|
│ Batters Index: │
|
|
│ - away_idx (0-8) │
|
|
│ - home_idx (0-8) │
|
|
└────────┬─────────┘
|
|
│
|
|
┌─────────────┼─────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
│ RunnerState │ │DefDecision │ │OffDecision │
|
|
│ │ │ │ │ │
|
|
│ lineup_id │ │ alignment │ │ approach │
|
|
│ card_id │ │ if_depth │ │ steal_atts │
|
|
│ on_base(1-3) │ │ of_depth │ │ hit_and_run │
|
|
└──────────────┘ │ hold_runners│ │ bunt_attempt │
|
|
└─────────────┘ └──────────────┘
|
|
|
|
|
|
┌──────────────────┐
|
|
│ TeamLineupState │
|
|
│ │
|
|
│ team_id: int │
|
|
│ players: List[ │
|
|
│ LineupPlayer │
|
|
│ State │
|
|
│ ] │
|
|
│ │
|
|
│ Methods: │
|
|
│ - get_batting_ │
|
|
│ order() │
|
|
│ - get_pitcher() │
|
|
│ - get_batter() │
|
|
└──────────────────┘
|
|
│
|
|
│ Contains list of
|
|
▼
|
|
┌──────────────────┐
|
|
│LineupPlayerState │
|
|
│ │
|
|
│ lineup_id: int │
|
|
│ card_id: int │
|
|
│ position: str │
|
|
│ batting_order │
|
|
│ is_active: bool │
|
|
└──────────────────┘
|
|
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ ROSTER MODELS (roster_models.py) │
|
|
│ Pydantic - Type-safe Roster Operations │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
┌────────────────────────┐
|
|
│ BaseRosterLinkData │ (Abstract)
|
|
│ │
|
|
│ id: Optional[int] │
|
|
│ game_id: UUID │
|
|
│ team_id: int │
|
|
│ │
|
|
│ Abstract Methods: │
|
|
│ - get_entity_id() │
|
|
│ - get_entity_type() │
|
|
└───────────┬────────────┘
|
|
│
|
|
┌───────────┴───────────┐
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────────────┐ ┌──────────────────────┐
|
|
│ PdRosterLinkData │ │ SbaRosterLinkData │
|
|
│ │ │ │
|
|
│ card_id: int 🅿 │ │ player_id: int 🆂 │
|
|
│ │ │ │
|
|
│ get_entity_id() │ │ get_entity_id() │
|
|
│ → returns card_id │ │ → returns player_id │
|
|
│ │ │ │
|
|
│ get_entity_type() │ │ get_entity_type() │
|
|
│ → returns "card" │ │ → returns "player" │
|
|
└──────────────────────┘ └──────────────────────┘
|
|
|
|
|
|
┌────────────────────────┐
|
|
│ RosterLinkCreate │ (Request model)
|
|
│ │
|
|
│ game_id: UUID │
|
|
│ team_id: int │
|
|
│ card_id: Optional │
|
|
│ player_id: Optional │
|
|
│ │
|
|
│ Validation: │
|
|
│ - XOR check (exactly │
|
|
│ one ID required) │
|
|
│ │
|
|
│ Methods: │
|
|
│ - to_pd_data() │
|
|
│ - to_sba_data() │
|
|
└────────────────────────┘
|
|
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
KEY RELATIONSHIPS SUMMARY
|
|
|
|
Database (PostgreSQL) In-Memory (Pydantic) Roster Types
|
|
═════════════════════ ═══════════════════ ════════════
|
|
|
|
Game ──┬──> Play GameState BaseRosterLinkData
|
|
│ │ │ │
|
|
│ └──> Lineup ├──> RunnerState ├─> PdRosterLinkData
|
|
│ (batter, │ │
|
|
│ pitcher, ├──> DefensiveDecision └─> SbaRosterLinkData
|
|
│ catcher, │
|
|
│ runners) └──> OffensiveDecision
|
|
│
|
|
├──> Lineup TeamLineupState
|
|
│ │
|
|
├──> GameCardsetLink └──> LineupPlayerState
|
|
│ (PD only)
|
|
│
|
|
├──> RosterLink RosterLinkCreate ─┬─> PdRosterLinkData
|
|
│ - card_id (PD) │
|
|
│ - player_id (SBA) └─> SbaRosterLinkData
|
|
│
|
|
├──> GameSession
|
|
│ (WebSocket state)
|
|
│
|
|
└──> Roll
|
|
(dice history)
|
|
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
POLYMORPHIC PATTERN SUMMARY
|
|
|
|
Both `Lineup` and `RosterLink` support PD and SBA leagues:
|
|
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ Lineup (db_models.py) RosterLink (db_models.py) │
|
|
│ ══════════════════ ════════════════════ │
|
|
│ │
|
|
│ card_id (nullable) card_id (nullable) │
|
|
│ player_id (nullable) player_id (nullable) │
|
|
│ │
|
|
│ CHECK: exactly one populated CHECK: exactly one │
|
|
│ UNIQUE: (game_id, card_id) UNIQUE: (game_id, card) │
|
|
│ UNIQUE: (game_id, player_id) UNIQUE: (game_id, player)│
|
|
│ │
|
|
│ 🅿 PD: card_id NOT NULL 🅿 PD: card_id NOT NULL │
|
|
│ 🆂 SBA: player_id NOT NULL 🆂 SBA: player_id NOT │
|
|
└──────────────────────────────────────────────────────────┘
|
|
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
DATA FLOW EXAMPLE
|
|
|
|
1. Create Game
|
|
═══════════
|
|
Game (DB) ────────────────> GameState (Memory)
|
|
│ │
|
|
├─> RosterLink (DB) │
|
|
├─> GameCardsetLink (PD) │
|
|
└─> GameSession (DB) │
|
|
|
|
2. Setup Lineup
|
|
════════════
|
|
Lineup (DB) ───────────────> TeamLineupState (Memory)
|
|
│ │
|
|
└─> (card_id or player_id) └─> LineupPlayerState[]
|
|
|
|
3. Execute Play
|
|
════════════
|
|
GameState ──┬──> DefensiveDecision
|
|
├──> OffensiveDecision
|
|
├──> RunnerState[]
|
|
└──> (resolve play)
|
|
│
|
|
▼
|
|
Play (DB) ──> Save outcome
|
|
│
|
|
├─> batter_id (FK to Lineup)
|
|
├─> pitcher_id (FK to Lineup)
|
|
├─> on_first/second/third_id
|
|
└─> 25+ stat fields
|
|
|
|
4. Dice Roll Audit
|
|
═══════════════
|
|
Roll (DB) ──> Stores cryptographic roll history
|
|
│
|
|
├─> roll_data (JSONB): complete roll
|
|
├─> context (JSONB): game situation
|
|
└─> Used for recovery/analytics
|
|
|
|
|
|
═══════════════════════════════════════════════════════════════════════════════
|