Consolidated constants that are duplicated in various constant files. Implemented dataclass methodoligies for tagging counter-related cards tagging

This commit is contained in:
mwisnowski 2025-08-15 09:36:37 -07:00
parent 27ee13fb54
commit 039b8fe89e
6 changed files with 228 additions and 125 deletions

View file

@ -1,4 +1,9 @@
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
from typing import Dict, List, Final, Tuple, Union, Callable
from settings import CARD_DATA_COLUMNS as CSV_REQUIRED_COLUMNS # unified
__all__ = [
'CSV_REQUIRED_COLUMNS'
]
import ast
# Commander selection configuration
@ -390,12 +395,7 @@ CSV_VALIDATION_RULES: Final[Dict[str, Dict[str, Union[str, int, float]]]] = {
'toughness': {'type': ('str', 'int', 'float', 'object'), 'pattern': r'^[\d*+-]+$'}
}
# Required columns for CSV validation
CSV_REQUIRED_COLUMNS: Final[List[str]] = [
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
]
# (CSV_REQUIRED_COLUMNS imported from settings to avoid duplication)
# DataFrame processing configuration
BATCH_SIZE: Final[int] = 1000 # Number of records to process at once

View file

@ -1,41 +1,40 @@
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
from typing import Dict, List
from settings import (
SETUP_COLORS,
COLOR_ABRV,
CARD_DATA_COLUMNS as COLUMN_ORDER, # backward compatible alias
CARD_DATA_COLUMNS as TAGGED_COLUMN_ORDER,
)
BANNED_CARDS: List[str] = [# in commander
'Ancestral Recall', 'Balance', 'Biorhythm', 'Black Lotus',
'Braids, Cabal Minion', 'Chaos Orb', 'Coalition Victory',
'Channel', 'Dockside Extortionist', 'Emrakul, the Aeons Torn',
'Erayo, Soratami Ascendant', 'Falling Star', 'Fastbond',
'Flash', 'Gifts Ungiven', 'Golos, Tireless Pilgrim',
'Griselbrand', 'Hullbreacher', 'Iona, Shield of Emeria',
'Karakas', 'Jeweled Lotus', 'Leovold, Emissary of Trest',
'Library of Alexandria', 'Limited Resources', 'Lutri, the Spellchaser',
'Mana Crypt', 'Mox Emerald', 'Mox Jet', 'Mox Pearl', 'Mox Ruby',
'Mox Sapphire', 'Nadu, Winged Wisdom', 'Panoptic Mirror',
'Paradox Engine', 'Primeval Titan', 'Prophet of Kruphix',
'Recurring Nightmare', 'Rofellos, Llanowar Emissary', 'Shahrazad',
'Sundering Titan', 'Sway of the Stars', 'Sylvan Primordial',
'Time Vault', 'Time Walk', 'Tinker', 'Tolarian Academy',
'Trade Secrets', 'Upheaval', 'Yawgmoth\'s Bargain',
# In constructed
'Invoke Prejudice', 'Cleanse', 'Stone-Throwing Devils', 'Pradesh Gypsies',
'Jihad', 'Imprison', 'Crusade'
]
__all__ = [
'SETUP_COLORS', 'COLOR_ABRV', 'COLUMN_ORDER', 'TAGGED_COLUMN_ORDER',
'BANNED_CARDS', 'MTGJSON_API_URL', 'LEGENDARY_OPTIONS', 'NON_LEGAL_SETS',
'CARD_TYPES_TO_EXCLUDE', 'CSV_PROCESSING_COLUMNS', 'SORT_CONFIG',
'FILTER_CONFIG'
]
SETUP_COLORS: List[str] = ['colorless', 'white', 'blue', 'black', 'green', 'red',
'azorius', 'orzhov', 'selesnya', 'boros', 'dimir',
'simic', 'izzet', 'golgari', 'rakdos', 'gruul',
'bant', 'esper', 'grixis', 'jund', 'naya',
'abzan', 'jeskai', 'mardu', 'sultai', 'temur',
'dune', 'glint', 'ink', 'witch', 'yore', 'wubrg']
COLOR_ABRV: List[str] = ['Colorless', 'W', 'U', 'B', 'G', 'R',
'U, W', 'B, W', 'G, W', 'R, W', 'B, U',
'G, U', 'R, U', 'B, G', 'B, R', 'G, R',
'G, U, W', 'B, U, W', 'B, R, U', 'B, G, R', 'G, R, W',
'B, G, W', 'R, U, W', 'B, R, W', 'B, G, U', 'G, R, U',
'B, G, R, W', 'B, G, R, U', 'G, R, U, W', 'B, G, U, W',
'B, R, U, W', 'B, G, R, U, W']
# Banned cards consolidated here (remains specific to setup concerns)
BANNED_CARDS: List[str] = [
# Commander banned list
'Ancestral Recall', 'Balance', 'Biorhythm', 'Black Lotus',
'Braids, Cabal Minion', 'Chaos Orb', 'Coalition Victory',
'Channel', 'Dockside Extortionist', 'Emrakul, the Aeons Torn',
'Erayo, Soratami Ascendant', 'Falling Star', 'Fastbond',
'Flash', 'Gifts Ungiven', 'Golos, Tireless Pilgrim',
'Griselbrand', 'Hullbreacher', 'Iona, Shield of Emeria',
'Karakas', 'Jeweled Lotus', 'Leovold, Emissary of Trest',
'Library of Alexandria', 'Limited Resources', 'Lutri, the Spellchaser',
'Mana Crypt', 'Mox Emerald', 'Mox Jet', 'Mox Pearl', 'Mox Ruby',
'Mox Sapphire', 'Nadu, Winged Wisdom', 'Panoptic Mirror',
'Paradox Engine', 'Primeval Titan', 'Prophet of Kruphix',
'Recurring Nightmare', 'Rofellos, Llanowar Emissary', 'Shahrazad',
'Sundering Titan', 'Sway of the Stars', 'Sylvan Primordial',
'Time Vault', 'Time Walk', 'Tinker', 'Tolarian Academy',
'Trade Secrets', 'Upheaval', "Yawgmoth's Bargain",
# Problematic / culturally sensitive or banned in other formats
'Invoke Prejudice', 'Cleanse', 'Stone-Throwing Devils', 'Pradesh Gypsies',
'Jihad', 'Imprison', 'Crusade'
]
# Constants for setup and CSV processing
MTGJSON_API_URL: str = 'https://mtgjson.com/api/v5/csv/cards.csv'
@ -105,14 +104,4 @@ FILTER_CONFIG: Dict[str, Dict[str, List[str]]] = {
}
}
COLUMN_ORDER: List[str] = [
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
]
TAGGED_COLUMN_ORDER: List[str] = [
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
]
# COLUMN_ORDER and TAGGED_COLUMN_ORDER now sourced from settings via CARD_DATA_COLUMNS

