""" 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/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 = 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, ) result.append(info) return result