2025-09-02 11:39:14 -07:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2025-09-29 21:32:08 -07:00
|
|
|
from typing import Any, Dict, Iterable, List, Optional
|
2025-09-02 11:39:14 -07:00
|
|
|
from deck_builder import builder_constants as bc
|
|
|
|
|
from .combo_utils import detect_for_summary as _detect_for_summary
|
|
|
|
|
|
|
|
|
|
|
2025-10-07 15:56:57 -07:00
|
|
|
def _owned_set_helper() -> set[str]:
|
|
|
|
|
try:
|
|
|
|
|
from .build_utils import owned_set as _owned_set # type: ignore
|
|
|
|
|
|
|
|
|
|
return _owned_set()
|
|
|
|
|
except Exception:
|
|
|
|
|
try:
|
|
|
|
|
from . import owned_store
|
|
|
|
|
|
|
|
|
|
return {str(n).strip().lower() for n in owned_store.get_names()}
|
|
|
|
|
except Exception:
|
|
|
|
|
return set()
|
|
|
|
|
|
|
|
|
|
|
2025-09-29 21:32:08 -07:00
|
|
|
def _sanitize_tag_list(values: Iterable[Any]) -> List[str]:
|
|
|
|
|
cleaned: List[str] = []
|
|
|
|
|
for raw in values or []: # type: ignore[arg-type]
|
|
|
|
|
text = str(raw or "").strip()
|
|
|
|
|
if not text:
|
|
|
|
|
continue
|
|
|
|
|
if text.startswith("['"):
|
|
|
|
|
text = text[2:]
|
|
|
|
|
if text.endswith("']") and len(text) >= 2:
|
|
|
|
|
text = text[:-2]
|
|
|
|
|
if text.startswith('"') and text.endswith('"') and len(text) >= 2:
|
|
|
|
|
text = text[1:-1]
|
|
|
|
|
if text.startswith("'") and text.endswith("'") and len(text) >= 2:
|
|
|
|
|
text = text[1:-1]
|
|
|
|
|
text = text.strip(" []\t\n\r")
|
|
|
|
|
if not text:
|
|
|
|
|
continue
|
|
|
|
|
cleaned.append(text)
|
|
|
|
|
return cleaned
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_summary_tags(summary: dict[str, Any] | None) -> None:
|
|
|
|
|
if not summary:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
type_breakdown = summary.get("type_breakdown") or {}
|
|
|
|
|
cards_by_type = type_breakdown.get("cards") or {}
|
|
|
|
|
for clist in cards_by_type.values():
|
|
|
|
|
if not isinstance(clist, list):
|
|
|
|
|
continue
|
|
|
|
|
for card in clist:
|
|
|
|
|
if not isinstance(card, dict):
|
|
|
|
|
continue
|
|
|
|
|
tags = card.get("tags") or []
|
|
|
|
|
if tags:
|
|
|
|
|
card["tags"] = _sanitize_tag_list(tags)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_theme_label(raw: Any) -> str:
|
|
|
|
|
text = str(raw or "").strip()
|
|
|
|
|
if not text:
|
|
|
|
|
return ""
|
|
|
|
|
text = text.replace("_", " ")
|
|
|
|
|
words = []
|
|
|
|
|
for part in text.split():
|
|
|
|
|
if not part:
|
|
|
|
|
continue
|
|
|
|
|
if part.isupper():
|
|
|
|
|
words.append(part)
|
|
|
|
|
else:
|
|
|
|
|
words.append(part[0].upper() + part[1:].lower() if len(part) > 1 else part.upper())
|
|
|
|
|
return " ".join(words)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_theme_list(values: Iterable[Any]) -> List[str]:
|
|
|
|
|
seen: set[str] = set()
|
|
|
|
|
result: List[str] = []
|
|
|
|
|
for raw in values or []: # type: ignore[arg-type]
|
|
|
|
|
label = format_theme_label(raw)
|
|
|
|
|
if not label:
|
|
|
|
|
continue
|
|
|
|
|
if len(label) <= 1:
|
|
|
|
|
continue
|
|
|
|
|
key = label.casefold()
|
|
|
|
|
if key in seen:
|
|
|
|
|
continue
|
|
|
|
|
seen.add(key)
|
|
|
|
|
result.append(label)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2025-09-02 11:39:14 -07:00
|
|
|
def summary_ctx(
|
|
|
|
|
*,
|
|
|
|
|
summary: dict | None,
|
|
|
|
|
commander: str | None = None,
|
|
|
|
|
tags: list[str] | None = None,
|
2025-09-29 21:32:08 -07:00
|
|
|
meta: Optional[dict[str, Any]] = None,
|
2025-09-02 11:39:14 -07:00
|
|
|
include_versions: bool = True,
|
|
|
|
|
) -> Dict[str, Any]:
|
|
|
|
|
"""Build a unified context payload for deck summary panels.
|
|
|
|
|
|
|
|
|
|
Provides owned_set, game_changers, combos/synergies, and detector versions.
|
|
|
|
|
"""
|
2025-09-29 21:32:08 -07:00
|
|
|
_normalize_summary_tags(summary)
|
|
|
|
|
|
2025-09-02 11:39:14 -07:00
|
|
|
det = _detect_for_summary(summary, commander_name=commander or "") if summary else {"combos": [], "synergies": [], "versions": {}}
|
|
|
|
|
combos = det.get("combos", [])
|
2025-09-29 21:32:08 -07:00
|
|
|
synergies_raw = det.get("synergies", []) or []
|
|
|
|
|
# Flatten synergy tag names while preserving appearance order and collapsing duplicates case-insensitively
|
|
|
|
|
synergy_tags: list[str] = []
|
|
|
|
|
seen: set[str] = set()
|
|
|
|
|
for entry in synergies_raw:
|
|
|
|
|
if entry is None:
|
|
|
|
|
continue
|
|
|
|
|
if isinstance(entry, dict):
|
|
|
|
|
tags = entry.get("tags", []) or []
|
|
|
|
|
else:
|
|
|
|
|
tags = getattr(entry, "tags", None) or []
|
|
|
|
|
for tag in tags:
|
|
|
|
|
text = str(tag).strip()
|
|
|
|
|
if not text:
|
|
|
|
|
continue
|
|
|
|
|
key = text.casefold()
|
|
|
|
|
if key in seen:
|
|
|
|
|
continue
|
|
|
|
|
seen.add(key)
|
|
|
|
|
synergy_tags.append(text)
|
|
|
|
|
if not synergy_tags:
|
|
|
|
|
fallback_sources: list[str] = []
|
|
|
|
|
for collection in (tags or []):
|
|
|
|
|
fallback_sources.append(str(collection))
|
|
|
|
|
meta_obj = meta or {}
|
|
|
|
|
meta_keys = [
|
|
|
|
|
"display_themes",
|
|
|
|
|
"resolved_themes",
|
|
|
|
|
"auto_filled_themes",
|
|
|
|
|
"random_display_themes",
|
|
|
|
|
"random_resolved_themes",
|
|
|
|
|
"random_auto_filled_themes",
|
|
|
|
|
"primary_theme",
|
|
|
|
|
"secondary_theme",
|
|
|
|
|
"tertiary_theme",
|
|
|
|
|
]
|
|
|
|
|
for key in meta_keys:
|
|
|
|
|
value = meta_obj.get(key) if isinstance(meta_obj, dict) else None
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
fallback_sources.extend(str(v) for v in value)
|
|
|
|
|
elif isinstance(value, str):
|
|
|
|
|
fallback_sources.append(value)
|
|
|
|
|
for raw in fallback_sources:
|
|
|
|
|
label = format_theme_label(raw)
|
|
|
|
|
if not label:
|
|
|
|
|
continue
|
|
|
|
|
key = label.casefold()
|
|
|
|
|
if key in seen:
|
|
|
|
|
continue
|
|
|
|
|
seen.add(key)
|
|
|
|
|
synergy_tags.append(label)
|
2025-09-02 11:39:14 -07:00
|
|
|
versions = det.get("versions", {} if include_versions else None)
|
|
|
|
|
return {
|
2025-10-07 15:56:57 -07:00
|
|
|
"owned_set": _owned_set_helper(),
|
2025-09-02 11:39:14 -07:00
|
|
|
"game_changers": bc.GAME_CHANGERS,
|
|
|
|
|
"combos": combos,
|
2025-09-29 21:32:08 -07:00
|
|
|
"synergies": synergy_tags,
|
|
|
|
|
"synergy_pairs": synergies_raw,
|
2025-09-02 11:39:14 -07:00
|
|
|
"versions": versions,
|
|
|
|
|
"commander": commander,
|
|
|
|
|
"tags": tags or [],
|
|
|
|
|
}
|