diff --git a/backend/app/api/routes.py b/backend/app/api/routes.py
index 6881747..2d159e3 100755
--- a/backend/app/api/routes.py
+++ b/backend/app/api/routes.py
@@ -20,6 +20,8 @@ from app.api.schemas import (
ResolvedItemResponse,
ErrorItemResponse,
AnalyticsResponse,
+ AgencyAnalyticsItem,
+ AgencyAnalyticsResponse,
DropdownOptionsResponse,
AgencyResponse,
AgencyCreate,
@@ -587,6 +589,21 @@ async def get_analytics(
return AnalyticsResponse(**analytics)
+@router.get("/analytics/by-agency", response_model=AgencyAnalyticsResponse)
+async def get_analytics_by_agency(
+ db: AsyncSession = Depends(get_db),
+ current_user: User = Depends(get_current_db_user),
+):
+ """Get per-agency analytics breakdown (admin only)."""
+ if current_user.role not in ("super_admin", "oversight_admin"):
+ return AgencyAnalyticsResponse(agencies=[])
+ repo = CampaignRepository(db)
+ rows = await repo.get_analytics_by_agency()
+ return AgencyAnalyticsResponse(
+ agencies=[AgencyAnalyticsItem(**row) for row in rows]
+ )
+
+
# ---------------------------------------------------------------------------
# User Management endpoints
# ---------------------------------------------------------------------------
diff --git a/backend/app/api/schemas.py b/backend/app/api/schemas.py
index 5d52793..a10ce0f 100755
--- a/backend/app/api/schemas.py
+++ b/backend/app/api/schemas.py
@@ -148,6 +148,20 @@ class AnalyticsResponse(BaseModel):
legal_review: int
+class AgencyAnalyticsItem(BaseModel):
+ agency_id: uuid.UUID
+ agency_name: str
+ total_reviews: int
+ passed: int
+ failed: int
+ errors: int
+ legal_review: int
+
+
+class AgencyAnalyticsResponse(BaseModel):
+ agencies: list[AgencyAnalyticsItem]
+
+
# Dropdown options schemas
class DropdownOptionsResponse(BaseModel):
campaigns: list[str]
diff --git a/backend/app/repositories/campaign_repository.py b/backend/app/repositories/campaign_repository.py
index d1654ab..68a67ff 100755
--- a/backend/app/repositories/campaign_repository.py
+++ b/backend/app/repositories/campaign_repository.py
@@ -5,7 +5,7 @@ from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
-from app.models.models import Campaign, Proof, ProofVersion
+from app.models.models import Agency, Campaign, Proof, ProofVersion
class CampaignRepository:
@@ -164,3 +164,39 @@ class CampaignRepository:
"errors": row.errors,
"legal_review": row.legal_review,
}
+
+ async def get_analytics_by_agency(self) -> list[dict]:
+ """Get analytics data grouped by agency."""
+ query = (
+ select(
+ Agency.id.label("agency_id"),
+ Agency.name.label("agency_name"),
+ func.count(ProofVersion.id).label("total"),
+ func.count(ProofVersion.id).filter(ProofVersion.overall_status == "Passed").label("passed"),
+ func.count(ProofVersion.id).filter(ProofVersion.overall_status == "Failed").label("failed"),
+ func.count(ProofVersion.id).filter(ProofVersion.overall_status == "Analysis Error").label("errors"),
+ func.count(ProofVersion.id).filter(ProofVersion.overall_status == "Requires Manual Legal Review").label("legal_review"),
+ )
+ .select_from(ProofVersion)
+ .join(Proof)
+ .join(Campaign)
+ .join(Agency, Campaign.agency_id == Agency.id)
+ .group_by(Agency.id, Agency.name)
+ .order_by(func.count(ProofVersion.id).desc())
+ )
+
+ result = await self.session.execute(query)
+ rows = result.all()
+
+ return [
+ {
+ "agency_id": row.agency_id,
+ "agency_name": row.agency_name,
+ "total_reviews": row.total,
+ "passed": row.passed,
+ "failed": row.failed,
+ "errors": row.errors,
+ "legal_review": row.legal_review,
+ }
+ for row in rows
+ ]
diff --git a/frontend/App.tsx b/frontend/App.tsx
index 0e20f38..976ac9f 100755
--- a/frontend/App.tsx
+++ b/frontend/App.tsx
@@ -852,7 +852,7 @@ const AppContent: React.FC<{ msalInstance: any }> = ({ msalInstance }) => {
const renderContent = () => {
switch (currentView) {
case 'Analytics':
- return
| Agency | +Proofs Reviewed | +Pass Rate | +Failed | +Errors | +Legal Review | +
|---|---|---|---|---|---|
| + {agency.agency_name} + {isSelected && (selected)} + | +{agency.total_reviews} | +
+
+ = 80 ? 'bg-success' : rate < 70 ? 'bg-error' : 'bg-warning'}`}>
+ {rate}%
+
+ |
+ {agency.failed} | +{agency.errors} | +{agency.legal_review} | +