"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var AnalyticsService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalyticsService = void 0; const common_1 = require("@nestjs/common"); const prisma_service_1 = require("../prisma/prisma.service"); const authorization_service_1 = require("../common/authorization.service"); let AnalyticsService = AnalyticsService_1 = class AnalyticsService { prisma; authorizationService; logger = new common_1.Logger(AnalyticsService_1.name); cache = new Map(); cacheTTL = 30_000; constructor(prisma, authorizationService) { this.prisma = prisma; this.authorizationService = authorizationService; } getCached(key) { const entry = this.cache.get(key); if (entry && entry.expiresAt > Date.now()) return entry.data; this.cache.delete(key); return null; } setCache(key, data) { this.cache.set(key, { data, expiresAt: Date.now() + this.cacheTTL }); if (this.cache.size > 100) { const oldest = this.cache.keys().next().value; if (oldest) this.cache.delete(oldest); } } async getDashboardMetrics(podcastId, userId) { await this.authorizationService.checkPodcastAccess(podcastId, userId); const cached = this.getCached(`dashboard:${podcastId}`); if (cached) return cached; const [totalGuests, guestsByStatus, totalEpisodes, episodesByStatus, upcomingBookings, recentGuests, topPriorityGuests,] = await Promise.all([ this.prisma.guest.count({ where: { podcastId } }), this.prisma.guest.groupBy({ by: ["status"], where: { podcastId }, _count: { id: true }, }), this.prisma.episode.count({ where: { podcastId } }), this.prisma.episode.groupBy({ by: ["status"], where: { podcastId }, _count: { id: true }, }), this.prisma.booking.count({ where: { episode: { podcastId }, startTime: { gte: new Date() }, }, }), this.prisma.guest.findMany({ where: { podcastId }, orderBy: { createdAt: "desc" }, take: 5, select: { id: true, fullName: true, email: true, company: true, status: true, priorityScore: true, createdAt: true, }, }), this.prisma.guest.findMany({ where: { podcastId, status: { in: ["prospect", "contacted"] } }, orderBy: { priorityScore: "desc" }, take: 5, select: { id: true, fullName: true, company: true, title: true, priorityScore: true, }, }), ]); const guestStatusMap = {}; for (const item of guestsByStatus) { guestStatusMap[item.status] = item._count.id; } const episodeStatusMap = {}; for (const item of episodesByStatus) { episodeStatusMap[item.status] = item._count.id; } const result = { totalGuests, guestsByStatus: guestStatusMap, totalEpisodes, episodesByStatus: episodeStatusMap, upcomingBookings, recentGuests, topPriorityGuests, }; this.setCache(`dashboard:${podcastId}`, result); return result; } async getGuestPipeline(podcastId, userId) { await this.authorizationService.checkPodcastAccess(podcastId, userId); const cached = this.getCached(`pipeline:${podcastId}`); if (cached) return cached; const pipeline = await this.prisma.guest.groupBy({ by: ["status"], where: { podcastId }, _count: { id: true }, _avg: { priorityScore: true }, }); const result = pipeline.map((stage) => ({ status: stage.status, count: stage._count.id, avgScore: Math.round(stage._avg.priorityScore || 0), })); this.setCache(`pipeline:${podcastId}`, result); return result; } async getOutreachMetrics(podcastId, userId) { await this.authorizationService.checkPodcastAccess(podcastId, userId); const cached = this.getCached(`outreach:${podcastId}`); if (cached) return cached; const guests = await this.prisma.guest.findMany({ where: { podcastId }, select: { id: true }, }); const guestIds = guests.map((g) => g.id); if (guestIds.length === 0) { return { totalSequences: 0, activeSequences: 0, totalMessagesSent: 0, openRate: 0, clickRate: 0, replyRate: 0, }; } const [totalSequences, activeSequences, messageStats] = await Promise.all([ this.prisma.outreachSequence.count({ where: { guestId: { in: guestIds } }, }), this.prisma.outreachSequence.count({ where: { guestId: { in: guestIds }, status: "active" }, }), this.prisma.outreachMessage.aggregate({ where: { sequence: { guestId: { in: guestIds } } }, _count: { id: true, sentAt: true, openedAt: true, clickedAt: true }, }), ]); const sent = messageStats._count.sentAt; const opened = messageStats._count.openedAt; const clicked = messageStats._count.clickedAt; const result = { totalSequences, activeSequences, totalMessagesSent: sent, openRate: sent > 0 ? Math.round((opened / sent) * 100) : 0, clickRate: sent > 0 ? Math.round((clicked / sent) * 100) : 0, }; this.setCache(`outreach:${podcastId}`, result); return result; } async getSchedulingMetrics(podcastId, userId, daysBack = 90) { await this.authorizationService.checkPodcastAccess(podcastId, userId); const cacheKey = `scheduling:${podcastId}:${daysBack}`; const cached = this.getCached(cacheKey); if (cached) return cached; const since = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1000); const [scheduledCount, recordedCount, publishedCount, avgDuration] = await Promise.all([ this.prisma.episode.count({ where: { podcastId, status: "scheduled", createdAt: { gte: since }, }, }), this.prisma.episode.count({ where: { podcastId, status: "recorded", createdAt: { gte: since }, }, }), this.prisma.episode.count({ where: { podcastId, status: "published", createdAt: { gte: since }, }, }), this.prisma.episode.aggregate({ where: { podcastId, durationMinutes: { not: null } }, _avg: { durationMinutes: true }, }), ]); const result = { scheduledCount, recordedCount, publishedCount, conversionRate: scheduledCount > 0 ? Math.round((recordedCount / scheduledCount) * 100) : 0, avgDurationMinutes: Math.round(avgDuration._avg.durationMinutes || 0), }; this.setCache(cacheKey, result); return result; } async getRecentActivity(podcastId, userId, limit = 20) { await this.authorizationService.checkPodcastAccess(podcastId, userId); const cacheKey = `recent:${podcastId}:${limit}`; const cached = this.getCached(cacheKey); if (cached) return cached; const [recentGuests, recentEpisodes, recentBookings, recentOutreach] = await Promise.all([ this.prisma.guest.findMany({ where: { podcastId }, orderBy: { createdAt: "desc" }, take: limit, select: { id: true, fullName: true, status: true, createdAt: true, _count: { select: { outreachSequences: true, }, }, }, }), this.prisma.episode.findMany({ where: { podcastId }, orderBy: { createdAt: "desc" }, take: limit, select: { id: true, title: true, status: true, scheduledAt: true, createdAt: true, guest: { select: { fullName: true, }, }, }, }), this.prisma.booking.findMany({ where: { episode: { podcastId } }, orderBy: { createdAt: "desc" }, take: limit, select: { id: true, startTime: true, isConfirmed: true, createdAt: true, episode: { select: { title: true, guest: { select: { fullName: true, }, }, }, }, }, }), this.prisma.outreachMessage.findMany({ where: { sequence: { guest: { podcastId } } }, orderBy: { sentAt: "desc" }, take: limit, select: { id: true, type: true, sentAt: true, openedAt: true, sequence: { select: { guest: { select: { fullName: true, }, }, }, }, }, }), ]); const activities = [ ...recentGuests.map((g) => ({ type: "guest_added", id: g.id, title: `New guest: ${g.fullName}`, status: g.status, timestamp: g.createdAt, })), ...recentEpisodes.map((e) => ({ type: "episode_created", id: e.id, title: `Episode: ${e.title}`, subtitle: `With ${e.guest.fullName}`, status: e.status, timestamp: e.createdAt, scheduledAt: e.scheduledAt, })), ...recentBookings.map((b) => ({ type: "booking_created", id: b.id, title: `Booking: ${b.episode.title}`, subtitle: `With ${b.episode.guest.fullName}`, timestamp: b.createdAt, startTime: b.startTime, isConfirmed: b.isConfirmed, })), ...recentOutreach.map((o) => ({ type: "message_sent", id: o.id, title: `Email sent to ${o.sequence.guest.fullName}`, subtitle: o.type, timestamp: o.sentAt, openedAt: o.openedAt, })), ]; const result = activities .filter((a) => a.timestamp != null) .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, limit); this.setCache(cacheKey, result); return result; } }; exports.AnalyticsService = AnalyticsService; exports.AnalyticsService = AnalyticsService = AnalyticsService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [prisma_service_1.PrismaService, authorization_service_1.AuthorizationService]) ], AnalyticsService); //# sourceMappingURL=analytics.service.js.map