"""安全治理中心 — unified read-only landing aggregate. One call that stitches the two 等保/认证/审计 pillars + GRC posture into a single pane for the security lead: periodic self-check health, the latest self-assessment cycle, open remediation measures, and the GRC control roll-up. Pure projection over existing services — no new tables. """ from __future__ import annotations import uuid from fastapi import APIRouter, Depends, Request from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.auth.models import AuthUser from app.core.database import get_db from app.core.dependencies import get_current_user from app.core.exceptions import ForbiddenError from app.core.rbac import can_view_security from app.core.response import success_response from app.domains.auditor import assessment_service, self_check_service from app.domains.auditor.models import AuditorAssessmentMeasure router = APIRouter(prefix="/api/v1/auditor/governance", tags=["auditor"]) @router.get("/overview") async def governance_overview( request: Request, user: AuthUser = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): if not can_view_security(user.role_names): raise ForbiddenError( "auditor or global_admin role required to view security data", ) self_check = await self_check_service.get_overview(db) cycles = await assessment_service.list_cycles(db) latest_cycle = cycles[0] if cycles else None open_measures = int((await db.execute( select(func.count()).select_from(AuditorAssessmentMeasure).where( AuditorAssessmentMeasure.archived.is_(False), AuditorAssessmentMeasure.status != "done", ) )).scalar() or 0) # GRC control roll-up (best-effort — never block the landing on it). controls: dict = {} try: from app.domains.auditor.compliance_service import get_posture posture = await get_posture(db) controls = posture.get("controls", {}) except Exception: # noqa: BLE001 — posture is a nice-to-have on this page controls = {} # Half-yearly access self-attestation coverage tile (best-effort). access_attestation: dict | None = None try: from app.domains.auditor import access_certification_service as acs latest = await acs.latest_campaign_status( db, campaign_type=acs.CAMPAIGN_TYPE_SELF_ATTESTATION) if latest is not None: summary = await acs.build_self_attestation_summary( db, certification_id=uuid.UUID(latest["id"])) access_attestation = { "certification_id": latest["id"], "label": latest["label"], "status": latest["status"], "coverage_pct": summary["coverage_pct"] if summary else None, "flagged_removals": ( summary["by_decision"].get("revoke", 0) if summary else 0), "non_responders": ( len(summary["non_responders"]) if summary else 0), } except Exception: # noqa: BLE001 access_attestation = None return success_response( { "self_check": self_check["summary"], "latest_assessment": latest_cycle, "open_measures": open_measures, "controls": controls, "access_attestation": access_attestation, }, request=request, )