CLAUDE: Update project plan for next session - Substitution system completion
Generated comprehensive NEXT_SESSION.md with: - Complete analysis of Phase 3E-Final and Substitution core logic - 4 detailed tasks with code examples and acceptance criteria - Outstanding questions and architecture decisions documented - 6-7 hour estimate to complete remaining 40% Next session will complete: - WebSocket substitution events (2-3 hrs) - Validation unit tests (2 hrs) - Manager integration tests (2 hrs) - API documentation (1 hr) Phase 3 will be ~99% complete after next session. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d1619b4a1f
commit
5ebbd9ebda
@ -1,226 +1,798 @@
|
||||
# Next Session - Phase 3E: X-Check Position Ratings
|
||||
# Next Session Plan - Phase 3: Substitution System Completion
|
||||
|
||||
## Current Status: Phase 3E-Main Complete ✅
|
||||
|
||||
**Overall Phase 3E Progress: 90%**
|
||||
|
||||
### ✅ Phase 3E-Main: Position Ratings Integration (COMPLETED)
|
||||
|
||||
**Completion Date**: 2025-11-03
|
||||
**Commit**: `02e816a` - CLAUDE: Phase 3E-Main - Position ratings integration for X-Check resolution
|
||||
|
||||
#### What Was Accomplished
|
||||
|
||||
1. **PD API Client Integration**
|
||||
- Created `app/services/pd_api_client.py` - async HTTP client using httpx
|
||||
- Endpoint: `GET https://pd.manticorum.com/api/v2/cardpositions?player_id={id}`
|
||||
- Supports position filtering via query parameters
|
||||
- Tested with real API data (player 8807 - 7 positions)
|
||||
|
||||
2. **Position Rating Service with Caching**
|
||||
- Created `app/services/position_rating_service.py`
|
||||
- In-memory caching (16,601x speedup: 0.214s API → 0.000s cache)
|
||||
- League-aware: PD fetches ratings, SBA returns empty list
|
||||
- Graceful degradation on API errors
|
||||
|
||||
3. **GameState Integration**
|
||||
- Added `position_rating` field to `LineupPlayerState`
|
||||
- Added `get_defender_for_position()` using StateManager lineup cache
|
||||
- Self-contained X-Check data - no lookups during resolution
|
||||
|
||||
4. **League Config Pattern**
|
||||
- Added `supports_position_ratings()` to both league configs
|
||||
- PD: Returns True (uses API)
|
||||
- SBA: Returns False (uses defaults)
|
||||
|
||||
5. **PlayResolver Updates**
|
||||
- Integrated StateManager for O(1) defender lookups
|
||||
- Uses actual position ratings when available
|
||||
- Falls back to defaults (range=3, error=15) for SBA or missing data
|
||||
|
||||
6. **Game Engine Updates**
|
||||
- Added `_load_position_ratings_for_lineup()` method
|
||||
- Loads ratings at game start for both teams (PD league only)
|
||||
- Updated PlayResolver instantiation with state_manager
|
||||
|
||||
7. **Comprehensive Testing**
|
||||
- Live API integration test (`test_pd_api_live.py`)
|
||||
- Mock API test for CI/CD (`test_pd_api_mock.py`)
|
||||
- Pytest integration suite (`tests/integration/test_position_ratings_api.py`)
|
||||
- Verified full flow: API → Cache → GameState → X-Check resolution
|
||||
|
||||
#### Live Test Results
|
||||
|
||||
**Player 8807** (7 positions verified):
|
||||
```
|
||||
Position Range Error Arm Innings
|
||||
CF 3 2 3 372
|
||||
2B 3 8 3 212
|
||||
SS 4 12 4 159
|
||||
RF 2 2 2 74
|
||||
LF 3 2 3 62
|
||||
1B 4 0 3 46
|
||||
3B 3 65 2 34
|
||||
```
|
||||
|
||||
**Performance Metrics**:
|
||||
- API call: 0.214s
|
||||
- Cache hit: 0.000013s
|
||||
- Speedup: 16,601x
|
||||
**Current Status**: Phase 3 - 60% Substitution System Complete
|
||||
**Last Commit**: `d1619b4` - "CLAUDE: Phase 3 - Substitution System Core Logic"
|
||||
**Date**: 2025-11-03
|
||||
**Remaining Work**: 40% (WebSocket events, tests, documentation)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Phase 3E-Final: Remaining Work (10%)
|
||||
## Quick Start for Next AI Agent
|
||||
|
||||
### Tasks to Complete
|
||||
### 🎯 Where to Begin
|
||||
1. Read this entire document first
|
||||
2. Review `.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md` for architecture overview
|
||||
3. Review core logic files in "Files to Review Before Starting" section
|
||||
4. Start with Task 1: WebSocket Events (highest user impact)
|
||||
5. Run test commands after each task
|
||||
|
||||
#### 1. WebSocket Event Handlers for X-Check UI
|
||||
**Priority**: HIGH
|
||||
**Estimate**: 2-3 hours
|
||||
### 📍 Current Context
|
||||
|
||||
- [ ] Create `handle_xcheck_confirm` event handler
|
||||
- [ ] Emit `xcheck_result` events with defender ratings
|
||||
- [ ] Update frontend WebSocket listeners
|
||||
- [ ] Test real-time X-Check flow end-to-end
|
||||
We just 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** (WebSocket events for real-time gameplay) and **verification** (comprehensive testing).
|
||||
|
||||
**Files to Modify**:
|
||||
- `app/websocket/game_events.py`
|
||||
- Frontend Socket.io listeners (both leagues)
|
||||
Phase 3E (X-Check system with position ratings and Redis caching) is **100% complete**. Phase 3 overall is at ~98% for X-Check work, with substitutions now being the active focus.
|
||||
|
||||
#### 2. Upgrade to Redis Caching
|
||||
**Priority**: MEDIUM
|
||||
**Estimate**: 3-4 hours
|
||||
---
|
||||
|
||||
- [ ] Add redis-py to requirements.txt
|
||||
- [ ] Create Redis connection pool in app startup
|
||||
- [ ] Migrate `position_rating_service.py` from in-memory to Redis
|
||||
- [ ] Set TTL on cached ratings (e.g., 24 hours)
|
||||
- [ ] Add Redis cache warming on game start
|
||||
- [ ] Test cache invalidation and recovery
|
||||
## What We Just Completed ✅
|
||||
|
||||
**Files to Modify**:
|
||||
- `requirements.txt`
|
||||
- `app/main.py` (startup/shutdown events)
|
||||
- `app/services/position_rating_service.py`
|
||||
### 1. Phase 3E-Final: Redis Caching & X-Check WebSocket Integration (adf7c76)
|
||||
- `app/services/redis_client.py` - Async Redis client with connection pooling
|
||||
- `app/services/position_rating_service.py` - Migrated from in-memory to Redis (760x speedup)
|
||||
- `app/main.py` - Redis startup/shutdown lifecycle
|
||||
- `app/config.py` - Added redis_url setting
|
||||
- `app/websocket/handlers.py` - Enhanced submit_manual_outcome with X-Check details
|
||||
- `app/websocket/X_CHECK_FRONTEND_GUIDE.md` - 517 lines of frontend integration docs
|
||||
- `app/websocket/MANUAL_VS_AUTO_MODE.md` - 588 lines of workflow documentation
|
||||
- `tests/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
|
||||
|
||||
**Technical Notes**:
|
||||
### 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 substitutions
|
||||
- `validate_defensive_replacement()` - Validates defensive replacements
|
||||
- `validate_pitching_change()` - Validates pitching changes
|
||||
- `validate_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 substitution
|
||||
- `defensive_replace()` - Execute defensive replacement
|
||||
- `change_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 entry
|
||||
- `get_eligible_substitutes()` - Gets inactive players (potential subs)
|
||||
- **Database Fields Used**:
|
||||
- `is_active`: Tracks current vs benched
|
||||
- `is_starter`: Original vs substitute
|
||||
- `entered_inning`: When player entered
|
||||
- `replacing_id`: Links to replaced player
|
||||
- `after_play`: Exact play number of substitution
|
||||
|
||||
#### Model Enhancements (+15 lines)
|
||||
- `backend/app/models/game_models.py` - Added helper method
|
||||
- `get_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
|
||||
|
||||
---
|
||||
|
||||
## 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_active` flag for current lineup
|
||||
- `replacing_id` creates 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 (2-3 hours)
|
||||
|
||||
**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**:
|
||||
|
||||
1. **Import SubstitutionManager** at top of file:
|
||||
```python
|
||||
# Redis key pattern: "position_ratings:{card_id}:{position}"
|
||||
# TTL: 86400 seconds (24 hours)
|
||||
# Store as JSON serialized PositionRating.model_dump()
|
||||
from app.core.substitution_manager import SubstitutionManager
|
||||
```
|
||||
|
||||
#### 3. Full Defensive Lineup in GameState
|
||||
**Priority**: LOW
|
||||
**Estimate**: 1-2 hours
|
||||
2. **Initialize in GameEngine** (if not already there):
|
||||
```python
|
||||
# In GameEngine.__init__()
|
||||
self.substitution_manager = SubstitutionManager(self.db_ops)
|
||||
```
|
||||
|
||||
Currently only `current_pitcher` and `current_catcher` are direct fields.
|
||||
Consider adding full defensive positions for easier access:
|
||||
3. **Add event handler for pinch hitter**:
|
||||
```python
|
||||
@sio.event
|
||||
async def request_pinch_hitter(sid, data):
|
||||
"""
|
||||
Request pinch hitter substitution.
|
||||
|
||||
- [ ] Add optional `defensive_positions: Dict[str, int]` to GameState
|
||||
- [ ] Map position → lineup_id for all 9 fielders
|
||||
- [ ] Update on lineup changes and substitutions
|
||||
- [ ] Evaluate if this improves performance vs current StateManager lookup
|
||||
Data:
|
||||
- game_id: UUID
|
||||
- player_out_lineup_id: int
|
||||
- player_in_card_id: int
|
||||
- team_id: int
|
||||
"""
|
||||
try:
|
||||
game_id = UUID(data['game_id'])
|
||||
|
||||
**Evaluation Needed**: May not be necessary since StateManager lookup is already O(1).
|
||||
# Verify user has access to game and team
|
||||
# TODO: Add authorization check
|
||||
|
||||
#### 4. Manual vs Auto Mode X-Check Workflows
|
||||
**Priority**: MEDIUM
|
||||
**Estimate**: 2 hours
|
||||
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']
|
||||
)
|
||||
|
||||
- [ ] Document manual mode flow (player confirms chart reads)
|
||||
- [ ] Document auto mode flow (immediate resolution)
|
||||
- [ ] Ensure UI shows defender ratings in both modes
|
||||
- [ ] Add confirmation step in manual mode before advancing
|
||||
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)
|
||||
```
|
||||
|
||||
4. **Add similar handlers for**:
|
||||
- `request_defensive_replacement` (similar structure)
|
||||
- `request_pitching_change` (similar structure)
|
||||
|
||||
5. **Add get_lineup event** for UI to request current lineup:
|
||||
```python
|
||||
@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**:
|
||||
```bash
|
||||
# Manual test with terminal client
|
||||
python -m terminal_client
|
||||
# Then in REPL, test substitution flow
|
||||
```
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `request_pinch_hitter` event handler implemented
|
||||
- [ ] `request_defensive_replacement` event handler implemented
|
||||
- [ ] `request_pitching_change` event handler implemented
|
||||
- [ ] `get_lineup` event handler implemented
|
||||
- [ ] Successful substitutions broadcast to all clients
|
||||
- [ ] Errors sent only to requester
|
||||
- [ ] No crashes on invalid data
|
||||
|
||||
---
|
||||
|
||||
## 📊 Phase 3 Overall Progress
|
||||
### Task 2: Substitution Validation Tests (2 hours)
|
||||
|
||||
| Phase | Status | Progress |
|
||||
|-------|--------|----------|
|
||||
| 3A: Core Models | ✅ Complete | 100% |
|
||||
| 3B: Play Validation | ✅ Complete | 100% |
|
||||
| 3C: Result Charts | ✅ Complete | 100% |
|
||||
| 3D: Dice & Resolution | ✅ Complete | 100% |
|
||||
| 3E-Prep: Refactoring | ✅ Complete | 100% |
|
||||
| **3E-Main: Position Ratings** | ✅ Complete | **100%** |
|
||||
| **3E-Final: UI & Redis** | ⏳ In Progress | **0%** |
|
||||
| **Overall Phase 3** | ⏳ In Progress | **~95%** |
|
||||
**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:
|
||||
|
||||
```python
|
||||
"""
|
||||
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**:
|
||||
```bash
|
||||
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.)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start for Next Session
|
||||
### Task 3: Substitution Manager Integration Tests (2 hours)
|
||||
|
||||
### To continue Phase 3E-Final:
|
||||
**File(s)**: `backend/tests/integration/test_substitution_manager.py` (NEW)
|
||||
|
||||
1. **Start with WebSocket handlers** (highest user impact):
|
||||
```bash
|
||||
cd /mnt/NV2/Development/strat-gameplay-webapp/backend
|
||||
source venv/bin/activate
|
||||
```
|
||||
**Goal**: Integration tests for SubstitutionManager that verify full DB + state sync flow.
|
||||
|
||||
2. **Test current implementation**:
|
||||
```bash
|
||||
export PYTHONPATH=.
|
||||
python test_pd_api_live.py # Verify API still works
|
||||
```
|
||||
**Changes**:
|
||||
|
||||
3. **Add Redis** (requires Redis server running):
|
||||
```bash
|
||||
# Install Redis if needed
|
||||
sudo dnf install redis # or brew install redis
|
||||
Create new integration test file:
|
||||
|
||||
# Start Redis
|
||||
sudo systemctl start redis
|
||||
```python
|
||||
"""
|
||||
Integration tests for SubstitutionManager.
|
||||
|
||||
# Add to requirements
|
||||
echo "redis>=5.0.0" >> requirements.txt
|
||||
pip install redis
|
||||
```
|
||||
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
|
||||
|
||||
4. **Run existing tests**:
|
||||
```bash
|
||||
pytest tests/integration/test_position_ratings_api.py -v
|
||||
```
|
||||
@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**:
|
||||
```bash
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## 📝 Important Notes
|
||||
### Task 4: Documentation - API Reference (1 hour)
|
||||
|
||||
### Architecture Decisions
|
||||
- **In-memory cache is temporary**: Intentional technical debt, Redis upgrade planned
|
||||
- **StateManager pattern**: O(1) defender lookups, no DB queries during play resolution
|
||||
- **League-agnostic design**: Config-driven behavior, easy to extend to new leagues
|
||||
- **Graceful degradation**: Always works even if API/cache is down
|
||||
**File(s)**: `.claude/implementation/SUBSTITUTION_API_REFERENCE.md` (NEW)
|
||||
|
||||
### API Details
|
||||
- **Base URL**: `https://pd.manticorum.com`
|
||||
- **Endpoint**: `GET /api/v2/cardpositions?player_id={id}&position={pos}`
|
||||
- **Rate Limiting**: Unknown - caching mitigates this risk
|
||||
- **Error Handling**: Returns empty list on API errors, uses defaults
|
||||
**Goal**: Create comprehensive API documentation for substitution system usage.
|
||||
|
||||
### Testing Strategy
|
||||
- **Live tests**: Use player 8807 (7 positions) for comprehensive testing
|
||||
- **Mock tests**: For CI/CD without API dependency
|
||||
- **Integration tests**: Full flow from API → GameState → X-Check
|
||||
**Changes**:
|
||||
|
||||
### Performance
|
||||
- Cache is critical: 16,000x+ speedup
|
||||
- Load ratings at game start (not during play resolution)
|
||||
- Redis will enable cross-instance sharing and persistence
|
||||
Create new documentation file with:
|
||||
|
||||
1. **WebSocket Events Reference**:
|
||||
- Event names
|
||||
- Request data format
|
||||
- Response data format
|
||||
- Error codes
|
||||
- Example payloads
|
||||
|
||||
2. **Python API Reference**:
|
||||
- SubstitutionManager methods
|
||||
- SubstitutionRules methods
|
||||
- Database operations
|
||||
- Example code snippets
|
||||
|
||||
3. **Workflows**:
|
||||
- Pinch hitter flow diagram
|
||||
- Defensive replacement flow
|
||||
- Pitching change flow
|
||||
- Error handling patterns
|
||||
|
||||
4. **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
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
## Files to Review Before Starting
|
||||
|
||||
- **Full implementation details**: `backend/CLAUDE.md` lines 2132-2264
|
||||
- **Phase 3E-Main commit**: `02e816a`
|
||||
- **PD API client**: `app/services/pd_api_client.py`
|
||||
- **Position rating service**: `app/services/position_rating_service.py`
|
||||
- **Live integration test**: `test_pd_api_live.py`
|
||||
**Critical files** (understand before coding):
|
||||
|
||||
1. **`backend/app/core/substitution_rules.py`** - All validation logic
|
||||
- Understand each validation method and error codes
|
||||
|
||||
2. **`backend/app/core/substitution_manager.py`** - Orchestration logic
|
||||
- Understand DB-first pattern implementation
|
||||
- Note how state is updated after DB
|
||||
|
||||
3. **`backend/app/database/operations.py:290-403`** - DB operations
|
||||
- Review `create_substitution()` method
|
||||
- Note transaction handling
|
||||
|
||||
4. **`backend/app/models/game_models.py:128-141`** - Model helper
|
||||
- Review `get_player_by_card_id()` method
|
||||
|
||||
5. **`backend/app/websocket/handlers.py`** - Existing WebSocket patterns
|
||||
- Understand error handling pattern
|
||||
- Note broadcast vs. emit_to_user patterns
|
||||
|
||||
6. **`.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md`** - Architecture overview
|
||||
- Complete context on design decisions
|
||||
|
||||
7. **`backend/app/core/game_engine.py:33-42`** - GameEngine initialization
|
||||
- See where SubstitutionManager will be integrated
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-11-03
|
||||
**Next Session Focus**: WebSocket handlers + Redis caching (Phase 3E-Final)
|
||||
## Verification Steps
|
||||
|
||||
After completing all tasks:
|
||||
|
||||
### 1. Run all tests:
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
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:
|
||||
```sql
|
||||
-- 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:
|
||||
```bash
|
||||
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 3E (X-Check): 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**: 327 tests (base), +45 expected after this session
|
||||
**Last Test Run**: All passing (2025-11-03)
|
||||
**Branch**: `implement-phase-3`
|
||||
**Python**: 3.13.3
|
||||
**Virtual Env**: `backend/venv/`
|
||||
|
||||
**Key Imports for Next Session**:
|
||||
```python
|
||||
# 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):
|
||||
```
|
||||
d1619b4 - CLAUDE: Phase 3 - Substitution System Core Logic (2 minutes ago)
|
||||
adf7c76 - CLAUDE: Phase 3E-Final - Redis Caching & X-Check WebSocket Integration (66 minutes ago)
|
||||
7d15018 - CLAUDE: Update documentation for Phase 3E-Main completion (2 hours ago)
|
||||
02e816a - CLAUDE: Phase 3E-Main - Position Ratings Integration for X-Check Resolution (3 hours ago)
|
||||
a55b31d - CLAUDE: Update documentation for Phase 3E-Prep completion (10 hours ago)
|
||||
d560844 - CLAUDE: Phase 3E-Prep - Refactor GameState to use full LineupPlayerState objects (10 hours ago)
|
||||
7417a3f - Offline catchup (11 hours ago)
|
||||
683954f - CLAUDE: Update implementation notes to reflect Phase 2 completion (17 hours ago)
|
||||
fc0e2f1 - CLAUDE: Integrate X-Check advancement with full GameState support (24 hours ago)
|
||||
5f42576 - CLAUDE: Remove double-dipping on double play probability (24 hours ago)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Context for AI Agent Resume
|
||||
|
||||
**If the next agent needs to understand the bigger picture**:
|
||||
- Overall project: See `@prd-web-scorecard-1.1.md` and `@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**:
|
||||
1. `backend/app/core/substitution_rules.py` - All validation logic (345 lines)
|
||||
2. `backend/app/core/substitution_manager.py` - Orchestration (552 lines)
|
||||
3. `backend/app/database/operations.py` - DB operations (with substitution methods)
|
||||
4. `backend/app/websocket/handlers.py` - Will add event handlers here
|
||||
5. `backend/app/core/game_engine.py` - GameEngine class
|
||||
6. `backend/app/core/state_manager.py` - StateManager singleton
|
||||
7. `backend/app/models/game_models.py` - Game state models
|
||||
8. `backend/app/models/db_models.py` - Lineup model with substitution fields
|
||||
9. `.claude/implementation/SUBSTITUTION_SYSTEM_SUMMARY.md` - Architecture doc
|
||||
10. `backend/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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user