--- phase: 01-tournament-engine plan: "07" subsystem: api, database tags: [player, fts5, qrcode, csv-import, ranking, bounty, pko, undo] requires: - phase: 01-tournament-engine provides: "audit trail + undo engine (Plan C), financial engine (Plan F), seating engine (Plan H), schema with FTS5 (Plan B)" provides: - "PlayerService with CRUD, FTS5 typeahead search, merge, CSV import" - "QR code generation per player (felt://player/{uuid})" - "Tournament player operations: register, bust, undo bust" - "RankingEngine deriving positions from bust-out order" - "Player API routes for venue-level and tournament-scoped operations" - "Buy-in flow: register + financial + auto-seat" - "Bust flow: hitman + bounty + rank + balance check" - "CSV export safety: formula injection neutralization" affects: [09-tournament-lifecycle, 11-tournament-ui, 14-integration] tech-stack: added: [skip2/go-qrcode] patterns: [derived-rankings, csv-formula-injection-safety, fts5-prefix-matching] key-files: created: - internal/player/player.go - internal/player/ranking.go - internal/player/qrcode.go - internal/player/export.go - internal/server/routes/players.go - internal/player/ranking_test.go - internal/player/player_test.go modified: - go.mod - go.sum key-decisions: - "Rankings derived from bust_out_at timestamps, never stored independently (Pitfall 6)" - "RecalculateAllRankings rebuilds all bust_out_order values from timestamps for undo consistency" - "FTS5 query terms wrapped in quotes with * suffix for prefix matching" - "CSV formula injection neutralized with tab prefix on =, +, -, @ characters" - "Buy-in flow auto-registers player if not yet in tournament" - "QR code URL format: felt://player/{uuid} for future PWA self-check-in" patterns-established: - "Derived rankings: always compute from bust-out list, never store" - "CSV export safety: SanitizeCSVField for any user data in CSV output" - "Player API pattern: venue-level CRUD + tournament-scoped actions" requirements-completed: [PLYR-02, PLYR-03, PLYR-04, PLYR-05, PLYR-06, PLYR-07] duration: 7min completed: 2026-03-01 --- # Plan 07: Player Management Summary **Player CRUD with FTS5 typeahead, CSV import, QR codes, bust/undo flows with derived rankings from bust-out order** ## Performance - **Duration:** 7 min - **Started:** 2026-03-01T03:27:50Z - **Completed:** 2026-03-01T03:35:13Z - **Tasks:** 2 - **Files modified:** 9 ## Accomplishments - Full player service with CRUD, FTS5 prefix-matching search, merge, CSV import with safety limits - QR code generation per player using skip2/go-qrcode (felt://player/{uuid}) - Tournament player operations: register, bust (with PKO bounty), undo bust with re-ranking - RankingEngine that derives all positions from ordered bust-out list (never stored) - Complete player API routes: venue-level CRUD + tournament-scoped actions + rankings - CSV export formula injection neutralization (tab-prefix on =, +, -, @) - 12 passing tests covering ranking derivation, undo re-ranking, re-entry, deals, concurrency ## Task Commits Each task was committed atomically: 1. **Task G1: Player CRUD, search, merge, import, QR codes** - `9373628` (feat) 2. **Task G2: Ranking engine and player API routes** - `8b4b131` (feat) ## Files Created/Modified - `internal/player/player.go` - PlayerService with CRUD, search, merge, import, bust, undo - `internal/player/ranking.go` - RankingEngine deriving positions from bust-out order - `internal/player/qrcode.go` - QR code generation using skip2/go-qrcode - `internal/player/export.go` - CSV export safety: formula injection neutralization - `internal/server/routes/players.go` - PlayerHandler with venue-level and tournament-scoped routes - `internal/player/ranking_test.go` - 7 ranking tests (bust order, undo, re-entry, deals, concurrency) - `internal/player/player_test.go` - 5 tests (CSV safety, import limits, UUID) - `go.mod` - Added skip2/go-qrcode dependency - `go.sum` - Updated checksums ## Decisions Made - Rankings derived from bust_out_at timestamps via RecalculateAllRankings (not stored independently per Pitfall 6) - FTS5 queries use quoted terms with * suffix for prefix matching - CSV formula injection neutralized with tab prefix on =, +, -, @ characters - Buy-in flow auto-registers player in tournament_players if not already present - QR code URL format: felt://player/{uuid} for future PWA self-check-in ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking] Installed skip2/go-qrcode dependency** - **Found during:** Task G1 (QR code generation) - **Issue:** No QR code library in go.mod - **Fix:** Ran `go get github.com/skip2/go-qrcode` - **Files modified:** go.mod, go.sum - **Verification:** QR code generation compiles and encodes valid PNG - **Committed in:** 9373628 (Task G1 commit) **2. [Rule 3 - Blocking] Created RankingEngine in Task G1 (planned for G2)** - **Found during:** Task G1 (UndoBust implementation) - **Issue:** UndoBust calls RecalculateAllRankings which requires the RankingEngine - **Fix:** Implemented RankingEngine in ranking.go as part of Task G1 - **Files modified:** internal/player/ranking.go - **Verification:** Package compiles, tests pass - **Committed in:** 9373628 (Task G1 commit) --- **Total deviations:** 2 auto-fixed (2 blocking) **Impact on plan:** Both auto-fixes necessary for compilation and correctness. No scope creep. ## Issues Encountered None ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - Player management API is complete and ready for tournament lifecycle integration (Plan I) - Ranking engine provides derived rankings for the clock/tournament UI - Buy-in and bust flows integrate with financial engine and seating engine - CSV import and merge provide player database management for TDs ## Self-Check: PASSED All 7 created files verified on disk. Both task commits (9373628, 8b4b131) verified in git log. --- *Phase: 01-tournament-engine* *Completed: 2026-03-01*