feat: evolution/refractor schema migration (generated from dev) #128

Closed
cal wants to merge 1 commits from feat/evolution-refractor-schema-migration into main
Owner

Replaces #84, which was closed due to schema drift.

What

Idempotent PostgreSQL migration generated directly from paperdynasty_dev on 2026-03-23. This captures exactly what was applied manually to dev, so running it against prod will bring prod into sync.

Tables Created

Table Description
evolution_track Three tracks (Batter, SP, RP) with t1–t4 thresholds and formula
evolution_card_state Per (player_id, team_id) tier state: current_tier, current_value, fully_evolved
evolution_tier_boost Per-tier stat boosts keyed to a track; UNIQUE on (track_id, tier, boost_type, boost_target)
evolution_cosmetic Cosmetic unlocks by tier_required; standalone (no FK to card_state)
batting_season_stats In-season batting accumulators per (player_id, team_id, season)
pitching_season_stats In-season pitching accumulators per (player_id, team_id, season)

Columns Added

Table Column Type Notes
card variant INTEGER NULL Refractor/variant tier identifier
battingcard image_url VARCHAR(500) NULL Cached rendered card image URL
pitchingcard image_url VARCHAR(500) NULL Cached rendered card image URL

Differences from PR #84

  • Splits player_season_stats into batting_season_stats + pitching_season_stats (matching actual dev schema)
  • evolution_tier_boost has full columns (boost_type, boost_target, boost_value) — not a stub
  • evolution_cosmetic has full columns (cosmetic_type, css_class, asset_url) — not a stub, and has no FK to evolution_card_state
  • evolution_card_state has no FK to evolution_cosmetic (cosmetics are a lookup table, not per-state)
  • Batting/pitching season stats FKs use ON DELETE SET NULL for last_game_id (stratgame)

Idempotency

  • CREATE TABLE IF NOT EXISTS for all 6 tables
  • CREATE [UNIQUE] INDEX IF NOT EXISTS for all 10 indexes
  • ALTER TABLE ... ADD COLUMN IF NOT EXISTS for all 3 column additions
  • Wrapped in a single BEGIN / COMMIT transaction

Important

Do NOT run this migration against dev — it is already applied there. Target is prod only.

Replaces #84, which was closed due to schema drift. ## What Idempotent PostgreSQL migration generated directly from `paperdynasty_dev` on 2026-03-23. This captures exactly what was applied manually to dev, so running it against prod will bring prod into sync. ## Tables Created | Table | Description | |---|---| | `evolution_track` | Three tracks (Batter, SP, RP) with t1–t4 thresholds and formula | | `evolution_card_state` | Per `(player_id, team_id)` tier state: current_tier, current_value, fully_evolved | | `evolution_tier_boost` | Per-tier stat boosts keyed to a track; UNIQUE on (track_id, tier, boost_type, boost_target) | | `evolution_cosmetic` | Cosmetic unlocks by tier_required; standalone (no FK to card_state) | | `batting_season_stats` | In-season batting accumulators per (player_id, team_id, season) | | `pitching_season_stats` | In-season pitching accumulators per (player_id, team_id, season) | ## Columns Added | Table | Column | Type | Notes | |---|---|---|---| | `card` | `variant` | `INTEGER NULL` | Refractor/variant tier identifier | | `battingcard` | `image_url` | `VARCHAR(500) NULL` | Cached rendered card image URL | | `pitchingcard` | `image_url` | `VARCHAR(500) NULL` | Cached rendered card image URL | ## Differences from PR #84 - Splits `player_season_stats` into `batting_season_stats` + `pitching_season_stats` (matching actual dev schema) - `evolution_tier_boost` has full columns (boost_type, boost_target, boost_value) — not a stub - `evolution_cosmetic` has full columns (cosmetic_type, css_class, asset_url) — not a stub, and has no FK to `evolution_card_state` - `evolution_card_state` has no FK to `evolution_cosmetic` (cosmetics are a lookup table, not per-state) - Batting/pitching season stats FKs use `ON DELETE SET NULL` for `last_game_id` (stratgame) ## Idempotency - `CREATE TABLE IF NOT EXISTS` for all 6 tables - `CREATE [UNIQUE] INDEX IF NOT EXISTS` for all 10 indexes - `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` for all 3 column additions - Wrapped in a single `BEGIN` / `COMMIT` transaction ## Important Do NOT run this migration against dev — it is already applied there. Target is prod only.
cal added 1 commit 2026-03-23 13:54:32 +00:00
feat: evolution/refractor schema migration (generated from dev)
All checks were successful
Build Docker Image / build (pull_request) Successful in 8m32s
0569974454
Idempotent PostgreSQL migration extracted directly from paperdynasty_dev
on 2026-03-23. Replaces PR #84 which had schema drift.

