""" FastAPI routes for Risk management. Endpoints: - /risks: Risk listing and CRUD - /risks/matrix: Risk matrix visualization """ import logging from typing import Optional from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from classroom_engine.database import get_db from ..db import RiskRepository, RiskLevelEnum from .schemas import ( RiskCreate, RiskUpdate, RiskResponse, RiskListResponse, RiskMatrixResponse, ) logger = logging.getLogger(__name__) router = APIRouter(tags=["compliance-risks"]) # ============================================================================ # Risks # ============================================================================ @router.get("/risks", response_model=RiskListResponse) async def list_risks( category: Optional[str] = None, status: Optional[str] = None, risk_level: Optional[str] = None, db: Session = Depends(get_db), ): """List risks with optional filters.""" repo = RiskRepository(db) risks = repo.get_all() if category: risks = [r for r in risks if r.category == category] if status: risks = [r for r in risks if r.status == status] if risk_level: try: level = RiskLevelEnum(risk_level) risks = [r for r in risks if r.inherent_risk == level] except ValueError: pass results = [ RiskResponse( id=r.id, risk_id=r.risk_id, title=r.title, description=r.description, category=r.category, likelihood=r.likelihood, impact=r.impact, inherent_risk=r.inherent_risk.value if r.inherent_risk else None, mitigating_controls=r.mitigating_controls, residual_likelihood=r.residual_likelihood, residual_impact=r.residual_impact, residual_risk=r.residual_risk.value if r.residual_risk else None, owner=r.owner, status=r.status, treatment_plan=r.treatment_plan, identified_date=r.identified_date, review_date=r.review_date, last_assessed_at=r.last_assessed_at, created_at=r.created_at, updated_at=r.updated_at, ) for r in risks ] return RiskListResponse(risks=results, total=len(results)) @router.post("/risks", response_model=RiskResponse) async def create_risk( risk_data: RiskCreate, db: Session = Depends(get_db), ): """Create a new risk.""" repo = RiskRepository(db) risk = repo.create( risk_id=risk_data.risk_id, title=risk_data.title, description=risk_data.description, category=risk_data.category, likelihood=risk_data.likelihood, impact=risk_data.impact, mitigating_controls=risk_data.mitigating_controls, owner=risk_data.owner, treatment_plan=risk_data.treatment_plan, ) db.commit() return RiskResponse( id=risk.id, risk_id=risk.risk_id, title=risk.title, description=risk.description, category=risk.category, likelihood=risk.likelihood, impact=risk.impact, inherent_risk=risk.inherent_risk.value if risk.inherent_risk else None, mitigating_controls=risk.mitigating_controls, residual_likelihood=risk.residual_likelihood, residual_impact=risk.residual_impact, residual_risk=risk.residual_risk.value if risk.residual_risk else None, owner=risk.owner, status=risk.status, treatment_plan=risk.treatment_plan, identified_date=risk.identified_date, review_date=risk.review_date, last_assessed_at=risk.last_assessed_at, created_at=risk.created_at, updated_at=risk.updated_at, ) @router.put("/risks/{risk_id}", response_model=RiskResponse) async def update_risk( risk_id: str, update: RiskUpdate, db: Session = Depends(get_db), ): """Update a risk.""" repo = RiskRepository(db) risk = repo.get_by_risk_id(risk_id) if not risk: raise HTTPException(status_code=404, detail=f"Risk {risk_id} not found") update_data = update.model_dump(exclude_unset=True) updated = repo.update(risk.id, **update_data) db.commit() return RiskResponse( id=updated.id, risk_id=updated.risk_id, title=updated.title, description=updated.description, category=updated.category, likelihood=updated.likelihood, impact=updated.impact, inherent_risk=updated.inherent_risk.value if updated.inherent_risk else None, mitigating_controls=updated.mitigating_controls, residual_likelihood=updated.residual_likelihood, residual_impact=updated.residual_impact, residual_risk=updated.residual_risk.value if updated.residual_risk else None, owner=updated.owner, status=updated.status, treatment_plan=updated.treatment_plan, identified_date=updated.identified_date, review_date=updated.review_date, last_assessed_at=updated.last_assessed_at, created_at=updated.created_at, updated_at=updated.updated_at, ) @router.delete("/risks/{risk_id}") async def delete_risk( risk_id: str, db: Session = Depends(get_db), ): """Delete a risk.""" repo = RiskRepository(db) risk = repo.get_by_risk_id(risk_id) if not risk: raise HTTPException(status_code=404, detail=f"Risk {risk_id} not found") db.delete(risk) db.commit() logger.info(f"Risk {risk_id} deleted") return {"success": True, "message": f"Risk {risk_id} deleted"} @router.get("/risks/matrix", response_model=RiskMatrixResponse) async def get_risk_matrix(db: Session = Depends(get_db)): """Get risk matrix data for visualization.""" repo = RiskRepository(db) matrix_data = repo.get_risk_matrix() risks = repo.get_all() risk_responses = [ RiskResponse( id=r.id, risk_id=r.risk_id, title=r.title, description=r.description, category=r.category, likelihood=r.likelihood, impact=r.impact, inherent_risk=r.inherent_risk.value if r.inherent_risk else None, mitigating_controls=r.mitigating_controls, residual_likelihood=r.residual_likelihood, residual_impact=r.residual_impact, residual_risk=r.residual_risk.value if r.residual_risk else None, owner=r.owner, status=r.status, treatment_plan=r.treatment_plan, identified_date=r.identified_date, review_date=r.review_date, last_assessed_at=r.last_assessed_at, created_at=r.created_at, updated_at=r.updated_at, ) for r in risks ] return RiskMatrixResponse(matrix=matrix_data, risks=risk_responses)