geo.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import { and, asc, eq } from "drizzle-orm"
  2. import { Effect, Layer } from "effect"
  3. import * as Context from "effect/Context"
  4. import { DatabaseError, DrizzleClient } from "../database"
  5. import { geoStat } from "../database/schema"
  6. import {
  7. chunks,
  8. collapseRows,
  9. inserted,
  10. rankRowsWithMarketShare,
  11. synthesizeAllTierRows,
  12. toStatBaseRow,
  13. UPSERT_CHUNK_SIZE,
  14. type StatBaseAggregate,
  15. } from "./stat"
  16. export type GeoStatRow = typeof geoStat.$inferInsert
  17. export type GeoStatAggregate = StatBaseAggregate & { country: string; continent: string }
  18. export type GeoStatMetric = {
  19. periodStart: Date
  20. periodEnd: Date
  21. tier: string
  22. country: string
  23. continent: string
  24. totalTokens: number
  25. }
  26. export declare namespace GeoStatRepo {
  27. export interface Service {
  28. readonly listDaily: () => Effect.Effect<GeoStatMetric[], DatabaseError>
  29. readonly listByPeriod: (opts: {
  30. readonly grain: string
  31. readonly periodStart: Date
  32. readonly dataset?: string
  33. readonly tier?: string
  34. readonly client?: string
  35. readonly source?: string
  36. }) => Effect.Effect<GeoStatRow[], DatabaseError>
  37. readonly upsert: (rows: GeoStatRow[]) => Effect.Effect<void, DatabaseError>
  38. }
  39. }
  40. export class GeoStatRepo extends Context.Service<GeoStatRepo, GeoStatRepo.Service>()("@opencode/stats/GeoStatRepo") {
  41. static readonly layer: Layer.Layer<GeoStatRepo, never, DrizzleClient> = Layer.effect(
  42. GeoStatRepo,
  43. Effect.gen(function* () {
  44. const db = yield* DrizzleClient
  45. const listDaily = Effect.fn("GeoStatRepo.listDaily")(function* () {
  46. return yield* Effect.tryPromise({
  47. try: () =>
  48. db
  49. .select({
  50. periodStart: geoStat.period_start,
  51. periodEnd: geoStat.period_end,
  52. tier: geoStat.tier,
  53. country: geoStat.country,
  54. continent: geoStat.continent,
  55. totalTokens: geoStat.total_tokens,
  56. })
  57. .from(geoStat)
  58. .where(and(eq(geoStat.grain, "day"), eq(geoStat.client, "all"), eq(geoStat.source, "all")))
  59. .orderBy(asc(geoStat.period_start)),
  60. catch: (cause) => DatabaseError.make({ cause }),
  61. })
  62. })
  63. const listByPeriod = Effect.fn("GeoStatRepo.listByPeriod")(function* (opts: {
  64. readonly grain: string
  65. readonly periodStart: Date
  66. readonly dataset?: string
  67. readonly tier?: string
  68. readonly client?: string
  69. readonly source?: string
  70. }) {
  71. return yield* Effect.tryPromise({
  72. try: () =>
  73. db
  74. .select()
  75. .from(geoStat)
  76. .where(
  77. and(
  78. eq(geoStat.grain, opts.grain),
  79. eq(geoStat.period_start, opts.periodStart),
  80. eq(geoStat.dataset, opts.dataset ?? "zen"),
  81. eq(geoStat.tier, opts.tier ?? "all"),
  82. eq(geoStat.client, opts.client ?? "all"),
  83. eq(geoStat.source, opts.source ?? "all"),
  84. ),
  85. ),
  86. catch: (cause) => DatabaseError.make({ cause }),
  87. })
  88. })
  89. const upsert = Effect.fn("GeoStatRepo.upsert")(function* (rows: GeoStatRow[]) {
  90. yield* Effect.forEach(
  91. chunks(rows, UPSERT_CHUNK_SIZE),
  92. (chunk) =>
  93. Effect.tryPromise({
  94. try: () =>
  95. db
  96. .insert(geoStat)
  97. .values(chunk)
  98. .onDuplicateKeyUpdate({
  99. set: {
  100. period_end: inserted("period_end"),
  101. continent: inserted("continent"),
  102. sessions: inserted("sessions"),
  103. requests: inserted("requests"),
  104. input_tokens: inserted("input_tokens"),
  105. output_tokens: inserted("output_tokens"),
  106. reasoning_tokens: inserted("reasoning_tokens"),
  107. cache_read_tokens: inserted("cache_read_tokens"),
  108. total_tokens: inserted("total_tokens"),
  109. input_cost_microcents: inserted("input_cost_microcents"),
  110. output_cost_microcents: inserted("output_cost_microcents"),
  111. total_cost_microcents: inserted("total_cost_microcents"),
  112. avg_duration_ms: inserted("avg_duration_ms"),
  113. p50_duration_ms: inserted("p50_duration_ms"),
  114. p95_duration_ms: inserted("p95_duration_ms"),
  115. avg_ttfb_ms: inserted("avg_ttfb_ms"),
  116. p50_ttfb_ms: inserted("p50_ttfb_ms"),
  117. p95_ttfb_ms: inserted("p95_ttfb_ms"),
  118. avg_output_tps: inserted("avg_output_tps"),
  119. success_count: inserted("success_count"),
  120. error_count: inserted("error_count"),
  121. sample_count: inserted("sample_count"),
  122. market_share_tokens: inserted("market_share_tokens"),
  123. market_share_requests: inserted("market_share_requests"),
  124. market_share_sessions: inserted("market_share_sessions"),
  125. rank_by_tokens: inserted("rank_by_tokens"),
  126. rank_by_requests: inserted("rank_by_requests"),
  127. rank_by_sessions: inserted("rank_by_sessions"),
  128. rank_by_cost: inserted("rank_by_cost"),
  129. },
  130. }),
  131. catch: (cause) => DatabaseError.make({ cause }),
  132. }),
  133. { discard: true },
  134. )
  135. })
  136. return GeoStatRepo.of({ listDaily, listByPeriod, upsert })
  137. }),
  138. )
  139. }
  140. export function rowsFromAggregates(aggregates: GeoStatAggregate[]) {
  141. return rankRowsWithMarketShare([
  142. ...synthesizeAllTierRows(
  143. collapseRows(aggregates.filter((item) => item.grain === "week").map(toRow), dimensionKey),
  144. dimensionKey,
  145. ),
  146. ...synthesizeAllTierRows(
  147. collapseRows(aggregates.filter((item) => item.grain === "day").map(toRow), dimensionKey),
  148. dimensionKey,
  149. ),
  150. ])
  151. }
  152. function toRow(data: GeoStatAggregate): GeoStatRow {
  153. return {
  154. ...toStatBaseRow(data),
  155. country: data.country,
  156. continent: data.continent,
  157. }
  158. }
  159. function dimensionKey(row: GeoStatRow) {
  160. return row.country
  161. }