from __future__ import annotations from fastapi import Depends, HTTPException, Query from sqlalchemy.ext.asyncio import AsyncSession from ...core.response import ApiResponse, success_response from ...dependencies.permissions import require_manager_user from ...dependencies.tenant import get_tenant_id from ...models import User from ...schemas.mounted_ai_runtime import ( MountedAICutoverStatusPayload, MountedAIEvalSuiteDetailPayload, MountedAIEvalSuiteListPayload, MountedAIHardCaseListPayload, MountedAIReplayRunPayload, MountedAIReplayRunRequest, MountedAIRunDetailPayload, MountedAIRunListPayload, MountedAISourceActivationListPayload, MountedAISourceActivationUpsertPayload, MountedAISourceActivationUpsertRequest, MountedAISourceListPayload, MountedAIToolPolicyListPayload, MountedAIToolPolicyUpsertPayload, MountedAIToolPolicyUpsertRequest, MountedAITrainingOpsPayload, MountedAITopicListPayload, ) from ...services.mounted_ai_runtime_service import MountedAIRuntimeService from .shared import * @router.get("/mounted-ai/sources", response_model=ApiResponse[MountedAISourceListPayload]) async def list_mounted_ai_sources( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_sources(tenant_id=effective_tenant_id) return success_response(data) @router.get("/mounted-ai/source-activations", response_model=ApiResponse[MountedAISourceActivationListPayload]) async def list_mounted_ai_source_activations( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_source_activations(tenant_id=effective_tenant_id) return success_response(data) @router.get("/mounted-ai/tool-policies", response_model=ApiResponse[MountedAIToolPolicyListPayload]) async def list_mounted_ai_tool_policies( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_tool_policies(tenant_id=effective_tenant_id) return success_response(data) @router.get("/mounted-ai/topics", response_model=ApiResponse[MountedAITopicListPayload]) async def list_mounted_ai_topics( mode: str | None = Query(default=None), limit: int = Query(default=20, ge=1, le=100), offset: int = Query(default=0, ge=0), current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_topics( tenant_id=effective_tenant_id, limit=limit, offset=offset, mode=mode, ) return success_response(data) @router.get("/mounted-ai/runs", response_model=ApiResponse[MountedAIRunListPayload]) async def list_mounted_ai_runs( status: str | None = Query(default=None), limit: int = Query(default=20, ge=1, le=100), offset: int = Query(default=0, ge=0), current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_runs( tenant_id=effective_tenant_id, limit=limit, offset=offset, status=status, ) return success_response(data) @router.get("/mounted-ai/runs/{run_id}", response_model=ApiResponse[MountedAIRunDetailPayload]) async def get_mounted_ai_run_detail( run_id: int, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) try: data = await MountedAIRuntimeService(db).get_run_detail( tenant_id=effective_tenant_id, run_id=run_id, ) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return success_response(data) @router.get("/mounted-ai/cutover-status", response_model=ApiResponse[MountedAICutoverStatusPayload]) async def get_mounted_ai_cutover_status( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).get_cutover_status(tenant_id=effective_tenant_id) return success_response(data) @router.get("/mounted-ai/eval-suites", response_model=ApiResponse[MountedAIEvalSuiteListPayload]) async def list_mounted_ai_eval_suites( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_eval_suites(tenant_id=effective_tenant_id) return success_response(data) @router.get("/mounted-ai/eval-suites/{dataset_id}", response_model=ApiResponse[MountedAIEvalSuiteDetailPayload]) async def get_mounted_ai_eval_suite_detail( dataset_id: int, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) try: data = await MountedAIRuntimeService(db).get_eval_suite_detail( tenant_id=effective_tenant_id, dataset_id=dataset_id, ) except ValueError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return success_response(data) @router.get("/mounted-ai/training-ops", response_model=ApiResponse[MountedAITrainingOpsPayload]) async def get_mounted_ai_training_ops( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_training_ops(tenant_id=effective_tenant_id) return success_response(data) @router.get("/mounted-ai/hard-cases", response_model=ApiResponse[MountedAIHardCaseListPayload]) async def list_mounted_ai_hard_cases( limit: int = Query(default=20, ge=1, le=100), offset: int = Query(default=0, ge=0), current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) data = await MountedAIRuntimeService(db).list_hard_cases( tenant_id=effective_tenant_id, limit=limit, offset=offset, ) return success_response(data) @router.post("/mounted-ai/admin/source-activation", response_model=ApiResponse[MountedAISourceActivationUpsertPayload]) async def upsert_mounted_ai_source_activation( payload: MountedAISourceActivationUpsertRequest, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) try: data = await MountedAIRuntimeService(db).upsert_source_activation( tenant_id=effective_tenant_id, source_system=payload.source_system, scope_type=payload.scope_type, scope_ref=payload.scope_ref, status=payload.status, cutover_mode=payload.cutover_mode, auto_reply_enabled=payload.auto_reply_enabled, notes=payload.notes, metadata_json=payload.metadata_json, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc return success_response(data) @router.post("/mounted-ai/admin/tool-policy", response_model=ApiResponse[MountedAIToolPolicyUpsertPayload]) async def upsert_mounted_ai_tool_policy( payload: MountedAIToolPolicyUpsertRequest, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) try: data = await MountedAIRuntimeService(db).upsert_tool_policy( tenant_id=effective_tenant_id, tool_name=payload.tool_name, risk_level=payload.risk_level, confirm_required=payload.confirm_required, auto_execute_allowed=payload.auto_execute_allowed, is_active=payload.is_active, source_scope=payload.source_scope, tenant_scope=payload.tenant_scope, policy_metadata=payload.policy_metadata, notes=payload.notes, ) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc return success_response(data) @router.post("/mounted-ai/admin/replay-run", response_model=ApiResponse[MountedAIReplayRunPayload]) async def replay_mounted_ai_run( payload: MountedAIReplayRunRequest, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) try: data = await MountedAIRuntimeService(db).replay_run( tenant_id=effective_tenant_id, actor_id=int(getattr(current_user, "id", 0) or 0), run_id=payload.run_id, asset_type=payload.asset_type, bucket_key=payload.bucket_key, question_text=payload.question_text, answer_text=payload.answer_text, corrected_answer=payload.corrected_answer, notes=payload.notes, persist_quality_asset=payload.persist_quality_asset, overall_score=payload.overall_score, rule_score=payload.rule_score, llm_score=payload.llm_score, findings=payload.findings, ) except ValueError as exc: message = str(exc) raise HTTPException(status_code=404 if "not found" in message else 400, detail=message) from exc return success_response(data)