Files
mtgonline/backend/scripts/migrate_complete.py
T

872 lines
38 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
MTGJSON Database Migration - Fixed Version
This script:
1. Adds all MTGJSON columns to mtg_cards and mtg_sets tables
2. Populates them from existing JSON data
3. Creates the card interaction graph tables
4. Populates the interaction graph from existing data
5. Creates sample interaction data to demonstrate the system
"""
from sqlalchemy import create_engine, text
import json
DB_URL = "postgresql+psycopg2://mtgonline:mtgonline_pass@172.18.0.2:5432/mtgdata"
class MTGJSONFullMigration:
"""Complete migration for MTGJSON schema and card interaction graph."""
def __init__(self):
self.engine = create_engine(DB_URL)
self.conn = None
def connect(self):
"""Connect to database."""
self.conn = self.engine.connect()
print("✓ Connected to database")
def disconnect(self):
"""Disconnect from database."""
if self.conn:
self.conn.close()
self.engine.dispose()
print("✓ Disconnected from database")
def column_exists(self, table_name: str, column_name: str) -> bool:
"""Check if a column exists in a table."""
result = self.conn.execute(text("""
SELECT column_name
FROM information_schema.columns
WHERE table_name = :table AND column_name = :column
"""), {"table": table_name, "column": column_name})
return result.fetchone() is not None
def add_column(self, table_name: str, column_name: str, column_type: str):
"""Add a column to a table if it doesn't exist."""
if not self.column_exists(table_name, column_name):
self.conn.execute(text(f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"))
print(f" ✓ Added: {table_name}.{column_name} ({column_type})")
def create_table(self, table_sql: str):
"""Create a table if it doesn't exist."""
self.conn.execute(text(table_sql))
print(f" ✓ Created table")
def create_unique_constraint(self, constraint_sql: str):
"""Create a unique constraint if it doesn't exist."""
try:
self.conn.execute(text(constraint_sql))
except:
pass # Constraint might already exist
def create_index(self, index_sql: str):
"""Create an index if it doesn't exist."""
self.conn.execute(text(f"CREATE INDEX IF NOT EXISTS {index_sql}"))
print(f" ✓ Created index: {index_sql.split(' ON ')[1].split(' ')[0]}")
def step_1_add_mtgjson_columns(self):
"""Step 1: Add all MTGJSON columns to mtg_cards and mtg_sets tables."""
print("\n" + "=" * 60)
print("STEP 1: Adding MTGJSON columns to database")
print("=" * 60)
# Add columns to mtg_cards
print("\n📝 Adding columns to mtg_cards...")
card_columns = [
("colors", "VARCHAR(20)"),
("color_identity", "VARCHAR(10)"),
("supertypes", "VARCHAR(100)"),
("types", "VARCHAR(255)"),
("subtypes", "VARCHAR(255)"),
("legalities", "JSONB"),
("prices", "JSONB"),
("card_faces", "JSONB"),
("foreign_names", "JSONB"),
("related_cards", "JSONB"),
("keywords", "JSONB"),
("promo", "BOOLEAN DEFAULT FALSE"),
("digital", "BOOLEAN DEFAULT FALSE"),
("token", "BOOLEAN DEFAULT FALSE"),
("full_art", "BOOLEAN DEFAULT FALSE"),
("border_color", "VARCHAR(20)"),
("watermark", "VARCHAR(255)"),
("loyalty", "VARCHAR(50)"),
("frame", "VARCHAR(50)"),
("frame_effects", "JSONB"),
("lang", "VARCHAR(10) DEFAULT 'en'"),
("original_release_date", "DATE"),
("original_type_line", "VARCHAR(255)"),
("security_stamp", "VARCHAR(20)"),
("is_rebalanced", "BOOLEAN DEFAULT FALSE"),
("is_starter", "BOOLEAN DEFAULT FALSE"),
("in_booster", "BOOLEAN DEFAULT FALSE"),
("mystical_archive", "BOOLEAN DEFAULT FALSE"),
]
for col_name, col_type in card_columns:
self.add_column("mtg_cards", col_name, col_type)
# Add columns to mtg_sets
print("\n📝 Adding columns to mtg_sets...")
set_columns = [
("tcgplayer_group_id", "INTEGER"),
("scryfall_id", "VARCHAR(36)"),
("status", "VARCHAR(20)"),
("name_normalized", "VARCHAR(255)"),
("block_code", "VARCHAR(10)"),
("set_codes", "JSONB"),
("card_count", "INTEGER"),
]
for col_name, col_type in set_columns:
self.add_column("mtg_sets", col_name, col_type)
print("\n✓ Step 1 complete: All MTGJSON columns added")
def step_2_populate_mtgjson_columns(self):
"""Step 2: Populate new columns from existing JSON data."""
print("\n" + "=" * 60)
print("STEP 2: Populating MTGJSON columns from JSON data")
print("=" * 60)
# Extract data from identifiers JSON
print("\n🔄 Extracting data from identifiers JSON...")
self.conn.execute(text("""
UPDATE mtg_cards
SET
border_color = identifiers->>'border',
watermark = identifiers->>'watermark',
original_release_date = identifiers->>'originalReleaseDate',
original_type_line = identifiers->>'originalTypeLine',
security_stamp = identifiers->>'securityStamp',
lang = identifiers->>'lang',
promo = COALESCE((identifiers->>'isPromo')::BOOLEAN, false),
digital = COALESCE((identifiers->>'isDigital')::BOOLEAN, false),
token = COALESCE((identifiers->>'isToken')::BOOLEAN, false)
WHERE identifiers IS NOT NULL
AND identifiers != 'null'
"""))
print(" ✓ Updated basic fields from identifiers")
# Extract type information from type_line
print("\n🔄 Extracting type hierarchy from type_line...")
self.conn.execute(text("""
UPDATE mtg_cards
SET
supertypes = CASE
WHEN type_line LIKE '%Legendary%' THEN 'Legendary'
ELSE NULL
END,
types = CASE
WHEN type_line LIKE '%Creature%' THEN 'Creature'
WHEN type_line LIKE '%Instant%' THEN 'Instant'
WHEN type_line LIKE '%Sorcery%' THEN 'Sorcery'
WHEN type_line LIKE '%Enchantment%' THEN 'Enchantment'
WHEN type_line LIKE '%Artifact%' THEN 'Artifact'
WHEN type_line LIKE '%Land%' THEN 'Land'
WHEN type_line LIKE '%Planeswalker%' THEN 'Planeswalker'
ELSE NULL
END,
subtypes = CASE
WHEN type_line LIKE '%Elf%' THEN 'Elf'
WHEN type_line LIKE '%Human%' THEN 'Human'
WHEN type_line LIKE '%Goblin%' THEN 'Goblin'
WHEN type_line LIKE '%Vampire%' THEN 'Vampire'
WHEN type_line LIKE '%Angel%' THEN 'Angel'
WHEN type_line LIKE '%Dragon%' THEN 'Dragon'
ELSE NULL
END
WHERE type_line IS NOT NULL
AND type_line != ''
"""))
print(" ✓ Updated type hierarchy from type_line")
# Extract legalities, prices, card_faces from images JSON
print("\n🔄 Extracting complex data from images JSON...")
self.conn.execute(text("""
UPDATE mtg_cards
SET
legalities = images->'legalities',
prices = images->'prices',
card_faces = images->'cardFaces',
foreign_names = images->'foreignData',
related_cards = images->'relatedCards'
WHERE images IS NOT NULL
AND images != 'null'
"""))
print(" ✓ Updated complex fields from images JSON")
# Extract colors from mana_cost
print("\n🔄 Extracting colors from mana_cost...")
self.conn.execute(text("""
UPDATE mtg_cards
SET
colors = CASE
WHEN mana_cost LIKE '%{W}%' AND mana_cost LIKE '%{U}%' THEN 'W,U'
WHEN mana_cost LIKE '%{W}%' AND mana_cost LIKE '%{B}%' THEN 'W,B'
WHEN mana_cost LIKE '%{U}%' AND mana_cost LIKE '%{B}%' THEN 'U,B'
WHEN mana_cost LIKE '%{W}%' THEN 'W'
WHEN mana_cost LIKE '%{U}%' THEN 'U'
WHEN mana_cost LIKE '%{B}%' THEN 'B'
WHEN mana_cost LIKE '%{R}%' THEN 'R'
WHEN mana_cost LIKE '%{G}%' THEN 'G'
ELSE NULL
END
WHERE mana_cost IS NOT NULL
AND mana_cost != ''
"""))
print(" ✓ Updated colors from mana_cost")
# Update loyalty for Planeswalkers
print("\n🔄 Updating loyalty for Planeswalkers...")
self.conn.execute(text("""
UPDATE mtg_cards
SET loyalty = '3'
WHERE type_line LIKE '%Planeswalker%'
AND loyalty IS NULL
"""))
print(" ✓ Updated loyalty for Planeswalkers")
self.conn.commit()
print("\n✓ Step 2 complete: All columns populated")
def step_3_create_interaction_graph(self):
"""Step 3: Create card interaction graph tables."""
print("\n" + "=" * 60)
print("STEP 3: Creating card interaction graph")
print("=" * 60)
# Card mechanics table
print("\n📊 Creating mtg_card_mechanics table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_mechanics (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
mechanic VARCHAR(100) NOT NULL,
strength INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, mechanic)
)
""")
indexes = [
"idx_mechanics_card_id ON mtg_card_mechanics(card_id)",
"idx_mechanics_mechanic ON mtg_card_mechanics(mechanic)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card mechanics table created")
# Card archetypes table
print("\n📊 Creating mtg_card_archetypes table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_archetypes (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
archetype VARCHAR(100) NOT NULL,
strength INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, archetype)
)
""")
indexes = [
"idx_archetypes_card_id ON mtg_card_archetypes(card_id)",
"idx_archetypes_archetype ON mtg_card_archetypes(archetype)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card archetypes table created")
# Card themes table
print("\n📊 Creating mtg_card_themes table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_themes (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
theme VARCHAR(100) NOT NULL,
strength INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, theme)
)
""")
indexes = [
"idx_themes_card_id ON mtg_card_themes(card_id)",
"idx_themes_theme ON mtg_card_themes(theme)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card themes table created")
# Card relationships table
print("\n📊 Creating mtg_card_relationships table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_relationships (
id SERIAL PRIMARY KEY,
card_a_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
card_b_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
relationship_type VARCHAR(50) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_a_id, card_b_id, relationship_type)
)
""")
indexes = [
"idx_relationships_card_a ON mtg_card_relationships(card_a_id)",
"idx_relationships_card_b ON mtg_card_relationships(card_b_id)",
"idx_relationships_type ON mtg_card_relationships(relationship_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card relationships table created")
# Card synergies table
print("\n📊 Creating mtg_card_synergies table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_synergies (
id SERIAL PRIMARY KEY,
card_a_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
card_b_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
synergy_type VARCHAR(50) NOT NULL,
strength INTEGER NOT NULL CHECK (strength BETWEEN 1 AND 5),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_a_id, card_b_id, synergy_type)
)
""")
indexes = [
"idx_synergies_card_a ON mtg_card_synergies(card_a_id)",
"idx_synergies_card_b ON mtg_card_synergies(card_b_id)",
"idx_synergies_type ON mtg_card_synergies(synergy_type)",
"idx_synergies_strength ON mtg_card_synergies(strength)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card synergies table created")
# Card counters table
print("\n📊 Creating mtg_card_counters table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_counters (
id SERIAL PRIMARY KEY,
card_a_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
card_b_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
counter_type VARCHAR(50) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_a_id, card_b_id, counter_type)
)
""")
indexes = [
"idx_counters_card_a ON mtg_card_counters(card_a_id)",
"idx_counters_card_b ON mtg_card_counters(card_b_id)",
"idx_counters_type ON mtg_card_counters(counter_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card counters table created")
# Card evolution table
print("\n📊 Creating mtg_card_evolution table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_evolution (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
evolved_card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
evolution_type VARCHAR(50) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, evolved_card_id, evolution_type)
)
""")
indexes = [
"idx_evolution_card_id ON mtg_card_evolution(card_id)",
"idx_evolution_evolved_id ON mtg_card_evolution(evolved_card_id)",
"idx_evolution_type ON mtg_card_evolution(evolution_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card evolution table created")
# Card partners table
print("\n📊 Creating mtg_card_partners table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_partners (
id SERIAL PRIMARY KEY,
card_a_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
card_b_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
partnership_type VARCHAR(50) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_a_id, card_b_id, partnership_type)
)
""")
indexes = [
"idx_partners_card_a ON mtg_card_partners(card_a_id)",
"idx_partners_card_b ON mtg_card_partners(card_b_id)",
"idx_partners_type ON mtg_card_partners(partnership_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card partners table created")
# Card mana relations table
print("\n📊 Creating mtg_card_mana_relations table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_mana_relations (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
land_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
mana_type VARCHAR(10) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, land_id, mana_type)
)
""")
indexes = [
"idx_mana_card_id ON mtg_card_mana_relations(card_id)",
"idx_mana_land_id ON mtg_card_mana_relations(land_id)",
"idx_mana_type ON mtg_card_mana_relations(mana_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card mana relations table created")
# Card set relations table
print("\n📊 Creating mtg_card_set_relations table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_set_relations (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
set_id INTEGER REFERENCES mtg_sets(id) ON DELETE CASCADE,
theme VARCHAR(100) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, set_id, theme)
)
""")
indexes = [
"idx_setrel_card_id ON mtg_card_set_relations(card_id)",
"idx_setrel_set_id ON mtg_card_set_relations(set_id)",
"idx_setrel_theme ON mtg_card_set_relations(theme)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card set relations table created")
# Card power relations table
print("\n📊 Creating mtg_card_power_relations table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_power_relations (
id SERIAL PRIMARY KEY,
card_a_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
card_b_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
relation_type VARCHAR(50) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_a_id, card_b_id, relation_type)
)
""")
indexes = [
"idx_power_card_a ON mtg_card_power_relations(card_a_id)",
"idx_power_card_b ON mtg_card_power_relations(card_b_id)",
"idx_power_type ON mtg_card_power_relations(relation_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card power relations table created")
# Card history table
print("\n📊 Creating mtg_card_history table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_history (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
related_card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
history_type VARCHAR(50) NOT NULL,
strength INTEGER DEFAULT 1,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id, related_card_id, history_type)
)
""")
indexes = [
"idx_history_card_id ON mtg_card_history(card_id)",
"idx_history_related_id ON mtg_card_history(related_card_id)",
"idx_history_type ON mtg_card_history(history_type)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card history table created")
# Card interaction stats table
print("\n📊 Creating mtg_card_interaction_stats table...")
self.create_table("""
CREATE TABLE IF NOT EXISTS mtg_card_interaction_stats (
id SERIAL PRIMARY KEY,
card_id INTEGER REFERENCES mtg_cards(id) ON DELETE CASCADE,
total_synergies INTEGER DEFAULT 0,
total_counters INTEGER DEFAULT 0,
total_evolution INTEGER DEFAULT 0,
total_partners INTEGER DEFAULT 0,
total_mechanics INTEGER DEFAULT 0,
total_archetypes INTEGER DEFAULT 0,
total_themes INTEGER DEFAULT 0,
avg_synergy_strength DECIMAL(3,2) DEFAULT 0.00,
max_synergy_strength INTEGER DEFAULT 0,
primary_archetype VARCHAR(100),
primary_theme VARCHAR(100),
computed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(card_id)
)
""")
indexes = [
"idx_stats_card_id ON mtg_card_interaction_stats(card_id)",
"idx_stats_total_synergies ON mtg_card_interaction_stats(total_synergies)",
"idx_stats_primary_archetype ON mtg_card_interaction_stats(primary_archetype)",
]
for idx in indexes:
self.create_index(idx)
print(" ✓ Card interaction stats table created")
print("\n✓ Step 3 complete: Interaction graph tables created")
def step_4_populate_interaction_graph(self):
"""Step 4: Populate interaction graph from existing data."""
print("\n" + "=" * 60)
print("STEP 4: Populating interaction graph from existing data")
print("=" * 60)
# Populate mechanics from subtypes
print("\n🔄 Populating mechanics from subtypes...")
self.conn.execute(text("""
INSERT INTO mtg_card_mechanics (card_id, mechanic)
SELECT DISTINCT c.id, LOWER(UNNEST(string_to_array(c.subtypes, ',')))
FROM mtg_cards c
WHERE c.subtypes IS NOT NULL
AND c.subtypes != 'null'
AND LOWER(UNNEST(string_to_array(c.subtypes, ','))) IN (
'flying', 'first_strike', 'double_strike', 'deathtouch', 'lifelink',
'haste', 'trample', 'menace', 'vigilance', 'reach', 'indestructible',
'hexproof', 'shroud', 'defender', 'landfall', 'delve', 'soulshift',
'suspend', 'convoke', 'rampage', 'toxic', 'crew', 'equip', 'annihilator',
'spectacle', 'prowess', 'aftermath', 'adapt', 'amplify', 'awaken',
'banding', 'bestow', 'burst', 'channel', 'clash', 'crawl', 'curse',
'day_night', 'decay', 'defiant', 'demolish', 'detain', 'detect',
'devour', 'disguise', 'disturb', 'dome', 'double_strike', 'dredge',
'emerge', 'encore', 'endure', 'evoke', 'evolve', 'exalted', 'exile',
'exploit', 'extort', 'fairy', 'fanatic', 'fathom', 'fear', 'feline',
'flash', 'flight', 'foretell', 'frenzy', 'fumble', 'galvanize',
'gateway', 'genesis', 'graft', 'grave', 'grit', 'guardian', 'harvest',
'healer', 'heroic', 'hideaway', 'hinterland', 'hoard', 'hour', 'illusion',
'immortal', 'impulse', 'inspiration', 'instill', 'iron', 'junk', 'kicker',
'knight', 'land', 'leech', 'lich', 'lifespan', 'lightning', 'living',
'lurk', 'madness', 'manifest', 'map', 'meld', 'miracle', 'mitosis',
'modular', 'moon', 'mother', 'morph', 'mutate', 'ninja', 'night',
'nightmare', 'pact', 'paradox', 'persist', 'pillage', 'pivot', 'planar',
'polar', 'pour', 'prey', 'priest', 'primer', 'probe', 'prosperity',
'psychic', 'puppet', 'quest', 'quote', 'rage', 'raid', 'raise', 'rally',
'rapid', 'rat', 'rebound', 'reckless', 'recoup', 'reflect', 'refresh',
'replicate', 'reverberate', 'reviviant', 'rift', 'rip', 'ritual', 'rite',
'rogue', 'savant', 'scavenge', 'seek', 'shadow', 'shards', 'skulk',
'smelt', 'snap', 'snow', 'spectacle', 'splice', 'spore', 'sprawl',
'stabilize', 'stasis', 'storm', 'story', 'substitute', 'sunder', 'surge',
'survive', 'swarm', 'symbiosis', 'synchronized', 'synth', 'table', 'taint',
'tank', 'thorn', 'thwart', 'time', 'tinker', 'toxin', 'trail', 'transfigure',
'transform', 'transport', 'trouble', 'tunnel', 'unearth', 'unleash', 'unmask',
'unstoppable', 'urborg', 'urgent', 'utility', 'vengeful', 'vanish', 'venom',
'victory', 'villainous', 'vitalize', 'void', 'voyage', 'ward', 'watch', 'weave',
'wed', 'whammy', 'wild', 'will', 'wisp', 'witch', 'woe', 'wounded', 'wrap',
'wrought', 'wurm', 'wythe'
)
ON CONFLICT DO NOTHING
"""))
print(" ✓ Populated mechanics from subtypes")
# Populate archetypes from subtypes
print("\n🔄 Populating archetypes from subtypes...")
self.conn.execute(text("""
INSERT INTO mtg_card_archetypes (card_id, archetype)
SELECT DISTINCT c.id, LOWER(UNNEST(string_to_array(c.subtypes, ',')))
FROM mtg_cards c
WHERE c.subtypes IS NOT NULL
AND c.subtypes != 'null'
AND LOWER(UNNEST(string_to_array(c.subtypes, ','))) IN (
'goblin', 'elf', 'vampire', 'angel', 'dragon', 'human', 'zombie',
'soldier', 'knight', 'wizard', 'spirit', 'demon', 'snake', 'cat',
'wolf', 'bear', 'bird', 'insect', 'horror', 'goat', 'ox', 'elephant',
'whale', 'shark', 'fish', 'serpent', 'lizard', 'scorpion', 'spider',
'rat', 'drake', 'wyvern', 'phoenix', 'lynx', 'jaguar', 'hydra',
'leviathan', 'kraken', 'cyclops', 'golem', 'homunculus', 'clay',
'construct', 'myr', 'aether', 'pumpkin', 'pirate', 'pegasus',
'unicorn', 'centaur', 'merfolk', 'mermaid', 'naga', 'satyr', 'dryad',
'treant', 'elemental', 'fiend', 'imp', 'faerie', 'minion', 'abomination',
'beast', 'demigod', 'god', 'avatar', 'guardian', 'warrior', 'rogue',
'artificer', 'bard', 'monk', 'ninja', 'samurai', 'assassin', 'thief',
'acrobat', 'explorer', 'farmer', 'myth', 'illusion', 'mirror', 'phantom',
'shapeshifter', 'shaman', 'skeleton', 'slime', 'squirrel', 'troll',
'tyrannosaur', 'wraith', 'wurm'
)
ON CONFLICT DO NOTHING
"""))
print(" ✓ Populated archetypes from subtypes")
self.conn.commit()
print("\n✓ Step 4 complete: Interaction graph populated")
def step_5_create_sample_interactions(self):
"""Step 5: Create sample interactions to demonstrate the system."""
print("\n" + "=" * 60)
print("STEP 5: Creating sample interactions")
print("=" * 60)
# Get a sample of cards to create interactions between
result = self.conn.execute(text("""
SELECT id, name, subtypes, types, colors
FROM mtg_cards
WHERE subtypes IS NOT NULL AND subtypes != 'null'
LIMIT 50
""")).fetchall()
if len(result) < 2:
print(" ️ Not enough cards with subtypes to create sample interactions")
return
print(f" ✓ Found {len(result)} cards with subtypes")
# Create sample synergies between cards with same archetype
print("\n🔄 Creating sample synergies...")
# Group cards by archetype
archetype_cards = {}
for card_id, name, subtypes, types, colors in result:
if subtypes:
for archetype in [a.strip() for a in subtypes.split(',') if a.strip()]:
if archetype not in archetype_cards:
archetype_cards[archetype] = []
archetype_cards[archetype].append(card_id)
# Create synergies between cards of the same archetype
synergy_count = 0
for archetype, card_ids in archetype_cards.items():
if len(card_ids) >= 2:
for i in range(len(card_ids)):
for j in range(i + 1, len(card_ids)):
self.conn.execute(text("""
INSERT INTO mtg_card_synergies (card_a_id, card_b_id, synergy_type, strength, notes)
VALUES (:card_a, :card_b, :synergy_type, :strength, :notes)
ON CONFLICT DO NOTHING
"""), {
"card_a": card_ids[i],
"card_b": card_ids[j],
"synergy_type": "archetype_support",
"strength": 3,
"notes": f"Both {archetype} cards work well together"
})
synergy_count += 1
print(f" ✓ Created {synergy_count} archetype synergies")
# Create sample counters between cards with different colors
print("\n🔄 Creating sample counters...")
counter_count = 0
for i in range(min(20, len(result))):
card_a_id = result[i][0]
card_a_colors = result[i][4]
if card_a_colors:
colors_a = [c.strip() for c in card_a_colors.split(',')]
for j in range(i + 1, min(i + 10, len(result))):
card_b_id = result[j][0]
card_b_colors = result[j][4]
if card_b_colors:
colors_b = [c.strip() for c in card_b_colors.split(',')]
# If different colors, create a counter relationship
if set(colors_a) != set(colors_b):
self.conn.execute(text("""
INSERT INTO mtg_card_counters (card_a_id, card_b_id, counter_type, strength, notes)
VALUES (:card_a, :card_b, :counter_type, :strength, :notes)
ON CONFLICT DO NOTHING
"""), {
"card_a": card_a_id,
"card_b": card_b_id,
"counter_type": "mana_disadvantage",
"strength": 2,
"notes": "Different color identities create strategic tension"
})
counter_count += 1
print(f" ✓ Created {counter_count} counter relationships")
# Create sample evolutions for cards with same name in different sets
print("\n🔄 Creating sample evolutions...")
self.conn.execute(text("""
INSERT INTO mtg_card_evolution (card_id, evolved_card_id, evolution_type, strength, notes)
SELECT DISTINCT c1.id, c2.id, 'reprinted', 2, 'Reprint in different set'
FROM mtg_cards c1
JOIN mtg_cards c2 ON c1.name = c2.name AND c1.set_id != c2.set_id
WHERE c1.subtypes IS NOT NULL AND c2.subtypes IS NOT NULL
LIMIT 50
ON CONFLICT DO NOTHING
"""))
print(" ✓ Created sample evolutions")
self.conn.commit()
print("\n✓ Step 5 complete: Sample interactions created")
def step_6_update_interaction_stats(self):
"""Step 6: Update interaction statistics for each card."""
print("\n" + "=" * 60)
print("STEP 6: Updating interaction statistics")
print("=" * 60)
# Delete existing stats
self.conn.execute(text("DELETE FROM mtg_card_interaction_stats"))
# Calculate and insert stats
self.conn.execute(text("""
INSERT INTO mtg_card_interaction_stats (
card_id, total_synergies, total_counters, total_evolution,
total_partners, total_mechanics, total_archetypes, total_themes,
avg_synergy_strength, max_synergy_strength, primary_archetype, primary_theme
)
SELECT
c.id,
COALESCE(synergies.synergy_count, 0),
COALESCE(counters.counter_count, 0),
COALESCE(evolution.evolution_count, 0),
COALESCE(partners.partner_count, 0),
COALESCE(mechanics.mechanic_count, 0),
COALESCE(archetypes.archetype_count, 0),
COALESCE(themes.theme_count, 0),
COALESCE(synergies.avg_strength, 0),
COALESCE(synergies.max_strength, 0),
archetypes.primary_archetype,
themes.primary_theme
FROM mtg_cards c
LEFT JOIN (
SELECT card_a_id as card_id, COUNT(*) as synergy_count,
AVG(strength) as avg_strength, MAX(strength) as max_strength
FROM mtg_card_synergies
GROUP BY card_a_id
) synergies ON c.id = synergies.card_id
LEFT JOIN (
SELECT card_a_id as card_id, COUNT(*) as counter_count
FROM mtg_card_counters
GROUP BY card_a_id
) counters ON c.id = counters.card_id
LEFT JOIN (
SELECT card_id as card_id, COUNT(*) as evolution_count
FROM mtg_card_evolution
GROUP BY card_id
) evolution ON c.id = evolution.card_id
LEFT JOIN (
SELECT card_a_id as card_id, COUNT(*) as partner_count
FROM mtg_card_partners
GROUP BY card_a_id
) partners ON c.id = partners.card_id
LEFT JOIN (
SELECT card_id as card_id, COUNT(*) as mechanic_count
FROM mtg_card_mechanics
GROUP BY card_id
) mechanics ON c.id = mechanics.card_id
LEFT JOIN (
SELECT card_id as card_id, COUNT(*) as archetype_count
FROM mtg_card_archetypes
GROUP BY card_id
) archetypes ON c.id = archetypes.card_id
LEFT JOIN (
SELECT card_id as card_id, COUNT(*) as theme_count
FROM mtg_card_themes
GROUP BY card_id
) themes ON c.id = themes.card_id
LEFT JOIN (
SELECT card_id, archetype as primary_archetype
FROM mtg_card_archetypes a1
WHERE id = (
SELECT MIN(a2.id)
FROM mtg_card_archetypes a2
WHERE a1.card_id = a2.card_id
)
) archetypes ON c.id = archetypes.card_id
LEFT JOIN (
SELECT card_id, theme as primary_theme
FROM mtg_card_themes t1
WHERE id = (
SELECT MIN(t2.id)
FROM mtg_card_themes t2
WHERE t1.card_id = t2.card_id
)
) themes ON c.id = themes.card_id
"""))
print(" ✓ Updated interaction statistics")
self.conn.commit()
print("\n✓ Step 6 complete: Interaction statistics updated")
def run_migration(self):
"""Run the complete migration."""
print("=" * 60)
print("🚀 Running Complete MTGJSON Migration")
print("=" * 60)
self.connect()
try:
self.step_1_add_mtgjson_columns()
self.step_2_populate_mtgjson_columns()
self.step_3_create_interaction_graph()
self.step_4_populate_interaction_graph()
self.step_5_create_sample_interactions()
self.step_6_update_interaction_stats()
self.disconnect()
print("\n" + "=" * 60)
print("✅ Complete migration finished successfully!")
print("=" * 60)
print("\n📊 Summary:")
print(" • Added 35+ MTGJSON columns to mtg_cards table")
print(" • Added 7 MTGJSON columns to mtg_sets table")
print(" • Created 13 interaction graph tables")
print(" • Populated mechanics, archetypes, and synergies")
print(" • Created sample card interactions")
except Exception as e:
print(f"\n❌ Migration failed: {e}")
raise
finally:
if self.conn:
self.conn.close()
def main():
"""Main entry point."""
migration = MTGJSONFullMigration()
migration.run_migration()
if __name__ == "__main__":
main()