paper-dynasty-database/migrations/2026-03-17_add_evolution_tables.sql
Cal Corum d158a4ad4e feat(WP-04): add evolution SQL migration script
Creates player_season_stats, evolution_track, evolution_card_state,
evolution_tier_boost, and evolution_cosmetic tables with IF NOT EXISTS
guards, appropriate indexes, and rollback statements. Also extends card,
battingcard, and pitchingcard with variant and image_url columns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 19:32:03 -05:00

204 lines
7.9 KiB
PL/PgSQL

-- Migration: Add card evolution tables and column extensions
-- Date: 2026-03-17
-- Issue: WP-04
-- Purpose: Support the Card Evolution system — tracks player season stats,
-- evolution tracks with tier thresholds, per-card evolution state,
-- tier-based stat boosts, and cosmetic unlocks. Also extends the
-- card, battingcard, and pitchingcard tables with variant and
-- image_url columns required by the evolution display layer.
--
-- Run on dev first, verify with:
-- SELECT count(*) FROM player_season_stats;
-- SELECT count(*) FROM evolution_track;
-- SELECT count(*) FROM evolution_card_state;
-- SELECT count(*) FROM evolution_tier_boost;
-- SELECT count(*) FROM evolution_cosmetic;
-- SELECT column_name FROM information_schema.columns
-- WHERE table_name IN ('card', 'battingcard', 'pitchingcard')
-- AND column_name IN ('variant', 'image_url')
-- ORDER BY table_name, column_name;
--
-- Rollback: See DROP/ALTER statements at bottom of file
-- ============================================
-- FORWARD MIGRATION
-- ============================================
BEGIN;
-- --------------------------------------------
-- Table 1: player_season_stats
-- Accumulates per-player per-team per-season
-- batting and pitching totals for evolution
-- formula evaluation.
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS player_season_stats (
id SERIAL PRIMARY KEY,
player_id INTEGER NOT NULL REFERENCES player(id) ON DELETE CASCADE,
team_id INTEGER NOT NULL REFERENCES team(id) ON DELETE CASCADE,
season INTEGER NOT NULL,
-- Batting stats
games_batting INTEGER NOT NULL DEFAULT 0,
pa INTEGER NOT NULL DEFAULT 0,
ab INTEGER NOT NULL DEFAULT 0,
hits INTEGER NOT NULL DEFAULT 0,
doubles INTEGER NOT NULL DEFAULT 0,
triples INTEGER NOT NULL DEFAULT 0,
hr INTEGER NOT NULL DEFAULT 0,
bb INTEGER NOT NULL DEFAULT 0,
hbp INTEGER NOT NULL DEFAULT 0,
so INTEGER NOT NULL DEFAULT 0,
rbi INTEGER NOT NULL DEFAULT 0,
runs INTEGER NOT NULL DEFAULT 0,
sb INTEGER NOT NULL DEFAULT 0,
cs INTEGER NOT NULL DEFAULT 0,
-- Pitching stats
games_pitching INTEGER NOT NULL DEFAULT 0,
outs INTEGER NOT NULL DEFAULT 0,
k INTEGER NOT NULL DEFAULT 0,
bb_allowed INTEGER NOT NULL DEFAULT 0,
hits_allowed INTEGER NOT NULL DEFAULT 0,
hr_allowed INTEGER NOT NULL DEFAULT 0,
wins INTEGER NOT NULL DEFAULT 0,
losses INTEGER NOT NULL DEFAULT 0,
saves INTEGER NOT NULL DEFAULT 0,
holds INTEGER NOT NULL DEFAULT 0,
blown_saves INTEGER NOT NULL DEFAULT 0,
-- Meta
last_game_id INTEGER REFERENCES stratgame(id) ON DELETE SET NULL,
last_updated_at TIMESTAMP
);
-- One row per player per team per season
CREATE UNIQUE INDEX IF NOT EXISTS player_season_stats_player_team_season_uniq
ON player_season_stats (player_id, team_id, season);
-- Fast lookup by team + season (e.g. leaderboard queries)
CREATE INDEX IF NOT EXISTS player_season_stats_team_season_idx
ON player_season_stats (team_id, season);
-- Fast lookup by player across seasons
CREATE INDEX IF NOT EXISTS player_season_stats_player_season_idx
ON player_season_stats (player_id, season);
-- --------------------------------------------
-- Table 2: evolution_track
-- Defines the available evolution tracks
-- (e.g. "HR Mastery", "Ace SP"), their
-- metric formula, and the four tier thresholds.
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_track (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
card_type VARCHAR(50) NOT NULL, -- 'batting' or 'pitching'
formula VARCHAR(255) NOT NULL, -- e.g. 'hr', 'k_per_9', 'ops'
t1_threshold INTEGER NOT NULL,
t2_threshold INTEGER NOT NULL,
t3_threshold INTEGER NOT NULL,
t4_threshold INTEGER NOT NULL
);
-- --------------------------------------------
-- Table 3: evolution_card_state
-- Records each card's current evolution tier,
-- running metric value, and the track it
-- belongs to. One state row per card (player
-- + team combination uniquely identifies a
-- card in a given season).
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_card_state (
id SERIAL PRIMARY KEY,
player_id INTEGER NOT NULL REFERENCES player(id) ON DELETE CASCADE,
team_id INTEGER NOT NULL REFERENCES team(id) ON DELETE CASCADE,
track_id INTEGER NOT NULL REFERENCES evolution_track(id) ON DELETE CASCADE,
current_tier INTEGER NOT NULL DEFAULT 0,
current_value DOUBLE PRECISION NOT NULL DEFAULT 0.0,
fully_evolved BOOLEAN NOT NULL DEFAULT FALSE,
last_evaluated_at TIMESTAMP
);
-- One evolution state per card (player + team)
CREATE UNIQUE INDEX IF NOT EXISTS evolution_card_state_player_team_uniq
ON evolution_card_state (player_id, team_id);
-- --------------------------------------------
-- Table 4: evolution_tier_boost
-- Defines the stat boosts unlocked at each
-- tier within a track. A single tier may
-- grant multiple boosts (e.g. +1 HR and
-- +1 power rating).
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_tier_boost (
id SERIAL PRIMARY KEY,
track_id INTEGER NOT NULL REFERENCES evolution_track(id) ON DELETE CASCADE,
tier INTEGER NOT NULL, -- 1-4
boost_type VARCHAR(50) NOT NULL, -- e.g. 'rating_bump', 'display_only'
boost_target VARCHAR(50) NOT NULL, -- e.g. 'hr_rating', 'contact_rating'
boost_value DOUBLE PRECISION NOT NULL DEFAULT 0.0
);
-- Prevent duplicate boost definitions for the same track/tier/type/target
CREATE UNIQUE INDEX IF NOT EXISTS evolution_tier_boost_track_tier_type_target_uniq
ON evolution_tier_boost (track_id, tier, boost_type, boost_target);
-- --------------------------------------------
-- Table 5: evolution_cosmetic
-- Catalogue of unlockable visual treatments
-- (borders, foils, badges, etc.) tied to
-- minimum tier requirements.
-- --------------------------------------------
CREATE TABLE IF NOT EXISTS evolution_cosmetic (
id SERIAL PRIMARY KEY,
name VARCHAR(255) UNIQUE NOT NULL,
tier_required INTEGER NOT NULL DEFAULT 0,
cosmetic_type VARCHAR(50) NOT NULL, -- e.g. 'border', 'foil', 'badge'
css_class VARCHAR(255),
asset_url VARCHAR(500)
);
-- --------------------------------------------
-- Column extensions for existing tables
-- --------------------------------------------
-- Track which visual variant a card is displaying
-- (NULL = base card, 1+ = evolved variants)
ALTER TABLE card ADD COLUMN IF NOT EXISTS variant INTEGER DEFAULT NULL;
-- Store pre-rendered or externally-hosted card image URLs
ALTER TABLE battingcard ADD COLUMN IF NOT EXISTS image_url VARCHAR(500);
ALTER TABLE pitchingcard ADD COLUMN IF NOT EXISTS image_url VARCHAR(500);
COMMIT;
-- ============================================
-- VERIFICATION QUERIES
-- ============================================
-- \d player_season_stats
-- \d evolution_track
-- \d evolution_card_state
-- \d evolution_tier_boost
-- \d evolution_cosmetic
-- SELECT indexname FROM pg_indexes
-- WHERE tablename IN (
-- 'player_season_stats',
-- 'evolution_card_state',
-- 'evolution_tier_boost'
-- )
-- ORDER BY tablename, indexname;
-- SELECT column_name, data_type FROM information_schema.columns
-- WHERE table_name IN ('card', 'battingcard', 'pitchingcard')
-- AND column_name IN ('variant', 'image_url')
-- ORDER BY table_name, column_name;
-- ============================================
-- ROLLBACK (if needed)
-- ============================================
-- ALTER TABLE pitchingcard DROP COLUMN IF EXISTS image_url;
-- ALTER TABLE battingcard DROP COLUMN IF EXISTS image_url;
-- ALTER TABLE card DROP COLUMN IF EXISTS variant;
-- DROP TABLE IF EXISTS evolution_cosmetic CASCADE;
-- DROP TABLE IF EXISTS evolution_tier_boost CASCADE;
-- DROP TABLE IF EXISTS evolution_card_state CASCADE;
-- DROP TABLE IF EXISTS evolution_track CASCADE;
-- DROP TABLE IF EXISTS player_season_stats CASCADE;