Q75: charts and billing use full-period statistics computed per org

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 12:41:44 -04:00
1 parent daea9797d4
commit 8b4df5e11f
14 files changed
+231 -83

No files matched your search

+2
View File
@@ -3,6 +3,7 @@ import type { AppSnapshot } from "../../shared/domain";
import { PROVIDER_CATALOG } from "../../shared/provider-catalog";
import { IntegrationKeyStore } from "./integration-key-store";
import { currentProviderUsageWindow } from "../../shared/provider-runtime-domain";
import { buildUsageStats } from "./usage-stats";
type Row = Record<string, unknown>;
type ProviderUsageWindowRow = Pick<
@@ -128,6 +129,7 @@ export class SnapshotService {
from provider_requests order by created_at desc limit 100
`)).map((row) => camel(row) as unknown as AppSnapshot["providerRequests"][number]),
providerRateLimits,
usageStats: buildUsageStats(this.db, new Date()),
providerRateLimitObservations,
skillDefinitions: list(this.db, "skill_definitions", "created_at asc"),
socialAccounts: list(this.db, "social_accounts", "created_at asc"),
+75
View File
@@ -0,0 +1,75 @@
import type { DatabaseSync } from "node:sqlite";
import type { UsageStatsRecord } from "../../shared/usage-stats";
const WEEK_DAYS = 7;
const MONTH_DAYS = 30;
/** Period statistics per org, computed over every record in the period, not over the snapshot's recent lists. */
export function buildUsageStats(db: DatabaseSync, now: Date): UsageStatsRecord[] {
const orgs = db.prepare("select id from organizations").all() as Array<{ id: string }>;
const dayStarts = Array.from({ length: WEEK_DAYS }, (_, index) => localDayStart(now, WEEK_DAYS - 1 - index));
const weekStart = dayStarts[0].toISOString();
// "Last 30 days" is a rolling window ending now.
const monthStart = new Date(now.getTime() - MONTH_DAYS * 86_400_000).toISOString();
return orgs.map(({ id: orgId }) => {
const runs = db.prepare("select created_at, state from task_runs where org_id = ? and created_at >= ?").all(orgId, weekStart) as Array<{ created_at: string; state: string }>;
const calls = db.prepare(`
select created_at, state, input_tokens, output_tokens, cached_input_tokens, reasoning_tokens, cost_cents, agent_id, provider_id
from provider_requests where org_id = ? and created_at >= ?
`).all(orgId, monthStart) as Array<{
created_at: string; state: string; input_tokens: number | null; output_tokens: number | null;
cached_input_tokens: number | null; reasoning_tokens: number | null; cost_cents: number | null; agent_id: string | null; provider_id: string;
}>;
const days = dayStarts.map((start, index) => {
const end = index + 1 < dayStarts.length ? dayStarts[index + 1] : localDayStart(now, -1);
const inDay = (iso: string) => { const at = Date.parse(iso); return at >= start.getTime() && at < end.getTime(); };
const dayRuns = runs.filter((run) => inDay(run.created_at));
const dayCalls = calls.filter((call) => inDay(call.created_at));
return {
dayStart: start.toISOString(),
runs: dayRuns.length,
completedRuns: dayRuns.filter((run) => run.state === "completed").length,
failedRuns: dayRuns.filter((run) => run.state === "failed").length,
tokens: dayCalls.reduce((sum, call) => sum + (call.input_tokens ?? 0) + (call.output_tokens ?? 0), 0),
callsWithoutUsage: dayCalls.filter((call) => call.input_tokens === null).length,
};
});
const agentSpend = new Map<string, { requests: number; tokens: number; costCents: number }>();
for (const call of calls) {
if (!call.agent_id) continue;
const entry = agentSpend.get(call.agent_id) ?? { requests: 0, tokens: 0, costCents: 0 };
entry.requests += 1;
entry.tokens += (call.input_tokens ?? 0) + (call.output_tokens ?? 0);
entry.costCents += call.cost_cents ?? 0;
agentSpend.set(call.agent_id, entry);
}
const providerActivity = new Map<string, { requests: number; failures: number }>();
for (const call of calls) {
const entry = providerActivity.get(call.provider_id) ?? { requests: 0, failures: 0 };
entry.requests += 1;
if (call.state === "failed") entry.failures += 1;
providerActivity.set(call.provider_id, entry);
}
const sum = (pick: (call: typeof calls[number]) => number | null) => calls.reduce((total, call) => total + (pick(call) ?? 0), 0);
return {
orgId,
days,
month: {
requests: calls.length,
failures: calls.filter((call) => call.state === "failed").length,
inputTokens: sum((call) => call.input_tokens),
cachedInputTokens: sum((call) => call.cached_input_tokens),
outputTokens: sum((call) => call.output_tokens),
reasoningTokens: sum((call) => call.reasoning_tokens),
callsWithoutUsage: calls.filter((call) => call.input_tokens === null).length,
},
agentSpend: [...agentSpend].map(([agentId, entry]) => ({ agentId, ...entry })),
providerActivity: [...providerActivity].map(([providerId, entry]) => ({ providerId, ...entry })),
};
});
}
/** Local midnight of the calendar day `daysAgo` days before today; calendar arithmetic keeps DST days 23 or 25 hours long. */
function localDayStart(now: Date, daysAgo: number): Date {
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysAgo);
}