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

@ -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']: