Skip to content

feat(analytics): org model-usage analytics — ledger fields, telemetry dimensions, API + dashboard - #3491

Open
benjaminshafii wants to merge 12 commits into
devfrom
feat/3483-model-usage-analytics
Open

feat(analytics): org model-usage analytics — ledger fields, telemetry dimensions, API + dashboard#3491
benjaminshafii wants to merge 12 commits into
devfrom
feat/3483-model-usage-analytics

Conversation

@benjaminshafii

@benjaminshafii benjaminshafii commented Aug 4, 2026

Copy link
Copy Markdown
Member

Closes #3483

What

Org admins get model-usage analytics: which models their team actually uses, whether people stick with the default model or manually switch, and per-request token counts persisted for future spend views.

How (five stages, one per commit)

  1. Schemainference_usage_ledger_entries gains nullable model_id, provider_id, input_tokens, output_tokens, total_tokens (ee/packages/den-db/src/schema/inference.ts, migration drizzle/0052_brown_rictor.sql).
  2. Stop discarding data — the OpenRouter usage webhook already parsed model IDs and token counts, then dropped them; it now persists them to the ledger (ee/apps/inference/src/webhooks.ts).
  3. Client emission — the app send-site emits model (provider/model) and model_selection (default | manual, derived from whether a session model override is set) session dimensions through the existing telemetry path (apps/app/src/react-app/shell/session-route.tsx).
  4. APIGET /v1/telemetry/analytics returns a models block: per-model distinct-session counts (30d) and default-vs-manual selection totals; existing enterprise entitlement gate unchanged (ee/apps/den-api/src/routes/telemetry/index.ts).
  5. Dashboard — the Usage & adoption screen gains a "Models" section: sessions-by-model bars plus Default vs Manually-selected cards (ee/apps/den-web/.../analytics-screen.tsx).

Aggregates only — no message content is captured anywhere.

Tests (commands + results)

  • pnpm --filter @openwork-ee/den-db db:generate + test — migration generated; 13 pass, 3 skip.
  • Webhook tests (ee/apps/inference/test/webhooks.test.ts) — extended to assert persisted model/provider/token fields; pass. Full inference suite: 30 pass / 9 fail — the 9 failures are pre-existing on dev (stale openrouter/fusion fixtures; identical failure set reproduced on a clean dev checkout, which fails 10 — this branch fixes one of them).
  • Typechecks: den-api, den-web, @openwork/app — all clean.
  • End-to-end spec evals/specs/org-model-analytics.test.ts (pnpm --dir evals run spec specs/org-model-analytics.test.ts) — cold-boots a local Den via @openwork/testkit server(): ingests sessions with model/model_selection dimensions via the public ingest API, then asserts the admin analytics response aggregates models.usage30d and selection30d correctly and that a second org sees none of it (no cross-org leakage). Passed 1/1. Evidence tape published on this PR.

Notes

  • Dimension value validation now allows / (needed for provider/model values).
  • Token counts are persisted but not yet surfaced as dashboard aggregates (spend view is a natural follow-up).
  • Whether (a subset of) analytics unbundles to the team plan is a packaging decision tracked outside this PR; the entitlement gating stays as-is and configurable.

Update: full app-driven demo + a bug it caught

  • New e2e spec evals/specs/org-model-analytics-e2e.slow.test.ts (stack lane): two real members in the desktop app — one sends with the org default model, the other manually picks a different model via the picker (deterministic witness provider, per-identity token assertions); org analytics then shows both models (API counts asserted + den-web dashboard visibly rendering both bars and the 1/1 default-vs-manual split). Passed; photo roll with screenshots published below (5/5 frames, 2 facts).
  • Bug found by the demo, fixed here: desktop telemetry ingest used renderer fetch, which is cross-origin-blocked in Electron — desktop-emitted dimensions never reached Den. Now proxied through Electron main when cross-origin (apps/app/src/app/lib/den-telemetry.ts).
  • apps/desktop/scripts/electron-dev.mjs: opt-in OPENWORK_ELECTRON_SKIP_NATIVE_REBUILD=1 for prebuilt native bindings (default behavior unchanged).
  • Spec-lane fix: Den-booting specs renamed to .slow.test.ts so the app-less PR lane (no MySQL) excludes them, per the lane convention.

@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview Aug 4, 2026 9:35pm
openwork-den Ready Ready Preview Aug 4, 2026 9:35pm
openwork-den-worker-proxy Ready Ready Preview Aug 4, 2026 9:35pm
openwork-diagnostics Ready Ready Preview Aug 4, 2026 9:35pm
openwork-landing Ready Ready Preview, v0 Aug 4, 2026 9:35pm

