Implemented complete WebSocket integration for real-time player substitutions.
System is now 80% complete - only tests remain.
## WebSocket Events Implemented (600 lines)
### Event Handlers (backend/app/websocket/handlers.py):
1. request_pinch_hitter - Pinch hitter substitution
- Validates: game_id, player_out_lineup_id, player_in_card_id, team_id
- Executes: SubstitutionManager.pinch_hit()
- Broadcasts: player_substituted (all clients), substitution_confirmed (requester)
- Error codes: MISSING_FIELD, INVALID_FORMAT, NOT_CURRENT_BATTER, etc.
2. request_defensive_replacement - Defensive replacement
- Additional field: new_position (P, C, 1B, 2B, 3B, SS, LF, CF, RF)
- Executes: SubstitutionManager.defensive_replace()
- Same broadcast pattern as pinch hitter
3. request_pitching_change - Pitching change
- Validates minimum batters faced (handled in SubstitutionManager)
- Executes: SubstitutionManager.change_pitcher()
- Broadcasts new pitcher to all clients
4. get_lineup - Get active lineup for team
- Returns: lineup_data with all active players
- Uses: StateManager cache (O(1)) or database fallback
- Purpose: UI refresh after substitutions
### Event Pattern (follows existing handlers):
- Validate inputs (UUID format, required fields, game exists)
- Create SubstitutionManager instance with DatabaseOperations
- Execute substitution (validate → DB → state)
- Broadcast player_substituted to game room
- Send substitution_confirmed to requester
- Error handling with specific error codes
### Events Emitted:
- player_substituted (broadcast) - Includes: type, lineup IDs, position, batting_order
- substitution_confirmed (requester) - Success confirmation with new_lineup_id
- substitution_error (requester) - Validation error with error code
- lineup_data (requester) - Active lineup response
- error (requester) - Generic error
## Documentation Updates (350 lines)
### backend/app/websocket/CLAUDE.md:
- Complete handler documentation with examples
- Event data structures and response formats
- Error code reference (MISSING_FIELD, INVALID_FORMAT, NOT_CURRENT_BATTER, etc.)
- Client integration examples (JavaScript)
- Complete workflow diagrams
- Updated event summary table (+8 events)
- Updated Common Imports section
### .claude/implementation/ updates:
- NEXT_SESSION.md: Marked Task 1 complete, updated to 80% done
- SUBSTITUTION_SYSTEM_SUMMARY.md: Added WebSocket section, updated status
- GAMESTATE_REFACTOR_PLAN.md: Marked complete
- PHASE_3_OVERVIEW.md: Updated all phases to reflect completion
- phase-3e-COMPLETED.md: Created comprehensive completion doc
## Architecture
### DB-First Pattern (maintained):
```
Client Request → WebSocket Handler
↓
SubstitutionManager
├─ SubstitutionRules.validate_*()
├─ DatabaseOperations.create_substitution() (DB first!)
├─ StateManager.update_lineup_cache()
└─ Update GameState if applicable
↓
Success Responses
├─ player_substituted (broadcast to room)
└─ substitution_confirmed (to requester)
```
### Error Handling:
- Three-tier: ValidationError, GameValidationError, Exception
- Specific error codes for each failure type
- User-friendly error messages
- Comprehensive logging at each step
## Status Update
**Phase 3F Substitution System**: 80% Complete
- ✅ Core logic (SubstitutionRules, SubstitutionManager) - 1,027 lines
- ✅ Database operations (create_substitution, get_eligible_substitutes)
- ✅ WebSocket events (4 handlers) - 600 lines
- ✅ Documentation (350 lines)
- ⏳ Unit tests (20% remaining) - ~300 lines needed
- ⏳ Integration tests - ~400 lines needed
**Phase 3 Overall**: ~97% Complete
- Phase 3A-D (X-Check Core): 100%
- Phase 3E (GameState, Ratings, Redis, Testing): 100%
- Phase 3F (Substitutions): 80%
## Files Modified
backend/app/websocket/handlers.py (+600 lines)
backend/app/websocket/CLAUDE.md (+350 lines)
.claude/implementation/NEXT_SESSION.md (updated progress)
.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md (added WebSocket section)
.claude/implementation/GAMESTATE_REFACTOR_PLAN.md (marked complete)
.claude/implementation/PHASE_3_OVERVIEW.md (updated all phases)
.claude/implementation/phase-3e-COMPLETED.md (new file, 400+ lines)
## Next Steps
Remaining work (2-3 hours):
1. Unit tests for SubstitutionRules (~300 lines)
- 15+ pinch hitter tests
- 12+ defensive replacement tests
- 10+ pitching change tests
2. Integration tests for SubstitutionManager (~400 lines)
- Full DB + state sync flow
- State recovery verification
- Error handling and rollback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
33 KiB
Next Session Plan - Phase 3: Substitution System Completion
Current Status: Phase 3 - ~95% Complete (Only Substitution WebSocket Events Remain)
Last Commit: beb939b - "CLAUDE: Fix all unit test failures and implement 100% test requirement"
Date: 2025-11-04
Remaining Work: 5% (Substitution WebSocket events only)
Quick Start for Next AI Agent
🎯 Where to Begin
- Read this entire document first
- Review
.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.mdfor architecture overview - Review core logic files in "Files to Review Before Starting" section
- Start with Task 1: WebSocket Events (highest user impact)
- Run test commands after each task
📍 Current Context
We have completed Phase 3E (X-Check system) including GameState refactoring, position ratings integration, Redis caching, and comprehensive testing infrastructure. The X-Check system is 100% production-ready with terminal client testing support.
We also completed the core business logic for the substitution system (1,027 lines). The validation rules, database operations, and state management are fully implemented and follow the established DB-first pattern. What remains is integration only (WebSocket events for real-time gameplay).
Phase 3 Overall Progress: ~97% complete
- Phase 3A-D (X-Check Core): ✅ 100%
- Phase 3E-Prep (GameState Refactor): ✅ 100%
- Phase 3E-Main (Position Ratings): ✅ 100%
- Phase 3E-Final (Redis/WebSocket): ✅ 100%
- Phase 3E Testing (Terminal Client): ✅ 100%
- Phase 3F (Substitutions): ✅ 80% (Unit & integration tests remain)
What We Just Completed ✅
0. Substitution System WebSocket Events (2025-11-04) - COMPLETED ✅
Event Handlers Implemented (600+ lines)
request_pinch_hitter- Pinch hitter substitution eventrequest_defensive_replacement- Defensive replacement eventrequest_pitching_change- Pitching change eventget_lineup- Get active lineup for team (UI refresh)
Event Pattern (follows existing handlers):
- Validate inputs (game_id, player IDs, team_id)
- Create SubstitutionManager instance with DatabaseOperations
- Execute substitution (validate → DB → state)
- Broadcast
player_substitutedto all clients in game - Send
substitution_confirmedto requester - Error handling with specific error codes
Files Modified:
backend/app/websocket/handlers.py(+600 lines)- Added 4 event handlers
- Imports: SubstitutionManager, DatabaseOperations
- Error codes: MISSING_FIELD, INVALID_FORMAT, NOT_CURRENT_BATTER, etc.
backend/app/websocket/CLAUDE.md(+350 lines)- Complete handler documentation
- Event data structures
- Client integration examples
- Updated event summary table
Documentation:
- Comprehensive event documentation with examples
- Error code reference
- Complete workflow diagrams
- JavaScript client integration code
Status: Phase 3F Substitutions now 80% complete
- ✅ Core logic (SubstitutionRules, SubstitutionManager)
- ✅ Database operations
- ✅ WebSocket events (NEW)
- ⏳ Unit tests (20% remaining)
- ⏳ Integration tests
- ⏳ API documentation
1. Phase 3E-Final: Redis Caching & X-Check WebSocket Integration (adf7c76)
app/services/redis_client.py- Async Redis client with connection poolingapp/services/position_rating_service.py- Migrated from in-memory to Redis (760x speedup)app/main.py- Redis startup/shutdown lifecycleapp/config.py- Added redis_url settingapp/websocket/handlers.py- Enhanced submit_manual_outcome with X-Check detailsapp/websocket/X_CHECK_FRONTEND_GUIDE.md- 517 lines of frontend integration docsapp/websocket/MANUAL_VS_AUTO_MODE.md- 588 lines of workflow documentationtests/integration/test_xcheck_websocket.py- WebSocket integration tests (2 tests)test_redis_cache.py- Live Redis integration test (10 verification steps)- Performance: 760x speedup (0.274s API → 0.000361s Redis)
- Tests: 2/2 WebSocket tests passing, Redis live test validated
2. Substitution System Core Logic (d1619b4) - Just Completed
SubstitutionRules - Validation Logic (345 lines)
backend/app/core/substitution_rules.py- Complete validation for all substitution types- Methods:
validate_pinch_hitter()- Validates pinch hitter substitutionsvalidate_defensive_replacement()- Validates defensive replacementsvalidate_pitching_change()- Validates pitching changesvalidate_double_switch()- Validates complex double switches (2 simultaneous subs)
- Rules Enforced:
- No re-entry (once removed, cannot return)
- Roster eligibility (must be on roster)
- Active status (substitute must be inactive)
- Current batter check (pinch hitter only for current batter)
- Minimum batters faced (pitcher must face 1 batter unless injury)
- Position validation (valid baseball positions)
- Timing checks (warnings for mid-inning changes)
SubstitutionManager - Orchestration Logic (552 lines)
backend/app/core/substitution_manager.py- Orchestrates DB-first pattern- Methods:
pinch_hit()- Execute pinch hitter substitutiondefensive_replace()- Execute defensive replacementchange_pitcher()- Execute pitching change
- Pattern: Validate (in-memory) → Update DATABASE FIRST → Update state SECOND → Return result
- Features:
- Comprehensive error handling with error codes
- Automatic lineup cache updates
- Current player references updated (batter/pitcher/catcher in GameState)
- Detailed logging at every step
Database Operations (+115 lines)
backend/app/database/operations.py- Added substitution-specific operations- Methods:
create_substitution()- Marks old player inactive, creates new lineup entryget_eligible_substitutes()- Gets inactive players (potential subs)
- Database Fields Used:
is_active: Tracks current vs benchedis_starter: Original vs substituteentered_inning: When player enteredreplacing_id: Links to replaced playerafter_play: Exact play number of substitution
Model Enhancements (+15 lines)
backend/app/models/game_models.py- Added helper methodget_player_by_card_id()in TeamLineupState - Find player by card/player ID
3. Documentation
.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md- Complete architecture and status doc- Detailed implementation notes, integration points, testing strategy
4. Phase 3E-Prep: GameState Refactoring (2025-11-04) - COMPLETED ✅
Critical Architectural Improvement (cf7cc23, 76e0142, bb78de2, e6bd66e)
- Problem: GameState had inconsistent player references (runners were objects, batter/pitcher/catcher were IDs)
- Solution: Refactored all player references to use full
LineupPlayerStateobjects
Files Modified (7 files):
backend/app/models/game_models.py- Changedcurrent_batter_lineup_id→current_batter: LineupPlayerStatebackend/app/core/game_engine.py- Updated_prepare_next_play()to set full objectsbackend/app/core/play_resolver.py- Updated all references to use.lineup_idaccessorbackend/app/core/runner_advancement.py- Fixed 17 references to new structure (cf7cc23)backend/app/core/state_manager.py- Fixed game recovery (e6bd66e)backend/terminal_client/display.py- Updated status displaybackend/terminal_client/repl.py- Updated REPL commands
Benefits Realized:
- ✅ Consistent API for all player references
- ✅ Self-contained GameState (no external lookups needed)
- ✅ Simplified PlayResolver (direct access to player data)
- ✅ Enables position ratings integration (Phase 3E-Main prerequisite)
Bug Fixes Included:
- DO3 (double-3) batter advancement fix (
76e0142) - Batter now correctly reaches 2B, not 3B - Runner advancement for new GameState structure (
cf7cc23) - Fixed AttributeError - Game recovery for new structure (
e6bd66e) - State persistence working
Tests: 34 runner advancement tests passing, all integration tests passing
5. Phase 3E Testing: X-Check Terminal Integration (2025-11-04) - COMPLETED ✅
Terminal Client Enhancements (bb78de2, 8fb740f)
- New Feature:
resolve_with x-check <position>command- Complete X-Check resolution with defense tables and error charts
- Shows all resolution steps with audit trail
- Works with actual player ratings from PD API
X-Check Commands in Help System (8fb740f):
roll_jump/test_jump- Jump roll testingroll_fielding/test_fielding- Fielding roll testingtest_location- Hit location testingrollback- Undo last playforce_wild_pitch/force_passed_ball- Force specific outcomes
Usage Example:
⚾ > defensive
⚾ > offensive
⚾ > resolve_with x-check SS # Test X-Check to shortstop
Documentation Updates (c7b376d):
backend/app/models/CLAUDE.md- Documented GameState refactoringbackend/terminal_client/CLAUDE.md- Documented X-Check testing features
Files Modified (4 files):
backend/app/core/game_engine.py- Added xcheck_position parameterbackend/terminal_client/commands.py- Updated resolve_play() for X-Checkbackend/terminal_client/help_text.py- Added X-Check documentationbackend/terminal_client/repl.py- Added X-Check parsing
6. Test Infrastructure & Quality Assurance (2025-11-04) - COMPLETED ✅
100% Test Requirement Policy (beb939b)
- New Policy: All unit tests must pass before commits
- Documented in
backend/CLAUDE.mdandtests/CLAUDE.md
Git Hook System Created:
.git-hooks/pre-commit- Automatically runs all unit tests before each commit.git-hooks/install-hooks.sh- Easy installation script.git-hooks/README.md- Complete hook documentation
Test Fixes (beb939b):
- Fixed DiceSystem API to accept team_id/player_id parameters
- Fixed dice roll history timing issue
- Fixed terminal client mock for X-Check parameters
- Fixed result chart test mocks with missing pitching fields
- Fixed flaky test (groundball_a exists in both batting/pitching)
Test Status:
- Total Tests: 679 tests (up from 609)
- Unit Tests: 609/609 passing (100%)
- Integration Tests: Known asyncpg connection issues (documented)
Key Architecture Decisions Made
1. DB-First Pattern for Substitutions
Following established game engine pattern:
1. Validate (in-memory, fast)
2. Update DATABASE (source of truth)
3. Update STATE (cache)
4. Broadcast (WebSocket in handler)
Rationale: Database survives server restarts; in-memory state is just cache
2. Immutable Lineup History
- Never delete lineup entries
- Use
is_activeflag for current lineup replacing_idcreates complete audit trail
Benefits: Can reconstruct game at any point; rollback support; complete substitution history
3. Position Flexibility (MVP)
- Don't enforce strict position eligibility in MVP
- Any player can play any position
- Can add validation post-MVP
Rationale: Simplifies MVP; users know their rosters; reduces complexity
4. Batting Order Inheritance
- Pinch hitter takes batting order of replaced player
- Defensive replacement keeps batting order if in lineup
- Double switch allows batting order changes
Rationale: Matches real baseball rules
5. Error Codes for All Validation Failures
Every validation failure returns both human-readable message and machine-readable code:
NOT_CURRENT_BATTER,PLAYER_ALREADY_OUT,NOT_IN_ROSTER,ALREADY_ACTIVE, etc.
Rationale: Frontend can show appropriate UI based on error type
Blockers Encountered 🚧
None - development proceeded smoothly.
Core logic implementation was straightforward following established patterns. All validation rules are clear and testable. Database operations follow proven async session pattern.
Outstanding Questions ❓
1. Should we implement double switch in MVP?
Context: Double switch is complex (2 simultaneous subs with batting order swap). Core logic exists but no WebSocket events yet. Decision Needed: MVP or post-MVP? Recommendation: Post-MVP - rarely used, complex UX, single subs cover 95% of use cases
2. Position eligibility validation - when to add?
Context: Currently any player can play any position (MVP simplification). Decision Needed: Add position eligibility check post-MVP? Implication: Would require position lists on player models, more complex validation
3. Automatic pitcher fatigue system - scope?
Context: Database has is_fatigued flag on Lineup model.
Decision Needed: Implement automatic fatigue detection or manual only?
Recommendation: Manual only for MVP - managers know their staff better than algorithm
Tasks for Next Session
Task 1: WebSocket Substitution Events - ✅ COMPLETED (2025-11-04)
File(s): backend/app/websocket/handlers.py
Goal: Add WebSocket event handlers for pinch hitting, defensive replacements, and pitching changes. Enable real-time substitution notifications to all game clients.
Changes:
- Import SubstitutionManager at top of file:
from app.core.substitution_manager import SubstitutionManager
- Initialize in GameEngine (if not already there):
# In GameEngine.__init__()
self.substitution_manager = SubstitutionManager(self.db_ops)
- Add event handler for pinch hitter:
@sio.event
async def request_pinch_hitter(sid, data):
"""
Request pinch hitter substitution.
Data:
- game_id: UUID
- player_out_lineup_id: int
- player_in_card_id: int
- team_id: int
"""
try:
game_id = UUID(data['game_id'])
# Verify user has access to game and team
# TODO: Add authorization check
result = await game_engine.substitution_manager.pinch_hit(
game_id=game_id,
player_out_lineup_id=data['player_out_lineup_id'],
player_in_card_id=data['player_in_card_id'],
team_id=data['team_id']
)
if result.success:
# Broadcast to all clients in game
await manager.emit_to_game(
game_id,
'player_substituted',
{
'type': 'pinch_hitter',
'player_out_lineup_id': result.player_out_lineup_id,
'player_in_card_id': result.player_in_card_id,
'new_lineup_id': result.new_lineup_id,
'position': result.new_position,
'batting_order': result.new_batting_order,
'team_id': data['team_id']
}
)
# Send success to requester
await sio.emit('substitution_confirmed', {
'new_lineup_id': result.new_lineup_id
}, room=sid)
else:
# Send error to requester
await sio.emit('substitution_error', {
'error': result.error_message,
'code': result.error_code
}, room=sid)
except Exception as e:
logger.error(f"Error in pinch hitter request: {e}", exc_info=True)
await sio.emit('error', {'message': str(e)}, room=sid)
-
Add similar handlers for:
request_defensive_replacement(similar structure)request_pitching_change(similar structure)
-
Add get_lineup event for UI to request current lineup:
@sio.event
async def get_lineup(sid, data):
"""Get current active lineup for a team."""
try:
game_id = UUID(data['game_id'])
team_id = data['team_id']
lineup = state_manager.get_lineup(game_id, team_id)
if lineup:
await sio.emit('lineup_data', {
'team_id': team_id,
'players': [p.model_dump() for p in lineup.players if p.is_active]
}, room=sid)
else:
await sio.emit('error', {
'message': f'Lineup not found for team {team_id}'
}, room=sid)
except Exception as e:
logger.error(f"Error getting lineup: {e}", exc_info=True)
await sio.emit('error', {'message': str(e)}, room=sid)
Files to Update:
backend/app/websocket/handlers.py- Add 4 event handlers
Test Command:
# Manual test with terminal client
python -m terminal_client
# Then in REPL, test substitution flow
Acceptance Criteria:
request_pinch_hitterevent handler implemented ✅request_defensive_replacementevent handler implemented ✅request_pitching_changeevent handler implemented ✅get_lineupevent handler implemented ✅- Successful substitutions broadcast to all clients ✅
- Errors sent only to requester ✅
- No crashes on invalid data ✅
Completion Notes:
- 600+ lines of event handler code added
- All handlers follow established pattern (validate → execute → broadcast)
- Error codes: MISSING_FIELD, INVALID_FORMAT, NOT_CURRENT_BATTER, etc.
- Documentation: 350+ lines added to WebSocket CLAUDE.md
- Event summary table updated with 8 new events
Task 2: Substitution Validation Tests (2 hours)
File(s): backend/tests/unit/core/test_substitution_rules.py (NEW)
Goal: Comprehensive unit tests for all validation rules in SubstitutionRules class.
Changes:
Create new test file with pytest structure:
"""
Unit tests for SubstitutionRules validation logic.
"""
import pytest
from uuid import uuid4
from app.core.substitution_rules import SubstitutionRules, ValidationResult
from app.models.game_models import GameState, LineupPlayerState, TeamLineupState
@pytest.fixture
def game_state():
"""Create test game state."""
state = GameState(
game_id=uuid4(),
league_id='sba',
home_team_id=1,
away_team_id=2,
current_batter_lineup_id=10
)
return state
@pytest.fixture
def roster():
"""Create test roster."""
players = [
LineupPlayerState(lineup_id=10, card_id=101, position='CF', batting_order=1, is_active=True),
LineupPlayerState(lineup_id=11, card_id=102, position='SS', batting_order=2, is_active=True),
LineupPlayerState(lineup_id=12, card_id=103, position='P', batting_order=9, is_active=True),
# Bench players
LineupPlayerState(lineup_id=20, card_id=201, position='CF', batting_order=None, is_active=False),
LineupPlayerState(lineup_id=21, card_id=202, position='SS', batting_order=None, is_active=False),
]
return TeamLineupState(team_id=1, players=players)
class TestPinchHitterValidation:
def test_success(self, game_state, roster):
"""Test successful pinch hitter validation."""
player_out = roster.get_player_by_lineup_id(10)
result = SubstitutionRules.validate_pinch_hitter(
state=game_state,
player_out=player_out,
player_in_card_id=201,
roster=roster
)
assert result.is_valid
assert result.error_message is None
def test_not_current_batter(self, game_state, roster):
"""Test pinch hit fails when not current batter."""
player_out = roster.get_player_by_lineup_id(11) # Not current batter
result = SubstitutionRules.validate_pinch_hitter(
state=game_state,
player_out=player_out,
player_in_card_id=201,
roster=roster
)
assert not result.is_valid
assert result.error_code == "NOT_CURRENT_BATTER"
def test_substitute_not_in_roster(self, game_state, roster):
"""Test pinch hit fails when substitute not in roster."""
player_out = roster.get_player_by_lineup_id(10)
result = SubstitutionRules.validate_pinch_hitter(
state=game_state,
player_out=player_out,
player_in_card_id=999, # Not in roster
roster=roster
)
assert not result.is_valid
assert result.error_code == "NOT_IN_ROSTER"
def test_substitute_already_active(self, game_state, roster):
"""Test pinch hit fails when substitute already in game."""
player_out = roster.get_player_by_lineup_id(10)
result = SubstitutionRules.validate_pinch_hitter(
state=game_state,
player_out=player_out,
player_in_card_id=102, # Already active
roster=roster
)
assert not result.is_valid
assert result.error_code == "ALREADY_ACTIVE"
class TestDefensiveReplacementValidation:
# Similar structure, test all validation paths
pass
class TestPitchingChangeValidation:
# Similar structure, test minimum batters faced, etc.
pass
Files to Create:
backend/tests/unit/core/test_substitution_rules.py(new file, ~300 lines)
Test Command:
cd backend
pytest tests/unit/core/test_substitution_rules.py -v
Acceptance Criteria:
- 15+ tests for pinch hitter validation (all validation paths)
- 12+ tests for defensive replacement validation
- 10+ tests for pitching change validation
- 5+ tests for double switch validation (optional - can defer)
- All tests passing
- Edge cases covered (player already out, not in roster, already active, etc.)
Task 3: Substitution Manager Integration Tests (2 hours)
File(s): backend/tests/integration/test_substitution_manager.py (NEW)
Goal: Integration tests for SubstitutionManager that verify full DB + state sync flow.
Changes:
Create new integration test file:
"""
Integration tests for SubstitutionManager.
Tests full flow: validation → DB → state sync
"""
import pytest
from uuid import uuid4
from app.core.substitution_manager import SubstitutionManager
from app.core.state_manager import state_manager
from app.database.operations import DatabaseOperations
from app.models.game_models import GameState
@pytest.fixture
async def setup_game():
"""Create test game with lineups in database."""
db_ops = DatabaseOperations()
game_id = uuid4()
# Create game in DB
await db_ops.create_game(
game_id=game_id,
league_id='sba',
home_team_id=1,
away_team_id=2,
game_mode='friendly',
visibility='public'
)
# Create lineup entries in DB
lineup_ids = {}
for i in range(1, 10):
lineup_id = await db_ops.add_sba_lineup_player(
game_id=game_id,
team_id=1,
player_id=100 + i,
position='P' if i == 1 else f'{i}B',
batting_order=i,
is_starter=True
)
lineup_ids[i] = lineup_id
# Add bench players
bench_ids = {}
for i in range(1, 4):
bench_id = await db_ops.add_sba_lineup_player(
game_id=game_id,
team_id=1,
player_id=200 + i,
position='OF',
batting_order=None,
is_starter=False
)
bench_ids[i] = bench_id
# Create game state
state = await state_manager.create_game(
game_id=game_id,
league_id='sba',
home_team_id=1,
away_team_id=2
)
state.current_batter_lineup_id = lineup_ids[1]
state_manager.update_state(game_id, state)
yield game_id, lineup_ids, bench_ids
# Cleanup
state_manager.remove_game(game_id)
@pytest.mark.asyncio
async def test_pinch_hit_full_flow(setup_game):
"""Test complete pinch hit flow: validation → DB → state."""
game_id, lineup_ids, bench_ids = await setup_game
db_ops = DatabaseOperations()
manager = SubstitutionManager(db_ops)
# Execute substitution
result = await manager.pinch_hit(
game_id=game_id,
player_out_lineup_id=lineup_ids[1],
player_in_card_id=201,
team_id=1
)
# Verify result
assert result.success
assert result.new_lineup_id is not None
assert result.player_out_lineup_id == lineup_ids[1]
# Verify database updated
lineup = await db_ops.get_active_lineup(game_id, team_id=1)
active_ids = [p.id for p in lineup]
assert result.new_lineup_id in active_ids
assert lineup_ids[1] not in active_ids
# Verify state updated
state = state_manager.get_state(game_id)
assert state.current_batter_lineup_id == result.new_lineup_id
# Verify lineup cache updated
roster = state_manager.get_lineup(game_id, team_id=1)
old_player = roster.get_player_by_lineup_id(lineup_ids[1])
assert not old_player.is_active
new_player = roster.get_player_by_lineup_id(result.new_lineup_id)
assert new_player.is_active
assert new_player.card_id == 201
# Add similar tests for defensive_replace and change_pitcher
Files to Create:
backend/tests/integration/test_substitution_manager.py(new file, ~400 lines)
Test Command:
cd backend
pytest tests/integration/test_substitution_manager.py -v
Acceptance Criteria:
- Test pinch_hit full flow (DB + state sync verified)
- Test defensive_replace full flow
- Test change_pitcher full flow
- Test validation failures (error handling)
- Test state recovery after substitution
- All tests passing
Task 4: Documentation - API Reference (1 hour)
File(s): .claude/implementation/SUBSTITUTION_API_REFERENCE.md (NEW)
Goal: Create comprehensive API documentation for substitution system usage.
Changes:
Create new documentation file with:
-
WebSocket Events Reference:
- Event names
- Request data format
- Response data format
- Error codes
- Example payloads
-
Python API Reference:
- SubstitutionManager methods
- SubstitutionRules methods
- Database operations
- Example code snippets
-
Workflows:
- Pinch hitter flow diagram
- Defensive replacement flow
- Pitching change flow
- Error handling patterns
-
Integration Guide:
- How to integrate with GameEngine
- How to handle substitutions in UI
- State management considerations
Files to Create:
.claude/implementation/SUBSTITUTION_API_REFERENCE.md(~300 lines)
Acceptance Criteria:
- All WebSocket events documented with examples
- All Python APIs documented
- Error codes reference table included
- Flow diagrams for each substitution type
- Integration examples provided
Files to Review Before Starting
Critical files (understand before coding):
-
backend/app/core/substitution_rules.py- All validation logic- Understand each validation method and error codes
-
backend/app/core/substitution_manager.py- Orchestration logic- Understand DB-first pattern implementation
- Note how state is updated after DB
-
backend/app/database/operations.py:290-403- DB operations- Review
create_substitution()method - Note transaction handling
- Review
-
backend/app/models/game_models.py:128-141- Model helper- Review
get_player_by_card_id()method
- Review
-
backend/app/websocket/handlers.py- Existing WebSocket patterns- Understand error handling pattern
- Note broadcast vs. emit_to_user patterns
-
.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md- Architecture overview- Complete context on design decisions
-
backend/app/core/game_engine.py:33-42- GameEngine initialization- See where SubstitutionManager will be integrated
Verification Steps
After completing all tasks:
1. Run all tests:
cd backend
source venv/bin/activate
# Unit tests
pytest tests/unit/core/test_substitution_rules.py -v
# Integration tests
pytest tests/integration/test_substitution_manager.py -v
# All tests
pytest -v
2. Manual testing with terminal client:
python -m terminal_client
# In REPL:
⚾ > new_game
⚾ > status # Note current batter lineup_id
⚾ > # Manual WebSocket test would go here (needs WebSocket client)
3. Verify database state:
-- Check lineup entries
SELECT id, card_id, position, batting_order, is_active, is_starter, entered_inning, replacing_id
FROM lineups
WHERE game_id = '<test_game_id>'
ORDER BY id;
-- Verify inactive players
SELECT * FROM lineups WHERE is_active = false;
4. Commit changes:
git add backend/app/websocket/handlers.py
git add backend/tests/unit/core/test_substitution_rules.py
git add backend/tests/integration/test_substitution_manager.py
git add .claude/implementation/SUBSTITUTION_API_REFERENCE.md
git commit -m "CLAUDE: Phase 3 - Complete Substitution System with WebSocket & Tests
- Add WebSocket event handlers for substitutions
- Comprehensive unit tests for validation rules (37+ tests)
- Integration tests for full DB + state flow (8+ tests)
- Complete API documentation with examples
Substitution system now 100% complete and production-ready.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>"
Success Criteria
Phase 3 Substitution System will be 100% complete when:
- 4 WebSocket event handlers implemented and tested
- 37+ unit tests passing for SubstitutionRules
- 8+ integration tests passing for SubstitutionManager
- API documentation complete with examples
- Manual testing successful via terminal client
- Database verification confirms audit trail working
- Git commit created
Overall Phase 3 Progress will be:
- Phase 3A-D (X-Check Core): 100% complete ✅
- Phase 3E-Prep (GameState Refactor): 100% complete ✅
- Phase 3E-Main (Position Ratings): 100% complete ✅
- Phase 3E-Final (Redis/WebSocket): 100% complete ✅
- Phase 3E Testing (Terminal Client): 100% complete ✅
- Phase 3F (Substitutions): 100% complete ✅ (after this session)
- Phase 3 Overall: ~99% complete (only minor TODOs deferred to Phase 4+)
Quick Reference
Current Test Count: 679 tests (609 unit tests passing 100%, 70 integration tests)
Last Test Run: 609/609 unit tests passing (2025-11-04)
Branch: implement-phase-3
Python: 3.13.3
Package Manager: UV (migrated from pip)
Virtual Env: Managed by UV
Key Imports for Next Session:
# WebSocket integration
from app.core.substitution_manager import SubstitutionManager
from app.core.state_manager import state_manager
from uuid import UUID
# Testing
import pytest
from app.core.substitution_rules import SubstitutionRules, ValidationResult
from app.models.game_models import GameState, LineupPlayerState, TeamLineupState
Recent Commit History (Last 10):
beb939b - CLAUDE: Fix all unit test failures and implement 100% test requirement (2025-11-04)
c7b376d - CLAUDE: Update documentation for GameState refactoring and X-Check testing (2025-11-04)
76e0142 - CLAUDE: Fix DO3 (double-3) batter advancement (2025-11-04)
cf7cc23 - CLAUDE: Fix runner_advancement for new GameState structure (2025-11-04)
bb78de2 - CLAUDE: Add X-Check testing to resolve_with command (2025-11-04)
8fb740f - CLAUDE: Add X-Check commands to terminal client help system (2025-11-04)
e6bd66e - CLAUDE: Fix game recovery for new GameState structure (2025-11-04)
440adf2 - CLAUDE: Update REPL for new GameState and standardize UV commands (2025-11-04)
7de70b3 - Merge pull request #4 from calcorum/phase-3-uv-migration (2025-11-03)
4a7c9f7 - CLAUDE: Update terminal client documentation for UV (2025-11-03)
Context for AI Agent Resume
If the next agent needs to understand the bigger picture:
- Overall project: See
@prd-web-scorecard-1.1.mdand@CLAUDE.md - Architecture: See
@.claude/implementation/00-index.md - Current phase details: See
@.claude/implementation/03-gameplay-features.md - Substitution system: See
@.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md
Critical files in current focus area:
backend/app/core/substitution_rules.py- All validation logic (345 lines)backend/app/core/substitution_manager.py- Orchestration (552 lines)backend/app/database/operations.py- DB operations (with substitution methods)backend/app/websocket/handlers.py- Will add event handlers herebackend/app/core/game_engine.py- GameEngine classbackend/app/core/state_manager.py- StateManager singletonbackend/app/models/game_models.py- Game state modelsbackend/app/models/db_models.py- Lineup model with substitution fields.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md- Architecture docbackend/tests/unit/core/- Where to add unit tests
What NOT to do:
- Don't modify core validation logic - it's complete and follows baseball rules correctly
- Don't change DB-first pattern - it's critical for data integrity
- Don't add position eligibility validation yet - deferred to post-MVP
- Don't implement double switch WebSocket events - deferred to post-MVP
- Don't change database schema - all fields needed are already there
Patterns we've established:
- DB-first: validate → DB → state → broadcast
- Error codes for all validation failures
- Comprehensive logging at every step
- Immutable lineup history (never delete, use is_active flag)
- Batting order inheritance for substitutions
Estimated Time for Next Session: 6-7 hours (2-3 WebSocket + 2 unit tests + 2 integration + 1 docs) Priority: Medium (completes substitution system, not blocking other work) Blocking Other Work: No (X-Check and core game engine are complete) Next Milestone After This: Phase 4 - Spectator Mode & Polish OR Phase 3 - AI Opponent (choose based on priority)