From 2c46488f0f8287cb6bacd19dbe153b7e5c5e3db3 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Tue, 2 Jun 2026 02:01:45 -0700 Subject: [PATCH] refactor: move buildPeriodData into usage-aggregator module --- src/main.ts | 40 +-------------------------------------- src/usage-aggregator.ts | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 39 deletions(-) create mode 100644 src/usage-aggregator.ts diff --git a/src/main.ts b/src/main.ts index cc562e7..57a55a8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ import { getDaysInRange, ensureCacheHydrated, loadDailyCache, emptyCache, BACKFI import { aggregateProjectsIntoDays, buildPeriodDataFromDays, dateKey } from './day-aggregator.js' import { CATEGORY_LABELS, type DateRange, type ProjectSummary, type TaskCategory } from './types.js' import { aggregateModelEfficiency } from './model-efficiency.js' +import { buildPeriodData } from './usage-aggregator.js' import { renderDashboard } from './dashboard.js' import { formatDateRangeLabel, parseDateRangeFlags, parseDayFlag, parseDaysFlag, getDateRange, toPeriod, type Period } from './cli-date.js' import { runOptimize, scanAndDetect } from './optimize.js' @@ -407,45 +408,6 @@ program await renderDashboard(period, opts.provider, opts.refresh, opts.project, opts.exclude, customRange, customRangeLabel, daySelection?.day) }) -function buildPeriodData(label: string, projects: ProjectSummary[]): PeriodData { - const sessions = projects.flatMap(p => p.sessions) - const catTotals: Record = {} - const modelTotals: Record = {} - let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheWriteTokens = 0 - - for (const sess of sessions) { - inputTokens += sess.totalInputTokens - outputTokens += sess.totalOutputTokens - cacheReadTokens += sess.totalCacheReadTokens - cacheWriteTokens += sess.totalCacheWriteTokens - for (const [cat, d] of Object.entries(sess.categoryBreakdown)) { - if (!catTotals[cat]) catTotals[cat] = { turns: 0, cost: 0, editTurns: 0, oneShotTurns: 0 } - catTotals[cat].turns += d.turns - catTotals[cat].cost += d.costUSD - catTotals[cat].editTurns += d.editTurns - catTotals[cat].oneShotTurns += d.oneShotTurns - } - for (const [model, d] of Object.entries(sess.modelBreakdown)) { - if (!modelTotals[model]) modelTotals[model] = { calls: 0, cost: 0 } - modelTotals[model].calls += d.calls - modelTotals[model].cost += d.costUSD - } - } - - return { - label, - cost: projects.reduce((s, p) => s + p.totalCostUSD, 0), - calls: projects.reduce((s, p) => s + p.totalApiCalls, 0), - sessions: projects.reduce((s, p) => s + p.sessions.length, 0), - inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, - categories: Object.entries(catTotals) - .sort(([, a], [, b]) => b.cost - a.cost) - .map(([cat, d]) => ({ name: CATEGORY_LABELS[cat as TaskCategory] ?? cat, ...d })), - models: Object.entries(modelTotals) - .sort(([, a], [, b]) => b.cost - a.cost) - .map(([name, d]) => ({ name, ...d })), - } -} program .command('status') diff --git a/src/usage-aggregator.ts b/src/usage-aggregator.ts new file mode 100644 index 0000000..ebc2932 --- /dev/null +++ b/src/usage-aggregator.ts @@ -0,0 +1,42 @@ +import { CATEGORY_LABELS, type ProjectSummary, type TaskCategory } from './types.js' +import { type PeriodData } from './menubar-json.js' + +export function buildPeriodData(label: string, projects: ProjectSummary[]): PeriodData { + const sessions = projects.flatMap(p => p.sessions) + const catTotals: Record = {} + const modelTotals: Record = {} + let inputTokens = 0, outputTokens = 0, cacheReadTokens = 0, cacheWriteTokens = 0 + + for (const sess of sessions) { + inputTokens += sess.totalInputTokens + outputTokens += sess.totalOutputTokens + cacheReadTokens += sess.totalCacheReadTokens + cacheWriteTokens += sess.totalCacheWriteTokens + for (const [cat, d] of Object.entries(sess.categoryBreakdown)) { + if (!catTotals[cat]) catTotals[cat] = { turns: 0, cost: 0, editTurns: 0, oneShotTurns: 0 } + catTotals[cat].turns += d.turns + catTotals[cat].cost += d.costUSD + catTotals[cat].editTurns += d.editTurns + catTotals[cat].oneShotTurns += d.oneShotTurns + } + for (const [model, d] of Object.entries(sess.modelBreakdown)) { + if (!modelTotals[model]) modelTotals[model] = { calls: 0, cost: 0 } + modelTotals[model].calls += d.calls + modelTotals[model].cost += d.costUSD + } + } + + return { + label, + cost: projects.reduce((s, p) => s + p.totalCostUSD, 0), + calls: projects.reduce((s, p) => s + p.totalApiCalls, 0), + sessions: projects.reduce((s, p) => s + p.sessions.length, 0), + inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, + categories: Object.entries(catTotals) + .sort(([, a], [, b]) => b.cost - a.cost) + .map(([cat, d]) => ({ name: CATEGORY_LABELS[cat as TaskCategory] ?? cat, ...d })), + models: Object.entries(modelTotals) + .sort(([, a], [, b]) => b.cost - a.cost) + .map(([name, d]) => ({ name, ...d })), + } +}