View file

@ -1,6 +1,6 @@
from __future__ import annotations
from settings import os
import os
import logging
# Create logs directory if it doesn't exist
@ -26,4 +26,13 @@ file_handler.setFormatter(NoDunderFormatter(LOG_FORMAT))
# Stream handler
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(NoDunderFormatter(LOG_FORMAT))
stream_handler.setFormatter(NoDunderFormatter(LOG_FORMAT))
# Root logger assembly helper (idempotent)
def get_logger(name: str = 'deck_builder') -> logging.Logger:
logger = logging.getLogger(name)
if not logger.handlers:
logger.setLevel(LOG_LEVEL)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
return logger

View file

@ -1,27 +1,94 @@
from __future__ import annotations
# Standard library imports
import os
from sys import exit
from typing import Dict, List, Optional, Final, Tuple, Pattern, Union, Callable
from typing import Dict, List, Optional
# Third-party imports
# ----------------------------------------------------------------------------------
# COLOR CONSTANTS
# ----------------------------------------------------------------------------------
# NOTE:
# Existing code in setup uses an ordered list (green before red) to align indices
# with the parallel COLOR_ABRV list. The previously defined COLORS list had a
# different ordering (red before green) which made it unsuitable for index based
# mapping. To avoid subtle bugs we expose two explicit constants:
# SETUP_COLORS -> ordering required for setup / abbreviation mapping
# COLORS -> legacy superset including 'commander' (kept for compatibility)
COLORS = ['colorless', 'white', 'blue', 'black', 'red', 'green',
'azorius', 'orzhov', 'selesnya', 'boros', 'dimir',
'simic', 'izzet', 'golgari', 'rakdos', 'gruul',
'bant', 'esper', 'grixis', 'jund', 'naya',
'abzan', 'jeskai', 'mardu', 'sultai', 'temur',
'dune', 'glint', 'ink', 'witch', 'yore', 'wubrg',
'commander']
SETUP_COLORS: List[str] = [
'colorless', 'white', 'blue', 'black', 'green', 'red',
'azorius', 'orzhov', 'selesnya', 'boros', 'dimir',
'simic', 'izzet', 'golgari', 'rakdos', 'gruul',
'bant', 'esper', 'grixis', 'jund', 'naya',
'abzan', 'jeskai', 'mardu', 'sultai', 'temur',
'dune', 'glint', 'ink', 'witch', 'yore', 'wubrg'
]
COLOR_ABRV: List[str] = ['Colorless', 'W', 'U', 'B', 'G', 'R',
'U, W', 'B, W', 'G, W', 'R, W', 'B, U',
'G, U', 'R, U', 'B, G', 'B, R', 'G, R',
'G, U, W', 'B, U, W', 'B, R, U', 'B, G, R', 'G, R, W',
'B, G, W', 'R, U, W', 'B, R, W', 'B, G, U', 'G, R, U',
'B, G, R, W', 'B, G, R, U', 'G, R, U, W', 'B, G, U, W',
'B, R, U, W', 'B, G, R, U, W']
# Legacy constant (includes 'commander', preserves previous external usage)
COLORS: List[str] = [
*SETUP_COLORS,
'commander'
]
COLOR_ABRV: List[str] = [
'Colorless', 'W', 'U', 'B', 'G', 'R',
'U, W', 'B, W', 'G, W', 'R, W', 'B, U',
'G, U', 'R, U', 'B, G', 'B, R', 'G, R',
'G, U, W', 'B, U, W', 'B, R, U', 'B, G, R', 'G, R, W',
'B, G, W', 'R, U, W', 'B, R, W', 'B, G, U', 'G, R, U',
'B, G, R, W', 'B, G, R, U', 'G, R, U, W', 'B, G, U, W',
'B, R, U, W', 'B, G, R, U, W'
]
# Convenience mapping from long color name to primary abbreviation
PRIMARY_COLOR_ABBR_MAP: Dict[str, str] = {
'colorless': 'Colorless', 'white': 'W', 'blue': 'U', 'black': 'B', 'green': 'G', 'red': 'R'
}
# ----------------------------------------------------------------------------------
# CARD / DATAFRAME COLUMN CONSTANTS
# ----------------------------------------------------------------------------------
# Unified column definition used across setup, tagging, and deck building modules.
# This consolidates previously duplicated lists: COLUMN_ORDER, TAGGED_COLUMN_ORDER,
# REQUIRED_COLUMNS (tag_constants), and CSV_REQUIRED_COLUMNS (builder_constants).
CARD_DATA_COLUMNS: List[str] = [
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
]
# Alias for semantic clarity in different contexts
REQUIRED_CARD_COLUMNS = CARD_DATA_COLUMNS # Validation
CARD_COLUMN_ORDER = CARD_DATA_COLUMNS # Output / ordering
# ----------------------------------------------------------------------------------
# MENU / UI CONSTANTS
# ----------------------------------------------------------------------------------
MAIN_MENU_ITEMS: List[str] = ['Build A Deck', 'Setup CSV Files', 'Tag CSV Files', 'Quit']
SETUP_MENU_ITEMS: List[str] = ['Initial Setup', 'Regenerate CSV', 'Main Menu']
CSV_DIRECTORY: str = 'csv_files'
# ----------------------------------------------------------------------------------
# DATAFRAME NA HANDLING
# ----------------------------------------------------------------------------------
FILL_NA_COLUMNS: Dict[str, Optional[str]] = {
'colorIdentity': 'Colorless', # Default color identity for cards without one
'faceName': None # Use card's name column value when face name is not available
}
# ----------------------------------------------------------------------------------
# SPECIAL CARD EXCEPTIONS
# ----------------------------------------------------------------------------------
MULTIPLE_COPY_CARDS = [
'Dragon\'s Approach', 'Hare Apparent', 'Nazgûl', 'Persistent Petitioners',
'Rat Colony', 'Relentless Rats', 'Seven Dwarves', 'Shadowborn Apostle',
'Slime Against Humanity', 'Templar Knight'
]
# Backwards compatibility exports (older modules may still import these names)
COLUMN_ORDER = CARD_COLUMN_ORDER
TAGGED_COLUMN_ORDER = CARD_COLUMN_ORDER
REQUIRED_COLUMNS = REQUIRED_CARD_COLUMNS
MAIN_MENU_ITEMS: List[str] = ['Build A Deck', 'Setup CSV Files', 'Tag CSV Files', 'Quit']

