From f4793f7da672769696ae4178bbb8de808e8870db Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Tue, 17 Mar 2026 19:11:38 -0500 Subject: [PATCH] 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 --- .../2026-03-17_add_evolution_tables.sql | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 migrations/2026-03-17_add_evolution_tables.sql diff --git a/migrations/2026-03-17_add_evolution_tables.sql b/migrations/2026-03-17_add_evolution_tables.sql new file mode 100644 index 0000000..8aedac3 --- /dev/null +++ b/migrations/2026-03-17_add_evolution_tables.sql @@ -0,0 +1,203 @@ +-- 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;