Creates evolution_track, evolution_card_state, evolution_tier_boost,
evolution_cosmetic, batting_season_stats, pitching_season_stats.
Adds card.variant, battingcard.image_url, pitchingcard.image_url.

All DDL uses CREATE TABLE IF NOT EXISTS and ADD COLUMN IF NOT EXISTS.
Wrapped in a single transaction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude added the
ai-reviewing
label 2026-03-23 14:15:53 +00:00
Claude reviewed 2026-03-23 14:18:35 +00:00
Claude left a comment
Collaborator

AI Code Review

Files Reviewed

  • migrations/2026-03-23_add_evolution_refractor_schema.sql (added)

Findings

Correctness

  • SQL is idempotent and structurally correct. All 6 tables use CREATE TABLE IF NOT EXISTS, all 10 indexes use CREATE [UNIQUE] INDEX IF NOT EXISTS, all 3 column additions use ALTER TABLE ... ADD COLUMN IF NOT EXISTS, and the whole script is wrapped in BEGIN/COMMIT. ✓
  • FK references verified: player(player_id) — correct PK for Player model; team(id) — correct; stratgame(id) — correct. ✓
  • evolution_card_state UNIQUE on (player_id, team_id) is correct — matches the application's pack-opening hook (PR #107) which uses get_or_create(player_id=..., team_id=...) against this same UNIQUE key. ✓
  • batting_season_stats and pitching_season_stats columns match ORM exactly — all fields in BattingSeasonStats (db_engine.py:1053) and PitchingSeasonStats (db_engine.py:1104) are reflected in the DDL, including the last_game_id → stratgame ON DELETE SET NULL FK. ✓
  • card.variant added to DB but absent from the Card ORM model (db_engine.py:472). Code in cards.py already has Card.variant references commented out (lines 66, 197, 354), confirming the intent. A follow-up ORM PR is needed before those code paths can be activated.
  • battingcard.image_url and pitchingcard.image_url added but ORM models BattingCard (db_engine.py:746) and PitchingCard (db_engine.py:815) do not include these fields. No application code references them yet — DB-first is a valid approach, but a follow-up ORM PR is required before these columns are usable.
  • Evolution ORM models (EvolutionTrack, EvolutionCardState, EvolutionTierBoost, EvolutionCosmetic) are still missing from db_engine.py. The evolution router (routers_v2/evolution.py:20) does from ..db_engine import EvolutionTrack at call time — this will ImportError until ORM models are added. This is a pre-existing gap from PR #100, not introduced here. The migration correctly adds the DB tables; the ORM layer needs a separate follow-up.

Security

  • No issues. This is pure DDL — no user input surface, no auth concerns, no credential exposure.

Style & Conventions

  • The existing migration file (2026-03-04_add_scout_opportunities_claims.sql) includes a rollback section with DROP statements at the bottom. This migration omits it. Minor deviation from convention — a rollback block would be helpful for emergency recovery.
  • evolution_track DDL comment says card_type is UNIQUE but only name has a UNIQUE index. No UNIQUE constraint exists on card_type in the DDL. Low risk given controlled seed data, but the comment is misleading. Either add CREATE UNIQUE INDEX IF NOT EXISTS evolution_track_card_type_key ON evolution_track (card_type) or remove the claim from the comment.

Suggestions

  1. Add rollback DROP statements at the bottom to match the convention established in the earlier migration file.
  2. Either enforce card_type uniqueness with a CREATE UNIQUE INDEX IF NOT EXISTS evolution_track_card_type_key ON evolution_track (card_type) or remove the "card_type is UNIQUE" comment from the DDL block.
  3. After merging, file follow-up issues for: (a) ORM models for all 4 evolution tables in db_engine.py; (b) Card.variant, BattingCard.image_url, PitchingCard.image_url fields in the ORM.

Verdict: COMMENT

The SQL is correct, idempotent, and FK-safe. The ORM gaps (evolution_* models, image_url fields, Card.variant) are expected in a DB-first migration approach — this PR's job is to sync prod schema with dev, which it does correctly. No blocking issues.


Automated review by Claude PR Reviewer