View file

@ -1,4 +1,16 @@
from typing import Dict, List, Final
from typing import Dict, List, Final, Iterable
from dataclasses import dataclass
from settings import REQUIRED_CARD_COLUMNS as REQUIRED_COLUMNS # unified column list
__all__ = [
'TRIGGERS', 'NUM_TO_SEARCH', 'TAG_GROUPS', 'PATTERN_GROUPS', 'PHRASE_GROUPS',
'CREATE_ACTION_PATTERN', 'COUNTER_TYPES', 'CREATURE_TYPES', 'NON_CREATURE_TYPES',
'OUTLAW_TYPES', 'ENCHANTMENT_TOKENS', 'ARTIFACT_TOKENS', 'REQUIRED_COLUMNS',
'TYPE_TAG_MAPPING', 'DRAW_RELATED_TAGS', 'DRAW_EXCLUSION_PATTERNS',
'EQUIPMENT_EXCLUSIONS', 'EQUIPMENT_SPECIFIC_CARDS', 'EQUIPMENT_RELATED_TAGS',
'EQUIPMENT_TEXT_PATTERNS', 'AURA_SPECIFIC_CARDS', 'VOLTRON_COMMANDER_CARDS',
'VOLTRON_PATTERNS'
]
TRIGGERS: List[str] = ['when', 'whenever', 'at']
@ -56,41 +68,75 @@ PHRASE_GROUPS: Dict[str, List[str]] = {
CREATE_ACTION_PATTERN: Final[str] = r"create|put"
# Creature/Counter types
COUNTER_TYPES: List[str] = [r'\+0/\+1', r'\+0/\+2', r'\+1/\+0', r'\+1/\+2', r'\+2/\+0', r'\+2/\+2',
'-0/-1', '-0/-2', '-1/-0', '-1/-2', '-2/-0', '-2/-2',
'Acorn', 'Aegis', 'Age', 'Aim', 'Arrow', 'Arrowhead','Awakening',
'Bait', 'Blaze', 'Blessing', 'Blight',' Blood', 'Bloddline',
'Bloodstain', 'Book', 'Bounty', 'Brain', 'Bribery', 'Brick',
'Burden', 'Cage', 'Carrion', 'Charge', 'Coin', 'Collection',
'Component', 'Contested', 'Corruption', 'CRANK!', 'Credit',
'Croak', 'Corpse', 'Crystal', 'Cube', 'Currency', 'Death',
'Defense', 'Delay', 'Depletion', 'Descent', 'Despair', 'Devotion',
'Divinity', 'Doom', 'Dream', 'Duty', 'Echo', 'Egg', 'Elixir',
'Ember', 'Energy', 'Enlightened', 'Eon', 'Eruption', 'Everything',
'Experience', 'Eyeball', 'Eyestalk', 'Fade', 'Fate', 'Feather',
'Feeding', 'Fellowship', 'Fetch', 'Filibuster', 'Finality', 'Flame',
'Flood', 'Foreshadow', 'Fungus', 'Fury', 'Fuse', 'Gem', 'Ghostform',
'Glpyh', 'Gold', 'Growth', 'Hack', 'Harmony', 'Hatching', 'Hatchling',
'Healing', 'Hit', 'Hope',' Hone', 'Hoofprint', 'Hour', 'Hourglass',
'Hunger', 'Ice', 'Imposter', 'Incarnation', 'Incubation', 'Infection',
'Influence', 'Ingenuity', 'Intel', 'Intervention', 'Invitation',
'Isolation', 'Javelin', 'Judgment', 'Keyword', 'Ki', 'Kick',
'Knickknack', 'Knowledge', 'Landmark', 'Level', 'Loot', 'Lore',
'Loyalty', 'Luck', 'Magnet', 'Manabond', 'Manifestation', 'Mannequin',
'Mask', 'Matrix', 'Memory', 'Midway', 'Mine', 'Mining', 'Mire',
'Music', 'Muster', 'Necrodermis', 'Nest', 'Net', 'Night', 'Oil',
'Omen', 'Ore', 'Page', 'Pain', 'Palliation', 'Paralyzing', 'Pause',
'Petal', 'Petrification', 'Phyresis', 'Phylatery', 'Pin', 'Plague',
'Plot', 'Point', 'Poison', 'Polyp', 'Possession', 'Pressure', 'Prey',
'Pupa', 'Quest', 'Rad', 'Rejection', 'Reprieve', 'Rev', 'Revival',
'Ribbon', 'Ritual', 'Rope', 'Rust', 'Scream', 'Scroll', 'Shell',
'Shield', 'Silver', 'Shred', 'Sleep', 'Sleight', 'Slime', 'Slumber',
'Soot', 'Soul', 'Spark', 'Spite', 'Spore', 'Stash', 'Storage',
'Story', 'Strife', 'Study', 'Stun', 'Supply', 'Suspect', 'Takeover',
'Task', 'Ticket', 'Tide', 'Time', 'Tower', 'Training', 'Trap',
'Treasure', 'Unity', 'Unlock', 'Valor', 'Velocity', 'Verse',
'Vitality', 'Void', 'Volatile', 'Vortex', 'Vow', 'Voyage', 'Wage',
'Winch', 'Wind', 'Wish']
"""Counter type vocabularies."""
# Power/Toughness modifier counters (regex fragments already escaped where needed)
PT_COUNTER_TYPES: List[str] = [
r'\+0/\+1', r'\+0/\+2', r'\+1/\+0', r'\+1/\+2', r'\+2/\+0', r'\+2/\+2',
'-0/-1', '-0/-2', '-1/-0', '-1/-2', '-2/-0', '-2/-2'
]
# Named counters (alphabetical within rough thematic blocks)
NAMED_COUNTER_TYPES: List[str] = [
'Acorn', 'Aegis', 'Age', 'Aim', 'Arrow', 'Arrowhead', 'Awakening',
'Bait', 'Blaze', 'Blessing', 'Blight', 'Blood', 'Bloodline', 'Bloodstain', 'Book',
'Bounty', 'Brain', 'Bribery', 'Brick', 'Burden', 'Cage', 'Carrion', 'Charge', 'Coin',
'Collection', 'Component', 'Contested', 'Corruption', 'CRANK!', 'Credit', 'Croak',
'Corpse', 'Crystal', 'Cube', 'Currency', 'Death', 'Defense', 'Delay', 'Depletion',
'Descent', 'Despair', 'Devotion', 'Divinity', 'Doom', 'Dream', 'Duty', 'Echo', 'Egg',
'Elixir', 'Ember', 'Energy', 'Enlightened', 'Eon', 'Eruption', 'Everything',
'Experience', 'Eyeball', 'Eyestalk', 'Fade', 'Fate', 'Feather', 'Feeding',
'Fellowship', 'Fetch', 'Filibuster', 'Finality', 'Flame', 'Flood', 'Foreshadow',
'Fungus', 'Fury', 'Fuse', 'Gem', 'Ghostform', 'Glyph', 'Gold', 'Growth', 'Hack',
'Harmony', 'Hatching', 'Hatchling', 'Healing', 'Hit', 'Hope', 'Hone', 'Hoofprint',
'Hour', 'Hourglass', 'Hunger', 'Ice', 'Imposter', 'Incarnation', 'Incubation',
'Infection', 'Influence', 'Ingenuity', 'Intel', 'Intervention', 'Invitation',
'Isolation', 'Javelin', 'Judgment', 'Keyword', 'Ki', 'Kick', 'Knickknack',
'Knowledge', 'Landmark', 'Level', 'Loot', 'Lore', 'Loyalty', 'Luck', 'Magnet',
'Manabond', 'Manifestation', 'Mannequin', 'Mask', 'Matrix', 'Memory', 'Midway',
'Mine', 'Mining', 'Mire', 'Music', 'Muster', 'Necrodermis', 'Nest', 'Net', 'Night',
'Oil', 'Omen', 'Ore', 'Page', 'Pain', 'Palliation', 'Paralyzing', 'Pause', 'Petal',
'Petrification', 'Phyresis', 'Phylactery', 'Pin', 'Plague', 'Plot', 'Point', 'Poison',
'Polyp', 'Possession', 'Pressure', 'Prey', 'Pupa', 'Quest', 'Rad', 'Rejection',
'Reprieve', 'Rev', 'Revival', 'Ribbon', 'Ritual', 'Rope', 'Rust', 'Scream', 'Scroll',
'Shell', 'Shield', 'Silver', 'Shred', 'Sleep', 'Sleight', 'Slime', 'Slumber', 'Soot',
'Soul', 'Spark', 'Spite', 'Spore', 'Stash', 'Storage', 'Story', 'Strife', 'Study',
'Stun', 'Supply', 'Suspect', 'Takeover', 'Task', 'Ticket', 'Tide', 'Time', 'Tower',
'Training', 'Trap', 'Treasure', 'Unity', 'Unlock', 'Valor', 'Velocity', 'Verse',
'Vitality', 'Void', 'Volatile', 'Vortex', 'Vow', 'Voyage', 'Wage', 'Winch', 'Wind',
'Wish'
]
# Dataclass describing a counter pattern and display label
@dataclass(frozen=True)
class CounterSpec:
pattern: str # Regex fragment (without trailing " counter")
label: str # Human-readable label (used in tag text)
group: str # 'pt' or 'named' (for future filtering)
def search_pattern(self) -> str:
"""Full regex used for searching (matches singular/plural)."""
return rf"{self.pattern} counter[s]?"
# Helper to derive label from pattern (unescape common sequences)
def _derive_label(p: str) -> str:
return p.replace('\\+','+')
def _build_counter_specs(pt_list: Iterable[str], named_list: Iterable[str]) -> List[CounterSpec]:
specs: List[CounterSpec] = []
specs.extend(CounterSpec(pattern=p, label=_derive_label(p), group='pt') for p in pt_list)
specs.extend(CounterSpec(pattern=p, label=p, group='named') for p in named_list)
return specs
ALL_COUNTER_SPECS: List[CounterSpec] = _build_counter_specs(PT_COUNTER_TYPES, NAMED_COUNTER_TYPES)
# Backward-compatible flat list (legacy usage)
COUNTER_TYPES: List[str] = [s.pattern for s in ALL_COUNTER_SPECS]
# Basic duplication guard (fails fast during import if misconfigured)
if len(COUNTER_TYPES) != len(set(COUNTER_TYPES)):
duplicate = sorted({p for p in COUNTER_TYPES if COUNTER_TYPES.count(p) > 1})
raise ValueError(f"Duplicate counter patterns detected: {duplicate}")
CREATURE_TYPES: List[str] = ['Advisor', 'Aetherborn', 'Alien', 'Ally', 'Angel', 'Antelope', 'Ape', 'Archer', 'Archon', 'Armadillo',
'Army', 'Artificer', 'Assassin', 'Assembly-Worker', 'Astartes', 'Atog', 'Aurochs', 'Automaton',
@ -145,12 +191,7 @@ ENCHANTMENT_TOKENS: List[str] = ['Cursed Role', 'Monster Role', 'Royal Role', 'S
ARTIFACT_TOKENS: List[str] = ['Blood', 'Clue', 'Food', 'Gold', 'Incubator',
'Junk','Map','Powerstone', 'Treasure']
# Constants for DataFrame validation and processing
REQUIRED_COLUMNS: List[str] = [
'name', 'faceName', 'edhrecRank', 'colorIdentity', 'colors',
'manaCost', 'manaValue', 'type', 'creatureTypes', 'text',
'power', 'toughness', 'keywords', 'themeTags', 'layout', 'side'
]
# (REQUIRED_COLUMNS imported from settings to avoid duplication)
# Mapping of card types to their corresponding theme tags
TYPE_TAG_MAPPING: Dict[str, List[str]] = {

View file

@ -3044,18 +3044,15 @@ def tag_for_special_counters(df: pd.DataFrame, color: str) -> None:
start_time = pd.Timestamp.now()
try:
# Process each counter type
# Process each counter type (supports singular/plural)
counter_counts = {}
for counter_type in tag_constants.COUNTER_TYPES:
# Create pattern for this counter type
pattern = f'{counter_type} counter'
for spec in tag_constants.ALL_COUNTER_SPECS:
pattern = spec.search_pattern()
mask = tag_utils.create_text_mask(df, pattern)
if mask.any():
# Apply tags via rules engine
tags = [f'{counter_type} Counters', 'Counters Matter']
tag_utils.apply_rules(df, [ { 'mask': mask, 'tags': tags } ])
counter_counts[counter_type] = mask.sum()
tags = [f'{spec.label} Counters', 'Counters Matter']
tag_utils.apply_rules(df, [ {'mask': mask, 'tags': tags} ])
counter_counts[spec.label] = int(mask.sum())
# Log results
duration = (pd.Timestamp.now() - start_time).total_seconds()