Called via `import routes._wire` at the bottom of lab_api.py (after all
symbols are defined). Registers every shared object into routes.shared
and mounts all five APIRouters onto the FastAPI app.
Batch 6/6 — final wiring step.
Two fixes:
1. _parse_text_decklist(): replace fragile startswith() section-header
detection with case-insensitive regex so [Commander], [Main], [Deck]
etc. are always recognised regardless of capitalisation.
2. add_deck_card endpoint: when a card is added with is_commander=1,
also update the deck-level commander_name + commander_scryfall_id
so the portrait and top-bar label stay in sync.
Two fixes:
1. _parse_text_decklist(): replace fragile startswith() section header
detection with proper case-insensitive regex so [Commander], [Main],
[Deck] etc. are always recognised regardless of capitalisation.
2. add_deck_card endpoint: when a card is added with is_commander=1,
also update the deck-level commander_name and commander_scryfall_id
fields so the portrait and top-bar label stay in sync without
requiring a full reload.
- run_n() accepts 2-4 decks; run() wraps for backward compat
- _select_attack_target() heuristic for multiplayer combat targeting
- N-player elimination order tracking with finish positions
- Removal scans all opponents for scariest creature
- ML snapshot captures all N players
- Log entries use per-seat boards dict with legacy boardA/boardB for 2-player
Two places lost the commander flag:
1. _load_deck_cards_by_name() .dck fallback — used a flat in_cards
boolean for [Main], [Deck], and [Commander] sections so it couldn't
tell which section a card came from. Now tracks current_section and
sets is_commander=1 for cards in [Commander].
2. parse_dck_file() — stored section='Commander' in the card dict but
the frontend filters on c.is_commander, not c.section. Now includes
is_commander: 1/0 alongside the existing section field.
Replace all print() calls with structured logging using the Python
logging module. This provides:
- Named loggers per subsystem: commander_ai_lab.api, .batch, .sim,
.coach, .deckgen, .collection, .scanner, .ml, .cache, .pplx
- Dual output: console (StreamHandler) + rotating file (RotatingFileHandler)
at logs/commander-ai-lab.log (25 MB x 5 backups = 125 MB max)
- Configurable via env vars: CAL_LOG_DIR, CAL_LOG_MAX_MB, CAL_LOG_BACKUP_COUNT
- --verbose / -v flag for DEBUG-level output
- overnight-run.py: separate rotating log at logs/overnight-run.log (10 MB x 5)
- Sub-module loggers in ml/, scanner/, deck_sources/, src/ all propagate
to the root commander_ai_lab logger
- Stripped redundant [TAG] prefixes from messages (logger name carries context)
- Format: '2026-03-13 14:00:00 INFO [commander_ai_lab.batch] message'
Files changed: lab_api.py, overnight-run.py, and 11 sub-module .py files
176 total print() calls converted (101 in lab_api.py, 76 in sub-modules)
GameResult.to_dict() now returns both legacy keys (winner, playerA, playerB)
that the UI expects AND new N-player keys (winningSeat, playerResults) for
the Java BatchResult schema.
When collection_entries has multiple rows with the same scryfall_id
(e.g. Counterspell imported from two different sets), the LEFT JOIN
on deck_cards produced duplicate rows — making it appear like 2 copies
were added or removed.
Fixed all 6 deck_cards-to-collection_entries JOINs to use a
GROUP BY scryfall_id subquery, guaranteeing 1:1 cardinality.
- Added _fill_basic_lands() post-processing for V1 and V3 generators
- Strips LLM-provided basics, then distributes evenly across commander colors
- Always hits exactly 100 cards regardless of LLM output
- Frontend group counts now sum card.count instead of entry count
- Cards with count > 1 show '5x Forest' style prefix
- Colorless commanders get Wastes
Previously the coach required running simulations first to generate a
deck-reports/*.json file. Now if no simulation report is found, the
endpoint builds a lightweight DeckReport on the fly from the deck_cards
table — pulling card names, types, CMC, and color identity directly from
the DB. The LLM can still analyze composition and give advice, just
without simulation-derived performance metrics (impactScore, etc.).
Also removes the '[no report]' tag from the dropdown since all decks
can now be coached.
The decks table has 'commander_name' not 'commander'. The SQL query
was throwing OperationalError which the frontend silently caught,
resulting in an empty dropdown.
Root cause: Perplexity deck generation hit the 8192 max_tokens limit,
truncating the JSON response mid-string at card ~70-80. json.loads()
fails with 'Unterminated string' and the fallback also couldn't recover.
Fixes:
- Bump DECK_GEN_MAX_TOKENS from 8192 to 16384 (primary fix)
- Add _repair_truncated_json() to PerplexityClient — if token limit is
still hit, salvages all complete card objects from the truncated JSON
instead of returning a 422 error
- Add OUTPUT EFFICIENCY instructions to system prompt to reduce per-card
verbosity (shorter reasons, fewer synergy_with entries)
- Add 'truncated' flag to response so UI can warn if deck is incomplete
- Log warning when recovered deck has <90 cards
Backend returns [{deck_id, deck_name, commander, has_report, ...}] directly,
not {decks: [...]}. Frontend was reading data.decks which was always undefined.
Now properly unwraps the array, shows deck name + commander, slugifies name
for coach session deck_id, and marks decks without reports.
- lab_api.py: Flip serving priority — ui/ folder (legacy) served first, React dist/ only as fallback
- Legacy UI has full features: card images, mana costs, type grouping, collection filters, EDHREC recs, ratio dashboard, commander portraits, 3-column deck builder
- React SPA was causing blank pages and missing functionality
- Installer no longer builds or bundles React dist/
- All 7 pages work: Batch Sim, Collection, Deck Builder, Auto Gen, Simulator, Coach, Training
1. /api/lab/status: StatusResponse now includes run_id, games_completed,
total_games, current_decks aliases so the React SPA gets the field
names it expects.
2. /api/lab/decks returns {decks: [...]}, /api/lab/history returns
{results: [...]}, /api/lab/precons returns {precons: [...]} — but
the frontend expected bare arrays. Updated lab.ts API client to
unwrap these wrapper objects automatically.
GET /api/lab/status and /api/lab/result required batchId as a
mandatory query param, but the React SPA calls them with no param
on page load. FastAPI returned 422 Unprocessable Content, which
caused the BatchSimPage to crash and the UI to flash then vanish
in a retry loop.
Fix: batchId is now Optional[str] = None. When omitted, returns
the most recent active batch (or idle defaults for status).
Removed unused imports in DecksPage, TrainingPage, and decks.ts
that caused noUnusedLocals/noUnusedParameters errors during
the React build step in the installer.