Iteration 7 Spec — Admin Dashboard & Metrics API¶
Goal¶
Backend API endpoints giving admins and senior checkers full visibility into request pipeline health, reviewer performance, and audit history. No UI in this iteration — Datastar frontend comes later.
New Endpoints¶
1. GET /admin/requests — All-projects request overview¶
Auth: tre_admin or senior_checker on any project.
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
status |
str (comma-separated) |
all | Filter by status(es) |
project_id |
uuid |
all | Filter by project |
direction |
str |
all | egress or ingress |
sort |
str |
-created_at |
Sort field, prefix - for desc |
limit |
int |
50 | Page size (max 100) |
offset |
int |
0 | Pagination offset |
Response: { items: RequestSummary[], total: int }
{
"items": [
{
"id": "uuid",
"project_id": "uuid",
"project_name": "display_name",
"title": "str",
"status": "HUMAN_REVIEW",
"direction": "egress",
"submitted_by_name": "str",
"object_count": 3,
"created_at": "iso",
"updated_at": "iso",
"age_hours": 72.5
}
],
"total": 142
}
2. GET /admin/metrics — Pipeline metrics¶
Auth: tre_admin or senior_checker.
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
project_id |
uuid |
all | Scope to project |
since |
datetime |
30 days ago | Start of window |
Response:
{
"total_requests": 142,
"by_status": { "DRAFT": 5, "SUBMITTED": 3, ... },
"median_review_hours": 18.5,
"mean_review_hours": 24.2,
"approval_rate": 0.78,
"revision_rate": 0.15,
"rejection_rate": 0.07,
"median_revisions_per_request": 1,
"requests_per_reviewer": [
{ "reviewer_id": "uuid", "reviewer_name": "str", "count": 12 }
],
"stuck_requests": [
{ "request_id": "uuid", "title": "str", "status": "HUMAN_REVIEW", "waiting_hours": 96.2 }
]
}
Stuck threshold: configurable via Settings.stuck_request_hours (default: 72).
3. GET /admin/audit — Filterable audit log¶
Auth: tre_admin.
Query parameters:
| Param | Type | Default | Description |
|---|---|---|---|
project_id |
uuid |
all | Filter by project |
actor_id |
str |
all | Filter by actor |
event_type |
str |
all | Filter by event type (prefix match) |
since |
datetime |
30 days ago | Start of window |
until |
datetime |
now | End of window |
limit |
int |
100 | Page size (max 500) |
offset |
int |
0 | Pagination offset |
Response: { items: AuditEventRead[], total: int }
4. GET /admin/audit/export — CSV export¶
Auth: tre_admin.
Same query params as GET /admin/audit (no limit/offset — streams all matching rows).
Response: text/csv with headers: id, timestamp, event_type, actor_id, request_id, payload.
New Schemas¶
RequestSummary (Pydantic)¶
Fields: id, project_id, project_name, title, status, direction, submitted_by_name, object_count, created_at, updated_at, age_hours.
PipelineMetrics (Pydantic)¶
Fields: total_requests, by_status, median_review_hours, mean_review_hours, approval_rate, revision_rate, rejection_rate, median_revisions_per_request, requests_per_reviewer, stuck_requests.
ReviewerStats (Pydantic)¶
Fields: reviewer_id, reviewer_name, count.
StuckRequest (Pydantic)¶
Fields: request_id, title, status, waiting_hours.
Settings additions¶
| Variable | Type | Default | Description |
|---|---|---|---|
STUCK_REQUEST_HOURS |
int |
72 | Hours before a request is flagged as stuck |
Implementation notes¶
- No new DB tables or migrations. All data from existing tables via queries.
- Metrics queries use SQLAlchemy Core for efficiency (per ADR-0003).
func.count,func.avg, subqueries for median. - Median in SQLite: use Python-side sort. In PostgreSQL:
percentile_cont(0.5). age_hourscomputed as(now - created_at).total_seconds() / 3600.- Stuck requests:
status in (SUBMITTED, AGENT_REVIEW, HUMAN_REVIEW)andupdated_at < now - stuck_threshold. - CSV export uses
StreamingResponsewith a generator yielding rows. - Senior checker access: query
ProjectMembershipforrole = senior_checkeron any project. If found, grant read access to admin endpoints (except audit export, which is admin-only).
New files¶
src/trevor/routers/admin.py— all 4 endpointssrc/trevor/schemas/admin.py— RequestSummary, PipelineMetrics, ReviewerStats, StuckRequestsrc/trevor/services/metrics_service.py— query functionstests/test_admin.py— endpoint tests
Test plan¶
GET /admin/requests— empty, with data, filtered by status, filtered by project, paginationGET /admin/metrics— basic metrics calculation, stuck detectionGET /admin/audit— filtered by event_type, by actor, by date range, paginationGET /admin/audit/export— CSV format, content-type header- Auth: non-admin rejected (403), senior_checker access for requests/metrics