Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd94435993 | ||
|
|
dff7ae2860 |
No files matched your search
+10
-16
@@ -22,15 +22,10 @@ except ImportError:
|
|||||||
print("ERROR: FastAPI not installed. Run: pip install fastapi uvicorn")
|
print("ERROR: FastAPI not installed. Run: pip install fastapi uvicorn")
|
||||||
import sys; sys.exit(1)
|
import sys; sys.exit(1)
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG, COMMANDER_META, load_commander_meta
|
||||||
CFG,
|
from services.database import init_collection_db
|
||||||
setup_logging,
|
from services.logging import setup_logging
|
||||||
init_collection_db,
|
from services.precon_service import PRECON_INDEX, download_precon_database
|
||||||
download_precon_database,
|
|
||||||
load_commander_meta,
|
|
||||||
PRECON_INDEX,
|
|
||||||
COMMANDER_META,
|
|
||||||
)
|
|
||||||
from routes.collection import router as collection_router
|
from routes.collection import router as collection_router
|
||||||
from routes.deckbuilder import router as deckbuilder_router
|
from routes.deckbuilder import router as deckbuilder_router
|
||||||
from routes.precon import router as precon_router
|
from routes.precon import router as precon_router
|
||||||
@@ -80,15 +75,14 @@ async def health_check():
|
|||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def _on_startup():
|
async def _on_startup():
|
||||||
"""Ensure DB and precon index are ready (supports uvicorn --reload)."""
|
"""Ensure DB and precon index are ready (supports uvicorn --reload)."""
|
||||||
import routes.shared as _shared
|
|
||||||
# Ensure precon_dir is resolved even when started via uvicorn directly
|
# Ensure precon_dir is resolved even when started via uvicorn directly
|
||||||
if not CFG.precon_dir:
|
if not CFG.precon_dir:
|
||||||
CFG.precon_dir = _resolve_precon_dir()
|
CFG.precon_dir = _resolve_precon_dir()
|
||||||
_shared.init_collection_db()
|
init_collection_db()
|
||||||
if not _shared.PRECON_INDEX:
|
if not PRECON_INDEX:
|
||||||
_shared.download_precon_database()
|
download_precon_database()
|
||||||
if not _shared.COMMANDER_META:
|
if not COMMANDER_META:
|
||||||
_shared.load_commander_meta()
|
load_commander_meta()
|
||||||
|
|
||||||
# ── Static UI ──────────────────────────────────────────────────
|
# ── Static UI ──────────────────────────────────────────────────
|
||||||
_legacy_ui_dir = Path(__file__).parent / "ui"
|
_legacy_ui_dir = Path(__file__).parent / "ui"
|
||||||
@@ -288,7 +282,7 @@ def main():
|
|||||||
CFG.ximilar_api_key = args.ximilar_key
|
CFG.ximilar_api_key = args.ximilar_key
|
||||||
CFG.pplx_api_key = args.pplx_key
|
CFG.pplx_api_key = args.pplx_key
|
||||||
|
|
||||||
from routes.shared import get_java17
|
from services.forge_runner import get_java17
|
||||||
j17 = get_java17()
|
j17 = get_java17()
|
||||||
|
|
||||||
log.info("Commander AI Lab — API Server v3.0.0")
|
log.info("Commander AI Lab — API Server v3.0.0")
|
||||||
|
|||||||
+1
-1
@@ -2,5 +2,5 @@
|
|||||||
routes package — API routers for Commander AI Lab.
|
routes package — API routers for Commander AI Lab.
|
||||||
|
|
||||||
Each module defines a FastAPI APIRouter with real endpoint implementations.
|
Each module defines a FastAPI APIRouter with real endpoint implementations.
|
||||||
All shared helpers, models, and state live in routes.shared.
|
Shared helpers live in services/, models live in models/.
|
||||||
"""
|
"""
|
||||||
+4
-6
@@ -33,12 +33,10 @@ from fastapi import APIRouter, HTTPException
|
|||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from models.requests import CoachRequestBody, CoachChatMessage, CoachChatRequest, CoachApplyRequest, CoachGoalsRequest
|
from models.requests import CoachRequestBody, CoachChatMessage, CoachChatRequest, CoachApplyRequest, CoachGoalsRequest
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG
|
||||||
CFG,
|
from services.database import _get_db_conn
|
||||||
log_coach, log_deckgen,
|
from services.deck_service import _compute_deck_analysis
|
||||||
_get_db_conn,
|
from services.logging import log_coach, log_deckgen
|
||||||
_compute_deck_analysis,
|
|
||||||
)
|
|
||||||
|
|
||||||
router = APIRouter(tags=["coach"])
|
router = APIRouter(tags=["coach"])
|
||||||
|
|
||||||
|
|||||||
+15
-24
@@ -25,30 +25,21 @@ from typing import Optional
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG
|
||||||
CFG,
|
from services.card_analysis import _detect_card_roles
|
||||||
_get_db_conn,
|
from services.database import (
|
||||||
_row_to_dict,
|
_get_db_conn, _row_to_dict, _add_image_url, _build_collection_filters,
|
||||||
_add_image_url,
|
_snake_to_camel, VALID_SORT_FIELDS, _JSON_FIELDS,
|
||||||
_build_collection_filters,
|
)
|
||||||
_detect_card_roles,
|
from services.deck_service import _classify_card_type
|
||||||
_classify_card_type,
|
from services.import_service import (
|
||||||
_enrich_from_scryfall,
|
_parse_finish, _parse_text_line, _parse_csv_content, _auto_infer_mapping,
|
||||||
_fetch_scryfall_api,
|
)
|
||||||
_parse_finish,
|
from services.logging import log_collect, log_cache
|
||||||
_parse_text_line,
|
from services.scryfall import (
|
||||||
_parse_csv_content,
|
SCRYFALL_CACHE_DB_PATH, SCRYFALL_CACHE_TTL_SECONDS, _API_HEADERS,
|
||||||
_auto_infer_mapping,
|
_scryfall_cache, _scryfall_rate_limit,
|
||||||
_scryfall_cache,
|
_enrich_from_scryfall, _fetch_scryfall_api,
|
||||||
_scryfall_rate_limit,
|
|
||||||
_snake_to_camel,
|
|
||||||
VALID_SORT_FIELDS,
|
|
||||||
_JSON_FIELDS,
|
|
||||||
SCRYFALL_CACHE_DB_PATH,
|
|
||||||
SCRYFALL_CACHE_TTL_SECONDS,
|
|
||||||
_API_HEADERS,
|
|
||||||
log_collect,
|
|
||||||
log_cache,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(tags=["collection"])
|
router = APIRouter(tags=["collection"])
|
||||||
|
|||||||
+18
-28
@@ -36,35 +36,25 @@ from urllib.parse import quote
|
|||||||
from fastapi import APIRouter, HTTPException, Request as FastAPIRequest
|
from fastapi import APIRouter, HTTPException, Request as FastAPIRequest
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG
|
||||||
CFG,
|
from models.requests import (
|
||||||
_get_db_conn,
|
CreateDeckRequest, UpdateDeckRequest, AddDeckCardRequest,
|
||||||
_row_to_dict,
|
PatchDeckCardRequest, BulkAddRequest, BulkAddRecommendedRequest,
|
||||||
_add_image_url,
|
)
|
||||||
_get_deck_or_404,
|
from services.card_analysis import _detect_card_roles
|
||||||
_compute_deck_analysis,
|
from services.database import _get_db_conn, _row_to_dict, _add_image_url
|
||||||
_check_ratio_limit,
|
from services.deck_service import (
|
||||||
_classify_card_type,
|
_get_deck_or_404, _compute_deck_analysis, _check_ratio_limit,
|
||||||
_detect_card_roles,
|
_classify_card_type, _to_edhrec_slug, _TYPE_TARGETS,
|
||||||
_enrich_from_scryfall,
|
|
||||||
_fetch_scryfall_api,
|
|
||||||
_scryfall_rate_limit,
|
|
||||||
_to_edhrec_slug,
|
|
||||||
_edhrec_cache_get,
|
|
||||||
_edhrec_cache_set,
|
|
||||||
_fetch_edhrec_average,
|
|
||||||
_save_profile_to_dck,
|
_save_profile_to_dck,
|
||||||
_parse_text_decklist,
|
)
|
||||||
_API_HEADERS,
|
from services.import_helpers import (
|
||||||
_TYPE_TARGETS,
|
_fetch_edhrec_average, _parse_text_decklist,
|
||||||
CreateDeckRequest,
|
_edhrec_cache_get, _edhrec_cache_set,
|
||||||
UpdateDeckRequest,
|
)
|
||||||
AddDeckCardRequest,
|
from services.logging import log_collect, log_deckgen
|
||||||
PatchDeckCardRequest,
|
from services.scryfall import (
|
||||||
BulkAddRequest,
|
_API_HEADERS, _enrich_from_scryfall, _fetch_scryfall_api, _scryfall_rate_limit,
|
||||||
BulkAddRecommendedRequest,
|
|
||||||
log_collect,
|
|
||||||
log_deckgen,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(tags=["deckbuilder"])
|
router = APIRouter(tags=["deckbuilder"])
|
||||||
|
|||||||
+7
-8
@@ -33,17 +33,16 @@ import httpx
|
|||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
from models.requests import DeckResearchRequest, DeckGenerateAIRequest
|
|
||||||
|
|
||||||
import routes.coach as _coach # for _deck_gen_v3 / _deck_gen_v3_error module-level access
|
import routes.coach as _coach # for _deck_gen_v3 / _deck_gen_v3_error module-level access
|
||||||
from routes.shared import (
|
from models.state import CFG
|
||||||
CFG,
|
from models.requests import (
|
||||||
log_deckgen, log_pplx,
|
|
||||||
_get_db_conn,
|
|
||||||
_scryfall_rate_limit, _API_HEADERS,
|
|
||||||
DeckGenerationRequest, DeckGenV3Request, DeckGenV3SubstituteRequest,
|
DeckGenerationRequest, DeckGenV3Request, DeckGenV3SubstituteRequest,
|
||||||
GeneratedDeckCard, DeckGenerationSourceConfig,
|
DeckGenerationSourceConfig, DeckResearchRequest, DeckGenerateAIRequest,
|
||||||
)
|
)
|
||||||
|
from models.responses import GeneratedDeckCard
|
||||||
|
from services.database import _get_db_conn
|
||||||
|
from services.logging import log_deckgen, log_pplx
|
||||||
|
from services.scryfall import _scryfall_rate_limit, _API_HEADERS
|
||||||
from services.deck_service import _write_dck_file, _build_dck_lines
|
from services.deck_service import _write_dck_file, _build_dck_lines
|
||||||
|
|
||||||
router = APIRouter(tags=["deckgen"])
|
router = APIRouter(tags=["deckgen"])
|
||||||
|
|||||||
+4
-6
@@ -28,12 +28,10 @@ from fastapi import APIRouter, HTTPException
|
|||||||
from fastapi import Request as FastAPIRequest
|
from fastapi import Request as FastAPIRequest
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG
|
||||||
CFG, log_sim,
|
from services.database import _get_db_conn
|
||||||
_get_db_conn,
|
from services.forge_runner import _get_deepseek_brain
|
||||||
_get_deepseek_brain,
|
from services.logging import log_sim
|
||||||
_ml_logging_enabled,
|
|
||||||
)
|
|
||||||
from commander_ai_lab.sim.validator_brain import ValidatorBrain, ValidatorConfig
|
from commander_ai_lab.sim.validator_brain import ValidatorBrain, ValidatorConfig
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
+7
-11
@@ -15,17 +15,13 @@ from typing import Optional
|
|||||||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
import routes.shared as _shared
|
from models.state import COMMANDER_META
|
||||||
from routes.shared import (
|
from models.requests import ImportUrlRequest, ImportTextRequest, MetaFetchRequest
|
||||||
ImportUrlRequest,
|
from services.import_helpers import (
|
||||||
ImportTextRequest,
|
_import_from_url, _parse_text_decklist, _save_profile_to_dck,
|
||||||
MetaFetchRequest,
|
|
||||||
_import_from_url,
|
|
||||||
_parse_text_decklist,
|
|
||||||
_save_profile_to_dck,
|
|
||||||
_fetch_edhrec_average,
|
_fetch_edhrec_average,
|
||||||
log,
|
|
||||||
)
|
)
|
||||||
|
from services.logging import log
|
||||||
|
|
||||||
router = APIRouter(tags=["import"])
|
router = APIRouter(tags=["import"])
|
||||||
|
|
||||||
@@ -74,7 +70,7 @@ async def import_from_text(req: ImportTextRequest):
|
|||||||
async def list_meta_commanders():
|
async def list_meta_commanders():
|
||||||
"""List available commanders in the meta mapping."""
|
"""List available commanders in the meta mapping."""
|
||||||
commanders = []
|
commanders = []
|
||||||
for name, entries in _shared.COMMANDER_META.items():
|
for name, entries in COMMANDER_META.items():
|
||||||
entry = entries[0] if entries else {}
|
entry = entries[0] if entries else {}
|
||||||
commanders.append({
|
commanders.append({
|
||||||
"name": name,
|
"name": name,
|
||||||
@@ -90,7 +86,7 @@ async def search_meta_commanders(q: str = ""):
|
|||||||
"""Search commanders by partial name match."""
|
"""Search commanders by partial name match."""
|
||||||
query = q.lower()
|
query = q.lower()
|
||||||
matches = []
|
matches = []
|
||||||
for name, entries in _shared.COMMANDER_META.items():
|
for name, entries in COMMANDER_META.items():
|
||||||
if query in name.lower():
|
if query in name.lower():
|
||||||
entry = entries[0] if entries else {}
|
entry = entries[0] if entries else {}
|
||||||
matches.append({
|
matches.append({
|
||||||
|
|||||||
+8
-16
@@ -31,23 +31,15 @@ from typing import Optional
|
|||||||
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request as FastAPIRequest
|
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request as FastAPIRequest
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG, active_batches, BatchState, AI_PROFILES
|
||||||
CFG,
|
from models.requests import StartRequest
|
||||||
active_batches,
|
from models.responses import StartResponse, StatusResponse
|
||||||
BatchState,
|
from services.database import _get_db_conn
|
||||||
StartRequest,
|
from services.deck_service import parse_dck_file, _load_deck_cards_by_name
|
||||||
StartResponse,
|
from services.forge_runner import (
|
||||||
StatusResponse,
|
build_java_command, run_batch_subprocess, _run_deepseek_batch_thread,
|
||||||
AI_PROFILES,
|
|
||||||
build_java_command,
|
|
||||||
run_batch_subprocess,
|
|
||||||
_run_deepseek_batch_thread,
|
|
||||||
parse_dck_file,
|
|
||||||
_get_db_conn,
|
|
||||||
_load_deck_cards_by_name,
|
|
||||||
log,
|
|
||||||
log_batch,
|
|
||||||
)
|
)
|
||||||
|
from services.logging import log, log_batch
|
||||||
|
|
||||||
router = APIRouter(tags=["lab"])
|
router = APIRouter(tags=["lab"])
|
||||||
|
|
||||||
|
|||||||
+6
-10
@@ -32,8 +32,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Request as FastAPIRequest
|
from fastapi import APIRouter, HTTPException, Request as FastAPIRequest
|
||||||
|
|
||||||
from routes.shared import CFG, _ml_logging_enabled
|
from models.state import CFG
|
||||||
|
|
||||||
log_ml = logging.getLogger("commander_ai_lab.ml")
|
log_ml = logging.getLogger("commander_ai_lab.ml")
|
||||||
|
|
||||||
router = APIRouter(tags=["ml"])
|
router = APIRouter(tags=["ml"])
|
||||||
@@ -93,9 +92,6 @@ _ppo_lock = threading.Lock()
|
|||||||
_tournament_state = _TournamentState()
|
_tournament_state = _TournamentState()
|
||||||
_tournament_lock = threading.Lock()
|
_tournament_lock = threading.Lock()
|
||||||
|
|
||||||
# module-level toggle (local copy; the shared one is for batch runs)
|
|
||||||
_ml_logging_enabled_local = _ml_logging_enabled
|
|
||||||
|
|
||||||
|
|
||||||
def _get_policy_service():
|
def _get_policy_service():
|
||||||
"""Get or initialize the policy inference service (lazy loading)."""
|
"""Get or initialize the policy inference service (lazy loading)."""
|
||||||
@@ -121,7 +117,7 @@ def _get_policy_service():
|
|||||||
@router.get("/api/ml/status")
|
@router.get("/api/ml/status")
|
||||||
async def ml_status():
|
async def ml_status():
|
||||||
"""Get ML decision logging status and available training data."""
|
"""Get ML decision logging status and available training data."""
|
||||||
import routes.shared as _shared
|
import services.logging as _logging_mod
|
||||||
lab_root = Path(__file__).resolve().parent.parent
|
lab_root = Path(__file__).resolve().parent.parent
|
||||||
results_dir = lab_root / CFG.results_dir
|
results_dir = lab_root / CFG.results_dir
|
||||||
ml_files = []
|
ml_files = []
|
||||||
@@ -140,7 +136,7 @@ async def ml_status():
|
|||||||
})
|
})
|
||||||
total_decisions = sum(f["decisions"] for f in ml_files)
|
total_decisions = sum(f["decisions"] for f in ml_files)
|
||||||
return {
|
return {
|
||||||
"ml_logging_enabled": _shared._ml_logging_enabled,
|
"ml_logging_enabled": _logging_mod._ml_logging_enabled,
|
||||||
"training_files": ml_files,
|
"training_files": ml_files,
|
||||||
"total_decisions": total_decisions,
|
"total_decisions": total_decisions,
|
||||||
"total_files": len(ml_files),
|
"total_files": len(ml_files),
|
||||||
@@ -150,10 +146,10 @@ async def ml_status():
|
|||||||
@router.post("/api/ml/toggle")
|
@router.post("/api/ml/toggle")
|
||||||
async def ml_toggle(enable: bool = True):
|
async def ml_toggle(enable: bool = True):
|
||||||
"""Enable or disable ML decision logging for future batch runs."""
|
"""Enable or disable ML decision logging for future batch runs."""
|
||||||
import routes.shared as _shared
|
import services.logging as _logging_mod
|
||||||
_shared._ml_logging_enabled = enable
|
_logging_mod._ml_logging_enabled = enable
|
||||||
return {
|
return {
|
||||||
"ml_logging_enabled": _shared._ml_logging_enabled,
|
"ml_logging_enabled": _logging_mod._ml_logging_enabled,
|
||||||
"message": f"ML decision logging {'enabled' if enable else 'disabled'} for future batches",
|
"message": f"ML decision logging {'enabled' if enable else 'disabled'} for future batches",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -15,9 +15,9 @@ from pathlib import Path
|
|||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
import routes.shared as _shared # module reference, not value import
|
from models.state import CFG
|
||||||
from routes.shared import CFG, download_precon_database, log
|
from services.precon_service import download_precon_database, PRECON_INDEX, _get_precon_dir
|
||||||
from services.precon_service import _get_precon_dir
|
from services.logging import log
|
||||||
|
|
||||||
router = APIRouter(tags=["precon"])
|
router = APIRouter(tags=["precon"])
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ router = APIRouter(tags=["precon"])
|
|||||||
@router.get("/api/lab/precons")
|
@router.get("/api/lab/precons")
|
||||||
async def list_precons():
|
async def list_precons():
|
||||||
"""List all available precon decks."""
|
"""List all available precon decks."""
|
||||||
return {"precons": _shared.PRECON_INDEX} # reads current value every call
|
return {"precons": PRECON_INDEX}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/lab/precons/install")
|
@router.post("/api/lab/precons/install")
|
||||||
|
|||||||
+7
-7
@@ -22,13 +22,13 @@ from urllib.error import URLError
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from fastapi import Request as FastAPIRequest
|
from fastapi import Request as FastAPIRequest
|
||||||
|
|
||||||
from routes.shared import (
|
from models.state import CFG
|
||||||
CFG, log_scan, log_deckgen,
|
from services.card_analysis import _detect_card_roles
|
||||||
_get_db_conn, _row_to_dict, _enrich_from_scryfall,
|
from services.database import _get_db_conn, _row_to_dict
|
||||||
_scryfall_fuzzy_lookup, _detect_card_roles,
|
from services.deck_service import _to_edhrec_slug
|
||||||
_to_edhrec_slug, _API_HEADERS,
|
from services.import_helpers import _edhrec_cache_get, _edhrec_cache_set
|
||||||
_edhrec_cache_get, _edhrec_cache_set,
|
from services.logging import log_scan, log_deckgen
|
||||||
)
|
from services.scryfall import _API_HEADERS, _enrich_from_scryfall, _scryfall_fuzzy_lookup
|
||||||
|
|
||||||
router = APIRouter(tags=["scanner"])
|
router = APIRouter(tags=["scanner"])
|
||||||
|
|
||||||
|
|||||||
@@ -1,204 +0,0 @@
|
|||||||
"""
|
|
||||||
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
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
"""
|
||||||
|
services/logging.py
|
||||||
|
====================
|
||||||
|
Single source of truth for application logging configuration and named loggers.
|
||||||
|
|
||||||
|
Moved from routes/shared.py so that every layer (routes, services, CLI
|
||||||
|
entry-points) can import logging without pulling in the full shared module.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ── Format / rotation constants ───────────────────────────────────────────────
|
||||||
|
_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)
|
||||||
|
|
||||||
|
|
||||||
|
# ── Named loggers ────────────────────────────────────────────────────────────
|
||||||
|
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")
|
||||||
|
|
||||||
|
# ── ML logging toggle ────────────────────────────────────────────────────────
|
||||||
|
_ml_logging_enabled = True
|
||||||
Reference in new issue
Block a user