Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd94435993 | ||
|
|
dff7ae2860 |
No files matched your search
+2
-2
@@ -13,7 +13,7 @@ from pathlib import Path
|
||||
LAB_ROOT = Path(os.environ.get("COMMANDER_LAB_ROOT", Path(__file__).parent.parent))
|
||||
|
||||
# ── Ollama (OpenAI-compatible API) ──────────────────────
|
||||
LLM_URL = os.environ.get("LLM_URL", "http://localhost:11434/v1")
|
||||
LLM_URL = os.environ.get("LLM_URL", "http://192.168.0.240:11434/v1")
|
||||
LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-oss:20b")
|
||||
LLM_TIMEOUT = int(os.environ.get("LLM_TIMEOUT", "360")) # seconds
|
||||
LLM_MAX_RETRIES = 3
|
||||
@@ -44,7 +44,7 @@ DEFAULT_MAX_TOKENS = 8192
|
||||
DECK_GEN_PROVIDER = os.environ.get("DECK_GEN_PROVIDER", "local")
|
||||
# Model for deck generation (sonar = fast/$0.004, sonar-pro = deep/$0.04)
|
||||
DECK_GEN_MODEL = os.environ.get("DECK_GEN_MODEL", "gpt-oss:20b")
|
||||
DECK_GEN_BASE_URL = os.environ.get("DECK_GEN_BASE_URL", "http://localhost:11434/v1")
|
||||
DECK_GEN_BASE_URL = os.environ.get("DECK_GEN_BASE_URL", "http://192.168.0.240:11434/v1")
|
||||
DECK_GEN_TEMPERATURE = float(os.environ.get("DECK_GEN_TEMPERATURE", "0.2"))
|
||||
DECK_GEN_MAX_TOKENS = int(os.environ.get("DECK_GEN_MAX_TOKENS", "16384"))
|
||||
|
||||
|
||||
+11
-17
@@ -22,15 +22,10 @@ except ImportError:
|
||||
print("ERROR: FastAPI not installed. Run: pip install fastapi uvicorn")
|
||||
import sys; sys.exit(1)
|
||||
|
||||
from routes.shared import (
|
||||
CFG,
|
||||
setup_logging,
|
||||
init_collection_db,
|
||||
download_precon_database,
|
||||
load_commander_meta,
|
||||
PRECON_INDEX,
|
||||
COMMANDER_META,
|
||||
)
|
||||
from models.state import CFG, COMMANDER_META, load_commander_meta
|
||||
from services.database import init_collection_db
|
||||
from services.logging import setup_logging
|
||||
from services.precon_service import PRECON_INDEX, download_precon_database
|
||||
from routes.collection import router as collection_router
|
||||
from routes.deckbuilder import router as deckbuilder_router
|
||||
from routes.precon import router as precon_router
|
||||
@@ -45,7 +40,7 @@ from routes.ml import router as ml_router
|
||||
log = logging.getLogger("commander_ai_lab.api")
|
||||
|
||||
# ── CORS origins (env-configurable) ────────────────────────────
|
||||
_DEFAULT_ORIGINS = "http://localhost:5173,http://localhost:8080"
|
||||
_DEFAULT_ORIGINS = "http://localhost:5173,http://localhost:8080,http://192.168.0.240:5173,http://192.168.0.240:8080"
|
||||
_allowed = os.environ.get("ALLOWED_ORIGINS", _DEFAULT_ORIGINS)
|
||||
allowed_origins = [o.strip() for o in _allowed.split(",") if o.strip()]
|
||||
|
||||
@@ -80,15 +75,14 @@ async def health_check():
|
||||
@app.on_event("startup")
|
||||
async def _on_startup():
|
||||
"""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
|
||||
if not CFG.precon_dir:
|
||||
CFG.precon_dir = _resolve_precon_dir()
|
||||
_shared.init_collection_db()
|
||||
if not _shared.PRECON_INDEX:
|
||||
_shared.download_precon_database()
|
||||
if not _shared.COMMANDER_META:
|
||||
_shared.load_commander_meta()
|
||||
init_collection_db()
|
||||
if not PRECON_INDEX:
|
||||
download_precon_database()
|
||||
if not COMMANDER_META:
|
||||
load_commander_meta()
|
||||
|
||||
# ── Static UI ──────────────────────────────────────────────────
|
||||
_legacy_ui_dir = Path(__file__).parent / "ui"
|
||||
@@ -288,7 +282,7 @@ def main():
|
||||
CFG.ximilar_api_key = args.ximilar_key
|
||||
CFG.pplx_api_key = args.pplx_key
|
||||
|
||||
from routes.shared import get_java17
|
||||
from services.forge_runner import get_java17
|
||||
j17 = get_java17()
|
||||
|
||||
log.info("Commander AI Lab — API Server v3.0.0")
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
routes package — API routers for Commander AI Lab.
|
||||
|
||||
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/.
|
||||
"""
|
||||
+5
-7
@@ -33,12 +33,10 @@ from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from models.requests import CoachRequestBody, CoachChatMessage, CoachChatRequest, CoachApplyRequest, CoachGoalsRequest
|
||||
|
||||
from routes.shared import (
|
||||
CFG,
|
||||
log_coach, log_deckgen,
|
||||
_get_db_conn,
|
||||
_compute_deck_analysis,
|
||||
)
|
||||
from models.state import CFG
|
||||
from services.database import _get_db_conn
|
||||
from services.deck_service import _compute_deck_analysis
|
||||
from services.logging import log_coach, log_deckgen
|
||||
|
||||
router = APIRouter(tags=["coach"])
|
||||
|
||||
@@ -87,7 +85,7 @@ def init_coach_service():
|
||||
if llm_status.get("connected"):
|
||||
log_coach.info(f" Coach LLM: Connected ({llm_status.get('active_model', 'unknown')})")
|
||||
else:
|
||||
log_coach.warning(f" Coach LLM: Not connected (start Ollama on localhost:11434)")
|
||||
log_coach.warning(f" Coach LLM: Not connected (start Ollama on 192.168.0.240:11434)")
|
||||
|
||||
# Initialize V3 Deck Generator (Ollama-based)
|
||||
_deck_gen_v3_error = None
|
||||
|
||||
+15
-24
@@ -25,30 +25,21 @@ from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from routes.shared import (
|
||||
CFG,
|
||||
_get_db_conn,
|
||||
_row_to_dict,
|
||||
_add_image_url,
|
||||
_build_collection_filters,
|
||||
_detect_card_roles,
|
||||
_classify_card_type,
|
||||
_enrich_from_scryfall,
|
||||
_fetch_scryfall_api,
|
||||
_parse_finish,
|
||||
_parse_text_line,
|
||||
_parse_csv_content,
|
||||
_auto_infer_mapping,
|
||||
_scryfall_cache,
|
||||
_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,
|
||||
from models.state import CFG
|
||||
from services.card_analysis import _detect_card_roles
|
||||
from services.database import (
|
||||
_get_db_conn, _row_to_dict, _add_image_url, _build_collection_filters,
|
||||
_snake_to_camel, VALID_SORT_FIELDS, _JSON_FIELDS,
|
||||
)
|
||||
from services.deck_service import _classify_card_type
|
||||
from services.import_service import (
|
||||
_parse_finish, _parse_text_line, _parse_csv_content, _auto_infer_mapping,
|
||||
)
|
||||
from services.logging import log_collect, log_cache
|
||||
from services.scryfall import (
|
||||
SCRYFALL_CACHE_DB_PATH, SCRYFALL_CACHE_TTL_SECONDS, _API_HEADERS,
|
||||
_scryfall_cache, _scryfall_rate_limit,
|
||||
_enrich_from_scryfall, _fetch_scryfall_api,
|
||||
)
|
||||
|
||||
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.responses import JSONResponse
|
||||
|
||||
from routes.shared import (
|
||||
CFG,
|
||||
_get_db_conn,
|
||||
_row_to_dict,
|
||||
_add_image_url,
|
||||
_get_deck_or_404,
|
||||
_compute_deck_analysis,
|
||||
_check_ratio_limit,
|
||||
_classify_card_type,
|
||||
_detect_card_roles,
|
||||
_enrich_from_scryfall,
|
||||
_fetch_scryfall_api,
|
||||
_scryfall_rate_limit,
|
||||
_to_edhrec_slug,
|
||||
_edhrec_cache_get,
|
||||
_edhrec_cache_set,
|
||||
_fetch_edhrec_average,
|
||||
from models.state import CFG
|
||||
from models.requests import (
|
||||
CreateDeckRequest, UpdateDeckRequest, AddDeckCardRequest,
|
||||
PatchDeckCardRequest, BulkAddRequest, BulkAddRecommendedRequest,
|
||||
)
|
||||
from services.card_analysis import _detect_card_roles
|
||||
from services.database import _get_db_conn, _row_to_dict, _add_image_url
|
||||
from services.deck_service import (
|
||||
_get_deck_or_404, _compute_deck_analysis, _check_ratio_limit,
|
||||
_classify_card_type, _to_edhrec_slug, _TYPE_TARGETS,
|
||||
_save_profile_to_dck,
|
||||
_parse_text_decklist,
|
||||
_API_HEADERS,
|
||||
_TYPE_TARGETS,
|
||||
CreateDeckRequest,
|
||||
UpdateDeckRequest,
|
||||
AddDeckCardRequest,
|
||||
PatchDeckCardRequest,
|
||||
BulkAddRequest,
|
||||
BulkAddRecommendedRequest,
|
||||
log_collect,
|
||||
log_deckgen,
|
||||
)
|
||||
from services.import_helpers import (
|
||||
_fetch_edhrec_average, _parse_text_decklist,
|
||||
_edhrec_cache_get, _edhrec_cache_set,
|
||||
)
|
||||
from services.logging import log_collect, log_deckgen
|
||||
from services.scryfall import (
|
||||
_API_HEADERS, _enrich_from_scryfall, _fetch_scryfall_api, _scryfall_rate_limit,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["deckbuilder"])
|
||||
|
||||
+7
-8
@@ -33,17 +33,16 @@ import httpx
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
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
|
||||
from routes.shared import (
|
||||
CFG,
|
||||
log_deckgen, log_pplx,
|
||||
_get_db_conn,
|
||||
_scryfall_rate_limit, _API_HEADERS,
|
||||
from models.state import CFG
|
||||
from models.requests import (
|
||||
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
|
||||
|
||||
router = APIRouter(tags=["deckgen"])
|
||||
|
||||
+4
-6
@@ -28,12 +28,10 @@ from fastapi import APIRouter, HTTPException
|
||||
from fastapi import Request as FastAPIRequest
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from routes.shared import (
|
||||
CFG, log_sim,
|
||||
_get_db_conn,
|
||||
_get_deepseek_brain,
|
||||
_ml_logging_enabled,
|
||||
)
|
||||
from models.state import CFG
|
||||
from services.database import _get_db_conn
|
||||
from services.forge_runner import _get_deepseek_brain
|
||||
from services.logging import log_sim
|
||||
from commander_ai_lab.sim.validator_brain import ValidatorBrain, ValidatorConfig
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+7
-11
@@ -15,17 +15,13 @@ from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
import routes.shared as _shared
|
||||
from routes.shared import (
|
||||
ImportUrlRequest,
|
||||
ImportTextRequest,
|
||||
MetaFetchRequest,
|
||||
_import_from_url,
|
||||
_parse_text_decklist,
|
||||
_save_profile_to_dck,
|
||||
from models.state import COMMANDER_META
|
||||
from models.requests import ImportUrlRequest, ImportTextRequest, MetaFetchRequest
|
||||
from services.import_helpers import (
|
||||
_import_from_url, _parse_text_decklist, _save_profile_to_dck,
|
||||
_fetch_edhrec_average,
|
||||
log,
|
||||
)
|
||||
from services.logging import log
|
||||
|
||||
router = APIRouter(tags=["import"])
|
||||
|
||||
@@ -74,7 +70,7 @@ async def import_from_text(req: ImportTextRequest):
|
||||
async def list_meta_commanders():
|
||||
"""List available commanders in the meta mapping."""
|
||||
commanders = []
|
||||
for name, entries in _shared.COMMANDER_META.items():
|
||||
for name, entries in COMMANDER_META.items():
|
||||
entry = entries[0] if entries else {}
|
||||
commanders.append({
|
||||
"name": name,
|
||||
@@ -90,7 +86,7 @@ async def search_meta_commanders(q: str = ""):
|
||||
"""Search commanders by partial name match."""
|
||||
query = q.lower()
|
||||
matches = []
|
||||
for name, entries in _shared.COMMANDER_META.items():
|
||||
for name, entries in COMMANDER_META.items():
|
||||
if query in name.lower():
|
||||
entry = entries[0] if entries else {}
|
||||
matches.append({
|
||||
|
||||
+8
-16
@@ -31,23 +31,15 @@ from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException, BackgroundTasks, Request as FastAPIRequest
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from routes.shared import (
|
||||
CFG,
|
||||
active_batches,
|
||||
BatchState,
|
||||
StartRequest,
|
||||
StartResponse,
|
||||
StatusResponse,
|
||||
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 models.state import CFG, active_batches, BatchState, AI_PROFILES
|
||||
from models.requests import StartRequest
|
||||
from models.responses import StartResponse, StatusResponse
|
||||
from services.database import _get_db_conn
|
||||
from services.deck_service import parse_dck_file, _load_deck_cards_by_name
|
||||
from services.forge_runner import (
|
||||
build_java_command, run_batch_subprocess, _run_deepseek_batch_thread,
|
||||
)
|
||||
from services.logging import log, log_batch
|
||||
|
||||
router = APIRouter(tags=["lab"])
|
||||
|
||||
|
||||
+6
-10
@@ -32,8 +32,7 @@ from pathlib import Path
|
||||
|
||||
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")
|
||||
|
||||
router = APIRouter(tags=["ml"])
|
||||
@@ -93,9 +92,6 @@ _ppo_lock = threading.Lock()
|
||||
_tournament_state = _TournamentState()
|
||||
_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():
|
||||
"""Get or initialize the policy inference service (lazy loading)."""
|
||||
@@ -121,7 +117,7 @@ def _get_policy_service():
|
||||
@router.get("/api/ml/status")
|
||||
async def ml_status():
|
||||
"""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
|
||||
results_dir = lab_root / CFG.results_dir
|
||||
ml_files = []
|
||||
@@ -140,7 +136,7 @@ async def ml_status():
|
||||
})
|
||||
total_decisions = sum(f["decisions"] for f in ml_files)
|
||||
return {
|
||||
"ml_logging_enabled": _shared._ml_logging_enabled,
|
||||
"ml_logging_enabled": _logging_mod._ml_logging_enabled,
|
||||
"training_files": ml_files,
|
||||
"total_decisions": total_decisions,
|
||||
"total_files": len(ml_files),
|
||||
@@ -150,10 +146,10 @@ async def ml_status():
|
||||
@router.post("/api/ml/toggle")
|
||||
async def ml_toggle(enable: bool = True):
|
||||
"""Enable or disable ML decision logging for future batch runs."""
|
||||
import routes.shared as _shared
|
||||
_shared._ml_logging_enabled = enable
|
||||
import services.logging as _logging_mod
|
||||
_logging_mod._ml_logging_enabled = enable
|
||||
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",
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -15,9 +15,9 @@ from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
import routes.shared as _shared # module reference, not value import
|
||||
from routes.shared import CFG, download_precon_database, log
|
||||
from services.precon_service import _get_precon_dir
|
||||
from models.state import CFG
|
||||
from services.precon_service import download_precon_database, PRECON_INDEX, _get_precon_dir
|
||||
from services.logging import log
|
||||
|
||||
router = APIRouter(tags=["precon"])
|
||||
|
||||
@@ -25,7 +25,7 @@ router = APIRouter(tags=["precon"])
|
||||
@router.get("/api/lab/precons")
|
||||
async def list_precons():
|
||||
"""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")
|
||||
|
||||
+7
-7
@@ -22,13 +22,13 @@ from urllib.error import URLError
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi import Request as FastAPIRequest
|
||||
|
||||
from routes.shared import (
|
||||
CFG, log_scan, log_deckgen,
|
||||
_get_db_conn, _row_to_dict, _enrich_from_scryfall,
|
||||
_scryfall_fuzzy_lookup, _detect_card_roles,
|
||||
_to_edhrec_slug, _API_HEADERS,
|
||||
_edhrec_cache_get, _edhrec_cache_set,
|
||||
)
|
||||
from models.state import CFG
|
||||
from services.card_analysis import _detect_card_roles
|
||||
from services.database import _get_db_conn, _row_to_dict
|
||||
from services.deck_service import _to_edhrec_slug
|
||||
from services.import_helpers import _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"])
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -9,7 +9,7 @@ The LLM receives a structured game-state snapshot and returns a JSON
|
||||
action describing what to do this turn (play land, cast spell, attack,
|
||||
hold/pass, use removal, use board wipe, cast ramp, cast commander).
|
||||
|
||||
Default target: GPT-OSS 20B (Q4_K_M) via Ollama at localhost:11434
|
||||
Default target: GPT-OSS 20B (Q4_K_M) via Ollama at 192.168.0.240:11434
|
||||
ollama run deepseek-r1:8b
|
||||
|
||||
Also supports:
|
||||
@@ -64,7 +64,7 @@ class DeepSeekConfig:
|
||||
)
|
||||
|
||||
Example — Ollama (default)::
|
||||
config = DeepSeekConfig() # points at localhost:11434 / deepseek-r1:8b
|
||||
config = DeepSeekConfig() # points at 192.168.0.240:11434 / deepseek-r1:8b
|
||||
"""
|
||||
|
||||
# API endpoint — Ollama OpenAI-compatible base (NO /v1 suffix)
|
||||
|
||||
@@ -23,7 +23,7 @@ Usage::
|
||||
from commander_ai_lab.sim.deepseek_engine import DeepSeekGameEngine
|
||||
from commander_ai_lab.sim.deepseek_brain import DeepSeekBrain, DeepSeekConfig
|
||||
|
||||
brain = DeepSeekBrain(DeepSeekConfig(api_base="http://localhost:11434"))
|
||||
brain = DeepSeekBrain(DeepSeekConfig(api_base="http://192.168.0.240:11434"))
|
||||
brain.check_connection()
|
||||
engine = DeepSeekGameEngine(brain=brain, ai_player_index=1)
|
||||
result = engine.run(deck_a, deck_b, name_a="My Deck", name_b="DeepSeek AI")
|
||||
|
||||
@@ -50,7 +50,7 @@ class ValidatorConfig:
|
||||
|
||||
Example -- LM Studio::
|
||||
cfg = ValidatorConfig(
|
||||
api_base="http://localhost:1234",
|
||||
api_base="http://192.168.0.240:1234",
|
||||
model="deepseek-r1-distill-qwen-14b",
|
||||
)
|
||||
"""
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@
|
||||
<div class="deepseek-config-row">
|
||||
<div class="form-group sim-field-grow">
|
||||
<label class="form-label" for="ds-api-base">API Endpoint</label>
|
||||
<input type="text" id="ds-api-base" class="form-input" value="http://localhost:11434" placeholder="http://localhost:11434" />
|
||||
<input type="text" id="ds-api-base" class="form-input" value="http://192.168.0.240:11434" placeholder="http://192.168.0.240:11434" />
|
||||
</div>
|
||||
<div class="form-group sim-field-auto">
|
||||
<label class="form-label" for="ds-model">Model</label>
|
||||
|
||||
Reference in new issue
Block a user