import type { Pool, PoolClient } from 'pg'; import type { AnalyticsEventInput } from '../../../shared/analytics/events.js'; import type { AnalyticsMetricBucket } from '../../../shared/types.js'; type DbClient = Pool | PoolClient; type AnalyticsBucketRow = { key: string; count: string; }; export async function insertAnalyticsEvent(db: DbClient, input: AnalyticsEventInput) { await db.query( ` insert into public.analytics_events ( event_name, event_source, user_id, workspace_id, plan_code, addon_code, resource, amount, currency, metadata_json, occurred_at ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, coalesce($11::timestamptz, now())) `, [ input.eventName, input.eventSource, input.userId ?? null, input.workspaceId ?? null, input.planCode ?? null, input.addonCode ?? null, input.resource ?? null, input.amount ?? null, input.currency ?? null, input.metadata ?? {}, input.occurredAt ?? null, ], ); } export function listAnalyticsCountsByEvent(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, 'event_name', `event_name`); } export function listPricingPlanSelectionCounts(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, `coalesce(plan_code, metadata_json->>'planCode', 'unknown')`, `event_name = 'pricing_plan_selected'`); } export function listQuotaExhaustionCounts(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, `coalesce(resource, metadata_json->>'resource', 'unknown')`, `event_name = 'quota_exhausted_blocked'`); } export function listUpgradeTriggerCounts(db: DbClient, sinceIso: string): Promise { return listBuckets( db, sinceIso, `coalesce(metadata_json->>'denialReason', event_name)`, `event_name in ('quota_exhausted_blocked', 'feature_gate_encountered')`, ); } export function listAddonAttachCounts(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, `coalesce(addon_code, metadata_json->>'addonCode', 'unknown')`, `event_name = 'addon_purchase_completed'`); } export function listPlanMixCounts(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, `coalesce(plan_code, metadata_json->>'planCode', 'unknown')`, `event_name in ('checkout_completed', 'plan_changed')`); } export function listChurnSignalCounts(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, `event_name`, `event_name in ('subscription_canceled', 'payment_failed')`); } export function listExpansionSignalCounts(db: DbClient, sinceIso: string): Promise { return listBuckets(db, sinceIso, `event_name`, `event_name in ('checkout_completed', 'addon_purchase_completed', 'plan_changed')`); } async function listBuckets( db: DbClient, sinceIso: string, keyExpression: string, filterSql: string, ): Promise { const result = await db.query( ` select ${keyExpression} as key, count(*)::text as count from public.analytics_events where occurred_at >= $1 and ${filterSql} group by 1 order by count(*) desc, 1 asc `, [sinceIso], ); return result.rows.map((row) => ({ key: row.key, count: Number(row.count), })); }