Skip to main content
Version: 2.x

:::info Executable tutorial

This page is generated from a tested Jupyter notebook. Open it in Google Colab or view the source notebook.

:::

Query usage analytics

Bursar records metered charges in PostgreSQL usage rollups so applications can query spend without scanning the ledger. This tutorial seeds usage, groups it by account and model, inspects daily totals, and reads aggregate statistics.

Learning objectives​

After completing this tutorial, you can:

  • Query spend by account, model, and day
  • Rank high-usage accounts
  • Read aggregate usage statistics
  • Distinguish usage rollups from canonical ledger entries

Prerequisites​

  • Complete the credit lifecycle tutorial
  • Start the notebook server from samples/python/notebooks/

Setup​

Three demo users start on the pro plan, which has no free allowance, so every deduction becomes real spend against their purchased credits. (The free plan's monthly allowance would otherwise cover small deducts and they would not show up as charged spend.)

import atexit
from shared import start_postgres_store, cleanup, base_config, publish_config, USER_ADA, USER_ALEX, USER_JAMAL
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from bursar.metrics import UsageMetrics

store, pgdata = start_postgres_store()
atexit.register(cleanup, pgdata)
bursar = publish_config(store, base_config())
credits = bursar.credits

for user, account in (
(USER_ADA, "acct_ada"),
(USER_ALEX, "acct_alex"),
(USER_JAMAL, "acct_jamal"),
):
bursar.accounts.on_account_created(user, account)
credits.set_user_plan(user, "pro")
credits.add_credits(
user, Decimal(5000), idempotency_key=f"analytics:seed:{account}"
)
print("users on pro plan")

Seeding spend​

Each deduction is priced by the standard rate card: input tokens at 0.0025 per 1M, output at 0.0100 per 1M, cached reads at 0.00125 per 1M, and one execution job at 0.04 per job.

credits.deduct(USER_ADA, UsageMetrics(
operation="completion",
measures={"input_tokens": Decimal(100000), "output_tokens": Decimal(20000)},
dimensions={"model": "gpt-4o"},
), idempotency_key="analytics:ada:completion:1")
credits.deduct(USER_ADA, UsageMetrics(
operation="completion",
measures={
"input_tokens": Decimal(200000),
"output_tokens": Decimal(50000),
"cache_read_tokens": Decimal(100000),
},
dimensions={"model": "gpt-4o-mini"},
), idempotency_key="analytics:ada:completion:2")
credits.deduct(USER_ALEX, UsageMetrics(
operation="completion",
measures={"input_tokens": Decimal(50000), "output_tokens": Decimal(10000)},
dimensions={"model": "gpt-4o"},
), idempotency_key="analytics:alex:completion:1")
credits.deduct(USER_JAMAL, UsageMetrics(
operation="execution",
measures={"jobs": Decimal(5), "compute_seconds": Decimal(30)},
dimensions={"model": "gpt-4o"},
), idempotency_key="analytics:jamal:execution:1")

start = datetime.now(UTC) - timedelta(days=1)
end = datetime.now(UTC) + timedelta(days=1)
print("seeded")

Spend by user and by model​

spend_by_user and spend_by_model aggregate charged spend (never allowance-covered usage) over a time window.

for row in credits.spend_by_user(start, end):
print(row.user_id[:8], row.total_spend, "entries:", row.entry_count)

for row in credits.spend_by_model(start, end):
print(row.model, row.total_spend, "entries:", row.entry_count)

Top users and daily spend​

top_users ranks accounts by spend in the window. daily_spend projects the same data per UTC day — currently the Postgres backend surfaces rollup days as date objects while the typed row expects str, so validation raises; the other analytics methods are unaffected.

for row in credits.top_users(5, start, end):
print(row.user_id[:8], row.total_spend)

try:
for row in credits.daily_spend(start, end):
print(row.date, row.total_spend, row.entry_count)
except Exception as exc:
print(type(exc).__name__, str(exc)[:60])

Aggregate statistics​

aggregate_stats collapses the window into one row: total credits consumed, active users, average daily spend, and the top model and user.

stats = credits.aggregate_stats(start, end)
print("total consumed:", stats.total_credits_consumed)
print("active users:", stats.active_users)
print("avg daily spend:", stats.avg_daily_spend)
print("top model:", stats.top_model)
print("top user:", stats.top_user[:8])

The metered charges​

list_usage_charges returns the raw usage-charge rows behind the rollups, including allowance_covered (how much of the charge was covered by free allowance) and the idempotency key that makes webhook redelivery safe.

page = credits.list_usage_charges(USER_ADA, limit=10)
print("charges:", len(page.items))
for charge in page.items:
print(charge.model, charge.operation, "requested:", charge.requested, "charged:", charge.charged,
"allowance covered:", charge.allowance_covered, "idem:", charge.idempotency_key[:24])
print("all done")