> ## Documentation Index
> Fetch the complete documentation index at: https://docs.volvox.bot/llms.txt
> Use this file to discover all available pages before exploring further.

# 2026 06 07 ai usage ops dashboard

# AI Usage Ops Dashboard Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build an owner-only global AI usage dashboard backed by the existing `ai_usage` table.

**Architecture:** Add a bot API route that validates `x-api-secret`, parses range/filter query params, queries global AI usage aggregates, and resolves guild names from the Discord client cache. Add a Next.js global-admin-gated proxy route and a dashboard page that renders summary cards, charts, and comparison tables using existing dashboard primitives.

**Tech Stack:** Node 22, Express 5, PostgreSQL, Vitest, Next.js 16 App Router, React 19, TypeScript, Tailwind 4, Recharts via `StableResponsiveContainer`.

***

## Task 1: Backend AI Usage Aggregation

**Files:**

* Create: `src/api/repositories/aiUsageRepository.js`

* Test: `tests/api/repositories/aiUsageRepository.test.js`

* [x] **Step 1: Write failing repository tests**

Add tests for:

```js theme={null}
import { describe, expect, it, vi } from 'vitest';
import {
  fetchAiUsageOpsSnapshot,
  parseAiUsageOpsQuery,
  parseProviderFromModel,
} from '../../../src/api/repositories/aiUsageRepository.js';

it('parses supported ranges and rejects invalid filters', () => {
  expect(parseAiUsageOpsQuery({ range: '24h' }).preset).toBe('24h');
  expect(parseAiUsageOpsQuery({ range: '7d' }).interval).toBe('hour');
  expect(parseAiUsageOpsQuery({ range: '30d' }).interval).toBe('day');
  expect(() => parseAiUsageOpsQuery({ range: '999d' })).toThrow('Invalid range');
  expect(() => parseAiUsageOpsQuery({ type: 'delete' })).toThrow('Invalid type');
});

it('parses provider from provider:model and groups bare models as unknown', () => {
  expect(parseProviderFromModel('z-ai:glm-5.1')).toEqual({ provider: 'z-ai', model: 'glm-5.1' });
  expect(parseProviderFromModel('bare-model')).toEqual({ provider: 'unknown', model: 'bare-model' });
});

it('returns empty aggregates when ai_usage has no rows', async () => {
  const dbPool = { query: vi.fn().mockResolvedValue({ rows: [] }) };
  const result = await fetchAiUsageOpsSnapshot({ dbPool, client: { guilds: { cache: new Map() } }, query: {} });
  expect(result.summary.requests).toBe(0);
  expect(result.summary.cacheReadRate).toBeNull();
  expect(result.byProvider).toEqual([]);
});

it('aggregates totals, cache rates, weighted TPS, p95 latency, and guild names', async () => {
  const rows = [
    { id: 1, guild_id: 'guild-1', channel_id: 'chan-1', type: 'respond', model: 'z-ai:glm-5.1', input_tokens: 1000, output_tokens: 250, cache_read_tokens: 300, cache_creation_tokens: 50, cost_usd: '0.012', duration_ms: 500, search_count: 2, created_at: '2026-06-07T10:00:00.000Z' },
    { id: 2, guild_id: 'guild-2', channel_id: 'chan-2', type: 'safety', model: 'bare-model', input_tokens: 500, output_tokens: 100, cache_read_tokens: 0, cache_creation_tokens: 0, cost_usd: '0.002', duration_ms: 1000, search_count: 0, created_at: '2026-06-07T10:10:00.000Z' },
  ];
  const dbPool = { query: vi.fn().mockResolvedValue({ rows }) };
  const client = { guilds: { cache: new Map([['guild-1', { name: 'Alpha' }]]) } };
  const result = await fetchAiUsageOpsSnapshot({ dbPool, client, query: { range: '24h' } });
  expect(result.summary.requests).toBe(2);
  expect(result.summary.totalTokens).toBe(1850);
  expect(result.summary.cacheReadRate).toBeCloseTo(0.2);
  expect(result.summary.avgTokensPerSecond).toBeCloseTo(1233.333, 2);
  expect(result.byProvider.map((row) => row.name)).toEqual(['z-ai', 'unknown']);
  expect(result.topGuilds[0]).toMatchObject({ guildId: 'guild-1', guildName: 'Alpha' });
});
```

* [x] **Step 2: Run repository tests to verify RED**

Run: `pnpm test -- tests/api/repositories/aiUsageRepository.test.js`

Expected: fail because `src/api/repositories/aiUsageRepository.js` does not exist.

* [x] **Step 3: Implement repository**

Create `src/api/repositories/aiUsageRepository.js` with:

* `parseAiUsageOpsQuery(query)` supporting `24h`, `7d`, `30d`, `90d`, and `type` in `classify/respond/safety`.

* `parseProviderFromModel(modelString)` using the first colon.

