Skip to content

feat: export cost and governance data as CSV/JSONFeat/csv json export token governance - #146

Open
RjyavardhanSingh wants to merge 2 commits into
theagentplane:mainfrom
RjyavardhanSingh:feat/csv-json-export-token-governance
Open

RjyavardhanSingh wants to merge 2 commits into
theagentplane:mainfrom
RjyavardhanSingh:feat/csv-json-export-token-governance

Conversation

@RjyavardhanSingh

Copy link
Copy Markdown
Contributor

What

targets issue #145
On-demand export of run-records and governance events from the control plane's
SQLite store as CSV (finance/spreadsheets) and JSON (pipelines). Two surfaces:
an GET /v1/export HTTP endpoint and a Streamlit Dashboard "Export" expander.

Why

The plane holds all cost and governance data but there was no way to pull it into
a spreadsheet, a cost tool, or an archive. This is the FinOps view (who spent what)
and the compliance audit trail (halts, MUTATE/INJECT decisions, which policy fired).

Changes

Backend — src/tokenops/control/store.py

  • Added _coerce_epoch() helper: defensively converts epoch floats, ISO date
    strings, datetime.date, and datetime objects to epoch floats. Prevents the
    SQLite type-mismatch bug where REAL >= TEXT silently returns zero rows.
  • Added Store.export_runs(): filtered query over the runs table supporting
    from_at, to_at, agent, status, tenant, and limit (capped at 10 000).
    Ordered by started_at DESC.

Backend — src/tokenops/control/http.py

  • Mounted GET /v1/export on the FastAPI app.
  • Query params: from_at, to_at, agent, status, tenant, format (json|csv),
    limit.
  • CSV columns: run_id, agent, status, cost_micros, steps, started_at,
    ended_at, duration_s, dims, halt_reason, detector, governance_events.
  • JSON response includes X-Total-Count header; CSV includes
    Content-Disposition: attachment.

Backend — src/tokenops/server/app.py

  • Imported and mounted mount_export(app, store).

UI — src/tokenops/ui/views/dashboard.py

  • Added "Export run data" expander with four filter controls (From, To, Agent, Status).
  • Displays "N runs matched" count.
  • Download buttons for CSV and JSON.
  • _date_to_epoch() converts Streamlit date_input values to epoch floats
    (start-of-day for from_at, end-of-day for to_at).

Tests — tests/test_export.py

  • 25 tests: Store.export_runs() unit tests (filters, limit, empty, governance
    events), GET /v1/export integration tests (JSON, CSV, empty, limit cap),
    _coerce_epoch() unit tests (None, float, int, date string, datetime string,
    date object, datetime object), and regression tests for string-date filtering.

Also in this branch

  • src/tokenops/control/pricing.py: added Gemini model rates.
  • src/tokenops/providers/openai.py: added OPENAI_BASE_URL passthrough.

Acceptance criteria

  • Pull run-records + governance events over a time range in CSV and JSON.
  • Scoped by read auth and tenant; no cross-tenant export (tenant filter).
  • Admin "Export" button in Dashboard.
  • No DB schema changes — new query only, existing tables untouched.
  • No breaking changes to existing API — new GET /v1/export route only.
  • All 283 tests pass, 0 failures.
  • Lint and format clean.

How to test

  1. make install && make run (plane on :7700, dashboard on :8501)
  2. make demo (agents register runs in tokenops.db)
  3. Open localhost:8501 → scroll to "Export run data" → pick filters → Download CSV
  4. Or via API: curl http://localhost:7700/v1/export?format=csv

Results

CSV
image

JSON

[
  {
    "run_id": "run-5",
    "agent": "researcher",
    "status": "error",
    "parent_run": null,
    "parent_span": null,
    "halt_reason": null,
    "detector": null,
    "cost_micros": 12000,
    "steps": 6,
    "started_at": 1789762067.1769028,
    "ended_at": 1789762077.1769028,
    "task": null,
    "dims": {
      "tenant": "globex"
    },
    "governance_events": [
      {
        "kind": "halt",
        "reason": "model not found",
        "policy": "pre_call_worst_case"
      }
    ]
  },
  {
    "run_id": "run-4",
    "agent": "editor",
    "status": "completed",
    "parent_run": null,
    "parent_span": null,
    "halt_reason": null,
    "detector": null,
    "cost_micros": 3000,
    "steps": 3,
    "started_at": 1789761767.1769006,
    "ended_at": 1789761787.1769009,
    "task": null,
    "dims": {
      "tenant": "acme"
    },
    "governance_events": []
  },
  {
    "run_id": "run-3",
    "agent": "writer",
    "status": "completed",
    "parent_run": null,
    "parent_span": null,
    "halt_reason": null,
    "detector": null,
    "cost_micros": 8000,
    "steps": 5,
    "started_at": 1789760867.1768982,
    "ended_at": 1789760917.1768985,
    "task": null,
    "dims": {
      "tenant": "globex"
    },
    "governance_events": []
  },
  {
    "run_id": "run-1",
    "agent": "researcher",
    "status": "completed",
    "parent_run": null,
    "parent_span": null,
    "halt_reason": null,
    "detector": null,
    "cost_micros": 15000,
    "steps": 12,
    "started_at": 1789759067.1768858,
    "ended_at": 1789759167.1768873,
    "task": null,
    "dims": {
      "tenant": "acme"
    },
    "governance_events": [
      {
        "kind": "mutate",
        "reason": "cap applied",
        "policy": "pre_call_worst_case"
      }
    ]
  },
  {
    "run_id": "run-2",
    "agent": "researcher",
    "status": "halted",
    "parent_run": null,
    "parent_span": null,
    "halt_reason": null,
    "detector": null,
    "cost_micros": 50000,
    "steps": 8,
    "started_at": 1789755467.1768959,
    "ended_at": 1789755567.1768959,
    "task": null,
    "dims": {
      "tenant": "acme"
    },
    "governance_events": [
      {
        "kind": "halt",
        "reason": "budget exceeded",
        "policy": "cost_budget"
      }
    ]
  }
]

Add GET /v1/export endpoint and Dashboard "Export run data" expander for
FinOps / chargeback reporting. Supports CSV (spreadsheets) and JSON
(pipelines) with filters for time range, agent, status, and tenant.

Includes _coerce_epoch() to prevent SQLite type-mismatch when date
strings are passed as timestamps. 25 new tests cover store query,
HTTP integration, and type coercion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant