"""HTTP API for the identity data-quality panel. GET /api/v1/identity/data-quality/summary tiles + trend GET /api/v1/identity/data-quality/findings paged list PATCH /api/v1/identity/data-quality/findings/{id} acknowledge / accept-risk / resolve POST /api/v1/identity/data-quality/scan run a scan now Reads are gated at ``require_admin_read`` (global_admin / auditor / management — the governance read tier); decisions + manual scan are ``global_admin``. """ from __future__ import annotations from uuid import UUID from fastapi import APIRouter, Depends, Query, Request from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.auth.models import AuthUser from app.core.database import get_db from app.core.dependencies import require_admin, require_admin_read from app.core.response import success_response from app.identity.data_quality_service import IdentityDataQualityService router = APIRouter(prefix="/api/v1/identity/data-quality", tags=["identity"]) class DecisionRequest(BaseModel): status: str # acknowledged | accepted_risk | resolved note: str | None = None @router.get("/summary", summary="Open-finding tiles + scan trend") async def get_summary( request: Request, _user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await IdentityDataQualityService(db).summary() return success_response(data=data, request=request) @router.get("/findings", summary="List data-quality findings") async def list_findings( request: Request, status: str | None = Query(None), finding_type: str | None = Query(None), severity: str | None = Query(None), page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=200), _user: AuthUser = Depends(require_admin_read()), db: AsyncSession = Depends(get_db), ): data = await IdentityDataQualityService(db).list_findings( status=status, finding_type=finding_type, severity=severity, page=page, page_size=page_size, ) return success_response(data=data, request=request) @router.patch("/findings/{finding_id}", summary="Decide on a finding") async def decide_finding( finding_id: UUID, body: DecisionRequest, request: Request, user: AuthUser = Depends(require_admin()), db: AsyncSession = Depends(get_db), ): data = await IdentityDataQualityService(db).decide( finding_id=finding_id, status=body.status, note=body.note, actor_user_id=user.id, ) return success_response(data=data, request=request) @router.post("/scan", summary="Run a data-quality scan now") async def run_scan_now( request: Request, user: AuthUser = Depends(require_admin()), db: AsyncSession = Depends(get_db), ): data = await IdentityDataQualityService(db).run_scan( trigger_source=f"manual:{user.id}", ) return success_response(data=data, request=request)