File size: 10,347 Bytes
4bbfe8b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import { sql } from "drizzle-orm"

export const UPSERT_CHUNK_SIZE = 500
export const DATA_SITE_TIERS = ["Go", "go", "Free", "free"]
const DAY_MS = 86_400_000

export type StatGrain = "day" | "week"

export type StatBaseAggregate = {
  grain: StatGrain
  period_key: string
  dataset: string
  tier: string
  sessions: number
  requests: number
  unique_users: number
  input_tokens: number
  output_tokens: number
  reasoning_tokens: number
  cache_read_tokens: number
  total_tokens: number
  input_cost_microcents: number
  output_cost_microcents: number
  total_cost_microcents: number
  avg_duration_ms: number | null
  p50_duration_ms: number | null
  p95_duration_ms: number | null
  avg_ttfb_ms: number | null
  p50_ttfb_ms: number | null
  p95_ttfb_ms: number | null
  avg_output_tps: number | null
  success_count: number
  error_count: number
  sample_count: number
}

export type StatBaseRow = {
  grain: string
  period_key: string
  dataset?: string
  tier?: string
  client?: string
  source?: string
  sessions?: number
  requests?: number
  unique_users?: number
  input_tokens?: number
  output_tokens?: number
  reasoning_tokens?: number
  cache_read_tokens?: number
  total_tokens?: number
  input_cost_microcents?: number
  output_cost_microcents?: number
  total_cost_microcents?: number
  avg_duration_ms?: number | null
  p50_duration_ms?: number | null
  p95_duration_ms?: number | null
  avg_ttfb_ms?: number | null
  p50_ttfb_ms?: number | null
  p95_ttfb_ms?: number | null
  avg_output_tps?: number | null
  success_count?: number
  error_count?: number
  sample_count?: number
}

export function toStatBaseRow(data: StatBaseAggregate) {
  return {
    grain: data.grain,
    period_key: data.period_key,
    dataset: data.dataset,
    tier: data.tier,
    client: "all",
    source: "all",
    sessions: data.sessions,
    requests: data.requests,
    unique_users: data.unique_users,
    input_tokens: data.input_tokens,
    output_tokens: data.output_tokens,
    reasoning_tokens: data.reasoning_tokens,
    cache_read_tokens: data.cache_read_tokens,
    total_tokens: data.total_tokens,
    input_cost_microcents: data.input_cost_microcents,
    output_cost_microcents: data.output_cost_microcents,
    total_cost_microcents: data.total_cost_microcents,
    avg_duration_ms: data.avg_duration_ms,
    p50_duration_ms: data.p50_duration_ms,
    p95_duration_ms: data.p95_duration_ms,
    avg_ttfb_ms: data.avg_ttfb_ms,
    p50_ttfb_ms: data.p50_ttfb_ms,
    p95_ttfb_ms: data.p95_ttfb_ms,
    avg_output_tps: data.avg_output_tps,
    success_count: data.success_count,
    error_count: data.error_count,
    sample_count: data.sample_count,
  }
}

export function synthesizeAllTierRows<T extends StatBaseRow>(rows: T[], dimensionKey: (row: T) => string) {
  return [
    ...rows,
    ...Object.values(
      rows.reduce<Record<string, T>>((result, row) => {
        const key = [row.grain, row.period_key, row.dataset, row.client, row.source, dimensionKey(row)].join("\u0000")
        result[key] = result[key] ? combineRows(result[key], row) : { ...row, tier: "all" }
        return result
      }, {}),
    ),
  ]
}

export function collapseRows<T extends StatBaseRow>(rows: T[], dimensionKey: (row: T) => string) {
  return Object.values(
    rows.reduce<Record<string, T>>((result, row) => {
      const key = [row.grain, row.period_key, row.dataset, row.tier, row.client, row.source, dimensionKey(row)].join(
        "\u0000",
      )
      result[key] = result[key] ? combineRows(result[key], row) : row
      return result
    }, {}),
  )
}

export function combineRows<T extends StatBaseRow>(left: T, right: T): T {
  return {
    ...left,
    sessions: (left.sessions ?? 0) + (right.sessions ?? 0),
    requests: (left.requests ?? 0) + (right.requests ?? 0),
    unique_users: (left.unique_users ?? 0) + (right.unique_users ?? 0),
    input_tokens: (left.input_tokens ?? 0) + (right.input_tokens ?? 0),
    output_tokens: (left.output_tokens ?? 0) + (right.output_tokens ?? 0),
    reasoning_tokens: (left.reasoning_tokens ?? 0) + (right.reasoning_tokens ?? 0),
    cache_read_tokens: (left.cache_read_tokens ?? 0) + (right.cache_read_tokens ?? 0),
    total_tokens: (left.total_tokens ?? 0) + (right.total_tokens ?? 0),
    input_cost_microcents: (left.input_cost_microcents ?? 0) + (right.input_cost_microcents ?? 0),
    output_cost_microcents: (left.output_cost_microcents ?? 0) + (right.output_cost_microcents ?? 0),
    total_cost_microcents: (left.total_cost_microcents ?? 0) + (right.total_cost_microcents ?? 0),
    avg_duration_ms: weightedAverage(left.avg_duration_ms, left.requests, right.avg_duration_ms, right.requests),
    p50_duration_ms: null,
    p95_duration_ms: null,
    avg_ttfb_ms: weightedAverage(left.avg_ttfb_ms, left.requests, right.avg_ttfb_ms, right.requests),
    p50_ttfb_ms: null,
    p95_ttfb_ms: null,
    avg_output_tps: weightedAverage(left.avg_output_tps, left.requests, right.avg_output_tps, right.requests),
    success_count: (left.success_count ?? 0) + (right.success_count ?? 0),
    error_count: (left.error_count ?? 0) + (right.error_count ?? 0),
    sample_count: (left.sample_count ?? 0) + (right.sample_count ?? 0),
  }
}

