bugfix: update editorial_governance to use small smaple set of themes instead of looking for the full library

This commit is contained in:
matt 2025-10-06 09:50:56 -07:00
parent f68f8949e8
commit 7679176414
15 changed files with 716 additions and 9 deletions

View file

@ -96,6 +96,11 @@ WEB_AUTO_ENFORCE=0 # dockerhub: WEB_AUTO_ENFORCE="0"
# THEME_CATALOG_YAML_SCAN_INTERVAL_SEC=2.0 # Poll for YAML changes (dev)
# WEB_THEME_FILTER_PREWARM=0 # 1=prewarm common filters for faster first renders
############################
# Testing & CI helpers
############################
# EDITORIAL_TEST_USE_FIXTURES=0 # 1=stage lightweight catalog fixtures for editorial governance tests (CI convenience)
############################
# Headless Export Options
############################

View file

@ -44,6 +44,8 @@ jobs:
- name: Run regression & unit tests (editorial subset + enforcement)
run: |
python -m pytest -q code/tests/test_theme_description_fallback_regression.py code/tests/test_synergy_pairs_and_provenance.py code/tests/test_editorial_governance_phase_d_closeout.py code/tests/test_theme_editorial_min_examples_enforced.py
env:
EDITORIAL_TEST_USE_FIXTURES: '1'
- name: Ratchet proposal (non-blocking)
run: |
python code/scripts/ratchet_description_thresholds.py > ratchet_proposal.json || true

View file

@ -49,6 +49,7 @@ This format follows Keep a Changelog principles and aims for Semantic Versioning
- Setup/tagging auto-refresh now runs the partner suggestion dataset builder so `config/analytics/partner_synergy.json` tracks the latest commander catalog and deck exports without manual scripts.
- CSV/TXT deck exports append commander metadata columns, text headers include partner mode and colors, and summary sidecars embed serialized combined commander details without breaking legacy consumers.
- Partner commander previews in Step 2 and the build summary now mirror the primary commander card layout (including hover metadata and high-res art) so both selections share identical interactions.
- Editorial governance CI stages lightweight catalog fixtures when `EDITORIAL_TEST_USE_FIXTURES=1`, avoiding the need to sync `config/themes/catalog` into source control.
### Fixed
- Regenerated `background_cards.csv` and tightened background detection so the picker only lists true Background enchantments, preventing "Choose a Background" commanders from appearing as illegal partners and restoring background availability when the CSV was missing.

View file