* `fetchAiUsageOpsSnapshot({ dbPool, client, query })` issuing one parameterized `SELECT` from `ai_usage` for the requested range and optional filters, then aggregating rows in JavaScript.

* helper functions for totals, cache rates, weighted TPS, p95 latency, provider/model/type/guild grouping, time buckets, and recent expensive rows.

* [x] **Step 4: Run repository tests to verify GREEN**

Run: `pnpm test -- tests/api/repositories/aiUsageRepository.test.js`

Expected: pass.

* [x] **Step 5: Commit backend repository**

```bash theme={null}
git add src/api/repositories/aiUsageRepository.js tests/api/repositories/aiUsageRepository.test.js
git commit -m "feat(api): aggregate global ai usage"
```

### Task 2: Bot API Route

**Files:**

* Create: `src/api/routes/aiUsage.js`

* Modify: `src/api/index.js`

* Test: `tests/api/routes/aiUsage.test.js`

* [x] **Step 1: Write failing route tests**

Add tests that build the Express app with a mocked pool and verify:

```js theme={null}
it('returns 401 without x-api-secret', async () => {
  const res = await request(app).get('/api/v1/ai-usage');
  expect(res.status).toBe(401);
});

it('returns 400 for an invalid range', async () => {
  const res = await request(app).get('/api/v1/ai-usage?range=bad').set('x-api-secret', TEST_SECRET);
  expect(res.status).toBe(400);
});

it('returns a snapshot with valid auth', async () => {
  const res = await request(app).get('/api/v1/ai-usage?range=24h').set('x-api-secret', TEST_SECRET);
  expect(res.status).toBe(200);
  expect(res.body).toHaveProperty('summary');
  expect(res.body).toHaveProperty('byProvider');
  expect(res.body.range.preset).toBe('24h');
});
```

* [x] **Step 2: Run route tests to verify RED**

Run: `pnpm test -- tests/api/routes/aiUsage.test.js`

Expected: fail with 404 or missing route.

* [x] **Step 3: Implement route and mount it**

Create `src/api/routes/aiUsage.js` using `Router()`, `isValidSecret(req.headers['x-api-secret'])`, and `fetchAiUsageOpsSnapshot({ dbPool: req.app.locals.dbPool, client: req.app.locals.client, query: req.query })`.

Mount it in `src/api/index.js` near `/performance`:

```js theme={null}
import aiUsageRouter from './routes/aiUsage.js';
router.use('/ai-usage', aiUsageRouter);
```

* [x] **Step 4: Run route tests to verify GREEN**

Run: `pnpm test -- tests/api/routes/aiUsage.test.js`

Expected: pass.

* [x] **Step 5: Commit bot route**

```bash theme={null}
git add src/api/routes/aiUsage.js src/api/index.js tests/api/routes/aiUsage.test.js
git commit -m "feat(api): expose owner ai usage snapshot"
```

### Task 3: Web Proxy, Types, and Page Titles

**Files:**

* Create: `web/src/app/api/ai-usage/route.ts`

* Create: `web/src/types/ai-usage.ts`

* Modify: `web/src/lib/page-titles.ts`

* Test: `web/tests/api/ai-usage-route.test.ts`

* Test: `web/tests/lib/page-titles.test.ts`

* [x] **Step 1: Write failing web API and title tests**

Add route tests that mock `authorizeRequestGlobalAdmin` and `proxyBotApiEndpoint`:

```ts theme={null}
it('returns the global admin auth error without proxying', async () => {
  mockAuthorizeRequestGlobalAdmin.mockResolvedValueOnce(NextResponse.json({ error: 'Forbidden' }, { status: 403 }));
  const response = await GET(new NextRequest('http://localhost/api/ai-usage'));
  expect(response.status).toBe(403);
  expect(mockProxyBotApiEndpoint).not.toHaveBeenCalled();
});

it('proxies range and filters to the bot API after owner auth', async () => {
  mockAuthorizeRequestGlobalAdmin.mockResolvedValueOnce(null);
  await GET(new NextRequest('http://localhost/api/ai-usage?range=24h&type=respond'));
  expect(mockProxyBotApiEndpoint).toHaveBeenCalledWith('/ai-usage?range=24h&type=respond', '[api/ai-usage]', 'Failed to fetch AI usage data');
});
```

Update `web/tests/lib/page-titles.test.ts` to expect `AI Usage` and docs href for `/dashboard/ai-usage`.

* [x] **Step 2: Run web API/title tests to verify RED**

Run: `pnpm --filter volvox-bot-web test -- tests/api/ai-usage-route.test.ts tests/lib/page-titles.test.ts`

Expected: fail because route/title wiring is missing.

* [x] **Step 3: Implement web route, types, and title wiring**

Create `web/src/app/api/ai-usage/route.ts` with `dynamic = 'force-dynamic'`, owner auth, URL search forwarding, and `proxyBotApiEndpoint`.

Create `web/src/types/ai-usage.ts` matching the spec response.

Add `/dashboard/ai-usage` title/docs matcher to `web/src/lib/page-titles.ts`.

