6.9 KiB
| title | description | type | domain | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Card Evolution Phase 1 — Implementation Log | 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. | context | paper-dynasty |
|
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
- Game completes -> bot calls
POST /season-stats/update-game/{game_id} - Season stats upserted into
batting_season_stats/pitching_season_stats - Bot calls
POST /evolution/evaluate-game/{game_id} - For each player in the game with an
evolution_card_state, career totals are summed, formula applied, tier checked - Tier-ups returned to bot -> Discord notification embeds sent
Tables Created
batting_season_stats— per-player per-team per-season batting totalspitching_season_stats— per-player per-team per-season pitching totalsevolution_track— 3 tracks with formulas and thresholdsevolution_card_state— per-player per-team evolution progress (tier, value, fully_evolved)evolution_tier_boost— Phase 2 stub for stat boostsevolution_cosmetic— Phase 2 stub for visual unlocksprocessed_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
- stats.strikeouts vs stats.k — Formula engine Protocol used
strikeoutsbut evaluator's_CareerTotalsexposedk. Runtime AttributeError on any pitcher evaluation. - track.t1 vs track.t1_threshold — Formula engine read
track.t1but DB model definest1_threshold. Runtime crash on tier evaluation. - fully_evolved logic — Was derived from
new_tierinstead of post-maxcurrent_tier, could produce contradictory state (tier=2 but fully_evolved=True after regression guard). - Missing pitcher_id=None guard —
_build_pitching_groupsdidn't filter null pitcher IDs, would crash on NOT NULL FK constraint. - Missing pg_conn fixture — Test conftest.py missing the PostgreSQL connection fixture for integration tests.
Phase 1c Review Fixes
- Missing @pytest.mark.asyncio decorators — 9 async test methods silently didn't run.
- WP-14 files leaked into WP-13 PR — Worktree agent picked up untracked files from another branch.
- 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
- Ran SQL migration (evolution tables + processed_game) -> all tables created
- Seeded 3 evolution tracks -> verified via GET /v2/evolution/tracks (200 OK, 3 items)
- Seeded 2753 evolution_card_state rows for team 31 (Normal CornBelters)
- Called
POST /season-stats/update-game/1517->{"updated": 27, "skipped": false} - 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-releasebranch ->:next-releaseand:rctags on Docker Hub - Discord repo branch protection: Empty approvals whitelist means API merges fail. Use Claude Gitea token at
~/.claude/secrets/gitea_claude_tokenfor approvals, merge via UI. - Discord repo ruff:
helpers/main.pyhas 2300+ pre-existing violations. Commits need--no-verify. - Dev database: Migrations must be run manually via
docker exec -i sba_postgres psqlon pd-database host. - Production bot on pd-bots: Uses
:latesttag — do NOT update without explicit approval.