diff --git a/apps/api/src/routes/ops.ts b/apps/api/src/routes/ops.ts index 072ab87..c9d5189 100644 --- a/apps/api/src/routes/ops.ts +++ b/apps/api/src/routes/ops.ts @@ -1,8 +1,6 @@ import { timingSafeEqual } from "node:crypto"; -import { gte, sql } from "drizzle-orm"; import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import BRAND from "../../../../brand.config.json" with { type: "json" }; -import { geminiUsage } from "@app/database"; import { errors } from "../lib/errors.js"; const CACHE_KEY = "ops:summary:cache"; @@ -63,54 +61,6 @@ export async function opsRoutes(app: FastifyInstance) { } const enriched = structuredClone(summary); - const geminiDefaults = { - anrop_24h: null, - prompt_tokens_24h: null, - output_tokens_24h: null, - total_tokens_24h: null, - total_tokens_manad: null, - kostnad_usd_mikrocent_24h: null, - andel_pa_aamos: 0.0, - aamos_agreement: null, - }; - - try { - const now = new Date(); - const since24h = new Date(now.getTime() - 24 * 60 * 60 * 1000); - const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); - - const [dayAgg, monthAgg] = await Promise.all([ - app.db - .select({ - count: sql`count(*)`, - promptTokens: sql`coalesce(sum(${geminiUsage.promptTokens}), 0)`, - outputTokens: sql`coalesce(sum(${geminiUsage.outputTokens}), 0)`, - totalTokens: sql`coalesce(sum(${geminiUsage.totalTokens}), 0)`, - costMicrocents: sql`coalesce(sum(${geminiUsage.costMicrocents}), 0)`, - }) - .from(geminiUsage) - .where(gte(geminiUsage.createdAt, since24h)), - app.db - .select({ - totalTokens: sql`coalesce(sum(${geminiUsage.totalTokens}), 0)`, - }) - .from(geminiUsage) - .where(gte(geminiUsage.createdAt, startOfMonth)), - ]); - - enriched.gemini = { - anrop_24h: Number(dayAgg[0]?.count ?? 0), - prompt_tokens_24h: Number(dayAgg[0]?.promptTokens ?? 0), - output_tokens_24h: Number(dayAgg[0]?.outputTokens ?? 0), - total_tokens_24h: Number(dayAgg[0]?.totalTokens ?? 0), - total_tokens_manad: Number(monthAgg[0]?.totalTokens ?? 0), - kostnad_usd_mikrocent_24h: Number(dayAgg[0]?.costMicrocents ?? 0), - andel_pa_aamos: 0.0, - aamos_agreement: null, - }; - } catch { - enriched.gemini = geminiDefaults; - } if ( enriched.wall && diff --git a/packages/database/src/ops-summary.ts b/packages/database/src/ops-summary.ts index 7c16ea9..c1547f4 100644 --- a/packages/database/src/ops-summary.ts +++ b/packages/database/src/ops-summary.ts @@ -1,4 +1,4 @@ -import { sql } from "drizzle-orm"; +import { gte, sql } from "drizzle-orm"; import type { OpsAppBlock, OpsActivationBlock, @@ -17,10 +17,12 @@ import type { OpsWallTile, OpsAlarm, OpsAlarmBlock, + OpsGeminiBlock, } from "@app/shared-types"; import { SUBSCRIPTION_PLANS, type SubscriptionPlan } from "@app/shared-types"; import type { Database } from "./client.js"; import { schema } from "./index.js"; +import { geminiUsage } from "./schema/index.js"; const BUDGET_KEY_PREFIX = "gemini:daily:budget:"; @@ -652,6 +654,57 @@ async function feedbackBlock(db: Database): Promise { }; } +async function geminiBlock(db: Database): Promise { + const defaults: OpsGeminiBlock = { + anrop_24h: null, + prompt_tokens_24h: null, + output_tokens_24h: null, + total_tokens_24h: null, + total_tokens_manad: null, + kostnad_usd_mikrocent_24h: null, + andel_pa_aamos: 0.0, + aamos_agreement: null, + }; + + try { + const now = new Date(); + const since24h = new Date(now.getTime() - 24 * 60 * 60 * 1000); + const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + + const [dayAgg, monthAgg] = await Promise.all([ + db + .select({ + count: sql`count(*)`, + promptTokens: sql`coalesce(sum(${geminiUsage.promptTokens}), 0)`, + outputTokens: sql`coalesce(sum(${geminiUsage.outputTokens}), 0)`, + totalTokens: sql`coalesce(sum(${geminiUsage.totalTokens}), 0)`, + costMicrocents: sql`coalesce(sum(${geminiUsage.costMicrocents}), 0)`, + }) + .from(geminiUsage) + .where(gte(geminiUsage.createdAt, since24h)), + db + .select({ + totalTokens: sql`coalesce(sum(${geminiUsage.totalTokens}), 0)`, + }) + .from(geminiUsage) + .where(gte(geminiUsage.createdAt, startOfMonth)), + ]); + + return { + anrop_24h: Number(dayAgg[0]?.count ?? 0), + prompt_tokens_24h: Number(dayAgg[0]?.promptTokens ?? 0), + output_tokens_24h: Number(dayAgg[0]?.outputTokens ?? 0), + total_tokens_24h: Number(dayAgg[0]?.totalTokens ?? 0), + total_tokens_manad: Number(monthAgg[0]?.totalTokens ?? 0), + kostnad_usd_mikrocent_24h: Number(dayAgg[0]?.costMicrocents ?? 0), + andel_pa_aamos: 0.0, + aamos_agreement: null, + }; + } catch { + return defaults; + } +} + function alarmBlock(budgetUsd: number, dailySpendUsd: number | null | undefined): OpsAlarmBlock { const alarms: OpsAlarm[] = []; if (budgetUsd > 0 && dailySpendUsd != null && dailySpendUsd >= budgetUsd) { @@ -669,7 +722,7 @@ function alarmBlock(budgetUsd: number, dailySpendUsd: number | null | undefined) export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Promise { const { db, budgetUsd, dailySpendUsd, queueSummary, planPrices: planPricesOverride } = options; const planPrices = { ...getPlanPrices(), ...(planPricesOverride ?? {}) }; - const [app, economy, users, subscriptions, butik, feedbackData, ai, activation, engagement, payment, safety, larm] = + const [app, economy, users, subscriptions, butik, feedbackData, ai, activation, engagement, payment, safety, larm, gemini] = await Promise.all([ Promise.resolve(appBlock()), economyBlock(db, planPrices), @@ -683,6 +736,7 @@ export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Prom paymentBlock(db), safetyBlock(db), Promise.resolve(alarmBlock(budgetUsd, dailySpendUsd)), + geminiBlock(db), ]); const base: OpsSummary = { @@ -700,6 +754,7 @@ export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Prom sakerhet: safety, larm, wall: { boards: [] }, + gemini, as_of: new Date().toISOString(), }; diff --git a/packages/shared-types/src/ops.ts b/packages/shared-types/src/ops.ts index cdab7c0..a14a516 100644 --- a/packages/shared-types/src/ops.ts +++ b/packages/shared-types/src/ops.ts @@ -127,6 +127,17 @@ export interface OpsAlarmBlock { larm: OpsAlarm[]; } +export interface OpsGeminiBlock { + anrop_24h: number | null; + prompt_tokens_24h: number | null; + output_tokens_24h: number | null; + total_tokens_24h: number | null; + total_tokens_manad: number | null; + kostnad_usd_mikrocent_24h: number | null; + andel_pa_aamos: number; + aamos_agreement: string | null; +} + export interface OpsSummary { app: OpsAppBlock; ekonomi: OpsEconomyBlock; @@ -142,5 +153,6 @@ export interface OpsSummary { sakerhet: OpsSafetyBlock; larm: OpsAlarmBlock; wall: OpsWallBlock; + gemini: OpsGeminiBlock; as_of: string; }