Source code for veupath_chatbot.transport.http.routers.strategies.plan

"""Plan normalization endpoints (frontend-consumer alignment)."""

from collections.abc import Mapping

from fastapi import APIRouter

from veupath_chatbot.platform.errors import WDKError
from veupath_chatbot.platform.types import JSONObject, JSONValue
from veupath_chatbot.services.strategies.plan_normalize import (
    canonicalize_plan_parameters,
)
from veupath_chatbot.services.wdk import (
    encode_context_param_values_for_wdk,
    get_strategy_api,
)
from veupath_chatbot.transport.http.schemas import (
    PlanNormalizeRequest,
    PlanNormalizeResponse,
)

router = APIRouter(prefix="/api/v1/strategies", tags=["strategies"])


[docs] @router.post("/plan/normalize", response_model=PlanNormalizeResponse) async def normalize_plan(payload: PlanNormalizeRequest) -> PlanNormalizeResponse: """Normalize/coerce plan parameters using backend-owned rules. This endpoint exists so the frontend can be a consumer of backend canonicalization (and avoid re-implementing CSV/JSON parsing and WDK quirks). """ api = get_strategy_api(payload.siteId) plan_dump = payload.plan.model_dump(exclude_none=True) # Ensure plan is properly typed as JSONObject plan: JSONObject = plan_dump if isinstance(plan_dump, dict) else {} async def load_details( record_type: str, name: str, params: Mapping[str, JSONValue] ) -> JSONObject: # Use context-dependent search details so vocab-dependent params (e.g. min/max/avg ops) # validate correctly when the plan already contains concrete selections. # Convert params to JSONObject for encode_context_param_values_for_wdk params_dict: JSONObject = dict(params) if isinstance(params, Mapping) else {} context = encode_context_param_values_for_wdk(params_dict) try: result = await api.client.get_search_details_with_params( record_type, name, context=context, expand_params=True, ) # Ensure result is JSONObject if isinstance(result, dict): return result return {} except WDKError: # Some WDK deployments/questions error on POST /searches/{name} when certain context # values are provided (500 Internal Error). Fall back to GET details so we can still # canonicalize plan shapes without blocking the user. result = await api.client.get_search_details( record_type, name, expand_params=True, ) # Ensure result is JSONObject if isinstance(result, dict): return result return {} canonical = await canonicalize_plan_parameters( plan=plan, site_id=payload.siteId, load_search_details=load_details, ) # Convert canonical JSONObject back to StrategyPlan from veupath_chatbot.transport.http.schemas.plan import StrategyPlan canonical_plan = StrategyPlan.model_validate(canonical) return PlanNormalizeResponse(plan=canonical_plan)