@subvalue/cli 0.1.2 → 0.1.4

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.
Files changed (40) hide show
  1. package/PRE_RELEASE.md +6 -6
  2. package/README.md +7 -5
  3. package/dist/PRICING.md +15 -16
  4. package/dist/packages/cli/src/index.js +109 -20
  5. package/dist/packages/cli/src/protocol.js +13 -0
  6. package/dist/packages/cli/src/runtime.js +44 -14
  7. package/dist/packages/cli/src/server.js +306 -62
  8. package/dist/packages/core/src/calendar.js +47 -0
  9. package/dist/packages/core/src/coverage.js +12 -0
  10. package/dist/packages/core/src/demo.js +61 -9
  11. package/dist/packages/core/src/metadata.js +52 -9
  12. package/dist/packages/core/src/pricing/current.js +346 -297
  13. package/dist/packages/core/src/pricing/index.js +373 -73
  14. package/dist/packages/core/src/providers/claude/index.js +93 -21
  15. package/dist/packages/core/src/providers/codex/index.js +213 -46
  16. package/dist/packages/core/src/scanner.js +226 -46
  17. package/dist/packages/core/src/security.js +127 -34
  18. package/dist/packages/core/src/storage.js +215 -47
  19. package/dist/packages/core/src/subscriptions.js +146 -21
  20. package/dist/packages/core/src/summary.js +409 -51
  21. package/dist/packages/core/src/types.js +71 -17
  22. package/dist/packages/core/src/usage-json.js +166 -47
  23. package/dist/packages/receipt/src/index.js +387 -80
  24. package/dist/packages/ui/src/brand.js +48 -3
  25. package/dist/public/app.css +2280 -56
  26. package/dist/public/apps/web/src/landing.js +200 -8
  27. package/dist/public/apps/web/src/main.js +1143 -63
  28. package/dist/public/index.html +24 -1
  29. package/dist/public/landing.css +3235 -70
  30. package/dist/public/landing.html +21 -1
  31. package/dist/public/logo/wordmark-monochrome.svg +1 -0
  32. package/dist/public/logo/wordmark.svg +1 -0
  33. package/dist/public/packages/core/src/calendar.js +47 -0
  34. package/dist/public/packages/core/src/coverage.js +12 -0
  35. package/dist/public/packages/core/src/subscriptions.js +146 -21
  36. package/dist/public/packages/receipt/src/index.js +387 -80
  37. package/dist/public/packages/ui/src/brand.js +48 -3
  38. package/dist/public/privacy.css +197 -6
  39. package/dist/public/privacy.html +1 -1
  40. package/package.json +1 -1
