@coursebuilder/analytics 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/providers/database.ts","../../src/providers/ga4.ts","../../src/providers/derived.ts","../../src/providers/mux.ts","../../src/providers/survey.ts"],"sourcesContent":["import {\n\tand,\n\tcount,\n\tdesc,\n\teq,\n\tgt,\n\tgte,\n\tinArray,\n\tlte,\n\tsql,\n\tsum,\n} from 'drizzle-orm'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnalyticsTimeRange = '24h' | '7d' | '30d' | '90d' | 'all'\n\nexport interface AttributionTrailEvent {\n\ttype: 'click' | 'signup' | 'progress' | 'purchase'\n\ttimestamp: Date\n\tdetail: Record<string, any>\n}\n\nexport interface AttributionTrail {\n\tuser: {\n\t\tid: string\n\t\temail: string | null\n\t\tname: string | null\n\t\tcreatedAt: Date\n\t} | null\n\tevents: AttributionTrailEvent[]\n\tpurchases: {\n\t\tid: string\n\t\ttotalAmount: number\n\t\tproductName: string\n\t\tcreatedAt: Date\n\t\tcountry: string | null\n\t\tutmSource: string | null\n\t\tutmMedium: string | null\n\t\tutmCampaign: string | null\n\t}[]\n}\n\n// ─── Schema type ─────────────────────────────────────────────────────────────\n\nexport interface DatabaseAnalyticsSchema {\n\tpurchases: any\n\tproducts: any\n\tusers: any\n\tcoupon: any\n\tresourceProgress: any\n\tshortlink: any\n\tshortlinkAttribution: any\n\tshortlinkClick: any\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a database analytics provider that wraps all analytics query\n * functions with an injected drizzle db instance and schema tables.\n *\n * @param db - Drizzle database instance\n * @param schema - Object containing the required table references\n */\nexport function createDatabaseProvider(\n\tdb: any,\n\tschema: DatabaseAnalyticsSchema,\n) {\n\tconst {\n\t\tpurchases,\n\t\tproducts,\n\t\tusers,\n\t\tcoupon,\n\t\tresourceProgress,\n\t\tshortlink,\n\t\tshortlinkAttribution,\n\t\tshortlinkClick,\n\t} = schema\n\n\t// ─── Internal helpers ───────────────────────────────────────────────────\n\n\tconst PAID_STATUSES = ['Valid', 'Restricted'] as const\n\n\tfunction paidPurchase() {\n\t\treturn inArray(purchases.status, [...PAID_STATUSES])\n\t}\n\n\tfunction rangeToDate(range: AnalyticsTimeRange): Date | null {\n\t\tif (range === 'all') return null\n\t\tconst now = new Date()\n\t\tconst hours: Record<string, number> = {\n\t\t\t'24h': 24,\n\t\t\t'7d': 7 * 24,\n\t\t\t'30d': 30 * 24,\n\t\t\t'90d': 90 * 24,\n\t\t}\n\t\treturn new Date(now.getTime() - (hours[range] ?? 30 * 24) * 60 * 60 * 1000)\n\t}\n\n\t// ─── Revenue ───────────────────────────────────────────────────────────\n\n\tasync function getRevenueSummary(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst [totals] = await db\n\t\t\t.select({\n\t\t\t\ttotalRevenue: sum(purchases.totalAmount),\n\t\t\t\tpurchaseCount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\n\t\treturn {\n\t\t\ttotalRevenue: Number(totals?.totalRevenue ?? 0),\n\t\t\tpurchaseCount: totals?.purchaseCount ?? 0,\n\t\t\tavgOrderValue:\n\t\t\t\ttotals?.purchaseCount && totals.purchaseCount > 0\n\t\t\t\t\t? Number(totals.totalRevenue ?? 0) / totals.purchaseCount\n\t\t\t\t\t: 0,\n\t\t}\n\t}\n\n\tasync function getRevenueByDay(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tdate: sql<string>`DATE(${purchases.createdAt})`.as('date'),\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(sql`DATE(${purchases.createdAt})`)\n\t\t\t.orderBy(sql`DATE(${purchases.createdAt})`)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tdate: r.date,\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\t/**\n\t * Revenue by day for the previous period of equal length.\n\t * E.g., if range = '30d', returns the 30 days before those 30 days.\n\t * Returns data with a `dayOffset` (0 = start of period) for overlay\n\t * alignment.\n\t */\n\tasync function getPreviousPeriodRevenueByDay(\n\t\trange: AnalyticsTimeRange = '30d',\n\t) {\n\t\tif (range === 'all') return []\n\n\t\tconst hours: Record<string, number> = {\n\t\t\t'24h': 24,\n\t\t\t'7d': 7 * 24,\n\t\t\t'30d': 30 * 24,\n\t\t\t'90d': 90 * 24,\n\t\t}\n\t\tconst periodMs = (hours[range] ?? 30 * 24) * 60 * 60 * 1000\n\t\tconst now = new Date()\n\t\tconst periodStart = new Date(now.getTime() - periodMs)\n\t\tconst prevStart = new Date(periodStart.getTime() - periodMs)\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tdate: sql<string>`DATE(${purchases.createdAt})`.as('date'),\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\tpaidPurchase(),\n\t\t\t\t\tgte(purchases.createdAt, prevStart),\n\t\t\t\t\tlte(purchases.createdAt, periodStart),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.groupBy(sql`DATE(${purchases.createdAt})`)\n\t\t\t.orderBy(sql`DATE(${purchases.createdAt})`)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tdate: r.date,\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getRevenueByProduct(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tproductId: purchases.productId,\n\t\t\t\tproductName: products.name,\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(purchases.productId, products.name)\n\t\t\t.orderBy(desc(sum(purchases.totalAmount)))\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tproductId: r.productId,\n\t\t\tproductName: r.productName ?? '(unknown)',\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getRevenueByCountry(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tcountry: purchases.country,\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(purchases.country)\n\t\t\t.orderBy(desc(sum(purchases.totalAmount)))\n\t\t\t.limit(20)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tcountry: r.country ?? '(unknown)',\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getRecentPurchases(\n\t\tlimit: number = 20,\n\t\tfilter: 'all' | 'team' | 'individual' = 'all',\n\t\trange: AnalyticsTimeRange = 'all',\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tif (filter === 'team') {\n\t\t\t// Multi-seat purchases: join coupon to filter seats > 1, sort by amount\n\t\t\tconditions.push(sql`${purchases.bulkCouponId} IS NOT NULL`)\n\n\t\t\tconst rows = await db\n\t\t\t\t.select({\n\t\t\t\t\tid: purchases.id,\n\t\t\t\t\tcreatedAt: purchases.createdAt,\n\t\t\t\t\ttotalAmount: purchases.totalAmount,\n\t\t\t\t\tproductName: products.name,\n\t\t\t\t\tproductId: purchases.productId,\n\t\t\t\t\tcountry: purchases.country,\n\t\t\t\t\tcouponId: purchases.couponId,\n\t\t\t\t\tuserId: purchases.userId,\n\t\t\t\t\tuserName: users.name,\n\t\t\t\t\tuserEmail: users.email,\n\t\t\t\t\torganizationId: purchases.organizationId,\n\t\t\t\t\tseats: coupon.maxUses,\n\t\t\t\t})\n\t\t\t\t.from(purchases)\n\t\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t\t.leftJoin(users, eq(purchases.userId, users.id))\n\t\t\t\t.leftJoin(coupon, eq(purchases.bulkCouponId, coupon.id))\n\t\t\t\t.where(and(...conditions, gt(coupon.maxUses, 1)))\n\t\t\t\t.orderBy(desc(purchases.totalAmount))\n\t\t\t\t.limit(limit)\n\n\t\t\treturn rows.map((r: any) => ({\n\t\t\t\tid: r.id,\n\t\t\t\tcreatedAt: r.createdAt,\n\t\t\t\ttotalAmount: Number(r.totalAmount),\n\t\t\t\tproductName: r.productName ?? '(unknown)',\n\t\t\t\tproductId: r.productId,\n\t\t\t\tcountry: r.country,\n\t\t\t\tcouponId: r.couponId,\n\t\t\t\tuserName: r.userName ?? null,\n\t\t\t\tuserEmail: r.userEmail ?? null,\n\t\t\t\tisTeam: true,\n\t\t\t\tseats: r.seats ?? null,\n\t\t\t}))\n\t\t}\n\n\t\tif (filter === 'individual') {\n\t\t\tconditions.push(sql`${purchases.bulkCouponId} IS NULL`)\n\t\t}\n\n\t\tconst rows = await db.query.purchases.findMany({\n\t\t\twhere: and(...conditions),\n\t\t\torderBy: [desc(purchases.totalAmount)],\n\t\t\tlimit,\n\t\t\twith: {\n\t\t\t\tproduct: true,\n\t\t\t\tuser: true,\n\t\t\t},\n\t\t})\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tid: r.id,\n\t\t\tcreatedAt: r.createdAt,\n\t\t\ttotalAmount: Number(r.totalAmount),\n\t\t\tproductName: r.product?.name ?? '(unknown)',\n\t\t\tproductId: r.productId,\n\t\t\tcountry: r.country,\n\t\t\tcouponId: r.couponId,\n\t\t\tuserName: r.user?.name ?? null,\n\t\t\tuserEmail: r.user?.email ?? null,\n\t\t\tisTeam: r.organizationId != null,\n\t\t\tseats: null as number | null,\n\t\t}))\n\t}\n\n\t// ─── Attribution ────────────────────────────────────────────────────────\n\n\tasync function getAttributionSummary(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions: any[] = []\n\t\tif (since) conditions.push(gte(shortlinkAttribution.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\ttype: shortlinkAttribution.type,\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(shortlinkAttribution)\n\t\t\t.where(conditions.length > 0 ? and(...conditions) : undefined)\n\t\t\t.groupBy(shortlinkAttribution.type)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\ttype: r.type,\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getShortlinkPerformance(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst clickConditions: any[] = []\n\t\tif (since) clickConditions.push(gte(shortlinkClick.timestamp, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tshortlinkId: shortlinkClick.shortlinkId,\n\t\t\t\tslug: shortlink.slug,\n\t\t\t\turl: shortlink.url,\n\t\t\t\tclicks: count(),\n\t\t\t})\n\t\t\t.from(shortlinkClick)\n\t\t\t.innerJoin(shortlink, eq(shortlinkClick.shortlinkId, shortlink.id))\n\t\t\t.where(clickConditions.length > 0 ? and(...clickConditions) : undefined)\n\t\t\t.groupBy(shortlinkClick.shortlinkId, shortlink.slug, shortlink.url)\n\t\t\t.orderBy(desc(count()))\n\t\t\t.limit(20)\n\n\t\t// Get attribution counts per shortlink\n\t\tconst attrConditions: any[] = []\n\t\tif (since) attrConditions.push(gte(shortlinkAttribution.createdAt, since))\n\n\t\tconst attrRows = await db\n\t\t\t.select({\n\t\t\t\tshortlinkId: shortlinkAttribution.shortlinkId,\n\t\t\t\ttype: shortlinkAttribution.type,\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(shortlinkAttribution)\n\t\t\t.where(attrConditions.length > 0 ? and(...attrConditions) : undefined)\n\t\t\t.groupBy(shortlinkAttribution.shortlinkId, shortlinkAttribution.type)\n\n\t\tconst attrMap = new Map<string, { signups: number; purchases: number }>()\n\t\tfor (const a of attrRows) {\n\t\t\tconst existing = attrMap.get(a.shortlinkId) ?? {\n\t\t\t\tsignups: 0,\n\t\t\t\tpurchases: 0,\n\t\t\t}\n\t\t\tif (a.type === 'signup') existing.signups = a.count\n\t\t\tif (a.type === 'purchase') existing.purchases = a.count\n\t\t\tattrMap.set(a.shortlinkId, existing)\n\t\t}\n\n\t\treturn rows.map((r: any) => {\n\t\t\tconst attr = attrMap.get(r.shortlinkId)\n\t\t\treturn {\n\t\t\t\tshortlinkId: r.shortlinkId,\n\t\t\t\tslug: r.slug,\n\t\t\t\turl: r.url,\n\t\t\t\tclicks: r.clicks,\n\t\t\t\tsignups: attr?.signups ?? 0,\n\t\t\t\tpurchases: attr?.purchases ?? 0,\n\t\t\t}\n\t\t})\n\t}\n\n\t// ─── Revenue Attribution ─────────────────────────────────────────────────\n\n\tasync function getRevenueBySource(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tsource:\n\t\t\t\t\tsql<string>`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource'))`.as(\n\t\t\t\t\t\t'source',\n\t\t\t\t\t),\n\t\t\t\tmedium:\n\t\t\t\t\tsql<string>`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmMedium'))`.as(\n\t\t\t\t\t\t'medium',\n\t\t\t\t\t),\n\t\t\t\tcampaign:\n\t\t\t\t\tsql<string>`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmCampaign'))`.as(\n\t\t\t\t\t\t'campaign',\n\t\t\t\t\t),\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(\n\t\t\t\tsql`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource'))`,\n\t\t\t\tsql`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmMedium'))`,\n\t\t\t\tsql`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmCampaign'))`,\n\t\t\t)\n\t\t\t.orderBy(desc(sum(purchases.totalAmount)))\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tsource: r.source ?? null,\n\t\t\tmedium: r.medium ?? null,\n\t\t\tcampaign: r.campaign ?? null,\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getConversionFunnel(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\n\t\tconst userConditions = since ? [gte(users.createdAt, since)] : []\n\t\tconst purchaseConditions = [paidPurchase()]\n\t\tif (since) purchaseConditions.push(gte(purchases.createdAt, since))\n\n\t\tconst [userCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(users)\n\t\t\t.where(userConditions.length > 0 ? and(...userConditions) : undefined)\n\n\t\tconst [purchaseCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...purchaseConditions))\n\n\t\tconst [attributedCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\t...purchaseConditions,\n\t\t\t\t\tsql`(\n JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource')) IS NOT NULL\n OR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.gaClientId')) IS NOT NULL\n )`,\n\t\t\t\t),\n\t\t\t)\n\n\t\tconst totalSignups = userCount?.total ?? 0\n\t\tconst totalPurchases = purchaseCount?.total ?? 0\n\t\tconst attributedPurchases = attributedCount?.total ?? 0\n\n\t\treturn {\n\t\t\ttotalSignups,\n\t\t\ttotalPurchases,\n\t\t\tattributedPurchases,\n\t\t\tconversionRate: totalSignups > 0 ? totalPurchases / totalSignups : 0,\n\t\t\tattributionCoverage:\n\t\t\t\ttotalPurchases > 0 ? attributedPurchases / totalPurchases : 0,\n\t\t}\n\t}\n\n\tasync function getAttributedRevenueSummary(\n\t\trange: AnalyticsTimeRange = '30d',\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst [totals] = await db\n\t\t\t.select({ total: sum(purchases.totalAmount), count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\n\t\tconst [attributed] = await db\n\t\t\t.select({ total: sum(purchases.totalAmount) })\n\t\t\t.from(purchases)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\t...conditions,\n\t\t\t\t\tsql`(\n JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource')) IS NOT NULL\n OR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.gaClientId')) IS NOT NULL\n )`,\n\t\t\t\t),\n\t\t\t)\n\n\t\tconst totalRevenue = Number(totals?.total ?? 0)\n\t\tconst attributedRevenue = Number(attributed?.total ?? 0)\n\t\tconst unattributedRevenue = totalRevenue - attributedRevenue\n\t\tconst totalPurchases = totals?.count ?? 0\n\n\t\treturn {\n\t\t\ttotalRevenue,\n\t\t\tattributedRevenue,\n\t\t\tunattributedRevenue,\n\t\t\tattributionRate: totalRevenue > 0 ? attributedRevenue / totalRevenue : 0,\n\t\t\ttotalPurchases,\n\t\t}\n\t}\n\n\tasync function getContentPurchaseCorrelation(\n\t\trange: AnalyticsTimeRange = '30d',\n\t\tlimit: number = 20,\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst purchaseConditions = [paidPurchase()]\n\t\tif (since) purchaseConditions.push(gte(purchases.createdAt, since))\n\n\t\tconst purchaserRows = await db\n\t\t\t.selectDistinct({ userId: purchases.userId })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...purchaseConditions))\n\n\t\tconst purchaserIds = purchaserRows\n\t\t\t.map((r: any) => r.userId)\n\t\t\t.filter((id: any): id is string => id !== null)\n\n\t\tif (purchaserIds.length === 0) return []\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tresourceId: resourceProgress.resourceId,\n\t\t\t\tpurchaserCount: count(),\n\t\t\t})\n\t\t\t.from(resourceProgress)\n\t\t\t.where(\n\t\t\t\tsql`${resourceProgress.userId} IN (${sql.join(\n\t\t\t\t\tpurchaserIds.map((id: string) => sql`${id}`),\n\t\t\t\t\tsql`, `,\n\t\t\t\t)})`,\n\t\t\t)\n\t\t\t.groupBy(resourceProgress.resourceId)\n\t\t\t.orderBy(desc(count()))\n\t\t\t.limit(limit)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tresourceId: r.resourceId,\n\t\t\tpurchaserCount: r.purchaserCount,\n\t\t}))\n\t}\n\n\t// ─── Attribution Trail ───────────────────────────────────────────────────\n\n\t/**\n\t * Trace the full attribution journey for a user by email or purchaseId.\n\t * Walks: ShortlinkClick → ShortlinkAttribution (signup) →\n\t * ResourceProgress → Purchase\n\t */\n\tasync function traceAttribution(opts: {\n\t\temail?: string\n\t\tpurchaseId?: string\n\t}): Promise<AttributionTrail> {\n\t\tconst events: AttributionTrailEvent[] = []\n\n\t\t// Resolve user\n\t\tlet userId: string | null = null\n\t\tlet userEmail: string | null = opts.email ?? null\n\t\tlet userRecord: AttributionTrail['user'] = null\n\n\t\tif (opts.purchaseId) {\n\t\t\tconst [purchase] = await db\n\t\t\t\t.select({\n\t\t\t\t\tuserId: purchases.userId,\n\t\t\t\t\temail: users.email,\n\t\t\t\t})\n\t\t\t\t.from(purchases)\n\t\t\t\t.leftJoin(users, eq(purchases.userId, users.id))\n\t\t\t\t.where(eq(purchases.id, opts.purchaseId))\n\t\t\t\t.limit(1)\n\t\t\tuserId = purchase?.userId ?? null\n\t\t\tuserEmail = purchase?.email ?? userEmail\n\t\t}\n\n\t\tif (userEmail && !userId) {\n\t\t\tconst [u] = await db\n\t\t\t\t.select({ id: users.id })\n\t\t\t\t.from(users)\n\t\t\t\t.where(eq(users.email, userEmail))\n\t\t\t\t.limit(1)\n\t\t\tuserId = u?.id ?? null\n\t\t}\n\n\t\tif (userId) {\n\t\t\tconst [u] = await db\n\t\t\t\t.select({\n\t\t\t\t\tid: users.id,\n\t\t\t\t\temail: users.email,\n\t\t\t\t\tname: users.name,\n\t\t\t\t\tcreatedAt: users.createdAt,\n\t\t\t\t})\n\t\t\t\t.from(users)\n\t\t\t\t.where(eq(users.id, userId))\n\t\t\t\t.limit(1)\n\t\t\tuserRecord = u\n\t\t\t\t? {\n\t\t\t\t\t\tid: u.id,\n\t\t\t\t\t\temail: u.email,\n\t\t\t\t\t\tname: u.name ?? null,\n\t\t\t\t\t\tcreatedAt: u.createdAt!,\n\t\t\t\t\t}\n\t\t\t\t: null\n\t\t}\n\n\t\t// Find shortlink attributions for this user (by userId or email)\n\t\tconst attrConditions: any[] = []\n\t\tif (userId) attrConditions.push(eq(shortlinkAttribution.userId, userId))\n\t\tif (userEmail)\n\t\t\tattrConditions.push(eq(shortlinkAttribution.email, userEmail))\n\n\t\tif (attrConditions.length > 0) {\n\t\t\tconst attrs = await db\n\t\t\t\t.select({\n\t\t\t\t\ttype: shortlinkAttribution.type,\n\t\t\t\t\tcreatedAt: shortlinkAttribution.createdAt,\n\t\t\t\t\tmetadata: shortlinkAttribution.metadata,\n\t\t\t\t\tshortlinkId: shortlinkAttribution.shortlinkId,\n\t\t\t\t\tslug: shortlink.slug,\n\t\t\t\t\turl: shortlink.url,\n\t\t\t\t})\n\t\t\t\t.from(shortlinkAttribution)\n\t\t\t\t.leftJoin(shortlink, eq(shortlinkAttribution.shortlinkId, shortlink.id))\n\t\t\t\t.where(sql`(${sql.join(attrConditions, sql` OR `)})`)\n\t\t\t\t.orderBy(shortlinkAttribution.createdAt)\n\n\t\t\tfor (const attr of attrs) {\n\t\t\t\t// Find clicks on this shortlink before the attribution event\n\t\t\t\tconst clicks = await db\n\t\t\t\t\t.select({\n\t\t\t\t\t\ttimestamp: shortlinkClick.timestamp,\n\t\t\t\t\t\treferrer: shortlinkClick.referrer,\n\t\t\t\t\t\tcountry: shortlinkClick.country,\n\t\t\t\t\t\tdevice: shortlinkClick.device,\n\t\t\t\t\t})\n\t\t\t\t\t.from(shortlinkClick)\n\t\t\t\t\t.where(\n\t\t\t\t\t\tand(\n\t\t\t\t\t\t\teq(shortlinkClick.shortlinkId, attr.shortlinkId),\n\t\t\t\t\t\t\tlte(shortlinkClick.timestamp, attr.createdAt),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\t.orderBy(desc(shortlinkClick.timestamp))\n\t\t\t\t\t.limit(3) // last 3 clicks before attribution\n\n\t\t\t\tfor (const click of clicks) {\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: 'click',\n\t\t\t\t\t\ttimestamp: click.timestamp,\n\t\t\t\t\t\tdetail: {\n\t\t\t\t\t\t\tshortlink: `/s/${attr.slug}`,\n\t\t\t\t\t\t\tdestination: attr.url,\n\t\t\t\t\t\t\treferrer: click.referrer,\n\t\t\t\t\t\t\tcountry: click.country,\n\t\t\t\t\t\t\tdevice: click.device,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tevents.push({\n\t\t\t\t\ttype: attr.type === 'purchase' ? 'purchase' : 'signup',\n\t\t\t\t\ttimestamp: attr.createdAt,\n\t\t\t\t\tdetail: {\n\t\t\t\t\t\tshortlink: `/s/${attr.slug}`,\n\t\t\t\t\t\tmetadata: attr.metadata ? JSON.parse(attr.metadata) : null,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Find resource progress for this user\n\t\tif (userId) {\n\t\t\tconst progress = await db\n\t\t\t\t.select({\n\t\t\t\t\tresourceId: resourceProgress.resourceId,\n\t\t\t\t\tcompletedAt: resourceProgress.completedAt,\n\t\t\t\t\tcreatedAt: resourceProgress.createdAt,\n\t\t\t\t})\n\t\t\t\t.from(resourceProgress)\n\t\t\t\t.where(eq(resourceProgress.userId, userId))\n\t\t\t\t.orderBy(resourceProgress.createdAt)\n\t\t\t\t.limit(20) // cap at 20 most recent\n\n\t\t\tfor (const p of progress) {\n\t\t\t\tevents.push({\n\t\t\t\t\ttype: 'progress',\n\t\t\t\t\ttimestamp: p.completedAt ?? p.createdAt,\n\t\t\t\t\tdetail: { resourceId: p.resourceId },\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Find purchases\n\t\tconst purchaseConditions = [paidPurchase()]\n\t\tif (opts.purchaseId) {\n\t\t\tpurchaseConditions.push(eq(purchases.id, opts.purchaseId))\n\t\t} else if (userId) {\n\t\t\tpurchaseConditions.push(eq(purchases.userId, userId))\n\t\t} else {\n\t\t\t// No user found, return empty\n\t\t\treturn { user: userRecord, events: [], purchases: [] }\n\t\t}\n\n\t\tconst purchaseRows = await db\n\t\t\t.select({\n\t\t\t\tid: purchases.id,\n\t\t\t\ttotalAmount: purchases.totalAmount,\n\t\t\t\tproductName: products.name,\n\t\t\t\tcreatedAt: purchases.createdAt,\n\t\t\t\tcountry: purchases.country,\n\t\t\t\tfields: purchases.fields,\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t.where(and(...purchaseConditions))\n\t\t\t.orderBy(purchases.createdAt)\n\n\t\tconst purchaseResults = purchaseRows.map((p: any) => {\n\t\t\tconst fields = (p.fields as Record<string, any>) ?? {}\n\t\t\tevents.push({\n\t\t\t\ttype: 'purchase',\n\t\t\t\ttimestamp: p.createdAt,\n\t\t\t\tdetail: {\n\t\t\t\t\tpurchaseId: p.id,\n\t\t\t\t\tamount: Number(p.totalAmount),\n\t\t\t\t\tproduct: p.productName,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tid: p.id,\n\t\t\t\ttotalAmount: Number(p.totalAmount),\n\t\t\t\tproductName: p.productName ?? 'Unknown',\n\t\t\t\tcreatedAt: p.createdAt,\n\t\t\t\tcountry: p.country,\n\t\t\t\tutmSource: fields.utmSource ?? null,\n\t\t\t\tutmMedium: fields.utmMedium ?? null,\n\t\t\t\tutmCampaign: fields.utmCampaign ?? null,\n\t\t\t}\n\t\t})\n\n\t\t// Sort all events by timestamp\n\t\tevents.sort(\n\t\t\t(a, b) =>\n\t\t\t\tnew Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),\n\t\t)\n\n\t\treturn { user: userRecord, events, purchases: purchaseResults }\n\t}\n\n\treturn {\n\t\tgetRevenueSummary,\n\t\tgetRevenueByDay,\n\t\tgetPreviousPeriodRevenueByDay,\n\t\tgetRevenueByProduct,\n\t\tgetRevenueByCountry,\n\t\tgetRecentPurchases,\n\t\tgetAttributionSummary,\n\t\tgetShortlinkPerformance,\n\t\tgetRevenueBySource,\n\t\tgetConversionFunnel,\n\t\tgetAttributedRevenueSummary,\n\t\tgetContentPurchaseCorrelation,\n\t\ttraceAttribution,\n\t}\n}\n\nexport type DatabaseAnalyticsProvider = ReturnType<\n\ttypeof createDatabaseProvider\n>\n","import { BetaAnalyticsDataClient } from '@google-analytics/data'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type GA4TimeRange = '24h' | '7d' | '30d' | '90d'\n\nexport interface GA4ProviderConfig {\n\tpropertyId: string\n\tclientEmail: string\n\tprivateKey: string\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a GA4 analytics provider with injected credentials.\n * Lazily initializes the BetaAnalyticsDataClient on first use.\n *\n * @param config - GA4 credentials and property configuration\n */\nexport function createGA4Provider(config: GA4ProviderConfig) {\n\tconst { propertyId, clientEmail, privateKey } = config\n\n\tlet _client: BetaAnalyticsDataClient | null = null\n\n\tfunction getClient(): BetaAnalyticsDataClient {\n\t\tif (!_client) {\n\t\t\t_client = new BetaAnalyticsDataClient({\n\t\t\t\tcredentials: {\n\t\t\t\t\tclient_email: clientEmail,\n\t\t\t\t\tprivate_key: privateKey.replace(/\\\\n/g, '\\n'),\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\treturn _client\n\t}\n\n\tfunction rangeToDateRange(range: GA4TimeRange) {\n\t\tconst map: Record<GA4TimeRange, string> = {\n\t\t\t'24h': '1daysAgo',\n\t\t\t'7d': '7daysAgo',\n\t\t\t'30d': '30daysAgo',\n\t\t\t'90d': '90daysAgo',\n\t\t}\n\t\treturn { startDate: map[range], endDate: 'today' }\n\t}\n\n\t// ─── Reports ──────────────────────────────────────────────────────────────\n\n\tasync function getTrafficOverview(range: GA4TimeRange = '30d') {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tmetrics: [\n\t\t\t\t{ name: 'sessions' },\n\t\t\t\t{ name: 'totalUsers' },\n\t\t\t\t{ name: 'newUsers' },\n\t\t\t\t{ name: 'screenPageViews' },\n\t\t\t\t{ name: 'averageSessionDuration' },\n\t\t\t\t{ name: 'bounceRate' },\n\t\t\t],\n\t\t})\n\n\t\tconst row = response?.rows?.[0]\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsessions: 0,\n\t\t\t\ttotalUsers: 0,\n\t\t\t\tnewUsers: 0,\n\t\t\t\tpageviews: 0,\n\t\t\t\tavgSessionDuration: 0,\n\t\t\t\tbounceRate: 0,\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tsessions: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\ttotalUsers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\tnewUsers: Number(row.metricValues?.[2]?.value ?? 0),\n\t\t\tpageviews: Number(row.metricValues?.[3]?.value ?? 0),\n\t\t\tavgSessionDuration: Number(row.metricValues?.[4]?.value ?? 0),\n\t\t\tbounceRate: Number(row.metricValues?.[5]?.value ?? 0),\n\t\t}\n\t}\n\n\tasync function getTopPages(range: GA4TimeRange = '30d', limit = 20) {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tdimensions: [{ name: 'pagePath' }],\n\t\t\tmetrics: [\n\t\t\t\t{ name: 'screenPageViews' },\n\t\t\t\t{ name: 'totalUsers' },\n\t\t\t\t{ name: 'averageSessionDuration' },\n\t\t\t],\n\t\t\torderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],\n\t\t\tlimit,\n\t\t})\n\n\t\treturn (\n\t\t\tresponse?.rows?.map((row) => ({\n\t\t\t\tpath: row.dimensionValues?.[0]?.value ?? '',\n\t\t\t\tpageviews: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\t\tusers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\t\tavgDuration: Number(row.metricValues?.[2]?.value ?? 0),\n\t\t\t})) ?? []\n\t\t)\n\t}\n\n\tasync function getTrafficSources(range: GA4TimeRange = '30d', limit = 15) {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tdimensions: [{ name: 'sessionSource' }, { name: 'sessionMedium' }],\n\t\t\tmetrics: [{ name: 'sessions' }, { name: 'totalUsers' }],\n\t\t\torderBys: [{ metric: { metricName: 'sessions' }, desc: true }],\n\t\t\tlimit,\n\t\t})\n\n\t\treturn (\n\t\t\tresponse?.rows?.map((row) => ({\n\t\t\t\tsource: row.dimensionValues?.[0]?.value ?? '(direct)',\n\t\t\t\tmedium: row.dimensionValues?.[1]?.value ?? '(none)',\n\t\t\t\tsessions: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\t\tusers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\t})) ?? []\n\t\t)\n\t}\n\n\tasync function getSessionsByDay(range: GA4TimeRange = '30d') {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tdimensions: [{ name: 'date' }],\n\t\t\tmetrics: [\n\t\t\t\t{ name: 'sessions' },\n\t\t\t\t{ name: 'totalUsers' },\n\t\t\t\t{ name: 'screenPageViews' },\n\t\t\t],\n\t\t\torderBys: [{ dimension: { dimensionName: 'date' }, desc: false }],\n\t\t})\n\n\t\treturn (\n\t\t\tresponse?.rows?.map((row) => {\n\t\t\t\tconst raw = row.dimensionValues?.[0]?.value ?? ''\n\t\t\t\tconst date = `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`\n\t\t\t\treturn {\n\t\t\t\t\tdate,\n\t\t\t\t\tsessions: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\t\t\tusers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\t\t\tpageviews: Number(row.metricValues?.[2]?.value ?? 0),\n\t\t\t\t}\n\t\t\t}) ?? []\n\t\t)\n\t}\n\n\treturn {\n\t\tgetTrafficOverview,\n\t\tgetTopPages,\n\t\tgetTrafficSources,\n\t\tgetSessionsByDay,\n\t}\n}\n\nexport type GA4AnalyticsProvider = ReturnType<typeof createGA4Provider>\n","import type { GA4TimeRange } from './ga4'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnalyticsRange = '24h' | '7d' | '30d' | '90d' | 'all'\n\nexport interface TrafficRevenueCorrelation {\n\ttraffic: {\n\t\tdate: string\n\t\tsessions: number\n\t\tusers: number\n\t\tpageviews: number\n\t}[]\n\trevenue: {\n\t\tdate: string\n\t\trevenue: number\n\t\tcount: number\n\t}[]\n}\n\nexport interface DerivedProviderDeps {\n\tdatabase: {\n\t\tgetRevenueByDay: (range: AnalyticsRange) => Promise<\n\t\t\t{\n\t\t\t\tdate: string\n\t\t\t\trevenue: number\n\t\t\t\tcount: number\n\t\t\t}[]\n\t\t>\n\t}\n\tga4: {\n\t\tgetSessionsByDay: (range: GA4TimeRange) => Promise<\n\t\t\t{\n\t\t\t\tdate: string\n\t\t\t\tsessions: number\n\t\t\t\tusers: number\n\t\t\t\tpageviews: number\n\t\t\t}[]\n\t\t>\n\t}\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a derived analytics provider that combines data from multiple\n * sources (database + GA4) to compute correlation metrics.\n *\n * @param deps - Provider dependencies (database and ga4 providers)\n */\nexport function createDerivedProvider(deps: DerivedProviderDeps) {\n\tconst { database, ga4 } = deps\n\n\tfunction toGA4Range(range: AnalyticsRange): GA4TimeRange {\n\t\tif (range === 'all') return '90d'\n\t\treturn range\n\t}\n\n\t/**\n\t * Correlates GA4 traffic sessions by day with revenue by day,\n\t * enabling side-by-side analysis of traffic and revenue trends.\n\t */\n\tasync function getTrafficRevenueCorrelation(\n\t\trange: AnalyticsRange,\n\t): Promise<TrafficRevenueCorrelation> {\n\t\tconst [traffic, revenue] = await Promise.all([\n\t\t\tga4.getSessionsByDay(toGA4Range(range)),\n\t\t\tdatabase.getRevenueByDay(range),\n\t\t])\n\n\t\treturn { traffic, revenue }\n\t}\n\n\treturn {\n\t\tgetTrafficRevenueCorrelation,\n\t}\n}\n\nexport type DerivedAnalyticsProvider = ReturnType<typeof createDerivedProvider>\n","import { sql } from 'drizzle-orm'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type TimeRange = '7:days' | '30:days' | '90:days'\n\n/** Ranges available on the Top Videos table (independent from page-level range) */\nexport type VideoTableRange = '24:hours' | '7:days' | '30:days' | '90:days'\n\nexport interface MuxOverallResponse {\n\tdata: {\n\t\tvalue: number\n\t\ttotal_watch_time: number\n\t\ttotal_playing_time: number\n\t\ttotal_views: number\n\t\tglobal_value: number | null\n\t}\n\ttimeframe: [number, number]\n}\n\nexport interface MuxTimeseriesResponse {\n\tdata: [string, number | null, number | null][]\n\ttimeframe: [number, number]\n\ttotal_row_count: number\n}\n\nexport interface MuxBreakdownItem {\n\tviews: number\n\tvalue: number\n\ttotal_watch_time: number\n\ttotal_playing_time: number\n\tnegative_impact: number | null\n\tfield: string\n}\n\nexport interface MuxBreakdownResponse {\n\tdata: MuxBreakdownItem[]\n\ttimeframe: [number, number]\n\ttotal_row_count: number\n}\n\nexport interface VideoDashboardData {\n\toverview: {\n\t\ttotalViews: number\n\t\tuniqueViewers: number\n\t\ttotalWatchTimeMs: number\n\t\ttotalPlayingTimeMs: number\n\t\tviewerExperienceScore: number\n\t\tglobalExperienceScore: number | null\n\t}\n\twatchTimeSeries: {\n\t\tdate: string\n\t\twatchTimeMs: number\n\t}[]\n\ttopVideos: {\n\t\ttitle: string\n\t\tviews: number\n\t\twatchTimeMs: number\n\t\tplayingTimeMs: number\n\t}[]\n\tcountries: {\n\t\tcountry: string\n\t\tviews: number\n\t\twatchTimeMs: number\n\t}[]\n}\n\nexport type VideoDetailBreakdowns = {\n\tcountries: {\n\t\tcountry: string\n\t\tviews: number\n\t\twatchTimeMs: number\n\t}[]\n\ttimeseries: {\n\t\tdate: string\n\t\tviews: number\n\t}[]\n}\n\nexport interface MuxProviderConfig {\n\ttokenId: string\n\ttokenSecret: string\n}\n\nexport interface MuxDbDeps {\n\tdb: any\n\tcontentResource: any\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a Mux analytics provider with injected credentials.\n * Optionally accepts db dependencies for thumbnail lookups that\n * require querying ContentResource records.\n *\n * @param config - Mux Data API token configuration\n * @param dbDeps - Optional drizzle db + contentResource table for thumbnail queries\n */\nexport function createMuxProvider(\n\tconfig: MuxProviderConfig,\n\tdbDeps?: MuxDbDeps,\n) {\n\tconst MUX_DATA_BASE = 'https://api.mux.com/data/v1'\n\n\tfunction getAuthHeader(): string {\n\t\treturn `Basic ${Buffer.from(\n\t\t\t`${config.tokenId}:${config.tokenSecret}`,\n\t\t).toString('base64')}`\n\t}\n\n\tasync function muxDataFetch<T>(\n\t\tpath: string,\n\t\tparams?: Record<string, string | string[]>,\n\t): Promise<T> {\n\t\tconst url = new URL(`${MUX_DATA_BASE}${path}`)\n\t\tif (params) {\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const v of value) {\n\t\t\t\t\t\turl.searchParams.append(key, v)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\turl.searchParams.set(key, value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst response = await fetch(url.toString(), {\n\t\t\theaders: {\n\t\t\t\tAuthorization: getAuthHeader(),\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t\tnext: { revalidate: 300 }, // cache 5 minutes\n\t\t} as RequestInit)\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Mux Data API error: ${response.status} ${response.statusText}`,\n\t\t\t)\n\t\t}\n\n\t\treturn response.json() as Promise<T>\n\t}\n\n\t// ─── Comparison (unique viewers) ─────────────────────────────────────────\n\n\tinterface MuxComparisonItem {\n\t\tname: string\n\t\twatch_time?: number\n\t\tview_count?: number\n\t\tunique_viewers?: number\n\t\tstarted_views?: number\n\t\tended_views?: number\n\t\t[key: string]: unknown\n\t}\n\n\tinterface MuxComparisonResponse {\n\t\tdata: MuxComparisonItem[]\n\t\ttimeframe: [number, number]\n\t\ttotal_row_count: number | null\n\t}\n\n\tasync function getComparisonTotals(timeRange: TimeRange = '30:days') {\n\t\tconst resp = await muxDataFetch<MuxComparisonResponse>(\n\t\t\t'/metrics/comparison',\n\t\t\t{ 'timeframe[]': timeRange },\n\t\t)\n\t\tconst totals = resp.data.find((d) => d.name === 'totals')\n\t\treturn {\n\t\t\tuniqueViewers: totals?.unique_viewers ?? 0,\n\t\t\tviewCount: totals?.view_count ?? 0,\n\t\t\twatchTimeMs: totals?.watch_time ?? 0,\n\t\t}\n\t}\n\n\t// ─── API Functions ────────────────────────────────────────────────────────\n\n\tasync function getViewsOverall(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxOverallResponse>('/metrics/views/overall', {\n\t\t\t'timeframe[]': timeRange,\n\t\t})\n\t}\n\n\tasync function getViewerExperienceScore(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxOverallResponse>(\n\t\t\t'/metrics/viewer_experience_score/overall',\n\t\t\t{ 'timeframe[]': timeRange },\n\t\t)\n\t}\n\n\tasync function getViewsTimeseries(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxTimeseriesResponse>('/metrics/views/timeseries', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'day',\n\t\t})\n\t}\n\n\t/**\n\t * Fetch watch time (playing_time) timeseries grouped by day.\n\t * Mux returns [date, totalPlayingTimeMs, viewCount] tuples.\n\t * The value IS the total playing time in ms (not the average).\n\t */\n\tasync function getWatchTimeTimeseries(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxTimeseriesResponse>(\n\t\t\t'/metrics/playing_time/timeseries',\n\t\t\t{\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'day',\n\t\t\t},\n\t\t)\n\t}\n\n\tasync function getVideoBreakdown(\n\t\ttimeRange: TimeRange = '30:days',\n\t\tlimit: number = 25,\n\t) {\n\t\treturn muxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'video_title',\n\t\t\torder_by: 'views',\n\t\t\torder_direction: 'desc',\n\t\t\tlimit: String(limit),\n\t\t})\n\t}\n\n\t/**\n\t * Standalone video breakdown fetcher for the Top Videos table's\n\t * independent time-range tabs. Accepts VideoTableRange.\n\t */\n\tasync function getVideoBreakdownForRange(\n\t\ttimeRange: VideoTableRange,\n\t\tlimit: number = 50,\n\t) {\n\t\treturn muxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'video_title',\n\t\t\torder_by: 'views',\n\t\t\torder_direction: 'desc',\n\t\t\tlimit: String(limit),\n\t\t})\n\t}\n\n\tasync function getCountryBreakdown(\n\t\ttimeRange: TimeRange = '30:days',\n\t\tlimit: number = 10,\n\t) {\n\t\treturn muxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'country',\n\t\t\torder_by: 'views',\n\t\t\torder_direction: 'desc',\n\t\t\tlimit: String(limit),\n\t\t})\n\t}\n\n\tasync function getVideoDetailBreakdowns(\n\t\tvideoTitle: string,\n\t\ttimeRange: TimeRange = '30:days',\n\t): Promise<VideoDetailBreakdowns> {\n\t\tconst filter = `video_title:${videoTitle}`\n\t\tconst [countries, timeseries] = await Promise.all([\n\t\t\tmuxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'country',\n\t\t\t\torder_by: 'views',\n\t\t\t\torder_direction: 'desc',\n\t\t\t\tlimit: '8',\n\t\t\t\t'filters[]': filter,\n\t\t\t}),\n\t\t\tmuxDataFetch<MuxTimeseriesResponse>('/metrics/views/timeseries', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'day',\n\t\t\t\t'filters[]': filter,\n\t\t\t}),\n\t\t])\n\n\t\treturn {\n\t\t\tcountries: countries.data.map((c) => ({\n\t\t\t\tcountry: c.field,\n\t\t\t\tviews: c.views,\n\t\t\t\twatchTimeMs: c.total_watch_time,\n\t\t\t})),\n\t\t\ttimeseries: timeseries.data.map(([date, value]) => ({\n\t\t\t\tdate,\n\t\t\t\tviews: value ?? 0,\n\t\t\t})),\n\t\t}\n\t}\n\n\t// ─── Aggregate fetcher ────────────────────────────────────────────────────\n\n\tasync function getVideoDashboardData(\n\t\ttimeRange: TimeRange = '30:days',\n\t): Promise<VideoDashboardData> {\n\t\tconst [views, experience, comparison, watchTime, videos, countries] =\n\t\t\tawait Promise.all([\n\t\t\t\tgetViewsOverall(timeRange),\n\t\t\t\tgetViewerExperienceScore(timeRange),\n\t\t\t\tgetComparisonTotals(timeRange),\n\t\t\t\tgetWatchTimeTimeseries(timeRange),\n\t\t\t\tgetVideoBreakdown(timeRange, 50),\n\t\t\t\tgetCountryBreakdown(timeRange, 15),\n\t\t\t])\n\n\t\treturn {\n\t\t\toverview: {\n\t\t\t\ttotalViews: views.data.total_views,\n\t\t\t\tuniqueViewers: comparison.uniqueViewers,\n\t\t\t\ttotalWatchTimeMs: views.data.total_watch_time,\n\t\t\t\ttotalPlayingTimeMs: views.data.total_playing_time,\n\t\t\t\tviewerExperienceScore: experience.data.value,\n\t\t\t\tglobalExperienceScore: experience.data.global_value,\n\t\t\t},\n\t\t\twatchTimeSeries: watchTime.data.map(([date, totalMs]) => ({\n\t\t\t\tdate,\n\t\t\t\t// Mux playing_time timeseries: value is total playing time in ms\n\t\t\t\twatchTimeMs: totalMs ?? 0,\n\t\t\t})),\n\t\t\ttopVideos: videos.data\n\t\t\t\t.filter((v) => v.field !== '')\n\t\t\t\t.map((v) => ({\n\t\t\t\t\ttitle: v.field,\n\t\t\t\t\tviews: v.views,\n\t\t\t\t\twatchTimeMs: v.total_watch_time,\n\t\t\t\t\tplayingTimeMs: v.total_playing_time,\n\t\t\t\t})),\n\t\t\tcountries: countries.data.map((c) => ({\n\t\t\t\tcountry: c.field,\n\t\t\t\tviews: c.views,\n\t\t\t\twatchTimeMs: c.total_watch_time,\n\t\t\t})),\n\t\t}\n\t}\n\n\t/**\n\t * Resolve video titles to Mux thumbnail URLs.\n\t * 1. Break down by video_id to get ContentResource IDs for top videos\n\t * 2. Look up playback IDs from ContentResource.fields.muxPlaybackId\n\t * 3. Build image.mux.com thumbnail URLs\n\t * Returns Record<title, thumbnailUrl>.\n\t * Requires dbDeps to be provided at factory construction time.\n\t */\n\tasync function getVideoThumbnails(\n\t\ttimeRange: TimeRange = '30:days',\n\t\tlimit: number = 10,\n\t): Promise<Record<string, string>> {\n\t\tif (!dbDeps) {\n\t\t\tthrow new Error(\n\t\t\t\t'getVideoThumbnails requires dbDeps (db + contentResource) ' +\n\t\t\t\t\t'to be provided to createMuxProvider',\n\t\t\t)\n\t\t}\n\n\t\tconst { db, contentResource } = dbDeps\n\n\t\t// Get breakdown by both video_id and video_title\n\t\tconst [byId, byTitle] = await Promise.all([\n\t\t\tmuxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'video_id',\n\t\t\t\torder_by: 'views',\n\t\t\t\torder_direction: 'desc',\n\t\t\t\tlimit: String(limit),\n\t\t\t}),\n\t\t\tmuxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'video_title',\n\t\t\t\torder_by: 'views',\n\t\t\t\torder_direction: 'desc',\n\t\t\t\tlimit: String(limit),\n\t\t\t}),\n\t\t])\n\n\t\t// video_id breakdown gives ContentResource IDs — look up playback IDs\n\t\tconst videoIds = byId.data\n\t\t\t.filter((v) => v.field && v.field !== '')\n\t\t\t.map((v) => v.field)\n\n\t\tif (videoIds.length === 0) return {}\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tid: contentResource.id,\n\t\t\t\tplaybackId:\n\t\t\t\t\tsql<string>`JSON_UNQUOTE(JSON_EXTRACT(${contentResource.fields}, '$.muxPlaybackId'))`.as(\n\t\t\t\t\t\t'playbackId',\n\t\t\t\t\t),\n\t\t\t})\n\t\t\t.from(contentResource)\n\t\t\t.where(\n\t\t\t\tsql`${contentResource.id} IN (${sql.join(\n\t\t\t\t\tvideoIds.map((id: string) => sql`${id}`),\n\t\t\t\t\tsql`, `,\n\t\t\t\t)})`,\n\t\t\t)\n\n\t\t// Map video_id → playbackId\n\t\tconst idToPlayback = new Map<string, string>()\n\t\tfor (const r of rows) {\n\t\t\tif (r.playbackId && r.playbackId !== 'null') {\n\t\t\t\tidToPlayback.set(r.id, r.playbackId)\n\t\t\t}\n\t\t}\n\n\t\t// Match by position: byId and byTitle are both sorted by views desc,\n\t\t// so position i in byId corresponds to position i in byTitle\n\t\tconst result: Record<string, string> = {}\n\t\tfor (let i = 0; i < Math.min(byId.data.length, byTitle.data.length); i++) {\n\t\t\tconst videoId = byId.data[i]?.field\n\t\t\tconst title = byTitle.data[i]?.field\n\t\t\tif (!videoId || !title) continue\n\t\t\tconst playbackId = idToPlayback.get(videoId)\n\t\t\tif (playbackId) {\n\t\t\t\tresult[title] =\n\t\t\t\t\t`https://image.mux.com/${playbackId}/thumbnail.jpg?width=240&height=135&fit_mode=smartcrop`\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\treturn {\n\t\tgetComparisonTotals,\n\t\tgetViewsOverall,\n\t\tgetViewerExperienceScore,\n\t\tgetViewsTimeseries,\n\t\tgetWatchTimeTimeseries,\n\t\tgetVideoBreakdown,\n\t\tgetVideoBreakdownForRange,\n\t\tgetCountryBreakdown,\n\t\tgetVideoDetailBreakdowns,\n\t\tgetVideoDashboardData,\n\t\tgetVideoThumbnails,\n\t}\n}\n\nexport type MuxAnalyticsProvider = ReturnType<typeof createMuxProvider>\n","import { and, count, eq, sql } from 'drizzle-orm'\n\nimport type { AnalyticsRange } from '../types'\n\n// ─── Schema types ────────────────────────────────────────────────────────────\n\n/**\n * Minimal column shape required from the contentResource table.\n * We only reference the columns we actually query against.\n */\ninterface ContentResourceTable {\n\tid: any\n\ttype: any\n\tfields: any\n}\n\n/**\n * Minimal column shape required from the contentResourceResource table.\n */\ninterface ContentResourceResourceTable {\n\tresourceOfId: any\n\tresourceId: any\n}\n\n/**\n * Minimal column shape required from the questionResponse table.\n */\ninterface QuestionResponseTable {\n\tid: any\n\tsurveyId: any\n\tquestionId: any\n\trespondentKey: any\n\tsurveySessionId: any\n\tuserId: any\n\temailListSubscriberId: any\n\tcreatedAt: any\n\tupdatedAt: any\n\tfields: any\n}\n\n/**\n * Minimal column shape required from the users table.\n * Optional — only needed for getSurveyResponses to resolve user emails.\n */\ninterface UsersTable {\n\tid: any\n\temail: any\n}\n\nexport interface SurveyAnalyticsSchema {\n\tcontentResource: ContentResourceTable\n\tcontentResourceResource: ContentResourceResourceTable\n\tquestionResponse: QuestionResponseTable\n\tusers?: UsersTable\n}\n\ntype CanonicalSurveyRow = {\n\tresponseId: string\n\tsurveyId: string\n\tsurveyTitle: string | null\n\tsurveySlug: string | null\n\tquestionId: string\n\tquestionText: string | null\n\tquestionType: string | null\n\tanswer: string | null\n\trespondentKey: string\n\tsurveySessionId: string | null\n\tuserId: string | null\n\tuserEmail: string | null\n\temailListSubscriberId: string | null\n\tcreatedAt: Date | null\n\tupdatedAt: Date | null\n}\n\n// ─── Return type ─────────────────────────────────────────────────────────────\n\nexport interface SurveyAnalyticsProvider {\n\tgetSurveySummary: (range?: AnalyticsRange) => Promise<{\n\t\ttotalSurveys: number\n\t\ttotalResponses: number\n\t\tuniqueRespondents: number\n\t\tavgResponsesPerSurvey: number\n\t}>\n\n\tgetSurveyList: (range?: AnalyticsRange) => Promise<\n\t\tArray<{\n\t\t\tsurveyId: string\n\t\t\tsurveyTitle: string\n\t\t\tsurveySlug: string\n\t\t\tresponses: number\n\t\t\tuniqueRespondents: number\n\t\t\tquestionCount: number\n\t\t}>\n\t>\n\n\tgetSurveyResponsesByDay: (range?: AnalyticsRange) => Promise<\n\t\tArray<{\n\t\t\tdate: string\n\t\t\tresponses: number\n\t\t}>\n\t>\n\n\tgetSurveyQuestionBreakdown: (\n\t\trange?: AnalyticsRange,\n\t\tlimit?: number,\n\t) => Promise<\n\t\tArray<{\n\t\t\tquestionId: string\n\t\t\tquestion: string\n\t\t\ttype: string | null\n\t\t\tresponses: number\n\t\t\tuniqueRespondents: number\n\t\t\tanswerDistribution: Array<{ answer: string; count: number }>\n\t\t}>\n\t>\n\n\tgetSurveyResponses: (\n\t\trange?: AnalyticsRange,\n\t\tlimit?: number,\n\t) => Promise<\n\t\tArray<{\n\t\t\tresponseId: string\n\t\t\tsurveyId: string\n\t\t\tsurveyTitle: string\n\t\t\tsurveySlug: string\n\t\t\tquestionId: string\n\t\t\tquestionText: string\n\t\t\tquestionType: string | null\n\t\t\tanswer: string\n\t\t\tuserId: string | null\n\t\t\tuserEmail: string | null\n\t\t\temailListSubscriberId: string | null\n\t\t\tcreatedAt: string\n\t\t}>\n\t>\n}\n\nfunction normalizeRespondentKey(row: {\n\trespondentKey: string | null\n\tuserId: string | null\n\temailListSubscriberId: string | null\n\tsurveySessionId: string | null\n}) {\n\tif (row.respondentKey) return row.respondentKey\n\tif (row.userId) return `user:${row.userId}`\n\tif (row.emailListSubscriberId) {\n\t\treturn `subscriber:${row.emailListSubscriberId}`\n\t}\n\tif (row.surveySessionId) return `session:${row.surveySessionId}`\n\treturn null\n}\n\nfunction getRowTimestamp(row: {\n\tupdatedAt: Date | null\n\tcreatedAt: Date | null\n}) {\n\tconst date = row.updatedAt ?? row.createdAt\n\treturn date instanceof Date ? date.getTime() : 0\n}\n\nfunction sortByNewest<\n\tT extends { createdAt: Date | null; updatedAt: Date | null },\n>(rows: T[]) {\n\treturn [...rows].sort((a, b) => getRowTimestamp(b) - getRowTimestamp(a))\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a survey analytics provider bound to the given Drizzle db instance\n * and schema tables.\n *\n * @param db - Drizzle database instance\n * @param schema - Object containing the required table references\n */\nexport function createSurveyProvider(\n\tdb: any,\n\tschema: SurveyAnalyticsSchema,\n): SurveyAnalyticsProvider {\n\tconst { contentResource, contentResourceResource, questionResponse } = schema\n\n\t// ─── Range helpers ───────────────────────────────────────────────────────\n\n\tfunction rangeToInterval(range: AnalyticsRange): string {\n\t\tswitch (range) {\n\t\t\tcase '24h':\n\t\t\t\treturn '1 DAY'\n\t\t\tcase '7d':\n\t\t\t\treturn '7 DAY'\n\t\t\tcase '30d':\n\t\t\t\treturn '30 DAY'\n\t\t\tcase '90d':\n\t\t\t\treturn '90 DAY'\n\t\t\tcase 'all':\n\t\t\t\treturn '3650 DAY'\n\t\t}\n\t}\n\n\tfunction rangeWhere(range: AnalyticsRange, column: any) {\n\t\treturn sql`${column} >= DATE_SUB(NOW(), INTERVAL ${sql.raw(rangeToInterval(range))})`\n\t}\n\n\tasync function fetchCanonicalRows(range: AnalyticsRange) {\n\t\tconst { users } = schema\n\n\t\tconst rawRows = users\n\t\t\t? await db\n\t\t\t\t\t.select({\n\t\t\t\t\t\tresponseId: questionResponse.id,\n\t\t\t\t\t\tsurveyId: questionResponse.surveyId,\n\t\t\t\t\t\tsurveyTitle: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.title'))`,\n\t\t\t\t\t\tsurveySlug: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.slug'))`,\n\t\t\t\t\t\tquestionId: questionResponse.questionId,\n\t\t\t\t\t\tquestionText: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.question'))`,\n\t\t\t\t\t\tquestionType: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.type'))`,\n\t\t\t\t\t\tanswer: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.answer'))`,\n\t\t\t\t\t\trespondentKey: questionResponse.respondentKey,\n\t\t\t\t\t\tsurveySessionId: questionResponse.surveySessionId,\n\t\t\t\t\t\tuserId: questionResponse.userId,\n\t\t\t\t\t\tuserEmail: users.email,\n\t\t\t\t\t\temailListSubscriberId: questionResponse.emailListSubscriberId,\n\t\t\t\t\t\tcreatedAt: questionResponse.createdAt,\n\t\t\t\t\t\tupdatedAt: questionResponse.updatedAt,\n\t\t\t\t\t})\n\t\t\t\t\t.from(questionResponse)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS survey_cr`,\n\t\t\t\t\t\tsql`survey_cr.id = ${questionResponse.surveyId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS question_cr`,\n\t\t\t\t\t\tsql`question_cr.id = ${questionResponse.questionId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.leftJoin(users, eq(questionResponse.userId, users.id))\n\t\t\t\t\t.where(rangeWhere(range, questionResponse.createdAt))\n\t\t\t: await db\n\t\t\t\t\t.select({\n\t\t\t\t\t\tresponseId: questionResponse.id,\n\t\t\t\t\t\tsurveyId: questionResponse.surveyId,\n\t\t\t\t\t\tsurveyTitle: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.title'))`,\n\t\t\t\t\t\tsurveySlug: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.slug'))`,\n\t\t\t\t\t\tquestionId: questionResponse.questionId,\n\t\t\t\t\t\tquestionText: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.question'))`,\n\t\t\t\t\t\tquestionType: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.type'))`,\n\t\t\t\t\t\tanswer: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.answer'))`,\n\t\t\t\t\t\trespondentKey: questionResponse.respondentKey,\n\t\t\t\t\t\tsurveySessionId: questionResponse.surveySessionId,\n\t\t\t\t\t\tuserId: questionResponse.userId,\n\t\t\t\t\t\tuserEmail: sql<string | null>`NULL`,\n\t\t\t\t\t\temailListSubscriberId: questionResponse.emailListSubscriberId,\n\t\t\t\t\t\tcreatedAt: questionResponse.createdAt,\n\t\t\t\t\t\tupdatedAt: questionResponse.updatedAt,\n\t\t\t\t\t})\n\t\t\t\t\t.from(questionResponse)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS survey_cr`,\n\t\t\t\t\t\tsql`survey_cr.id = ${questionResponse.surveyId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS question_cr`,\n\t\t\t\t\t\tsql`question_cr.id = ${questionResponse.questionId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.where(rangeWhere(range, questionResponse.createdAt))\n\n\t\tconst latestByAnswer = new Map<string, CanonicalSurveyRow>()\n\n\t\tfor (const row of rawRows) {\n\t\t\tconst respondentKey = normalizeRespondentKey(row)\n\t\t\tif (!respondentKey) continue\n\n\t\t\tconst dedupeKey = `${row.surveyId}::${row.questionId}::${respondentKey}`\n\t\t\tconst current = latestByAnswer.get(dedupeKey)\n\n\t\t\tif (!current || getRowTimestamp(row) >= getRowTimestamp(current)) {\n\t\t\t\tlatestByAnswer.set(dedupeKey, {\n\t\t\t\t\t...row,\n\t\t\t\t\trespondentKey,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\treturn sortByNewest(Array.from(latestByAnswer.values()))\n\t}\n\n\t// ─── Survey Summary ──────────────────────────────────────────────────────\n\n\tasync function getSurveySummary(range: AnalyticsRange = '30d') {\n\t\tconst [surveyCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(contentResource)\n\t\t\t.where(eq(contentResource.type, 'survey'))\n\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst respondentKeys = new Set(\n\t\t\tcanonicalRows.map((row) => row.respondentKey),\n\t\t)\n\t\tconst totalSurveys = surveyCount?.total ?? 0\n\t\tconst totalResponses = canonicalRows.length\n\n\t\treturn {\n\t\t\ttotalSurveys,\n\t\t\ttotalResponses,\n\t\t\tuniqueRespondents: respondentKeys.size,\n\t\t\tavgResponsesPerSurvey:\n\t\t\t\ttotalSurveys > 0 ? totalResponses / totalSurveys : 0,\n\t\t}\n\t}\n\n\t// ─── Survey List ─────────────────────────────────────────────────────────\n\n\tasync function getSurveyList(range: AnalyticsRange = '30d') {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst responsesBySurvey = new Map<\n\t\t\tstring,\n\t\t\t{ responses: number; respondents: Set<string> }\n\t\t>()\n\n\t\tfor (const row of canonicalRows) {\n\t\t\tconst current = responsesBySurvey.get(row.surveyId) ?? {\n\t\t\t\tresponses: 0,\n\t\t\t\trespondents: new Set<string>(),\n\t\t\t}\n\t\t\tcurrent.responses += 1\n\t\t\tcurrent.respondents.add(row.respondentKey)\n\t\t\tresponsesBySurvey.set(row.surveyId, current)\n\t\t}\n\n\t\tconst surveys = await db\n\t\t\t.select({\n\t\t\t\tsurveyId: contentResource.id,\n\t\t\t\tsurveyTitle: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${contentResource.fields}, '$.title'))`,\n\t\t\t\tsurveySlug: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${contentResource.fields}, '$.slug'))`,\n\t\t\t})\n\t\t\t.from(contentResource)\n\t\t\t.where(eq(contentResource.type, 'survey'))\n\n\t\tconst questionCounts = await db\n\t\t\t.select({\n\t\t\t\tsurveyId: contentResourceResource.resourceOfId,\n\t\t\t\tquestionCount: count(),\n\t\t\t})\n\t\t\t.from(contentResourceResource)\n\t\t\t.innerJoin(\n\t\t\t\tcontentResource,\n\t\t\t\tand(\n\t\t\t\t\teq(contentResourceResource.resourceId, contentResource.id),\n\t\t\t\t\teq(contentResource.type, 'question'),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.groupBy(contentResourceResource.resourceOfId)\n\n\t\tconst questionCountMap = new Map(\n\t\t\tquestionCounts.map((qc: { surveyId: string; questionCount: number }) => [\n\t\t\t\tqc.surveyId,\n\t\t\t\tqc.questionCount,\n\t\t\t]),\n\t\t)\n\n\t\treturn surveys\n\t\t\t.map(\n\t\t\t\t(s: {\n\t\t\t\t\tsurveyId: string\n\t\t\t\t\tsurveyTitle: string | null\n\t\t\t\t\tsurveySlug: string | null\n\t\t\t\t}) => {\n\t\t\t\t\tconst counts = responsesBySurvey.get(s.surveyId)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsurveyId: s.surveyId,\n\t\t\t\t\t\tsurveyTitle: s.surveyTitle ?? '',\n\t\t\t\t\t\tsurveySlug: s.surveySlug ?? '',\n\t\t\t\t\t\tresponses: counts?.responses ?? 0,\n\t\t\t\t\t\tuniqueRespondents: counts?.respondents.size ?? 0,\n\t\t\t\t\t\tquestionCount: questionCountMap.get(s.surveyId) ?? 0,\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t\t.sort(\n\t\t\t\t(a: { responses: number }, b: { responses: number }) =>\n\t\t\t\t\tb.responses - a.responses,\n\t\t\t)\n\t}\n\n\t// ─── Daily Responses ─────────────────────────────────────────────────────\n\n\tasync function getSurveyResponsesByDay(range: AnalyticsRange = '30d') {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst grouped = new Map<string, number>()\n\n\t\tfor (const row of canonicalRows) {\n\t\t\tif (!(row.createdAt instanceof Date)) continue\n\t\t\tconst date = row.createdAt.toISOString().slice(0, 10)\n\t\t\tgrouped.set(date, (grouped.get(date) ?? 0) + 1)\n\t\t}\n\n\t\treturn Array.from(grouped.entries())\n\t\t\t.sort((entryA: [string, number], entryB: [string, number]) =>\n\t\t\t\tentryA[0].localeCompare(entryB[0]),\n\t\t\t)\n\t\t\t.map(([date, responses]) => ({ date, responses }))\n\t}\n\n\t// ─── Question Breakdown ──────────────────────────────────────────────────\n\n\tasync function getSurveyQuestionBreakdown(\n\t\trange: AnalyticsRange = '30d',\n\t\tlimit = 20,\n\t) {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst grouped = new Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tquestionId: string\n\t\t\t\tquestion: string\n\t\t\t\ttype: string | null\n\t\t\t\tresponses: number\n\t\t\t\trespondents: Set<string>\n\t\t\t\tanswers: Map<string, number>\n\t\t\t}\n\t\t>()\n\n\t\tfor (const row of canonicalRows) {\n\t\t\tconst current = grouped.get(row.questionId) ?? {\n\t\t\t\tquestionId: row.questionId,\n\t\t\t\tquestion: row.questionText ?? '',\n\t\t\t\ttype: row.questionType ?? null,\n\t\t\t\tresponses: 0,\n\t\t\t\trespondents: new Set<string>(),\n\t\t\t\tanswers: new Map<string, number>(),\n\t\t\t}\n\n\t\t\tcurrent.responses += 1\n\t\t\tcurrent.respondents.add(row.respondentKey)\n\t\t\tconst answer = row.answer ?? '(no answer)'\n\t\t\tcurrent.answers.set(answer, (current.answers.get(answer) ?? 0) + 1)\n\t\t\tgrouped.set(row.questionId, current)\n\t\t}\n\n\t\treturn Array.from(grouped.values())\n\t\t\t.sort(\n\t\t\t\t(a: { responses: number }, b: { responses: number }) =>\n\t\t\t\t\tb.responses - a.responses,\n\t\t\t)\n\t\t\t.slice(0, limit)\n\t\t\t.map((entry) => ({\n\t\t\t\tquestionId: entry.questionId,\n\t\t\t\tquestion: entry.question,\n\t\t\t\ttype: entry.type,\n\t\t\t\tresponses: entry.responses,\n\t\t\t\tuniqueRespondents: entry.respondents.size,\n\t\t\t\tanswerDistribution: Array.from(entry.answers.entries())\n\t\t\t\t\t.sort((a: [string, number], b: [string, number]) => b[1] - a[1])\n\t\t\t\t\t.map(([answer, count]) => ({ answer, count })),\n\t\t\t}))\n\t}\n\n\t// ─── Individual Response Rows ────────────────────────────────────────────\n\n\tasync function getSurveyResponses(\n\t\trange: AnalyticsRange = '30d',\n\t\tlimit = 100,\n\t) {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\n\t\treturn canonicalRows.slice(0, limit).map((row) => ({\n\t\t\tresponseId: row.responseId,\n\t\t\tsurveyId: row.surveyId,\n\t\t\tsurveyTitle: row.surveyTitle ?? '',\n\t\t\tsurveySlug: row.surveySlug ?? '',\n\t\t\tquestionId: row.questionId,\n\t\t\tquestionText: row.questionText ?? '',\n\t\t\tquestionType: row.questionType ?? null,\n\t\t\tanswer: row.answer ?? '',\n\t\t\tuserId: row.userId ?? null,\n\t\t\tuserEmail: row.userEmail ?? null,\n\t\t\temailListSubscriberId: row.emailListSubscriberId ?? null,\n\t\t\tcreatedAt: row.createdAt ? String(row.createdAt) : '',\n\t\t}))\n\t}\n\n\treturn {\n\t\tgetSurveySummary,\n\t\tgetSurveyList,\n\t\tgetSurveyResponsesByDay,\n\t\tgetSurveyQuestionBreakdown,\n\t\tgetSurveyResponses,\n\t}\n}\n"],"mappings":"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","names":["and","count","desc","eq","gt","gte","inArray","lte","sql","sum","createDatabaseProvider","db","schema","purchases","products","users","coupon","resourceProgress","shortlink","shortlinkAttribution","shortlinkClick","PAID_STATUSES","paidPurchase","inArray","status","rangeToDate","range","now","Date","hours","getTime","getRevenueSummary","since","conditions","push","gte","createdAt","totals","select","totalRevenue","sum","totalAmount","purchaseCount","count","from","where","and","Number","avgOrderValue","getRevenueByDay","rows","date","sql","as","revenue","groupBy","orderBy","map","r","getPreviousPeriodRevenueByDay","periodMs","periodStart","prevStart","lte","getRevenueByProduct","productId","productName","name","leftJoin","eq","id","desc","getRevenueByCountry","country","limit","getRecentPurchases","filter","bulkCouponId","couponId","userId","userName","userEmail","email","organizationId","seats","maxUses","gt","isTeam","query","findMany","with","product","user","getAttributionSummary","type","length","undefined","getShortlinkPerformance","clickConditions","timestamp","shortlinkId","slug","url","clicks","innerJoin","attrConditions","attrRows","attrMap","Map","a","existing","get","signups","set","attr","getRevenueBySource","source","fields","medium","campaign","getConversionFunnel","userConditions","purchaseConditions","userCount","total","attributedCount","totalSignups","totalPurchases","attributedPurchases","conversionRate","attributionCoverage","getAttributedRevenueSummary","attributed","attributedRevenue","unattributedRevenue","attributionRate","getContentPurchaseCorrelation","purchaserRows","selectDistinct","purchaserIds","resourceId","purchaserCount","join","traceAttribution","opts","events","userRecord","purchaseId","purchase","u","attrs","metadata","referrer","device","click","detail","destination","JSON","parse","progress","completedAt","p","purchaseRows","purchaseResults","amount","utmSource","utmMedium","utmCampaign","sort","b","BetaAnalyticsDataClient","createGA4Provider","config","propertyId","clientEmail","privateKey","_client","getClient","BetaAnalyticsDataClient","credentials","client_email","private_key","replace","rangeToDateRange","range","map","startDate","endDate","getTrafficOverview","client","response","runReport","property","dateRanges","metrics","name","row","rows","sessions","totalUsers","newUsers","pageviews","avgSessionDuration","bounceRate","Number","metricValues","value","getTopPages","limit","dimensions","orderBys","metric","metricName","desc","path","dimensionValues","users","avgDuration","getTrafficSources","source","medium","getSessionsByDay","dimension","dimensionName","raw","date","slice","createDerivedProvider","deps","database","ga4","toGA4Range","range","getTrafficRevenueCorrelation","traffic","revenue","Promise","all","getSessionsByDay","getRevenueByDay","sql","createMuxProvider","config","dbDeps","MUX_DATA_BASE","getAuthHeader","Buffer","from","tokenId","tokenSecret","toString","muxDataFetch","path","params","url","URL","key","value","Object","entries","Array","isArray","v","searchParams","append","set","response","fetch","headers","Authorization","next","revalidate","ok","Error","status","statusText","json","getComparisonTotals","timeRange","resp","totals","data","find","d","name","uniqueViewers","unique_viewers","viewCount","view_count","watchTimeMs","watch_time","getViewsOverall","getViewerExperienceScore","getViewsTimeseries","group_by","getWatchTimeTimeseries","getVideoBreakdown","limit","order_by","order_direction","String","getVideoBreakdownForRange","getCountryBreakdown","getVideoDetailBreakdowns","videoTitle","filter","countries","timeseries","Promise","all","map","c","country","field","views","total_watch_time","date","getVideoDashboardData","experience","comparison","watchTime","videos","overview","totalViews","total_views","totalWatchTimeMs","totalPlayingTimeMs","total_playing_time","viewerExperienceScore","globalExperienceScore","global_value","watchTimeSeries","totalMs","topVideos","title","playingTimeMs","getVideoThumbnails","db","contentResource","byId","byTitle","videoIds","length","rows","select","id","playbackId","sql","fields","as","where","join","idToPlayback","Map","r","result","i","Math","min","videoId","get","and","count","eq","sql","normalizeRespondentKey","row","respondentKey","userId","emailListSubscriberId","surveySessionId","getRowTimestamp","date","updatedAt","createdAt","Date","getTime","sortByNewest","rows","sort","a","b","createSurveyProvider","db","schema","contentResource","contentResourceResource","questionResponse","rangeToInterval","range","rangeWhere","column","sql","raw","fetchCanonicalRows","users","rawRows","select","responseId","id","surveyId","surveyTitle","surveySlug","questionId","questionText","questionType","answer","fields","userEmail","email","from","leftJoin","eq","where","latestByAnswer","Map","dedupeKey","current","get","set","Array","values","getSurveySummary","surveyCount","total","count","type","canonicalRows","respondentKeys","Set","map","totalSurveys","totalResponses","length","uniqueRespondents","size","avgResponsesPerSurvey","getSurveyList","responsesBySurvey","responses","respondents","add","surveys","questionCounts","resourceOfId","questionCount","innerJoin","and","resourceId","groupBy","questionCountMap","qc","s","counts","getSurveyResponsesByDay","grouped","toISOString","slice","entries","entryA","entryB","localeCompare","getSurveyQuestionBreakdown","limit","question","answers","entry","answerDistribution","getSurveyResponses","String"]}
1
+ {"version":3,"sources":["../../src/providers/database.ts","../../src/providers/attribution-recovery.ts","../../src/providers/ga4.ts","../../src/providers/derived.ts","../../src/providers/mux.ts","../../src/providers/survey.ts"],"sourcesContent":["import {\n\tand,\n\tcount,\n\tdesc,\n\teq,\n\tgt,\n\tgte,\n\tinArray,\n\tlte,\n\tsql,\n\tsum,\n} from 'drizzle-orm'\n\nimport { SHORTLINK_RECOVERY_LANE } from './attribution-recovery'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnalyticsTimeRange = '24h' | '7d' | '30d' | '90d' | 'all'\n\nexport interface AttributionTrailEvent {\n\ttype: 'click' | 'signup' | 'progress' | 'purchase'\n\ttimestamp: Date\n\tdetail: Record<string, any>\n}\n\nexport interface AttributionTrail {\n\tuser: {\n\t\tid: string\n\t\temail: string | null\n\t\tname: string | null\n\t\tcreatedAt: Date\n\t} | null\n\tevents: AttributionTrailEvent[]\n\tpurchases: {\n\t\tid: string\n\t\ttotalAmount: number\n\t\tproductName: string\n\t\tcreatedAt: Date\n\t\tcountry: string | null\n\t\tutmSource: string | null\n\t\tutmMedium: string | null\n\t\tutmCampaign: string | null\n\t}[]\n}\n\n// ─── Schema type ─────────────────────────────────────────────────────────────\n\nexport interface DatabaseAnalyticsSchema {\n\tpurchases: any\n\tproducts: any\n\tusers: any\n\tcoupon: any\n\tresourceProgress: any\n\tshortlink: any\n\tshortlinkAttribution: any\n\tshortlinkClick: any\n\tquestionResponse?: any\n\tcontactEvent?: any\n\tsideEffectIntent?: any\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a database analytics provider that wraps all analytics query\n * functions with an injected drizzle db instance and schema tables.\n *\n * @param db - Drizzle database instance\n * @param schema - Object containing the required table references\n */\nexport function createDatabaseProvider(\n\tdb: any,\n\tschema: DatabaseAnalyticsSchema,\n) {\n\tconst {\n\t\tpurchases,\n\t\tproducts,\n\t\tusers,\n\t\tcoupon,\n\t\tresourceProgress,\n\t\tshortlink,\n\t\tshortlinkAttribution,\n\t\tshortlinkClick,\n\t\tquestionResponse,\n\t\tcontactEvent,\n\t\tsideEffectIntent,\n\t} = schema\n\n\t// ─── Internal helpers ───────────────────────────────────────────────────\n\n\tconst PAID_STATUSES = ['Valid', 'Restricted'] as const\n\n\tfunction commerceRecord() {\n\t\treturn inArray(purchases.status, [...PAID_STATUSES])\n\t}\n\n\tfunction paidPurchase() {\n\t\treturn and(commerceRecord(), gt(purchases.totalAmount, 0))\n\t}\n\n\tfunction accessGrant() {\n\t\treturn and(commerceRecord(), sql`${purchases.totalAmount} = 0`)\n\t}\n\n\tfunction backfillFreeUpgrade() {\n\t\treturn and(\n\t\t\taccessGrant(),\n\t\t\tsql`JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.backfill')) = 'true'`,\n\t\t)\n\t}\n\n\tconst syntheticSignalSql = sql`(\n\t\tJSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.synthetic')) = 'true'\n\t\tOR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.synthetic')) = 'true'\n\t\tOR JSON_SEARCH(JSON_EXTRACT(${purchases.fields}, '$.attribution.clickIds'), 'one', 'TEST_%') IS NOT NULL\n\t)`\n\n\tfunction syntheticPurchase() {\n\t\treturn and(commerceRecord(), syntheticSignalSql)\n\t}\n\n\tconst purchaseFieldAttributionSignalSql = sql`(\n\t\tJSON_EXTRACT(${purchases.fields}, '$.attribution') IS NOT NULL\n\t\tOR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource')) IS NOT NULL\n\t\tOR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.gaClientId')) IS NOT NULL\n\t)`\n\n\tconst exactShortlinkPurchaseAttributionSignalSql = sql`EXISTS (\n\t\tSELECT 1\n\t\tFROM ${shortlinkAttribution}\n\t\tWHERE ${shortlinkAttribution.type} = 'purchase'\n\t\t\tAND JSON_VALID(${shortlinkAttribution.metadata})\n\t\t\tAND JSON_UNQUOTE(JSON_EXTRACT(${shortlinkAttribution.metadata}, '$.purchaseId')) = ${purchases.id}\n\t)`\n\n\tconst shortlinkRecoveredAttributionSignalSql = sql`(\n\t\tNOT ${purchaseFieldAttributionSignalSql}\n\t\tAND ${exactShortlinkPurchaseAttributionSignalSql}\n\t)`\n\n\tconst attributionSignalSql = sql`(\n\t\t${purchaseFieldAttributionSignalSql}\n\t\tOR ${exactShortlinkPurchaseAttributionSignalSql}\n\t)`\n\n\tfunction rangeToDate(range: AnalyticsTimeRange): Date | null {\n\t\tif (range === 'all') return null\n\t\tconst now = new Date()\n\t\tconst hours: Record<string, number> = {\n\t\t\t'24h': 24,\n\t\t\t'7d': 7 * 24,\n\t\t\t'30d': 30 * 24,\n\t\t\t'90d': 90 * 24,\n\t\t}\n\t\treturn new Date(now.getTime() - (hours[range] ?? 30 * 24) * 60 * 60 * 1000)\n\t}\n\n\t// ─── Revenue ───────────────────────────────────────────────────────────\n\n\tasync function getRevenueSummary(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst [totals] = await db\n\t\t\t.select({\n\t\t\t\ttotalRevenue: sum(purchases.totalAmount),\n\t\t\t\tpurchaseCount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\n\t\treturn {\n\t\t\ttotalRevenue: Number(totals?.totalRevenue ?? 0),\n\t\t\tpurchaseCount: totals?.purchaseCount ?? 0,\n\t\t\tavgOrderValue:\n\t\t\t\ttotals?.purchaseCount && totals.purchaseCount > 0\n\t\t\t\t\t? Number(totals.totalRevenue ?? 0) / totals.purchaseCount\n\t\t\t\t\t: 0,\n\t\t}\n\t}\n\n\tasync function getRevenueByDay(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tdate: sql<string>`DATE(${purchases.createdAt})`.as('date'),\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(sql`DATE(${purchases.createdAt})`)\n\t\t\t.orderBy(sql`DATE(${purchases.createdAt})`)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tdate: r.date,\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\t/**\n\t * Revenue by day for the previous period of equal length.\n\t * E.g., if range = '30d', returns the 30 days before those 30 days.\n\t * Returns data with a `dayOffset` (0 = start of period) for overlay\n\t * alignment.\n\t */\n\tasync function getPreviousPeriodRevenueByDay(\n\t\trange: AnalyticsTimeRange = '30d',\n\t) {\n\t\tif (range === 'all') return []\n\n\t\tconst hours: Record<string, number> = {\n\t\t\t'24h': 24,\n\t\t\t'7d': 7 * 24,\n\t\t\t'30d': 30 * 24,\n\t\t\t'90d': 90 * 24,\n\t\t}\n\t\tconst periodMs = (hours[range] ?? 30 * 24) * 60 * 60 * 1000\n\t\tconst now = new Date()\n\t\tconst periodStart = new Date(now.getTime() - periodMs)\n\t\tconst prevStart = new Date(periodStart.getTime() - periodMs)\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tdate: sql<string>`DATE(${purchases.createdAt})`.as('date'),\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(\n\t\t\t\tand(\n\t\t\t\t\tpaidPurchase(),\n\t\t\t\t\tgte(purchases.createdAt, prevStart),\n\t\t\t\t\tlte(purchases.createdAt, periodStart),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.groupBy(sql`DATE(${purchases.createdAt})`)\n\t\t\t.orderBy(sql`DATE(${purchases.createdAt})`)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tdate: r.date,\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getRevenueByProduct(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tproductId: purchases.productId,\n\t\t\t\tproductName: products.name,\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(purchases.productId, products.name)\n\t\t\t.orderBy(desc(sum(purchases.totalAmount)))\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tproductId: r.productId,\n\t\t\tproductName: r.productName ?? '(unknown)',\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getRevenueByCountry(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tcountry: purchases.country,\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(purchases.country)\n\t\t\t.orderBy(desc(sum(purchases.totalAmount)))\n\t\t\t.limit(20)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tcountry: r.country ?? '(unknown)',\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getRecentPurchases(\n\t\tlimit: number = 20,\n\t\tfilter: 'all' | 'team' | 'individual' = 'all',\n\t\trange: AnalyticsTimeRange = 'all',\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\n\t\tif (filter === 'team') {\n\t\t\t// Multi-seat purchases: join coupon to filter seats > 1, sort by amount\n\t\t\tconditions.push(sql`${purchases.bulkCouponId} IS NOT NULL`)\n\n\t\t\tconst rows = await db\n\t\t\t\t.select({\n\t\t\t\t\tid: purchases.id,\n\t\t\t\t\tcreatedAt: purchases.createdAt,\n\t\t\t\t\ttotalAmount: purchases.totalAmount,\n\t\t\t\t\tproductName: products.name,\n\t\t\t\t\tproductId: purchases.productId,\n\t\t\t\t\tcountry: purchases.country,\n\t\t\t\t\tcouponId: purchases.couponId,\n\t\t\t\t\tuserId: purchases.userId,\n\t\t\t\t\tuserName: users.name,\n\t\t\t\t\tuserEmail: users.email,\n\t\t\t\t\torganizationId: purchases.organizationId,\n\t\t\t\t\tseats: coupon.maxUses,\n\t\t\t\t})\n\t\t\t\t.from(purchases)\n\t\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t\t.leftJoin(users, eq(purchases.userId, users.id))\n\t\t\t\t.leftJoin(coupon, eq(purchases.bulkCouponId, coupon.id))\n\t\t\t\t.where(and(...conditions, gt(coupon.maxUses, 1)))\n\t\t\t\t.orderBy(desc(purchases.totalAmount))\n\t\t\t\t.limit(limit)\n\n\t\t\treturn rows.map((r: any) => ({\n\t\t\t\tid: r.id,\n\t\t\t\tcreatedAt: r.createdAt,\n\t\t\t\ttotalAmount: Number(r.totalAmount),\n\t\t\t\tproductName: r.productName ?? '(unknown)',\n\t\t\t\tproductId: r.productId,\n\t\t\t\tcountry: r.country,\n\t\t\t\tcouponId: r.couponId,\n\t\t\t\tuserName: r.userName ?? null,\n\t\t\t\tuserEmail: r.userEmail ?? null,\n\t\t\t\tisTeam: true,\n\t\t\t\tseats: r.seats ?? null,\n\t\t\t}))\n\t\t}\n\n\t\tif (filter === 'individual') {\n\t\t\tconditions.push(sql`${purchases.bulkCouponId} IS NULL`)\n\t\t}\n\n\t\tconst rows = await db.query.purchases.findMany({\n\t\t\twhere: and(...conditions),\n\t\t\torderBy: [desc(purchases.totalAmount)],\n\t\t\tlimit,\n\t\t\twith: {\n\t\t\t\tproduct: true,\n\t\t\t\tuser: true,\n\t\t\t},\n\t\t})\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tid: r.id,\n\t\t\tcreatedAt: r.createdAt,\n\t\t\ttotalAmount: Number(r.totalAmount),\n\t\t\tproductName: r.product?.name ?? '(unknown)',\n\t\t\tproductId: r.productId,\n\t\t\tcountry: r.country,\n\t\t\tcouponId: r.couponId,\n\t\t\tuserName: r.user?.name ?? null,\n\t\t\tuserEmail: r.user?.email ?? null,\n\t\t\tisTeam: r.organizationId != null,\n\t\t\tseats: null as number | null,\n\t\t}))\n\t}\n\n\t// ─── Attribution ────────────────────────────────────────────────────────\n\n\tasync function getAttributionSummary(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions: any[] = []\n\t\tif (since) conditions.push(gte(shortlinkAttribution.createdAt, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\ttype: shortlinkAttribution.type,\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(shortlinkAttribution)\n\t\t\t.where(conditions.length > 0 ? and(...conditions) : undefined)\n\t\t\t.groupBy(shortlinkAttribution.type)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\ttype: r.type,\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getShortlinkPerformance(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\t\tconst clickConditions: any[] = []\n\t\tif (since) clickConditions.push(gte(shortlinkClick.timestamp, since))\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tshortlinkId: shortlinkClick.shortlinkId,\n\t\t\t\tslug: shortlink.slug,\n\t\t\t\turl: shortlink.url,\n\t\t\t\tclicks: count(),\n\t\t\t})\n\t\t\t.from(shortlinkClick)\n\t\t\t.innerJoin(shortlink, eq(shortlinkClick.shortlinkId, shortlink.id))\n\t\t\t.where(clickConditions.length > 0 ? and(...clickConditions) : undefined)\n\t\t\t.groupBy(shortlinkClick.shortlinkId, shortlink.slug, shortlink.url)\n\t\t\t.orderBy(desc(count()))\n\t\t\t.limit(20)\n\n\t\t// Get attribution counts per shortlink\n\t\tconst attrConditions: any[] = []\n\t\tif (since) attrConditions.push(gte(shortlinkAttribution.createdAt, since))\n\n\t\tconst attrRows = await db\n\t\t\t.select({\n\t\t\t\tshortlinkId: shortlinkAttribution.shortlinkId,\n\t\t\t\ttype: shortlinkAttribution.type,\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(shortlinkAttribution)\n\t\t\t.where(attrConditions.length > 0 ? and(...attrConditions) : undefined)\n\t\t\t.groupBy(shortlinkAttribution.shortlinkId, shortlinkAttribution.type)\n\n\t\tconst attrMap = new Map<string, { signups: number; purchases: number }>()\n\t\tfor (const a of attrRows) {\n\t\t\tconst existing = attrMap.get(a.shortlinkId) ?? {\n\t\t\t\tsignups: 0,\n\t\t\t\tpurchases: 0,\n\t\t\t}\n\t\t\tif (a.type === 'signup') existing.signups = a.count\n\t\t\tif (a.type === 'purchase') existing.purchases = a.count\n\t\t\tattrMap.set(a.shortlinkId, existing)\n\t\t}\n\n\t\treturn rows.map((r: any) => {\n\t\t\tconst attr = attrMap.get(r.shortlinkId)\n\t\t\treturn {\n\t\t\t\tshortlinkId: r.shortlinkId,\n\t\t\t\tslug: r.slug,\n\t\t\t\turl: r.url,\n\t\t\t\tclicks: r.clicks,\n\t\t\t\tsignups: attr?.signups ?? 0,\n\t\t\t\tpurchases: attr?.purchases ?? 0,\n\t\t\t}\n\t\t})\n\t}\n\n\t// ─── Revenue Attribution ─────────────────────────────────────────────────\n\n\tasync function getRevenueBySource(\n\t\trange: AnalyticsTimeRange = '30d',\n\t\tfilters: { productId?: string } = {},\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [commerceRecord()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\t\tif (filters.productId)\n\t\t\tconditions.push(eq(purchases.productId, filters.productId))\n\n\t\tconst kindExpr = sql<string>`CASE\n\t\t\tWHEN ${syntheticSignalSql} THEN 'synthetic'\n\t\t\tWHEN ${purchases.totalAmount} = 0 THEN 'access_grant'\n\t\t\tWHEN ${purchases.totalAmount} > 0 THEN 'paid_conversion'\n\t\t\tELSE 'unknown'\n\t\tEND`\n\t\tconst sourceExpr = sql<string>`CASE\n\t\t\tWHEN JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.backfill')) = 'true'\n\t\t\t\tTHEN 'internal'\n\t\t\tWHEN ${shortlinkRecoveredAttributionSignalSql} THEN ${SHORTLINK_RECOVERY_LANE}\n\t\t\tELSE COALESCE(\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.source')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.utm.source')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource')), 'null'),\n\t\t\t\tCASE\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.attribution.shortlink.slug') IS NOT NULL THEN 'shortlink'\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.attribution.selfReportedSource') IS NOT NULL THEN 'self_reported'\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.attribution.ga.clientId') IS NOT NULL THEN 'ga4'\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.gaClientId') IS NOT NULL THEN 'ga4'\n\t\t\t\t\tELSE NULL\n\t\t\t\tEND\n\t\t\t)\n\t\tEND`\n\t\tconst mediumExpr = sql<string>`CASE\n\t\t\tWHEN JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.backfill')) = 'true'\n\t\t\t\tTHEN 'free_upgrade'\n\t\t\tWHEN ${shortlinkRecoveredAttributionSignalSql} THEN 'shortlink'\n\t\t\tELSE COALESCE(\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.medium')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.utm.medium')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmMedium')), 'null'),\n\t\t\t\tCASE\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.attribution.shortlink.slug') IS NOT NULL THEN 'shortlink'\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.attribution.selfReportedSource') IS NOT NULL THEN 'checkout_survey'\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.attribution.ga.clientId') IS NOT NULL THEN 'client_id'\n\t\t\t\t\tWHEN JSON_EXTRACT(${purchases.fields}, '$.gaClientId') IS NOT NULL THEN 'client_id'\n\t\t\t\t\tELSE NULL\n\t\t\t\tEND\n\t\t\t)\n\t\tEND`\n\t\tconst campaignExpr = sql<string>`CASE\n\t\t\tWHEN JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.backfill')) = 'true'\n\t\t\t\tTHEN COALESCE(\n\t\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.migrationBatchId')), 'null'),\n\t\t\t\t\t'free_upgrade'\n\t\t\t\t)\n\t\t\tWHEN ${shortlinkRecoveredAttributionSignalSql} THEN NULL\n\t\t\tELSE COALESCE(\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.campaign')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.utm.campaign')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmCampaign')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.shortlink.slug')), 'null'),\n\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.attribution.selfReportedSource')), 'null')\n\t\t\t)\n\t\tEND`\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tkind: kindExpr.as('kind'),\n\t\t\t\tsource: sourceExpr.as('source'),\n\t\t\t\tmedium: mediumExpr.as('medium'),\n\t\t\t\tcampaign: campaignExpr.as('campaign'),\n\t\t\t\trevenue: sum(purchases.totalAmount),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\t\t.groupBy(sql`1`, sql`2`, sql`3`, sql`4`)\n\t\t\t.orderBy(desc(sum(purchases.totalAmount)), desc(count()))\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tkind: r.kind ?? 'unknown',\n\t\t\tsource: r.source ?? null,\n\t\t\tmedium: r.medium ?? null,\n\t\t\tcampaign: r.campaign ?? null,\n\t\t\trevenue: Number(r.revenue ?? 0),\n\t\t\tcount: r.count,\n\t\t}))\n\t}\n\n\tasync function getConversionFunnel(range: AnalyticsTimeRange = '30d') {\n\t\tconst since = rangeToDate(range)\n\n\t\tconst userConditions = since ? [gte(users.createdAt, since)] : []\n\t\tconst purchaseConditions = [paidPurchase()]\n\t\tif (since) purchaseConditions.push(gte(purchases.createdAt, since))\n\n\t\tconst [userCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(users)\n\t\t\t.where(userConditions.length > 0 ? and(...userConditions) : undefined)\n\n\t\tconst [purchaseCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...purchaseConditions))\n\n\t\tconst [attributedCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...purchaseConditions, attributionSignalSql))\n\n\t\tconst totalSignups = userCount?.total ?? 0\n\t\tconst totalPurchases = purchaseCount?.total ?? 0\n\t\tconst attributedPurchases = attributedCount?.total ?? 0\n\n\t\treturn {\n\t\t\ttotalSignups,\n\t\t\ttotalPurchases,\n\t\t\tattributedPurchases,\n\t\t\tconversionRate: totalSignups > 0 ? totalPurchases / totalSignups : 0,\n\t\t\tattributionCoverage:\n\t\t\t\ttotalPurchases > 0 ? attributedPurchases / totalPurchases : 0,\n\t\t}\n\t}\n\n\tasync function getCommerceLaneSummary(\n\t\trange: AnalyticsTimeRange = '30d',\n\t\tfilters: { productId?: string } = {},\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [commerceRecord()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\t\tif (filters.productId)\n\t\t\tconditions.push(eq(purchases.productId, filters.productId))\n\n\t\tconst [commerce] = await db\n\t\t\t.select({ count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\t\tconst [paid] = await db\n\t\t\t.select({ count: count(), revenue: sum(purchases.totalAmount) })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, paidPurchase()))\n\t\tconst [grants] = await db\n\t\t\t.select({ count: count(), revenue: sum(purchases.totalAmount) })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, accessGrant()))\n\t\tconst [freeUpgrades] = await db\n\t\t\t.select({ count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, backfillFreeUpgrade()))\n\t\tconst [synthetic] = await db\n\t\t\t.select({ count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, syntheticPurchase()))\n\t\tconst reasonRows = await db\n\t\t\t.select({\n\t\t\t\treason: sql<string>`COALESCE(\n\t\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.backfillReason')), 'null'),\n\t\t\t\t\t'access_grant'\n\t\t\t\t)`.as('reason'),\n\t\t\t\tcount: count(),\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, accessGrant()))\n\t\t\t.groupBy(\n\t\t\t\tsql`COALESCE(\n\t\t\t\t\tNULLIF(JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.backfillReason')), 'null'),\n\t\t\t\t\t'access_grant'\n\t\t\t\t)`,\n\t\t\t)\n\n\t\treturn {\n\t\t\tcommerceRecords: commerce?.count ?? 0,\n\t\t\tpaidPurchases: paid?.count ?? 0,\n\t\t\tpaidRevenue: Number(paid?.revenue ?? 0),\n\t\t\taccessGrants: grants?.count ?? 0,\n\t\t\taccessGrantRevenue: Number(grants?.revenue ?? 0),\n\t\t\tfreeUpgrades: freeUpgrades?.count ?? 0,\n\t\t\tsyntheticPurchases: synthetic?.count ?? 0,\n\t\t\tbyAccessGrantReason: Object.fromEntries(\n\t\t\t\treasonRows.map((row: any) => [row.reason, row.count]),\n\t\t\t),\n\t\t}\n\t}\n\n\tasync function getAttributedRevenueSummary(\n\t\trange: AnalyticsTimeRange = '30d',\n\t\tfilters: { productId?: string } = {},\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst conditions = [paidPurchase()]\n\t\tif (since) conditions.push(gte(purchases.createdAt, since))\n\t\tif (filters.productId)\n\t\t\tconditions.push(eq(purchases.productId, filters.productId))\n\n\t\tconst [totals] = await db\n\t\t\t.select({ total: sum(purchases.totalAmount), count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions))\n\n\t\tconst [attributed] = await db\n\t\t\t.select({ total: sum(purchases.totalAmount), count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, attributionSignalSql))\n\n\t\tconst [purchaseFieldAttributed] = await db\n\t\t\t.select({ total: sum(purchases.totalAmount), count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, purchaseFieldAttributionSignalSql))\n\n\t\tconst [recoveredFromShortlinkAttributionTable] = await db\n\t\t\t.select({ total: sum(purchases.totalAmount), count: count() })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...conditions, shortlinkRecoveredAttributionSignalSql))\n\n\t\tconst signalConditions = since\n\t\t\t? [commerceRecord(), gte(purchases.createdAt, since)]\n\t\t\t: [commerceRecord()]\n\t\tif (filters.productId) {\n\t\t\tsignalConditions.push(eq(purchases.productId, filters.productId))\n\t\t}\n\t\tconst signalCount = async (condition: any) => {\n\t\t\tconst [row] = await db\n\t\t\t\t.select({ count: count() })\n\t\t\t\t.from(purchases)\n\t\t\t\t.where(and(...signalConditions, condition))\n\t\t\treturn row?.count ?? 0\n\t\t}\n\t\tconst [\n\t\t\tshortlinkCount,\n\t\t\tutmCount,\n\t\t\tclickIdCount,\n\t\t\tgaClientIdCount,\n\t\t\tselfReportedSourceCount,\n\t\t\trecoveredFromShortlinkAttributionTableCount,\n\t\t] = await Promise.all([\n\t\t\tsignalCount(\n\t\t\t\tsql`(\n\t\t\t\t\t\tJSON_EXTRACT(${purchases.fields}, '$.attribution.shortlink.slug') IS NOT NULL\n\t\t\t\t\t\tOR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.shortlinkRef')) IS NOT NULL\n\t\t\t\t\t)`,\n\t\t\t),\n\t\t\tsignalCount(\n\t\t\t\tsql`(\n\t\t\t\t\t\tJSON_EXTRACT(${purchases.fields}, '$.attribution.utm') IS NOT NULL\n\t\t\t\t\t\tOR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.utmSource')) IS NOT NULL\n\t\t\t\t\t)`,\n\t\t\t),\n\t\t\tsignalCount(\n\t\t\t\tsql`JSON_EXTRACT(${purchases.fields}, '$.attribution.clickIds') IS NOT NULL`,\n\t\t\t),\n\t\t\tsignalCount(\n\t\t\t\tsql`(\n\t\t\t\t\t\tJSON_EXTRACT(${purchases.fields}, '$.attribution.ga.clientId') IS NOT NULL\n\t\t\t\t\t\tOR JSON_UNQUOTE(JSON_EXTRACT(${purchases.fields}, '$.gaClientId')) IS NOT NULL\n\t\t\t\t\t)`,\n\t\t\t),\n\t\t\tsignalCount(\n\t\t\t\tsql`JSON_EXTRACT(${purchases.fields}, '$.attribution.selfReportedSource') IS NOT NULL`,\n\t\t\t),\n\t\t\tsignalCount(shortlinkRecoveredAttributionSignalSql),\n\t\t])\n\n\t\tconst commerce = await getCommerceLaneSummary(range, filters)\n\t\tconst totalRevenue = Number(totals?.total ?? 0)\n\t\tconst attributedRevenue = Number(attributed?.total ?? 0)\n\t\tconst purchaseFieldAttributedRevenue = Number(\n\t\t\tpurchaseFieldAttributed?.total ?? 0,\n\t\t)\n\t\tconst recoveredFromShortlinkAttributionTableRevenue = Number(\n\t\t\trecoveredFromShortlinkAttributionTable?.total ?? 0,\n\t\t)\n\t\tconst unattributedRevenue = totalRevenue - attributedRevenue\n\t\tconst paidPurchases = totals?.count ?? 0\n\n\t\treturn {\n\t\t\ttotalRevenue,\n\t\t\tattributedRevenue,\n\t\t\tunattributedRevenue,\n\t\t\tattributionRate: totalRevenue > 0 ? attributedRevenue / totalRevenue : 0,\n\t\t\ttotalPurchases: commerce.commerceRecords,\n\t\t\tpaidPurchases,\n\t\t\tpurchaseFieldAttributedPurchases: purchaseFieldAttributed?.count ?? 0,\n\t\t\tpurchaseFieldAttributedRevenue,\n\t\t\trecoveredFromShortlinkAttributionTablePurchases:\n\t\t\t\trecoveredFromShortlinkAttributionTable?.count ?? 0,\n\t\t\trecoveredFromShortlinkAttributionTableRevenue,\n\t\t\tcommerceRecords: commerce.commerceRecords,\n\t\t\taccessGrants: commerce.accessGrants,\n\t\t\tfreeUpgrades: commerce.freeUpgrades,\n\t\t\tsyntheticPurchases: commerce.syntheticPurchases,\n\t\t\tcommerce,\n\t\t\tsignals: {\n\t\t\t\tshortlink: shortlinkCount,\n\t\t\t\tutm: utmCount,\n\t\t\t\tpaidClickId: clickIdCount,\n\t\t\t\tgaClientId: gaClientIdCount,\n\t\t\t\tselfReportedSource: selfReportedSourceCount,\n\t\t\t\trecoveredFromShortlinkAttributionTable:\n\t\t\t\t\trecoveredFromShortlinkAttributionTableCount,\n\t\t\t\tinternalFreeUpgrade: commerce.freeUpgrades,\n\t\t\t},\n\t\t}\n\t}\n\n\tasync function getContentPurchaseCorrelation(\n\t\trange: AnalyticsTimeRange = '30d',\n\t\tlimit: number = 20,\n\t) {\n\t\tconst since = rangeToDate(range)\n\t\tconst purchaseConditions = [paidPurchase()]\n\t\tif (since) purchaseConditions.push(gte(purchases.createdAt, since))\n\n\t\tconst purchaserRows = await db\n\t\t\t.selectDistinct({ userId: purchases.userId })\n\t\t\t.from(purchases)\n\t\t\t.where(and(...purchaseConditions))\n\n\t\tconst purchaserIds = purchaserRows\n\t\t\t.map((r: any) => r.userId)\n\t\t\t.filter((id: any): id is string => id !== null)\n\n\t\tif (purchaserIds.length === 0) return []\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tresourceId: resourceProgress.resourceId,\n\t\t\t\tpurchaserCount: count(),\n\t\t\t})\n\t\t\t.from(resourceProgress)\n\t\t\t.where(\n\t\t\t\tsql`${resourceProgress.userId} IN (${sql.join(\n\t\t\t\t\tpurchaserIds.map((id: string) => sql`${id}`),\n\t\t\t\t\tsql`, `,\n\t\t\t\t)})`,\n\t\t\t)\n\t\t\t.groupBy(resourceProgress.resourceId)\n\t\t\t.orderBy(desc(count()))\n\t\t\t.limit(limit)\n\n\t\treturn rows.map((r: any) => ({\n\t\t\tresourceId: r.resourceId,\n\t\t\tpurchaserCount: r.purchaserCount,\n\t\t}))\n\t}\n\n\t/**\n\t * Parses an unknown JSON-like value into a plain record.\n\t *\n\t * @param value Unknown input value from a JSON database column.\n\t * @returns A Record<string, any> when the value is a record, otherwise an empty object.\n\t */\n\tfunction parseJsonRecord(value: unknown): Record<string, any> {\n\t\treturn Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\t\t\t? (value as Record<string, any>)\n\t\t\t: {}\n\t}\n\n\t/**\n\t * Checks whether a record contains at least one non-empty string value.\n\t *\n\t * @param value Unknown input value to inspect.\n\t * @returns True when at least one record value is a non-empty string.\n\t */\n\tfunction hasRecordValue(value: unknown) {\n\t\treturn (\n\t\t\tBoolean(value) &&\n\t\t\ttypeof value === 'object' &&\n\t\t\t!Array.isArray(value) &&\n\t\t\tObject.values(value as Record<string, unknown>).some(\n\t\t\t\t(entry) => typeof entry === 'string' && entry.length > 0,\n\t\t\t)\n\t\t)\n\t}\n\n\t/**\n\t * Checks whether a click ID record contains a synthetic TEST_ value.\n\t *\n\t * @param value Unknown input value to inspect.\n\t * @returns True when any record value is a string starting with TEST_.\n\t */\n\tfunction hasSyntheticClickId(value: unknown) {\n\t\treturn (\n\t\t\tBoolean(value) &&\n\t\t\ttypeof value === 'object' &&\n\t\t\t!Array.isArray(value) &&\n\t\t\tObject.values(value as Record<string, unknown>).some(\n\t\t\t\t(entry) => typeof entry === 'string' && entry.startsWith('TEST_'),\n\t\t\t)\n\t\t)\n\t}\n\n\tasync function getCheckoutAttributionReceipt(opts: { purchaseId?: string }) {\n\t\tif (!opts.purchaseId) {\n\t\t\treturn {\n\t\t\t\tpurchase: null,\n\t\t\t\tchecks: {\n\t\t\t\t\tpurchaseFound: false,\n\t\t\t\t\tattributionSnapshot: false,\n\t\t\t\t\tutm: false,\n\t\t\t\t\tactualUtm: false,\n\t\t\t\t\tclickId: false,\n\t\t\t\t\tsynthetic: false,\n\t\t\t\t\tshortlink: false,\n\t\t\t\t\tselfReportedSource: false,\n\t\t\t\t\tgaClientId: false,\n\t\t\t\t},\n\t\t\t\tattribution: null,\n\t\t\t\tlegacy: {},\n\t\t\t\tfieldKeys: [],\n\t\t\t}\n\t\t}\n\n\t\tconst [row] = await db\n\t\t\t.select({\n\t\t\t\tid: purchases.id,\n\t\t\t\tcreatedAt: purchases.createdAt,\n\t\t\t\tproductId: purchases.productId,\n\t\t\t\tproductName: products.name,\n\t\t\t\ttotalAmount: purchases.totalAmount,\n\t\t\t\tstatus: purchases.status,\n\t\t\t\tcountry: purchases.country,\n\t\t\t\tfields: purchases.fields,\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t.where(eq(purchases.id, opts.purchaseId))\n\t\t\t.limit(1)\n\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tpurchase: null,\n\t\t\t\tchecks: {\n\t\t\t\t\tpurchaseFound: false,\n\t\t\t\t\tattributionSnapshot: false,\n\t\t\t\t\tutm: false,\n\t\t\t\t\tactualUtm: false,\n\t\t\t\t\tclickId: false,\n\t\t\t\t\tsynthetic: false,\n\t\t\t\t\tshortlink: false,\n\t\t\t\t\tselfReportedSource: false,\n\t\t\t\t\tgaClientId: false,\n\t\t\t\t},\n\t\t\t\tattribution: null,\n\t\t\t\tlegacy: {},\n\t\t\t\tfieldKeys: [],\n\t\t\t}\n\t\t}\n\n\t\tconst fields = parseJsonRecord(row.fields)\n\t\tconst attribution = parseJsonRecord(fields.attribution)\n\t\tconst utm = parseJsonRecord(attribution.utm)\n\t\tconst clickIds = parseJsonRecord(attribution.clickIds)\n\t\tconst shortlinkSnapshot = parseJsonRecord(attribution.shortlink)\n\t\tconst ga = parseJsonRecord(attribution.ga)\n\t\tconst legacy = Object.fromEntries(\n\t\t\t[\n\t\t\t\t'utmSource',\n\t\t\t\t'utmMedium',\n\t\t\t\t'utmCampaign',\n\t\t\t\t'gaClientId',\n\t\t\t\t'shortlinkRef',\n\t\t\t\t'landingPath',\n\t\t\t\t'referrer',\n\t\t\t\t'ltUtmSource',\n\t\t\t\t'ltUtmMedium',\n\t\t\t\t'ltUtmCampaign',\n\t\t\t]\n\t\t\t\t.map((key) => [key, fields[key]])\n\t\t\t\t.filter(([, value]) => value !== undefined && value !== null),\n\t\t)\n\t\tconst hasActualUtm = Boolean(\n\t\t\tutm.source || utm.medium || utm.campaign || fields.utmSource,\n\t\t)\n\t\tconst synthetic = Boolean(\n\t\t\tattribution.synthetic ||\n\t\t\tfields.synthetic ||\n\t\t\thasSyntheticClickId(clickIds),\n\t\t)\n\n\t\treturn {\n\t\t\tpurchase: {\n\t\t\t\tid: row.id,\n\t\t\t\tcreatedAt: row.createdAt,\n\t\t\t\tproductId: row.productId,\n\t\t\t\tproductName: row.productName ?? null,\n\t\t\t\ttotalAmount: Number(row.totalAmount ?? 0),\n\t\t\t\tstatus: row.status,\n\t\t\t\tcountry: row.country,\n\t\t\t},\n\t\t\tchecks: {\n\t\t\t\tpurchaseFound: true,\n\t\t\t\tattributionSnapshot: Object.keys(attribution).length > 0,\n\t\t\t\tutm: hasActualUtm,\n\t\t\t\tactualUtm: hasActualUtm,\n\t\t\t\tclickId: hasRecordValue(clickIds),\n\t\t\t\tsynthetic,\n\t\t\t\tshortlink: Boolean(shortlinkSnapshot.slug || fields.shortlinkRef),\n\t\t\t\tselfReportedSource: Boolean(attribution.selfReportedSource),\n\t\t\t\tgaClientId: Boolean(ga.clientId || fields.gaClientId),\n\t\t\t},\n\t\t\tattribution: Object.keys(attribution).length > 0 ? attribution : null,\n\t\t\tlegacy,\n\t\t\tfieldKeys: Object.keys(fields).sort(),\n\t\t}\n\t}\n\n\tasync function getCheckoutSurveyFallbackReport(\n\t\trange: AnalyticsTimeRange = '30d',\n\t\tlimit: number = 20,\n\t\tfilters: { productId?: string } = {},\n\t) {\n\t\tconst label = 'recovered_from_checkout_survey_response'\n\t\tif (!questionResponse) {\n\t\t\treturn {\n\t\t\t\tlabel,\n\t\t\t\tproductId: filters.productId ?? null,\n\t\t\t\ttotalDarkPurchases: 0,\n\t\t\t\ttotalDarkRevenue: 0,\n\t\t\t\texactPurchaseLinkedPurchases: 0,\n\t\t\t\texactPurchaseLinkedRevenue: 0,\n\t\t\t\tuserLinkedPurchases: 0,\n\t\t\t\tuserLinkedRevenue: 0,\n\t\t\t\tbyAnswer: [],\n\t\t\t\tnotes: ['questionResponse table was not provided to this provider'],\n\t\t\t}\n\t\t}\n\n\t\tconst since = rangeToDate(range)\n\t\tconst purchaseConditions = [\n\t\t\tpaidPurchase(),\n\t\t\tsql`NOT ${attributionSignalSql}`,\n\t\t]\n\t\tif (since) purchaseConditions.push(gte(purchases.createdAt, since))\n\t\tif (filters.productId) {\n\t\t\tpurchaseConditions.push(eq(purchases.productId, filters.productId))\n\t\t}\n\n\t\tconst darkRows = await db\n\t\t\t.select({\n\t\t\t\tid: purchases.id,\n\t\t\t\tuserId: purchases.userId,\n\t\t\t\trevenue: purchases.totalAmount,\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.where(and(...purchaseConditions))\n\n\t\tconst totalDarkRevenue = darkRows.reduce(\n\t\t\t(total: number, row: any) => total + Number(row.revenue ?? 0),\n\t\t\t0,\n\t\t)\n\n\t\tif (darkRows.length === 0) {\n\t\t\treturn {\n\t\t\t\tlabel,\n\t\t\t\tproductId: filters.productId ?? null,\n\t\t\t\ttotalDarkPurchases: 0,\n\t\t\t\ttotalDarkRevenue: 0,\n\t\t\t\texactPurchaseLinkedPurchases: 0,\n\t\t\t\texactPurchaseLinkedRevenue: 0,\n\t\t\t\tuserLinkedPurchases: 0,\n\t\t\t\tuserLinkedRevenue: 0,\n\t\t\t\tbyAnswer: [],\n\t\t\t\tnotes: ['No dark paid purchases matched the filters'],\n\t\t\t}\n\t\t}\n\n\t\tconst darkById = new Map<string, (typeof darkRows)[number]>(\n\t\t\tdarkRows.map((row: (typeof darkRows)[number]) => [row.id, row]),\n\t\t)\n\t\tconst darkByUserId = new Map<string, any[]>()\n\t\tfor (const row of darkRows) {\n\t\t\tif (!row.userId) continue\n\t\t\tconst bucket = darkByUserId.get(row.userId) ?? []\n\t\t\tbucket.push(row)\n\t\t\tdarkByUserId.set(row.userId, bucket)\n\t\t}\n\n\t\tconst responseConditions: any[] = []\n\t\tif (since) responseConditions.push(gte(questionResponse.createdAt, since))\n\t\tconst darkIds = [...darkById.keys()]\n\t\tconst darkUserIds = [...darkByUserId.keys()]\n\t\tconst exactPurchaseCondition = sql`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.purchaseId')) IN (${sql.join(\n\t\t\tdarkIds.map((id) => sql`${id}`),\n\t\t\tsql`, `,\n\t\t)})`\n\t\tconst userCondition =\n\t\t\tdarkUserIds.length > 0\n\t\t\t\t? sql`${questionResponse.userId} IN (${sql.join(\n\t\t\t\t\t\tdarkUserIds.map((id) => sql`${id}`),\n\t\t\t\t\t\tsql`, `,\n\t\t\t\t\t)})`\n\t\t\t\t: sql`FALSE`\n\t\tresponseConditions.push(\n\t\t\tsql`(${exactPurchaseCondition} OR ${userCondition})`,\n\t\t)\n\n\t\tconst responseRows = await db\n\t\t\t.select({\n\t\t\t\tpurchaseId: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.purchaseId'))`,\n\t\t\t\tanswer: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.answer'))`,\n\t\t\t\tuserId: questionResponse.userId,\n\t\t\t\tcreatedAt: questionResponse.createdAt,\n\t\t\t})\n\t\t\t.from(questionResponse)\n\t\t\t.where(and(...responseConditions))\n\n\t\tconst exactByPurchase = new Map<string, any>()\n\t\tconst latestByUser = new Map<string, any>()\n\t\tfor (const row of responseRows) {\n\t\t\tif (row.purchaseId && darkById.has(row.purchaseId)) {\n\t\t\t\texactByPurchase.set(row.purchaseId, row)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif (!row.userId) continue\n\t\t\tconst current = latestByUser.get(row.userId)\n\t\t\tconst rowTime = new Date(row.createdAt ?? 0).getTime()\n\t\t\tconst currentTime = current\n\t\t\t\t? new Date(current.createdAt ?? 0).getTime()\n\t\t\t\t: -1\n\t\t\tif (!current || rowTime >= currentTime) latestByUser.set(row.userId, row)\n\t\t}\n\n\t\tconst classified = new Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tconfidence: 'exact_purchase_link' | 'user_linked'\n\t\t\t\tanswer: string\n\t\t\t\trevenue: number\n\t\t\t}\n\t\t>()\n\t\tfor (const [purchaseId, response] of exactByPurchase) {\n\t\t\tconst purchase = darkById.get(purchaseId)\n\t\t\tclassified.set(purchaseId, {\n\t\t\t\tconfidence: 'exact_purchase_link',\n\t\t\t\tanswer: response.answer || '(no answer)',\n\t\t\t\trevenue: Number(purchase?.revenue ?? 0),\n\t\t\t})\n\t\t}\n\t\tfor (const row of darkRows) {\n\t\t\tif (classified.has(row.id) || !row.userId) continue\n\t\t\tconst response = latestByUser.get(row.userId)\n\t\t\tif (!response) continue\n\t\t\tclassified.set(row.id, {\n\t\t\t\tconfidence: 'user_linked',\n\t\t\t\tanswer: response.answer || '(no answer)',\n\t\t\t\trevenue: Number(row.revenue ?? 0),\n\t\t\t})\n\t\t}\n\n\t\tconst byAnswer = new Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tanswer: string\n\t\t\t\tconfidence: 'exact_purchase_link' | 'user_linked'\n\t\t\t\tpurchases: number\n\t\t\t\trevenue: number\n\t\t\t}\n\t\t>()\n\t\tlet exactPurchaseLinkedPurchases = 0\n\t\tlet exactPurchaseLinkedRevenue = 0\n\t\tlet userLinkedPurchases = 0\n\t\tlet userLinkedRevenue = 0\n\t\tfor (const entry of classified.values()) {\n\t\t\tif (entry.confidence === 'exact_purchase_link') {\n\t\t\t\texactPurchaseLinkedPurchases += 1\n\t\t\t\texactPurchaseLinkedRevenue += entry.revenue\n\t\t\t} else {\n\t\t\t\tuserLinkedPurchases += 1\n\t\t\t\tuserLinkedRevenue += entry.revenue\n\t\t\t}\n\t\t\tconst key = `${entry.confidence}:${entry.answer}`\n\t\t\tconst current = byAnswer.get(key) ?? {\n\t\t\t\tanswer: entry.answer,\n\t\t\t\tconfidence: entry.confidence,\n\t\t\t\tpurchases: 0,\n\t\t\t\trevenue: 0,\n\t\t\t}\n\t\t\tcurrent.purchases += 1\n\t\t\tcurrent.revenue += entry.revenue\n\t\t\tbyAnswer.set(key, current)\n\t\t}\n\n\t\treturn {\n\t\t\tlabel,\n\t\t\tproductId: filters.productId ?? null,\n\t\t\ttotalDarkPurchases: darkRows.length,\n\t\t\ttotalDarkRevenue,\n\t\t\texactPurchaseLinkedPurchases,\n\t\t\texactPurchaseLinkedRevenue,\n\t\t\tuserLinkedPurchases,\n\t\t\tuserLinkedRevenue,\n\t\t\tbyAnswer: [...byAnswer.values()]\n\t\t\t\t.sort((a, b) => b.revenue - a.revenue || b.purchases - a.purchases)\n\t\t\t\t.slice(0, limit),\n\t\t\tnotes: [\n\t\t\t\t'Report-only fallback. No purchase fields are mutated.',\n\t\t\t\t'Exact purchase-linked responses use QuestionResponse.fields.purchaseId when present.',\n\t\t\t\t'User-linked responses are lower-confidence and remain report-only.',\n\t\t\t],\n\t\t}\n\t}\n\n\t// ─── Attribution Trail ───────────────────────────────────────────────────\n\n\t/**\n\t * Trace the full attribution journey for a user by email or purchaseId.\n\t * Walks: ShortlinkClick → ShortlinkAttribution (signup) →\n\t * ResourceProgress → Purchase\n\t */\n\n\tasync function getValuePathSummary(range: AnalyticsTimeRange = '30d') {\n\t\tif (!contactEvent || !sideEffectIntent) {\n\t\t\treturn {\n\t\t\t\tcontacts: 0,\n\t\t\t\tevents: 0,\n\t\t\t\tintents: 0,\n\t\t\t\tcompletedIntents: 0,\n\t\t\t\tpendingIntents: 0,\n\t\t\t\tblockedIntents: 0,\n\t\t\t\tanswerEvents: 0,\n\t\t\t\tdripEvents: 0,\n\t\t\t\tenteredEvents: 0,\n\t\t\t\tparticipantsWithAnswerClicks: 0,\n\t\t\t\tparticipantsWithNoAnswerClicks: 0,\n\t\t\t\tterminalParticipants: 0,\n\t\t\t\tanswerOptions: [],\n\t\t\t\tanswerSteps: [],\n\t\t\t\tterminalSteps: [],\n\t\t\t\tnotes: ['Contact Event and SideEffectIntent tables are unavailable.'],\n\t\t\t}\n\t\t}\n\n\t\tconst since = rangeToDate(range)\n\t\tconst eventConditions = [sql`${contactEvent.eventType} LIKE 'value-path.%'`]\n\t\tconst intentConditions = [\n\t\t\teq(sideEffectIntent.type, 'send-value-path-email'),\n\t\t]\n\t\tif (since) {\n\t\t\teventConditions.push(gte(contactEvent.occurredAt, since))\n\t\t\tintentConditions.push(gte(sideEffectIntent.createdAt, since))\n\t\t}\n\n\t\tconst [events, intents] = await Promise.all([\n\t\t\tdb\n\t\t\t\t.select({\n\t\t\t\t\tcontactId: contactEvent.contactId,\n\t\t\t\t\teventType: contactEvent.eventType,\n\t\t\t\t\tproviderEventId: contactEvent.providerEventId,\n\t\t\t\t\toccurredAt: contactEvent.occurredAt,\n\t\t\t\t})\n\t\t\t\t.from(contactEvent)\n\t\t\t\t.where(and(...eventConditions)),\n\t\t\tdb\n\t\t\t\t.select({\n\t\t\t\t\tcontactId: sideEffectIntent.contactId,\n\t\t\t\t\tstatus: sideEffectIntent.status,\n\t\t\t\t\tmetadata: sideEffectIntent.metadata,\n\t\t\t\t\tcreatedAt: sideEffectIntent.createdAt,\n\t\t\t\t})\n\t\t\t\t.from(sideEffectIntent)\n\t\t\t\t.where(and(...intentConditions)),\n\t\t])\n\n\t\tconst contactIds = new Set<string>()\n\t\tfor (const event of events) contactIds.add(event.contactId)\n\t\tfor (const intent of intents) contactIds.add(intent.contactId)\n\n\t\tconst answerEvents = events.filter(\n\t\t\t(event: any) => event.eventType === 'value-path.answer-selected',\n\t\t)\n\t\tconst dripEvents = events.filter(\n\t\t\t(event: any) => event.eventType === 'value-path.drip-progressed',\n\t\t)\n\t\tconst enteredEvents = events.filter(\n\t\t\t(event: any) => event.eventType === 'value-path.entered',\n\t\t)\n\n\t\tconst answersByContact = new Map<string, number>()\n\t\tfor (const event of answerEvents) {\n\t\t\tanswersByContact.set(\n\t\t\t\tevent.contactId,\n\t\t\t\t(answersByContact.get(event.contactId) ?? 0) + 1,\n\t\t\t)\n\t\t}\n\n\t\tconst answerKeyCounts = new Map<string, number>()\n\t\tconst answerStepCounts = new Map<string, number>()\n\t\tfor (const event of answerEvents) {\n\t\t\tconst key = parseValuePathAnswerKey(event.providerEventId)\n\t\t\tanswerKeyCounts.set(key, (answerKeyCounts.get(key) ?? 0) + 1)\n\t\t\tconst step = answerStepFromKey(key)\n\t\t\tanswerStepCounts.set(step, (answerStepCounts.get(step) ?? 0) + 1)\n\t\t}\n\n\t\tconst latestIntentByContact = new Map<string, any>()\n\t\tfor (const intent of [...intents].sort(\n\t\t\t(a: any, b: any) =>\n\t\t\t\tnew Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\n\t\t)) {\n\t\t\tlatestIntentByContact.set(intent.contactId, intent)\n\t\t}\n\n\t\tconst terminalCounts = new Map<string, number>()\n\t\tfor (const intent of latestIntentByContact.values()) {\n\t\t\tconst emailResourceId = String(intent.metadata?.emailResourceId ?? '')\n\t\t\tif (!isTerminalValuePathEmail(emailResourceId)) continue\n\t\t\tterminalCounts.set(\n\t\t\t\temailResourceId,\n\t\t\t\t(terminalCounts.get(emailResourceId) ?? 0) + 1,\n\t\t\t)\n\t\t}\n\n\t\treturn {\n\t\t\tcontacts: contactIds.size,\n\t\t\tevents: events.length,\n\t\t\tintents: intents.length,\n\t\t\tcompletedIntents: intents.filter(\n\t\t\t\t(intent: any) => intent.status === 'completed',\n\t\t\t).length,\n\t\t\tpendingIntents: intents.filter(\n\t\t\t\t(intent: any) => intent.status === 'pending',\n\t\t\t).length,\n\t\t\tblockedIntents: intents.filter(\n\t\t\t\t(intent: any) => intent.status === 'blocked',\n\t\t\t).length,\n\t\t\tanswerEvents: answerEvents.length,\n\t\t\tdripEvents: dripEvents.length,\n\t\t\tenteredEvents: enteredEvents.length,\n\t\t\tparticipantsWithAnswerClicks: answersByContact.size,\n\t\t\tparticipantsWithNoAnswerClicks: Math.max(\n\t\t\t\t0,\n\t\t\t\tcontactIds.size - answersByContact.size,\n\t\t\t),\n\t\t\tterminalParticipants: [...terminalCounts.values()].reduce(\n\t\t\t\t(total, count) => total + count,\n\t\t\t\t0,\n\t\t\t),\n\t\t\tanswerOptions: [...answerKeyCounts.entries()]\n\t\t\t\t.map(([key, count]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tstep: answerStepFromKey(key),\n\t\t\t\t\toptionValue: answerOptionFromKey(key),\n\t\t\t\t\tcount,\n\t\t\t\t}))\n\t\t\t\t.sort((a, b) => b.count - a.count),\n\t\t\tanswerSteps: [...answerStepCounts.entries()]\n\t\t\t\t.map(([step, count]) => ({ step, count }))\n\t\t\t\t.sort((a, b) => b.count - a.count),\n\t\t\tterminalSteps: [...terminalCounts.entries()]\n\t\t\t\t.map(([emailResourceId, count]) => ({ emailResourceId, count }))\n\t\t\t\t.sort((a, b) => b.count - a.count),\n\t\t\tnotes: [\n\t\t\t\t'Counts are based on Contact Events and SideEffectIntents.',\n\t\t\t\t'Kit-native opens, unsubscribes, complaints, and raw Kit link clicks are not included.',\n\t\t\t],\n\t\t}\n\t}\n\n\tfunction parseValuePathAnswerKey(providerEventId: string | null | undefined) {\n\t\tconst match = String(providerEventId ?? '').match(/:answer:(.+)$/)\n\t\treturn match?.[1] ?? 'unknown'\n\t}\n\n\tfunction answerStepFromKey(key: string) {\n\t\tconst index = key.lastIndexOf('.')\n\t\treturn index > -1 ? key.slice(0, index) : key\n\t}\n\n\tfunction answerOptionFromKey(key: string) {\n\t\tconst index = key.lastIndexOf('.')\n\t\treturn index > -1 ? key.slice(index + 1) : 'unknown'\n\t}\n\n\tfunction isTerminalValuePathEmail(emailResourceId: string) {\n\t\treturn /(?:^|\\.)(?:email-6|team-email-6)$/.test(emailResourceId)\n\t}\n\n\tasync function traceAttribution(opts: {\n\t\temail?: string\n\t\tpurchaseId?: string\n\t}): Promise<AttributionTrail> {\n\t\tconst events: AttributionTrailEvent[] = []\n\n\t\t// Resolve user\n\t\tlet userId: string | null = null\n\t\tlet userEmail: string | null = opts.email ?? null\n\t\tlet userRecord: AttributionTrail['user'] = null\n\n\t\tif (opts.purchaseId) {\n\t\t\tconst [purchase] = await db\n\t\t\t\t.select({\n\t\t\t\t\tuserId: purchases.userId,\n\t\t\t\t\temail: users.email,\n\t\t\t\t})\n\t\t\t\t.from(purchases)\n\t\t\t\t.leftJoin(users, eq(purchases.userId, users.id))\n\t\t\t\t.where(eq(purchases.id, opts.purchaseId))\n\t\t\t\t.limit(1)\n\t\t\tuserId = purchase?.userId ?? null\n\t\t\tuserEmail = purchase?.email ?? userEmail\n\t\t}\n\n\t\tif (userEmail && !userId) {\n\t\t\tconst [u] = await db\n\t\t\t\t.select({ id: users.id })\n\t\t\t\t.from(users)\n\t\t\t\t.where(eq(users.email, userEmail))\n\t\t\t\t.limit(1)\n\t\t\tuserId = u?.id ?? null\n\t\t}\n\n\t\tif (userId) {\n\t\t\tconst [u] = await db\n\t\t\t\t.select({\n\t\t\t\t\tid: users.id,\n\t\t\t\t\temail: users.email,\n\t\t\t\t\tname: users.name,\n\t\t\t\t\tcreatedAt: users.createdAt,\n\t\t\t\t})\n\t\t\t\t.from(users)\n\t\t\t\t.where(eq(users.id, userId))\n\t\t\t\t.limit(1)\n\t\t\tuserRecord = u\n\t\t\t\t? {\n\t\t\t\t\t\tid: u.id,\n\t\t\t\t\t\temail: u.email,\n\t\t\t\t\t\tname: u.name ?? null,\n\t\t\t\t\t\tcreatedAt: u.createdAt!,\n\t\t\t\t\t}\n\t\t\t\t: null\n\t\t}\n\n\t\t// Find shortlink attributions for this user (by userId or email)\n\t\tconst attrConditions: any[] = []\n\t\tif (userId) attrConditions.push(eq(shortlinkAttribution.userId, userId))\n\t\tif (userEmail)\n\t\t\tattrConditions.push(eq(shortlinkAttribution.email, userEmail))\n\n\t\tif (attrConditions.length > 0) {\n\t\t\tconst attrs = await db\n\t\t\t\t.select({\n\t\t\t\t\ttype: shortlinkAttribution.type,\n\t\t\t\t\tcreatedAt: shortlinkAttribution.createdAt,\n\t\t\t\t\tmetadata: shortlinkAttribution.metadata,\n\t\t\t\t\tshortlinkId: shortlinkAttribution.shortlinkId,\n\t\t\t\t\tslug: shortlink.slug,\n\t\t\t\t\turl: shortlink.url,\n\t\t\t\t})\n\t\t\t\t.from(shortlinkAttribution)\n\t\t\t\t.leftJoin(shortlink, eq(shortlinkAttribution.shortlinkId, shortlink.id))\n\t\t\t\t.where(sql`(${sql.join(attrConditions, sql` OR `)})`)\n\t\t\t\t.orderBy(shortlinkAttribution.createdAt)\n\n\t\t\tfor (const attr of attrs) {\n\t\t\t\t// Find clicks on this shortlink before the attribution event\n\t\t\t\tconst clicks = await db\n\t\t\t\t\t.select({\n\t\t\t\t\t\ttimestamp: shortlinkClick.timestamp,\n\t\t\t\t\t\treferrer: shortlinkClick.referrer,\n\t\t\t\t\t\tcountry: shortlinkClick.country,\n\t\t\t\t\t\tdevice: shortlinkClick.device,\n\t\t\t\t\t})\n\t\t\t\t\t.from(shortlinkClick)\n\t\t\t\t\t.where(\n\t\t\t\t\t\tand(\n\t\t\t\t\t\t\teq(shortlinkClick.shortlinkId, attr.shortlinkId),\n\t\t\t\t\t\t\tlte(shortlinkClick.timestamp, attr.createdAt),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t\t.orderBy(desc(shortlinkClick.timestamp))\n\t\t\t\t\t.limit(3) // last 3 clicks before attribution\n\n\t\t\t\tfor (const click of clicks) {\n\t\t\t\t\tevents.push({\n\t\t\t\t\t\ttype: 'click',\n\t\t\t\t\t\ttimestamp: click.timestamp,\n\t\t\t\t\t\tdetail: {\n\t\t\t\t\t\t\tshortlink: `/s/${attr.slug}`,\n\t\t\t\t\t\t\tdestination: attr.url,\n\t\t\t\t\t\t\treferrer: click.referrer,\n\t\t\t\t\t\t\tcountry: click.country,\n\t\t\t\t\t\t\tdevice: click.device,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\tevents.push({\n\t\t\t\t\ttype: attr.type === 'purchase' ? 'purchase' : 'signup',\n\t\t\t\t\ttimestamp: attr.createdAt,\n\t\t\t\t\tdetail: {\n\t\t\t\t\t\tshortlink: `/s/${attr.slug}`,\n\t\t\t\t\t\tmetadata: attr.metadata ? JSON.parse(attr.metadata) : null,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Find resource progress for this user\n\t\tif (userId) {\n\t\t\tconst progress = await db\n\t\t\t\t.select({\n\t\t\t\t\tresourceId: resourceProgress.resourceId,\n\t\t\t\t\tcompletedAt: resourceProgress.completedAt,\n\t\t\t\t\tcreatedAt: resourceProgress.createdAt,\n\t\t\t\t})\n\t\t\t\t.from(resourceProgress)\n\t\t\t\t.where(eq(resourceProgress.userId, userId))\n\t\t\t\t.orderBy(resourceProgress.createdAt)\n\t\t\t\t.limit(20) // cap at 20 most recent\n\n\t\t\tfor (const p of progress) {\n\t\t\t\tevents.push({\n\t\t\t\t\ttype: 'progress',\n\t\t\t\t\ttimestamp: p.completedAt ?? p.createdAt,\n\t\t\t\t\tdetail: { resourceId: p.resourceId },\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// Find purchases\n\t\tconst purchaseConditions = [paidPurchase()]\n\t\tif (opts.purchaseId) {\n\t\t\tpurchaseConditions.push(eq(purchases.id, opts.purchaseId))\n\t\t} else if (userId) {\n\t\t\tpurchaseConditions.push(eq(purchases.userId, userId))\n\t\t} else {\n\t\t\t// No user found, return empty\n\t\t\treturn { user: userRecord, events: [], purchases: [] }\n\t\t}\n\n\t\tconst purchaseRows = await db\n\t\t\t.select({\n\t\t\t\tid: purchases.id,\n\t\t\t\ttotalAmount: purchases.totalAmount,\n\t\t\t\tproductName: products.name,\n\t\t\t\tcreatedAt: purchases.createdAt,\n\t\t\t\tcountry: purchases.country,\n\t\t\t\tfields: purchases.fields,\n\t\t\t})\n\t\t\t.from(purchases)\n\t\t\t.leftJoin(products, eq(purchases.productId, products.id))\n\t\t\t.where(and(...purchaseConditions))\n\t\t\t.orderBy(purchases.createdAt)\n\n\t\tconst purchaseResults = purchaseRows.map((p: any) => {\n\t\t\tconst fields = (p.fields as Record<string, any>) ?? {}\n\t\t\tevents.push({\n\t\t\t\ttype: 'purchase',\n\t\t\t\ttimestamp: p.createdAt,\n\t\t\t\tdetail: {\n\t\t\t\t\tpurchaseId: p.id,\n\t\t\t\t\tamount: Number(p.totalAmount),\n\t\t\t\t\tproduct: p.productName,\n\t\t\t\t},\n\t\t\t})\n\t\t\treturn {\n\t\t\t\tid: p.id,\n\t\t\t\ttotalAmount: Number(p.totalAmount),\n\t\t\t\tproductName: p.productName ?? 'Unknown',\n\t\t\t\tcreatedAt: p.createdAt,\n\t\t\t\tcountry: p.country,\n\t\t\t\tutmSource: fields.utmSource ?? null,\n\t\t\t\tutmMedium: fields.utmMedium ?? null,\n\t\t\t\tutmCampaign: fields.utmCampaign ?? null,\n\t\t\t}\n\t\t})\n\n\t\t// Sort all events by timestamp\n\t\tevents.sort(\n\t\t\t(a, b) =>\n\t\t\t\tnew Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),\n\t\t)\n\n\t\treturn { user: userRecord, events, purchases: purchaseResults }\n\t}\n\n\treturn {\n\t\tgetRevenueSummary,\n\t\tgetRevenueByDay,\n\t\tgetPreviousPeriodRevenueByDay,\n\t\tgetRevenueByProduct,\n\t\tgetRevenueByCountry,\n\t\tgetRecentPurchases,\n\t\tgetAttributionSummary,\n\t\tgetShortlinkPerformance,\n\t\tgetRevenueBySource,\n\t\tgetConversionFunnel,\n\t\tgetCommerceLaneSummary,\n\t\tgetAttributedRevenueSummary,\n\t\tgetContentPurchaseCorrelation,\n\t\tgetCheckoutAttributionReceipt,\n\t\tgetCheckoutSurveyFallbackReport,\n\t\tgetValuePathSummary,\n\t\ttraceAttribution,\n\t}\n}\n\nexport type DatabaseAnalyticsProvider = ReturnType<\n\ttypeof createDatabaseProvider\n>\n","export const SHORTLINK_RECOVERY_LANE =\n\t'recovered_from_shortlink_attribution_table' as const\n\nexport type PurchaseAttributionClass =\n\t| 'purchase_field'\n\t| typeof SHORTLINK_RECOVERY_LANE\n\t| 'dark'\n\nexport interface ShortlinkAttributionEvidence {\n\ttype?: string | null\n\tmetadata?: unknown\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction nonEmptyString(value: unknown) {\n\treturn typeof value === 'string' && value.trim().length > 0\n}\n\nfunction parseMetadata(value: unknown): Record<string, unknown> | null {\n\tif (typeof value === 'string') {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(value) as unknown\n\t\t\treturn isRecord(parsed) ? parsed : null\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\treturn isRecord(value) ? value : null\n}\n\n/**\n * Detects durable purchase-field attribution on a purchase fields object.\n *\n * @param fields Unknown purchase fields payload.\n * @returns True when the purchase already has attribution, legacy UTM source, or legacy GA client ID evidence.\n * @example\n * hasPurchaseFieldAttribution({ attribution: { source: 'email' } })\n */\nexport function hasPurchaseFieldAttribution(fields: unknown) {\n\tconst record = isRecord(fields) ? fields : {}\n\tconst attribution = record.attribution\n\n\treturn Boolean(\n\t\t(isRecord(attribution) && Object.keys(attribution).length > 0) ||\n\t\tnonEmptyString(record.utmSource) ||\n\t\tnonEmptyString(record.gaClientId),\n\t)\n}\n\n/**\n * Detects exact shortlink purchase evidence for one purchase.\n *\n * @param params.purchaseId Purchase ID that must match attribution metadata.purchaseId.\n * @param params.attributions ShortlinkAttributionEvidence rows to inspect.\n * @returns True only for purchase rows with valid JSON metadata and an exact purchase ID match.\n */\nexport function hasExactShortlinkPurchaseAttribution(params: {\n\tpurchaseId: string\n\tattributions: readonly ShortlinkAttributionEvidence[]\n}) {\n\treturn params.attributions.some((attribution) => {\n\t\tif (attribution.type !== 'purchase') return false\n\t\tconst metadata = parseMetadata(attribution.metadata)\n\t\treturn metadata?.purchaseId === params.purchaseId\n\t})\n}\n\n/**\n * Classifies purchase attribution with purchase fields taking precedence.\n *\n * @param params.purchaseId Purchase ID being classified.\n * @param params.fields Purchase fields payload.\n * @param params.shortlinkAttributions ShortlinkAttributionEvidence rows used as exact fallback evidence.\n * @returns A PurchaseAttributionClass: 'purchase_field', SHORTLINK_RECOVERY_LANE, or 'dark'.\n * @example\n * classifyPurchaseAttribution({ purchaseId: 'p1', fields: {}, shortlinkAttributions: [] })\n */\nexport function classifyPurchaseAttribution(params: {\n\tpurchaseId: string\n\tfields?: unknown\n\tshortlinkAttributions?: readonly ShortlinkAttributionEvidence[]\n}): PurchaseAttributionClass {\n\tif (hasPurchaseFieldAttribution(params.fields)) return 'purchase_field'\n\tif (\n\t\thasExactShortlinkPurchaseAttribution({\n\t\t\tpurchaseId: params.purchaseId,\n\t\t\tattributions: params.shortlinkAttributions ?? [],\n\t\t})\n\t) {\n\t\treturn SHORTLINK_RECOVERY_LANE\n\t}\n\treturn 'dark'\n}\n","import { BetaAnalyticsDataClient } from '@google-analytics/data'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type GA4TimeRange = '24h' | '7d' | '30d' | '90d'\n\nexport interface GA4ProviderConfig {\n\tpropertyId: string\n\tclientEmail: string\n\tprivateKey: string\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a GA4 analytics provider with injected credentials.\n * Lazily initializes the BetaAnalyticsDataClient on first use.\n *\n * @param config - GA4 credentials and property configuration\n */\nexport function createGA4Provider(config: GA4ProviderConfig) {\n\tconst { propertyId, clientEmail, privateKey } = config\n\n\tlet _client: BetaAnalyticsDataClient | null = null\n\n\tfunction getClient(): BetaAnalyticsDataClient {\n\t\tif (!_client) {\n\t\t\t_client = new BetaAnalyticsDataClient({\n\t\t\t\tcredentials: {\n\t\t\t\t\tclient_email: clientEmail,\n\t\t\t\t\tprivate_key: privateKey.replace(/\\\\n/g, '\\n'),\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\treturn _client\n\t}\n\n\tfunction rangeToDateRange(range: GA4TimeRange) {\n\t\tconst map: Record<GA4TimeRange, string> = {\n\t\t\t'24h': '1daysAgo',\n\t\t\t'7d': '7daysAgo',\n\t\t\t'30d': '30daysAgo',\n\t\t\t'90d': '90daysAgo',\n\t\t}\n\t\treturn { startDate: map[range], endDate: 'today' }\n\t}\n\n\t// ─── Reports ──────────────────────────────────────────────────────────────\n\n\tasync function getTrafficOverview(range: GA4TimeRange = '30d') {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tmetrics: [\n\t\t\t\t{ name: 'sessions' },\n\t\t\t\t{ name: 'totalUsers' },\n\t\t\t\t{ name: 'newUsers' },\n\t\t\t\t{ name: 'screenPageViews' },\n\t\t\t\t{ name: 'averageSessionDuration' },\n\t\t\t\t{ name: 'bounceRate' },\n\t\t\t],\n\t\t})\n\n\t\tconst row = response?.rows?.[0]\n\t\tif (!row) {\n\t\t\treturn {\n\t\t\t\tsessions: 0,\n\t\t\t\ttotalUsers: 0,\n\t\t\t\tnewUsers: 0,\n\t\t\t\tpageviews: 0,\n\t\t\t\tavgSessionDuration: 0,\n\t\t\t\tbounceRate: 0,\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tsessions: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\ttotalUsers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\tnewUsers: Number(row.metricValues?.[2]?.value ?? 0),\n\t\t\tpageviews: Number(row.metricValues?.[3]?.value ?? 0),\n\t\t\tavgSessionDuration: Number(row.metricValues?.[4]?.value ?? 0),\n\t\t\tbounceRate: Number(row.metricValues?.[5]?.value ?? 0),\n\t\t}\n\t}\n\n\tasync function getTopPages(range: GA4TimeRange = '30d', limit = 20) {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tdimensions: [{ name: 'pagePath' }],\n\t\t\tmetrics: [\n\t\t\t\t{ name: 'screenPageViews' },\n\t\t\t\t{ name: 'totalUsers' },\n\t\t\t\t{ name: 'averageSessionDuration' },\n\t\t\t],\n\t\t\torderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],\n\t\t\tlimit,\n\t\t})\n\n\t\treturn (\n\t\t\tresponse?.rows?.map((row) => ({\n\t\t\t\tpath: row.dimensionValues?.[0]?.value ?? '',\n\t\t\t\tpageviews: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\t\tusers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\t\tavgDuration: Number(row.metricValues?.[2]?.value ?? 0),\n\t\t\t})) ?? []\n\t\t)\n\t}\n\n\tasync function getTrafficSources(range: GA4TimeRange = '30d', limit = 15) {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tdimensions: [{ name: 'sessionSource' }, { name: 'sessionMedium' }],\n\t\t\tmetrics: [{ name: 'sessions' }, { name: 'totalUsers' }],\n\t\t\torderBys: [{ metric: { metricName: 'sessions' }, desc: true }],\n\t\t\tlimit,\n\t\t})\n\n\t\treturn (\n\t\t\tresponse?.rows?.map((row) => ({\n\t\t\t\tsource: row.dimensionValues?.[0]?.value ?? '(direct)',\n\t\t\t\tmedium: row.dimensionValues?.[1]?.value ?? '(none)',\n\t\t\t\tsessions: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\t\tusers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\t})) ?? []\n\t\t)\n\t}\n\n\tasync function getSessionsByDay(range: GA4TimeRange = '30d') {\n\t\tconst client = getClient()\n\n\t\tconst [response] = await client.runReport({\n\t\t\tproperty: `properties/${propertyId}`,\n\t\t\tdateRanges: [rangeToDateRange(range)],\n\t\t\tdimensions: [{ name: 'date' }],\n\t\t\tmetrics: [\n\t\t\t\t{ name: 'sessions' },\n\t\t\t\t{ name: 'totalUsers' },\n\t\t\t\t{ name: 'screenPageViews' },\n\t\t\t],\n\t\t\torderBys: [{ dimension: { dimensionName: 'date' }, desc: false }],\n\t\t})\n\n\t\treturn (\n\t\t\tresponse?.rows?.map((row) => {\n\t\t\t\tconst raw = row.dimensionValues?.[0]?.value ?? ''\n\t\t\t\tconst date = `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`\n\t\t\t\treturn {\n\t\t\t\t\tdate,\n\t\t\t\t\tsessions: Number(row.metricValues?.[0]?.value ?? 0),\n\t\t\t\t\tusers: Number(row.metricValues?.[1]?.value ?? 0),\n\t\t\t\t\tpageviews: Number(row.metricValues?.[2]?.value ?? 0),\n\t\t\t\t}\n\t\t\t}) ?? []\n\t\t)\n\t}\n\n\treturn {\n\t\tgetTrafficOverview,\n\t\tgetTopPages,\n\t\tgetTrafficSources,\n\t\tgetSessionsByDay,\n\t}\n}\n\nexport type GA4AnalyticsProvider = ReturnType<typeof createGA4Provider>\n","import type { GA4TimeRange } from './ga4'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type AnalyticsRange = '24h' | '7d' | '30d' | '90d' | 'all'\n\nexport interface TrafficRevenueCorrelation {\n\ttraffic: {\n\t\tdate: string\n\t\tsessions: number\n\t\tusers: number\n\t\tpageviews: number\n\t}[]\n\trevenue: {\n\t\tdate: string\n\t\trevenue: number\n\t\tcount: number\n\t}[]\n}\n\nexport interface DerivedProviderDeps {\n\tdatabase: {\n\t\tgetRevenueByDay: (range: AnalyticsRange) => Promise<\n\t\t\t{\n\t\t\t\tdate: string\n\t\t\t\trevenue: number\n\t\t\t\tcount: number\n\t\t\t}[]\n\t\t>\n\t}\n\tga4: {\n\t\tgetSessionsByDay: (range: GA4TimeRange) => Promise<\n\t\t\t{\n\t\t\t\tdate: string\n\t\t\t\tsessions: number\n\t\t\t\tusers: number\n\t\t\t\tpageviews: number\n\t\t\t}[]\n\t\t>\n\t}\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a derived analytics provider that combines data from multiple\n * sources (database + GA4) to compute correlation metrics.\n *\n * @param deps - Provider dependencies (database and ga4 providers)\n */\nexport function createDerivedProvider(deps: DerivedProviderDeps) {\n\tconst { database, ga4 } = deps\n\n\tfunction toGA4Range(range: AnalyticsRange): GA4TimeRange {\n\t\tif (range === 'all') return '90d'\n\t\treturn range\n\t}\n\n\t/**\n\t * Correlates GA4 traffic sessions by day with revenue by day,\n\t * enabling side-by-side analysis of traffic and revenue trends.\n\t */\n\tasync function getTrafficRevenueCorrelation(\n\t\trange: AnalyticsRange,\n\t): Promise<TrafficRevenueCorrelation> {\n\t\tconst [traffic, revenue] = await Promise.all([\n\t\t\tga4.getSessionsByDay(toGA4Range(range)),\n\t\t\tdatabase.getRevenueByDay(range),\n\t\t])\n\n\t\treturn { traffic, revenue }\n\t}\n\n\treturn {\n\t\tgetTrafficRevenueCorrelation,\n\t}\n}\n\nexport type DerivedAnalyticsProvider = ReturnType<typeof createDerivedProvider>\n","import { sql } from 'drizzle-orm'\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type TimeRange = '7:days' | '30:days' | '90:days'\n\n/** Ranges available on the Top Videos table (independent from page-level range) */\nexport type VideoTableRange = '24:hours' | '7:days' | '30:days' | '90:days'\n\nexport interface MuxOverallResponse {\n\tdata: {\n\t\tvalue: number\n\t\ttotal_watch_time: number\n\t\ttotal_playing_time: number\n\t\ttotal_views: number\n\t\tglobal_value: number | null\n\t}\n\ttimeframe: [number, number]\n}\n\nexport interface MuxTimeseriesResponse {\n\tdata: [string, number | null, number | null][]\n\ttimeframe: [number, number]\n\ttotal_row_count: number\n}\n\nexport interface MuxBreakdownItem {\n\tviews: number\n\tvalue: number\n\ttotal_watch_time: number\n\ttotal_playing_time: number\n\tnegative_impact: number | null\n\tfield: string\n}\n\nexport interface MuxBreakdownResponse {\n\tdata: MuxBreakdownItem[]\n\ttimeframe: [number, number]\n\ttotal_row_count: number\n}\n\nexport interface VideoDashboardData {\n\toverview: {\n\t\ttotalViews: number\n\t\tuniqueViewers: number\n\t\ttotalWatchTimeMs: number\n\t\ttotalPlayingTimeMs: number\n\t\tviewerExperienceScore: number\n\t\tglobalExperienceScore: number | null\n\t}\n\twatchTimeSeries: {\n\t\tdate: string\n\t\twatchTimeMs: number\n\t}[]\n\ttopVideos: {\n\t\ttitle: string\n\t\tviews: number\n\t\twatchTimeMs: number\n\t\tplayingTimeMs: number\n\t}[]\n\tcountries: {\n\t\tcountry: string\n\t\tviews: number\n\t\twatchTimeMs: number\n\t}[]\n}\n\nexport type VideoDetailBreakdowns = {\n\tcountries: {\n\t\tcountry: string\n\t\tviews: number\n\t\twatchTimeMs: number\n\t}[]\n\ttimeseries: {\n\t\tdate: string\n\t\tviews: number\n\t}[]\n}\n\nexport interface MuxProviderConfig {\n\ttokenId: string\n\ttokenSecret: string\n}\n\nexport interface MuxDbDeps {\n\tdb: any\n\tcontentResource: any\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a Mux analytics provider with injected credentials.\n * Optionally accepts db dependencies for thumbnail lookups that\n * require querying ContentResource records.\n *\n * @param config - Mux Data API token configuration\n * @param dbDeps - Optional drizzle db + contentResource table for thumbnail queries\n */\nexport function createMuxProvider(\n\tconfig: MuxProviderConfig,\n\tdbDeps?: MuxDbDeps,\n) {\n\tconst MUX_DATA_BASE = 'https://api.mux.com/data/v1'\n\n\tfunction getAuthHeader(): string {\n\t\treturn `Basic ${Buffer.from(\n\t\t\t`${config.tokenId}:${config.tokenSecret}`,\n\t\t).toString('base64')}`\n\t}\n\n\tasync function muxDataFetch<T>(\n\t\tpath: string,\n\t\tparams?: Record<string, string | string[]>,\n\t): Promise<T> {\n\t\tconst url = new URL(`${MUX_DATA_BASE}${path}`)\n\t\tif (params) {\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tfor (const v of value) {\n\t\t\t\t\t\turl.searchParams.append(key, v)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\turl.searchParams.set(key, value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst response = await fetch(url.toString(), {\n\t\t\theaders: {\n\t\t\t\tAuthorization: getAuthHeader(),\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t\tnext: { revalidate: 300 }, // cache 5 minutes\n\t\t} as RequestInit)\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Mux Data API error: ${response.status} ${response.statusText}`,\n\t\t\t)\n\t\t}\n\n\t\treturn response.json() as Promise<T>\n\t}\n\n\t// ─── Comparison (unique viewers) ─────────────────────────────────────────\n\n\tinterface MuxComparisonItem {\n\t\tname: string\n\t\twatch_time?: number\n\t\tview_count?: number\n\t\tunique_viewers?: number\n\t\tstarted_views?: number\n\t\tended_views?: number\n\t\t[key: string]: unknown\n\t}\n\n\tinterface MuxComparisonResponse {\n\t\tdata: MuxComparisonItem[]\n\t\ttimeframe: [number, number]\n\t\ttotal_row_count: number | null\n\t}\n\n\tasync function getComparisonTotals(timeRange: TimeRange = '30:days') {\n\t\tconst resp = await muxDataFetch<MuxComparisonResponse>(\n\t\t\t'/metrics/comparison',\n\t\t\t{ 'timeframe[]': timeRange },\n\t\t)\n\t\tconst totals = resp.data.find((d) => d.name === 'totals')\n\t\treturn {\n\t\t\tuniqueViewers: totals?.unique_viewers ?? 0,\n\t\t\tviewCount: totals?.view_count ?? 0,\n\t\t\twatchTimeMs: totals?.watch_time ?? 0,\n\t\t}\n\t}\n\n\t// ─── API Functions ────────────────────────────────────────────────────────\n\n\tasync function getViewsOverall(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxOverallResponse>('/metrics/views/overall', {\n\t\t\t'timeframe[]': timeRange,\n\t\t})\n\t}\n\n\tasync function getViewerExperienceScore(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxOverallResponse>(\n\t\t\t'/metrics/viewer_experience_score/overall',\n\t\t\t{ 'timeframe[]': timeRange },\n\t\t)\n\t}\n\n\tasync function getViewsTimeseries(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxTimeseriesResponse>('/metrics/views/timeseries', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'day',\n\t\t})\n\t}\n\n\t/**\n\t * Fetch watch time (playing_time) timeseries grouped by day.\n\t * Mux returns [date, totalPlayingTimeMs, viewCount] tuples.\n\t * The value IS the total playing time in ms (not the average).\n\t */\n\tasync function getWatchTimeTimeseries(timeRange: TimeRange = '30:days') {\n\t\treturn muxDataFetch<MuxTimeseriesResponse>(\n\t\t\t'/metrics/playing_time/timeseries',\n\t\t\t{\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'day',\n\t\t\t},\n\t\t)\n\t}\n\n\tasync function getVideoBreakdown(\n\t\ttimeRange: TimeRange = '30:days',\n\t\tlimit: number = 25,\n\t) {\n\t\treturn muxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'video_title',\n\t\t\torder_by: 'views',\n\t\t\torder_direction: 'desc',\n\t\t\tlimit: String(limit),\n\t\t})\n\t}\n\n\t/**\n\t * Standalone video breakdown fetcher for the Top Videos table's\n\t * independent time-range tabs. Accepts VideoTableRange.\n\t */\n\tasync function getVideoBreakdownForRange(\n\t\ttimeRange: VideoTableRange,\n\t\tlimit: number = 50,\n\t) {\n\t\treturn muxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'video_title',\n\t\t\torder_by: 'views',\n\t\t\torder_direction: 'desc',\n\t\t\tlimit: String(limit),\n\t\t})\n\t}\n\n\tasync function getCountryBreakdown(\n\t\ttimeRange: TimeRange = '30:days',\n\t\tlimit: number = 10,\n\t) {\n\t\treturn muxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t'timeframe[]': timeRange,\n\t\t\tgroup_by: 'country',\n\t\t\torder_by: 'views',\n\t\t\torder_direction: 'desc',\n\t\t\tlimit: String(limit),\n\t\t})\n\t}\n\n\tasync function getVideoDetailBreakdowns(\n\t\tvideoTitle: string,\n\t\ttimeRange: TimeRange = '30:days',\n\t): Promise<VideoDetailBreakdowns> {\n\t\tconst filter = `video_title:${videoTitle}`\n\t\tconst [countries, timeseries] = await Promise.all([\n\t\t\tmuxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'country',\n\t\t\t\torder_by: 'views',\n\t\t\t\torder_direction: 'desc',\n\t\t\t\tlimit: '8',\n\t\t\t\t'filters[]': filter,\n\t\t\t}),\n\t\t\tmuxDataFetch<MuxTimeseriesResponse>('/metrics/views/timeseries', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'day',\n\t\t\t\t'filters[]': filter,\n\t\t\t}),\n\t\t])\n\n\t\treturn {\n\t\t\tcountries: countries.data.map((c) => ({\n\t\t\t\tcountry: c.field,\n\t\t\t\tviews: c.views,\n\t\t\t\twatchTimeMs: c.total_watch_time,\n\t\t\t})),\n\t\t\ttimeseries: timeseries.data.map(([date, value]) => ({\n\t\t\t\tdate,\n\t\t\t\tviews: value ?? 0,\n\t\t\t})),\n\t\t}\n\t}\n\n\t// ─── Aggregate fetcher ────────────────────────────────────────────────────\n\n\tasync function getVideoDashboardData(\n\t\ttimeRange: TimeRange = '30:days',\n\t): Promise<VideoDashboardData> {\n\t\tconst [views, experience, comparison, watchTime, videos, countries] =\n\t\t\tawait Promise.all([\n\t\t\t\tgetViewsOverall(timeRange),\n\t\t\t\tgetViewerExperienceScore(timeRange),\n\t\t\t\tgetComparisonTotals(timeRange),\n\t\t\t\tgetWatchTimeTimeseries(timeRange),\n\t\t\t\tgetVideoBreakdown(timeRange, 50),\n\t\t\t\tgetCountryBreakdown(timeRange, 15),\n\t\t\t])\n\n\t\treturn {\n\t\t\toverview: {\n\t\t\t\ttotalViews: views.data.total_views,\n\t\t\t\tuniqueViewers: comparison.uniqueViewers,\n\t\t\t\ttotalWatchTimeMs: views.data.total_watch_time,\n\t\t\t\ttotalPlayingTimeMs: views.data.total_playing_time,\n\t\t\t\tviewerExperienceScore: experience.data.value,\n\t\t\t\tglobalExperienceScore: experience.data.global_value,\n\t\t\t},\n\t\t\twatchTimeSeries: watchTime.data.map(([date, totalMs]) => ({\n\t\t\t\tdate,\n\t\t\t\t// Mux playing_time timeseries: value is total playing time in ms\n\t\t\t\twatchTimeMs: totalMs ?? 0,\n\t\t\t})),\n\t\t\ttopVideos: videos.data\n\t\t\t\t.filter((v) => v.field !== '')\n\t\t\t\t.map((v) => ({\n\t\t\t\t\ttitle: v.field,\n\t\t\t\t\tviews: v.views,\n\t\t\t\t\twatchTimeMs: v.total_watch_time,\n\t\t\t\t\tplayingTimeMs: v.total_playing_time,\n\t\t\t\t})),\n\t\t\tcountries: countries.data.map((c) => ({\n\t\t\t\tcountry: c.field,\n\t\t\t\tviews: c.views,\n\t\t\t\twatchTimeMs: c.total_watch_time,\n\t\t\t})),\n\t\t}\n\t}\n\n\t/**\n\t * Resolve video titles to Mux thumbnail URLs.\n\t * 1. Break down by video_id to get ContentResource IDs for top videos\n\t * 2. Look up playback IDs from ContentResource.fields.muxPlaybackId\n\t * 3. Build image.mux.com thumbnail URLs\n\t * Returns Record<title, thumbnailUrl>.\n\t * Requires dbDeps to be provided at factory construction time.\n\t */\n\tasync function getVideoThumbnails(\n\t\ttimeRange: TimeRange = '30:days',\n\t\tlimit: number = 10,\n\t): Promise<Record<string, string>> {\n\t\tif (!dbDeps) {\n\t\t\tthrow new Error(\n\t\t\t\t'getVideoThumbnails requires dbDeps (db + contentResource) ' +\n\t\t\t\t\t'to be provided to createMuxProvider',\n\t\t\t)\n\t\t}\n\n\t\tconst { db, contentResource } = dbDeps\n\n\t\t// Get breakdown by both video_id and video_title\n\t\tconst [byId, byTitle] = await Promise.all([\n\t\t\tmuxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'video_id',\n\t\t\t\torder_by: 'views',\n\t\t\t\torder_direction: 'desc',\n\t\t\t\tlimit: String(limit),\n\t\t\t}),\n\t\t\tmuxDataFetch<MuxBreakdownResponse>('/metrics/views/breakdown', {\n\t\t\t\t'timeframe[]': timeRange,\n\t\t\t\tgroup_by: 'video_title',\n\t\t\t\torder_by: 'views',\n\t\t\t\torder_direction: 'desc',\n\t\t\t\tlimit: String(limit),\n\t\t\t}),\n\t\t])\n\n\t\t// video_id breakdown gives ContentResource IDs — look up playback IDs\n\t\tconst videoIds = byId.data\n\t\t\t.filter((v) => v.field && v.field !== '')\n\t\t\t.map((v) => v.field)\n\n\t\tif (videoIds.length === 0) return {}\n\n\t\tconst rows = await db\n\t\t\t.select({\n\t\t\t\tid: contentResource.id,\n\t\t\t\tplaybackId:\n\t\t\t\t\tsql<string>`JSON_UNQUOTE(JSON_EXTRACT(${contentResource.fields}, '$.muxPlaybackId'))`.as(\n\t\t\t\t\t\t'playbackId',\n\t\t\t\t\t),\n\t\t\t})\n\t\t\t.from(contentResource)\n\t\t\t.where(\n\t\t\t\tsql`${contentResource.id} IN (${sql.join(\n\t\t\t\t\tvideoIds.map((id: string) => sql`${id}`),\n\t\t\t\t\tsql`, `,\n\t\t\t\t)})`,\n\t\t\t)\n\n\t\t// Map video_id → playbackId\n\t\tconst idToPlayback = new Map<string, string>()\n\t\tfor (const r of rows) {\n\t\t\tif (r.playbackId && r.playbackId !== 'null') {\n\t\t\t\tidToPlayback.set(r.id, r.playbackId)\n\t\t\t}\n\t\t}\n\n\t\t// Match by position: byId and byTitle are both sorted by views desc,\n\t\t// so position i in byId corresponds to position i in byTitle\n\t\tconst result: Record<string, string> = {}\n\t\tfor (let i = 0; i < Math.min(byId.data.length, byTitle.data.length); i++) {\n\t\t\tconst videoId = byId.data[i]?.field\n\t\t\tconst title = byTitle.data[i]?.field\n\t\t\tif (!videoId || !title) continue\n\t\t\tconst playbackId = idToPlayback.get(videoId)\n\t\t\tif (playbackId) {\n\t\t\t\tresult[title] =\n\t\t\t\t\t`https://image.mux.com/${playbackId}/thumbnail.jpg?width=240&height=135&fit_mode=smartcrop`\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n\treturn {\n\t\tgetComparisonTotals,\n\t\tgetViewsOverall,\n\t\tgetViewerExperienceScore,\n\t\tgetViewsTimeseries,\n\t\tgetWatchTimeTimeseries,\n\t\tgetVideoBreakdown,\n\t\tgetVideoBreakdownForRange,\n\t\tgetCountryBreakdown,\n\t\tgetVideoDetailBreakdowns,\n\t\tgetVideoDashboardData,\n\t\tgetVideoThumbnails,\n\t}\n}\n\nexport type MuxAnalyticsProvider = ReturnType<typeof createMuxProvider>\n","import { and, count, eq, sql } from 'drizzle-orm'\n\nimport type { AnalyticsRange } from '../types'\n\n// ─── Schema types ────────────────────────────────────────────────────────────\n\n/**\n * Minimal column shape required from the contentResource table.\n * We only reference the columns we actually query against.\n */\ninterface ContentResourceTable {\n\tid: any\n\ttype: any\n\tfields: any\n}\n\n/**\n * Minimal column shape required from the contentResourceResource table.\n */\ninterface ContentResourceResourceTable {\n\tresourceOfId: any\n\tresourceId: any\n}\n\n/**\n * Minimal column shape required from the questionResponse table.\n */\ninterface QuestionResponseTable {\n\tid: any\n\tsurveyId: any\n\tquestionId: any\n\trespondentKey: any\n\tsurveySessionId: any\n\tuserId: any\n\temailListSubscriberId: any\n\tcreatedAt: any\n\tupdatedAt: any\n\tfields: any\n}\n\n/**\n * Minimal column shape required from the users table.\n * Optional — only needed for getSurveyResponses to resolve user emails.\n */\ninterface UsersTable {\n\tid: any\n\temail: any\n}\n\nexport interface SurveyAnalyticsSchema {\n\tcontentResource: ContentResourceTable\n\tcontentResourceResource: ContentResourceResourceTable\n\tquestionResponse: QuestionResponseTable\n\tusers?: UsersTable\n}\n\ntype CanonicalSurveyRow = {\n\tresponseId: string\n\tsurveyId: string\n\tsurveyTitle: string | null\n\tsurveySlug: string | null\n\tquestionId: string\n\tquestionText: string | null\n\tquestionType: string | null\n\tanswer: string | null\n\trespondentKey: string\n\tsurveySessionId: string | null\n\tuserId: string | null\n\tuserEmail: string | null\n\temailListSubscriberId: string | null\n\tcreatedAt: Date | null\n\tupdatedAt: Date | null\n}\n\n// ─── Return type ─────────────────────────────────────────────────────────────\n\nexport interface SurveyAnalyticsProvider {\n\tgetSurveySummary: (range?: AnalyticsRange) => Promise<{\n\t\ttotalSurveys: number\n\t\ttotalResponses: number\n\t\tuniqueRespondents: number\n\t\tavgResponsesPerSurvey: number\n\t}>\n\n\tgetSurveyList: (range?: AnalyticsRange) => Promise<\n\t\tArray<{\n\t\t\tsurveyId: string\n\t\t\tsurveyTitle: string\n\t\t\tsurveySlug: string\n\t\t\tresponses: number\n\t\t\tuniqueRespondents: number\n\t\t\tquestionCount: number\n\t\t}>\n\t>\n\n\tgetSurveyResponsesByDay: (range?: AnalyticsRange) => Promise<\n\t\tArray<{\n\t\t\tdate: string\n\t\t\tresponses: number\n\t\t}>\n\t>\n\n\tgetSurveyQuestionBreakdown: (\n\t\trange?: AnalyticsRange,\n\t\tlimit?: number,\n\t) => Promise<\n\t\tArray<{\n\t\t\tquestionId: string\n\t\t\tquestion: string\n\t\t\ttype: string | null\n\t\t\tresponses: number\n\t\t\tuniqueRespondents: number\n\t\t\tanswerDistribution: Array<{ answer: string; count: number }>\n\t\t}>\n\t>\n\n\tgetSurveyResponses: (\n\t\trange?: AnalyticsRange,\n\t\tlimit?: number,\n\t\toffset?: number,\n\t) => Promise<\n\t\tArray<{\n\t\t\tresponseId: string\n\t\t\tsurveyId: string\n\t\t\tsurveyTitle: string\n\t\t\tsurveySlug: string\n\t\t\tquestionId: string\n\t\t\tquestionText: string\n\t\t\tquestionType: string | null\n\t\t\tanswer: string\n\t\t\tuserId: string | null\n\t\t\tuserEmail: string | null\n\t\t\temailListSubscriberId: string | null\n\t\t\tcreatedAt: string\n\t\t}>\n\t>\n}\n\nfunction normalizeRespondentKey(row: {\n\trespondentKey: string | null\n\tuserId: string | null\n\temailListSubscriberId: string | null\n\tsurveySessionId: string | null\n}) {\n\tif (row.respondentKey) return row.respondentKey\n\tif (row.userId) return `user:${row.userId}`\n\tif (row.emailListSubscriberId) {\n\t\treturn `subscriber:${row.emailListSubscriberId}`\n\t}\n\tif (row.surveySessionId) return `session:${row.surveySessionId}`\n\treturn null\n}\n\nfunction getRowTimestamp(row: {\n\tupdatedAt: Date | null\n\tcreatedAt: Date | null\n}) {\n\tconst date = row.updatedAt ?? row.createdAt\n\treturn date instanceof Date ? date.getTime() : 0\n}\n\nfunction sortByNewest<\n\tT extends { createdAt: Date | null; updatedAt: Date | null },\n>(rows: T[]) {\n\treturn [...rows].sort((a, b) => getRowTimestamp(b) - getRowTimestamp(a))\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/**\n * Creates a survey analytics provider bound to the given Drizzle db instance\n * and schema tables.\n *\n * @param db - Drizzle database instance\n * @param schema - Object containing the required table references\n */\nexport function createSurveyProvider(\n\tdb: any,\n\tschema: SurveyAnalyticsSchema,\n): SurveyAnalyticsProvider {\n\tconst { contentResource, contentResourceResource, questionResponse } = schema\n\n\t// ─── Range helpers ───────────────────────────────────────────────────────\n\n\tfunction rangeToInterval(range: AnalyticsRange): string {\n\t\tswitch (range) {\n\t\t\tcase '24h':\n\t\t\t\treturn '1 DAY'\n\t\t\tcase '7d':\n\t\t\t\treturn '7 DAY'\n\t\t\tcase '30d':\n\t\t\t\treturn '30 DAY'\n\t\t\tcase '90d':\n\t\t\t\treturn '90 DAY'\n\t\t\tcase 'all':\n\t\t\t\treturn '3650 DAY'\n\t\t}\n\t}\n\n\tfunction rangeWhere(range: AnalyticsRange, column: any) {\n\t\treturn sql`${column} >= DATE_SUB(NOW(), INTERVAL ${sql.raw(rangeToInterval(range))})`\n\t}\n\n\tasync function fetchCanonicalRows(range: AnalyticsRange) {\n\t\tconst { users } = schema\n\n\t\tconst rawRows = users\n\t\t\t? await db\n\t\t\t\t\t.select({\n\t\t\t\t\t\tresponseId: questionResponse.id,\n\t\t\t\t\t\tsurveyId: questionResponse.surveyId,\n\t\t\t\t\t\tsurveyTitle: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.title'))`,\n\t\t\t\t\t\tsurveySlug: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.slug'))`,\n\t\t\t\t\t\tquestionId: questionResponse.questionId,\n\t\t\t\t\t\tquestionText: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.question'))`,\n\t\t\t\t\t\tquestionType: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.type'))`,\n\t\t\t\t\t\tanswer: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.answer'))`,\n\t\t\t\t\t\trespondentKey: questionResponse.respondentKey,\n\t\t\t\t\t\tsurveySessionId: questionResponse.surveySessionId,\n\t\t\t\t\t\tuserId: questionResponse.userId,\n\t\t\t\t\t\tuserEmail: users.email,\n\t\t\t\t\t\temailListSubscriberId: questionResponse.emailListSubscriberId,\n\t\t\t\t\t\tcreatedAt: questionResponse.createdAt,\n\t\t\t\t\t\tupdatedAt: questionResponse.updatedAt,\n\t\t\t\t\t})\n\t\t\t\t\t.from(questionResponse)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS survey_cr`,\n\t\t\t\t\t\tsql`survey_cr.id = ${questionResponse.surveyId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS question_cr`,\n\t\t\t\t\t\tsql`question_cr.id = ${questionResponse.questionId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.leftJoin(users, eq(questionResponse.userId, users.id))\n\t\t\t\t\t.where(rangeWhere(range, questionResponse.createdAt))\n\t\t\t: await db\n\t\t\t\t\t.select({\n\t\t\t\t\t\tresponseId: questionResponse.id,\n\t\t\t\t\t\tsurveyId: questionResponse.surveyId,\n\t\t\t\t\t\tsurveyTitle: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.title'))`,\n\t\t\t\t\t\tsurveySlug: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(survey_cr.fields, '$.slug'))`,\n\t\t\t\t\t\tquestionId: questionResponse.questionId,\n\t\t\t\t\t\tquestionText: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.question'))`,\n\t\t\t\t\t\tquestionType: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(question_cr.fields, '$.type'))`,\n\t\t\t\t\t\tanswer: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${questionResponse.fields}, '$.answer'))`,\n\t\t\t\t\t\trespondentKey: questionResponse.respondentKey,\n\t\t\t\t\t\tsurveySessionId: questionResponse.surveySessionId,\n\t\t\t\t\t\tuserId: questionResponse.userId,\n\t\t\t\t\t\tuserEmail: sql<string | null>`NULL`,\n\t\t\t\t\t\temailListSubscriberId: questionResponse.emailListSubscriberId,\n\t\t\t\t\t\tcreatedAt: questionResponse.createdAt,\n\t\t\t\t\t\tupdatedAt: questionResponse.updatedAt,\n\t\t\t\t\t})\n\t\t\t\t\t.from(questionResponse)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS survey_cr`,\n\t\t\t\t\t\tsql`survey_cr.id = ${questionResponse.surveyId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\tsql`${contentResource} AS question_cr`,\n\t\t\t\t\t\tsql`question_cr.id = ${questionResponse.questionId}`,\n\t\t\t\t\t)\n\t\t\t\t\t.where(rangeWhere(range, questionResponse.createdAt))\n\n\t\tconst latestByAnswer = new Map<string, CanonicalSurveyRow>()\n\n\t\tfor (const row of rawRows) {\n\t\t\tconst respondentKey = normalizeRespondentKey(row)\n\t\t\tif (!respondentKey) continue\n\n\t\t\tconst dedupeKey = `${row.surveyId}::${row.questionId}::${respondentKey}`\n\t\t\tconst current = latestByAnswer.get(dedupeKey)\n\n\t\t\tif (!current || getRowTimestamp(row) >= getRowTimestamp(current)) {\n\t\t\t\tlatestByAnswer.set(dedupeKey, {\n\t\t\t\t\t...row,\n\t\t\t\t\trespondentKey,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\treturn sortByNewest(Array.from(latestByAnswer.values()))\n\t}\n\n\t// ─── Survey Summary ──────────────────────────────────────────────────────\n\n\tasync function getSurveySummary(range: AnalyticsRange = '30d') {\n\t\tconst [surveyCount] = await db\n\t\t\t.select({ total: count() })\n\t\t\t.from(contentResource)\n\t\t\t.where(eq(contentResource.type, 'survey'))\n\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst respondentKeys = new Set(\n\t\t\tcanonicalRows.map((row) => row.respondentKey),\n\t\t)\n\t\tconst totalSurveys = surveyCount?.total ?? 0\n\t\tconst totalResponses = canonicalRows.length\n\n\t\treturn {\n\t\t\ttotalSurveys,\n\t\t\ttotalResponses,\n\t\t\tuniqueRespondents: respondentKeys.size,\n\t\t\tavgResponsesPerSurvey:\n\t\t\t\ttotalSurveys > 0 ? totalResponses / totalSurveys : 0,\n\t\t}\n\t}\n\n\t// ─── Survey List ─────────────────────────────────────────────────────────\n\n\tasync function getSurveyList(range: AnalyticsRange = '30d') {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst responsesBySurvey = new Map<\n\t\t\tstring,\n\t\t\t{ responses: number; respondents: Set<string> }\n\t\t>()\n\n\t\tfor (const row of canonicalRows) {\n\t\t\tconst current = responsesBySurvey.get(row.surveyId) ?? {\n\t\t\t\tresponses: 0,\n\t\t\t\trespondents: new Set<string>(),\n\t\t\t}\n\t\t\tcurrent.responses += 1\n\t\t\tcurrent.respondents.add(row.respondentKey)\n\t\t\tresponsesBySurvey.set(row.surveyId, current)\n\t\t}\n\n\t\tconst surveys = await db\n\t\t\t.select({\n\t\t\t\tsurveyId: contentResource.id,\n\t\t\t\tsurveyTitle: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${contentResource.fields}, '$.title'))`,\n\t\t\t\tsurveySlug: sql<string>`JSON_UNQUOTE(JSON_EXTRACT(${contentResource.fields}, '$.slug'))`,\n\t\t\t})\n\t\t\t.from(contentResource)\n\t\t\t.where(eq(contentResource.type, 'survey'))\n\n\t\tconst questionCounts = await db\n\t\t\t.select({\n\t\t\t\tsurveyId: contentResourceResource.resourceOfId,\n\t\t\t\tquestionCount: count(),\n\t\t\t})\n\t\t\t.from(contentResourceResource)\n\t\t\t.innerJoin(\n\t\t\t\tcontentResource,\n\t\t\t\tand(\n\t\t\t\t\teq(contentResourceResource.resourceId, contentResource.id),\n\t\t\t\t\teq(contentResource.type, 'question'),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.groupBy(contentResourceResource.resourceOfId)\n\n\t\tconst questionCountMap = new Map(\n\t\t\tquestionCounts.map((qc: { surveyId: string; questionCount: number }) => [\n\t\t\t\tqc.surveyId,\n\t\t\t\tqc.questionCount,\n\t\t\t]),\n\t\t)\n\n\t\treturn surveys\n\t\t\t.map(\n\t\t\t\t(s: {\n\t\t\t\t\tsurveyId: string\n\t\t\t\t\tsurveyTitle: string | null\n\t\t\t\t\tsurveySlug: string | null\n\t\t\t\t}) => {\n\t\t\t\t\tconst counts = responsesBySurvey.get(s.surveyId)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsurveyId: s.surveyId,\n\t\t\t\t\t\tsurveyTitle: s.surveyTitle ?? '',\n\t\t\t\t\t\tsurveySlug: s.surveySlug ?? '',\n\t\t\t\t\t\tresponses: counts?.responses ?? 0,\n\t\t\t\t\t\tuniqueRespondents: counts?.respondents.size ?? 0,\n\t\t\t\t\t\tquestionCount: questionCountMap.get(s.surveyId) ?? 0,\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t\t.sort(\n\t\t\t\t(a: { responses: number }, b: { responses: number }) =>\n\t\t\t\t\tb.responses - a.responses,\n\t\t\t)\n\t}\n\n\t// ─── Daily Responses ─────────────────────────────────────────────────────\n\n\tasync function getSurveyResponsesByDay(range: AnalyticsRange = '30d') {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst grouped = new Map<string, number>()\n\n\t\tfor (const row of canonicalRows) {\n\t\t\tif (!(row.createdAt instanceof Date)) continue\n\t\t\tconst date = row.createdAt.toISOString().slice(0, 10)\n\t\t\tgrouped.set(date, (grouped.get(date) ?? 0) + 1)\n\t\t}\n\n\t\treturn Array.from(grouped.entries())\n\t\t\t.sort((entryA: [string, number], entryB: [string, number]) =>\n\t\t\t\tentryA[0].localeCompare(entryB[0]),\n\t\t\t)\n\t\t\t.map(([date, responses]) => ({ date, responses }))\n\t}\n\n\t// ─── Question Breakdown ──────────────────────────────────────────────────\n\n\tasync function getSurveyQuestionBreakdown(\n\t\trange: AnalyticsRange = '30d',\n\t\tlimit = 20,\n\t) {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\t\tconst grouped = new Map<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tquestionId: string\n\t\t\t\tquestion: string\n\t\t\t\ttype: string | null\n\t\t\t\tresponses: number\n\t\t\t\trespondents: Set<string>\n\t\t\t\tanswers: Map<string, number>\n\t\t\t}\n\t\t>()\n\n\t\tfor (const row of canonicalRows) {\n\t\t\tconst current = grouped.get(row.questionId) ?? {\n\t\t\t\tquestionId: row.questionId,\n\t\t\t\tquestion: row.questionText ?? '',\n\t\t\t\ttype: row.questionType ?? null,\n\t\t\t\tresponses: 0,\n\t\t\t\trespondents: new Set<string>(),\n\t\t\t\tanswers: new Map<string, number>(),\n\t\t\t}\n\n\t\t\tcurrent.responses += 1\n\t\t\tcurrent.respondents.add(row.respondentKey)\n\t\t\tconst answer = row.answer ?? '(no answer)'\n\t\t\tcurrent.answers.set(answer, (current.answers.get(answer) ?? 0) + 1)\n\t\t\tgrouped.set(row.questionId, current)\n\t\t}\n\n\t\treturn Array.from(grouped.values())\n\t\t\t.sort(\n\t\t\t\t(a: { responses: number }, b: { responses: number }) =>\n\t\t\t\t\tb.responses - a.responses,\n\t\t\t)\n\t\t\t.slice(0, limit)\n\t\t\t.map((entry) => ({\n\t\t\t\tquestionId: entry.questionId,\n\t\t\t\tquestion: entry.question,\n\t\t\t\ttype: entry.type,\n\t\t\t\tresponses: entry.responses,\n\t\t\t\tuniqueRespondents: entry.respondents.size,\n\t\t\t\tanswerDistribution: Array.from(entry.answers.entries())\n\t\t\t\t\t.sort((a: [string, number], b: [string, number]) => b[1] - a[1])\n\t\t\t\t\t.map(([answer, count]) => ({ answer, count })),\n\t\t\t}))\n\t}\n\n\t// ─── Individual Response Rows ────────────────────────────────────────────\n\n\tasync function getSurveyResponses(\n\t\trange: AnalyticsRange = '30d',\n\t\tlimit = 100,\n\t\toffset = 0,\n\t) {\n\t\tconst canonicalRows = await fetchCanonicalRows(range)\n\n\t\treturn canonicalRows.slice(offset, offset + limit).map((row) => ({\n\t\t\tresponseId: row.responseId,\n\t\t\tsurveyId: row.surveyId,\n\t\t\tsurveyTitle: row.surveyTitle ?? '',\n\t\t\tsurveySlug: row.surveySlug ?? '',\n\t\t\tquestionId: row.questionId,\n\t\t\tquestionText: row.questionText ?? '',\n\t\t\tquestionType: row.questionType ?? null,\n\t\t\tanswer: row.answer ?? '',\n\t\t\tuserId: row.userId ?? null,\n\t\t\tuserEmail: row.userEmail ?? null,\n\t\t\temailListSubscriberId: row.emailListSubscriberId ?? null,\n\t\t\tcreatedAt: row.createdAt ? String(row.createdAt) : '',\n\t\t}))\n\t}\n\n\treturn {\n\t\tgetSurveySummary,\n\t\tgetSurveyList,\n\t\tgetSurveyResponsesByDay,\n\t\tgetSurveyQuestionBreakdown,\n\t\tgetSurveyResponses,\n\t}\n}\n"],"mappings":"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","names":["and","count","desc","eq","gt","gte","inArray","lte","sql","sum","SHORTLINK_RECOVERY_LANE","createDatabaseProvider","db","schema","purchases","products","users","coupon","resourceProgress","shortlink","shortlinkAttribution","shortlinkClick","questionResponse","contactEvent","sideEffectIntent","PAID_STATUSES","commerceRecord","inArray","status","paidPurchase","and","gt","totalAmount","accessGrant","sql","backfillFreeUpgrade","fields","syntheticSignalSql","syntheticPurchase","purchaseFieldAttributionSignalSql","exactShortlinkPurchaseAttributionSignalSql","type","metadata","id","shortlinkRecoveredAttributionSignalSql","attributionSignalSql","rangeToDate","range","now","Date","hours","getTime","getRevenueSummary","since","conditions","push","gte","createdAt","totals","select","totalRevenue","sum","purchaseCount","count","from","where","Number","avgOrderValue","getRevenueByDay","rows","date","as","revenue","groupBy","orderBy","map","r","getPreviousPeriodRevenueByDay","periodMs","periodStart","prevStart","lte","getRevenueByProduct","productId","productName","name","leftJoin","eq","desc","getRevenueByCountry","country","limit","getRecentPurchases","filter","bulkCouponId","couponId","userId","userName","userEmail","email","organizationId","seats","maxUses","isTeam","query","findMany","with","product","user","getAttributionSummary","length","undefined","getShortlinkPerformance","clickConditions","timestamp","shortlinkId","slug","url","clicks","innerJoin","attrConditions","attrRows","attrMap","Map","a","existing","get","signups","set","attr","getRevenueBySource","filters","kindExpr","sourceExpr","SHORTLINK_RECOVERY_LANE","mediumExpr","campaignExpr","kind","source","medium","campaign","getConversionFunnel","userConditions","purchaseConditions","userCount","total","attributedCount","totalSignups","totalPurchases","attributedPurchases","conversionRate","attributionCoverage","getCommerceLaneSummary","commerce","paid","grants","freeUpgrades","synthetic","reasonRows","reason","commerceRecords","paidPurchases","paidRevenue","accessGrants","accessGrantRevenue","syntheticPurchases","byAccessGrantReason","Object","fromEntries","row","getAttributedRevenueSummary","attributed","purchaseFieldAttributed","recoveredFromShortlinkAttributionTable","signalConditions","signalCount","condition","shortlinkCount","utmCount","clickIdCount","gaClientIdCount","selfReportedSourceCount","recoveredFromShortlinkAttributionTableCount","Promise","all","attributedRevenue","purchaseFieldAttributedRevenue","recoveredFromShortlinkAttributionTableRevenue","unattributedRevenue","attributionRate","purchaseFieldAttributedPurchases","recoveredFromShortlinkAttributionTablePurchases","signals","utm","paidClickId","gaClientId","selfReportedSource","internalFreeUpgrade","getContentPurchaseCorrelation","purchaserRows","selectDistinct","purchaserIds","resourceId","purchaserCount","join","parseJsonRecord","value","Boolean","Array","isArray","hasRecordValue","values","some","entry","hasSyntheticClickId","startsWith","getCheckoutAttributionReceipt","opts","purchaseId","purchase","checks","purchaseFound","attributionSnapshot","actualUtm","clickId","attribution","legacy","fieldKeys","clickIds","shortlinkSnapshot","ga","key","hasActualUtm","utmSource","keys","shortlinkRef","clientId","sort","getCheckoutSurveyFallbackReport","label","totalDarkPurchases","totalDarkRevenue","exactPurchaseLinkedPurchases","exactPurchaseLinkedRevenue","userLinkedPurchases","userLinkedRevenue","byAnswer","notes","darkRows","reduce","darkById","darkByUserId","bucket","responseConditions","darkIds","darkUserIds","exactPurchaseCondition","userCondition","responseRows","answer","exactByPurchase","latestByUser","has","current","rowTime","currentTime","classified","response","confidence","b","slice","getValuePathSummary","contacts","events","intents","completedIntents","pendingIntents","blockedIntents","answerEvents","dripEvents","enteredEvents","participantsWithAnswerClicks","participantsWithNoAnswerClicks","terminalParticipants","answerOptions","answerSteps","terminalSteps","eventConditions","eventType","intentConditions","occurredAt","contactId","providerEventId","contactIds","Set","event","add","intent","answersByContact","answerKeyCounts","answerStepCounts","parseValuePathAnswerKey","step","answerStepFromKey","latestIntentByContact","terminalCounts","emailResourceId","String","isTerminalValuePathEmail","size","Math","max","entries","optionValue","answerOptionFromKey","match","index","lastIndexOf","test","traceAttribution","userRecord","u","attrs","referrer","device","click","detail","destination","JSON","parse","progress","completedAt","p","purchaseRows","purchaseResults","amount","utmMedium","utmCampaign","BetaAnalyticsDataClient","createGA4Provider","config","propertyId","clientEmail","privateKey","_client","getClient","BetaAnalyticsDataClient","credentials","client_email","private_key","replace","rangeToDateRange","range","map","startDate","endDate","getTrafficOverview","client","response","runReport","property","dateRanges","metrics","name","row","rows","sessions","totalUsers","newUsers","pageviews","avgSessionDuration","bounceRate","Number","metricValues","value","getTopPages","limit","dimensions","orderBys","metric","metricName","desc","path","dimensionValues","users","avgDuration","getTrafficSources","source","medium","getSessionsByDay","dimension","dimensionName","raw","date","slice","createDerivedProvider","deps","database","ga4","toGA4Range","range","getTrafficRevenueCorrelation","traffic","revenue","Promise","all","getSessionsByDay","getRevenueByDay","sql","createMuxProvider","config","dbDeps","MUX_DATA_BASE","getAuthHeader","Buffer","from","tokenId","tokenSecret","toString","muxDataFetch","path","params","url","URL","key","value","Object","entries","Array","isArray","v","searchParams","append","set","response","fetch","headers","Authorization","next","revalidate","ok","Error","status","statusText","json","getComparisonTotals","timeRange","resp","totals","data","find","d","name","uniqueViewers","unique_viewers","viewCount","view_count","watchTimeMs","watch_time","getViewsOverall","getViewerExperienceScore","getViewsTimeseries","group_by","getWatchTimeTimeseries","getVideoBreakdown","limit","order_by","order_direction","String","getVideoBreakdownForRange","getCountryBreakdown","getVideoDetailBreakdowns","videoTitle","filter","countries","timeseries","Promise","all","map","c","country","field","views","total_watch_time","date","getVideoDashboardData","experience","comparison","watchTime","videos","overview","totalViews","total_views","totalWatchTimeMs","totalPlayingTimeMs","total_playing_time","viewerExperienceScore","globalExperienceScore","global_value","watchTimeSeries","totalMs","topVideos","title","playingTimeMs","getVideoThumbnails","db","contentResource","byId","byTitle","videoIds","length","rows","select","id","playbackId","sql","fields","as","where","join","idToPlayback","Map","r","result","i","Math","min","videoId","get","and","count","eq","sql","normalizeRespondentKey","row","respondentKey","userId","emailListSubscriberId","surveySessionId","getRowTimestamp","date","updatedAt","createdAt","Date","getTime","sortByNewest","rows","sort","a","b","createSurveyProvider","db","schema","contentResource","contentResourceResource","questionResponse","rangeToInterval","range","rangeWhere","column","sql","raw","fetchCanonicalRows","users","rawRows","select","responseId","id","surveyId","surveyTitle","surveySlug","questionId","questionText","questionType","answer","fields","userEmail","email","from","leftJoin","eq","where","latestByAnswer","Map","dedupeKey","current","get","set","Array","values","getSurveySummary","surveyCount","total","count","type","canonicalRows","respondentKeys","Set","map","totalSurveys","totalResponses","length","uniqueRespondents","size","avgResponsesPerSurvey","getSurveyList","responsesBySurvey","responses","respondents","add","surveys","questionCounts","resourceOfId","questionCount","innerJoin","and","resourceId","groupBy","questionCountMap","qc","s","counts","getSurveyResponsesByDay","grouped","toISOString","slice","entries","entryA","entryB","localeCompare","getSurveyQuestionBreakdown","limit","question","answers","entry","answerDistribution","getSurveyResponses","offset","String"]}