UNPKG

cakemail-mcp-server

Version:

Enterprise MCP server for Cakemail API integration with Claude AI - includes comprehensive template management, list management, sub-account management, BEEeditor visual email design, and advanced analytics

843 lines 40.2 kB
/** * Behavioral Pattern Recognition Module for Cakemail Campaign Logs * Analyzes subscriber engagement patterns and provides actionable insights */ import { performance } from 'perf_hooks'; export var EngagementLevel; (function (EngagementLevel) { EngagementLevel["HIGHLY_ENGAGED"] = "highly_engaged"; EngagementLevel["MODERATELY_ENGAGED"] = "moderately_engaged"; EngagementLevel["LOW_ENGAGEMENT"] = "low_engagement"; EngagementLevel["DECLINING"] = "declining"; EngagementLevel["INACTIVE"] = "inactive"; EngagementLevel["AT_RISK"] = "at_risk"; })(EngagementLevel || (EngagementLevel = {})); export var BehaviorPattern; (function (BehaviorPattern) { BehaviorPattern["CONSISTENT_OPENER"] = "consistent_opener"; BehaviorPattern["SELECTIVE_CLICKER"] = "selective_clicker"; BehaviorPattern["MOBILE_PREFERRER"] = "mobile_preferrer"; BehaviorPattern["WEEKEND_ENGAGER"] = "weekend_engager"; BehaviorPattern["EARLY_BIRD"] = "early_bird"; BehaviorPattern["NIGHT_OWL"] = "night_owl"; BehaviorPattern["BINGE_READER"] = "binge_reader"; BehaviorPattern["QUICK_SCANNER"] = "quick_scanner"; BehaviorPattern["UNSUBSCRIBE_RISK"] = "unsubscribe_risk"; BehaviorPattern["LOYAL_SUBSCRIBER"] = "loyal_subscriber"; BehaviorPattern["CONTENT_SKIMMER"] = "content_skimmer"; })(BehaviorPattern || (BehaviorPattern = {})); export class BehavioralPatternRecognizer { minCampaignsForAnalysis; debugMode; constructor(options = {}) { this.minCampaignsForAnalysis = options.minCampaignsForAnalysis || 3; this.debugMode = options.debugMode || false; } /** * Main analysis function that processes campaign logs and returns behavioral insights */ async analyzeCampaignLogs(logsData) { const startTime = performance.now(); if (this.debugMode) { console.log(`[Behavioral Analysis] Starting analysis of ${logsData.length} log entries`); } // Group logs by contact const contactLogs = this.groupLogsByContact(logsData); // Analyze each contact's behavior const contactProfiles = {}; for (const [contactId, logs] of Object.entries(contactLogs)) { if (logs.length >= this.minCampaignsForAnalysis) { const profile = await this.analyzeContactBehavior(parseInt(contactId), logs); contactProfiles[contactId] = profile; } } // Generate aggregate insights const aggregateInsights = this.generateAggregateInsights(contactProfiles); // Identify anomalies and trends const anomalies = this.detectAnomalies(contactProfiles); // Generate recommendations const recommendations = this.generateRecommendations(contactProfiles, aggregateInsights); // Generate predictive insights const predictiveInsights = this.generatePredictiveInsights(contactProfiles); const endTime = performance.now(); const processingTime = endTime - startTime; return { analysis_metadata: { total_contacts_analyzed: Object.keys(contactProfiles).length, total_log_entries: logsData.length, analysis_period: this.getAnalysisPeriod(logsData), generated_at: new Date().toISOString(), performance_metrics: (() => { const memUsage = this.getMemoryUsage(); const metrics = { processing_time_ms: Math.round(processingTime) }; if (memUsage !== undefined) { metrics.memory_usage_mb = memUsage; } return metrics; })() }, contact_profiles: contactProfiles, aggregate_insights: aggregateInsights, anomalies: anomalies, recommendations: recommendations, predictive_insights: predictiveInsights }; } groupLogsByContact(logsData) { const contactLogs = {}; for (const log of logsData) { const contactId = log.contact_id; if (contactId) { if (!contactLogs[contactId]) { contactLogs[contactId] = []; } contactLogs[contactId].push(log); } } return contactLogs; } async analyzeContactBehavior(contactId, logs) { // Calculate engagement metrics const engagementMetrics = this.calculateEngagementMetrics(logs); // Determine engagement level const engagementLevel = this.classifyEngagementLevel(engagementMetrics); // Identify behavioral patterns const behavioralPatterns = this.identifyBehavioralPatterns(logs, engagementMetrics); // Determine lifecycle stage const lifecycleStage = this.determineLifecycleStage(logs, engagementMetrics); // Predict future actions const predictedActions = this.predictFutureActions(logs, engagementMetrics); // Find optimal send time const optimalSendTime = this.findOptimalSendTime(logs); // Analyze content preferences const contentPreferences = this.analyzeContentPreferences(logs); // Calculate risk scores const riskScores = this.calculateRiskScores(logs, engagementMetrics); // Generate personalization opportunities const personalizationOpportunities = this.generatePersonalizationOpportunities(logs, behavioralPatterns); // Generate segment recommendations const segmentRecommendations = this.generateSegmentRecommendations(engagementLevel, behavioralPatterns); // Get email from logs const email = logs[0]?.email || `contact_${contactId}@unknown.com`; return { contact_id: contactId, email: email, engagement_level: engagementLevel, engagement_metrics: engagementMetrics, behavioral_patterns: behavioralPatterns, lifecycle_stage: lifecycleStage, predicted_actions: predictedActions, ...(optimalSendTime !== undefined && { optimal_send_time: optimalSendTime }), content_preferences: contentPreferences, risk_scores: riskScores, personalization_opportunities: personalizationOpportunities, segment_recommendations: segmentRecommendations }; } calculateEngagementMetrics(logs) { // Count different event types const eventCounts = {}; logs.forEach(log => { const eventType = log.type?.name || log.type || 'unknown'; eventCounts[eventType] = (eventCounts[eventType] || 0) + 1; }); const totalOpens = (eventCounts['open'] || 0) + (eventCounts['implied_open'] || 0); const totalClicks = eventCounts['click'] || 0; // Get unique campaigns const campaignIds = new Set(logs.map(log => log.campaign_id).filter(Boolean)); const totalCampaignsSent = campaignIds.size; // Calculate rates const openRate = totalCampaignsSent > 0 ? totalOpens / totalCampaignsSent : 0; const clickRate = totalCampaignsSent > 0 ? totalClicks / totalCampaignsSent : 0; const clickToOpenRate = totalOpens > 0 ? totalClicks / totalOpens : 0; // Calculate timing metrics const openTimes = logs .filter(log => ['open', 'implied_open'].includes(log.type?.name || log.type)) .map(log => log.timestamp) .filter(Boolean); const clickTimes = logs .filter(log => (log.type?.name || log.type) === 'click') .map(log => log.timestamp) .filter(Boolean); const avgTimeToOpen = openTimes.length > 0 ? openTimes.reduce((sum, time) => sum + time, 0) / openTimes.length : undefined; const avgTimeToClick = clickTimes.length > 0 ? clickTimes.reduce((sum, time) => sum + time, 0) / clickTimes.length : undefined; // Determine engagement trend const engagementTrend = this.calculateEngagementTrend(logs); // Last engagement const allEngagementTimes = logs .filter(log => ['open', 'click', 'implied_open'].includes(log.type?.name || log.type)) .map(log => log.timestamp) .filter(Boolean); let lastEngagementDate; let daysSinceLastEngagement; if (allEngagementTimes.length > 0) { const lastEngagementTimestamp = Math.max(...allEngagementTimes); lastEngagementDate = new Date(lastEngagementTimestamp * 1000).toISOString(); daysSinceLastEngagement = Math.floor((Date.now() - lastEngagementTimestamp * 1000) / (1000 * 60 * 60 * 24)); } // Calculate engagement consistency const engagementConsistency = this.calculateEngagementConsistency(logs); // Find peak engagement days const peakEngagementDays = this.findPeakEngagementDays(logs); return { total_campaigns_sent: totalCampaignsSent, total_opens: totalOpens, total_clicks: totalClicks, unique_opens: new Set(logs.filter(log => ['open', 'implied_open'].includes(log.type?.name || log.type)).map(log => log.id)).size, unique_clicks: new Set(logs.filter(log => (log.type?.name || log.type) === 'click').map(log => log.id)).size, open_rate: Number(openRate.toFixed(3)), click_rate: Number(clickRate.toFixed(3)), click_to_open_rate: Number(clickToOpenRate.toFixed(3)), ...(avgTimeToOpen !== undefined && { average_time_to_open: avgTimeToOpen }), ...(avgTimeToClick !== undefined && { average_time_to_click: avgTimeToClick }), engagement_trend: engagementTrend, ...(lastEngagementDate !== undefined && { last_engagement_date: lastEngagementDate }), ...(daysSinceLastEngagement !== undefined && { days_since_last_engagement: daysSinceLastEngagement }), engagement_consistency: engagementConsistency, peak_engagement_days: peakEngagementDays }; } classifyEngagementLevel(metrics) { if (metrics.days_since_last_engagement && metrics.days_since_last_engagement > 90) { return EngagementLevel.INACTIVE; } if (metrics.engagement_trend === "declining" && metrics.open_rate < 0.1) { return EngagementLevel.AT_RISK; } if (metrics.open_rate >= 0.5 && metrics.click_rate >= 0.1) { return EngagementLevel.HIGHLY_ENGAGED; } else if (metrics.open_rate >= 0.25 && metrics.click_rate >= 0.05) { return EngagementLevel.MODERATELY_ENGAGED; } else if (metrics.engagement_trend === "declining") { return EngagementLevel.DECLINING; } else { return EngagementLevel.LOW_ENGAGEMENT; } } identifyBehavioralPatterns(logs, metrics) { const patterns = []; // Consistent opener pattern if (metrics.open_rate > 0.8) { patterns.push({ pattern: BehaviorPattern.CONSISTENT_OPENER, confidence: Math.min(metrics.open_rate, 1.0), description: `Opens ${(metrics.open_rate * 100).toFixed(1)}% of campaigns consistently`, recommendation: "Continue current email frequency and consider premium content", supporting_data: { open_rate: metrics.open_rate, total_opens: metrics.total_opens }, impact_score: 8 }); } // Selective clicker pattern if (metrics.click_to_open_rate > 0.3) { patterns.push({ pattern: BehaviorPattern.SELECTIVE_CLICKER, confidence: metrics.click_to_open_rate, description: `High click-to-open rate of ${(metrics.click_to_open_rate * 100).toFixed(1)}%`, recommendation: "Focus on high-quality, targeted content with clear CTAs", supporting_data: { click_to_open_rate: metrics.click_to_open_rate }, impact_score: 7 }); } // Loyal subscriber pattern if (metrics.engagement_consistency > 0.8 && metrics.total_campaigns_sent > 10) { patterns.push({ pattern: BehaviorPattern.LOYAL_SUBSCRIBER, confidence: metrics.engagement_consistency, description: "Consistently engages across multiple campaigns", recommendation: "Offer exclusive content or VIP benefits to maintain loyalty", supporting_data: { consistency: metrics.engagement_consistency, campaigns: metrics.total_campaigns_sent }, impact_score: 9 }); } // Mobile preference detection const mobileInteractions = logs.filter(log => log.user_agent?.device_type === 'mobile' || log.user_agent?.is_mobile === true).length; if (mobileInteractions / logs.length > 0.7) { patterns.push({ pattern: BehaviorPattern.MOBILE_PREFERRER, confidence: mobileInteractions / logs.length, description: "Primarily engages via mobile device", recommendation: "Optimize emails for mobile experience", supporting_data: { mobile_interaction_rate: mobileInteractions / logs.length }, impact_score: 6 }); } // Time-based patterns const hourEngagement = this.analyzeTimePatterns(logs); if (hourEngagement && Object.keys(hourEngagement).length > 0) { const peakHour = Object.keys(hourEngagement).reduce((a, b) => hourEngagement[a] > hourEngagement[b] ? a : b); if (parseInt(peakHour) < 9) { patterns.push({ pattern: BehaviorPattern.EARLY_BIRD, confidence: 0.8, description: "Most active in early morning hours", recommendation: "Send campaigns between 6-9 AM", supporting_data: { peak_hour: peakHour, hour_distribution: hourEngagement }, impact_score: 5 }); } else if (parseInt(peakHour) > 20) { patterns.push({ pattern: BehaviorPattern.NIGHT_OWL, confidence: 0.8, description: "Most active in evening hours", recommendation: "Send campaigns between 7-10 PM", supporting_data: { peak_hour: peakHour, hour_distribution: hourEngagement }, impact_score: 5 }); } } // Unsubscribe risk pattern if (metrics.engagement_trend === "declining" && metrics.days_since_last_engagement && metrics.days_since_last_engagement > 30) { const riskScore = Math.min((metrics.days_since_last_engagement - 30) / 60, 1.0); patterns.push({ pattern: BehaviorPattern.UNSUBSCRIBE_RISK, confidence: riskScore, description: `High unsubscribe risk - ${metrics.days_since_last_engagement} days since last engagement`, recommendation: "Send re-engagement campaign or reduce frequency", supporting_data: { days_since_engagement: metrics.days_since_last_engagement, risk_score: riskScore }, impact_score: 10 }); } // Quick scanner pattern (opens but doesn't click much) if (metrics.open_rate > 0.5 && metrics.click_to_open_rate < 0.1) { patterns.push({ pattern: BehaviorPattern.QUICK_SCANNER, confidence: 0.7, description: "Opens emails frequently but rarely clicks", recommendation: "Focus on compelling subject lines and preview text rather than CTAs", supporting_data: { open_rate: metrics.open_rate, click_to_open_rate: metrics.click_to_open_rate }, impact_score: 4 }); } return patterns.sort((a, b) => b.impact_score - a.impact_score); } calculateEngagementTrend(logs) { // Sort logs by timestamp const sortedLogs = logs .filter(log => log.timestamp) .sort((a, b) => a.timestamp - b.timestamp); if (sortedLogs.length < 6) { return "stable"; // Not enough data } // Split into two halves and compare engagement rates const midPoint = Math.floor(sortedLogs.length / 2); const firstHalf = sortedLogs.slice(0, midPoint); const secondHalf = sortedLogs.slice(midPoint); const calculateEngagementRate = (logSubset) => { const engagementEvents = logSubset.filter(log => ['open', 'click', 'implied_open'].includes(log.type?.name || log.type)).length; return logSubset.length > 0 ? engagementEvents / logSubset.length : 0; }; const firstHalfRate = calculateEngagementRate(firstHalf); const secondHalfRate = calculateEngagementRate(secondHalf); if (secondHalfRate > firstHalfRate * 1.2) { return "increasing"; } else if (secondHalfRate < firstHalfRate * 0.8) { return "declining"; } else { return "stable"; } } calculateEngagementConsistency(logs) { // Group logs by campaign and calculate engagement rate per campaign const campaignEngagement = {}; logs.forEach(log => { const campaignId = log.campaign_id || 'unknown'; if (!campaignEngagement[campaignId]) { campaignEngagement[campaignId] = { total: 0, engaged: 0 }; } campaignEngagement[campaignId].total++; if (['open', 'click', 'implied_open'].includes(log.type?.name || log.type)) { campaignEngagement[campaignId].engaged++; } }); // Calculate engagement rates for each campaign const engagementRates = Object.values(campaignEngagement) .map(campaign => campaign.total > 0 ? campaign.engaged / campaign.total : 0); if (engagementRates.length === 0) return 0; // Calculate consistency as inverse of standard deviation const mean = engagementRates.reduce((sum, rate) => sum + rate, 0) / engagementRates.length; const variance = engagementRates.reduce((sum, rate) => sum + Math.pow(rate - mean, 2), 0) / engagementRates.length; const stdDev = Math.sqrt(variance); // Convert to 0-1 scale where 1 is perfectly consistent return Math.max(0, 1 - stdDev); } findPeakEngagementDays(logs) { const dayEngagement = {}; logs.forEach(log => { if (log.timestamp && ['open', 'click', 'implied_open'].includes(log.type?.name || log.type)) { const date = new Date(log.timestamp * 1000); const dayOfWeek = date.toLocaleDateString('en-US', { weekday: 'long' }); dayEngagement[dayOfWeek] = (dayEngagement[dayOfWeek] || 0) + 1; } }); // Return top 2 days return Object.entries(dayEngagement) .sort(([, a], [, b]) => b - a) .slice(0, 2) .map(([day]) => day); } determineLifecycleStage(logs, metrics) { const engagementLevel = this.classifyEngagementLevel(metrics); if (!logs.length) return "unknown"; const timestamps = logs.map(log => log.timestamp).filter(Boolean); if (!timestamps.length) return "unknown"; const daysActive = Math.floor((Date.now() - Math.min(...timestamps) * 1000) / (1000 * 60 * 60 * 24)); if (daysActive < 30) { return "new_subscriber"; } else if (engagementLevel === EngagementLevel.HIGHLY_ENGAGED) { return "engaged_subscriber"; } else if ([EngagementLevel.DECLINING, EngagementLevel.AT_RISK].includes(engagementLevel)) { return "at_risk_subscriber"; } else if (engagementLevel === EngagementLevel.INACTIVE) { return "inactive_subscriber"; } else { return "regular_subscriber"; } } predictFutureActions(_logs, metrics) { const engagementLevel = this.classifyEngagementLevel(metrics); const predictions = {}; // Predict open probability predictions["will_open_next_campaign"] = Math.min(metrics.open_rate * 1.1, 1.0); // Predict click probability predictions["will_click_next_campaign"] = Math.min(metrics.click_rate * 1.1, 1.0); // Predict unsubscribe probability let unsubscribeRisk = 0.0; if (metrics.engagement_trend === "declining") unsubscribeRisk += 0.3; if (metrics.days_since_last_engagement && metrics.days_since_last_engagement > 60) unsubscribeRisk += 0.4; if (metrics.open_rate < 0.1) unsubscribeRisk += 0.2; predictions["will_unsubscribe"] = Math.min(unsubscribeRisk, 1.0); // Predict re-engagement probability if ([EngagementLevel.DECLINING, EngagementLevel.AT_RISK].includes(engagementLevel)) { predictions["will_re_engage"] = Math.max(0.3 - (metrics.days_since_last_engagement || 0) / 100, 0.1); } else { predictions["will_re_engage"] = 0.8; } return predictions; } findOptimalSendTime(logs) { const hourEngagement = this.analyzeTimePatterns(logs); if (!hourEngagement || Object.keys(hourEngagement).length === 0) { return undefined; } const bestHour = Object.keys(hourEngagement).reduce((a, b) => hourEngagement[a] > hourEngagement[b] ? a : b); const hour = parseInt(bestHour); if (hour < 12) { return `${hour}:00 AM`; } else if (hour === 12) { return "12:00 PM"; } else { return `${hour - 12}:00 PM`; } } analyzeTimePatterns(logs) { const hourEngagement = {}; logs.forEach(log => { if (log.timestamp && ['open', 'click', 'implied_open'].includes(log.type?.name || log.type)) { const hour = new Date(log.timestamp * 1000).getHours(); hourEngagement[hour.toString()] = (hourEngagement[hour.toString()] || 0) + 1; } }); return hourEngagement; } analyzeContentPreferences(logs) { // This would be enhanced with actual campaign content analysis // For now, return basic preferences based on engagement patterns const clickLogs = logs.filter(log => (log.type?.name || log.type) === 'click'); const preferences = { promotional_content: 0.5, educational_content: 0.5, newsletter_content: 0.5, product_updates: 0.5 }; // Basic heuristics based on click-through rate if (clickLogs.length / logs.length > 0.1) { preferences["promotional_content"] = 0.8; } return preferences; } calculateRiskScores(logs, metrics) { const risks = {}; // Churn risk let churnRisk = 0.0; if (metrics.engagement_trend === "declining") churnRisk += 0.4; if (metrics.days_since_last_engagement && metrics.days_since_last_engagement > 30) { churnRisk += Math.min((metrics.days_since_last_engagement - 30) / 60, 0.5); } if (metrics.open_rate < 0.05) churnRisk += 0.3; risks["churn_risk"] = Math.min(churnRisk, 1.0); // Spam complaint risk let spamRisk = 0.1; // baseline if (metrics.open_rate < 0.02 && logs.length > 10) spamRisk += 0.3; risks["spam_complaint_risk"] = Math.min(spamRisk, 1.0); // Low engagement risk risks["low_engagement_risk"] = 1.0 - metrics.open_rate; return risks; } generatePersonalizationOpportunities(_logs, patterns) { const opportunities = []; // Time-based personalization const timePatterns = patterns.filter(p => [BehaviorPattern.EARLY_BIRD, BehaviorPattern.NIGHT_OWL].includes(p.pattern)); if (timePatterns.length > 0) { opportunities.push("Personalize send times based on individual engagement patterns"); } // Device-based personalization const mobilePattern = patterns.find(p => p.pattern === BehaviorPattern.MOBILE_PREFERRER); if (mobilePattern) { opportunities.push("Create mobile-first email designs for this subscriber"); } // Content-based personalization const selectivePattern = patterns.find(p => p.pattern === BehaviorPattern.SELECTIVE_CLICKER); if (selectivePattern) { opportunities.push("Curate highly targeted content based on past click behavior"); } // Engagement-based personalization const consistentPattern = patterns.find(p => p.pattern === BehaviorPattern.CONSISTENT_OPENER); if (consistentPattern) { opportunities.push("Offer exclusive or early access content to reward loyalty"); } return opportunities; } generateSegmentRecommendations(level, patterns) { const recommendations = []; // Engagement level based segments switch (level) { case EngagementLevel.HIGHLY_ENGAGED: recommendations.push("VIP Segment", "Brand Advocates", "Early Access Group"); break; case EngagementLevel.MODERATELY_ENGAGED: recommendations.push("Regular Subscribers", "Content Nurture Segment"); break; case EngagementLevel.DECLINING: recommendations.push("Re-engagement Campaign", "Win-back Segment"); break; case EngagementLevel.AT_RISK: recommendations.push("Urgent Re-engagement", "Churn Prevention"); break; case EngagementLevel.INACTIVE: recommendations.push("Sunset Campaign", "Final Attempt Segment"); break; } // Pattern-based segments patterns.forEach(pattern => { switch (pattern.pattern) { case BehaviorPattern.MOBILE_PREFERRER: recommendations.push("Mobile-Optimized Segment"); break; case BehaviorPattern.EARLY_BIRD: recommendations.push("Morning Engagement Segment"); break; case BehaviorPattern.NIGHT_OWL: recommendations.push("Evening Engagement Segment"); break; case BehaviorPattern.SELECTIVE_CLICKER: recommendations.push("High-Intent Segment"); break; } }); return [...new Set(recommendations)]; // Remove duplicates } generateAggregateInsights(contactProfiles) { if (Object.keys(contactProfiles).length === 0) { return { engagement_level_distribution: {}, average_open_rate: 0, average_click_rate: 0, most_common_behavioral_patterns: [], lifecycle_stage_distribution: {}, total_at_risk_contacts: 0, high_value_contacts: 0, optimal_send_times: {}, content_performance: {}, device_preferences: {} }; } const profiles = Object.values(contactProfiles); // Engagement level distribution const engagementDistribution = {}; profiles.forEach(profile => { const level = profile.engagement_level; engagementDistribution[level] = (engagementDistribution[level] || 0) + 1; }); // Average metrics const avgOpenRate = profiles.reduce((sum, p) => sum + p.engagement_metrics.open_rate, 0) / profiles.length; const avgClickRate = profiles.reduce((sum, p) => sum + p.engagement_metrics.click_rate, 0) / profiles.length; // Most common patterns const allPatterns = []; profiles.forEach(profile => { profile.behavioral_patterns.forEach(pattern => { allPatterns.push(pattern.pattern); }); }); const patternCounts = {}; allPatterns.forEach(pattern => { patternCounts[pattern] = (patternCounts[pattern] || 0) + 1; }); const commonPatterns = Object.entries(patternCounts) .sort(([, a], [, b]) => b - a) .slice(0, 5); // Lifecycle stage distribution const lifecycleDistribution = {}; profiles.forEach(profile => { const stage = profile.lifecycle_stage; lifecycleDistribution[stage] = (lifecycleDistribution[stage] || 0) + 1; }); // Risk analysis const atRiskContacts = profiles.filter(p => [EngagementLevel.AT_RISK, EngagementLevel.DECLINING].includes(p.engagement_level)).length; const highValueContacts = profiles.filter(p => p.engagement_level === EngagementLevel.HIGHLY_ENGAGED).length; // Optimal send times const sendTimes = {}; profiles.forEach(profile => { if (profile.optimal_send_time) { sendTimes[profile.optimal_send_time] = (sendTimes[profile.optimal_send_time] || 0) + 1; } }); // Content performance (simplified) const contentPerformance = { promotional: profiles.reduce((sum, p) => sum + (p.content_preferences.promotional_content || 0), 0) / profiles.length, educational: profiles.reduce((sum, p) => sum + (p.content_preferences.educational_content || 0), 0) / profiles.length, newsletter: profiles.reduce((sum, p) => sum + (p.content_preferences.newsletter_content || 0), 0) / profiles.length }; // Device preferences const mobilePreference = profiles.filter(p => p.behavioral_patterns.some(pattern => pattern.pattern === BehaviorPattern.MOBILE_PREFERRER)).length; const devicePreferences = { mobile: mobilePreference, desktop: profiles.length - mobilePreference }; return { engagement_level_distribution: engagementDistribution, average_open_rate: Number(avgOpenRate.toFixed(3)), average_click_rate: Number(avgClickRate.toFixed(3)), most_common_behavioral_patterns: commonPatterns, lifecycle_stage_distribution: lifecycleDistribution, total_at_risk_contacts: atRiskContacts, high_value_contacts: highValueContacts, optimal_send_times: sendTimes, content_performance: contentPerformance, device_preferences: devicePreferences }; } detectAnomalies(contactProfiles) { const anomalies = []; const profiles = Object.values(contactProfiles); // Find contacts with sudden engagement drops profiles.forEach(profile => { if (profile.engagement_metrics.engagement_trend === "declining" && profile.engagement_metrics.open_rate < 0.1 && profile.engagement_metrics.total_campaigns_sent > 5) { anomalies.push({ type: "sudden_engagement_drop", contact_id: profile.contact_id, description: `Contact ${profile.contact_id} shows sudden drop in engagement`, severity: "high", metrics: { open_rate: profile.engagement_metrics.open_rate, days_since_last_engagement: profile.engagement_metrics.days_since_last_engagement } }); } }); // Find unusually high performers const highPerformers = profiles.filter(p => p.engagement_metrics.open_rate > 0.9 && p.engagement_metrics.click_rate > 0.2); if (highPerformers.length > 0) { anomalies.push({ type: "exceptional_engagement", description: `Found ${highPerformers.length} exceptionally engaged contacts`, severity: "positive", contact_ids: highPerformers.map(p => p.contact_id) }); } // Detect potential bot behavior (extremely high engagement but no real patterns) profiles.forEach(profile => { if (profile.engagement_metrics.open_rate > 0.95 && profile.engagement_metrics.engagement_consistency < 0.3 && profile.engagement_metrics.total_campaigns_sent > 10) { anomalies.push({ type: "potential_bot_behavior", contact_id: profile.contact_id, description: `Contact ${profile.contact_id} shows potential automated engagement patterns`, severity: "medium", metrics: { open_rate: profile.engagement_metrics.open_rate, consistency: profile.engagement_metrics.engagement_consistency } }); } }); return anomalies; } generateRecommendations(_contactProfiles, aggregateInsights) { const recommendations = []; // Re-engagement recommendations if (aggregateInsights.total_at_risk_contacts > 0) { recommendations.push({ type: "re_engagement", priority: "high", description: `${aggregateInsights.total_at_risk_contacts} contacts are at risk of churning`, action: "Create targeted re-engagement campaign for at-risk subscribers", expected_impact: "Reduce churn rate by 15-25%", affected_contacts: aggregateInsights.total_at_risk_contacts, estimated_roi: "High" }); } // Send time optimization const topSendTime = Object.entries(aggregateInsights.optimal_send_times) .sort(([, a], [, b]) => b - a)[0]; if (topSendTime) { recommendations.push({ type: "send_time_optimization", priority: "medium", description: `Optimize send times based on engagement patterns`, action: `Consider sending more campaigns at ${topSendTime[0]} when most subscribers are active`, expected_impact: "Increase open rates by 8-12%", estimated_roi: "Medium" }); } // Segmentation recommendations if (aggregateInsights.high_value_contacts > 10) { recommendations.push({ type: "vip_segmentation", priority: "medium", description: `Create VIP segment for ${aggregateInsights.high_value_contacts} highly engaged contacts`, action: "Develop exclusive content and offers for top-performing subscribers", expected_impact: "Increase customer lifetime value by 20-30%", affected_contacts: aggregateInsights.high_value_contacts, estimated_roi: "High" }); } // Mobile optimization const mobileUsers = aggregateInsights.device_preferences.mobile || 0; const totalUsers = Object.values(aggregateInsights.device_preferences).reduce((sum, count) => sum + count, 0); if (mobileUsers / totalUsers > 0.6) { recommendations.push({ type: "mobile_optimization", priority: "medium", description: "High percentage of mobile users detected", action: "Prioritize mobile-first email design and shorter subject lines", expected_impact: "Improve mobile engagement by 15-20%", estimated_roi: "Medium" }); } return recommendations.sort((a, b) => { const priorityOrder = { high: 3, medium: 2, low: 1 }; return priorityOrder[b.priority] - priorityOrder[a.priority]; }); } generatePredictiveInsights(contactProfiles) { const profiles = Object.values(contactProfiles); // Churn predictions const churnPredictions = profiles .filter(p => p.predicted_actions.will_unsubscribe > 0.3) .map(profile => ({ contact_id: profile.contact_id, churn_probability: profile.predicted_actions.will_unsubscribe, ...(profile.engagement_metrics.days_since_last_engagement !== undefined && { days_to_churn: Math.round(90 - profile.engagement_metrics.days_since_last_engagement) }), intervention_recommendations: [ "Send personalized re-engagement email", "Offer special discount or incentive", "Reduce email frequency", "Survey for feedback and preferences" ] })) .sort((a, b) => b.churn_probability - a.churn_probability); // Engagement forecasts const engagementForecasts = { overall_open_rate_trend: profiles.reduce((sum, p) => sum + p.predicted_actions.will_open_next_campaign, 0) / profiles.length, overall_click_rate_trend: profiles.reduce((sum, p) => sum + p.predicted_actions.will_click_next_campaign, 0) / profiles.length, re_engagement_potential: profiles.reduce((sum, p) => sum + p.predicted_actions.will_re_engage, 0) / profiles.length }; // Optimal campaign recommendations const optimalCampaigns = [ { segment: "highly_engaged", recommended_timing: "Tuesday 10:00 AM", content_type: "premium_content", expected_performance: { open_rate: 0.65, click_rate: 0.12 } }, { segment: "at_risk", recommended_timing: "Thursday 2:00 PM", content_type: "re_engagement", expected_performance: { open_rate: 0.25, click_rate: 0.05 } }, { segment: "mobile_users", recommended_timing: "Evening 7:00 PM", content_type: "mobile_optimized", expected_performance: { open_rate: 0.45, click_rate: 0.08 } } ]; return { churn_predictions: churnPredictions, engagement_forecasts: engagementForecasts, optimal_campaigns: optimalCampaigns }; } getAnalysisPeriod(logsData) { if (!logsData.length) return {}; const timestamps = logsData.map(log => log.timestamp).filter(Boolean); if (!timestamps.length) return {}; const minTimestamp = Math.min(...timestamps); const maxTimestamp = Math.max(...timestamps); return { start: new Date(minTimestamp * 1000).toISOString(), end: new Date(maxTimestamp * 1000).toISOString(), duration_days: Math.ceil((maxTimestamp - minTimestamp) / (60 * 60 * 24)) }; } getMemoryUsage() { try { if (process.memoryUsage) { return Math.round(process.memoryUsage().heapUsed / 1024 / 1024); } } catch (error) { // Memory usage not available } return undefined; } } // Export convenience function for quick analysis export async function analyzeBehavioralPatterns(logsData, options) { const analyzer = new BehavioralPatternRecognizer(options); return analyzer.analyzeCampaignLogs(logsData); } //# sourceMappingURL=behavioral-pattern-recognition.js.map