""" Intent Rule Management API. [AC-AISVC-65~AC-AISVC-68] Intent rule CRUD endpoints. """ import logging import uuid from typing import Any from fastapi import APIRouter, Depends, Header, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_session from app.models.entities import IntentRuleCreate, IntentRuleUpdate from app.services.intent.rule_service import IntentRuleService logger = logging.getLogger(__name__) router = APIRouter(prefix="/admin/intent-rules", tags=["Intent Rules"]) def get_tenant_id(x_tenant_id: str = Header(..., alias="X-Tenant-Id")) -> str: """Extract tenant ID from header.""" if not x_tenant_id: raise HTTPException(status_code=400, detail="X-Tenant-Id header is required") return x_tenant_id @router.get("") async def list_rules( tenant_id: str = Depends(get_tenant_id), response_type: str | None = None, is_enabled: bool | None = None, session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-66] List all intent rules for a tenant. """ logger.info( f"[AC-AISVC-66] Listing intent rules for tenant={tenant_id}, " f"response_type={response_type}, is_enabled={is_enabled}" ) service = IntentRuleService(session) rules = await service.list_rules(tenant_id, response_type, is_enabled) data = [] for rule in rules: data.append(await service.rule_to_info_dict(rule)) return {"data": data} @router.post("", status_code=201) async def create_rule( body: IntentRuleCreate, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-65] Create a new intent rule. """ valid_response_types = ["fixed", "rag", "flow", "transfer"] if body.response_type not in valid_response_types: raise HTTPException( status_code=400, detail=f"Invalid response_type. Must be one of: {valid_response_types}" ) if body.response_type == "rag" and not body.target_kb_ids: logger.warning( f"[AC-AISVC-65] Creating rag rule without target_kb_ids: tenant={tenant_id}" ) if body.response_type == "flow" and not body.flow_id: raise HTTPException( status_code=400, detail="flow_id is required when response_type is 'flow'" ) if body.response_type == "fixed" and not body.fixed_reply: raise HTTPException( status_code=400, detail="fixed_reply is required when response_type is 'fixed'" ) if body.response_type == "transfer" and not body.transfer_message: raise HTTPException( status_code=400, detail="transfer_message is required when response_type is 'transfer'" ) logger.info( f"[AC-AISVC-65] Creating intent rule for tenant={tenant_id}, name={body.name}" ) service = IntentRuleService(session) rule = await service.create_rule(tenant_id, body) return await service.rule_to_info_dict(rule) @router.get("/{rule_id}") async def get_rule( rule_id: uuid.UUID, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-66] Get intent rule detail. """ logger.info(f"[AC-AISVC-66] Getting intent rule for tenant={tenant_id}, id={rule_id}") service = IntentRuleService(session) rule = await service.get_rule(tenant_id, rule_id) if not rule: raise HTTPException(status_code=404, detail="Intent rule not found") return await service.rule_to_info_dict(rule) @router.put("/{rule_id}") async def update_rule( rule_id: uuid.UUID, body: IntentRuleUpdate, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> dict[str, Any]: """ [AC-AISVC-67] Update an intent rule. """ valid_response_types = ["fixed", "rag", "flow", "transfer"] if body.response_type is not None and body.response_type not in valid_response_types: raise HTTPException( status_code=400, detail=f"Invalid response_type. Must be one of: {valid_response_types}" ) logger.info(f"[AC-AISVC-67] Updating intent rule for tenant={tenant_id}, id={rule_id}") service = IntentRuleService(session) rule = await service.update_rule(tenant_id, rule_id, body) if not rule: raise HTTPException(status_code=404, detail="Intent rule not found") return await service.rule_to_info_dict(rule) @router.delete("/{rule_id}", status_code=204) async def delete_rule( rule_id: uuid.UUID, tenant_id: str = Depends(get_tenant_id), session: AsyncSession = Depends(get_session), ) -> None: """ [AC-AISVC-68] Delete an intent rule. """ logger.info(f"[AC-AISVC-68] Deleting intent rule for tenant={tenant_id}, id={rule_id}") service = IntentRuleService(session) success = await service.delete_rule(tenant_id, rule_id) if not success: raise HTTPException(status_code=404, detail="Intent rule not found")