From 4ffadd223cb370233643119f04abf667015cf6dc Mon Sep 17 00:00:00 2001 From: Inferencebot Date: Wed, 28 Oct 2026 04:15:03 +0000 Subject: [PATCH] metrics: share the series with members through the broker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/main.py | 58 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/app/main.py b/app/main.py index a435163..fde633c 100644 --- a/app/main.py +++ b/app/main.py @@ -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):