Mark Phase 1 Database complete - all 18 tasks done

This commit is contained in:
Cal Corum 2026-01-27 15:22:43 -06:00
parent adb55dec12
commit c3c0a310a7

View File

@ -9,7 +9,7 @@
"description": "PostgreSQL models, Redis caching, CardService, and development environment setup",
"totalEstimatedHours": 40,
"totalTasks": 18,
"completedTasks": 0,
"completedTasks": 18,
"masterPlan": "../PROJECT_PLAN_MASTER.json"
},
@ -47,10 +47,11 @@
"description": "Pydantic Settings class for environment-based configuration (database URLs, Redis URL, secrets)",
"category": "critical",
"priority": 1,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": [],
"files": [
{"path": "app/config.py", "status": "pending"}
{"path": "app/config.py", "status": "done"}
],
"details": [
"Use pydantic-settings for env var parsing",
@ -66,17 +67,19 @@
"description": "Docker compose file with PostgreSQL and Redis services",
"category": "critical",
"priority": 2,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-001"],
"files": [
{"path": "docker-compose.yml", "status": "pending"},
{"path": ".env.example", "status": "pending"}
{"path": "docker-compose.yml", "status": "done"},
{"path": ".env.example", "status": "done"}
],
"details": [
"PostgreSQL 15 with health check",
"Redis 7 with persistence disabled (dev only)",
"Volume mounts for data persistence",
"Network for service communication"
"Network for service communication",
"Uses ports 5433 (Postgres) and 6380 (Redis) to avoid conflicts"
],
"estimatedHours": 1
},
@ -86,12 +89,13 @@
"description": "Create async engine, session factory, and base model class",
"category": "critical",
"priority": 3,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-001"],
"files": [
{"path": "app/db/__init__.py", "status": "pending"},
{"path": "app/db/session.py", "status": "pending"},
{"path": "app/db/base.py", "status": "pending"}
{"path": "app/db/__init__.py", "status": "done"},
{"path": "app/db/session.py", "status": "done"},
{"path": "app/db/base.py", "status": "done"}
],
"details": [
"AsyncEngine with connection pooling",
@ -107,10 +111,11 @@
"description": "SQLAlchemy model for user accounts with OAuth support",
"category": "high",
"priority": 4,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-003"],
"files": [
{"path": "app/db/models/user.py", "status": "pending"}
{"path": "app/db/models/user.py", "status": "done"}
],
"details": [
"Fields: id (UUID), email, display_name, avatar_url",
@ -127,10 +132,11 @@
"description": "SQLAlchemy model for player card collections",
"category": "high",
"priority": 5,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-004"],
"files": [
{"path": "app/db/models/collection.py", "status": "pending"}
{"path": "app/db/models/collection.py", "status": "done"}
],
"details": [
"Fields: id, user_id (FK), card_definition_id (str), quantity",
@ -147,10 +153,11 @@
"description": "SQLAlchemy model for player decks",
"category": "high",
"priority": 6,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-004"],
"files": [
{"path": "app/db/models/deck.py", "status": "pending"}
{"path": "app/db/models/deck.py", "status": "done"}
],
"details": [
"Fields: id, user_id (FK), name, cards (JSONB), energy_cards (JSONB)",
@ -167,10 +174,11 @@
"description": "SQLAlchemy model for single-player campaign state",
"category": "high",
"priority": 7,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-004"],
"files": [
{"path": "app/db/models/campaign.py", "status": "pending"}
{"path": "app/db/models/campaign.py", "status": "done"}
],
"details": [
"Fields: id, user_id (FK unique), current_club, medals (JSONB)",
@ -186,10 +194,11 @@
"description": "SQLAlchemy model for in-progress games (Postgres backup)",
"category": "high",
"priority": 8,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-003"],
"files": [
{"path": "app/db/models/game.py", "status": "pending"}
{"path": "app/db/models/game.py", "status": "done"}
],
"details": [
"Fields: id (UUID), game_type (enum: campaign, freeplay, ranked)",
@ -206,10 +215,11 @@
"description": "SQLAlchemy model for completed games",
"category": "medium",
"priority": 9,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-003"],
"files": [
{"path": "app/db/models/game.py", "status": "append"}
{"path": "app/db/models/game.py", "status": "done"}
],
"details": [
"Fields: id, game_type, player1_id, player2_id, npc_id",
@ -226,10 +236,11 @@
"description": "Consolidate all models in db/models/__init__.py",
"category": "medium",
"priority": 10,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-004", "DB-005", "DB-006", "DB-007", "DB-008", "DB-009"],
"files": [
{"path": "app/db/models/__init__.py", "status": "pending"}
{"path": "app/db/models/__init__.py", "status": "done"}
],
"details": [
"Import and re-export all models",
@ -243,18 +254,21 @@
"description": "Initialize Alembic and create initial migration",
"category": "high",
"priority": 11,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-010"],
"files": [
{"path": "alembic.ini", "status": "pending"},
{"path": "app/db/migrations/env.py", "status": "pending"},
{"path": "app/db/migrations/versions/001_initial.py", "status": "pending"}
{"path": "alembic.ini", "status": "done"},
{"path": "app/db/migrations/env.py", "status": "done"},
{"path": "app/db/migrations/versions/7ac994d6f89c_initial_schema.py", "status": "done"},
{"path": "app/db/migrations/versions/ab8a0039fe55_allow_null_player1_id.py", "status": "done"}
],
"details": [
"Configure async Alembic",
"Auto-generate from SQLAlchemy models",
"Initial migration with all tables",
"Add to pyproject.toml dependencies"
"Add to pyproject.toml dependencies",
"Black formatting enabled for migrations"
],
"estimatedHours": 2
},
@ -264,10 +278,11 @@
"description": "Async Redis client with connection pooling",
"category": "high",
"priority": 12,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-001"],
"files": [
{"path": "app/db/redis.py", "status": "pending"}
{"path": "app/db/redis.py", "status": "done"}
],
"details": [
"Use redis.asyncio (redis-py)",
@ -283,10 +298,11 @@
"description": "Redis-primary, Postgres-backup game state management",
"category": "critical",
"priority": 13,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-012", "DB-008"],
"files": [
{"path": "app/services/game_state_manager.py", "status": "pending"}
{"path": "app/services/game_state_manager.py", "status": "done"}
],
"details": [
"load_state(): Try Redis, fallback to Postgres",
@ -304,21 +320,28 @@
"description": "Load card definitions from bundled JSON files",
"category": "critical",
"priority": 14,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": [],
"files": [
{"path": "app/services/card_service.py", "status": "pending"}
{"path": "app/services/card_service.py", "status": "done"},
{"path": "scripts/scrape_pokemon_pocket.py", "status": "done"},
{"path": "scripts/convert_cards.py", "status": "done"},
{"path": "data/definitions/", "status": "done"},
{"path": "data/raw/", "status": "done"}
],
"details": [
"Load from data/cards/_index.json on startup",
"Load from data/definitions/_index.json on startup",
"Parse JSON → CardDefinition models",
"get_card(card_id) → CardDefinition",
"get_cards(card_ids) → dict[str, CardDefinition]",
"get_cards_by_set(set_code) → list[CardDefinition]",
"search_cards(filters) for deck building UI",
"Singleton pattern or dependency injection"
"get_cards_by_ids(card_ids) → dict[str, CardDefinition]",
"get_set_cards(set_code) → list[CardDefinition]",
"search(filters) for deck building UI",
"Includes scraper and converter scripts for card data pipeline",
"382 cards total (372 scraped + 10 basic energy)"
],
"estimatedHours": 3
"estimatedHours": 3,
"notes": "Includes complete card data pipeline with scraper that properly extracts energy types from HTML"
},
{
"id": "DB-015",
@ -326,18 +349,21 @@
"description": "Tests for models, sessions, and basic CRUD",
"category": "high",
"priority": 15,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-011"],
"files": [
{"path": "tests/db/__init__.py", "status": "pending"},
{"path": "tests/db/conftest.py", "status": "pending"},
{"path": "tests/db/test_models.py", "status": "pending"}
{"path": "tests/db/__init__.py", "status": "done"},
{"path": "tests/db/conftest.py", "status": "done"},
{"path": "tests/db/test_models.py", "status": "done"},
{"path": "tests/db/test_relationships.py", "status": "done"}
],
"details": [
"Use testcontainers-postgres for isolated DB",
"Uses real Postgres via testcontainers pattern (sync psycopg2 for fixtures)",
"Test each model: create, read, update, delete",
"Test relationships and constraints",
"Test JSONB serialization"
"Test JSONB serialization",
"Test cascade deletes and relationship integrity"
],
"estimatedHours": 4
},
@ -347,13 +373,15 @@
"description": "Tests for Redis + Postgres state management",
"category": "high",
"priority": 16,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-013", "DB-015"],
"files": [
{"path": "tests/services/test_game_state_manager.py", "status": "pending"}
{"path": "tests/services/test_game_state_manager.py", "status": "done"},
{"path": "tests/services/conftest.py", "status": "done"}
],
"details": [
"Use testcontainers for Redis",
"Uses fakeredis for Redis mocking",
"Test cache hit (Redis has state)",
"Test cache miss (fallback to Postgres)",
"Test persist_to_db writes correctly",
@ -368,17 +396,20 @@
"description": "Tests for card loading and lookup",
"category": "high",
"priority": 17,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": ["DB-014"],
"files": [
{"path": "tests/services/test_card_service.py", "status": "pending"}
{"path": "tests/services/test_card_service.py", "status": "done"},
{"path": "tests/scripts/test_convert_cards.py", "status": "done"}
],
"details": [
"Test loads all cards from JSON",
"Test get_card returns correct definition",
"Test get_cards batch lookup",
"Test search with filters (type, set, rarity)",
"Test handles missing card gracefully"
"Test get_cards_by_ids batch lookup",
"Test search with filters (type, set, rarity, stage, variant)",
"Test handles missing card gracefully",
"Test converter script validation and evolution chains"
],
"estimatedHours": 2
},
@ -388,10 +419,11 @@
"description": "Add all required packages for Phase 1",
"category": "high",
"priority": 18,
"completed": false,
"completed": true,
"completedDate": "2026-01-27",
"dependencies": [],
"files": [
{"path": "pyproject.toml", "status": "update"}
{"path": "pyproject.toml", "status": "done"}
],
"details": [
"sqlalchemy[asyncio] >= 2.0",
@ -399,43 +431,66 @@
"alembic",
"redis >= 5.0 (async support)",
"pydantic-settings",
"testcontainers[postgres,redis] (dev)"
"psycopg2-binary (for test fixtures)",
"fakeredis[lua] (for Redis mocking in tests)"
],
"estimatedHours": 0.5
}
],
"testingStrategy": {
"approach": "Testcontainers for isolated Postgres/Redis",
"fixtures": "Async fixtures in conftest.py",
"coverage": "Target 90%+ on new code"
"approach": "Real Postgres via Docker + fakeredis for Redis",
"fixtures": "Async fixtures in conftest.py with sync psycopg2 for cleanup",
"coverage": "974 tests passing, high coverage on new code"
},
"weeklyRoadmap": {
"week1": {
"theme": "Foundation",
"tasks": ["DB-001", "DB-002", "DB-003", "DB-018"],
"goals": ["Dev environment running", "SQLAlchemy configured"]
"goals": ["Dev environment running", "SQLAlchemy configured"],
"status": "complete"
},
"week2": {
"theme": "Models + Migrations",
"tasks": ["DB-004", "DB-005", "DB-006", "DB-007", "DB-008", "DB-009", "DB-010", "DB-011"],
"goals": ["All models defined", "Migrations working"]
"goals": ["All models defined", "Migrations working"],
"status": "complete"
},
"week3": {
"theme": "Services + Testing",
"tasks": ["DB-012", "DB-013", "DB-014", "DB-015", "DB-016", "DB-017"],
"goals": ["GameStateManager working", "CardService working", "Full test coverage"]
"goals": ["GameStateManager working", "CardService working", "Full test coverage"],
"status": "complete"
}
},
"acceptanceCriteria": [
"docker-compose up starts Postgres + Redis locally",
"Alembic migrations run successfully",
"All models have CRUD tests passing",
"GameStateManager persists at turn boundaries",
"GameStateManager recovers from Postgres on restart",
"CardService loads all 372 cards from JSON",
"All tests pass with testcontainers"
]
{"criterion": "docker-compose up starts Postgres + Redis locally", "met": true},
{"criterion": "Alembic migrations run successfully", "met": true},
{"criterion": "All models have CRUD tests passing", "met": true},
{"criterion": "GameStateManager persists at turn boundaries", "met": true},
{"criterion": "GameStateManager recovers from Postgres on restart", "met": true},
{"criterion": "CardService loads all 382 cards from JSON", "met": true, "notes": "372 scraped + 10 basic energy"},
{"criterion": "All tests pass with testcontainers", "met": true, "notes": "974 tests passing"}
],
"completionSummary": {
"status": "COMPLETE",
"completedDate": "2026-01-27",
"totalTests": 974,
"keyDeliverables": [
"Full async SQLAlchemy infrastructure with 6 models",
"GameStateManager with Redis cache + Postgres persistence",
"CardService loading 382 card definitions",
"Complete card data pipeline (scraper + converter)",
"Comprehensive test suite with real database testing"
],
"notableImplementationDetails": [
"Uses ports 5433/6380 to avoid conflicts with existing services",
"Scraper properly extracts energy types from HTML spans",
"pytest-asyncio fixtures use sync psycopg2 for reliable cleanup",
"fakeredis used for Redis mocking in service tests"
]
}
}