@benjaminshafii

benjaminshafii commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Photo roll — two-members-visibly-drive-default-and-manual-model-analytics-end-to-end — 5/5 frames passed · 2 facts

5/5 frames passed · 2 facts · 12 expectations passed · 0 failed

✅ PASS — 1. The Models section visibly says Sessions by model and No model usage yet

  • PASS The Models section visibly says Sessions by model and No model usage yet — The organization analytics Models section is empty before either member sends a task.
  • PASS The Default model and Manually selected cards both visibly show zero activity — The organization analytics Models section is empty before either member sends a task.

The Models section visibly says Sessions by model and No model usage yet

✅ PASS — 2. The composer visibly shows Analytics Default as the selected model

  • PASS The composer visibly shows Analytics Default as the selected model — Jordan's task shows the Analytics Default composer choice and a completed witness reply.
  • PASS The assistant reply 'Welcome aboard — your team is glad you're here.' is visibly streamed into the conversation — Jordan's task shows the Analytics Default composer choice and a completed witness reply.

The composer visibly shows Analytics Default as the selected model

✅ PASS — 3. The open model picker visibly offers Analytics Manual

  • PASS The open model picker visibly offers Analytics Manual — Riley has opened the model picker and searched for the different Analytics Manual choice.
  • PASS The composer still visibly shows Analytics Default before the different model is chosen — Riley has opened the model picker and searched for the different Analytics Manual choice.

The open model picker visibly offers Analytics Manual

✅ PASS — 4. The composer visibly shows Analytics Manual as the selected model

  • PASS The composer visibly shows Analytics Manual as the selected model — Riley's task shows the manually selected Analytics Manual and its completed witness reply.
  • PASS The assistant reply 'Milestone reached — great work, team!' is visibly streamed into the conversation — Riley's task shows the manually selected Analytics Manual and its completed witness reply.

The composer visibly shows Analytics Manual as the selected model

ℹ️ FACT — 5. Two real model sessions reached org analytics

[{"id":"analytics-witness/model-default","label":"analytics-witness/model-default","sessions":1},{"id":"analytics-witness/model-manual","label":"analytics-witness/model-manual","sessions":1}]

  • PASS Two real model sessions reached org analytics — [{"id":"analytics-witness/model-default","label":"analytics-witness/model-default","sessions":1},{"id":"analytics-witness/model-manual","label":"analytics-witness/model-manual","sessions":1}]

ℹ️ FACT — 6. Default and manual selection totals are distinct

{"default":1,"manual":1}

  • PASS Default and manual selection totals are distinct — {"default":1,"manual":1}

✅ PASS — 7. The Sessions by model chart visibly contains bars for analytics-witness/model-default and analytics-witness/model-manual

  • PASS The Sessions by model chart visibly contains bars for analytics-witness/model-default and analytics-witness/model-manual — The Models section shows both witness model bars and a one-to-one default versus manual split.
  • PASS The Default model and Manually selected cards visibly show one session each — The Models section shows both witness model bars and a one-to-one default versus manual split.

The Sessions by model chart visibly contains bars for analytics-witness/model-default and analytics-witness/model-manual


Roll created 2026-08-04T21:31:24.053Z · Source: evals/results/rolls/2026-08-04T21-31-24-053Z-two-members-visibly-drive-default-and-manual-model-analytics-end-to-end/roll.json · Repro: pnpm fraimz:publish -- --pr 3491 --roll 2026-08-04T21-31-24-053Z-two-members-visibly-drive-default-and-manual-model-analytics-end-to-end

Comment thread ee/apps/den-api/src/routes/telemetry/index.ts
diff-warden[bot]
diff-warden Bot previously approved these changes Aug 4, 2026

@diff-warden diff-warden Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warden security clearance: clear. No new security issues found in this diff (b4ca3f73e84b3771a79c3bfd05ce8fde8b841f4b). Automated clearance satisfies the required-review gate only — a human still reviews and merges. Analysis run

…analytics

# Conflicts:
#	ee/packages/den-db/drizzle/meta/0052_snapshot.json
#	ee/packages/den-db/drizzle/meta/_journal.json
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploying openwork with  Cloudflare Pages  Cloudflare Pages

Latest commit: be92aaf
Status: ✅  Deploy successful!
Preview URL: https://39cc8a99.openwork.pages.dev
Branch Preview URL: https://feat-3483-model-usage-analyt.openwork.pages.dev

View logs

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.

[Feature]: Org model-usage analytics — selection trends (default vs manual), per-model demand, tokens/spend

1 participant