235 lines
6.5 KiB
Python
235 lines
6.5 KiB
Python
|
|
"""
|
||
|
|
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,
|
||
|
|
"type": slot.type,
|
||
|
|
"required": slot.required,
|
||
|
|
"extract_strategy": slot.extract_strategy,
|
||
|
|
"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)
|