fix(ui): count only LiteLLM-injected caching in the total savings figures

The caching tile's headline switched to the gateway-attributed share, but the
Total saved tile and the by-driver donut kept summing total caching, so the
three visible headlines no longer added up to the total beside them and the
donut sliced a number no tile printed. Point the total, the per-day series and
the donut at the injected share; caching the gateway did not cause stays
visible as the caching tile's secondary Total figure
This commit is contained in:
Tin Chi Lo 2026-08-27 19:51:32 -07:00
parent baf140a35d
commit 23afd7f4a8
4 changed files with 28 additions and 18 deletions

View file

@ -137,28 +137,34 @@ describe("UsageTab", () => {
});
it("sums compression and caching dollars across days into the summary cards", () => {
// Total caching and the LiteLLM-injected share deliberately differ so these
// assertions pin which one each figure uses: the caching headline and the
// Total-saved tile take the injected share, the secondary keeps the total.
const { getByText } = renderWith([
day("2026-07-12", {
compression_savings_spend: 0.04,
prompt_caching_savings_spend: 0.006,
gateway_injected_caching_savings_spend: 0.004,
compression_saved_tokens: 40000,
}),
day("2026-07-13", {
compression_savings_spend: 0.1,
prompt_caching_savings_spend: 0.01,
gateway_injected_caching_savings_spend: 0.006,
compression_saved_tokens: 100000,
}),
]);
expect(getByText("$0.1560")).toBeInTheDocument();
expect(getByText("$0.1500")).toBeInTheDocument();
expect(getByText("$0.1400")).toBeInTheDocument();
expect(getByText("$0.0100")).toBeInTheDocument();
expect(getByText("$0.0160")).toBeInTheDocument();
expect(getByText("140,000 tokens compressed")).toBeInTheDocument();
});
const twoDays = () => [
day("2026-07-12", { compression_savings_spend: 0.04, prompt_caching_savings_spend: 0.006 }),
day("2026-07-13", { compression_savings_spend: 0.1, prompt_caching_savings_spend: 0.01 }),
day("2026-07-12", { compression_savings_spend: 0.04, gateway_injected_caching_savings_spend: 0.006 }),
day("2026-07-13", { compression_savings_spend: 0.1, gateway_injected_caching_savings_spend: 0.01 }),
];
it("opens on a running total anchored at $0 at the start of the range", () => {
@ -179,7 +185,7 @@ describe("UsageTab", () => {
// synthetic start anchor gives the line a zero origin to climb from.
const oneDay = new Date(2026, 6, 24);
const { getByTestId } = renderWith(
[day("2026-07-24", { compression_savings_spend: 0.2, prompt_caching_savings_spend: 0.05 })],
[day("2026-07-24", { compression_savings_spend: 0.2, gateway_injected_caching_savings_spend: 0.05 })],
{ from: oneDay, to: oneDay },
);
@ -194,8 +200,8 @@ describe("UsageTab", () => {
// still read left to right in time, and the running total must climb toward
// the newest day, not fall away from it.
const newestFirst = [
day("2026-07-13", { prompt_caching_savings_spend: 0.1 }),
day("2026-07-12", { prompt_caching_savings_spend: 0.04 }),
day("2026-07-13", { gateway_injected_caching_savings_spend: 0.1 }),
day("2026-07-12", { gateway_injected_caching_savings_spend: 0.04 }),
];
const { getByTestId, getByRole } = renderWith(newestFirst);
@ -260,7 +266,7 @@ describe("UsageTab", () => {
const { getByRole, getByTestId } = renderWith([
day("2026-07-12", {
compression_savings_spend: 0.1,
prompt_caching_savings_spend: 0.02,
gateway_injected_caching_savings_spend: 0.02,
autorouter_savings_spend: -0.05,
}),
]);
@ -312,7 +318,7 @@ describe("UsageTab", () => {
const { getByText, getByTestId } = renderWith([
day("2026-07-12", {
compression_savings_spend: 0.1,
prompt_caching_savings_spend: 0.02,
gateway_injected_caching_savings_spend: 0.02,
autorouter_savings_spend: -0.05,
}),
]);
@ -329,12 +335,12 @@ describe("UsageTab", () => {
const { getByText, getByTestId } = renderWith([
day("2026-07-12", {
compression_savings_spend: 0.04,
prompt_caching_savings_spend: 0.006,
gateway_injected_caching_savings_spend: 0.006,
autorouter_savings_spend: 0.02,
}),
day("2026-07-13", {
compression_savings_spend: 0.1,
prompt_caching_savings_spend: 0.01,
gateway_injected_caching_savings_spend: 0.01,
autorouter_savings_spend: 0.05,
}),
]);

View file

@ -11,9 +11,9 @@ import { getToolSpend, ToolSpendResponse } from "@/components/networking";
import {
autorouterOf,
buildDailyToolSeries,
cachingOf,
compressionOf,
formatRangeLabel,
gatewayAttributedCachingOf,
localIsoDay,
MAX_POINTS_WITH_DOTS,
SAVINGS_COLORS,
@ -88,7 +88,7 @@ const UsageTab: React.FC<UsageTabProps> = ({ accessToken, activity }) => {
.map((d) => ({
date: shortDate(d.date),
Compression: compressionOf(d.metrics),
"Prompt caching": cachingOf(d.metrics),
"Prompt caching": gatewayAttributedCachingOf(d.metrics),
"Auto-router": autorouterOf(d.metrics),
})),
[results],
@ -119,9 +119,11 @@ const UsageTab: React.FC<UsageTabProps> = ({ accessToken, activity }) => {
SAVINGS_DRIVERS.map(({ name, color }) => ({
driver: name,
color,
usd: { Compression: totals.compression, "Prompt caching": totals.caching, "Auto-router": totals.autorouter }[
name
],
usd: {
Compression: totals.compression,
"Prompt caching": totals.gatewayAttributedCaching,
"Auto-router": totals.autorouter,
}[name],
})).filter((d) => d.usd > 0),
[totals],
);

View file

@ -22,13 +22,14 @@ export const useSavingsTotals = (results: DailyData[]) =>
const compression = sumOf(compressionOf);
const caching = sumOf(cachingOf);
const autorouter = sumOf(autorouterOf);
const gatewayAttributedCaching = sumOf(gatewayAttributedCachingOf);
return {
compression,
caching,
autorouter,
gatewayAttributedCaching: sumOf(gatewayAttributedCachingOf),
gatewayAttributedCaching,
savedTokens: sumOf(savedTokensOf),
total: compression + caching + autorouter,
total: compression + gatewayAttributedCaching + autorouter,
};
}, [results]);
@ -41,6 +42,7 @@ const SavingsTiles = ({ results, isLoading }: { results: DailyData[]; isLoading:
label="Total saved"
value={usd(totals.total)}
hint={isLoading ? "Loading..." : "Compression + prompt caching + auto-router"}
info="The sum of the three tiles beside it. Its caching term is the LiteLLM-injected share, so this total is what the gateway itself delivered; caching that clients or providers brought on their own appears only in the caching tile's Total figure."
/>
<SummaryCard
label="Compression savings"

View file

@ -87,7 +87,7 @@ describe("KeySavingsTab", () => {
renderTab();
expect(screen.getByTestId("summary-card-total-saved")).toHaveTextContent("$6.00");
expect(screen.getByTestId("summary-card-total-saved")).toHaveTextContent("$5.40");
expect(screen.getByTestId("summary-card-compression-savings")).toHaveTextContent("$2.00");
expect(screen.getByTestId("summary-card-compression-savings")).toHaveTextContent("1,000 tokens compressed");
// the card leads with what LiteLLM's own injection earned and carries the total beneath it,