diff --git a/packages/stats/app/src/routes/api/health.ts b/packages/stats/app/src/routes/api/health.ts
index 81e60f82e..eac4abb89 100644
--- a/packages/stats/app/src/routes/api/health.ts
+++ b/packages/stats/app/src/routes/api/health.ts
@@ -1,19 +1,3 @@
-import { AppConfig } from "@opencode-ai/stats-core/config"
-import { Effect } from "effect"
-import { runStatsEffect } from "../../stats-runtime"
-
export async function GET() {
- return Response.json(
- await runStatsEffect(
- Effect.gen(function* () {
- const config = yield* AppConfig
- return {
- ok: true,
- app: "stats",
- stage: config.stage,
- publicUrl: config.publicUrl,
- }
- }),
- ),
- )
+ return Response.json({ ok: true, app: "stats" })
}
diff --git a/packages/stats/app/src/stats-runtime.ts b/packages/stats/app/src/stats-runtime.ts
index 15beb678b..0fd2da5fd 100644
--- a/packages/stats/app/src/stats-runtime.ts
+++ b/packages/stats/app/src/stats-runtime.ts
@@ -1,16 +1,5 @@
-import { AppConfig } from "@opencode-ai/stats-core/config"
-import { layer } from "@opencode-ai/stats-core/database"
-import { GeoStatRepo } from "@opencode-ai/stats-core/domain/geo"
-import { ModelStatRepo } from "@opencode-ai/stats-core/domain/model"
-import { ProviderStatRepo } from "@opencode-ai/stats-core/domain/provider"
-import { Effect, Layer } from "effect"
-import type { Success } from "effect/Layer"
+import { Effect } from "effect"
-const repoLayer = Layer.mergeAll(ModelStatRepo.layer, ProviderStatRepo.layer, GeoStatRepo.layer).pipe(
- Layer.provide(layer),
-)
-const statsLayer = Layer.mergeAll(AppConfig.layer, layer, repoLayer)
-
-export function runStatsEffect(effect: Effect.Effect>) {
- return Effect.runPromise(Effect.provide(effect, statsLayer))
+export function runStatsEffect(effect: Effect.Effect) {
+ return Effect.runPromise(effect)
}
diff --git a/packages/stats/core/src/domain/home.ts b/packages/stats/core/src/domain/home.ts
index 83a242d6b..c8c4d3e13 100644
--- a/packages/stats/core/src/domain/home.ts
+++ b/packages/stats/core/src/domain/home.ts
@@ -1,8 +1,9 @@
+import { Client } from "@planetscale/database"
import { Effect } from "effect"
-import { DatabaseError } from "../database"
-import { GeoStatRepo, type GeoStatMetric } from "./geo"
-import { ModelStatRepo, type ModelStatMetric } from "./model"
-import { ProviderStatRepo, type ProviderStatMetric } from "./provider"
+import { Resource } from "sst/resource"
+import type { GeoStatMetric } from "./geo"
+import type { ModelStatMetric } from "./model"
+import type { ProviderStatMetric } from "./provider"
export type UsageProduct = "All Users" | "Zen" | "Go" | "Enterprise"
export type TokenProduct = "Zen" | "Go" | "Enterprise"
@@ -91,6 +92,14 @@ export type StatsHomeData = {
country: Record
}
+export class StatsDataError extends Error {
+ override name = "StatsDataError"
+
+ constructor(readonly cause: unknown) {
+ super("Failed to load stats data")
+ }
+}
+
const DAY_MS = 86_400_000
const TOKEN_SCALE = 1_000_000
const DOLLARS_PER_MICROCENT = 1 / 100_000_000
@@ -130,53 +139,129 @@ type ModelAggregate = {
totalCostMicrocents: number
}
-export function getStatsHomeData(): Effect.Effect<
- StatsHomeData,
- DatabaseError,
- ModelStatRepo | ProviderStatRepo | GeoStatRepo
-> {
- return Effect.gen(function* () {
- const modelStats = yield* ModelStatRepo
- const providerStats = yield* ProviderStatRepo
- const geoStats = yield* GeoStatRepo
- const [modelRows, providerRows, geoRows] = yield* Effect.all(
- [modelStats.listDaily(), providerStats.listDaily(), geoStats.listDaily()],
- { concurrency: "unbounded" },
- )
- return buildStatsHomeData(modelRows, providerRows, geoRows)
+type RawRow = Record
+
+export function getStatsHomeData(): Effect.Effect {
+ return Effect.tryPromise({
+ try: async () => {
+ const [modelRows, providerRows, geoRows] = await Promise.all([listModelDaily(), listProviderDaily(), listGeoDaily()])
+ return buildStatsHomeData(modelRows, providerRows, geoRows)
+ },
+ catch: (cause) => new StatsDataError(cause),
})
}
export function getStatsModelData(
model: string,
provider?: string,
-): Effect.Effect {
- return Effect.gen(function* () {
- const modelStats = yield* ModelStatRepo
- const geoStats = yield* GeoStatRepo
- const modelRows = yield* modelStats.listDaily()
- const normalized = modelRows.flatMap(normalizeStatRow)
- const resolvedModel = resolveModelName(model, normalized, provider)
- if (!resolvedModel) return null
- return buildStatsModelData(
- resolvedModel,
- modelRows,
- yield* geoStats.listDaily({
- model: resolvedModel,
- provider: resolveModelProvider(resolvedModel, normalized, provider),
- }),
- provider,
- )
+): Effect.Effect {
+ return Effect.tryPromise({
+ try: async () => {
+ const modelRows = await listModelDaily()
+ const normalized = modelRows.flatMap(normalizeStatRow)
+ const resolvedModel = resolveModelName(model, normalized, provider)
+ if (!resolvedModel) return null
+ return buildStatsModelData(
+ resolvedModel,
+ modelRows,
+ await listGeoDaily({
+ model: resolvedModel,
+ provider: resolveModelProvider(resolvedModel, normalized, provider),
+ }),
+ provider,
+ )
+ },
+ catch: (cause) => new StatsDataError(cause),
})
}
-export function getStatsLabData(provider: string): Effect.Effect {
- return Effect.gen(function* () {
- const modelStats = yield* ModelStatRepo
- return buildStatsLabData(provider, yield* modelStats.listDaily())
+export function getStatsLabData(provider: string): Effect.Effect {
+ return Effect.tryPromise({
+ try: async () => buildStatsLabData(provider, await listModelDaily()),
+ catch: (cause) => new StatsDataError(cause),
})
}
+async function listModelDaily(): Promise {
+ return (await queryRows(`select period_key, updated_at, tier, provider, model, sessions, unique_users, input_tokens,
+ output_tokens, reasoning_tokens, cache_read_tokens, total_tokens, input_cost_microcents, output_cost_microcents,
+ total_cost_microcents from model_stat where grain = 'day' and client = 'all' and source = 'all'
+ and tier in ('Go', 'go') order by period_key`)).map((row) => ({
+ periodKey: stringValue(row.period_key),
+ updatedAt: dateValue(row.updated_at),
+ tier: stringValue(row.tier),
+ provider: stringValue(row.provider),
+ model: stringValue(row.model),
+ sessions: numberValue(row.sessions),
+ uniqueUsers: numberValue(row.unique_users),
+ inputTokens: numberValue(row.input_tokens),
+ outputTokens: numberValue(row.output_tokens),
+ reasoningTokens: numberValue(row.reasoning_tokens),
+ cacheReadTokens: numberValue(row.cache_read_tokens),
+ totalTokens: numberValue(row.total_tokens),
+ inputCostMicrocents: numberValue(row.input_cost_microcents),
+ outputCostMicrocents: numberValue(row.output_cost_microcents),
+ totalCostMicrocents: numberValue(row.total_cost_microcents),
+ }))
+}
+
+async function listProviderDaily(): Promise {
+ return (await queryRows(`select period_key, updated_at, tier, provider, total_tokens from provider_stat
+ where grain = 'day' and client = 'all' and source = 'all' and tier in ('Go', 'go') order by period_key`)).map(
+ (row) => ({
+ periodKey: stringValue(row.period_key),
+ updatedAt: dateValue(row.updated_at),
+ tier: stringValue(row.tier),
+ provider: stringValue(row.provider),
+ totalTokens: numberValue(row.total_tokens),
+ }),
+ )
+}
+
+async function listGeoDaily(opts?: { provider?: string; model?: string }): Promise {
+ const scope =
+ opts?.model && opts.provider
+ ? "and provider = ? and model = ?"
+ : opts?.model
+ ? "and model = ?"
+ : "and provider = 'all' and model = 'all'"
+ const params = opts?.model && opts.provider ? [opts.provider, opts.model] : opts?.model ? [opts.model] : []
+ return (await queryRows(
+ `select period_key, updated_at, tier, provider, model, country, continent, total_tokens from geo_stat
+ where grain = 'day' and client = 'all' and source = 'all' and tier in ('Go', 'go') ${scope} order by period_key`,
+ params,
+ )).map((row) => ({
+ periodKey: stringValue(row.period_key),
+ updatedAt: dateValue(row.updated_at),
+ tier: stringValue(row.tier),
+ provider: stringValue(row.provider),
+ model: stringValue(row.model),
+ country: stringValue(row.country),
+ continent: stringValue(row.continent),
+ totalTokens: numberValue(row.total_tokens),
+ }))
+}
+
+async function queryRows(query: string, params: string[] = []) {
+ return (await new Client({ url: databaseUrl() }).execute(query, params)).rows as RawRow[]
+}
+
+function databaseUrl() {
+ return process.env.DATABASE_URL ?? Resource.StatsDatabase.url
+}
+
+function stringValue(value: unknown) {
+ return value == null ? "" : String(value)
+}
+
+function numberValue(value: unknown) {
+ return Number(value ?? 0)
+}
+
+function dateValue(value: unknown) {
+ return value instanceof Date ? value : new Date(stringValue(value))
+}
+
function buildStatsHomeData(
modelRows: ModelStatMetric[],
providerRows: ProviderStatMetric[],