| 1 | /** |
| 2 | * Shapes merged daily totals into the per-day provider stacks both chart |
| 3 | * implementations (Swift Charts on iOS, plain views elsewhere) render. |
| 4 | * |
| 5 | * @module usageChartData |
| 6 | */ |
| 7 | import type { UsageProviderKind } from "@t3tools/contracts"; |
| 8 | import type { DailyTotals } from "@t3tools/shared/usageMerge"; |
| 9 | |
| 10 | import { PROVIDER_ORDER } from "./usageProviders"; |
| 11 | |
| 12 | export type UsageChartMetric = "cost" | "tokens"; |
| 13 | |
| 14 | export interface UsageChartDay { |
| 15 | readonly day: string; |
| 16 | /** In {@link PROVIDER_ORDER}, i.e. bottom of the stack first. */ |
| 17 | readonly values: readonly { readonly provider: UsageProviderKind; readonly value: number }[]; |
| 18 | readonly total: number; |
| 19 | } |
| 20 | |
| 21 | /** One entry per day in the window, zero-filled where nothing happened. */ |
| 22 | export function buildChartDays( |
| 23 | days: readonly string[], |
| 24 | daily: readonly DailyTotals[], |
| 25 | metric: UsageChartMetric, |
| 26 | ): readonly UsageChartDay[] { |
| 27 | const byDay = new Map(daily.map((totals) => [totals.day, totals])); |
| 28 | return days.map((day) => { |
| 29 | const totals = byDay.get(day); |
| 30 | const values = PROVIDER_ORDER.map((provider) => { |
| 31 | const entry = totals?.byProvider.get(provider); |
| 32 | const value = entry === undefined ? 0 : metric === "cost" ? entry.costUsd : entry.totalTokens; |
| 33 | return { provider, value }; |
| 34 | }); |
| 35 | return { |
| 36 | day, |
| 37 | values, |
| 38 | total: values.reduce((sum, entry) => sum + entry.value, 0), |
| 39 | }; |
| 40 | }); |
| 41 | } |