fix(ops): gemini-block i summary-byggaren, inte routen

This commit is contained in:
Sven (AAMOS AI)
2026-08-12 22:41:11 +07:00
parent 7dc7cd1403
commit 10ba64e4ba
3 changed files with 69 additions and 52 deletions
-50
View File
@@ -1,8 +1,6 @@
import { timingSafeEqual } from "node:crypto"; import { timingSafeEqual } from "node:crypto";
import { gte, sql } from "drizzle-orm";
import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import BRAND from "../../../../brand.config.json" with { type: "json" }; import BRAND from "../../../../brand.config.json" with { type: "json" };
import { geminiUsage } from "@app/database";
import { errors } from "../lib/errors.js"; import { errors } from "../lib/errors.js";
const CACHE_KEY = "ops:summary:cache"; const CACHE_KEY = "ops:summary:cache";
@@ -63,54 +61,6 @@ export async function opsRoutes(app: FastifyInstance) {
} }
const enriched = structuredClone(summary); 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<number>`count(*)`,
promptTokens: sql<number>`coalesce(sum(${geminiUsage.promptTokens}), 0)`,
outputTokens: sql<number>`coalesce(sum(${geminiUsage.outputTokens}), 0)`,
totalTokens: sql<number>`coalesce(sum(${geminiUsage.totalTokens}), 0)`,
costMicrocents: sql<number>`coalesce(sum(${geminiUsage.costMicrocents}), 0)`,
})
.from(geminiUsage)
.where(gte(geminiUsage.createdAt, since24h)),
app.db
.select({
totalTokens: sql<number>`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 ( if (
enriched.wall && enriched.wall &&
+57 -2
View File
@@ -1,4 +1,4 @@
import { sql } from "drizzle-orm"; import { gte, sql } from "drizzle-orm";
import type { import type {
OpsAppBlock, OpsAppBlock,
OpsActivationBlock, OpsActivationBlock,
@@ -17,10 +17,12 @@ import type {
OpsWallTile, OpsWallTile,
OpsAlarm, OpsAlarm,
OpsAlarmBlock, OpsAlarmBlock,
OpsGeminiBlock,
} from "@app/shared-types"; } from "@app/shared-types";
import { SUBSCRIPTION_PLANS, type SubscriptionPlan } from "@app/shared-types"; import { SUBSCRIPTION_PLANS, type SubscriptionPlan } from "@app/shared-types";
import type { Database } from "./client.js"; import type { Database } from "./client.js";
import { schema } from "./index.js"; import { schema } from "./index.js";
import { geminiUsage } from "./schema/index.js";
const BUDGET_KEY_PREFIX = "gemini:daily:budget:"; const BUDGET_KEY_PREFIX = "gemini:daily:budget:";
@@ -652,6 +654,57 @@ async function feedbackBlock(db: Database): Promise<OpsFeedbackBlock> {
}; };
} }
async function geminiBlock(db: Database): Promise<OpsGeminiBlock> {
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<number>`count(*)`,
promptTokens: sql<number>`coalesce(sum(${geminiUsage.promptTokens}), 0)`,
outputTokens: sql<number>`coalesce(sum(${geminiUsage.outputTokens}), 0)`,
totalTokens: sql<number>`coalesce(sum(${geminiUsage.totalTokens}), 0)`,
costMicrocents: sql<number>`coalesce(sum(${geminiUsage.costMicrocents}), 0)`,
})
.from(geminiUsage)
.where(gte(geminiUsage.createdAt, since24h)),
db
.select({
totalTokens: sql<number>`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 { function alarmBlock(budgetUsd: number, dailySpendUsd: number | null | undefined): OpsAlarmBlock {
const alarms: OpsAlarm[] = []; const alarms: OpsAlarm[] = [];
if (budgetUsd > 0 && dailySpendUsd != null && dailySpendUsd >= budgetUsd) { 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<OpsSummary> { export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Promise<OpsSummary> {
const { db, budgetUsd, dailySpendUsd, queueSummary, planPrices: planPricesOverride } = options; const { db, budgetUsd, dailySpendUsd, queueSummary, planPrices: planPricesOverride } = options;
const planPrices = { ...getPlanPrices(), ...(planPricesOverride ?? {}) }; 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([ await Promise.all([
Promise.resolve(appBlock()), Promise.resolve(appBlock()),
economyBlock(db, planPrices), economyBlock(db, planPrices),
@@ -683,6 +736,7 @@ export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Prom
paymentBlock(db), paymentBlock(db),
safetyBlock(db), safetyBlock(db),
Promise.resolve(alarmBlock(budgetUsd, dailySpendUsd)), Promise.resolve(alarmBlock(budgetUsd, dailySpendUsd)),
geminiBlock(db),
]); ]);
const base: OpsSummary = { const base: OpsSummary = {
@@ -700,6 +754,7 @@ export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Prom
sakerhet: safety, sakerhet: safety,
larm, larm,
wall: { boards: [] }, wall: { boards: [] },
gemini,
as_of: new Date().toISOString(), as_of: new Date().toISOString(),
}; };
+12
View File
@@ -127,6 +127,17 @@ export interface OpsAlarmBlock {
larm: OpsAlarm[]; 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 { export interface OpsSummary {
app: OpsAppBlock; app: OpsAppBlock;
ekonomi: OpsEconomyBlock; ekonomi: OpsEconomyBlock;
@@ -142,5 +153,6 @@ export interface OpsSummary {
sakerhet: OpsSafetyBlock; sakerhet: OpsSafetyBlock;
larm: OpsAlarmBlock; larm: OpsAlarmBlock;
wall: OpsWallBlock; wall: OpsWallBlock;
gemini: OpsGeminiBlock;
as_of: string; as_of: string;
} }