metrics: share the series with members through the broker

Members asked to see how the co-op is doing, not just their own usage. The
series was admin-only; now the same query serves both surfaces.

- metrics_series(days) holds the query once; /admin/metrics/series and the new
  /broker/metrics both call it (no second copy to drift).
- /broker/metrics is broker-authenticated and returns AGGREGATE, NON-PERSONAL
  figures only: co-op size, requests served, spend. Same class as
  /broker/model-leaderboard, and the same rule — no per-member figure passes
  through the broker.
This commit is contained in:
inference-bot committed 2026-10-28 04:15:03 +00:00
1 parent 6ab0554d00
commit 4ffadd223c
1 file changed
+42 -16
+42 -16
View File
@@ -3054,22 +3054,14 @@ async def admin_overview(request: Request):
}
@app.get("/admin/metrics/series")
@limiter.limit("30/minute")
async def admin_metrics_series(request: Request, days: int = 90):
"""Daily metrics series for charts.
def metrics_series(days: int = 90) -> list[dict]:
"""Daily metrics rows, oldest first.
Authenticated by the X-Admin-Token header. Returns oldest-first rows from
`metrics_daily` for the last `days` days, for the admin panel and the member
dashboard to chart from one source of truth. A NULL column means that source
could not be read on that day: render a gap, not a zero.
Cumulative columns (members_*, newsletter_subs, matrix_space_members) are
counts as of that day; spend_usd/requests are that day's traffic only.
Backfilled days carry a LOWER BOUND for members_total (members provisioned
without a welcome email are absent from the history).
One query, two callers: the admin panel (via /admin/metrics/series) and the
member dashboard (via /broker/metrics). Columns are cumulative as of that
day except spend_usd/requests, which are that day only; NULL means the
source could not be read that day.
"""
_verify_admin_token(request)
limit = max(1, min(int(days or 90), 730))
conn = get_db()
try:
@@ -3092,8 +3084,27 @@ async def admin_metrics_series(request: Request, days: int = 90):
"requests",
"recorded_at",
]
series = [dict(zip(cols, row)) for row in reversed(rows)]
return {"series": series, "count": len(series), "days": limit}
return [dict(zip(cols, row)) for row in reversed(rows)]
@app.get("/admin/metrics/series")
@limiter.limit("30/minute")
async def admin_metrics_series(request: Request, days: int = 90):
"""Daily metrics series for charts.
Authenticated by the X-Admin-Token header. Returns oldest-first rows from
`metrics_daily` for the last `days` days, for the admin panel and the member
dashboard to chart from one source of truth. A NULL column means that source
could not be read on that day: render a gap, not a zero.
Cumulative columns (members_*, newsletter_subs, matrix_space_members) are
counts as of that day; spend_usd/requests are that day's traffic only.
Backfilled days carry a LOWER BOUND for members_total (members provisioned
without a welcome email are absent from the history).
"""
_verify_admin_token(request)
series = metrics_series(days)
return {"series": series, "count": len(series), "days": max(1, min(int(days or 90), 730))}
@app.get("/admin/model-usage")
@@ -3469,6 +3480,21 @@ async def broker_usage(request: Request, days: int = 0):
return await broker_get_usage(email, days=days)
@app.get("/broker/model-leaderboard")
@limiter.limit("30/minute")
async def broker_metrics(request: Request, days: int = 90):
"""Co-op metrics over time, for the member dashboard.
The same series the admin panel reads. It comes through the broker because
it is AGGREGATE and non-personal — co-op size, requests served, spend — the
same class of social-proof data as /broker/model-leaderboard. No per-member
figure passes through here, and members asked to see how the co-op is doing.
"""
_verify_broker_secret(request)
series = metrics_series(days)
return {"series": series, "count": len(series), "days": max(1, min(int(days or 90), 730))}
@app.get("/broker/model-leaderboard")
@limiter.limit("30/minute")
async def broker_model_leaderboard(request: Request, days: int = 0):