feat: add run_substitution stub + clean up adapter
This commit is contained in:
1 parent
b3b664872b
commit
6135feb644
1 file changed
+34
-56
@@ -7,6 +7,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import sqlite3
|
||||
from collections import Counter
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from .pipeline.build_deck import build_deck
|
||||
@@ -30,7 +31,7 @@ class DeckBuilderAdapter:
|
||||
self.db_conn_factory = db_conn_factory
|
||||
self.model = model
|
||||
|
||||
# ── public API (matches DeckGeneratorV3.generate_deck signature) ──
|
||||
# -- public API (matches DeckGeneratorV3.generate_deck signature) --
|
||||
def generate_deck(
|
||||
self,
|
||||
commander_name: str,
|
||||
@@ -47,36 +48,27 @@ class DeckBuilderAdapter:
|
||||
Run the 7-step Ollama pipeline and return a dict that matches
|
||||
the shape expected by /api/deck/v3/generate.
|
||||
"""
|
||||
# Map legacy params -> BuildRequest
|
||||
request = BuildRequest(
|
||||
commander_name=commander_name,
|
||||
strategy_notes=strategy or None,
|
||||
collection_only=use_collection,
|
||||
budget_limit=budget_usd,
|
||||
ratios=DeckRatios(), # use defaults
|
||||
ratios=DeckRatios(),
|
||||
)
|
||||
|
||||
result: BuildResult = build_deck(request)
|
||||
deck = result.deck
|
||||
commander = deck.commander
|
||||
|
||||
# Build color_identity list
|
||||
color_identity = sorted(commander.color_identity)
|
||||
|
||||
# Convert cards to legacy dict format + check collection ownership
|
||||
cards_with_status = self._check_ownership(
|
||||
[self._card_to_dict(c) for c in deck.cards]
|
||||
)
|
||||
|
||||
# Fill basic lands to 100 total
|
||||
cards_with_status = self._fill_basic_lands(
|
||||
cards_with_status, color_identity, target_total=100
|
||||
)
|
||||
|
||||
# Compute stats
|
||||
stats = self._compute_stats(cards_with_status)
|
||||
|
||||
# Build the raw_deck structure (what Ollama/Perplexity originally returned)
|
||||
raw_deck = {
|
||||
"commander": commander.name,
|
||||
"strategy_summary": strategy or f"{commander.name} synergy deck",
|
||||
@@ -102,7 +94,7 @@ class DeckBuilderAdapter:
|
||||
},
|
||||
"color_identity": color_identity,
|
||||
"raw_deck": raw_deck,
|
||||
"cards": [c for c in cards_with_status],
|
||||
"cards": cards_with_status,
|
||||
"stats": stats,
|
||||
"strategy_summary": raw_deck["strategy_summary"],
|
||||
"bracket": raw_deck["bracket"],
|
||||
@@ -116,10 +108,27 @@ class DeckBuilderAdapter:
|
||||
"build_time_seconds": result.build_time_seconds,
|
||||
}
|
||||
|
||||
# ── helpers ───────────────────────────────────────────────────
|
||||
# -- substitution stub --
|
||||
def run_substitution(self, cards, commander: str = "", strategy: str = "", **kw):
|
||||
"""
|
||||
No-op substitution -- Ollama pipeline handles collection internally.
|
||||
Returns a SimpleNamespace matching the shape DeckGeneratorV3 returns.
|
||||
"""
|
||||
owned = sum(
|
||||
1 for c in cards
|
||||
if getattr(c, "owned", False)
|
||||
or (isinstance(c, dict) and c.get("owned"))
|
||||
)
|
||||
return SimpleNamespace(
|
||||
cards=cards,
|
||||
owned_count=owned,
|
||||
substituted_count=0,
|
||||
missing_count=len(cards) - owned,
|
||||
)
|
||||
|
||||
# -- helpers --
|
||||
@staticmethod
|
||||
def _card_to_dict(card) -> Dict[str, Any]:
|
||||
"""Convert a CardEntry to a flat dict."""
|
||||
return {
|
||||
"name": card.name,
|
||||
"count": card.quantity,
|
||||
@@ -135,7 +144,6 @@ class DeckBuilderAdapter:
|
||||
|
||||
@staticmethod
|
||||
def _card_to_raw(card) -> Dict[str, Any]:
|
||||
"""Convert a CardEntry to the raw_deck card format."""
|
||||
return {
|
||||
"name": card.name,
|
||||
"count": card.quantity,
|
||||
@@ -144,7 +152,6 @@ class DeckBuilderAdapter:
|
||||
}
|
||||
|
||||
def _check_ownership(self, cards: List[Dict]) -> List[Dict]:
|
||||
"""Check which cards are in the user's collection."""
|
||||
if not self.db_conn_factory:
|
||||
for c in cards:
|
||||
c["owned"] = False
|
||||
@@ -152,7 +159,6 @@ class DeckBuilderAdapter:
|
||||
c["from_collection"] = False
|
||||
c.setdefault("estimated_price_usd", 0)
|
||||
return cards
|
||||
|
||||
try:
|
||||
conn = self.db_conn_factory()
|
||||
for card in cards:
|
||||
@@ -160,87 +166,61 @@ class DeckBuilderAdapter:
|
||||
if not name:
|
||||
continue
|
||||
row = conn.execute(
|
||||
"SELECT name, scryfall_id, quantity, tcg_price, type_line, cmc "
|
||||
"SELECT name, scryfall_id, quantity, tcg_price "
|
||||
"FROM collection_entries WHERE name = ? COLLATE NOCASE LIMIT 1",
|
||||
(name,),
|
||||
).fetchone()
|
||||
if row:
|
||||
card["owned"] = True
|
||||
card["owned_qty"] = row["quantity"] if isinstance(row, dict) else (row[2] if row else 0)
|
||||
card["owned_qty"] = row[2] if row else 0
|
||||
card["from_collection"] = True
|
||||
card["scryfall_id"] = card.get("scryfall_id") or (row["scryfall_id"] if isinstance(row, dict) else row[1])
|
||||
price = row["tcg_price"] if isinstance(row, dict) else row[3]
|
||||
card["estimated_price_usd"] = float(price) if price else 0
|
||||
card["scryfall_id"] = card.get("scryfall_id") or row[1]
|
||||
card["estimated_price_usd"] = float(row[3]) if row[3] else 0
|
||||
else:
|
||||
card["owned"] = False
|
||||
card["owned_qty"] = 0
|
||||
card["from_collection"] = False
|
||||
card.setdefault("estimated_price_usd", 0)
|
||||
except Exception as e:
|
||||
logger.warning(f"Collection check failed: {e}")
|
||||
logger.warning("Collection check failed: %s", e)
|
||||
for c in cards:
|
||||
c.setdefault("owned", False)
|
||||
c.setdefault("owned_qty", 0)
|
||||
c.setdefault("from_collection", False)
|
||||
c.setdefault("estimated_price_usd", 0)
|
||||
|
||||
return cards
|
||||
|
||||
@staticmethod
|
||||
def _fill_basic_lands(
|
||||
cards: List[Dict], color_identity: List[str], target_total: int = 100
|
||||
) -> List[Dict]:
|
||||
"""Fill basic lands to reach target card count."""
|
||||
current = sum(c.get("count", 1) for c in cards)
|
||||
needed = target_total - current
|
||||
if needed <= 0:
|
||||
return cards
|
||||
|
||||
ci_to_basic = {
|
||||
"W": "Plains",
|
||||
"U": "Island",
|
||||
"B": "Swamp",
|
||||
"R": "Mountain",
|
||||
"G": "Forest",
|
||||
}
|
||||
|
||||
basics = [ci_to_basic[c] for c in color_identity if c in ci_to_basic]
|
||||
if not basics:
|
||||
basics = ["Wastes"]
|
||||
|
||||
# Distribute evenly
|
||||
ci_to_basic = {"W": "Plains", "U": "Island", "B": "Swamp", "R": "Mountain", "G": "Forest"}
|
||||
basics = [ci_to_basic[c] for c in color_identity if c in ci_to_basic] or ["Wastes"]
|
||||
for i in range(needed):
|
||||
land_name = basics[i % len(basics)]
|
||||
# Check if already in deck
|
||||
existing = next((c for c in cards if c["name"] == land_name), None)
|
||||
if existing:
|
||||
existing["count"] = existing.get("count", 1) + 1
|
||||
else:
|
||||
cards.append({
|
||||
"name": land_name,
|
||||
"count": 1,
|
||||
"category": "lands",
|
||||
"mana_cost": "",
|
||||
"cmc": 0,
|
||||
"type_line": "Basic Land",
|
||||
"color_identity": [],
|
||||
"owned": False,
|
||||
"owned_qty": 0,
|
||||
"from_collection": False,
|
||||
"estimated_price_usd": 0,
|
||||
"name": land_name, "count": 1, "category": "lands",
|
||||
"mana_cost": "", "cmc": 0, "type_line": "Basic Land",
|
||||
"color_identity": [], "owned": False, "owned_qty": 0,
|
||||
"from_collection": False, "estimated_price_usd": 0,
|
||||
"source": "auto-fill",
|
||||
})
|
||||
|
||||
return cards
|
||||
|
||||
@staticmethod
|
||||
def _compute_stats(cards: List[Dict]) -> Dict[str, Any]:
|
||||
"""Compute deck statistics."""
|
||||
total = sum(c.get("count", 1) for c in cards)
|
||||
categories = Counter()
|
||||
cmc_total = 0.0
|
||||
nonland_count = 0
|
||||
|
||||
for c in cards:
|
||||
count = c.get("count", 1)
|
||||
cat = c.get("category", "uncategorized")
|
||||
@@ -248,9 +228,7 @@ class DeckBuilderAdapter:
|
||||
if cat != "lands":
|
||||
cmc_total += c.get("cmc", 0) * count
|
||||
nonland_count += count
|
||||
|
||||
avg_cmc = round(cmc_total / nonland_count, 2) if nonland_count else 0
|
||||
|
||||
return {
|
||||
"total_cards": total,
|
||||
"category_breakdown": dict(categories),
|
||||
|
||||
Reference in new issue
Block a user