diff --git a/actions/setup/js/effective_tokens.cjs b/actions/setup/js/effective_tokens.cjs index 91955653be..29b349b31c 100644 --- a/actions/setup/js/effective_tokens.cjs +++ b/actions/setup/js/effective_tokens.cjs @@ -186,18 +186,26 @@ function computeEffectiveTokens(model, inputTokens, outputTokens, cacheReadToken /** * Formats an ET number in a compact, human-readable form. * + * Effective tokens are estimates, so sub-1000 values are rounded to the nearest 10. + * * Ranges: - * < 1,000 → exact integer (e.g. "900") + * < 10 → exact integer (e.g. "7") + * 10–999 → rounded to nearest 10 (e.g. "40", "120", "900") * 1,000–999,999 → Xk with one decimal when non-zero (e.g. "1.2K", "450K") - * >= 1,000,000 → Xm with one decimal when non-zero (e.g. "1.2M", "3M") + * >= 1,000,000 → rounded to nearest whole M (e.g. "1M", "12M") * * @param {number} n - Non-negative ET value (should be rounded before passing) * @returns {string} Compact string representation */ function formatET(n) { + // Round to nearest 10 for values in [10, 1000) — effective tokens are estimates + if (n >= 10 && n < 1000) { + n = Math.round(n / 10) * 10; + } if (n < 1000) return String(n); if (n < 1_000_000) return `${(n / 1000).toFixed(1).replace(/\.0$/, "")}K`; - return `${(n / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`; + // Round to nearest whole M — effective tokens are estimates + return `${Math.round(n / 1_000_000)}M`; } /** diff --git a/actions/setup/js/effective_tokens.test.cjs b/actions/setup/js/effective_tokens.test.cjs index 33455d33de..e3826696cb 100644 --- a/actions/setup/js/effective_tokens.test.cjs +++ b/actions/setup/js/effective_tokens.test.cjs @@ -302,11 +302,24 @@ describe("effective_tokens", () => { }); describe("formatET", () => { - test("returns exact string for values under 1000", () => { + test("returns exact string for values under 10", () => { expect(formatET(0)).toBe("0"); expect(formatET(1)).toBe("1"); + expect(formatET(9)).toBe("9"); + }); + + test("rounds values in [10, 1000) to nearest 10", () => { + expect(formatET(10)).toBe("10"); + expect(formatET(14)).toBe("10"); + expect(formatET(15)).toBe("20"); + expect(formatET(42)).toBe("40"); + expect(formatET(123)).toBe("120"); expect(formatET(900)).toBe("900"); - expect(formatET(999)).toBe("999"); + }); + + test("rounds values near 1000 up to 1K", () => { + expect(formatET(995)).toBe("1K"); + expect(formatET(999)).toBe("1K"); }); test("formats values in the thousands as K", () => { @@ -317,13 +330,14 @@ describe("effective_tokens", () => { expect(formatET(999999)).toBe("1000K"); }); - test("formats values in the millions as M", () => { + test("formats values in the millions as M (rounded to nearest whole M)", () => { expect(formatET(1_000_000)).toBe("1M"); - expect(formatET(1_200_000)).toBe("1.2M"); - expect(formatET(12_345_678)).toBe("12.3M"); + expect(formatET(1_200_000)).toBe("1M"); + expect(formatET(1_500_000)).toBe("2M"); + expect(formatET(12_345_678)).toBe("12M"); }); - test("omits trailing .0 in K/M format", () => { + test("omits trailing .0 in K format", () => { expect(formatET(2000)).toBe("2K"); expect(formatET(5_000_000)).toBe("5M"); }); diff --git a/pkg/cli/health_metrics.go b/pkg/cli/health_metrics.go index ae427fe2f0..17b0683ff2 100644 --- a/pkg/cli/health_metrics.go +++ b/pkg/cli/health_metrics.go @@ -235,18 +235,28 @@ func GroupRunsByWorkflow(runs []WorkflowRun) map[string][]WorkflowRun { return grouped } -// formatTokens formats token count in a human-readable format +// formatTokens formats token count in a human-readable format. +// Values in [10, 1000) are rounded to the nearest 10 — token counts are estimates. func formatTokens(tokens int) string { if tokens == 0 { return "-" } - if tokens < 1000 { + if tokens < 10 { return strconv.Itoa(tokens) } + if tokens < 1000 { + // Round to nearest 10 — token counts are estimates + rounded := (tokens + 5) / 10 * 10 + if rounded >= 1000 { + return "1K" + } + return strconv.Itoa(rounded) + } if tokens < 1000000 { return fmt.Sprintf("%.1fK", float64(tokens)/1000) } - return fmt.Sprintf("%.1fM", float64(tokens)/1000000) + // Round to nearest whole M — token counts are estimates + return fmt.Sprintf("%dM", (tokens+500000)/1000000) } // formatCost formats cost in a human-readable format diff --git a/pkg/cli/health_metrics_test.go b/pkg/cli/health_metrics_test.go index add5f944c0..d1da9a6bf6 100644 --- a/pkg/cli/health_metrics_test.go +++ b/pkg/cli/health_metrics_test.go @@ -233,10 +233,30 @@ func TestFormatTokens(t *testing.T) { expected: "-", }, { - name: "small tokens", + name: "single digit kept exact", + tokens: 7, + expected: "7", + }, + { + name: "rounds to nearest 10", + tokens: 42, + expected: "40", + }, + { + name: "rounds up to nearest 10", + tokens: 45, + expected: "50", + }, + { + name: "multiple of 10 unchanged", tokens: 500, expected: "500", }, + { + name: "rounds near 1000 to 1K", + tokens: 999, + expected: "1K", + }, { name: "thousands", tokens: 5000, @@ -245,7 +265,12 @@ func TestFormatTokens(t *testing.T) { { name: "millions", tokens: 2500000, - expected: "2.5M", + expected: "3M", + }, + { + name: "millions rounded down", + tokens: 2400000, + expected: "2M", }, }