modcomms/backend/app/api/schemas.py
michael e3575052ee Add per-agency analytics breakdown table for admin users
New GET /analytics/by-agency endpoint groups review metrics by agency.
The Analytics page now shows a sortable agency performance table with
pass rates, failures, errors, and legal review counts for each agency.
Only visible to super_admin and oversight_admin users. Selected agency
row is highlighted when the AgencyFilterBar is active.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 14:33:48 -06:00

238 lines
5.1 KiB
Python
Executable file

"""Pydantic schemas for API request/response validation."""
import uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel
# Campaign schemas
class CampaignCreate(BaseModel):
name: str
workfront_id: Optional[str] = None
client_lead: Optional[str] = None
agency_lead: Optional[str] = None
brand_guidelines: Optional[str] = None
class CampaignUpdate(BaseModel):
name: Optional[str] = None
workfront_id: Optional[str] = None
client_lead: Optional[str] = None
agency_lead: Optional[str] = None
brand_guidelines: Optional[str] = None
status: Optional[str] = None
class CampaignResponse(BaseModel):
id: uuid.UUID
name: str
workfront_id: Optional[str]
client_lead: Optional[str]
agency_lead: Optional[str]
brand_guidelines: Optional[str]
status: str
agency: Optional[str]
created_by: Optional[uuid.UUID]
created_at: datetime
updated_at: datetime
proofs: int = 0
class Config:
from_attributes = True
# Proof schemas
class ProofCreate(BaseModel):
proof_name: str
channel: Optional[str] = None
sub_channel: Optional[str] = None
proof_type: Optional[str] = None
class ProofVersionResponse(BaseModel):
id: uuid.UUID
version: int
file_storage_key: Optional[str]
thumbnail_url: Optional[str]
agent_review: Optional[dict]
overall_status: Optional[str]
workfront_id: Optional[str]
is_identical_file: Optional[bool] = False
created_at: datetime
class Config:
from_attributes = True
class ProofResponse(BaseModel):
id: uuid.UUID
proof_name: str
channel: Optional[str]
sub_channel: Optional[str]
proof_type: Optional[str]
workfront_id: Optional[str]
created_at: datetime
versions: list[ProofVersionResponse] = []
class Config:
from_attributes = True
# Audit schemas
class FlaggedItemCreate(BaseModel):
agent_flagged: str
comments: Optional[str] = None
class FlaggedItemResponse(BaseModel):
id: uuid.UUID
proof_version_id: uuid.UUID
agent_flagged: str
comments: Optional[str]
submitter_name: Optional[str]
submitter_agency: Optional[str]
campaign_name: Optional[str]
proof_name: Optional[str]
version: Optional[int]
created_at: datetime
class Config:
from_attributes = True
class ResolvedItemCreate(BaseModel):
agent: str
issue: Optional[str] = None
resolution: Optional[str] = None
class ResolvedItemResponse(BaseModel):
id: uuid.UUID
proof_version_id: uuid.UUID
agent: str
issue: Optional[str]
resolution: Optional[str]
submitter_name: Optional[str]
submitter_agency: Optional[str]
campaign_name: Optional[str]
proof_name: Optional[str]
version: Optional[int]
created_at: datetime
class Config:
from_attributes = True
class ErrorItemResponse(BaseModel):
id: uuid.UUID
proof_version_id: uuid.UUID
error_summary: Optional[str]
submitter_name: Optional[str]
submitter_agency: Optional[str]
campaign_name: Optional[str]
proof_name: Optional[str]
version: Optional[int]
created_at: datetime
class Config:
from_attributes = True
# Analytics schemas
class AnalyticsResponse(BaseModel):
total_reviews: int
passed: int
failed: int
errors: int
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]
channels: dict[str, dict[str, list[str]]]
brand_guidelines: list[str] = []
# Agency schemas
class AgencyResponse(BaseModel):
id: uuid.UUID
name: str
class Config:
from_attributes = True
# User schemas
class CurrentUserResponse(BaseModel):
"""Response for /api/me - the authenticated user's own profile."""
id: uuid.UUID
email: str
name: str
role: str
agency_id: Optional[uuid.UUID]
agency_name: Optional[str]
class Config:
from_attributes = True
class UserResponse(BaseModel):
id: uuid.UUID
email: str
name: str
role: str
agency: Optional[str]
agency_id: Optional[uuid.UUID] = None
created_at: datetime
class Config:
from_attributes = True
class UserUpdate(BaseModel):
"""Request body for updating a user's role and/or agency."""
role: Optional[str] = None
agency_id: Optional[uuid.UUID] = None
class AgencyCreate(BaseModel):
"""Request body for creating a new agency."""
name: str
# User change log schemas
class UserChangeLogResponse(BaseModel):
id: uuid.UUID
change_type: str
field_changed: Optional[str]
old_value: Optional[str]
new_value: Optional[str]
changed_by_name: Optional[str]
created_at: datetime
class Config:
from_attributes = True
# Support email schemas
class SupportEmailRequest(BaseModel):
message: str
subject: str
user_name: Optional[str] = None
user_email: Optional[str] = None