""" FastAPI routes for Template Rules — Empfehlungs-Regeln + Versionierung + Approval-Workflow + Tenant-Overrides + Recommend. Mounted unter /api/v1/compliance/* (Tenant via X-Tenant-ID Header). """ import logging from typing import Optional from fastapi import APIRouter, Depends, Header, Query from sqlalchemy.orm import Session from classroom_engine.database import get_db from compliance.api._http_errors import translate_domain_errors from compliance.schemas.template_rule import ( ApprovalActionRequest, ApprovalHistoryEntry, OverrideCreate, OverrideResponse, RecommendationRequest, RecommendationResult, RejectActionRequest, RuleCreate, RuleResponse, RuleVersionCreate, RuleVersionResponse, RuleVersionUpdate, SubmitForReviewRequest, ) from compliance.services.recommendation_service import RecommendationService from compliance.services.template_rule_service import TemplateRuleService logger = logging.getLogger(__name__) router = APIRouter(prefix="", tags=["template-rules"]) DEFAULT_TENANT = "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" def _get_tenant( x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), ) -> str: return x_tenant_id or DEFAULT_TENANT def _rule_svc(db: Session = Depends(get_db)) -> TemplateRuleService: return TemplateRuleService(db) def _rec_svc(db: Session = Depends(get_db)) -> RecommendationService: return RecommendationService(db) # ============================================================================ # Rules (Hülle) # ============================================================================ @router.get("/template-rules", response_model=list[RuleResponse]) async def list_rules( document_type: Optional[str] = Query(None), svc: TemplateRuleService = Depends(_rule_svc), ) -> list[RuleResponse]: with translate_domain_errors(): return svc.list_rules(document_type=document_type) @router.post("/template-rules", response_model=RuleResponse, status_code=201) async def create_rule( request: RuleCreate, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleResponse: with translate_domain_errors(): return svc.create_rule(request) @router.get("/template-rules/{rule_id}", response_model=RuleResponse) async def get_rule( rule_id: str, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleResponse: with translate_domain_errors(): return svc.get_rule(rule_id) @router.delete("/template-rules/{rule_id}", status_code=204) async def delete_rule( rule_id: str, svc: TemplateRuleService = Depends(_rule_svc), ) -> None: with translate_domain_errors(): svc.delete_rule(rule_id) # ============================================================================ # Versions # ============================================================================ @router.get( "/template-rules/{rule_id}/versions", response_model=list[RuleVersionResponse], ) async def list_versions( rule_id: str, svc: TemplateRuleService = Depends(_rule_svc), ) -> list[RuleVersionResponse]: with translate_domain_errors(): return svc.list_versions_for(rule_id) @router.post( "/template-rules/{rule_id}/versions", response_model=RuleVersionResponse, status_code=201, ) async def create_version( rule_id: str, request: RuleVersionCreate, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: request.rule_id = rule_id # URL gewinnt with translate_domain_errors(): return svc.create_version(request) @router.get( "/template-rule-versions/{version_id}", response_model=RuleVersionResponse, ) async def get_version( version_id: str, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: with translate_domain_errors(): return svc.get_version(version_id) @router.patch( "/template-rule-versions/{version_id}", response_model=RuleVersionResponse, ) async def update_version( version_id: str, request: RuleVersionUpdate, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: with translate_domain_errors(): return svc.update_version(version_id, request) @router.post( "/template-rule-versions/{version_id}/submit-review", response_model=RuleVersionResponse, ) async def submit_for_review( version_id: str, request: SubmitForReviewRequest, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: with translate_domain_errors(): return svc.submit_for_review(version_id, request) @router.post( "/template-rule-versions/{version_id}/approve", response_model=RuleVersionResponse, ) async def approve_version( version_id: str, request: ApprovalActionRequest, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: with translate_domain_errors(): return svc.approve(version_id, request) @router.post( "/template-rule-versions/{version_id}/publish", response_model=RuleVersionResponse, ) async def publish_version( version_id: str, request: ApprovalActionRequest, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: with translate_domain_errors(): return svc.publish(version_id, request) @router.post( "/template-rule-versions/{version_id}/reject", response_model=RuleVersionResponse, ) async def reject_version( version_id: str, request: RejectActionRequest, svc: TemplateRuleService = Depends(_rule_svc), ) -> RuleVersionResponse: with translate_domain_errors(): return svc.reject(version_id, request) @router.get( "/template-rule-versions/{version_id}/approval-history", response_model=list[ApprovalHistoryEntry], ) async def approval_history( version_id: str, svc: TemplateRuleService = Depends(_rule_svc), ) -> list[ApprovalHistoryEntry]: with translate_domain_errors(): return svc.approval_history(version_id) # ============================================================================ # Tenant Overrides # ============================================================================ @router.get( "/tenant-rule-overrides", response_model=list[OverrideResponse], ) async def list_overrides( tenant_id: str = Depends(_get_tenant), svc: TemplateRuleService = Depends(_rule_svc), ) -> list[OverrideResponse]: with translate_domain_errors(): return svc.list_overrides(tenant_id) @router.post( "/tenant-rule-overrides", response_model=OverrideResponse, status_code=201, ) async def upsert_override( request: OverrideCreate, tenant_id: str = Depends(_get_tenant), svc: TemplateRuleService = Depends(_rule_svc), ) -> OverrideResponse: with translate_domain_errors(): return svc.upsert_override(tenant_id, request) @router.delete( "/tenant-rule-overrides/{override_id}", status_code=204, ) async def delete_override( override_id: str, tenant_id: str = Depends(_get_tenant), svc: TemplateRuleService = Depends(_rule_svc), ) -> None: with translate_domain_errors(): svc.delete_override(tenant_id, override_id) # ============================================================================ # Recommend # ============================================================================ @router.post("/recommend", response_model=RecommendationResult) async def recommend( request: RecommendationRequest, tenant_id: str = Depends(_get_tenant), svc: RecommendationService = Depends(_rec_svc), ) -> RecommendationResult: with translate_domain_errors(): return svc.recommend(request, tenant_id=tenant_id)