forked from EduCraft/curriculum-project-hub
feat(hub): usage fact breakdown API + admin usage/session UI + release v0.0.34 (#10)
Expose UsageFact kind/capability rollups on org and project usage reports, and add admin pages that separate model tokens from external-capability meters. Co-authored-by: Hong Jiarong <me@jrhim.com> Co-committed-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
@@ -71,7 +71,7 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
|
||||
occurredAt: new Date(startedAt.getTime() + i * 1000),
|
||||
kind: f.kind ?? "model_completion",
|
||||
provider: f.provider ?? "openrouter",
|
||||
model: f.model ?? "mock-model",
|
||||
model: f.model !== undefined ? f.model : "mock-model",
|
||||
inputTokens: f.inputTokens ?? null,
|
||||
outputTokens: f.outputTokens ?? null,
|
||||
costUsd: f.costUsd ?? null,
|
||||
@@ -158,6 +158,32 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
|
||||
expect(report.totals.inputTokens).toBe(100);
|
||||
expect(report.totals.outputTokens).toBe(50);
|
||||
expect(report.totals.costUsd).toBeCloseTo(0.035, 8);
|
||||
// ADR-0026 breakdown: model tokens vs external non-token meter must not collapse.
|
||||
expect(report.breakdown).toHaveLength(2);
|
||||
const modelBucket = report.breakdown.find((b) => b.kind === "model_completion");
|
||||
const capBucket = report.breakdown.find((b) => b.kind === "external_capability");
|
||||
expect(modelBucket).toMatchObject({
|
||||
provider: "openrouter",
|
||||
model: "mock-model",
|
||||
capabilityId: null,
|
||||
inputTokens: 100,
|
||||
outputTokens: 50,
|
||||
quantity: null,
|
||||
costUsd: 0.02,
|
||||
factCount: 1,
|
||||
});
|
||||
expect(capBucket).toMatchObject({
|
||||
provider: "mineru",
|
||||
model: null,
|
||||
capabilityId: "pdf_to_md_bundle",
|
||||
unit: "pages",
|
||||
quantity: 12,
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
costUsd: 0.015,
|
||||
factCount: 1,
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("a run with no facts at all is runsWithoutCost and contributes nothing", async () => {
|
||||
@@ -180,6 +206,8 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
|
||||
expect(row.projectId).toBe("proj-x");
|
||||
expect(row.runCount).toBe(1);
|
||||
expect(row.costUsd).toBeCloseTo(0.1, 8);
|
||||
expect(row.breakdown).toHaveLength(1);
|
||||
expect(row.breakdown[0]).toMatchObject({ kind: "model_completion", costUsd: 0.1, inputTokens: 1 });
|
||||
});
|
||||
|
||||
it("returns an empty report for an org with no projects", async () => {
|
||||
@@ -187,5 +215,7 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
|
||||
expect(report.projects).toHaveLength(0);
|
||||
expect(report.totals.runCount).toBe(0);
|
||||
expect(report.totals.costUsd).toBeNull();
|
||||
expect(report.breakdown).toEqual([]);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user