@ -261,6 +261,11 @@ Most defaults are defined in `docker-compose.yml` and documented in `.env.exampl
| `OWNED_CARDS_DIR` / `CARD_LIBRARY_DIR` | `/app/owned_cards` | Override owned library path. |
| `CARD_INDEX_EXTRA_CSV` | _(blank)_ | Inject extra CSV data into the card index. |
### Testing aids
| Variable | Default | Purpose |
| --- | --- | --- |
| `EDITORIAL_TEST_USE_FIXTURES` | `0` | When set to `1`, editorial governance tests stage lightweight catalog fixtures instead of requiring generated YAML/JSON data. |
### Supplemental themes
| Variable | Default | Purpose |
| --- | --- | --- |

View file

@ -0,0 +1,41 @@
id: graveyard-fixture
display_name: Reanimate
curated_synergies:
- Graveyard Loops
- Sacrifice
- Big Threats
enforced_synergies:
- Tutor
inferred_synergies:
- Recursion
synergies:
- Graveyard Loops
- Sacrifice
- Big Threats
- Tutor
primary_color: Black
secondary_color: Blue
notes: Fixture entry for editorial governance CI checks.
example_commanders:
- Meren of Clan Nel Toth
- Chainer, Dementia Master
- The Scarab God
- Sheoldred, Whispering One
- Araumi of the Dead Tide
example_cards:
- Reanimate
- Animate Dead
- Victimize
- Entomb
- Buried Alive
synergy_commanders:
- Gisa and Geralf - Synergy (Graveyard Loops)
- Kess, Dissident Mage - Synergy (Tutor)
- Syr Konrad, the Grim - Synergy (Sacrifice)
deck_archetype: Control
popularity_bucket: Common
description: Loads high-impact cards into the graveyard early and reanimates them for explosive tempo or combo loops.
editorial_quality: reviewed
metadata_info:
script: build_theme_catalog.py
last_backfill: '2024-09-03T00:00:00Z'

View file

@ -0,0 +1,41 @@
id: tokens-fixture
display_name: Tokens Matter
curated_synergies:
- Go Wide
- Anthem
- Sacrifice Fodder
enforced_synergies:
- Card Draw
inferred_synergies:
- Populate
synergies:
- Go Wide
- Anthem
- Sacrifice Fodder
- Populate
primary_color: White
secondary_color: Green
notes: Fixture entry to ensure metadata coverage for tests.
example_commanders:
- Rhys the Redeemed
- Trostani, Selesnya's Voice
- Adeline, Resplendent Cathar
- Jetmir, Nexus of Revels
- Kyler, Sigardian Emissary
example_cards:
- Anointed Procession
- Parallel Lives
- Heroic Reinforcements
- Secure the Wastes
- Beastmaster Ascension
synergy_commanders:
- Chatterfang, Squirrel General - Synergy (Sacrifice Fodder)
- Emmara, Soul of the Accord - Synergy (Go Wide)
- Tana, the Bloodsower - Synergy (Populate)
deck_archetype: Midrange
popularity_bucket: Very Common
description: Goes wide with creature tokens then converts mass into damage, draw, drain, or sacrifice engines.
editorial_quality: reviewed
metadata_info:
script: build_theme_catalog.py
last_backfill: '2024-09-02T00:00:00Z'

View file

@ -0,0 +1,41 @@
id: treasure-fixture
display_name: Treasure
curated_synergies:
- Tokens
- Artifacts
- Sacrifice
enforced_synergies:
- Ramp
inferred_synergies:
- Combo
synergies:
- Tokens
- Artifacts
- Sacrifice
- Ramp
primary_color: Red
secondary_color: Black
notes: Sample fixture catalog entry for CI tests.
example_commanders:
- Prosper, Tome-Bound
- Magda, Brazen Outlaw
- Kalain, Reclusive Painter
- Marionette Master
- Jolene, the Plunder Queen
example_cards:
- Dockside Extortionist
- Professional Face-Breaker
- Brass's Bounty
- Revel in Riches
- Pitiless Plunderer
synergy_commanders:
- Brudiclad, Telchor Engineer - Synergy (Tokens)
- Ruthless Technomancer - Synergy (Sacrifice)
- Galazeth Prismari - Synergy (Artifacts)
deck_archetype: Combo
popularity_bucket: Common
description: Generates Treasure tokens as flexible ramp and combo fuel enabling explosive payoff turns.
editorial_quality: reviewed
metadata_info:
script: build_theme_catalog.py
last_backfill: '2024-09-01T00:00:00Z'

View file

@ -0,0 +1,40 @@
from __future__ import annotations
import os
import shutil
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
THEMES_DIR = ROOT / 'config' / 'themes'
CATALOG_DIR = THEMES_DIR / 'catalog'
FIXTURE_ROOT = Path(__file__).resolve().parent / 'fixtures' / 'editorial_catalog'
def ensure_editorial_fixtures(force: bool | None = None) -> None:
"""Populate minimal editorial catalog fixtures when real data is absent.
The repository intentionally does not track `config/themes/catalog` because the
production catalog is generated dynamically. For CI we stage a small curated
sample so governance tests can exercise logic without requiring the full data
dump. Existing files are left untouched to avoid clobbering local updates.
"""
if force is None:
flag = os.environ.get('EDITORIAL_TEST_USE_FIXTURES', '').strip().lower()
force = flag in {'1', 'true', 'yes', 'on'}
if not FIXTURE_ROOT.exists():
return
catalog_fixture = FIXTURE_ROOT / 'catalog'
if catalog_fixture.exists():
CATALOG_DIR.mkdir(parents=True, exist_ok=True)
for src in catalog_fixture.glob('*.yml'):
dest = CATALOG_DIR / src.name
if force or not dest.exists():
shutil.copy(src, dest)
theme_list_fixture = FIXTURE_ROOT / 'theme_list.json'
theme_list_target = THEMES_DIR / 'theme_list.json'
if theme_list_fixture.exists() and (force or not theme_list_target.exists()):
theme_list_target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(theme_list_fixture, theme_list_target)

View file

@ -0,0 +1,41 @@
id: graveyard-fixture
display_name: Reanimate
curated_synergies:
- Graveyard Loops
- Sacrifice
- Big Threats
enforced_synergies:
- Tutor
inferred_synergies:
- Recursion
synergies:
- Graveyard Loops
- Sacrifice
- Big Threats
- Tutor
primary_color: Black
secondary_color: Blue
notes: Fixture entry for editorial governance CI checks.
example_commanders:
- Meren of Clan Nel Toth
- Chainer, Dementia Master
- The Scarab God
- Sheoldred, Whispering One
- Araumi of the Dead Tide
example_cards:
- Reanimate
- Animate Dead
- Victimize
- Entomb
- Buried Alive
synergy_commanders:
- Gisa and Geralf - Synergy (Graveyard Loops)
- Kess, Dissident Mage - Synergy (Tutor)
- Syr Konrad, the Grim - Synergy (Sacrifice)
deck_archetype: Control
popularity_bucket: Common
description: Loads high-impact cards into the graveyard early and reanimates them for explosive tempo or combo loops.
editorial_quality: reviewed
metadata_info:
script: build_theme_catalog.py
last_backfill: '2024-09-03T00:00:00Z'

View file

@ -0,0 +1,41 @@
id: tokens-fixture
display_name: Tokens Matter
curated_synergies:
- Go Wide
- Anthem
- Sacrifice Fodder
enforced_synergies:
- Card Draw
inferred_synergies:
- Populate
synergies:
- Go Wide
- Anthem
- Sacrifice Fodder
- Populate
primary_color: White
secondary_color: Green
notes: Fixture entry to ensure metadata coverage for tests.
example_commanders:
- Rhys the Redeemed
- Trostani, Selesnya's Voice
- Adeline, Resplendent Cathar
- Jetmir, Nexus of Revels
- Kyler, Sigardian Emissary
example_cards:
- Anointed Procession
- Parallel Lives
- Heroic Reinforcements
- Secure the Wastes
- Beastmaster Ascension
synergy_commanders:
- Chatterfang, Squirrel General - Synergy (Sacrifice Fodder)
- Emmara, Soul of the Accord - Synergy (Go Wide)
- Tana, the Bloodsower - Synergy (Populate)
deck_archetype: Midrange
popularity_bucket: Very Common
description: Goes wide with creature tokens then converts mass into damage, draw, drain, or sacrifice engines.
editorial_quality: reviewed
metadata_info:
script: build_theme_catalog.py
last_backfill: '2024-09-02T00:00:00Z'

View file

@ -0,0 +1,41 @@
id: treasure-fixture
display_name: Treasure
curated_synergies:
- Tokens
- Artifacts
- Sacrifice
enforced_synergies:
- Ramp
inferred_synergies:
- Combo
synergies:
- Tokens
- Artifacts
- Sacrifice
- Ramp
primary_color: Red
secondary_color: Black
notes: Sample fixture catalog entry for CI tests.
example_commanders:
- Prosper, Tome-Bound
- Magda, Brazen Outlaw
- Kalain, Reclusive Painter
- Marionette Master
- Jolene, the Plunder Queen
example_cards:
- Dockside Extortionist
- Professional Face-Breaker
- Brass's Bounty
- Revel in Riches
- Pitiless Plunderer
synergy_commanders:
- Brudiclad, Telchor Engineer - Synergy (Tokens)
- Ruthless Technomancer - Synergy (Sacrifice)
- Galazeth Prismari - Synergy (Artifacts)
deck_archetype: Combo
popularity_bucket: Common
description: Generates Treasure tokens as flexible ramp and combo fuel enabling explosive payoff turns.
editorial_quality: reviewed
metadata_info:
script: build_theme_catalog.py
last_backfill: '2024-09-01T00:00:00Z'

View file

@ -20,6 +20,10 @@ from pathlib import Path
from datetime import datetime
from typing import Dict, Any, List, Set
import pytest
from code.tests.editorial_test_utils import ensure_editorial_fixtures
ROOT = Path(__file__).resolve().parents[2]
THEMES_DIR = ROOT / 'config' / 'themes'
@ -28,6 +32,14 @@ CATALOG_DIR = THEMES_DIR / 'catalog'
HISTORY = THEMES_DIR / 'description_fallback_history.jsonl'
MAPPING = THEMES_DIR / 'description_mapping.yml'
USE_FIXTURES = (
os.environ.get('EDITORIAL_TEST_USE_FIXTURES', '').strip().lower() in {'1', 'true', 'yes', 'on'}
or not CATALOG_DIR.exists()
or not any(CATALOG_DIR.glob('*.yml'))
)
ensure_editorial_fixtures(force=USE_FIXTURES)
def _load_catalog() -> Dict[str, Any]:
data = json.loads(CATALOG_JSON.read_text(encoding='utf-8'))
@ -70,7 +82,8 @@ def test_kpi_history_integrity():
def test_metadata_info_block_coverage():
import yaml # type: ignore
assert CATALOG_DIR.exists(), "Catalog YAML directory missing"
if not CATALOG_DIR.exists() or not any(CATALOG_DIR.glob('*.yml')):
pytest.skip('Catalog YAML directory missing; editorial fixtures not staged.')
total = 0
with_prov = 0
for p in CATALOG_DIR.glob('*.yml'):

View file

@ -3,10 +3,22 @@ import os
from pathlib import Path
import subprocess
import pytest
from code.tests.editorial_test_utils import ensure_editorial_fixtures
ROOT = Path(__file__).resolve().parents[2]
SCRIPT = ROOT / 'code' / 'scripts' / 'build_theme_catalog.py'
CATALOG_DIR = ROOT / 'config' / 'themes' / 'catalog'
USE_FIXTURES = (
os.environ.get('EDITORIAL_TEST_USE_FIXTURES', '').strip().lower() in {'1', 'true', 'yes', 'on'}
or not CATALOG_DIR.exists()
or not any(CATALOG_DIR.glob('*.yml'))
)
ensure_editorial_fixtures(force=USE_FIXTURES)
def run(cmd, env=None):
env_vars = os.environ.copy()
@ -53,7 +65,8 @@ def test_synergy_pairs_fallback_and_metadata_info(tmp_path):
run(['python', str(SCRIPT), '--force-backfill-yaml', '--backfill-yaml'], env={'EDITORIAL_INCLUDE_FALLBACK_SUMMARY': '1'})
# Locate YAML and verify metadata_info (or legacy provenance) inserted
yaml_path = CATALOG_DIR / f"{candidate.lower().replace(' ', '-')}.yml"
if yaml_path.exists():
raw = yaml_path.read_text(encoding='utf-8').splitlines()
if not yaml_path.exists():
pytest.skip('Catalog YAML directory missing expected theme; fixture was not staged.')
raw = yaml_path.read_text(encoding='utf-8').splitlines()
has_meta = any(line.strip().startswith(('metadata_info:','provenance:')) for line in raw)
assert has_meta, 'metadata_info block missing after forced backfill'

View file

@ -9,18 +9,35 @@ below the policy threshold after Phase D close-out.
from __future__ import annotations
import os
from pathlib import Path
import json
from pathlib import Path
import pytest
from code.tests.editorial_test_utils import ensure_editorial_fixtures
ROOT = Path(__file__).resolve().parents[2]
CATALOG = ROOT / 'config' / 'themes' / 'theme_list.json'
THEMES_DIR = ROOT / 'config' / 'themes'
CATALOG_DIR = THEMES_DIR / 'catalog'
CATALOG = THEMES_DIR / 'theme_list.json'
FIXTURE_THEME_LIST = Path(__file__).resolve().parent / 'fixtures' / 'editorial_catalog' / 'theme_list.json'
USE_FIXTURES = (
os.environ.get('EDITORIAL_TEST_USE_FIXTURES', '').strip().lower() in {'1', 'true', 'yes', 'on'}
or not CATALOG_DIR.exists()
or not any(CATALOG_DIR.glob('*.yml'))
)
ensure_editorial_fixtures(force=USE_FIXTURES)
def test_all_themes_meet_minimum_examples():
os.environ['EDITORIAL_MIN_EXAMPLES_ENFORCE'] = '1'
min_required = int(os.environ.get('EDITORIAL_MIN_EXAMPLES', '5'))
assert CATALOG.exists(), 'theme_list.json missing (run build script before tests)'
data = json.loads(CATALOG.read_text(encoding='utf-8'))
source = FIXTURE_THEME_LIST if USE_FIXTURES else CATALOG
if not source.exists():
pytest.skip('theme list unavailable; editorial fixtures not staged.')
data = json.loads(source.read_text(encoding='utf-8'))
assert 'themes' in data
short = []
for entry in data['themes']:

View file

@ -16729,6 +16729,13 @@
"editorial_quality": "draft",
"description": "Builds around Partner leveraging synergies with Partner with and Performer Kindred."
},
{
"id": "partner-father-son",
"theme": "Partner - Father & Son",
"synergies": [],
"popularity_bucket": "Rare",
"description": "Builds around the Partner - Father & Son theme and its supporting synergies."
},
{
"id": "partner-with",
"theme": "Partner with",
@ -28012,12 +28019,370 @@
"generated_from": "merge (analytics + curated YAML + whitelist)",
"metadata_info": {
"mode": "merge",
"generated_at": "2025-10-05T05:42:11",
"generated_at": "2025-10-06T09:48:40",
"curated_yaml_files": 739,
"synergy_cap": 5,
"inference": "pmi",
"version": "phase-b-merge-v1",
"catalog_hash": "e9f1a812ddd1e5ed543e9cd233132ac8f6d1aa28f0a476d80ea6fd71fc5f74a5"
},
"description_fallback_summary": null
"description_fallback_summary": {
"total_themes": 741,
"generic_total": 285,
"generic_with_synergies": 266,
"generic_plain": 19,
"generic_pct": 38.46,
"top_generic_by_frequency": [
{
"theme": "Little Fellas",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 7126,
"description": "Builds around Little Fellas leveraging synergies with Banding and Licid Kindred."
},
{
"theme": "Combat Matters",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 6344,
"description": "Builds around Combat Matters leveraging synergies with Aggro and Voltron."
},
{
"theme": "Interaction",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 4142,
"description": "Builds around Interaction leveraging synergies with Removal and Combat Tricks."
},
{
"theme": "Toughness Matters",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 3482,
"description": "Builds around Toughness Matters leveraging synergies with Defender and Egg Kindred."
},
{
"theme": "Leave the Battlefield",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 3092,
"description": "Builds around Leave the Battlefield leveraging synergies with Blink and Enter the Battlefield."
},
{
"theme": "Enter the Battlefield",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 3088,
"description": "Builds around Enter the Battlefield leveraging synergies with Blink and Reanimate."
},
{
"theme": "Card Draw",
"popularity_bucket": "Very Common",
"synergy_count": 17,
"total_frequency": 2699,
"description": "Builds around Card Draw leveraging synergies with Loot and Wheels."
},
{
"theme": "Life Matters",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 2388,
"description": "Builds around Life Matters leveraging synergies with Lifegain and Lifedrain."
},
{
"theme": "Flying",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 2213,
"description": "Builds around Flying leveraging synergies with Phoenix Kindred and Archon Kindred."
},
{
"theme": "Removal",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 1594,
"description": "Builds around Removal leveraging synergies with Soulshift and Interaction."
},
{
"theme": "Legends Matter",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 1536,
"description": "Builds around Legends Matter leveraging synergies with Historics Matter and Superfriends."
},
{
"theme": "Topdeck",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 1104,
"description": "Builds around Topdeck leveraging synergies with Scry and Surveil."
},
{
"theme": "Discard Matters",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 1050,
"description": "Builds around Discard Matters leveraging synergies with Loot and Wheels."
},
{
"theme": "Unconditional Draw",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 1045,
"description": "Builds around Unconditional Draw leveraging synergies with Dredge and Learn."
},
{
"theme": "Combat Tricks",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 857,
"description": "Builds around Combat Tricks leveraging synergies with Flash and Strive."
},
{
"theme": "Protection",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 806,
"description": "Builds around Protection leveraging synergies with Ward and Hexproof."
},
{
"theme": "Exile Matters",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 712,
"description": "Builds around Exile Matters leveraging synergies with Impulse and Suspend."
},
{
"theme": "Board Wipes",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 647,
"description": "Builds around Board Wipes leveraging synergies with Bracket:MassLandDenial and Pingers."
},
{
"theme": "Pingers",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 637,
"description": "Builds around Pingers leveraging synergies with Extort and Devil Kindred."
},
{
"theme": "Loot",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 523,
"description": "Builds around Loot leveraging synergies with Card Draw and Discard Matters."
},
{
"theme": "Cantrips",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 514,
"description": "Builds around Cantrips leveraging synergies with Clue Token and Investigate."
},
{
"theme": "X Spells",
"popularity_bucket": "Very Common",
"synergy_count": 5,
"total_frequency": 505,
"description": "Builds around X Spells leveraging synergies with Ravenous and Firebending."
},
{
"theme": "Conditional Draw",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 459,
"description": "Builds around Conditional Draw leveraging synergies with Max speed and Start your engines!."
},
{
"theme": "Toolbox",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 453,
"description": "Builds around Toolbox leveraging synergies with Entwine and Bracket:TutorNonland."
},
{
"theme": "Cost Reduction",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 431,
"description": "Builds around Cost Reduction leveraging synergies with Affinity and Freerunning."
},
{
"theme": "Flash",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 429,
"description": "Builds around Flash leveraging synergies with Evoke and Combat Tricks."
},
{
"theme": "Haste",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 396,
"description": "Builds around Haste leveraging synergies with Hellion Kindred and Phoenix Kindred."
},
{
"theme": "Lifelink",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 396,
"description": "Builds around Lifelink leveraging synergies with Lifegain Triggers and Lifegain."
},
{
"theme": "Vigilance",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 396,
"description": "Builds around Vigilance leveraging synergies with Angel Kindred and Mount Kindred."
},
{
"theme": "Counterspells",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 395,
"description": "Builds around Counterspells leveraging synergies with Control and Stax."
},
{
"theme": "Mana Dork",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 336,
"description": "Builds around Mana Dork leveraging synergies with Firebending and Scion Kindred."
},
{
"theme": "Cycling",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 299,
"description": "Builds around Cycling leveraging synergies with Landcycling and Basic landcycling."
},
{
"theme": "Transform",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 296,
"description": "Builds around Transform leveraging synergies with Incubator Token and Incubate."
},
{
"theme": "Bracket:TutorNonland",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 293,
"description": "Builds around Bracket:TutorNonland leveraging synergies with Transmute and Bracket:GameChanger."
},
{
"theme": "Clones",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 282,
"description": "Builds around Clones leveraging synergies with Myriad and Populate."
},
{
"theme": "Scry",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 281,
"description": "Builds around Scry leveraging synergies with Topdeck and Role token."
},
{
"theme": "Reach",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 274,
"description": "Builds around Reach leveraging synergies with Spider Kindred and Archer Kindred."
},
{
"theme": "First strike",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 248,
"description": "Builds around First strike leveraging synergies with Banding and Kithkin Kindred."
},
{
"theme": "Politics",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 247,
"description": "Builds around Politics leveraging synergies with Encore and Melee."
},
{
"theme": "Defender",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 230,
"description": "Builds around Defender leveraging synergies with Wall Kindred and Egg Kindred."
},
{
"theme": "Menace",
"popularity_bucket": "Common",
"synergy_count": 5,
"total_frequency": 225,
"description": "Builds around Menace leveraging synergies with Warlock Kindred and Blood Token."
},
{
"theme": "Deathtouch",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 191,
"description": "Builds around Deathtouch leveraging synergies with Basilisk Kindred and Scorpion Kindred."
},
{
"theme": "Land Types Matter",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 185,
"description": "Builds around Land Types Matter leveraging synergies with Plainscycling and Mountaincycling."
},
{
"theme": "Equip",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 184,
"description": "Builds around Equip leveraging synergies with Job select and For Mirrodin!."
},
{
"theme": "Spell Copy",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 182,
"description": "Builds around Spell Copy leveraging synergies with Storm and Replicate."
},
{
"theme": "Landwalk",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 170,
"description": "Builds around Landwalk leveraging synergies with Swampwalk and Islandwalk."
},
{
"theme": "Impulse",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 164,
"description": "Builds around Impulse leveraging synergies with Junk Tokens and Junk Token."
},
{
"theme": "Morph",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 140,
"description": "Builds around Morph leveraging synergies with Beast Kindred and Illusion Kindred."
},
{
"theme": "Devoid",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 114,
"description": "Builds around Devoid leveraging synergies with Ingest and Processor Kindred."
},
{
"theme": "Resource Engine",
"popularity_bucket": "Uncommon",
"synergy_count": 5,
"total_frequency": 101,
"description": "Builds around Resource Engine leveraging synergies with Energy and Energy Counters."
}
]
}
}