Summary
The admin Statistics & Diagnostics dashboard shows no metrics even after uploading and processing several documents. Investigation found the daily rollup crashes on every run, so the stats_daily_rollup table is never populated — the dashboard is permanently empty, not merely stale. Two related UX defects compound the confusion.
Root cause (the empty dashboard)
app/services/stats_aggregator._rollup_content runs:
select(WikiPage.page_type, func.count(WikiPage.id)).group_by(WikiPage.page_type)
WikiPage.page_type is a hybrid_property whose SQL form is a CASE over slug with the bucket labels as literals. Under asyncpg those literals compile to bind parameters, so the CASE in the SELECT ($1..$9) and the identical CASE in the GROUP BY ($10..$18) occupy different parameter slots. Postgres cannot match them and raises:
column "wiki_pages.slug" must appear in the GROUP BY clause or be used in an aggregate function
This exception aborts the rollup transaction, and because run_daily_rollup shares one session across sections, every section then fails with current transaction is aborted (observed: {content:-1, contribution:-1, usage:-1, gaps:-1, audit:-1}). Net effect: the nightly daily_stats_rollup_cron has never once succeeded, and every dashboard KPI renders as —.
Secondary UX defects
- Misleading "As of".
GET /admin/stats/overview returned as_of = to_date (= today), implying the numbers were current, when the underlying rollup covers yesterday. The rollup rows carry a real computed_at that was never surfaced.
- Confusing controls. Two buttons — "Refresh" (re-fetches aggregated rows) and "Rollup {date}" (recomputes the rollup table) — with no indication of the difference, and the rollup targeted
to_date (yesterday), so today's activity could never appear even after clicking it.
Repro
- Upload/process documents so wiki pages exist (verified: 9 sources, 47 wiki pages present).
- Open the admin dashboard → every KPI shows
—.
- Trigger the rollup → all sections return
-1; server logs show the GROUP BY/InFailedSQLTransactionError cascade.
Fixes applied locally
- Rollup crash: bucket
page_type in Python (there are only tens of pages; reuses the hybrid's Python branch) instead of grouping by the parameterized CASE in SQL. All five sections now succeed: {content:10, contribution:8, usage:9, gaps:1, audit:2}, and wiki.pages.total = 47 lands with a real computed_at.
- Honest "As of":
OverviewResponse.as_of now returns the freshest computed_at across the KPI rows (a UTC datetime, or null if never computed).
- Clear controls: renamed the buttons to "Refresh Page" (page/data reload only) and "Refresh Statistics" (recompute the rollup as of now, targeting today so same-day activity is captured, then reload). The overview footer now reads "Statistics as of <local time> (<HH:MM> UTC)".
Known gap (not fixed here)
The aggregator tracks wiki pages / revisions / drafts / MCP usage but has no document/source-count metric — uploaded documents are never a first-class dashboard number even after this fix. Flagging for a follow-up if a "documents ingested" KPI is desired.
Summary
The admin Statistics & Diagnostics dashboard shows no metrics even after uploading and processing several documents. Investigation found the daily rollup crashes on every run, so the
stats_daily_rolluptable is never populated — the dashboard is permanently empty, not merely stale. Two related UX defects compound the confusion.Root cause (the empty dashboard)
app/services/stats_aggregator._rollup_contentruns:WikiPage.page_typeis ahybrid_propertywhose SQL form is aCASEoverslugwith the bucket labels as literals. Under asyncpg those literals compile to bind parameters, so theCASEin the SELECT ($1..$9) and the identicalCASEin the GROUP BY ($10..$18) occupy different parameter slots. Postgres cannot match them and raises:This exception aborts the rollup transaction, and because
run_daily_rollupshares one session across sections, every section then fails withcurrent transaction is aborted(observed:{content:-1, contribution:-1, usage:-1, gaps:-1, audit:-1}). Net effect: the nightlydaily_stats_rollup_cronhas never once succeeded, and every dashboard KPI renders as—.Secondary UX defects
GET /admin/stats/overviewreturnedas_of = to_date(= today), implying the numbers were current, when the underlying rollup covers yesterday. The rollup rows carry a realcomputed_atthat was never surfaced.to_date(yesterday), so today's activity could never appear even after clicking it.Repro
—.-1; server logs show theGROUP BY/InFailedSQLTransactionErrorcascade.Fixes applied locally
page_typein Python (there are only tens of pages; reuses the hybrid's Python branch) instead of grouping by the parameterized CASE in SQL. All five sections now succeed:{content:10, contribution:8, usage:9, gaps:1, audit:2}, andwiki.pages.total = 47lands with a realcomputed_at.OverviewResponse.as_ofnow returns the freshestcomputed_atacross the KPI rows (a UTC datetime, or null if never computed).Known gap (not fixed here)
The aggregator tracks wiki pages / revisions / drafts / MCP usage but has no document/source-count metric — uploaded documents are never a first-class dashboard number even after this fix. Flagging for a follow-up if a "documents ingested" KPI is desired.