dsh-all-usage 1.1.3 → 1.1.5

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.
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto'
2
- import { COST_SCHEMA_VERSION, addCostAccumulator, addCostAggregate, calculateCost, createCostAccumulator, decimalSubtract, isCostAccumulator, normalizeCostSnapshot, resolvePricing, serializeCostAggregate } from './pricing.js'
2
+ import { COST_SCHEMA_VERSION, addCostAccumulator, addCostAggregate, calculateCost, createCostAccumulator, decimalSubtract, isCostAccumulator, normalizeCostSnapshot, resolvePricing, serializeCostAggregate, temporalPlanFor } from './pricing.js'
3
3
  import { extractUsageEvent, normalizeEventSeq, normalizeUsageValues as usageValues, upsertUsageSample as upsertUsageSampleState, usageStepKey, validEventTime as validUsageEventTime } from './usage-core.js'
4
4
 
5
5
  export function createAggregation(host) {
@@ -75,14 +75,23 @@ export function createAggregation(host) {
75
75
  while (state.pricingResolutionCache.size > 5000) state.pricingResolutionCache.delete(state.pricingResolutionCache.keys().next().value)
76
76
  return resolved
77
77
  }
78
- function costForUsage(values, identity, previous) {
78
+ function costForUsage(values, identity, previous, pricingAt, pricingTimeSource) {
79
79
  const previousCost = normalizeCostSnapshot(previous && previous.cost)
80
- if (previousCost !== null && previousCost.pricingMode === 'official-model' && usageBasisEqual(previous, identity, values)) return previousCost
81
- return calculateCost(values, resolveCurrentPricing(identity))
80
+ if (previousCost !== null && previousCost.pricingMode === 'official-model' && previousCost.pricingTimeSource !== 'legacy-unknown' && usageBasisEqual(previous, identity, values) && costTemporalCompatible(previousCost, identity, pricingAt, pricingTimeSource)) return previousCost
81
+ return calculateCost(values, resolveCurrentPricing(identity), pricingAt, pricingTimeSource)
82
+ }
83
+ function costTemporalCompatible(previousCost, identity, pricingAt, pricingTimeSource) {
84
+ if (previousCost.pricingAt === null || previousCost.pricingTimeSource === null) return false
85
+ const resolved = resolveCurrentPricing(identity)
86
+ const plan = temporalPlanFor(resolved, pricingAt)
87
+ const normalizedAt = Number.isFinite(pricingAt) && pricingAt >= 0 ? Math.trunc(pricingAt) : null
88
+ const normalizedSource = pricingTimeSource === 'request-context' || pricingTimeSource === 'usage-event' ? pricingTimeSource : 'usage-event'
89
+ return previousCost.pricingBand === plan.band && previousCost.pricingPolicyId === plan.policyId && previousCost.pricingPolicyHash === plan.policyHash && previousCost.pricingTimezone === plan.timezone && previousCost.temporalApplicable === (plan.status === 'applied') && previousCost.temporalExemptReason === plan.exemptReason && previousCost.pricingAt === normalizedAt && previousCost.pricingTimeSource === normalizedSource
82
90
  }
83
91
  function resetAggregationState() {
84
92
  state.aggregationGeneration += 1
85
93
  state.wsMeta.clear()
94
+ if (state.sessionContextTimes) state.sessionContextTimes.clear()
86
95
  state.pathIndex.clear()
87
96
  state.memberOf.clear()
88
97
  state.byDay.clear()
@@ -343,10 +352,35 @@ export function createAggregation(host) {
343
352
  adjustDay(state.byDay, dates.local, wsId, values, normalized, direction, sid, cost)
344
353
  adjustDay(state.byDayUtc, dates.utc, wsId, values, normalized, direction, sid, cost)
345
354
  }
355
+ function removeSession(sid) {
356
+ if (typeof sid !== 'string' || sid === '') return 0
357
+ let removed = 0
358
+ const usageKeys = []
359
+ for (const [key, item] of state.usageByStep) if (item.sid === sid) usageKeys.push([key, item])
360
+ for (const [key, item] of usageKeys) {
361
+ unindexUsage(item)
362
+ adjustUsage(item.wsId, item.time, item.values, item.identity || item.modelId, -1, sid, { local: item.date, utc: item.dateUtc }, item.cost)
363
+ adjustQueryUsage(state.byDay.get(item.date), item, -1, false)
364
+ adjustQueryUsage(state.byDayUtc.get(item.dateUtc), item, -1, true)
365
+ state.usageByStep.delete(key)
366
+ removed += 1
367
+ }
368
+ const turnKeys = []
369
+ for (const [key, turn] of state.turnRecords) if (turn.sid === sid) turnKeys.push([key, turn])
370
+ for (const [key, turn] of turnKeys) {
371
+ removeTurnRecord(turn)
372
+ state.turnRecords.delete(key)
373
+ removed += 1
374
+ }
375
+ state.sessionContextTimes.delete(sid)
376
+ state.sessionModel.delete(sid)
377
+ if (removed > 0) markStatsChanged('data')
378
+ return removed
379
+ }
346
380
  function upsertUsageSample(target, sample, options = {}) {
347
381
  const previous = target.get(sample.key)
348
382
  const candidate = sample.cost === undefined
349
- ? { ...sample, cost: costForUsage(sample.values, sample.identity, options.costPrevious === undefined ? previous : options.costPrevious) }
383
+ ? { ...sample, cost: costForUsage(sample.values, sample.identity, options.costPrevious === undefined ? previous : options.costPrevious, sample.pricingAt, sample.pricingTimeSource) }
350
384
  : sample
351
385
  const result = upsertUsageSampleState(target, candidate)
352
386
  if (!result.accepted || target !== state.usageByStep || options.materialize === false) return result
@@ -383,7 +417,7 @@ export function createAggregation(host) {
383
417
  update(state.byDay.get(dates.local), false)
384
418
  update(state.byDayUtc.get(dates.utc), true)
385
419
  }
386
- function addUsage(wsId, time, usage, model, sid, data, seq, materialization = 'live') {
420
+ function addUsage(wsId, time, usage, model, sid, data, seq, materialization = 'live', pricingAt, pricingTimeSource) {
387
421
  if (!validEventTime(time)) return
388
422
  const values = usageValues(usage)
389
423
  if (values === null || noValues(values)) return
@@ -405,13 +439,26 @@ export function createAggregation(host) {
405
439
  step: data && Number.isSafeInteger(data.step) && data.step >= 0 ? data.step : null,
406
440
  materialization,
407
441
  sid,
442
+ ...(pricingAt === undefined ? {} : { pricingAt }),
443
+ ...(pricingTimeSource === undefined ? {} : { pricingTimeSource }),
408
444
  })
409
445
  }
410
- function addDayTurn(dayMap, date, wsId, sid) {
446
+ function addDayTurn(dayMap, date, wsId, sid, direction = 1) {
411
447
  const day = ensureDay(dayMap, date)
412
- adjustDaySession(day, sid, 1)
413
- day.turns += 1
414
- day.perWs.set(wsId, (day.perWs.get(wsId) || 0) + 1)
448
+ adjustDaySession(day, sid, direction)
449
+ day.turns = Math.max(0, day.turns + direction)
450
+ const next = (day.perWs.get(wsId) || 0) + direction
451
+ if (next > 0) day.perWs.set(wsId, next)
452
+ else day.perWs.delete(wsId)
453
+ }
454
+ function removeTurnRecord(turn) {
455
+ const ws = state.perWorkspace.get(turn.wsId)
456
+ if (ws !== undefined) ws.turns = Math.max(0, ws.turns - 1)
457
+ state.totals.turns = Math.max(0, state.totals.turns - 1)
458
+ addDayTurn(state.byDay, turn.date, turn.wsId, turn.sid, -1)
459
+ addDayTurn(state.byDayUtc, turn.dateUtc, turn.wsId, turn.sid, -1)
460
+ adjustQueryTurn(state.byDay.get(turn.date), turn, -1, false)
461
+ adjustQueryTurn(state.byDayUtc.get(turn.dateUtc), turn, -1, true)
415
462
  }
416
463
  function turnRecordKey(sid, turn, seq, time) {
417
464
  if (Number.isSafeInteger(turn)) return sid + ':turn:' + turn
@@ -935,6 +982,7 @@ export function createAggregation(host) {
935
982
  usageBasisEqual,
936
983
  resolveCurrentPricing,
937
984
  costForUsage,
985
+ removeSession,
938
986
  resetAggregationState,
939
987
  ensureDay,
940
988
  ensureWs,