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
|
## Configuration Classes
|
||||||
|
|
||||||
Game rules are defined in `app/core/config.py` as Pydantic models. These are passed from the frontend as request parameters.
|
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",
|
"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",
|
"description": "Live service backend for play.mantimon.com - server-authoritative multiplayer TCG with campaign mode, free-play, and F2P monetization",
|
||||||
"totalPhases": 6,
|
"totalPhases": 6,
|
||||||
"completedPhases": 3,
|
"completedPhases": 4,
|
||||||
"status": "Phases 0-2 complete, Phase 3 (Collections + Decks) up next"
|
"status": "Phases 0-3 complete, Phase 4 (Game Service + WebSocket) up next"
|
||||||
},
|
},
|
||||||
|
|
||||||
"architectureDecisions": {
|
"architectureDecisions": {
|
||||||
@ -121,20 +121,26 @@
|
|||||||
{
|
{
|
||||||
"id": "PHASE_3",
|
"id": "PHASE_3",
|
||||||
"name": "Collections + Decks",
|
"name": "Collections + Decks",
|
||||||
"status": "NOT_STARTED",
|
"status": "COMPLETE",
|
||||||
"description": "Card ownership, deck building, starter deck selection",
|
"description": "Card ownership, deck building, starter deck selection",
|
||||||
"planFile": "project_plans/PHASE_3_COLLECTION_DECKS.json",
|
"planFile": "project_plans/PHASE_3_COLLECTION_DECKS.json",
|
||||||
"estimatedWeeks": "1-2",
|
"estimatedWeeks": "1-2",
|
||||||
"dependencies": ["PHASE_2"],
|
"dependencies": ["PHASE_2"],
|
||||||
"deliverables": [
|
"deliverables": [
|
||||||
"CollectionService (owned cards CRUD)",
|
"CollectionService with repository pattern",
|
||||||
"DeckService (create, edit, validate, delete)",
|
"DeckService with DI and DeckConfig from request",
|
||||||
"Mode-aware deck validation (owned vs full collection)",
|
"DeckValidator pure function with comprehensive rules",
|
||||||
"Deck slot limits (free vs premium)",
|
"Collections API (/collections/me, /collections/me/cards/{id}, admin endpoint)",
|
||||||
"Starter deck selection flow",
|
"Decks API (/decks CRUD, /decks/validate)",
|
||||||
"5 starter deck definitions",
|
"Starter deck endpoints (/users/me/starter-deck, /users/me/starter-status)",
|
||||||
"REST endpoints"
|
"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",
|
"id": "PHASE_4",
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
"description": "Card ownership (collections), deck building, validation, and starter deck selection",
|
"description": "Card ownership (collections), deck building, validation, and starter deck selection",
|
||||||
"totalEstimatedHours": 29,
|
"totalEstimatedHours": 29,
|
||||||
"totalTasks": 14,
|
"totalTasks": 14,
|
||||||
"completedTasks": 4,
|
"completedTasks": 14,
|
||||||
"status": "in_progress",
|
"status": "completed",
|
||||||
"masterPlan": "../PROJECT_PLAN_MASTER.json"
|
"masterPlan": "../PROJECT_PLAN_MASTER.json"
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -149,8 +149,8 @@
|
|||||||
"description": "Service layer for card collection CRUD operations",
|
"description": "Service layer for card collection CRUD operations",
|
||||||
"category": "critical",
|
"category": "critical",
|
||||||
"priority": 4,
|
"priority": 4,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-001"],
|
"dependencies": ["COLL-001"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/services/collection_service.py", "status": "create"}
|
{"path": "app/services/collection_service.py", "status": "create"}
|
||||||
@ -182,8 +182,8 @@
|
|||||||
"description": "Service layer for deck CRUD with validation",
|
"description": "Service layer for deck CRUD with validation",
|
||||||
"category": "critical",
|
"category": "critical",
|
||||||
"priority": 5,
|
"priority": 5,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-002", "COLL-003", "COLL-004"],
|
"dependencies": ["COLL-002", "COLL-003", "COLL-004"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/services/deck_service.py", "status": "create"}
|
{"path": "app/services/deck_service.py", "status": "create"}
|
||||||
@ -222,8 +222,8 @@
|
|||||||
"description": "Define 5 starter decks with real card IDs from scraped data",
|
"description": "Define 5 starter decks with real card IDs from scraped data",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 6,
|
"priority": 6,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/data/starter_decks.py", "status": "create"}
|
{"path": "app/data/starter_decks.py", "status": "create"}
|
||||||
@ -273,8 +273,8 @@
|
|||||||
"description": "REST endpoints for collection management",
|
"description": "REST endpoints for collection management",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 7,
|
"priority": 7,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-004"],
|
"dependencies": ["COLL-004"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/api/collections.py", "status": "create"}
|
{"path": "app/api/collections.py", "status": "create"}
|
||||||
@ -308,8 +308,8 @@
|
|||||||
"description": "REST endpoints for deck CRUD and validation",
|
"description": "REST endpoints for deck CRUD and validation",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 8,
|
"priority": 8,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-005"],
|
"dependencies": ["COLL-005"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/api/decks.py", "status": "create"}
|
{"path": "app/api/decks.py", "status": "create"}
|
||||||
@ -359,8 +359,8 @@
|
|||||||
"description": "Endpoint for new user starter deck selection flow",
|
"description": "Endpoint for new user starter deck selection flow",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 9,
|
"priority": 9,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-004", "COLL-005", "COLL-006"],
|
"dependencies": ["COLL-004", "COLL-005", "COLL-006"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/api/users.py", "status": "modify"}
|
{"path": "app/api/users.py", "status": "modify"}
|
||||||
@ -393,8 +393,8 @@
|
|||||||
"description": "Mount collections and decks routers",
|
"description": "Mount collections and decks routers",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 10,
|
"priority": 10,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-007", "COLL-008"],
|
"dependencies": ["COLL-007", "COLL-008"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "app/main.py", "status": "modify"}
|
{"path": "app/main.py", "status": "modify"}
|
||||||
@ -441,8 +441,8 @@
|
|||||||
"description": "Integration tests for collection operations",
|
"description": "Integration tests for collection operations",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 12,
|
"priority": 12,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-004"],
|
"dependencies": ["COLL-004"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "tests/services/test_collection_service.py", "status": "create"}
|
{"path": "tests/services/test_collection_service.py", "status": "create"}
|
||||||
@ -470,8 +470,8 @@
|
|||||||
"description": "Integration tests for deck operations",
|
"description": "Integration tests for deck operations",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 13,
|
"priority": 13,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-005"],
|
"dependencies": ["COLL-005"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "tests/services/test_deck_service.py", "status": "create"}
|
{"path": "tests/services/test_deck_service.py", "status": "create"}
|
||||||
@ -504,8 +504,8 @@
|
|||||||
"description": "Integration tests for collection and deck endpoints",
|
"description": "Integration tests for collection and deck endpoints",
|
||||||
"category": "high",
|
"category": "high",
|
||||||
"priority": 14,
|
"priority": 14,
|
||||||
"completed": false,
|
"completed": true,
|
||||||
"tested": false,
|
"tested": true,
|
||||||
"dependencies": ["COLL-007", "COLL-008", "COLL-009"],
|
"dependencies": ["COLL-007", "COLL-008", "COLL-009"],
|
||||||
"files": [
|
"files": [
|
||||||
{"path": "tests/api/test_collections_api.py", "status": "create"},
|
{"path": "tests/api/test_collections_api.py", "status": "create"},
|
||||||
@ -551,18 +551,18 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"acceptanceCriteria": [
|
"acceptanceCriteria": [
|
||||||
{"criterion": "User can view their card collection", "met": false},
|
{"criterion": "User can view their card collection", "met": true},
|
||||||
{"criterion": "User can create a deck with name, cards, and energy", "met": false},
|
{"criterion": "User can create a deck with name, cards, and energy", "met": true},
|
||||||
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": false},
|
{"criterion": "Deck validation enforces all rules (count, copies, Basic Pokemon)", "met": true},
|
||||||
{"criterion": "Deck validation reports all errors (not just first)", "met": false},
|
{"criterion": "Deck validation reports all errors (not just first)", "met": true},
|
||||||
{"criterion": "Free users limited to 5 decks", "met": false},
|
{"criterion": "Free users limited to 5 decks", "met": true},
|
||||||
{"criterion": "Premium users have unlimited decks", "met": false},
|
{"criterion": "Premium users have unlimited decks", "met": true},
|
||||||
{"criterion": "New user can select a starter deck (one time)", "met": false},
|
{"criterion": "New user can select a starter deck (one time)", "met": true},
|
||||||
{"criterion": "Starter deck selection grants cards and creates deck", "met": false},
|
{"criterion": "Starter deck selection grants cards and creates deck", "met": true},
|
||||||
{"criterion": "Campaign mode validates card ownership", "met": false},
|
{"criterion": "Campaign mode validates card ownership", "met": true},
|
||||||
{"criterion": "Freeplay mode skips ownership validation", "met": false},
|
{"criterion": "Freeplay mode skips ownership validation", "met": true},
|
||||||
{"criterion": "All endpoints require authentication", "met": false},
|
{"criterion": "All endpoints require authentication", "met": true},
|
||||||
{"criterion": "All tests pass with high coverage", "met": false}
|
{"criterion": "All tests pass with high coverage", "met": true}
|
||||||
],
|
],
|
||||||
|
|
||||||
"securityConsiderations": [
|
"securityConsiderations": [
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user