@tokscale/cli 1.0.16 → 1.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +220 -96
- package/dist/cli.js.map +1 -1
- package/dist/graph-types.d.ts +1 -1
- package/dist/graph-types.d.ts.map +1 -1
- package/dist/native-runner.js +5 -5
- package/dist/native-runner.js.map +1 -1
- package/dist/native.d.ts +9 -30
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +18 -134
- package/dist/native.js.map +1 -1
- package/dist/sessions/types.d.ts +1 -1
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/submit.d.ts +2 -0
- package/dist/submit.d.ts.map +1 -1
- package/dist/submit.js +32 -16
- package/dist/submit.js.map +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +13 -6
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/DailyView.d.ts.map +1 -1
- package/dist/tui/components/DailyView.js +25 -8
- package/dist/tui/components/DailyView.js.map +1 -1
- package/dist/tui/components/DateBreakdownPanel.js +2 -2
- package/dist/tui/components/DateBreakdownPanel.js.map +1 -1
- package/dist/tui/components/Footer.d.ts.map +1 -1
- package/dist/tui/components/Footer.js +2 -3
- package/dist/tui/components/Footer.js.map +1 -1
- package/dist/tui/components/LoadingSpinner.d.ts.map +1 -1
- package/dist/tui/components/LoadingSpinner.js +1 -2
- package/dist/tui/components/LoadingSpinner.js.map +1 -1
- package/dist/tui/components/ModelView.js +2 -2
- package/dist/tui/components/ModelView.js.map +1 -1
- package/dist/tui/config/settings.d.ts +4 -4
- package/dist/tui/config/settings.d.ts.map +1 -1
- package/dist/tui/config/settings.js +11 -4
- package/dist/tui/config/settings.js.map +1 -1
- package/dist/tui/hooks/useData.d.ts.map +1 -1
- package/dist/tui/hooks/useData.js +29 -42
- package/dist/tui/hooks/useData.js.map +1 -1
- package/dist/tui/types/index.d.ts +2 -2
- package/dist/tui/types/index.d.ts.map +1 -1
- package/dist/tui/types/index.js +3 -1
- package/dist/tui/types/index.js.map +1 -1
- package/dist/tui/utils/colors.d.ts +1 -0
- package/dist/tui/utils/colors.d.ts.map +1 -1
- package/dist/tui/utils/colors.js +7 -0
- package/dist/tui/utils/colors.js.map +1 -1
- package/dist/wrapped.d.ts.map +1 -1
- package/dist/wrapped.js +35 -53
- package/dist/wrapped.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +240 -103
- package/src/graph-types.ts +1 -1
- package/src/native-runner.ts +5 -5
- package/src/native.ts +35 -200
- package/src/sessions/types.ts +1 -1
- package/src/submit.ts +36 -22
- package/src/tui/App.tsx +9 -6
- package/src/tui/components/DailyView.tsx +29 -11
- package/src/tui/components/DateBreakdownPanel.tsx +2 -2
- package/src/tui/components/Footer.tsx +7 -2
- package/src/tui/components/LoadingSpinner.tsx +1 -2
- package/src/tui/components/ModelView.tsx +2 -2
- package/src/tui/config/settings.ts +18 -9
- package/src/tui/hooks/useData.ts +36 -47
- package/src/tui/types/index.ts +5 -4
- package/src/tui/utils/colors.ts +7 -0
- package/src/wrapped.ts +39 -59
- package/dist/graph.d.ts +0 -29
- package/dist/graph.d.ts.map +0 -1
- package/dist/graph.js +0 -383
- package/dist/graph.js.map +0 -1
- package/dist/pricing.d.ts +0 -58
- package/dist/pricing.d.ts.map +0 -1
- package/dist/pricing.js +0 -232
- package/dist/pricing.js.map +0 -1
- package/dist/sessions/claudecode.d.ts +0 -8
- package/dist/sessions/claudecode.d.ts.map +0 -1
- package/dist/sessions/claudecode.js +0 -84
- package/dist/sessions/claudecode.js.map +0 -1
- package/dist/sessions/codex.d.ts +0 -8
- package/dist/sessions/codex.d.ts.map +0 -1
- package/dist/sessions/codex.js +0 -158
- package/dist/sessions/codex.js.map +0 -1
- package/dist/sessions/gemini.d.ts +0 -8
- package/dist/sessions/gemini.d.ts.map +0 -1
- package/dist/sessions/gemini.js +0 -66
- package/dist/sessions/gemini.js.map +0 -1
- package/dist/sessions/index.d.ts +0 -32
- package/dist/sessions/index.d.ts.map +0 -1
- package/dist/sessions/index.js +0 -96
- package/dist/sessions/index.js.map +0 -1
- package/dist/sessions/opencode.d.ts +0 -9
- package/dist/sessions/opencode.d.ts.map +0 -1
- package/dist/sessions/opencode.js +0 -69
- package/dist/sessions/opencode.js.map +0 -1
- package/dist/sessions/reports.d.ts +0 -58
- package/dist/sessions/reports.d.ts.map +0 -1
- package/dist/sessions/reports.js +0 -337
- package/dist/sessions/reports.js.map +0 -1
- package/src/graph.ts +0 -485
- package/src/pricing.ts +0 -309
- package/src/sessions/claudecode.ts +0 -119
- package/src/sessions/codex.ts +0 -227
- package/src/sessions/gemini.ts +0 -108
- package/src/sessions/index.ts +0 -126
- package/src/sessions/opencode.ts +0 -117
- package/src/sessions/reports.ts +0 -475
package/src/graph.ts
DELETED
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Graph data generation module
|
|
3
|
-
* Aggregates token usage data by date for contribution graph visualization
|
|
4
|
-
*
|
|
5
|
-
* Key design: intensity is calculated based on COST ($), not tokens
|
|
6
|
-
*
|
|
7
|
-
* This module supports two implementations:
|
|
8
|
-
* - Native Rust (fast, ~10x faster) - used when available
|
|
9
|
-
* - Pure TypeScript (fallback) - always available
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { format } from "date-fns";
|
|
13
|
-
import {
|
|
14
|
-
parseOpenCodeMessages,
|
|
15
|
-
parseClaudeCodeMessages,
|
|
16
|
-
parseCodexMessages,
|
|
17
|
-
parseGeminiMessages,
|
|
18
|
-
} from "./sessions/index.js";
|
|
19
|
-
import { PricingFetcher } from "./pricing.js";
|
|
20
|
-
import type {
|
|
21
|
-
TokenContributionData,
|
|
22
|
-
DailyContribution,
|
|
23
|
-
YearSummary,
|
|
24
|
-
DataSummary,
|
|
25
|
-
GraphOptions,
|
|
26
|
-
UnifiedMessage,
|
|
27
|
-
SourceType,
|
|
28
|
-
} from "./graph-types.js";
|
|
29
|
-
|
|
30
|
-
const VERSION = "1.0.0";
|
|
31
|
-
|
|
32
|
-
// Try to load native module
|
|
33
|
-
let nativeModule: typeof import("./native.js") | null = null;
|
|
34
|
-
try {
|
|
35
|
-
nativeModule = await import("./native.js");
|
|
36
|
-
} catch {
|
|
37
|
-
// Native module not available, will use TypeScript implementation
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check if native implementation is available
|
|
42
|
-
*/
|
|
43
|
-
export function isNativeAvailable(): boolean {
|
|
44
|
-
return nativeModule?.isNativeAvailable() ?? false;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Generate contribution graph data from all sources
|
|
49
|
-
*
|
|
50
|
-
* Uses native Rust implementation if available, falls back to TypeScript.
|
|
51
|
-
* Set `options.forceTypescript = true` to skip native module.
|
|
52
|
-
*/
|
|
53
|
-
export async function generateGraphData(
|
|
54
|
-
options: GraphOptions & { forceTypescript?: boolean } = {}
|
|
55
|
-
): Promise<TokenContributionData> {
|
|
56
|
-
// Try native implementation first (unless forced to use TypeScript)
|
|
57
|
-
if (!options.forceTypescript && nativeModule?.isNativeAvailable()) {
|
|
58
|
-
try {
|
|
59
|
-
return nativeModule.generateGraphNative(options);
|
|
60
|
-
} catch (e) {
|
|
61
|
-
// Fall through to TypeScript implementation
|
|
62
|
-
console.warn("Native module failed, falling back to TypeScript:", e);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// TypeScript implementation
|
|
67
|
-
return generateGraphDataTS(options);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Pure TypeScript implementation of graph data generation
|
|
72
|
-
*/
|
|
73
|
-
export async function generateGraphDataTS(
|
|
74
|
-
options: GraphOptions = {}
|
|
75
|
-
): Promise<TokenContributionData> {
|
|
76
|
-
const fetcher = new PricingFetcher();
|
|
77
|
-
await fetcher.fetchPricing();
|
|
78
|
-
|
|
79
|
-
// Collect all messages from enabled sources
|
|
80
|
-
const messages = collectMessages(options, fetcher);
|
|
81
|
-
|
|
82
|
-
// Filter by date range
|
|
83
|
-
const filteredMessages = filterMessagesByDate(messages, options);
|
|
84
|
-
|
|
85
|
-
// Aggregate by date
|
|
86
|
-
const dailyMap = aggregateByDate(filteredMessages, fetcher);
|
|
87
|
-
|
|
88
|
-
// Convert to sorted array
|
|
89
|
-
let contributions = Array.from(dailyMap.values()).sort((a, b) =>
|
|
90
|
-
a.date.localeCompare(b.date)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
// Calculate intensity based on COST
|
|
94
|
-
contributions = calculateAllIntensities(contributions);
|
|
95
|
-
|
|
96
|
-
// Build final structure
|
|
97
|
-
const summary = calculateSummary(contributions);
|
|
98
|
-
const years = calculateYears(contributions);
|
|
99
|
-
|
|
100
|
-
const dateRange = {
|
|
101
|
-
start: contributions[0]?.date ?? "",
|
|
102
|
-
end: contributions[contributions.length - 1]?.date ?? "",
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
meta: {
|
|
107
|
-
generatedAt: new Date().toISOString(),
|
|
108
|
-
version: VERSION,
|
|
109
|
-
dateRange,
|
|
110
|
-
},
|
|
111
|
-
summary,
|
|
112
|
-
years,
|
|
113
|
-
contributions,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Collect messages from all enabled sources
|
|
119
|
-
*/
|
|
120
|
-
function collectMessages(
|
|
121
|
-
options: GraphOptions,
|
|
122
|
-
fetcher: PricingFetcher
|
|
123
|
-
): UnifiedMessage[] {
|
|
124
|
-
const messages: UnifiedMessage[] = [];
|
|
125
|
-
const enabledSources = getEnabledSources(options);
|
|
126
|
-
|
|
127
|
-
// OpenCode
|
|
128
|
-
if (enabledSources.includes("opencode")) {
|
|
129
|
-
const openCodeMessages = parseOpenCodeMessages();
|
|
130
|
-
for (const msg of openCodeMessages) {
|
|
131
|
-
const pricing = fetcher.getModelPricing(msg.modelId);
|
|
132
|
-
const cost = pricing
|
|
133
|
-
? fetcher.calculateCost(
|
|
134
|
-
{
|
|
135
|
-
input: msg.tokens.input,
|
|
136
|
-
output: msg.tokens.output,
|
|
137
|
-
reasoning: msg.tokens.reasoning,
|
|
138
|
-
cacheRead: msg.tokens.cacheRead,
|
|
139
|
-
cacheWrite: msg.tokens.cacheWrite,
|
|
140
|
-
},
|
|
141
|
-
pricing
|
|
142
|
-
)
|
|
143
|
-
: 0;
|
|
144
|
-
|
|
145
|
-
messages.push({
|
|
146
|
-
source: "opencode",
|
|
147
|
-
modelId: msg.modelId,
|
|
148
|
-
providerId: msg.providerId,
|
|
149
|
-
timestamp: msg.timestamp,
|
|
150
|
-
tokens: msg.tokens,
|
|
151
|
-
cost,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Claude Code
|
|
157
|
-
if (enabledSources.includes("claude")) {
|
|
158
|
-
const claudeMessages = parseClaudeCodeMessages();
|
|
159
|
-
for (const msg of claudeMessages) {
|
|
160
|
-
const pricing = fetcher.getModelPricing(msg.modelId);
|
|
161
|
-
const cost = pricing
|
|
162
|
-
? fetcher.calculateCost(
|
|
163
|
-
{
|
|
164
|
-
input: msg.tokens.input,
|
|
165
|
-
output: msg.tokens.output,
|
|
166
|
-
cacheRead: msg.tokens.cacheRead,
|
|
167
|
-
cacheWrite: msg.tokens.cacheWrite,
|
|
168
|
-
},
|
|
169
|
-
pricing
|
|
170
|
-
)
|
|
171
|
-
: 0;
|
|
172
|
-
|
|
173
|
-
messages.push({
|
|
174
|
-
source: "claude",
|
|
175
|
-
modelId: msg.modelId,
|
|
176
|
-
providerId: msg.providerId,
|
|
177
|
-
timestamp: msg.timestamp,
|
|
178
|
-
tokens: msg.tokens,
|
|
179
|
-
cost,
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Codex
|
|
185
|
-
if (enabledSources.includes("codex")) {
|
|
186
|
-
const codexMessages = parseCodexMessages();
|
|
187
|
-
for (const msg of codexMessages) {
|
|
188
|
-
const pricing = fetcher.getModelPricing(msg.modelId);
|
|
189
|
-
const cost = pricing
|
|
190
|
-
? fetcher.calculateCost(
|
|
191
|
-
{
|
|
192
|
-
input: msg.tokens.input,
|
|
193
|
-
output: msg.tokens.output,
|
|
194
|
-
cacheRead: msg.tokens.cacheRead,
|
|
195
|
-
cacheWrite: msg.tokens.cacheWrite,
|
|
196
|
-
},
|
|
197
|
-
pricing
|
|
198
|
-
)
|
|
199
|
-
: 0;
|
|
200
|
-
|
|
201
|
-
messages.push({
|
|
202
|
-
source: "codex",
|
|
203
|
-
modelId: msg.modelId,
|
|
204
|
-
providerId: msg.providerId,
|
|
205
|
-
timestamp: msg.timestamp,
|
|
206
|
-
tokens: msg.tokens,
|
|
207
|
-
cost,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Gemini
|
|
213
|
-
if (enabledSources.includes("gemini")) {
|
|
214
|
-
const geminiMessages = parseGeminiMessages();
|
|
215
|
-
for (const msg of geminiMessages) {
|
|
216
|
-
const pricing = fetcher.getModelPricing(msg.modelId);
|
|
217
|
-
// Gemini: thoughts/reasoning count as output for billing
|
|
218
|
-
const cost = pricing
|
|
219
|
-
? fetcher.calculateCost(
|
|
220
|
-
{
|
|
221
|
-
input: msg.tokens.input,
|
|
222
|
-
output: msg.tokens.output + msg.tokens.reasoning,
|
|
223
|
-
cacheRead: 0, // Gemini cached tokens are free
|
|
224
|
-
cacheWrite: 0,
|
|
225
|
-
},
|
|
226
|
-
pricing
|
|
227
|
-
)
|
|
228
|
-
: 0;
|
|
229
|
-
|
|
230
|
-
messages.push({
|
|
231
|
-
source: "gemini",
|
|
232
|
-
modelId: msg.modelId,
|
|
233
|
-
providerId: msg.providerId,
|
|
234
|
-
timestamp: msg.timestamp,
|
|
235
|
-
tokens: msg.tokens,
|
|
236
|
-
cost,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return messages;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Get list of enabled sources based on options
|
|
246
|
-
*/
|
|
247
|
-
function getEnabledSources(options: GraphOptions): SourceType[] {
|
|
248
|
-
if (options.sources && options.sources.length > 0) {
|
|
249
|
-
return options.sources;
|
|
250
|
-
}
|
|
251
|
-
return ["opencode", "claude", "codex", "gemini"];
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Filter messages by date range
|
|
256
|
-
*/
|
|
257
|
-
function filterMessagesByDate(
|
|
258
|
-
messages: UnifiedMessage[],
|
|
259
|
-
options: GraphOptions
|
|
260
|
-
): UnifiedMessage[] {
|
|
261
|
-
let filtered = messages;
|
|
262
|
-
|
|
263
|
-
// Filter by year
|
|
264
|
-
if (options.year) {
|
|
265
|
-
const yearStart = new Date(`${options.year}-01-01`).getTime();
|
|
266
|
-
const yearEnd = new Date(`${options.year}-12-31T23:59:59.999`).getTime();
|
|
267
|
-
filtered = filtered.filter(
|
|
268
|
-
(m) => m.timestamp >= yearStart && m.timestamp <= yearEnd
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Filter by since
|
|
273
|
-
if (options.since) {
|
|
274
|
-
const sinceTime = new Date(options.since).getTime();
|
|
275
|
-
filtered = filtered.filter((m) => m.timestamp >= sinceTime);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Filter by until
|
|
279
|
-
if (options.until) {
|
|
280
|
-
const untilTime = new Date(`${options.until}T23:59:59.999`).getTime();
|
|
281
|
-
filtered = filtered.filter((m) => m.timestamp <= untilTime);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return filtered;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Aggregate messages by date
|
|
289
|
-
*/
|
|
290
|
-
function aggregateByDate(
|
|
291
|
-
messages: UnifiedMessage[],
|
|
292
|
-
_fetcher: PricingFetcher
|
|
293
|
-
): Map<string, DailyContribution> {
|
|
294
|
-
const dailyMap = new Map<string, DailyContribution>();
|
|
295
|
-
|
|
296
|
-
for (const msg of messages) {
|
|
297
|
-
const dateKey = format(new Date(msg.timestamp), "yyyy-MM-dd");
|
|
298
|
-
|
|
299
|
-
let daily = dailyMap.get(dateKey);
|
|
300
|
-
if (!daily) {
|
|
301
|
-
daily = createEmptyDailyContribution(dateKey);
|
|
302
|
-
dailyMap.set(dateKey, daily);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Update totals
|
|
306
|
-
const totalTokens =
|
|
307
|
-
msg.tokens.input +
|
|
308
|
-
msg.tokens.output +
|
|
309
|
-
msg.tokens.cacheRead +
|
|
310
|
-
msg.tokens.cacheWrite +
|
|
311
|
-
msg.tokens.reasoning;
|
|
312
|
-
|
|
313
|
-
daily.totals.tokens += totalTokens;
|
|
314
|
-
daily.totals.cost += msg.cost;
|
|
315
|
-
daily.totals.messages += 1;
|
|
316
|
-
|
|
317
|
-
// Update token breakdown
|
|
318
|
-
daily.tokenBreakdown.input += msg.tokens.input;
|
|
319
|
-
daily.tokenBreakdown.output += msg.tokens.output;
|
|
320
|
-
daily.tokenBreakdown.cacheRead += msg.tokens.cacheRead;
|
|
321
|
-
daily.tokenBreakdown.cacheWrite += msg.tokens.cacheWrite;
|
|
322
|
-
daily.tokenBreakdown.reasoning += msg.tokens.reasoning;
|
|
323
|
-
|
|
324
|
-
// Update source contributions
|
|
325
|
-
let sourceContrib = daily.sources.find(
|
|
326
|
-
(s) => s.source === msg.source && s.modelId === msg.modelId
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
if (!sourceContrib) {
|
|
330
|
-
sourceContrib = {
|
|
331
|
-
source: msg.source,
|
|
332
|
-
modelId: msg.modelId,
|
|
333
|
-
providerId: msg.providerId,
|
|
334
|
-
tokens: {
|
|
335
|
-
input: 0,
|
|
336
|
-
output: 0,
|
|
337
|
-
cacheRead: 0,
|
|
338
|
-
cacheWrite: 0,
|
|
339
|
-
reasoning: 0,
|
|
340
|
-
},
|
|
341
|
-
cost: 0,
|
|
342
|
-
messages: 0,
|
|
343
|
-
};
|
|
344
|
-
daily.sources.push(sourceContrib);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
sourceContrib.tokens.input += msg.tokens.input;
|
|
348
|
-
sourceContrib.tokens.output += msg.tokens.output;
|
|
349
|
-
sourceContrib.tokens.cacheRead += msg.tokens.cacheRead;
|
|
350
|
-
sourceContrib.tokens.cacheWrite += msg.tokens.cacheWrite;
|
|
351
|
-
sourceContrib.tokens.reasoning += msg.tokens.reasoning;
|
|
352
|
-
sourceContrib.cost += msg.cost;
|
|
353
|
-
sourceContrib.messages += 1;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
return dailyMap;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Create empty daily contribution
|
|
361
|
-
*/
|
|
362
|
-
function createEmptyDailyContribution(date: string): DailyContribution {
|
|
363
|
-
return {
|
|
364
|
-
date,
|
|
365
|
-
totals: {
|
|
366
|
-
tokens: 0,
|
|
367
|
-
cost: 0,
|
|
368
|
-
messages: 0,
|
|
369
|
-
},
|
|
370
|
-
intensity: 0,
|
|
371
|
-
tokenBreakdown: {
|
|
372
|
-
input: 0,
|
|
373
|
-
output: 0,
|
|
374
|
-
cacheRead: 0,
|
|
375
|
-
cacheWrite: 0,
|
|
376
|
-
reasoning: 0,
|
|
377
|
-
},
|
|
378
|
-
sources: [],
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Calculate intensity for all contributions based on COST
|
|
384
|
-
*/
|
|
385
|
-
function calculateAllIntensities(
|
|
386
|
-
contributions: DailyContribution[]
|
|
387
|
-
): DailyContribution[] {
|
|
388
|
-
if (contributions.length === 0) return contributions;
|
|
389
|
-
|
|
390
|
-
// Find max cost for intensity calculation
|
|
391
|
-
const maxCost = Math.max(...contributions.map((c) => c.totals.cost));
|
|
392
|
-
|
|
393
|
-
return contributions.map((c) => ({
|
|
394
|
-
...c,
|
|
395
|
-
intensity: calculateIntensity(c.totals.cost, maxCost),
|
|
396
|
-
}));
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Calculate intensity grade based on cost ratio
|
|
401
|
-
* 0 = no activity, 4 = highest
|
|
402
|
-
*/
|
|
403
|
-
function calculateIntensity(
|
|
404
|
-
cost: number,
|
|
405
|
-
maxCost: number
|
|
406
|
-
): 0 | 1 | 2 | 3 | 4 {
|
|
407
|
-
if (cost === 0 || maxCost === 0) return 0;
|
|
408
|
-
const ratio = cost / maxCost;
|
|
409
|
-
if (ratio >= 0.75) return 4;
|
|
410
|
-
if (ratio >= 0.5) return 3;
|
|
411
|
-
if (ratio >= 0.25) return 2;
|
|
412
|
-
return 1;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Calculate summary statistics
|
|
417
|
-
*/
|
|
418
|
-
function calculateSummary(contributions: DailyContribution[]): DataSummary {
|
|
419
|
-
const totalTokens = contributions.reduce((sum, c) => sum + c.totals.tokens, 0);
|
|
420
|
-
const totalCost = contributions.reduce((sum, c) => sum + c.totals.cost, 0);
|
|
421
|
-
const activeDays = contributions.filter((c) => c.totals.cost > 0).length;
|
|
422
|
-
const maxCostInSingleDay = Math.max(
|
|
423
|
-
...contributions.map((c) => c.totals.cost),
|
|
424
|
-
0
|
|
425
|
-
);
|
|
426
|
-
|
|
427
|
-
// Collect unique sources and models
|
|
428
|
-
const sourcesSet = new Set<SourceType>();
|
|
429
|
-
const modelsSet = new Set<string>();
|
|
430
|
-
|
|
431
|
-
for (const c of contributions) {
|
|
432
|
-
for (const s of c.sources) {
|
|
433
|
-
sourcesSet.add(s.source);
|
|
434
|
-
modelsSet.add(s.modelId);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return {
|
|
439
|
-
totalTokens,
|
|
440
|
-
totalCost,
|
|
441
|
-
totalDays: contributions.length,
|
|
442
|
-
activeDays,
|
|
443
|
-
averagePerDay: activeDays > 0 ? totalCost / activeDays : 0,
|
|
444
|
-
maxCostInSingleDay,
|
|
445
|
-
sources: Array.from(sourcesSet),
|
|
446
|
-
models: Array.from(modelsSet),
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Calculate year summaries
|
|
452
|
-
*/
|
|
453
|
-
function calculateYears(contributions: DailyContribution[]): YearSummary[] {
|
|
454
|
-
const yearsMap = new Map<
|
|
455
|
-
string,
|
|
456
|
-
{ tokens: number; cost: number; start: string; end: string }
|
|
457
|
-
>();
|
|
458
|
-
|
|
459
|
-
for (const c of contributions) {
|
|
460
|
-
const year = c.date.substring(0, 4);
|
|
461
|
-
let yearData = yearsMap.get(year);
|
|
462
|
-
|
|
463
|
-
if (!yearData) {
|
|
464
|
-
yearData = { tokens: 0, cost: 0, start: c.date, end: c.date };
|
|
465
|
-
yearsMap.set(year, yearData);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
yearData.tokens += c.totals.tokens;
|
|
469
|
-
yearData.cost += c.totals.cost;
|
|
470
|
-
if (c.date < yearData.start) yearData.start = c.date;
|
|
471
|
-
if (c.date > yearData.end) yearData.end = c.date;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return Array.from(yearsMap.entries())
|
|
475
|
-
.map(([year, data]) => ({
|
|
476
|
-
year,
|
|
477
|
-
totalTokens: data.tokens,
|
|
478
|
-
totalCost: data.cost,
|
|
479
|
-
range: {
|
|
480
|
-
start: data.start,
|
|
481
|
-
end: data.end,
|
|
482
|
-
},
|
|
483
|
-
}))
|
|
484
|
-
.sort((a, b) => a.year.localeCompare(b.year));
|
|
485
|
-
}
|