export function isMissingUniqueUsersColumn(cause: unknown): boolean {
  return errorText(cause).includes("Unknown column 'unique_users'")
}

export function omitUniqueUsers<T extends { unique_users?: number }>(rows: T[]) {
  return rows.map((row) => {
    const result = { ...row }
    delete result.unique_users
    return result
  })
}

export function statPeriodKey(row: StatBaseRow) {
  return [row.grain, row.period_key, row.dataset, row.tier, row.client, row.source].join("\u0000")
}

export function statRowScope(rows: StatBaseRow[]) {
  if (rows.length === 0) return
  return {
    grains: unique(rows.map((row) => row.grain)),
    periodKeys: unique(rows.map((row) => row.period_key)),
    datasets: unique(rows.map((row) => row.dataset ?? "all")),
    clients: unique(rows.map((row) => row.client ?? "all")),
    sources: unique(rows.map((row) => row.source ?? "all")),
  }
}

export function periodKeyFor(grain: StatGrain, periodStart: Date) {
  if (grain === "week") return isoWeekId(periodStart)
  return utcDateId(periodStart)
}

export function startOfUtcDay(value: Date) {
  return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))
}

export function startOfIsoWeek(value: Date) {
  return new Date(
    Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate() - (value.getUTCDay() || 7) + 1),
  )
}

export function isoWeekId(value: Date) {
  const thursday = new Date(
    Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate() + 4 - (value.getUTCDay() || 7)),
  )
  return `${thursday.getUTCFullYear()}-W${String(Math.ceil(((thursday.getTime() - Date.UTC(thursday.getUTCFullYear(), 0, 1)) / DAY_MS + 1) / 7)).padStart(2, "0")}`
}

function utcDateId(value: Date) {
  return `${value.getUTCFullYear()}-${String(value.getUTCMonth() + 1).padStart(2, "0")}-${String(value.getUTCDate()).padStart(2, "0")}`
}

export function rankBy<T extends StatBaseRow>(rows: T[], value: (row: T) => number) {
  return new Map(rows.toSorted((a, b) => value(b) - value(a)).map((row, index) => [row, index + 1]))
}

export function rankRowsWithMarketShare<T extends StatBaseRow>(
  rows: T[],
  groupKey: (row: T) => string = statPeriodKey,
) {
  return Object.values(
    rows.reduce<Record<string, T[]>>((result, row) => {
      const key = groupKey(row)
      result[key] = [...(result[key] ?? []), row]
      return result
    }, {}),
  ).flatMap((group) => {
    const tokens = group.reduce((sum, row) => sum + (row.total_tokens ?? 0), 0)
    const requests = group.reduce((sum, row) => sum + (row.requests ?? 0), 0)
    const sessions = group.reduce((sum, row) => sum + (row.sessions ?? 0), 0)
    const tokenRanks = rankBy(group, (row) => row.total_tokens ?? 0)
    const requestRanks = rankBy(group, (row) => row.requests ?? 0)
    const sessionRanks = rankBy(group, (row) => row.sessions ?? 0)
    const costRanks = rankBy(group, (row) => row.total_cost_microcents ?? 0)
    return group.map((row) => ({
      ...row,
      market_share_tokens: share(row.total_tokens, tokens),
      market_share_requests: share(row.requests, requests),
      market_share_sessions: share(row.sessions, sessions),
      rank_by_tokens: tokenRanks.get(row) ?? null,
      rank_by_requests: requestRanks.get(row) ?? null,
      rank_by_sessions: sessionRanks.get(row) ?? null,
      rank_by_cost: costRanks.get(row) ?? null,
    }))
  })
}

export function share(value: number | null | undefined, total: number) {
  if (total <= 0) return null
  return Number(((value ?? 0) / total).toFixed(6))
}

export function chunks<T>(items: T[], size: number) {
  return Array.from({ length: Math.ceil(items.length / size) }, (_, index) =>
    items.slice(index * size, (index + 1) * size),
  )
}

function unique(values: string[]) {
  return [...new Set(values)]
}

export function inserted(column: string) {
  return sql.raw(`values(\`${column}\`)`)
}

function errorText(cause: unknown): string {
  if (cause instanceof Error) return `${cause.message} ${errorText((cause as { cause?: unknown }).cause)}`
  if (typeof cause === "object" && cause)
    return Object.values(cause as Record<string, unknown>)
      .map(errorText)
      .join(" ")
  return String(cause)
}

export function weightedAverage(
  left: number | null | undefined,
  leftWeight = 0,
  right: number | null | undefined,
  rightWeight = 0,
) {
  const totalWeight =
    (left === null || left === undefined ? 0 : leftWeight) + (right === null || right === undefined ? 0 : rightWeight)
  if (totalWeight === 0) return null
  return Number((((left ?? 0) * leftWeight + (right ?? 0) * rightWeight) / totalWeight).toFixed(2))
}

export function normalizeTier(value: string) {
  const normalized = value.toLowerCase()
  if (normalized === "paid" || normalized === "zen") return "Zen"
  if (normalized === "go") return "Go"
  if (normalized === "free") return "Free"
  if (normalized === "enterprise") return "Enterprise"
  if (normalized === "all") return "all"
  return value
}

export function normalizeCountry(value: string | undefined) {
  if (!value || value.length !== 2) return "ZZ"
  return value.toUpperCase()
}