- Sonar does not support response_format type json_schema
- Switch to {"type": "json_object"} which Sonar supports
- Keep json_schema/schema_name params for call-site compatibility (ignored)
- Add exc_info=True to V3 generate error logging for full tracebacks
The PRECON_INDEX list was imported by value in routes/shared.py.
When download_precon_database() or load_precon_index() reassigned it
with 'PRECON_INDEX = ...', the new list object was only visible in
precon_service module scope — shared.PRECON_INDEX (and thus the
/api/lab/precons endpoint) still pointed to the original empty [].
Fix: use PRECON_INDEX.clear() + .extend() instead of reassignment
so all modules sharing the same list reference see the updated data.
Browser was serving the old Base64-encoded ai-lab.js from cache.
Adding ?v=3 query parameter forces the browser to fetch the new
decoded JavaScript file.
The entire ai-lab.js file (139KB) was stored as a single Base64-encoded
string instead of actual JavaScript. The browser loaded it as a script
but couldn't parse it — AiLab was never defined, so the batch sim panel
never rendered.
Decoded the file back to its proper 2531-line JavaScript source. The
file already includes auto-initialization at the bottom:
if (document.readyState === 'loading')
document.addEventListener('DOMContentLoaded', AiLab.init);
else AiLab.init();
Also removed the now-redundant DOMContentLoaded listener from
index.html since the JS file handles init itself.
collection.py re-defined _ScryfallCache, _scryfall_cache,
_scryfall_rate_limit, _fetch_scryfall_api, _enrich_from_scryfall,
_parse_finish, _parse_text_line, _auto_infer_mapping, _parse_csv_content,
_TYPE_PRIORITY, and _TYPE_TARGETS locally (lines 333-509).
These local definitions shadowed the correct imports from
routes/shared.py -> services/scryfall.py. Critically, the local
_fetch_scryfall_api used Request and urlopen without importing them,
causing a NameError at runtime that was silently swallowed by the
broad except clause — making every card fail Scryfall enrichment
during collection import.
Removed all ~470 lines of duplicate code. The import endpoint now
correctly uses the functions imported from shared.py, which have
proper urllib imports and a working Scryfall cache.
The ai-lab.js IIFE defines the AiLab global with an init() method that
calls renderPanel(), detectBackend(), loadAvailableDecks(),
loadMetaCommanders(), loadPrecons(), and bindEvents() — but nothing
ever invoked it. Added a DOMContentLoaded listener after the script tag
in index.html to trigger initialization.
The API returns {"models":[...]} rather than a bare array.
Added MlModelsResponse wrapper class and updated LoadModelHistory
to unwrap the list before iterating, preventing the deserialization
exception in TrainingController.
- Replace blocking urlopen with async httpx.AsyncClient
- Add await at both call sites (deck_research, deck_generate_ai)
- Uvicorn event loop no longer stalled during Perplexity API calls
- Replace SELECT * + Python-side color loop in _get_collection_for_colors()
with projected SELECT + NOT EXISTS json_each() sub-query
- Apply same pattern to _build_collection_summary(), removing card_fits_identity()
- Add partial index idx_ce_quantity on collection_entries(quantity) WHERE qty > 0
- Handle NULL and empty-string color_identity via CASE guard
- Fix colorless commander bug: old code skipped color check entirely for []
- Add scripts/bench_deckgen.py benchmark (3-5x speedup on filtered queries)
- Add tests/test_deckgen_color_filter.py with 7 regression tests
- Add @dataclass classes _TrainingState, _PPOState, _TournamentState with snapshot() method
- Wrap all state mutations in threading.Lock() for thread safety
- Replace dict bracket access with attribute access
- Remove unnecessary global declarations from pipeline functions
Previously, init_collection_db() opened a separate sqlite3.connect() call,
ran migrations, then closed that connection — entirely outside the
thread-local pool that route handlers use via _get_db_conn().
This meant:
- The migration connection and the request-serving connection were
independent objects with no ordering guarantee under concurrent startup.
- The warm-up connection was thrown away immediately, so every handler
thread still had to open its own connection on first use.
Fix:
- init_collection_db() now calls _get_db_conn() directly, so schema
creation and migrations run on the same thread-local connection that
will serve subsequent requests.
- Added _close_db_conn() helper for explicit teardown on thread exit
(e.g. FastAPI lifespan shutdown or worker thread cleanup).
- Removed the redundant standalone conn.close() that discarded the
init connection without ever making it available to route handlers.