## AI Code Review ### Files Reviewed - `migrations/2026-03-23_add_evolution_refractor_schema.sql` (added) ### Findings #### Correctness - **SQL is idempotent and structurally correct.** All 6 tables use `CREATE TABLE IF NOT EXISTS`, all 10 indexes use `CREATE [UNIQUE] INDEX IF NOT EXISTS`, all 3 column additions use `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`, and the whole script is wrapped in `BEGIN`/`COMMIT`. ✓ - **FK references verified:** `player(player_id)` — correct PK for `Player` model; `team(id)` — correct; `stratgame(id)` — correct. ✓ - **`evolution_card_state` UNIQUE on `(player_id, team_id)` is correct** — matches the application's pack-opening hook (PR #107) which uses `get_or_create(player_id=..., team_id=...)` against this same UNIQUE key. ✓ - **`batting_season_stats` and `pitching_season_stats` columns match ORM exactly** — all fields in `BattingSeasonStats` (db_engine.py:1053) and `PitchingSeasonStats` (db_engine.py:1104) are reflected in the DDL, including the `last_game_id → stratgame ON DELETE SET NULL` FK. ✓ - **`card.variant` added to DB but absent from the `Card` ORM model** (db_engine.py:472). Code in `cards.py` already has `Card.variant` references commented out (lines 66, 197, 354), confirming the intent. A follow-up ORM PR is needed before those code paths can be activated. - **`battingcard.image_url` and `pitchingcard.image_url` added but ORM models `BattingCard` (db_engine.py:746) and `PitchingCard` (db_engine.py:815) do not include these fields.** No application code references them yet — DB-first is a valid approach, but a follow-up ORM PR is required before these columns are usable. - **Evolution ORM models (`EvolutionTrack`, `EvolutionCardState`, `EvolutionTierBoost`, `EvolutionCosmetic`) are still missing from `db_engine.py`.** The evolution router (`routers_v2/evolution.py:20`) does `from ..db_engine import EvolutionTrack` at call time — this will `ImportError` until ORM models are added. This is a pre-existing gap from PR #100, not introduced here. The migration correctly adds the DB tables; the ORM layer needs a separate follow-up. #### Security - No issues. This is pure DDL — no user input surface, no auth concerns, no credential exposure. #### Style & Conventions - The existing migration file (`2026-03-04_add_scout_opportunities_claims.sql`) includes a rollback section with `DROP` statements at the bottom. This migration omits it. Minor deviation from convention — a rollback block would be helpful for emergency recovery. - `evolution_track` DDL comment says `card_type is UNIQUE` but only `name` has a UNIQUE index. No `UNIQUE` constraint exists on `card_type` in the DDL. Low risk given controlled seed data, but the comment is misleading. Either add `CREATE UNIQUE INDEX IF NOT EXISTS evolution_track_card_type_key ON evolution_track (card_type)` or remove the claim from the comment. #### Suggestions 1. Add rollback `DROP` statements at the bottom to match the convention established in the earlier migration file. 2. Either enforce `card_type` uniqueness with a `CREATE UNIQUE INDEX IF NOT EXISTS evolution_track_card_type_key ON evolution_track (card_type)` or remove the "card_type is UNIQUE" comment from the DDL block. 3. After merging, file follow-up issues for: (a) ORM models for all 4 evolution tables in `db_engine.py`; (b) `Card.variant`, `BattingCard.image_url`, `PitchingCard.image_url` fields in the ORM. ### Verdict: COMMENT The SQL is correct, idempotent, and FK-safe. The ORM gaps (`evolution_*` models, `image_url` fields, `Card.variant`) are expected in a DB-first migration approach — this PR's job is to sync prod schema with dev, which it does correctly. No blocking issues. --- *Automated review by Claude PR Reviewer*
Claude added
ai-reviewed
and removed
ai-reviewing
labels 2026-03-23 14:18:57 +00:00
Author
Owner

Closing as superseded by work that landed today (2026-03-23).

This PR contained an idempotent schema migration for the evolution/refractor tables. That content has since shipped via two separate PRs that have been merged to main:

  • PR #130 (card-evolution Phase 1 backend) — carried the full schema migration and backend API implementation
  • PR #131 (evolution → refractor rename) — applied the naming convention cleanup on top

The schema changes described here are already present in main. There is nothing left to merge from this branch.

The branch feat/evolution-refractor-schema-migration can be deleted.

Closing as superseded by work that landed today (2026-03-23). This PR contained an idempotent schema migration for the evolution/refractor tables. That content has since shipped via two separate PRs that have been merged to `main`: - **PR #130** (card-evolution Phase 1 backend) — carried the full schema migration and backend API implementation - **PR #131** (evolution → refractor rename) — applied the naming convention cleanup on top The schema changes described here are already present in `main`. There is nothing left to merge from this branch. The branch `feat/evolution-refractor-schema-migration` can be deleted.
cal closed this pull request 2026-03-23 19:48:36 +00:00
All checks were successful
Build Docker Image / build (pull_request) Successful in 8m32s

Pull request closed

Sign in to join this conversation.
No description provided.