fix(admin): keyset export paging, rollup-free spend, consistent totals

Review pass over the preceding commits.

- The activity export paged by OFFSET. The journals are append-only and the
  feed is newest-first, so rows written mid-export shift the window down and
  repeat rows already emitted. Pages by keyset on (created_at, feed, id) now.
- top_token_users and the per-user breakdown counted run-level rollup rows.
  A scheduled run already has a row per LLM call, so its spend was billed
  twice -- invisible while the column was tokens, obvious once it was dollars.
  Both now exclude them, matching every other spend query.
- total_cost was summed from the per-model split, which drops rows with no
  model_id and so undercounted the figure printed above the series. Summed
  from the series instead.
- The CSV export encoded its detail cell without the fallback the NDJSON
  branch had, so a non-JSON-native value would have failed the stream.
- The activity view reset pagination in an effect, which fetched the stale
  page against the new filters before fetching again. Reset in the setters.
- The audit taxonomy is no longer re-exported from the helper module; the one
  caller that wanted it imports from where it lives.
This commit is contained in:
arc53-machine committed 2026-09-22 10:41:46 +01:00
1 parent f6f8af8cef
commit c2d1893992
9 files changed
+113 -47

No files matched your search

+2 -1
View File
@@ -6,7 +6,8 @@ from unittest.mock import MagicMock, Mock, patch
import pytest
from docsgpt.api.audit import ACTIVITY_CATEGORIES, category_for, record_event
from docsgpt.api.audit import record_event
from docsgpt.audit_events import ACTIVITY_CATEGORIES, category_for
@pytest.fixture
@@ -167,11 +167,11 @@ class TestPerRoundUsageRows:
return messages, None
rows = []
with patch.object(
usage_mod,
"_persist_call_usage",
side_effect=lambda llm_, cu: rows.append(dict(cu)),
):
def _record(llm_, cu, *, duration_ms=None, ttft_ms=None):
rows.append({**cu, "duration_ms": duration_ms, "ttft_ms": ttft_ms})
with patch.object(usage_mod, "_persist_call_usage", side_effect=_record):
with patch.object(handler, "handle_tool_calls", fake_tool_calls):
first = llm.gen_stream(
model="m", messages=[{"role": "user", "content": "hi"}]
@@ -185,6 +185,9 @@ class TestPerRoundUsageRows:
counts = lambda row: {k: row[k] for k in ("prompt_tokens", "generated_tokens")} # noqa: E731
assert counts(rows[0]) == {"prompt_tokens": 100, "generated_tokens": 10}
assert counts(rows[1]) == {"prompt_tokens": 200, "generated_tokens": 20}
# Each round is timed independently, and each streamed a chunk.
assert all(row["duration_ms"] is not None for row in rows)
assert all(row["ttft_ms"] is not None for row in rows)
class TestPreferProviderUsageClaim: