---
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.
