"""Agentic RICS inspection endpoints.""" from __future__ import annotations import logging from datetime import UTC, datetime from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy import update from sqlalchemy.ext.asyncio import AsyncSession from app.api import rate_limit from app.db.database import get_db from app.db.models import Report, ReportStatus from app.jobs.models import GenerationJob, JobType from app.jobs.queue import dispatch_or_enqueue from app.services.generation import mark_report_generation_failed, run_agentic_full_report_job from app.agentic.runtime_status import is_openai_inspector_live from app.agentic.tools import list_tool_specs logger = logging.getLogger(__name__) router = APIRouter() @router.post( "/reports/{report_id}/agentic/generate", summary="Generate a full RICS-style report using an agentic pipeline", ) async def agentic_generate_report( report_id: str, request: Request, body: dict, db: AsyncSession = Depends(get_db), _rl: None = Depends(rate_limit.check_generate), ) -> dict: tenant_id: str = request.state.tenant_id report = await db.get(Report, report_id) if report is None or report.tenant_id != tenant_id: raise HTTPException(status_code=404, detail="Report not found") bullets_by_section = body.get("bullets_by_section") or {} if not isinstance(bullets_by_section, dict) or not bullets_by_section: raise HTTPException(status_code=422, detail="bullets_by_section must be a non-empty object") ai_percent = body.get("ai_percent", 50) retrieval_level = body.get("retrieval_level", "paragraph") reference_document_ids = body.get("reference_document_ids") or [] similarity_scan = bool(body.get("similarity_scan", False)) peer_sections = body.get("peer_sections") if isinstance(body.get("peer_sections"), dict) else {} sim_excl = body.get("similarity_exclude_document_ids") similarity_exclude_document_ids = ( [str(x) for x in sim_excl] if isinstance(sim_excl, list) else [] ) interference_level = body.get("interference_level") il_arg = str(interference_level).strip() if interference_level is not None else None try: ai_percent_i = int(ai_percent) except Exception: # noqa: BLE001 ai_percent_i = 50 ai_percent_i = max(0, min(100, ai_percent_i)) cas_result = await db.execute( update(Report) .where( Report.id == report_id, Report.tenant_id == tenant_id, Report.status != ReportStatus.generating, ) .values( status=ReportStatus.generating, generation_started_at=datetime.now(UTC), ) ) if cas_result.rowcount == 0: raise HTTPException( status_code=409, detail="A generation task is already running for this report.", ) await db.commit() bullets_map = { str(k): list(v) for k, v in bullets_by_section.items() if isinstance(v, list) } ref_ids = ( [str(x) for x in reference_document_ids] if isinstance(reference_document_ids, list) else None ) peer_map = {str(k): str(v) for k, v in (peer_sections or {}).items()} agentic_job = GenerationJob( job_type=JobType.agentic_full, report_id=report_id, tenant_id=tenant_id, payload={ "bullets_by_section": bullets_map, "ai_percent": ai_percent_i, "retrieval_level": str(retrieval_level), "reference_document_ids": ref_ids, "similarity_scan": similarity_scan, "peer_sections": peer_map, "similarity_exclude_document_ids": similarity_exclude_document_ids or None, "interference_level": il_arg, }, ) async def _run_agentic_task() -> None: try: await run_agentic_full_report_job( report_id, tenant_id, bullets_by_section=bullets_map, ai_percent=ai_percent_i, retrieval_level=str(retrieval_level), reference_document_ids=ref_ids, similarity_scan=similarity_scan, peer_sections=peer_map, similarity_exclude_document_ids=similarity_exclude_document_ids or None, interference_level=il_arg, ) except Exception as exc: # noqa: BLE001 logger.exception("Agentic background task failed report=%s", report_id) await mark_report_generation_failed(report_id, tenant_id, str(exc)) queue_mode = await dispatch_or_enqueue(job=agentic_job, inline_factory=_run_agentic_task) inspector_mode = "openai_tool_agent" if is_openai_inspector_live() else "legacy_pipeline" queue_hint = " (Redis worker)" if queue_mode == "redis" else "" return { "report_id": report_id, "status": "generating", "message": ( f"Agentic generation queued{queue_hint}. " f"Poll GET /reports/{report_id}/status or " f"GET /reports/{report_id}/sections for results." ), "inspector_mode": inspector_mode, "ai_percent": ai_percent_i, "retrieval_level": str(retrieval_level), "agent_tools": [s.name for s in list_tool_specs()], }