"""CRA <-> IACE link. A CRA project references its IACE (CE risk assessment) project — one assessment, two views: the IACE "CRA / Cyber" tab resolves the linked CRA project's latest assessment snapshot via this reference (instead of a demo fixture). Thin standalone router (cra_routes.py is over the LOC cap). """ from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from sqlalchemy import text from database import SessionLocal from .tenant_utils import get_tenant_id router = APIRouter(prefix="/v1/cra", tags=["cra"]) class LinkRequest(BaseModel): iace_project_id: str @router.post("/projects/{project_id}/link-iace") async def link_iace(project_id: str, body: LinkRequest, tenant_id: str = Depends(get_tenant_id)): db = SessionLocal() try: row = db.execute(text(""" UPDATE compliance_cra_projects SET linked_iace_project_id = CAST(:iace AS uuid), updated_at = NOW() WHERE id = CAST(:pid AS uuid) AND tenant_id = :tid RETURNING id """), {"iace": body.iace_project_id, "pid": project_id, "tid": tenant_id}).fetchone() db.commit() if not row: raise HTTPException(status_code=404, detail="CRA project not found") return {"cra_project_id": project_id, "linked_iace_project_id": body.iace_project_id} except HTTPException: raise except Exception: db.rollback() raise finally: db.close() @router.get("/by-iace/{iace_project_id}") async def by_iace(iace_project_id: str, tenant_id: str = Depends(get_tenant_id)): """The CRA project linked to this IACE project + its latest assessment snapshot.""" db = SessionLocal() try: proj = db.execute(text(""" SELECT id, name, cra_classification, status FROM compliance_cra_projects WHERE linked_iace_project_id = CAST(:iace AS uuid) AND tenant_id = :tid AND status != 'archived' ORDER BY created_at DESC LIMIT 1 """), {"iace": iace_project_id, "tid": tenant_id}).fetchone() if not proj: return {"linked": False} snap = db.execute(text(""" SELECT id, version, generation_context FROM compliance_cra_documents WHERE cra_project_id = CAST(:pid AS uuid) AND tenant_id = :tid AND doc_type = 'doc_risk_assessment' ORDER BY version DESC LIMIT 1 """), {"pid": str(proj.id), "tid": tenant_id}).fetchone() return { "linked": True, "cra_project": {"id": str(proj.id), "name": proj.name, "classification": proj.cra_classification, "status": proj.status}, "assessment": (snap.generation_context if snap else None), "snapshot_version": (snap.version if snap else None), } finally: db.close()