""" Runtime config endpoint for the detection pipeline. GET /detect/config — read current config PUT /detect/config — update config (takes effect on next run) GET /detect/config/stages — list stage palette with config fields """ from __future__ import annotations import logging from fastapi import APIRouter from pydantic import BaseModel logger = logging.getLogger(__name__) router = APIRouter(prefix="/detect", tags=["detect"]) # In-memory config — persists until server restart. # Phase 12+ moves this to DB. _runtime_config: dict = {} class ConfigUpdate(BaseModel): detection: dict | None = None ocr: dict | None = None resolver: dict | None = None escalation: dict | None = None preprocessing: dict | None = None class StageConfigInfo(BaseModel): name: str label: str description: str category: str config_fields: list[dict] reads: list[str] writes: list[str] @router.get("/config") def read_config(): return _runtime_config @router.put("/config") def write_config(update: ConfigUpdate): changes = update.model_dump(exclude_none=True) for section, values in changes.items(): if section not in _runtime_config: _runtime_config[section] = {} _runtime_config[section].update(values) logger.info("Config updated: %s", list(changes.keys())) return _runtime_config @router.get("/config/profiles") def list_profiles(): """List available detection profiles.""" from detect.profiles import _PROFILES return [{"name": name} for name in _PROFILES] @router.get("/config/profiles/{profile_name}/pipeline") def get_pipeline_config(profile_name: str): """Return the pipeline composition for a profile.""" from detect.profiles import get_profile from fastapi import HTTPException from dataclasses import asdict try: profile = get_profile(profile_name) except ValueError: raise HTTPException(status_code=404, detail=f"Unknown profile: {profile_name}") config = profile.pipeline_config() return asdict(config) @router.get("/config/stages", response_model=list[StageConfigInfo]) def list_stage_configs(): """Return the stage palette with config field metadata for the editor.""" from detect.stages import list_stages result = [] for stage in list_stages(): info = _stage_to_info(stage) result.append(info) return result @router.get("/config/stages/{stage_name}", response_model=StageConfigInfo) def get_stage_config(stage_name: str): """Return config field metadata for a single stage.""" from detect.stages import get_stage try: stage = get_stage(stage_name) except KeyError: from fastapi import HTTPException raise HTTPException(status_code=404, detail=f"Unknown stage: {stage_name}") return _stage_to_info(stage) def _stage_to_info(stage) -> StageConfigInfo: return StageConfigInfo( name=stage.name, label=stage.label, description=stage.description, category=stage.category, config_fields=[ { "name": f.name, "type": f.type, "default": f.default, "description": f.description, "min": f.min, "max": f.max, "options": f.options, } for f in stage.config_fields ], reads=stage.io.reads, writes=stage.io.writes, )