205 lines
9.1 KiB
Python
205 lines
9.1 KiB
Python
"""
|
|
routes/shared.py
|
|
================
|
|
Single source of truth for everything every router needs.
|
|
|
|
Contains:
|
|
- Config class + CFG singleton
|
|
- Logging setup + all named loggers
|
|
- FastAPI app instance
|
|
- In-memory state (active_batches, COMMANDER_META, PRECON_INDEX, AI_PROFILES)
|
|
- All Pydantic request/response models
|
|
- SQLite DB helpers (_get_db_conn, init_collection_db)
|
|
- Scryfall cache + enrichment helpers
|
|
- Collection helpers (_detect_card_roles, _classify_card_type,
|
|
_build_collection_filters, _parse_finish, _parse_csv_content,
|
|
_parse_text_line, _auto_infer_mapping, _row_to_dict, _add_image_url,
|
|
_snake_to_camel, VALID_SORT_FIELDS, _JSON_FIELDS)
|
|
- Deck helpers (_get_deck_or_404, _compute_deck_analysis, _check_ratio_limit,
|
|
_TYPE_TARGETS, _TYPE_PRIORITY, _classify_card_type)
|
|
- Import helpers (_import_from_url, _fetch_archidekt_deck,
|
|
_fetch_edhrec_average, _parse_text_decklist, _save_profile_to_dck,
|
|
_to_edhrec_slug, _http_get, _API_HEADERS, _edhrec_cache_get,
|
|
_edhrec_cache_set)
|
|
- Precon helpers (PRECON_DIR, PRECON_INDEX, download_precon_database,
|
|
load_precon_index, GITHUB_PRECON_URL, PRECON_CACHE_HOURS)
|
|
- Sim helpers (build_java_command, get_java17, BatchState, parse_dck_file,
|
|
AI_PROFILES, run_batch_subprocess, _run_process_blocking,
|
|
_run_deepseek_batch_thread, _load_deck_cards_by_name, _get_deepseek_brain)
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import csv
|
|
import io
|
|
import json
|
|
import logging
|
|
import logging.handlers
|
|
import os
|
|
import re
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
import time
|
|
import uuid
|
|
from collections import Counter
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
from urllib.request import urlopen, Request
|
|
from urllib.error import URLError
|
|
|
|
try:
|
|
from fastapi import FastAPI, HTTPException, BackgroundTasks, Request as FastAPIRequest
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
|
from fastapi.staticfiles import StaticFiles
|
|
from pydantic import BaseModel
|
|
except ImportError:
|
|
print("ERROR: FastAPI not installed. Run: pip install fastapi uvicorn")
|
|
sys.exit(1)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Configuration
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
# Configuration (imported from models/state)
|
|
from models.state import Config, CFG
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Logging Setup
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
_LOG_FORMAT = "%(asctime)s %(levelname)-5s [%(name)s] %(message)s"
|
|
_LOG_DATE_FMT = "%Y-%m-%d %H:%M:%S"
|
|
_LOG_DIR = Path(os.environ.get("CAL_LOG_DIR", "logs"))
|
|
_LOG_MAX_BYTES = int(os.environ.get("CAL_LOG_MAX_MB", "25")) * 1024 * 1024
|
|
_LOG_BACKUP_COUNT = int(os.environ.get("CAL_LOG_BACKUP_COUNT", "5"))
|
|
|
|
|
|
def setup_logging(level: int = logging.INFO) -> None:
|
|
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
root_logger = logging.getLogger("commander_ai_lab")
|
|
root_logger.setLevel(level)
|
|
if root_logger.handlers:
|
|
return
|
|
formatter = logging.Formatter(_LOG_FORMAT, datefmt=_LOG_DATE_FMT)
|
|
console = logging.StreamHandler(sys.stdout)
|
|
console.setLevel(level)
|
|
console.setFormatter(formatter)
|
|
root_logger.addHandler(console)
|
|
file_handler = logging.handlers.RotatingFileHandler(
|
|
_LOG_DIR / "commander-ai-lab.log",
|
|
maxBytes=_LOG_MAX_BYTES,
|
|
backupCount=_LOG_BACKUP_COUNT,
|
|
encoding="utf-8",
|
|
)
|
|
file_handler.setLevel(level)
|
|
file_handler.setFormatter(formatter)
|
|
root_logger.addHandler(file_handler)
|
|
|
|
|
|
log = logging.getLogger("commander_ai_lab.api")
|
|
log_batch = logging.getLogger("commander_ai_lab.batch")
|
|
log_sim = logging.getLogger("commander_ai_lab.sim")
|
|
log_coach = logging.getLogger("commander_ai_lab.coach")
|
|
log_deckgen = logging.getLogger("commander_ai_lab.deckgen")
|
|
log_collect = logging.getLogger("commander_ai_lab.collection")
|
|
log_scan = logging.getLogger("commander_ai_lab.scanner")
|
|
log_ml = logging.getLogger("commander_ai_lab.ml")
|
|
log_cache = logging.getLogger("commander_ai_lab.cache")
|
|
log_pplx = logging.getLogger("commander_ai_lab.pplx")
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# In-Memory State
|
|
# ══════════════════════════════════════════════════════════════
|
|
|
|
# In-Memory State (imported from models/state)
|
|
from models.state import (
|
|
BatchState, active_batches, COMMANDER_META,
|
|
BUILTIN_COMMANDERS, load_commander_meta,
|
|
)
|
|
|
|
|
|
# Pydantic Models (imported from models/)
|
|
from models.requests import (
|
|
StartRequest, ImportUrlRequest, ImportTextRequest, MetaFetchRequest,
|
|
CreateDeckRequest, UpdateDeckRequest, AddDeckCardRequest,
|
|
PatchDeckCardRequest, BulkAddRequest, BulkAddRecommendedRequest,
|
|
DeckGenerationSourceConfig, DeckGenerationRequest,
|
|
DeckGenV3Request, DeckGenV3SubstituteRequest,
|
|
)
|
|
from models.responses import (
|
|
StartResponse, StatusResponse, DeckInfo, GeneratedDeckCard,
|
|
)
|
|
|
|
|
|
# Collection Database (imported from services/database.py)
|
|
from services.database import (
|
|
COLLECTION_DB_PATH, _get_db_conn, init_collection_db,
|
|
_JSON_FIELDS, VALID_SORT_FIELDS,
|
|
_row_to_dict, _snake_to_camel, _add_image_url, _build_collection_filters,
|
|
)
|
|
# Card Role Detection (imported from services/card_analysis.py)
|
|
from services.card_analysis import _detect_card_roles
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Deck Helpers (imported from services/deck_service.py)
|
|
from services.deck_service import (
|
|
_TYPE_PRIORITY, _TYPE_TARGETS, _classify_card_type, _get_deck_or_404,
|
|
_compute_deck_analysis, _check_ratio_limit, _to_edhrec_slug,
|
|
parse_dck_file, _save_profile_to_dck, _load_deck_cards_by_name,
|
|
)
|
|
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Scryfall Cache (imported from services/scryfall.py)
|
|
from services.scryfall import (
|
|
SCRYFALL_CACHE_DB_PATH, SCRYFALL_CACHE_TTL_SECONDS, _API_HEADERS,
|
|
_ScryfallCache, _scryfall_cache, _scryfall_lock, _scryfall_last_call,
|
|
_scryfall_rate_limit, _fetch_scryfall_api, _enrich_from_scryfall, _scryfall_fuzzy_lookup,
|
|
)
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Import Helpers (extracted to services/import_helpers.py)
|
|
from services.import_helpers import (
|
|
_http_get, _fetch_archidekt_deck, _fetch_edhrec_average,
|
|
_import_from_url, _parse_text_decklist, _save_profile_to_dck,
|
|
_edhrec_cache, _EDHREC_CACHE_TTL, _edhrec_cache_get, _edhrec_cache_set,
|
|
)
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Collection Import Helpers (extracted to services/import_service.py)
|
|
from services.import_service import (
|
|
_parse_finish, _parse_text_line, _auto_infer_mapping, _parse_csv_content,
|
|
)
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Precon Helpers (extracted to services/precon_service.py)
|
|
from services.precon_service import (
|
|
PRECON_DIR, PRECON_INDEX, GITHUB_PRECON_URL, PRECON_CACHE_HOURS,
|
|
load_precon_index, _sanitize_filename, _deck_to_dck, download_precon_database,
|
|
_get_precon_dir,
|
|
)
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# AI Profiles (imported from models/state)
|
|
from models.state import AI_PROFILES
|
|
|
|
# ══════════════════════════════════════════════════════════════
|
|
# Java / Sim Helpers (extracted to services/forge_runner.py)
|
|
from services.forge_runner import (
|
|
_find_java17, get_java17, build_java_command,
|
|
run_batch_subprocess, _run_process_blocking,
|
|
_run_deepseek_batch_thread, _get_deepseek_brain,
|
|
)
|
|
|
|
_ml_logging_enabled = True
|