Update project plans and documentation for Phase 3 completion
Project plan updates: - Mark all 14 Phase 3 tasks as completed - Update acceptance criteria to met - Update master plan status to Phase 4 next - Add detailed deliverables list for Phase 3 Documentation updates: - Add UNSET sentinel pattern to CLAUDE.md - Document when to use UNSET vs None for nullable fields Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7d397a2e22
commit
ebe776d54d
@ -150,6 +150,42 @@ class CollectionService:
|
||||
|
||||
---
|
||||
|
||||
## UNSET Sentinel Pattern
|
||||
|
||||
For nullable fields that can be explicitly cleared (set to `None`), use the `UNSET` sentinel to distinguish between "not provided" (keep existing) and "set to null" (clear the value).
|
||||
|
||||
### Pattern
|
||||
|
||||
```python
|
||||
from app.repositories.protocols import UNSET
|
||||
|
||||
# In repository/service method signatures
|
||||
async def update(
|
||||
self,
|
||||
deck_id: UUID,
|
||||
name: str | None = None, # None means "don't change"
|
||||
description: str | None = UNSET, # type: ignore[assignment]
|
||||
# UNSET = keep existing, None = clear, str = set new value
|
||||
) -> DeckEntry | None:
|
||||
...
|
||||
if description is not UNSET:
|
||||
record.description = description # Could be None (clear) or string (set)
|
||||
```
|
||||
|
||||
### API Layer Usage
|
||||
|
||||
```python
|
||||
# Check if field was explicitly provided in request
|
||||
description = deck_in.description if "description" in deck_in.model_fields_set else UNSET
|
||||
```
|
||||
|
||||
### When to Use
|
||||
|
||||
- Fields that can be meaningfully set to `None` (descriptions, notes, optional refs)
|
||||
- Not needed for fields where `None` means "don't update" (name, cards, etc.)
|
||||
|
||||
---
|
||||
|
||||
## Configuration Classes
|
||||
|
||||
Game rules are defined in `app/core/config.py` as Pydantic models. These are passed from the frontend as request parameters.
|
||||
|
||||
@ -7,8 +7,8 @@
|
||||
"projectName": "Mantimon TCG - Backend Services",
|
||||
"description": "Live service backend for play.mantimon.com - server-authoritative multiplayer TCG with campaign mode, free-play, and F2P monetization",
|
||||
"totalPhases": 6,
|
||||
"completedPhases": 3,
|
||||
"status": "Phases 0-2 complete, Phase 3 (Collections + Decks) up next"
|
||||
"completedPhases": 4,
|
||||
"status": "Phases 0-3 complete, Phase 4 (Game Service + WebSocket) up next"
|
||||
},
|
||||
|
||||
"architectureDecisions": {
|
||||
@ -121,20 +121,26 @@
|
||||
{
|
||||
"id": "PHASE_3",
|
||||
"name": "Collections + Decks",
|
||||
"status": "NOT_STARTED",
|
||||
"status": "COMPLETE",
|
||||
"description": "Card ownership, deck building, starter deck selection",
|
||||
"planFile": "project_plans/PHASE_3_COLLECTION_DECKS.json",
|
||||
"estimatedWeeks": "1-2",
|
||||
"dependencies": ["PHASE_2"],
|
||||
"deliverables": [
|
||||
"CollectionService (owned cards CRUD)",
|
||||
"DeckService (create, edit, validate, delete)",
|
||||
"Mode-aware deck validation (owned vs full collection)",
|
||||
"Deck slot limits (free vs premium)",
|
||||
"Starter deck selection flow",
|
||||
"5 starter deck definitions",
|
||||
"REST endpoints"
|
||||
]
|
||||
"CollectionService with repository pattern",
|
||||
"DeckService with DI and DeckConfig from request",
|
||||
"DeckValidator pure function with comprehensive rules",
|
||||
"Collections API (/collections/me, /collections/me/cards/{id}, admin endpoint)",
|
||||
"Decks API (/decks CRUD, /decks/validate)",
|
||||
"Starter deck endpoints (/users/me/starter-deck, /users/me/starter-status)",
|
||||
"5 starter deck definitions (grass, fire, water, psychic, lightning)",
|
||||
"UNSET sentinel pattern for nullable field updates",
|
||||
"Race condition protection via unique partial index",
|
||||
"Admin API key authentication",
|
||||
"Energy type validation",
|
||||
"127 new tests, 1199 total tests"
|
||||
],
|
||||
"completedDate": "2026-01-28"
|
||||
},
|
||||
{
|
||||
"id": "PHASE_4",
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
"description": "Card ownership (collections), deck building, validation, and starter deck selection",
|
||||
"totalEstimatedHours": 29,
|
||||
"totalTasks": 14,
|
||||
"completedTasks": 4,
|
||||
"status": "in_progress",
|
||||
"completedTasks": 14,
|
||||
"status": "completed",
|
||||
"masterPlan": "../PROJECT_PLAN_MASTER.json"
|
||||
},
|
||||
|
||||
@ -149,8 +149,8 @@
|
||||
"description": "Service layer for card collection CRUD operations",
|
||||
"category": "critical",
|
||||
"priority": 4,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-001"],
|
||||
"files": [
|
||||
{"path": "app/services/collection_service.py", "status": "create"}
|
||||
@ -182,8 +182,8 @@
|
||||
"description": "Service layer for deck CRUD with validation",
|
||||
"category": "critical",
|
||||
"priority": 5,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-002", "COLL-003", "COLL-004"],
|
||||
"files": [
|
||||
{"path": "app/services/deck_service.py", "status": "create"}
|
||||
@ -222,8 +222,8 @@
|
||||
"description": "Define 5 starter decks with real card IDs from scraped data",
|
||||
"category": "high",
|
||||
"priority": 6,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": [],
|
||||
"files": [
|
||||
{"path": "app/data/starter_decks.py", "status": "create"}
|
||||
@ -273,8 +273,8 @@
|
||||
"description": "REST endpoints for collection management",
|
||||
"category": "high",
|
||||
"priority": 7,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-004"],
|
||||
"files": [
|
||||
{"path": "app/api/collections.py", "status": "create"}
|
||||
@ -308,8 +308,8 @@
|
||||
"description": "REST endpoints for deck CRUD and validation",
|
||||
"category": "high",
|
||||
"priority": 8,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-005"],
|
||||
"files": [
|
||||
{"path": "app/api/decks.py", "status": "create"}
|
||||
@ -359,8 +359,8 @@
|
||||
"description": "Endpoint for new user starter deck selection flow",
|
||||
"category": "high",
|
||||
"priority": 9,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-004", "COLL-005", "COLL-006"],
|
||||
"files": [
|
||||
{"path": "app/api/users.py", "status": "modify"}
|
||||
@ -393,8 +393,8 @@
|
||||
"description": "Mount collections and decks routers",
|
||||
"category": "high",
|
||||
"priority": 10,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-007", "COLL-008"],
|
||||
"files": [
|
||||
{"path": "app/main.py", "status": "modify"}
|
||||
@ -441,8 +441,8 @@
|
||||
"description": "Integration tests for collection operations",
|
||||
"category": "high",
|
||||
"priority": 12,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-004"],
|
||||
"files": [
|
||||
{"path": "tests/services/test_collection_service.py", "status": "create"}
|
||||
@ -470,8 +470,8 @@
|
||||
"description": "Integration tests for deck operations",
|
||||
"category": "high",
|
||||
"priority": 13,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-005"],
|
||||
"files": [
|
||||
{"path": "tests/services/test_deck_service.py", "status": "create"}
|
||||
@ -504,8 +504,8 @@
|
||||
"description": "Integration tests for collection and deck endpoints",
|
||||
"category": "high",
|
||||
"priority": 14,
|
||||
"completed": false,
|
||||
"tested": false,
|
||||
"completed": true,
|
||||
"tested": true,
|
||||
"dependencies": ["COLL-007", "COLL-008", "COLL-009"],
|
||||
"files": [
|
||||
{"path": "tests/api/test_collections_api.py", "status": "create"},
|
||||
@ -551,18 +551,18 @@
|
||||
},
|
||||
|
||||
"acceptanceCriteria": [
|
||||
{"criterion": "User can view their card collection", "met": false},
|
||||
{"criterion": "User can create a deck with name, cards, and energy", "met": false},
|
||||
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": false},
|
||||
{"criterion": "Deck validation reports all errors (not just first)", "met": false},
|
||||
{"criterion": "Free users limited to 5 decks", "met": false},
|
||||
{"criterion": "Premium users have unlimited decks", "met": false},
|
||||
{"criterion": "New user can select a starter deck (one time)", "met": false},
|
||||
{"criterion": "Starter deck selection grants cards and creates deck", "met": false},
|
||||
{"criterion": "Campaign mode validates card ownership", "met": false},
|
||||
{"criterion": "Freeplay mode skips ownership validation", "met": false},
|
||||
{"criterion": "All endpoints require authentication", "met": false},
|
||||
{"criterion": "All tests pass with high coverage", "met": false}
|
||||
{"criterion": "User can view their card collection", "met": true},
|
||||
{"criterion": "User can create a deck with name, cards, and energy", "met": true},
|
||||
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": true},
|
||||
{"criterion": "Deck validation reports all errors (not just first)", "met": true},
|
||||
{"criterion": "Free users limited to 5 decks", "met": true},
|
||||
{"criterion": "Premium users have unlimited decks", "met": true},
|
||||
{"criterion": "New user can select a starter deck (one time)", "met": true},
|
||||
{"criterion": "Starter deck selection grants cards and creates deck", "met": true},
|
||||
{"criterion": "Campaign mode validates card ownership", "met": true},
|
||||
{"criterion": "Freeplay mode skips ownership validation", "met": true},
|
||||
{"criterion": "All endpoints require authentication", "met": true},
|
||||
{"criterion": "All tests pass with high coverage", "met": true}
|
||||
],
|
||||
|
||||
"securityConsiderations": [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user