@@ -1,55 +1,413 @@
1
-
2
- import {priceRecord,catalog, } from './pricing/index.js';
3
- const dayFormatter=new Intl.DateTimeFormat('en-CA',{year:'numeric',month:'2-digit',day:'2-digit'});
4
- function localDay(timestamp ) {const parts=dayFormatter.formatToParts(new Date(timestamp));return ['year','month','day'].map(type=>parts.find(part=>part.type===type) .value).join('-');}
5
-
6
- export function dateRange(preset ,now=new Date(),from ,to ) {
7
- const end=new Date(now.getFullYear(),now.getMonth(),now.getDate()+1);let start=new Date(now.getFullYear(),now.getMonth(),now.getDate()-29);let label='Last 30 days';
8
- if(preset==='7d'){start=new Date(now.getFullYear(),now.getMonth(),now.getDate()-6);label='Last 7 days';}
9
- else if(preset==='month'){start=new Date(now.getFullYear(),now.getMonth(),1);end.setTime(+new Date(now.getFullYear(),now.getMonth()+1,1));label=now.toLocaleDateString('en-US',{month:'long',year:'numeric'});}
10
- else if(preset==='custom'){
11
- if(!from||!to||!/^\d{4}-\d{2}-\d{2}$/.test(from)||!/^\d{4}-\d{2}-\d{2}$/.test(to))throw new Error('Choose a valid date range');
12
- start=new Date(from+'T00:00:00');const last=new Date(to+'T00:00:00');const matches=(date ,text )=>Number.isFinite(+date)&&date.getFullYear()===Number(text.slice(0,4))&&date.getMonth()+1===Number(text.slice(5,7))&&date.getDate()===Number(text.slice(8,10));if(!matches(start,from)||!matches(last,to)||start>last||+last-+start>36600*864e5)throw new Error('Choose a valid date range');end.setTime(+new Date(last.getFullYear(),last.getMonth(),last.getDate()+1));label=`${from} — ${to}`;
13
- }else if(preset!=='30d')throw new Error('Invalid period');
14
- return {from:start.toISOString(),until:end.toISOString(),label};
1
+
2
+ import { priceRecord, catalog, } from './pricing/index.js';
3
+ import { fullCalendarMonths, shiftCalendarDays } from './calendar.js';
4
+ export { fullCalendarMonths } from './calendar.js';
5
+ const dayFormatter = new Intl.DateTimeFormat('en-CA', {
6
+ year: 'numeric',
7
+ month: '2-digit',
8
+ day: '2-digit',
9
+ });
10
+ function localDay(timestamp ) {
11
+ const parts = dayFormatter.formatToParts(new Date(timestamp));
12
+ return ['year', 'month', 'day']
13
+ .map((type) => parts.find((part) => part.type === type) .value)
14
+ .join('-');
15
15
  }
16
-
17
- export function subscriptionForRange(monthly ,range ) {
18
- // Calendar-day proration in the server's local timezone, including multi-month ranges.
19
- let total=0;const day=new Date(range.from),until=new Date(range.until);let guard=0;
20
- while(day<until){const days=new Date(day.getFullYear(),day.getMonth()+1,0).getDate();total+=monthly/days;day.setDate(day.getDate()+1);if(++guard>36600)throw new Error('Range too large');}return total;
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+ export function dateRange(
26
+ preset ,
27
+ now = new Date(),
28
+ from ,
29
+ to ,
30
+ month ,
31
+ anchor ,
32
+ ) {
33
+ const today = localDay(now.toISOString());
34
+ if (anchor !== undefined) {
35
+ if (
36
+ (preset !== '7d' && preset !== '30d') ||
37
+ !/^(?:19|20|21)\d{2}-\d{2}-\d{2}$/.test(anchor) ||
38
+ shiftCalendarDays(anchor, 0) === null
39
+ )
40
+ throw new Error('Choose a valid period');
41
+ now = new Date(anchor + 'T12:00:00');
42
+ }
43
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
44
+ let start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 29);
45
+ let label = 'Last 30 days';
46
+ if (preset === '7d') {
47
+ start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 6);
48
+ label = 'Last 7 days';
49
+ } else if (preset === 'month') {
50
+ if (month !== undefined && !/^(?:19|20|21)\d{2}-(?:0[1-9]|1[0-2])$/.test(month))
51
+ throw new Error('Choose a valid month');
52
+ const year = month ? Number(month.slice(0, 4)) : now.getFullYear(),
53
+ index = month ? Number(month.slice(5, 7)) - 1 : now.getMonth();
54
+ start = new Date(year, index, 1);
55
+ end.setTime(+new Date(year, index + 1, 1));
56
+ label = start.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
57
+ } else if (preset === 'custom') {
58
+ if (!from || !to || !/^\d{4}-\d{2}-\d{2}$/.test(from) || !/^\d{4}-\d{2}-\d{2}$/.test(to))
59
+ throw new Error('Choose a valid date range');
60
+ start = new Date(from + 'T00:00:00');
61
+ const last = new Date(to + 'T00:00:00');
62
+ const matches = (date , text ) =>
63
+ Number.isFinite(+date) &&
64
+ date.getFullYear() === Number(text.slice(0, 4)) &&
65
+ date.getMonth() + 1 === Number(text.slice(5, 7)) &&
66
+ date.getDate() === Number(text.slice(8, 10));
67
+ if (
68
+ !matches(start, from) ||
69
+ !matches(last, to) ||
70
+ start > last ||
71
+ +last - +start > 36600 * 864e5
72
+ )
73
+ throw new Error('Choose a valid date range');
74
+ end.setTime(+new Date(last.getFullYear(), last.getMonth(), last.getDate() + 1));
75
+ label = `${from} — ${to}`;
76
+ } else if (preset !== '30d') throw new Error('Invalid period');
77
+ const calendarFrom = localDay(start.toISOString()),
78
+ calendarTo = localDay(new Date(+end - 1).toISOString());
79
+ if (anchor !== undefined && anchor !== today) label = `${calendarFrom} — ${calendarTo}`;
80
+ const range = {
81
+ from: start.toISOString(),
82
+ until: end.toISOString(),
83
+ label,
84
+ preset,
85
+ calendarFrom,
86
+ calendarTo,
87
+ };
88
+ return { ...range, calendarMonths: fullCalendarMonths(range) };
21
89
  }
