Completed Phase 3E-Final with Redis caching upgrade and WebSocket X-Check
integration for real-time defensive play resolution.
## Redis Caching System
### New Files
- app/services/redis_client.py - Async Redis client with connection pooling
* 10 connection pool size
* Automatic connect/disconnect lifecycle
* Ping health checks
* Environment-configurable via REDIS_URL
### Modified Files
- app/services/position_rating_service.py - Migrated from in-memory to Redis
* Redis key pattern: "position_ratings:{card_id}"
* TTL: 86400 seconds (24 hours)
* Graceful fallback if Redis unavailable
* Individual and bulk cache clearing (scan_iter)
* 760x performance improvement (0.274s API → 0.000361s Redis)
- app/main.py - Added Redis startup/shutdown events
* Connect on app startup with settings.redis_url
* Disconnect on shutdown
* Warning logged if Redis connection fails
- app/config.py - Added redis_url setting
* Default: "redis://localhost:6379/0"
* Override via REDIS_URL environment variable
- app/services/__init__.py - Export redis_client
### Testing
- test_redis_cache.py - Live integration test
* 10-step validation: connect, cache miss, cache hit, performance, etc.
* Verified 760x speedup with player 8807 (7 positions)
* Data integrity checks pass
## X-Check WebSocket Integration
### Modified Files
- app/websocket/handlers.py - Enhanced submit_manual_outcome handler
* Serialize XCheckResult to JSON when present
* Include x_check_details in play_resolved broadcast
* Fixed bug: Use result.outcome instead of submitted outcome
* Includes defender ratings, dice rolls, resolution steps
### New Files
- app/websocket/X_CHECK_FRONTEND_GUIDE.md - Comprehensive frontend documentation
* Event structure and field definitions
* Implementation examples (basic, enhanced, polished)
* Error handling and common pitfalls
* Test scenarios with expected data
* League differences (SBA vs PD)
* 500+ lines of frontend integration guide
- app/websocket/MANUAL_VS_AUTO_MODE.md - Workflow documentation
* Manual mode: Players read cards, submit outcomes
* Auto mode: System generates from ratings (PD only)
* X-Check resolution comparison
* UI recommendations for each mode
* Configuration reference
* Testing considerations
### Testing
- tests/integration/test_xcheck_websocket.py - WebSocket integration tests
* Test X-Check play includes x_check_details ✅
* Test non-X-Check plays don't include details ✅
* Full event structure validation
## Performance Impact
- Redis caching: 760x speedup for position ratings
- WebSocket: No performance impact (optional field)
- Graceful degradation: System works without Redis
## Phase 3E-Final Progress
- ✅ WebSocket event handlers for X-Check UI
- ✅ Frontend integration documentation
- ✅ Redis caching upgrade (from in-memory)
- ✅ Redis connection pool in app lifecycle
- ✅ Integration tests (2 WebSocket, 1 Redis)
- ✅ Manual vs Auto mode workflow documentation
Phase 3E-Final: 100% Complete
Phase 3 Overall: ~98% Complete
## Testing Results
All tests passing:
- X-Check table tests: 36/36 ✅
- WebSocket integration: 2/2 ✅
- Redis live test: 10/10 steps ✅
## Configuration
Development:
REDIS_URL=redis://localhost:6379/0 (Docker Compose)
Production options:
REDIS_URL=redis://10.10.0.42:6379/0 (DB server)
REDIS_URL=redis://your-redis-cloud.com:6379/0 (Managed)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
103 lines
2.6 KiB
Python
103 lines
2.6 KiB
Python
import logging
|
|
from contextlib import asynccontextmanager
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
import socketio
|
|
|
|
from app.config import get_settings
|
|
from app.api.routes import games, auth, health
|
|
from app.websocket.connection_manager import ConnectionManager
|
|
from app.websocket.handlers import register_handlers
|
|
from app.database.session import init_db
|
|
from app.utils.logging import setup_logging
|
|
from app.services import redis_client
|
|
|
|
logger = logging.getLogger(f'{__name__}.main')
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Startup and shutdown events"""
|
|
settings = get_settings()
|
|
|
|
# Startup
|
|
logger.info("Starting Paper Dynasty Game Backend")
|
|
setup_logging()
|
|
|
|
# Initialize database
|
|
await init_db()
|
|
logger.info("Database initialized")
|
|
|
|
# Initialize Redis
|
|
try:
|
|
redis_url = settings.redis_url
|
|
await redis_client.connect(redis_url)
|
|
logger.info(f"Redis initialized: {redis_url}")
|
|
except Exception as e:
|
|
logger.warning(f"Redis connection failed: {e}. Position rating caching will be unavailable.")
|
|
|
|
yield
|
|
|
|
# Shutdown
|
|
logger.info("Shutting down Paper Dynasty Game Backend")
|
|
|
|
# Disconnect Redis
|
|
if redis_client.is_connected:
|
|
await redis_client.disconnect()
|
|
logger.info("Redis disconnected")
|
|
|
|
|
|
# Initialize FastAPI app
|
|
app = FastAPI(
|
|
title="Paper Dynasty Game Backend",
|
|
description="Real-time baseball game engine for Paper Dynasty leagues",
|
|
version="1.0.0",
|
|
lifespan=lifespan
|
|
)
|
|
|
|
# CORS middleware
|
|
settings = get_settings()
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
# Initialize Socket.io
|
|
sio = socketio.AsyncServer(
|
|
async_mode='asgi',
|
|
cors_allowed_origins=settings.cors_origins,
|
|
logger=True,
|
|
engineio_logger=False
|
|
)
|
|
|
|
# Create Socket.io ASGI app
|
|
socket_app = socketio.ASGIApp(sio, app)
|
|
|
|
# Initialize connection manager and register handlers
|
|
connection_manager = ConnectionManager(sio)
|
|
register_handlers(sio, connection_manager)
|
|
|
|
# Include API routes
|
|
app.include_router(health.router, prefix="/api", tags=["health"])
|
|
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
|
|
app.include_router(games.router, prefix="/api/games", tags=["games"])
|
|
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {"message": "Paper Dynasty Game Backend", "version": "1.0.0"}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(
|
|
"app.main:socket_app",
|
|
host="0.0.0.0",
|
|
port=8000,
|
|
reload=True,
|
|
log_level="info"
|
|
)
|