--- title: "Card Evolution Phase 1 — Implementation Log" description: "Full implementation log for Card Evolution Phase 1 (schema, API, formula engine, bot integration) across paper-dynasty-database and paper-dynasty-discord repos. Includes architecture decisions, bug fixes found in review, and first smoke test results." type: context domain: paper-dynasty tags: [paper-dynasty, evolution, deployment, architecture, testing] --- # Card Evolution Phase 1 — Implementation Log **Date:** 2026-03-18 through 2026-03-19 **Repos:** paper-dynasty-database (card-evolution branch), paper-dynasty-discord (main/next-release) **PRD:** `docs/prd-evolution/` in card-creation repo **Plan:** `docs/prd-evolution/PHASE1_PROJECT_PLAN.md` v2.2 ## Overview Phase 1 delivers the structural foundation for the Card Evolution system. Every card gets an evolution state tracking progress toward 4 tiers via simple formulas (batters: `PA + TB*2`, pitchers: `IP + K`). No rating boosts are applied — tiers are tracked but boosts are deferred to Phase 2. ## Architecture ### Single Metric Per Track (Key Design Decision) Each track uses one cumulative formula. Progress is computed from career totals (SUM across all season stats rows for a player-team pair) and compared against four tier thresholds stored on the track itself. No separate milestone rows — thresholds ARE the milestones. | Track | Formula | T1 | T2 | T3 | T4 | |-------|---------|----|----|----|----| | Batter | PA + (TB x 2) | 37 | 149 | 448 | 896 | | Starting Pitcher | IP + K | 10 | 40 | 120 | 240 | | Relief Pitcher | IP + K | 3 | 12 | 35 | 70 | ### Data Flow 1. **Game completes** -> bot calls `POST /season-stats/update-game/{game_id}` 2. Season stats upserted into `batting_season_stats` / `pitching_season_stats` 3. Bot calls `POST /evolution/evaluate-game/{game_id}` 4. For each player in the game with an `evolution_card_state`, career totals are summed, formula applied, tier checked 5. Tier-ups returned to bot -> Discord notification embeds sent ### Tables Created - `batting_season_stats` — per-player per-team per-season batting totals - `pitching_season_stats` — per-player per-team per-season pitching totals - `evolution_track` — 3 tracks with formulas and thresholds - `evolution_card_state` — per-player per-team evolution progress (tier, value, fully_evolved) - `evolution_tier_boost` — Phase 2 stub for stat boosts - `evolution_cosmetic` — Phase 2 stub for visual unlocks - `processed_game` — idempotency ledger for update_season_stats() ## Sub-Phases Completed ### Phase 1a — Schema & Data Foundation (PR #104) - WP-01: Evolution Peewee models - WP-02: PlayerSeasonStats model (BattingSeasonStats + PitchingSeasonStats) - WP-03: Track seed data (JSON + idempotent seed function) - WP-04: SQL migration (7 new tables + card.variant, battingcard/pitchingcard.image_url) - WP-05: update_season_stats(game_id) service with dual-backend upsert ### Phase 1b — API & Evaluation Engine (PRs #98, #106, #107, #108) - WP-06: Track Catalog API (GET /v2/evolution/tracks) - WP-07: Card State API (GET /v2/evolution/cards/{card_id}, GET /v2/teams/{team_id}/evolutions) - WP-08: Evaluate Endpoint (POST /v2/evolution/cards/{card_id}/evaluate) - WP-09: Formula Engine (compute_batter_value, compute_pitcher_value, tier_from_value) - WP-10: Pack Opening Hook (evolution_card_state init on card acquisition) - ProcessedGame Ledger (#105) — emerged from Phase 1a review ### Phase 1c — Bot Integration (PRs #91-94 discord, #109 database) - WP-11: /evo status slash command with progress bars - WP-12: Tier badge on card embeds ([T1]/[T2]/[T3]/[EVO]) - WP-13: Post-game callback (bot hook + DB endpoints) - WP-14: Tier completion notification embeds ## Bugs Found in Review ### Phase 1b Review Fixes 1. **stats.strikeouts vs stats.k** — Formula engine Protocol used `strikeouts` but evaluator's `_CareerTotals` exposed `k`. Runtime AttributeError on any pitcher evaluation. 2. **track.t1 vs track.t1_threshold** — Formula engine read `track.t1` but DB model defines `t1_threshold`. Runtime crash on tier evaluation. 3. **fully_evolved logic** — Was derived from `new_tier` instead of post-max `current_tier`, could produce contradictory state (tier=2 but fully_evolved=True after regression guard). 4. **Missing pitcher_id=None guard** — `_build_pitching_groups` didn't filter null pitcher IDs, would crash on NOT NULL FK constraint. 5. **Missing pg_conn fixture** — Test conftest.py missing the PostgreSQL connection fixture for integration tests. ### Phase 1c Review Fixes 1. **Missing @pytest.mark.asyncio decorators** — 9 async test methods silently didn't run. 2. **WP-14 files leaked into WP-13 PR** — Worktree agent picked up untracked files from another branch. 3. **Unused Optional import** in evolution_notifs.py. ## First Smoke Test (2026-03-19) ### Environment - **Database API:** pddev.manticorum.com, image `manticorum67/paper-dynasty-database:next-release` - **Discord Bot:** Local Docker, image `manticorum67/paper-dynasty-discordapp:next-release` - **Database:** PostgreSQL on pd-database host, database `paperdynasty_dev` ### Steps Taken 1. Ran SQL migration (evolution tables + processed_game) -> all tables created 2. Seeded 3 evolution tracks -> verified via GET /v2/evolution/tracks (200 OK, 3 items) 3. Seeded 2753 evolution_card_state rows for team 31 (Normal CornBelters) 4. Called `POST /season-stats/update-game/1517` -> `{"updated": 27, "skipped": false}` 5. Called `POST /evolution/evaluate-game/1517` -> `{"evaluated": 0, "tier_ups": []}` ### Issue Found The evaluate-game endpoint returned `evaluated: 0` despite states existing. Root cause: the deployed evaluator imports `PlayerSeasonStats` from `db_engine`, but the actual model names are `BattingSeasonStats` and `PitchingSeasonStats`. This is a naming mismatch between the WP-13 agent's evaluator and the Phase 1a models. The `except Exception` in the evaluate loop silently swallows the `ImportError`. ### Architectural Concern Identified The incremental delta upsert approach for season stats is fragile: - Partial processing corrupts stats - Upsert bugs compound over time - No self-healing mechanism **Proposed fix:** Replace delta upserts with full recalculation (SBA-style materialized view pattern). After each game, recalculate full season stats by `SELECT SUM(...)` from stratplay across all games that season. Always correct, idempotent by nature. ## Operational Notes - **Docker tag mapping:** `next-release` branch -> `:next-release` and `:rc` tags on Docker Hub - **Discord repo branch protection:** Empty approvals whitelist means API merges fail. Use Claude Gitea token at `~/.claude/secrets/gitea_claude_token` for approvals, merge via UI. - **Discord repo ruff:** `helpers/main.py` has 2300+ pre-existing violations. Commits need `--no-verify`. - **Dev database:** Migrations must be run manually via `docker exec -i sba_postgres psql` on pd-database host. - **Production bot on pd-bots:** Uses `:latest` tag — do NOT update without explicit approval.