22
-
23
-
24
-
25
-
26
- function neutral(reason ,subscription =null) {return {subscription,value:null,roi:null,breakEven:null,outcome:'neutral',reason};}
27
- export function summarize(records ,statuses ,settings ,range ,c =catalog,demo=false) {
28
- const selected=records.filter(r=>r.timestamp&&r.timestamp>=range.from&&r.timestamp<range.until);const prices=new Map(selected.map(r=>[r.id,priceRecord(r,c)]));const dailyMap=new Map ();
29
- for(const r of selected){const date=localDay(r.timestamp );const d=dailyMap.get(date)??{date,codex:null,claude:null,hasUsage:true};const cost=prices.get(r.id) .usd;if(cost!==null)d[r.provider]=(d[r.provider]??0)+cost;dailyMap.set(date,d);}
30
- const daily=[...dailyMap.values()].sort((a,b)=>a.date.localeCompare(b.date));
31
- const providers=(['codex','claude'] ).map(provider=>{
32
- const rows=selected.filter(r=>r.provider===provider),all=records.filter(r=>r.provider===provider),source=statuses.find(s=>s.provider===provider);const issues ={};
33
- const costs=rows.map(r=>prices.get(r.id) );for(const p of costs)if(p.reason)issues[p.reason]=(issues[p.reason]??0)+1;
34
- const undated=all.filter(r=>!r.timestamp).length;if(undated)issues['undated-events']=undated;
35
- const priced=costs.filter(p=>p.usd!==null),subtotal=priced.length?priced.reduce((n,p)=>n+p.usd ,0):null;
36
- const historyPartial=!demo; // Local transcripts cannot prove that all historical requests were retained.
37
- const completePricing=rows.length>0&&priced.length===rows.length;const apiEquivalent=completePricing?subtotal:null;
38
- const billing=settings.billing[provider];const subscription=billing.mode==='SUBSCRIPTION'&&billing.monthly!==null?subscriptionForRange(billing.monthly,range):null;
39
- let comparison=neutral(billing.mode==='API'?'API usage':billing.mode==='MIXED'?'Mixed billing':billing.mode==='UNKNOWN'?'Billing not set':subscription===null?'Subscription price not set':!completePricing?'Pricing unavailable':'Partial history',subscription);
40
- if(completePricing&&subscription!==null){const value=apiEquivalent -subscription;let accumulated=0;let breakEven =null;for(const d of daily){accumulated+=d[provider]??0;if(accumulated>=subscription){breakEven=d.date;break;}}comparison={subscription,value,roi:subscription>0?apiEquivalent /subscription:null,breakEven:historyPartial?null:breakEven,outcome:historyPartial?'neutral':value>0?'positive':value<0?'negative':'neutral',reason:historyPartial?'Observed usage · Partial history':null};}
41
- const sumTokens=(key ) =>rows.length&&rows.every(r=>r[key]!==null)?rows.reduce((total,r)=>total+r[key] ,0):null;
42
- const tokenBreakdown={input:sumTokens('input_tokens'),output:sumTokens('output_tokens'),cacheRead:sumTokens('cached_input_tokens')};
43
- const tokensKnown=rows.filter(r=>r.total_tokens!==null);const timestamps=all.map(r=>r.timestamp).filter((t) =>!!t).sort();
44
- const hasUsage=rows.some(r=>r.total_tokens!==0);const sourceIncomplete=source?.errors||source?.missingFiles||source?.diagnostics.malformed||source?.diagnostics.partialTail||source?.diagnostics.oversize||source?.diagnostics.unsupported;
45
- return {provider,detected:source?.detected??false,visible:hasUsage||settings.include[provider],status:rows.length?(hasUsage?'USAGE_FOUND':'NO_USAGE'):'NO_DATA',records:rows.length,importedRecords:all.length,tokens:tokensKnown.length?tokensKnown.reduce((n,r)=>n+r.total_tokens ,0):null,tokensPartial:tokensKnown.length!==rows.length,tokenBreakdown,apiEquivalent,knownSubtotal:subtotal,pricedRecords:priced.length,priceCoverage:rows.length?priced.length/rows.length:null,confidence:!rows.length?'NO DATA':rows.some(r=>r.quality==='INCOMPLETE')?'INCOMPLETE':sourceIncomplete||undated||rows.some(r=>r.quality==='LOW')?'LOW':rows.some(r=>r.quality==='MEDIUM')?'MEDIUM':'HIGH',historyPartial,comparison,mode:billing.mode,monthly:billing.monthly,first:timestamps[0]??null,last:timestamps.at(-1)??null,days:new Set(timestamps.map(t=>t.slice(0,10))).size,issues} ;
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+ export function subscriptionForRange(monthly , range ) {
99
+ const months = fullCalendarMonths(range);
100
+ return months === null ? null : monthly * months;
101
+ }
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+
151
+
152
+
153
+
154
+
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+ function neutral(reason , subscription = null) {
165
+ return { subscription, value: null, roi: null, breakEven: null, outcome: 'neutral', reason };
166
+ }
167
+ export function summarize(
168
+ records ,
169
+ statuses ,
170
+ settings ,
171
+ range ,
172
+ c = catalog,
173
+ demo = false,
174
+ ) {
175
+ const selected = records.filter(
176
+ (r) => r.timestamp && r.timestamp >= range.from && r.timestamp < range.until,
177
+ );
178
+ const prices = new Map(selected.map((r) => [r.id, priceRecord(r, c)]));
179
+ const dailyMap = new Map ();
180
+ for (const r of selected) {
181
+ const date = localDay(r.timestamp );
182
+ const d = dailyMap.get(date) ?? { date, codex: null, claude: null, hasUsage: true };
183
+ const cost = prices.get(r.id) .usd;
184
+ if (cost !== null) d[r.provider] = (d[r.provider] ?? 0) + cost;
185
+ dailyMap.set(date, d);
186
+ }
187
+ const daily = [...dailyMap.values()].sort((a, b) => a.date.localeCompare(b.date));
188
+ const providers = (['codex', 'claude'] ).map((provider) => {
189
+ const rows = selected.filter((r) => r.provider === provider),
190
+ all = records.filter((r) => r.provider === provider),
191
+ source = statuses.find((s) => s.provider === provider);
192
+ const issues = {};
193
+ const costs = rows.map((r) => prices.get(r.id) );
194
+ for (const p of costs) if (p.reason) issues[p.reason] = (issues[p.reason] ?? 0) + 1;
195
+ const undated = all.filter((r) => !r.timestamp).length;
196
+ if (undated) issues['undated-events'] = undated;
197
+ const priced = costs.filter((p) => p.usd !== null),
198
+ subtotal = priced.length ? priced.reduce((n, p) => n + p.usd , 0) : null;
199
+ const historyPartial = !demo; // Local transcripts cannot prove that all historical requests were retained.
200
+ const completePricing = rows.length > 0 && priced.length === rows.length;
201
+ const apiEquivalent = completePricing ? subtotal : null;
202
+ const billing = settings.billing[provider];
203
+ const subscription =
204
+ billing.mode === 'SUBSCRIPTION' && billing.monthly !== null
205
+ ? subscriptionForRange(billing.monthly, range)
206
+ : null;
207
+ let comparison = neutral(
208
+ billing.mode === 'API'
209
+ ? 'API usage'
210
+ : billing.mode === 'MIXED'
211
+ ? 'Mixed billing'
212
+ : billing.mode === 'UNKNOWN'
213
+ ? 'Billing not set'
214
+ : fullCalendarMonths(range) === null
215
+ ? 'Choose a full month'
216
+ : subscription === null
217
+ ? 'Subscription price not set'
218
+ : !completePricing
219
+ ? 'Pricing unavailable'
220
+ : 'Partial history',
221
+ subscription,
222
+ );
223
+ if (completePricing && subscription !== null) {
224
+ const value = apiEquivalent - subscription;
225
+ let accumulated = 0;
226
+ let breakEven = null;
227
+ for (const d of daily) {
228
+ accumulated += d[provider] ?? 0;
229
+ if (accumulated >= subscription) {
230
+ breakEven = d.date;
231
+ break;
232
+ }
233
+ }
234
+ comparison = {
235
+ subscription,
236
+ value,
237
+ roi: subscription > 0 ? apiEquivalent / subscription : null,
238
+ breakEven: historyPartial ? null : breakEven,
239
+ outcome: historyPartial
240
+ ? 'neutral'
241
+ : value > 0
242
+ ? 'positive'
243
+ : value < 0
244
+ ? 'negative'
245
+ : 'neutral',
246
+ reason: historyPartial ? 'Observed usage · Partial history' : null,
247
+ };
248
+ }
249
+ const sumTokens = (
250
+ key ,
251
+ ) =>
252
+ rows.length && rows.every((r) => r[key] !== null)
253
+ ? rows.reduce((total, r) => total + r[key] , 0)
254
+ : null;
255
+ const tokenBreakdown = {
256
+ input: sumTokens('input_tokens'),
257
+ output: sumTokens('output_tokens'),
258
+ cacheRead: sumTokens('cached_input_tokens'),
259
+ };
260
+ const tokensKnown = rows.filter((r) => r.total_tokens !== null);
261
+ const timestamps = all
262
+ .map((r) => r.timestamp)
263
+ .filter((t) => !!t)
264
+ .sort();
265
+ const hasUsage = rows.some((r) => r.total_tokens !== 0);
266
+ const sourceIncomplete =
267
+ source?.errors ||
268
+ source?.missingFiles ||
269
+ source?.diagnostics.malformed ||
270
+ source?.diagnostics.partialTail ||
271
+ source?.diagnostics.oversize ||
272
+ source?.diagnostics.unsupported;
273
+ return {
274
+ provider,
275
+ detected: source?.detected ?? false,
276
+ visible: hasUsage || settings.include[provider],
277
+ status: rows.length ? (hasUsage ? 'USAGE_FOUND' : 'NO_USAGE') : 'NO_DATA',
278
+ records: rows.length,
279
+ importedRecords: all.length,
280
+ tokens: tokensKnown.length ? tokensKnown.reduce((n, r) => n + r.total_tokens , 0) : null,
281
+ tokensPartial: tokensKnown.length !== rows.length,
282
+ tokenBreakdown,
283
+ apiEquivalent,
284
+ knownSubtotal: subtotal,
285
+ pricedRecords: priced.length,
286
+ priceCoverage: rows.length ? priced.length / rows.length : null,
287
+ confidence: !rows.length
288
+ ? 'NO DATA'
289
+ : rows.some((r) => r.quality === 'INCOMPLETE')
290
+ ? 'INCOMPLETE'
291
+ : sourceIncomplete || undated || rows.some((r) => r.quality === 'LOW')
292
+ ? 'LOW'
293
+ : rows.some((r) => r.quality === 'MEDIUM')
294
+ ? 'MEDIUM'
295
+ : 'HIGH',
296
+ historyPartial,
297
+ comparison,
298
+ mode: billing.mode,
299
+ monthly: billing.monthly,
300
+ first: timestamps[0] ?? null,
301
+ last: timestamps.at(-1) ?? null,
302
+ days: new Set(timestamps.map((t) => t.slice(0, 10))).size,
303
+ issues,
304
+ } ;
305
+ });
306
+ const active = providers.filter((p) => p.records > 0),
307
+ pricedRecords = active.reduce((n, p) => n + p.pricedRecords, 0),
308
+ count = selected.length;
309
+ const known = active.filter((p) => p.knownSubtotal !== null);
310
+ const knownSubtotal = known.length ? known.reduce((n, p) => n + p.knownSubtotal , 0) : null;
311
+ const apiEquivalent = count > 0 && pricedRecords === count ? knownSubtotal : null;
312
+ const subs = active.filter((p) => p.mode === 'SUBSCRIPTION');
313
+ let comparison = neutral(
314
+ active.some((p) => p.mode === 'API' || p.mode === 'MIXED' || p.mode === 'UNKNOWN')
315
+ ? 'Subscription comparison unavailable'
316
+ : 'Partial history',
317
+ );
318
+ if (
319
+ subs.length &&
320
+ subs.length === active.length &&
321
+ subs.every((p) => p.comparison.value !== null)
322
+ ) {
323
+ const subscription = subs.reduce((n, p) => n + p.comparison.subscription , 0);
324
+ const value = apiEquivalent - subscription;
325
+ let accumulated = 0,
326
+ breakEven = null;
327
+ if (demo)
328
+ for (const d of daily) {
329
+ accumulated += (d.codex ?? 0) + (d.claude ?? 0);
330
+ if (accumulated >= subscription) {
331
+ breakEven = d.date;
332
+ break;
333
+ }
334
+ }
335
+ comparison = {
336
+ subscription,
337
+ value,
338
+ roi: subscription > 0 ? apiEquivalent / subscription : null,
339
+ breakEven,
340
+ outcome: demo ? (value > 0 ? 'positive' : value < 0 ? 'negative' : 'neutral') : 'neutral',
341
+ reason: demo ? null : 'Observed usage · Partial history',
342
+ };
343
+ } else if (
344
+ subs.length &&
345
+ subs.length === active.length &&
346
+ subs.every((p) => p.comparison.subscription !== null)
347
+ )
348
+ comparison.subscription = subs.reduce((n, p) => n + p.comparison.subscription , 0);
349
+ const confidence = active.length
350
+ ? ['INCOMPLETE', 'LOW', 'MEDIUM', 'HIGH'].find((q) => active.some((p) => p.confidence === q))
351
+ : 'NO DATA';
352
+ const groups = new Map ();
353
+ for (const r of selected) {
354
+ const key = r.provider + ':' + (r.model_raw ?? 'Unknown model');
355
+ let rows = groups.get(key);
356
+ if (!rows) {
357
+ rows = [];
358
+ groups.set(key, rows);
359
+ }
360
+ rows.push(r);
361
+ }
362
+ const models = [...groups.values()].map((rows) => {
363
+ const first = rows[0],
364
+ p = prices.get(first.id) ;
365
+ const priced = rows.filter((r) => prices.get(r.id) .usd !== null),
366
+ known = rows.filter((r) => r.total_tokens !== null);
367
+ const issues = {};
368
+ for (const r of rows) {
369
+ const reason = prices.get(r.id) .reason;
370
+ if (reason) issues[reason] = (issues[reason] ?? 0) + 1;
371
+ }
372
+ return {
373
+ provider: first.provider,
374
+ raw: first.model_raw ?? 'Unknown model',
375
+ canonical: p.canonical,
376
+ records: rows.length,
377
+ pricedRecords: priced.length,
378
+ tokens: known.length ? known.reduce((n, r) => n + r.total_tokens , 0) : null,
379
+ pricedTokens: priced.some((r) => r.total_tokens !== null)
380
+ ? priced.reduce((n, r) => n + (r.total_tokens ?? 0), 0)
381
+ : null,
382
+ knownSubtotal: priced.length ? priced.reduce((n, r) => n + prices.get(r.id) .usd , 0) : null,
383
+ mappingConfidence: p.mapping?.confidence ?? null,
384
+ mappingSource: p.mapping?.source ?? null,
385
+ issues,
386
+ };
46
387
  });
47
- const active=providers.filter(p=>p.records>0),pricedRecords=active.reduce((n,p)=>n+p.pricedRecords,0),count=selected.length;const known=active.filter(p=>p.knownSubtotal!==null);const knownSubtotal=known.length?known.reduce((n,p)=>n+p.knownSubtotal ,0):null;const apiEquivalent=count>0&&pricedRecords===count?knownSubtotal:null;
48
- const subs=active.filter(p=>p.mode==='SUBSCRIPTION');let comparison=neutral(active.some(p=>p.mode==='API'||p.mode==='MIXED'||p.mode==='UNKNOWN')?'Subscription comparison unavailable':'Partial history');
49
- if(subs.length&&subs.length===active.length&&subs.every(p=>p.comparison.value!==null)){const subscription=subs.reduce((n,p)=>n+p.comparison.subscription ,0);const value=apiEquivalent -subscription;let accumulated=0,breakEven =null;if(demo)for(const d of daily){accumulated+=(d.codex??0)+(d.claude??0);if(accumulated>=subscription){breakEven=d.date;break;}}comparison={subscription,value,roi:subscription>0?apiEquivalent /subscription:null,breakEven,outcome:demo?(value>0?'positive':value<0?'negative':'neutral'):'neutral',reason:demo?null:'Observed usage · Partial history'};}
50
- else if(subs.length&&subs.length===active.length&&subs.every(p=>p.comparison.subscription!==null))comparison.subscription=subs.reduce((n,p)=>n+p.comparison.subscription ,0);
51
- const confidence=active.length?['INCOMPLETE','LOW','MEDIUM','HIGH'].find(q=>active.some(p=>p.confidence===q)) :'NO DATA';
52
- const groups=new Map ();for(const r of selected){const key=r.provider+':'+(r.model_raw??'Unknown model');let rows=groups.get(key);if(!rows){rows=[];groups.set(key,rows);}rows.push(r);}
53
- const models=[...groups.values()].map(rows=>{const first=rows[0],p=prices.get(first.id) ;const priced=rows.filter(r=>prices.get(r.id) .usd!==null),known=rows.filter(r=>r.total_tokens!==null);const issues ={};for(const r of rows){const reason=prices.get(r.id) .reason;if(reason)issues[reason]=(issues[reason]??0)+1;}return {provider:first.provider,raw:first.model_raw??'Unknown model',canonical:p.canonical,records:rows.length,pricedRecords:priced.length,tokens:known.length?known.reduce((n,r)=>n+r.total_tokens ,0):null,pricedTokens:priced.some(r=>r.total_tokens!==null)?priced.reduce((n,r)=>n+(r.total_tokens??0),0):null,knownSubtotal:priced.length?priced.reduce((n,r)=>n+prices.get(r.id) .usd ,0):null,mappingConfidence:p.mapping?.confidence??null,mappingSource:p.mapping?.source??null,issues};});
54
- return {range,providers,total:{apiEquivalent,knownSubtotal,records:count,pricedRecords,priceCoverage:count?pricedRecords/count:null,comparison},daily,confidence,historyPartial:!demo,unresolvedModels:[...new Set(selected.filter(r=>!prices.get(r.id) .canonical).map(r=>r.model_raw??'Unknown model'))],models,pricingVersion:c.version,demo};
388
+ return {
389
+ range,
390
+ providers,
391
+ total: {
392
+ apiEquivalent,
393
+ knownSubtotal,
394
+ records: count,
395
+ pricedRecords,
396
+ priceCoverage: count ? pricedRecords / count : null,
397
+ comparison,
398
+ },
399
+ daily,
400
+ confidence,
401
+ historyPartial: !demo,
402
+ unresolvedModels: [
403
+ ...new Set(
404
+ selected
405
+ .filter((r) => !prices.get(r.id) .canonical)
406
+ .map((r) => r.model_raw ?? 'Unknown model'),
407
+ ),
408
+ ],
409
+ models,
410
+ pricingVersion: c.version,
411
+ demo,
412
+ };
55
413
  }
@@ -2,29 +2,83 @@
2
2
 
3
3
 
4
4
 
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
14
32
 
15
33
 
16
34
 
17
-
18
-
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
19
45
 
20
46
 
21
-
22
-
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
23
56
 
24
57
 
25
-
58
+
59
+
60
+
26
61
 
27
-
62
+
28
63
 
29
- export const defaultSettings = () => ({onboarded:false,currency:'USD',include:{codex:false,claude:false},billing:{codex:{mode:'UNKNOWN',monthly:null},claude:{mode:'UNKNOWN',monthly:null}}});
30
- export const emptyDiagnostics = () => ({malformed:0,inherited:0,repeated:0,resets:0,oversize:0,unsupported:0,partialTail:false,first:null,last:null,records:0});
64
+ export const defaultSettings = () => ({
65
+ onboarded: false,
66
+ currency: 'USD',
67
+ include: { codex: false, claude: false },
68
+ billing: {
69
+ codex: { mode: 'UNKNOWN', monthly: null },
70
+ claude: { mode: 'UNKNOWN', monthly: null },
71
+ },
72
+ });
73
+ export const emptyDiagnostics = () => ({
74
+ malformed: 0,
75
+ inherited: 0,
76
+ repeated: 0,
77
+ resets: 0,
78
+ oversize: 0,
79
+ unsupported: 0,
80
+ partialTail: false,
81
+ first: null,
82
+ last: null,
83
+ records: 0,
84
+ });