@roastcodes/ttdash 6.1.0

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.
@@ -0,0 +1,152 @@
1
+ function toNumber(value) {
2
+ return Number.isFinite(value) ? value : Number(value) || 0
3
+ }
4
+
5
+ function toStringValue(value) {
6
+ return typeof value === 'string' ? value : ''
7
+ }
8
+
9
+ function normalizeLegacyModelBreakdown(entry) {
10
+ return {
11
+ modelName: toStringValue(entry?.modelName),
12
+ inputTokens: toNumber(entry?.inputTokens),
13
+ outputTokens: toNumber(entry?.outputTokens),
14
+ cacheCreationTokens: toNumber(entry?.cacheCreationTokens),
15
+ cacheReadTokens: toNumber(entry?.cacheReadTokens),
16
+ thinkingTokens: toNumber(entry?.thinkingTokens),
17
+ cost: toNumber(entry?.cost),
18
+ requestCount: toNumber(entry?.requestCount),
19
+ };
20
+ }
21
+
22
+ function withDailyTotals(day) {
23
+ const totalTokens = toNumber(day.totalTokens) || (
24
+ toNumber(day.inputTokens) +
25
+ toNumber(day.outputTokens) +
26
+ toNumber(day.cacheCreationTokens) +
27
+ toNumber(day.cacheReadTokens) +
28
+ toNumber(day.thinkingTokens)
29
+ );
30
+
31
+ return {
32
+ date: toStringValue(day.date),
33
+ inputTokens: toNumber(day.inputTokens),
34
+ outputTokens: toNumber(day.outputTokens),
35
+ cacheCreationTokens: toNumber(day.cacheCreationTokens),
36
+ cacheReadTokens: toNumber(day.cacheReadTokens),
37
+ thinkingTokens: toNumber(day.thinkingTokens),
38
+ totalTokens,
39
+ totalCost: toNumber(day.totalCost),
40
+ requestCount: toNumber(day.requestCount),
41
+ modelsUsed: Array.isArray(day.modelsUsed) ? day.modelsUsed.filter((value) => typeof value === 'string') : [],
42
+ modelBreakdowns: Array.isArray(day.modelBreakdowns) ? day.modelBreakdowns.map(normalizeLegacyModelBreakdown) : [],
43
+ };
44
+ }
45
+
46
+ function normalizeLegacyDay(entry) {
47
+ const modelBreakdowns = Array.isArray(entry?.modelBreakdowns)
48
+ ? entry.modelBreakdowns.map(normalizeLegacyModelBreakdown)
49
+ : [];
50
+
51
+ return withDailyTotals({
52
+ date: entry?.date,
53
+ inputTokens: entry?.inputTokens,
54
+ outputTokens: entry?.outputTokens,
55
+ cacheCreationTokens: entry?.cacheCreationTokens,
56
+ cacheReadTokens: entry?.cacheReadTokens,
57
+ thinkingTokens: entry?.thinkingTokens,
58
+ totalTokens: entry?.totalTokens,
59
+ totalCost: entry?.totalCost,
60
+ requestCount: entry?.requestCount,
61
+ modelsUsed: Array.isArray(entry?.modelsUsed)
62
+ ? entry.modelsUsed
63
+ : modelBreakdowns.map((item) => item.modelName),
64
+ modelBreakdowns,
65
+ });
66
+ }
67
+
68
+ function normalizeToktrackDay(entry) {
69
+ const models = entry?.models && typeof entry.models === 'object' && !Array.isArray(entry.models)
70
+ ? entry.models
71
+ : {};
72
+
73
+ const modelBreakdowns = Object.entries(models).map(([modelName, modelData]) => ({
74
+ modelName,
75
+ inputTokens: toNumber(modelData?.input_tokens),
76
+ outputTokens: toNumber(modelData?.output_tokens),
77
+ cacheCreationTokens: toNumber(modelData?.cache_creation_tokens),
78
+ cacheReadTokens: toNumber(modelData?.cache_read_tokens),
79
+ thinkingTokens: toNumber(modelData?.thinking_tokens),
80
+ cost: toNumber(modelData?.cost_usd),
81
+ requestCount: toNumber(modelData?.count),
82
+ }));
83
+
84
+ const requestCount = modelBreakdowns.reduce((sum, item) => sum + item.requestCount, 0);
85
+
86
+ return withDailyTotals({
87
+ date: entry?.date,
88
+ inputTokens: entry?.total_input_tokens,
89
+ outputTokens: entry?.total_output_tokens,
90
+ cacheCreationTokens: entry?.total_cache_creation_tokens,
91
+ cacheReadTokens: entry?.total_cache_read_tokens,
92
+ thinkingTokens: entry?.total_thinking_tokens,
93
+ totalCost: entry?.total_cost_usd,
94
+ requestCount,
95
+ modelsUsed: modelBreakdowns.map((item) => item.modelName),
96
+ modelBreakdowns,
97
+ });
98
+ }
99
+
100
+ function computeTotals(daily) {
101
+ return daily.reduce((totals, day) => ({
102
+ inputTokens: totals.inputTokens + day.inputTokens,
103
+ outputTokens: totals.outputTokens + day.outputTokens,
104
+ cacheCreationTokens: totals.cacheCreationTokens + day.cacheCreationTokens,
105
+ cacheReadTokens: totals.cacheReadTokens + day.cacheReadTokens,
106
+ thinkingTokens: totals.thinkingTokens + day.thinkingTokens,
107
+ totalCost: totals.totalCost + day.totalCost,
108
+ totalTokens: totals.totalTokens + day.totalTokens,
109
+ requestCount: totals.requestCount + day.requestCount,
110
+ }), {
111
+ inputTokens: 0,
112
+ outputTokens: 0,
113
+ cacheCreationTokens: 0,
114
+ cacheReadTokens: 0,
115
+ thinkingTokens: 0,
116
+ totalCost: 0,
117
+ totalTokens: 0,
118
+ requestCount: 0,
119
+ });
120
+ }
121
+
122
+ function normalizeIncomingData(payload) {
123
+ let daily;
124
+
125
+ if (Array.isArray(payload)) {
126
+ daily = payload.map(normalizeToktrackDay);
127
+ } else if (payload && typeof payload === 'object' && Array.isArray(payload.daily)) {
128
+ const looksLikeToktrack = payload.daily.some((item) => item && typeof item === 'object' && 'total_input_tokens' in item);
129
+ daily = looksLikeToktrack
130
+ ? payload.daily.map(normalizeToktrackDay)
131
+ : payload.daily.map(normalizeLegacyDay);
132
+ } else {
133
+ throw new Error('Die JSON-Datei muss ein gültiges tägliches Nutzungsformat enthalten.');
134
+ }
135
+
136
+ const filtered = daily
137
+ .filter((item) => item.date)
138
+ .sort((a, b) => a.date.localeCompare(b.date));
139
+
140
+ if (filtered.length === 0) {
141
+ throw new Error('Keine Nutzungsdaten gefunden.');
142
+ }
143
+
144
+ return {
145
+ daily: filtered,
146
+ totals: computeTotals(filtered),
147
+ };
148
+ }
149
+
150
+ module.exports = {
151
+ normalizeIncomingData,
152
+ };