@mzzsfy/dsh-usage-dash 0.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,427 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+
4
+ import {
5
+ MAX_SLOTS,
6
+ daysInRange,
7
+ hourKeysInRange,
8
+ minuteKeysInRange,
9
+ aggregateRange,
10
+ attachCosts,
11
+ } from '../src/query.js'
12
+
13
+ const makeRow = (overrides = {}) => ({
14
+ bucket: '2020-01-01',
15
+ provider: 'default',
16
+ model: 'deepseek-chat',
17
+ inputTokens: 0,
18
+ outputTokens: 0,
19
+ cacheReadTokens: 0,
20
+ cacheWriteTokens: 0,
21
+ requests: 0,
22
+ turns: 0,
23
+ lastSeen: 0,
24
+ ...overrides,
25
+ })
26
+
27
+ test('daysInRange 单日闭区间返回单槽', () => {
28
+ assert.deepEqual(daysInRange('2020-01-01', '2020-01-01'), ['2020-01-01'])
29
+ })
30
+
31
+ test('daysInRange 跨月含闰日连续枚举', () => {
32
+ assert.deepEqual(daysInRange('2020-02-27', '2020-03-01'), [
33
+ '2020-02-27',
34
+ '2020-02-28',
35
+ '2020-02-29',
36
+ '2020-03-01',
37
+ ])
38
+ })
39
+
40
+ test('daysInRange 倒序边界返回空数组', () => {
41
+ assert.deepEqual(daysInRange('2020-01-02', '2020-01-01'), [])
42
+ })
43
+
44
+ test('daysInRange 非法格式或不存在日期返回空数组', () => {
45
+ assert.deepEqual(daysInRange('2020-1-1', '2020-01-02'), [])
46
+ assert.deepEqual(daysInRange('2020-01-01', '2020-02-30'), [])
47
+ assert.deepEqual(daysInRange('2020-01-01T00', '2020-01-02'), [])
48
+ })
49
+
50
+ test('hourKeysInRange 跨日闭区间连续枚举', () => {
51
+ assert.deepEqual(hourKeysInRange('2020-01-01T22', '2020-01-02T02'), [
52
+ '2020-01-01T22',
53
+ '2020-01-01T23',
54
+ '2020-01-02T00',
55
+ '2020-01-02T01',
56
+ '2020-01-02T02',
57
+ ])
58
+ })
59
+
60
+ test('hourKeysInRange 倒序或非法格式返回空数组', () => {
61
+ assert.deepEqual(hourKeysInRange('2020-01-02T00', '2020-01-01T23'), [])
62
+ assert.deepEqual(hourKeysInRange('2020-01-01', '2020-01-02'), [])
63
+ assert.deepEqual(hourKeysInRange('2020-01-01T25', '2020-01-02T00'), [])
64
+ })
65
+
66
+ test('minuteKeysInRange 按 10 分钟步长跨时枚举', () => {
67
+ assert.deepEqual(minuteKeysInRange('2020-01-01T23:50', '2020-01-02T00:10'), [
68
+ '2020-01-01T23:50',
69
+ '2020-01-02T00:00',
70
+ '2020-01-02T00:10',
71
+ ])
72
+ })
73
+
74
+ test('minuteKeysInRange from 未对齐桶边界返回空数组', () => {
75
+ assert.deepEqual(minuteKeysInRange('2020-01-01T23:58', '2020-01-02T00:10'), [])
76
+ assert.deepEqual(minuteKeysInRange('2020-01-01T00:01', '2020-01-01T00:31'), [])
77
+ })
78
+
79
+ test('minuteKeysInRange 倒序或格式不匹配返回空数组', () => {
80
+ assert.deepEqual(minuteKeysInRange('2020-01-01T00:10', '2020-01-01T00:00'), [])
81
+ assert.deepEqual(minuteKeysInRange('2020-01-01T00', '2020-01-01T00:20'), [])
82
+ })
83
+
84
+ test('MAX_SLOTS 为文档定值', () => {
85
+ assert.equal(MAX_SLOTS, 2000)
86
+ })
87
+
88
+ test('D 粒度聚合总量排序与占比正确', () => {
89
+ const rows = [
90
+ makeRow({
91
+ model: 'deepseek/deepseek-chat',
92
+ provider: 'deepseek',
93
+ inputTokens: 100,
94
+ outputTokens: 50,
95
+ cacheReadTokens: 200,
96
+ requests: 1,
97
+ }),
98
+ makeRow({
99
+ model: 'deepseek-chat',
100
+ provider: 'default',
101
+ inputTokens: 10,
102
+ outputTokens: 20,
103
+ cacheWriteTokens: 30,
104
+ requests: 2,
105
+ turns: 1,
106
+ }),
107
+ ]
108
+ const out = aggregateRange(rows, 'D', '2020-01-01', '2020-01-01')
109
+ assert.equal(out.from, '2020-01-01')
110
+ assert.equal(out.to, '2020-01-01')
111
+ assert.equal(out.tokens, 100 + 50 + 200 + 10 + 20 + 30)
112
+ assert.equal(out.requests, 3)
113
+ assert.equal(out.turns, 1)
114
+ assert.equal(out.cacheHit, 200)
115
+ assert.equal(out.cacheMiss, 100 + 10 + 30)
116
+ assert.equal(out.activeDays, 1)
117
+ assert.equal(out.topModel, 'deepseek/deepseek-chat')
118
+ assert.equal(out.topProvider, 'deepseek')
119
+ assert.deepEqual(out.models, [
120
+ { model: 'deepseek/deepseek-chat', provider: 'deepseek', tokens: 350, percent: (350 / 410) * 100 },
121
+ { model: 'deepseek-chat', provider: 'default', tokens: 60, percent: (60 / 410) * 100 },
122
+ ])
123
+ assert.deepEqual(out.providers, [
124
+ { provider: 'deepseek', tokens: 350, percent: (350 / 410) * 100 },
125
+ { provider: 'default', tokens: 60, percent: (60 / 410) * 100 },
126
+ ])
127
+ assert.deepEqual(out.daily, [
128
+ {
129
+ day: '2020-01-01',
130
+ total: 410,
131
+ byModel: { 'deepseek/deepseek-chat': 350, 'deepseek-chat': 60 },
132
+ byProvider: { deepseek: 350, default: 60 },
133
+ requests: 3,
134
+ turns: 1,
135
+ cacheHit: 200,
136
+ cacheMiss: 140,
137
+ },
138
+ ])
139
+ assert.equal('truncated' in out, false)
140
+ })
141
+
142
+ test('H 粒度零值槽全枚举含无数据槽', () => {
143
+ const rows = [
144
+ makeRow({ bucket: '2020-01-01T01', model: 'm1', provider: 'p1', inputTokens: 5, outputTokens: 5 }),
145
+ ]
146
+ const out = aggregateRange(rows, 'H', '2020-01-01T00', '2020-01-01T02')
147
+ assert.deepEqual(out.daily.map((slot) => slot.day), [
148
+ '2020-01-01T00',
149
+ '2020-01-01T01',
150
+ '2020-01-01T02',
151
+ ])
152
+ assert.deepEqual(out.daily[0], {
153
+ day: '2020-01-01T00',
154
+ total: 0,
155
+ byModel: {},
156
+ byProvider: {},
157
+ requests: 0,
158
+ turns: 0,
159
+ cacheHit: 0,
160
+ cacheMiss: 0,
161
+ })
162
+ assert.equal(out.daily[1].total, 10)
163
+ assert.deepEqual(out.daily[1].byModel, { m1: 10 })
164
+ assert.deepEqual(out.daily[1].byProvider, { p1: 10 })
165
+ assert.equal(out.activeDays, 1)
166
+ })
167
+
168
+ test('M 粒度纯计数行不计入模型与活跃桶,未对齐残行跳过', () => {
169
+ const rows = [
170
+ makeRow({ bucket: '2020-01-01T00:00', model: '(turns)', provider: 'default', turns: 1 }),
171
+ makeRow({ bucket: '2020-01-01T00:00', model: 'm1', provider: 'p1', requests: 2 }),
172
+ makeRow({ bucket: '2020-01-01T00:07', model: 'm1', provider: 'p1', inputTokens: 9 }),
173
+ ]
174
+ const out = aggregateRange(rows, 'M', '2020-01-01T00:00', '2020-01-01T00:00')
175
+ assert.equal(out.tokens, 0)
176
+ assert.equal(out.requests, 2)
177
+ assert.equal(out.turns, 1)
178
+ assert.equal(out.activeDays, 0)
179
+ assert.deepEqual(out.models, [])
180
+ assert.deepEqual(out.providers, [])
181
+ assert.deepEqual(out.daily, [
182
+ {
183
+ day: '2020-01-01T00:00',
184
+ total: 0,
185
+ byModel: {},
186
+ byProvider: {},
187
+ requests: 2,
188
+ turns: 1,
189
+ cacheHit: 0,
190
+ cacheMiss: 0,
191
+ },
192
+ ])
193
+ assert.equal(out.tokens, 0)
194
+ })
195
+
196
+ test('空 rows 时 top 值为空串且 daily 全零槽', () => {
197
+ const out = aggregateRange([], 'D', '2020-01-01', '2020-01-03')
198
+ assert.equal(out.topModel, '')
199
+ assert.equal(out.topProvider, '')
200
+ assert.equal(out.activeDays, 0)
201
+ assert.equal(out.tokens, 0)
202
+ assert.deepEqual(out.daily.map((slot) => slot.day), ['2020-01-01', '2020-01-02', '2020-01-03'])
203
+ assert.deepEqual(out.models, [])
204
+ assert.deepEqual(out.providers, [])
205
+ assert.equal('truncated' in out, false)
206
+ })
207
+
208
+ test('D 粒度跨月边界 daily 序列完整', () => {
209
+ const rows = [makeRow({ bucket: '2020-02-01', inputTokens: 1, outputTokens: 1 })]
210
+ const out = aggregateRange(rows, 'D', '2020-01-30', '2020-02-02')
211
+ assert.deepEqual(out.daily.map((slot) => slot.day), [
212
+ '2020-01-30',
213
+ '2020-01-31',
214
+ '2020-02-01',
215
+ '2020-02-02',
216
+ ])
217
+ assert.equal(out.daily[2].total, 2)
218
+ assert.equal(out.daily[0].total, 0)
219
+ assert.equal(out.activeDays, 1)
220
+ })
221
+
222
+ test('命中率口径 cacheMiss 含缓存写不含输出', () => {
223
+ const rows = [
224
+ makeRow({ inputTokens: 100, outputTokens: 400, cacheReadTokens: 300, cacheWriteTokens: 50 }),
225
+ ]
226
+ const out = aggregateRange(rows, 'D', '2020-01-01', '2020-01-01')
227
+ assert.equal(out.cacheHit, 300)
228
+ assert.equal(out.cacheMiss, 100 + 50)
229
+ assert.equal(out.cacheHit / (out.cacheHit + out.cacheMiss), 300 / (300 + 150))
230
+ })
231
+
232
+ test('H 粒度槽数超过 MAX_SLOTS 保最新丢最旧并标记 truncated', () => {
233
+ const from = '2020-05-01T00'
234
+ const all = hourKeysInRange(from, '2020-12-31T23')
235
+ assert.ok(all.length > MAX_SLOTS)
236
+ const to = all[MAX_SLOTS]
237
+ const out = aggregateRange([], 'H', from, to)
238
+ assert.equal(out.truncated, true)
239
+ assert.equal(out.daily.length, MAX_SLOTS)
240
+ assert.equal(out.daily[0].day, all[1])
241
+ assert.equal(out.daily[out.daily.length - 1].day, all[MAX_SLOTS])
242
+ })
243
+
244
+ test('H 粒度槽数恰为 MAX_SLOTS 时不出现 truncated', () => {
245
+ const from = '2020-05-01T00'
246
+ const all = hourKeysInRange(from, '2020-12-31T23')
247
+ const to = all[MAX_SLOTS - 1]
248
+ const out = aggregateRange([], 'H', from, to)
249
+ assert.equal('truncated' in out, false)
250
+ assert.equal(out.daily.length, MAX_SLOTS)
251
+ assert.equal(out.daily[0].day, all[0])
252
+ assert.equal(out.daily[out.daily.length - 1].day, to)
253
+ assert.equal(out.from, from)
254
+ assert.equal(out.to, to)
255
+ })
256
+
257
+ // ---- attachCosts 聚合计价:计价唯一以 H 桶起点匹配,费用精度=小时级 ----
258
+
259
+ const ruleOf = (overrides = {}) => ({
260
+ model: '*',
261
+ currency: '¥',
262
+ price: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
263
+ conditions: [],
264
+ ...overrides,
265
+ })
266
+
267
+ const inputPrice = (input) => ({ input, output: 0, cacheRead: 0, cacheWrite: 0 })
268
+
269
+ test('attachCosts H 槽按桶起点计价并归集 totals 与 models', () => {
270
+ const rows = [
271
+ makeRow({ bucket: '2020-01-01T01', model: 'm1', provider: 'p1', inputTokens: 1000000, outputTokens: 500000 }),
272
+ ]
273
+ const result = aggregateRange(rows, 'H', '2020-01-01T00', '2020-01-01T02')
274
+ const out = attachCosts(result, rows, 'H', [ruleOf({ model: 'm1', price: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 } })])
275
+ assert.equal(out.daily[1].cost, 2)
276
+ assert.equal(out.cost, 2)
277
+ assert.equal(out.unpriced, 0)
278
+ assert.deepEqual(out.models, [{ model: 'm1', provider: 'p1', tokens: 1500000, percent: 100, cost: 2 }])
279
+ assert.equal(out.from, '2020-01-01T00')
280
+ assert.equal(out.tokens, 1500000)
281
+ // 纯函数:入参 result 不被修改
282
+ assert.equal('cost' in result.daily[1], false)
283
+ assert.equal('cost' in result, false)
284
+ })
285
+
286
+ test('attachCosts 多行同桶多规则累加且残缺规则跳过后回落通配', () => {
287
+ const rows = [
288
+ makeRow({ bucket: '2020-01-01T01', model: 'm1', inputTokens: 1000000 }),
289
+ makeRow({ bucket: '2020-01-01T01', model: 'm2', inputTokens: 1000000 }),
290
+ ]
291
+ const rules = [
292
+ ruleOf({ model: 'm1', price: inputPrice(1) }),
293
+ { model: 'm2' },
294
+ ruleOf({ price: inputPrice(3) }),
295
+ ]
296
+ const result = aggregateRange(rows, 'H', '2020-01-01T01', '2020-01-01T01')
297
+ const out = attachCosts(result, rows, 'H', rules)
298
+ assert.equal(out.daily[0].cost, 4)
299
+ assert.deepEqual(out.models.map((entry) => entry.cost), [1, 3])
300
+ assert.equal(out.unpriced, 0)
301
+ })
302
+
303
+ test('attachCosts 无规则时 cost 恒零且 unpriced 按有 token 的 H 桶去重', () => {
304
+ const rows = [
305
+ makeRow({ bucket: '2020-01-01T01', model: 'm1', inputTokens: 5 }),
306
+ makeRow({ bucket: '2020-01-01T01', model: 'm2', inputTokens: 5 }),
307
+ makeRow({ bucket: '2020-01-01T02', model: 'm1', outputTokens: 5 }),
308
+ makeRow({ bucket: '2020-01-01T02', model: 'm1', requests: 1 }),
309
+ makeRow({ bucket: '2020-01-01T00', model: 'm1', turns: 1 }),
310
+ ]
311
+ for (const rules of [[], undefined]) {
312
+ const result = aggregateRange(rows, 'H', '2020-01-01T00', '2020-01-01T02')
313
+ const out = attachCosts(result, rows, 'H', rules)
314
+ assert.equal(out.cost, 0)
315
+ assert.equal(out.unpriced, 2)
316
+ assert.deepEqual(out.daily.map((slot) => slot.cost), [0, 0, 0])
317
+ assert.deepEqual(out.models.map((entry) => entry.cost), [0, 0])
318
+ }
319
+ })
320
+
321
+ test('attachCosts M 行按父 H 桶起点匹配价格且全桶计入分钟槽', () => {
322
+ const rows = [
323
+ makeRow({ bucket: '2020-01-01T01:10', model: 'm1', inputTokens: 500000 }),
324
+ makeRow({ bucket: '2020-01-01T01:20', model: 'm1', inputTokens: 500000 }),
325
+ ]
326
+ const result = aggregateRange(rows, 'M', '2020-01-01T01:10', '2020-01-01T01:20')
327
+ const out = attachCosts(result, rows, 'M', [ruleOf({ price: inputPrice(1) })])
328
+ assert.deepEqual(out.daily.map((slot) => slot.cost), [0.5, 0.5])
329
+ assert.equal(out.cost, 1)
330
+ assert.equal(out.unpriced, 0)
331
+ assert.equal(out.models[0].cost, 1)
332
+ })
333
+
334
+ test('attachCosts M 行 unpriced 按父 H 桶去重', () => {
335
+ const rows = [
336
+ makeRow({ bucket: '2020-01-01T01:10', model: 'm1', inputTokens: 5 }),
337
+ makeRow({ bucket: '2020-01-01T01:20', model: 'm1', inputTokens: 5 }),
338
+ makeRow({ bucket: '2020-01-01T02:00', model: 'm1', inputTokens: 5 }),
339
+ ]
340
+ const result = aggregateRange(rows, 'M', '2020-01-01T01:10', '2020-01-01T02:00')
341
+ const out = attachCosts(result, rows, 'M', [])
342
+ assert.equal(out.unpriced, 2)
343
+ })
344
+
345
+ test('attachCosts D 端点按 H 行桶起点计价折叠到日槽', () => {
346
+ const costRows = [
347
+ makeRow({ bucket: '2020-01-01T22', model: 'm1', inputTokens: 1000000 }),
348
+ makeRow({ bucket: '2020-01-01T23', model: 'm1', inputTokens: 1000000 }),
349
+ makeRow({ bucket: '2020-01-02T00', model: 'm1', inputTokens: 1000000 }),
350
+ ]
351
+ const aggregateRows = [
352
+ makeRow({ bucket: '2020-01-01', model: 'm1', inputTokens: 2000000 }),
353
+ makeRow({ bucket: '2020-01-02', model: 'm1', inputTokens: 1000000 }),
354
+ ]
355
+ const rules = [
356
+ ruleOf({ price: inputPrice(1), conditions: [{ kind: 'dateRange', from: '2020-01-01', to: '2020-01-01' }] }),
357
+ ruleOf({ price: inputPrice(3), conditions: [{ kind: 'dateRange', from: '2020-01-02', to: '2020-01-02' }] }),
358
+ ]
359
+ const result = aggregateRange(aggregateRows, 'D', '2020-01-01', '2020-01-02')
360
+ const out = attachCosts(result, costRows, 'D', rules)
361
+ assert.equal(out.daily[0].cost, 2)
362
+ assert.equal(out.daily[1].cost, 3)
363
+ assert.equal(out.cost, 5)
364
+ assert.equal(out.models[0].cost, 5)
365
+ assert.equal(out.unpriced, 0)
366
+ })
367
+
368
+ test('attachCosts D 端点 unpriced 按 H 行桶去重而非日', () => {
369
+ const rows = [
370
+ makeRow({ bucket: '2020-01-01T22', inputTokens: 5 }),
371
+ makeRow({ bucket: '2020-01-01T23', inputTokens: 5 }),
372
+ makeRow({ bucket: '2020-01-02T00', inputTokens: 5 }),
373
+ ]
374
+ const result = aggregateRange(rows, 'D', '2020-01-01', '2020-01-02')
375
+ const out = attachCosts(result, rows, 'D', [])
376
+ assert.equal(out.unpriced, 3)
377
+ })
378
+
379
+ test('attachCosts 截断后被丢槽的行整体不参与计价', () => {
380
+ const from = '2020-01-01'
381
+ const to = '2025-06-23'
382
+ const dropped = makeRow({ bucket: '2020-01-01T05', model: 'm1', inputTokens: 1000000 })
383
+ const kept = makeRow({ bucket: '2025-06-23T07', model: 'm1', inputTokens: 1000000 })
384
+ const aggregateRows = [makeRow({ bucket: '2025-06-23', model: 'm1', inputTokens: 1000000 })]
385
+ const result = aggregateRange(aggregateRows, 'D', from, to)
386
+ assert.equal(result.truncated, true)
387
+ const out = attachCosts(result, [dropped, kept], 'D', [ruleOf({ price: inputPrice(2) })])
388
+ assert.equal(out.cost, 2)
389
+ assert.deepEqual(out.daily.filter((slot) => slot.cost !== 0).map((slot) => slot.day), ['2025-06-23'])
390
+ assert.deepEqual(out.models.map((entry) => entry.cost), [2])
391
+ assert.equal(out.unpriced, 0)
392
+ const unpricedOnly = attachCosts(result, [dropped, kept], 'D', [])
393
+ assert.equal(unpricedOnly.unpriced, 1)
394
+ })
395
+
396
+ test('attachCosts 空串模型行的费用跟随 models 空串条目', () => {
397
+ const rows = [makeRow({ bucket: '2020-01-01T01', model: '', inputTokens: 1000000 })]
398
+ const result = aggregateRange(rows, 'H', '2020-01-01T01', '2020-01-01T01')
399
+ const out = attachCosts(result, rows, 'H', [ruleOf({ price: inputPrice(1) })])
400
+ assert.equal(out.cost, 1)
401
+ assert.deepEqual(out.models.map((entry) => [entry.model, entry.cost]), [['', 1]])
402
+ })
403
+
404
+ test('attachCosts 计价时间恒为 H 桶起点(非分钟/日起点)', () => {
405
+ // M 判别:窗口 01:05~01:55 下父 H 01:00 出窗回落价 3,按分钟起点判则
406
+ // 01:10/01:20 均在窗走价 1
407
+ const minuteWindow = ruleOf({ price: inputPrice(1), conditions: [{ kind: 'dailyWindow', from: '01:05', to: '01:55' }] })
408
+ const minuteRows = [
409
+ makeRow({ bucket: '2020-01-01T01:10', model: 'm1', inputTokens: 500000 }),
410
+ makeRow({ bucket: '2020-01-01T01:20', model: 'm1', inputTokens: 500000 }),
411
+ ]
412
+ const minuteResult = aggregateRange(minuteRows, 'M', '2020-01-01T01:10', '2020-01-01T01:20')
413
+ const minuteOut = attachCosts(minuteResult, minuteRows, 'M', [minuteWindow, ruleOf({ price: inputPrice(3) })])
414
+ assert.deepEqual(minuteOut.daily.map((slot) => slot.cost), [1.5, 1.5])
415
+ // D 判别:窗口 00:05~23:55 含 H 23:00 不含日起点 00:00,按日起点判则两日均出窗
416
+ const dayWindow = ruleOf({ price: inputPrice(1), conditions: [{ kind: 'dailyWindow', from: '00:05', to: '23:55' }] })
417
+ const hourRows = [
418
+ makeRow({ bucket: '2020-01-01T23', model: 'm1', inputTokens: 1000000 }),
419
+ makeRow({ bucket: '2020-01-02T00', model: 'm1', inputTokens: 1000000 }),
420
+ ]
421
+ const dayResult = aggregateRange([
422
+ makeRow({ bucket: '2020-01-01', model: 'm1', inputTokens: 1000000 }),
423
+ makeRow({ bucket: '2020-01-02', model: 'm1', inputTokens: 1000000 }),
424
+ ], 'D', '2020-01-01', '2020-01-02')
425
+ const dayOut = attachCosts(dayResult, hourRows, 'D', [dayWindow, ruleOf({ price: inputPrice(3) })])
426
+ assert.deepEqual(dayOut.daily.map((slot) => slot.cost), [1, 3])
427
+ })