* [x] **Step 4: Run web API/title tests to verify GREEN**

Run: `pnpm --filter volvox-bot-web test -- tests/api/ai-usage-route.test.ts tests/lib/page-titles.test.ts`

Expected: pass.

* [x] **Step 5: Commit web plumbing**

```bash theme={null}
git add web/src/app/api/ai-usage/route.ts web/src/types/ai-usage.ts web/src/lib/page-titles.ts web/tests/api/ai-usage-route.test.ts web/tests/lib/page-titles.test.ts
git commit -m "feat(web): proxy owner ai usage data"
```

### Task 4: Dashboard AI Usage UI

**Files:**

* Create: `web/src/components/dashboard/ai-usage-dashboard.tsx`

* Create: `web/src/app/dashboard/ai-usage/page.tsx`

* Modify: `web/src/components/layout/sidebar.tsx`

* Test: `web/tests/components/dashboard/ai-usage-dashboard.test.tsx`

* Test: `web/tests/components/layout/sidebar.test.tsx`

* [x] **Step 1: Write failing component and sidebar tests**

Add component tests that mock `fetch` and verify loading, empty, populated, filter changes, and failed response states.

Add sidebar expectations:

```ts theme={null}
mockGlobalAdminStatus.value = { isGlobalAdmin: true, isLoading: false, status: 'allowed' };
expect(screen.getByRole('link', { name: /ai usage/i })).toHaveAttribute('href', '/dashboard/ai-usage');

mockGlobalAdminStatus.value = { isGlobalAdmin: false, isLoading: false, status: 'denied' };
expect(screen.queryByRole('link', { name: /ai usage/i })).not.toBeInTheDocument();
```

* [x] **Step 2: Run UI tests to verify RED**

Run: `pnpm --filter volvox-bot-web test -- tests/components/dashboard/ai-usage-dashboard.test.tsx tests/components/layout/sidebar.test.tsx`

Expected: fail because component and nav item do not exist.

* [x] **Step 3: Implement dashboard page and component**

Create `AiUsageDashboard` with:

* range/provider/model/type filter controls.
* summary metric cards.
* time-series chart using `StableResponsiveContainer`.
* provider/model/type/top-guild/recent-request tables.
* loading, empty, and error states.

Create `web/src/app/dashboard/ai-usage/page.tsx` with metadata, `isDashboardGlobalAdmin()` redirect, `ErrorBoundary`, and `<AiUsageDashboard />`.

Add sidebar `AI Usage` with a lucide icon under System Ops and add `/dashboard/ai-usage` to `GLOBAL_ADMIN_ONLY_HREFS`.

* [x] **Step 4: Run UI tests to verify GREEN**

Run: `pnpm --filter volvox-bot-web test -- tests/components/dashboard/ai-usage-dashboard.test.tsx tests/components/layout/sidebar.test.tsx`

Expected: pass.

* [x] **Step 5: Commit UI**

```bash theme={null}
git add web/src/components/dashboard/ai-usage-dashboard.tsx web/src/app/dashboard/ai-usage/page.tsx web/src/components/layout/sidebar.tsx web/tests/components/dashboard/ai-usage-dashboard.test.tsx web/tests/components/layout/sidebar.test.tsx
git commit -m "feat(dashboard): add owner ai usage page"
```

### Task 5: Docs, AGENTS Check, and Verification

**Files:**

* Modify: `docs/features/analytics.mdx`

* Modify: `docs/dashboard.mdx`

* Read/check: `AGENTS.md`

* [x] **Step 1: Update docs**

Document that global admins can use System Ops -> AI Usage for deployment-wide token, cache, cost, model/provider, and top-server usage. Do not edit `docs/changelog.mdx` except to repair generated output required by docs validation.

* [x] **Step 2: Run targeted tests**

Run:

```bash theme={null}
pnpm test -- tests/api/repositories/aiUsageRepository.test.js tests/api/routes/aiUsage.test.js
pnpm --filter volvox-bot-web test -- tests/api/ai-usage-route.test.ts tests/lib/page-titles.test.ts tests/components/dashboard/ai-usage-dashboard.test.tsx tests/components/layout/sidebar.test.tsx
```

Expected: all targeted tests pass.

* [x] **Step 3: Run broader checks**

Run:

```bash theme={null}
pnpm lint
pnpm test -- tests/docs/docs.test.js
```

Expected: both pass. Record any pre-existing warnings separately.

* [x] **Step 4: Browser verification**

Start local dev:

```bash theme={null}
pnpm mono:dev
```

Use `agent-browser` to open `/dashboard/ai-usage`, verify owner access, desktop layout, mobile layout, filters, and no visible overflow. If browser tooling cannot run, state that plainly.

* [x] **Step 5: Final commit**

```bash theme={null}
git add docs/features/analytics.mdx docs/dashboard.mdx
git commit -m "docs(dashboard): document owner ai usage ops"
```
