Skip to content

Add a Usage tab/section to the Billing page (backed by billing-service usage/cost read API) #80

Description

@man4ish

Tracking issue for a Usage view in Studio, to be built as a tab/section
on the existing Billing page (src/ui/pages/Billing.jsx) — not a
separate page. Direction agreed; build deferred to a follow-up.

Related: #79 (Billing tile permission gating — same decision applies to
this tab). Depends on the org-context work already merged in e2fd715
(currentUser.orgId).


Investigation findings

omnibioai-usage-client is emit-only

Source: omnibioai-usage-client/usage_client/. Public surface is
emit_usage_event() → one XADD onto the Redis stream usage:events.
No query/read/aggregate methods. Fails open (Redis outage returns
False, never raises; no retry/queue).

Event shape (models.py): event_id, timestamp, organization_id,
service, resource, action, quantity (float ≥ 0), unit,
optional user_id, trace_id, metadata. resource is dot-namespaced
free text, not an enum.

Wired producers today (cross-repo state, per its README):

Service Events
Workbench (plugins/workflow_runner/usage_emit.py) workflow.execute (count), gpu.hour (hours)
RAG (ragbio/usage_emit.py) rag.query.executed (count); dataset.uploaded defined but unwired
model-registry (usage_emit.py) model.register (count)

tes / billing-service / billing-worker receive USAGE_REDIS_URL
in compose but are consumers/infra, not producers. No token or raw-compute
metering is live today (the README's llm.tokens example is aspirational).

The read API already exists — this is a frontend-only task

billing-worker (running) drains usage:events into MySQL:

usage:events → billing consumer
  → usage_events           (raw persisted, every event)
  → usage_daily_rollups    (per org / day / service+resource+action+unit)
  → billable_usage_records (billable subset)
  → rated_usage_records    (priced) → invoice line items

billing-service (app/routers/billing.py) exposes the read side, and
nginx-router.conf location ^~ /billing already proxies all of it
behind the same auth_request gate the Billing page uses (verified:
every endpoint returns a clean 401 through nginx — routed and gated, no
nginx change needed):

Endpoint Returns Source
GET /billing/organizations/{id}/usage?start_date&end_date raw quantity per (service, action, resource, unit), arbitrary range usage_daily_rollups
GET /billing/organizations/{id}/costs?start_date&end_date total rated $ + breakdowns by service/action/resource rated_usage_records
GET /billing/organizations/{id}/cost-history?start_date&end_date daily $ points (gaps = no usage) rated_usage_records
GET /billing/organizations/{id}/cost-breakdown?…&group_by=service|action|resource|month $ + quantity grouped one dimension at a time rated_usage_records
GET /billing/organizations/{id}/usage-events?…&resource&user_id&limit&offset raw per-event log, paginated (built for the HIPAA RAG-query-log report) usage_events
GET /billing/organizations/{id}/allowances?as_of included/used/remaining vs the rating-engine plan billing_plan_allowances + billable_usage_records

Work required: a usageApi.js (~identical to billingApi.js, or extend
it) + the tab UI + date-range UX. No backend work for the aggregated views.

Data is sparse in this deployment: usage_daily_rollups has real
test data for ~19 orgs (rag.query, model.register, workflow.execute,
gpu.hour), but billing_plan_allowances is empty (so /allowances and
/subscription/usage-limits return nothing for everyone) and there is
exactly 1 rated_usage_records row (so /costs* is near-empty apart from
org 1's $12.50 August period). Pipeline works; it hasn't been fed.

Per-org only

Every endpoint takes organization_id in the path; every query is
filtered by it. Org member → own org only; manage_all_orgs → any one
org, still one at a time. No platform-wide aggregate endpoint exists
a platform-wide view would need N calls or a new billing-service endpoint.

No redundancy with /subscription/usage-limits

Same source events, three different projections:

  • /subscription/usage-limits (already rendered on the Billing page):
    plan quota consumption (used / included, % bar) — needs an
    organization_subscriptions row + billing_plan_allowances.
  • /usage: raw operational quantity, no quota concept, arbitrary
    range — this is the new thing a Usage tab adds.
  • /costs*: dollar cost — the other new thing.

"What you're entitled to" ≠ "what you ran" ≠ "what it cost".

Auth — identical to everything else

billing-service/app/core/iam.py: independent JWT verification via
omnibioai-iam-client (same package api-gateway / control-center / rag
use), token org_id claim must match the URL or caller holds
manage_all_orgs; else 403; bad/missing token → 401. Frontend needs
currentUser.orgId (merged in e2fd715) + Authorization: Bearer
(already in billingApi.js).


Open decisions before building

  1. Permission gating — same as Workbench: Billing tile is ungated — decide the right permission level before wider rollout #79. Raw operational usage / cost is
    plausibly org-member-visible, not admin-only. Decide alongside Workbench: Billing tile is ungated — decide the right permission level before wider rollout #79.
  2. /usage-events per-user data — this endpoint returns a raw
    per-user activity log (it exists for the HIPAA RAG-query-log report).
    Aggregated /usage and /costs do not expose individual users;
    /usage-events does. Decide deliberately whether to surface it at
    all (and if so, behind what gate) — do NOT include it by default. Same
    posture as the cron-log finding.
  3. Date-range UX/usage, /costs, /cost-history all require
    explicit start_date / end_date; pick sensible defaults (current
    billing period? trailing 30 days?).
  4. Whether to show /costs* at all while rated_usage_records is
    effectively empty, or gate that section on there being data.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions