""" Slot Definition API. [AC-MRS-07,08,16] 槽位定义管理接口 """ import logging from typing import Annotated, Any from fastapi import APIRouter, Depends, Query from fastapi.responses import JSONResponse from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_session from app.core.exceptions import MissingTenantIdException from app.core.tenant import get_tenant_id from app.models.entities import SlotDefinitionCreate, SlotDefinitionUpdate from app.services.slot_definition_service import SlotDefinitionService logger = logging.getLogger(__name__) router = APIRouter(prefix="/admin/slot-definitions", tags=["SlotDefinition"]) def get_current_tenant_id() -> str: """Get current tenant ID from context.""" tenant_id = get_tenant_id() if not tenant_id: raise MissingTenantIdException() return tenant_id def _slot_to_dict(slot: dict[str, Any] | Any) -> dict[str, Any]: """Convert slot definition to dict""" if isinstance(slot, dict): return slot return { "id": str(slot.id), "tenant_id": str(slot.tenant_id), "slot_key": slot.slot_key, "display_name": slot.display_name, "description": slot.description, "type": slot.type, "required": slot.required, # [AC-MRS-07-UPGRADE] 返回新旧字段 "extract_strategy": slot.extract_strategy, "extract_strategies": slot.extract_strategies, "validation_rule": slot.validation_rule, "ask_back_prompt": slot.ask_back_prompt, "default_value": slot.default_value, "linked_field_id": str(slot.linked_field_id) if slot.linked_field_id else None, "created_at": slot.created_at.isoformat() if slot.created_at else None, "updated_at": slot.updated_at.isoformat() if slot.updated_at else None, } @router.get( "", operation_id="listSlotDefinitions", summary="List slot definitions", description="获取槽位定义列表", ) async def list_slot_definitions( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], required: Annotated[bool | None, Query( description="按是否必填过滤" )] = None, ) -> JSONResponse: """ 列出槽位定义 """ logger.info( f"Listing slot definitions: tenant={tenant_id}, required={required}" ) service = SlotDefinitionService(session) slots = await service.list_slot_definitions(tenant_id, required) return JSONResponse( content=[_slot_to_dict(s) for s in slots] ) @router.post( "", operation_id="createSlotDefinition", summary="Create slot definition", description="[AC-MRS-07,08] 创建新的槽位定义,可关联已有元数据字段", status_code=201, ) async def create_slot_definition( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], slot_create: SlotDefinitionCreate, ) -> JSONResponse: """ [AC-MRS-07,08] 创建槽位定义 """ logger.info( f"[AC-MRS-07] Creating slot definition: " f"tenant={tenant_id}, slot_key={slot_create.slot_key}, " f"linked_field_id={slot_create.linked_field_id}" ) service = SlotDefinitionService(session) try: slot = await service.create_slot_definition(tenant_id, slot_create) await session.commit() except ValueError as e: return JSONResponse( status_code=400, content={ "error_code": "VALIDATION_ERROR", "message": str(e), } ) return JSONResponse( status_code=201, content=_slot_to_dict(slot) ) @router.get( "/{id}", operation_id="getSlotDefinition", summary="Get slot definition by ID", description="获取单个槽位定义", ) async def get_slot_definition( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], id: str, ) -> JSONResponse: """ 获取单个槽位定义 """ logger.info( f"Getting slot definition: tenant={tenant_id}, id={id}" ) service = SlotDefinitionService(session) slot = await service.get_slot_definition_with_field(tenant_id, id) if not slot: return JSONResponse( status_code=404, content={ "error_code": "NOT_FOUND", "message": f"Slot definition {id} not found", } ) return JSONResponse(content=slot) @router.put( "/{id}", operation_id="updateSlotDefinition", summary="Update slot definition", description="更新槽位定义", ) async def update_slot_definition( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], id: str, slot_update: SlotDefinitionUpdate, ) -> JSONResponse: """ 更新槽位定义 """ logger.info( f"Updating slot definition: tenant={tenant_id}, id={id}" ) service = SlotDefinitionService(session) try: slot = await service.update_slot_definition(tenant_id, id, slot_update) except ValueError as e: return JSONResponse( status_code=400, content={ "error_code": "VALIDATION_ERROR", "message": str(e), } ) if not slot: return JSONResponse( status_code=404, content={ "error_code": "NOT_FOUND", "message": f"Slot definition {id} not found", } ) await session.commit() return JSONResponse(content=_slot_to_dict(slot)) @router.delete( "/{id}", operation_id="deleteSlotDefinition", summary="Delete slot definition", description="[AC-MRS-16] 删除槽位定义,无需考虑历史数据兼容性", status_code=204, ) async def delete_slot_definition( tenant_id: Annotated[str, Depends(get_current_tenant_id)], session: Annotated[AsyncSession, Depends(get_session)], id: str, ) -> JSONResponse: """ [AC-MRS-16] 删除槽位定义 """ logger.info( f"[AC-MRS-16] Deleting slot definition: tenant={tenant_id}, id={id}" ) service = SlotDefinitionService(session) success = await service.delete_slot_definition(tenant_id, id) if not success: return JSONResponse( status_code=404, content={ "error_code": "NOT_FOUND", "message": f"Slot definition not found: {id}", } ) await session.commit() return JSONResponse(status_code=204, content=None)