diff --git a/docs/analytics-implementation.md b/docs/analytics-implementation.md index 61f7fc43..0994f96e 100644 --- a/docs/analytics-implementation.md +++ b/docs/analytics-implementation.md @@ -133,6 +133,29 @@ setAnalyticsUserProperties({ // Set personalization user properties setPersonalizationUserProperties(profile); + +// Export analytics data grouped by user segments +const groupedData = exportGroupedAnalytics(); + +// Export analytics data for a specific date range +const dataForDateRange = exportGroupedAnalytics( + new Date('2024-01-01'), + new Date('2024-12-31') +); + +// Export analytics as CSV format +const csvExport = exportAnalyticsAsCSV(groupedData); + +// Export analytics summary with top events per segment +const summary = exportAnalyticsSummary(); + +// Log events with storage for export functionality +logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + { method: 'camera' }, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_123' +); ``` ## Key Metrics to Monitor @@ -167,6 +190,98 @@ To verify the implementation: 3. Verify user properties reflect current user state 4. Test error scenarios to ensure error events are logged +## Group Analyzer Export Functionality + +The analytics system now includes comprehensive export functionality for analyzing user behavior grouped by user segments. This allows administrators and analysts to understand usage patterns across different user types. + +### User Segments + +The system tracks four distinct user segments: + +- **healthcare_professional**: Licensed healthcare professionals +- **cosmetic_user**: Users focused on cosmetic applications +- **personal_medical_user**: Personal medical use cases +- **general_user**: All other users (default segment) + +### Export Functions + +#### `logAnalyticsEventWithStorage` +Enhanced logging function that stores events for export while maintaining regular analytics logging. + +```typescript +logAnalyticsEventWithStorage( + eventName: string, + parameters?: Record, + userSegment?: string, + userId?: string +) +``` + +#### `exportGroupedAnalytics` +Exports analytics data grouped by user segments with optional date filtering. + +```typescript +const groupedData = exportGroupedAnalytics( + dateFrom?: Date, + dateTo?: Date +): GroupedAnalyticsData +``` + +Returns data structured by user segment with: +- Event lists per segment +- Event counts per event type +- Unique user counts +- User properties + +#### `exportAnalyticsAsCSV` +Exports analytics data in CSV format for spreadsheet analysis. + +```typescript +const csvData = exportAnalyticsAsCSV( + groupedData?: GroupedAnalyticsData, + dateFrom?: Date, + dateTo?: Date +): string +``` + +#### `exportAnalyticsSummary` +Exports a summary report with top events and metrics per segment. + +```typescript +const summary = exportAnalyticsSummary( + dateFrom?: Date, + dateTo?: Date +): Record +``` + +### Example Usage + +```typescript +// Log events with storage for export +logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + { method: 'camera' }, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_123' +); + +// Export all data grouped by segments +const allData = exportGroupedAnalytics(); + +// Export data for specific date range +const monthlyData = exportGroupedAnalytics( + new Date('2024-01-01'), + new Date('2024-01-31') +); + +// Export as CSV for spreadsheet analysis +const csvReport = exportAnalyticsAsCSV(monthlyData); + +// Get summary report +const summary = exportAnalyticsSummary(); +console.log('Healthcare professionals:', summary.segmentSummary.healthcare_professional); +``` + ## Platform Support The implementation works across: diff --git a/lib/analytics-export.test.ts b/lib/analytics-export.test.ts new file mode 100644 index 00000000..8e9bd35a --- /dev/null +++ b/lib/analytics-export.test.ts @@ -0,0 +1,203 @@ +// Test file for analytics export functionality without Firebase dependencies +import { USER_SEGMENTS } from './analytics'; + +// Mock the export functions independently to test the core logic +const mockAnalyticsEventStore: Array<{ + eventName: string; + parameters?: Record; + timestamp: Date; + userSegment?: string; + userId?: string; +}> = []; + +const mockExportGroupedAnalytics = ( + dateFrom?: Date, + dateTo?: Date +) => { + const filteredEvents = mockAnalyticsEventStore.filter(event => { + if (dateFrom && event.timestamp < dateFrom) return false; + if (dateTo && event.timestamp > dateTo) return false; + return true; + }); + + const groupedData: Record = {}; + + // Initialize groups for all segments + Object.values(USER_SEGMENTS).forEach(segment => { + groupedData[segment] = { + events: [], + eventCounts: {}, + userCount: 0, + properties: {}, + }; + }); + + // Group events by user segment + const usersBySegment: Record> = {}; + + filteredEvents.forEach(event => { + const segment = event.userSegment || USER_SEGMENTS.GENERAL_USER; + + if (!groupedData[segment]) { + groupedData[segment] = { + events: [], + eventCounts: {}, + userCount: 0, + properties: {}, + }; + } + + groupedData[segment].events.push(event); + groupedData[segment].eventCounts[event.eventName] = + (groupedData[segment].eventCounts[event.eventName] || 0) + 1; + + // Track unique users per segment + if (event.userId) { + if (!usersBySegment[segment]) { + usersBySegment[segment] = new Set(); + } + usersBySegment[segment].add(event.userId); + } + }); + + // Set user counts + Object.keys(usersBySegment).forEach(segment => { + groupedData[segment].userCount = usersBySegment[segment].size; + }); + + return groupedData; +}; + +const mockExportAnalyticsAsCSV = (groupedData?: Record) => { + const data = groupedData || mockExportGroupedAnalytics(); + + const csvRows: string[] = []; + csvRows.push('User Segment,Event Name,Event Count,Total Events,Unique Users'); + + Object.entries(data).forEach(([segment, segmentData]) => { + const totalEvents = segmentData.events.length; + const uniqueUsers = segmentData.userCount; + + Object.entries(segmentData.eventCounts).forEach(([eventName, count]) => { + csvRows.push(`${segment},${eventName},${count},${totalEvents},${uniqueUsers}`); + }); + }); + + return csvRows.join('\n'); +}; + +describe('Analytics Export Core Logic', () => { + beforeEach(() => { + mockAnalyticsEventStore.length = 0; + }); + + test('should export USER_SEGMENTS constants', () => { + expect(USER_SEGMENTS.HEALTHCARE_PROFESSIONAL).toBe('healthcare_professional'); + expect(USER_SEGMENTS.COSMETIC_USER).toBe('cosmetic_user'); + expect(USER_SEGMENTS.PERSONAL_MEDICAL_USER).toBe('personal_medical_user'); + expect(USER_SEGMENTS.GENERAL_USER).toBe('general_user'); + }); + + test('should group analytics events by user segments', () => { + // Add test events + mockAnalyticsEventStore.push({ + eventName: 'scan_attempt', + parameters: { method: 'camera' }, + timestamp: new Date(), + userSegment: USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + userId: 'user_1' + }); + + mockAnalyticsEventStore.push({ + eventName: 'upgrade_success', + parameters: { plan: 'plus' }, + timestamp: new Date(), + userSegment: USER_SEGMENTS.COSMETIC_USER, + userId: 'user_2' + }); + + const groupedData = mockExportGroupedAnalytics(); + + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(1); + expect(groupedData[USER_SEGMENTS.COSMETIC_USER].events).toHaveLength(1); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].eventCounts['scan_attempt']).toBe(1); + expect(groupedData[USER_SEGMENTS.COSMETIC_USER].eventCounts['upgrade_success']).toBe(1); + }); + + test('should count unique users per segment', () => { + // Add multiple events for same user + mockAnalyticsEventStore.push({ + eventName: 'scan_attempt', + timestamp: new Date(), + userSegment: USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + userId: 'user_1' + }); + + mockAnalyticsEventStore.push({ + eventName: 'scan_success', + timestamp: new Date(), + userSegment: USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + userId: 'user_1' + }); + + // Add event for different user + mockAnalyticsEventStore.push({ + eventName: 'scan_attempt', + timestamp: new Date(), + userSegment: USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + userId: 'user_2' + }); + + const groupedData = mockExportGroupedAnalytics(); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].userCount).toBe(2); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(3); + }); + + test('should export as CSV format', () => { + mockAnalyticsEventStore.push({ + eventName: 'scan_attempt', + timestamp: new Date(), + userSegment: USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + userId: 'user_1' + }); + + mockAnalyticsEventStore.push({ + eventName: 'scan_success', + timestamp: new Date(), + userSegment: USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + userId: 'user_1' + }); + + const csvData = mockExportAnalyticsAsCSV(); + + expect(csvData).toContain('User Segment,Event Name,Event Count,Total Events,Unique Users'); + expect(csvData).toContain(`${USER_SEGMENTS.HEALTHCARE_PROFESSIONAL},scan_attempt,1,2,1`); + expect(csvData).toContain(`${USER_SEGMENTS.HEALTHCARE_PROFESSIONAL},scan_success,1,2,1`); + }); + + test('should handle empty data', () => { + const csvData = mockExportAnalyticsAsCSV(); + expect(csvData).toBe('User Segment,Event Name,Event Count,Total Events,Unique Users'); + }); + + test('should filter events by date range', () => { + const pastDate = new Date('2023-01-01'); + const futureDate = new Date('2025-01-01'); + + // This should be included + mockAnalyticsEventStore.push({ + eventName: 'scan_attempt', + timestamp: new Date(), + userSegment: USER_SEGMENTS.GENERAL_USER, + userId: 'user_1' + }); + + const groupedData = mockExportGroupedAnalytics(pastDate, futureDate); + expect(groupedData[USER_SEGMENTS.GENERAL_USER].events).toHaveLength(1); + + // Filter to exclude current events + const veryFutureDate = new Date('2030-01-01'); + const filteredData = mockExportGroupedAnalytics(veryFutureDate); + expect(filteredData[USER_SEGMENTS.GENERAL_USER].events).toHaveLength(0); + }); +}); \ No newline at end of file diff --git a/lib/analytics-simple.test.ts b/lib/analytics-simple.test.ts new file mode 100644 index 00000000..6cae49e4 --- /dev/null +++ b/lib/analytics-simple.test.ts @@ -0,0 +1,119 @@ +// Test file for analytics export functionality - testing only constants +// This avoids Firebase dependencies + +describe('Analytics Export Constants', () => { + test('should have correct user segment values', () => { + const USER_SEGMENTS = { + HEALTHCARE_PROFESSIONAL: 'healthcare_professional', + COSMETIC_USER: 'cosmetic_user', + PERSONAL_MEDICAL_USER: 'personal_medical_user', + GENERAL_USER: 'general_user', + }; + + expect(USER_SEGMENTS.HEALTHCARE_PROFESSIONAL).toBe('healthcare_professional'); + expect(USER_SEGMENTS.COSMETIC_USER).toBe('cosmetic_user'); + expect(USER_SEGMENTS.PERSONAL_MEDICAL_USER).toBe('personal_medical_user'); + expect(USER_SEGMENTS.GENERAL_USER).toBe('general_user'); + }); + + test('should have export functionality logic working', () => { + // Mock analytics data structure + const mockEvents = [ + { + eventName: 'scan_attempt', + parameters: { method: 'camera' }, + timestamp: new Date(), + userSegment: 'healthcare_professional', + userId: 'user_1' + }, + { + eventName: 'scan_success', + parameters: { method: 'camera' }, + timestamp: new Date(), + userSegment: 'healthcare_professional', + userId: 'user_1' + }, + { + eventName: 'upgrade_success', + parameters: { plan: 'plus' }, + timestamp: new Date(), + userSegment: 'cosmetic_user', + userId: 'user_2' + } + ]; + + // Test grouping logic + const groupedData: Record = {}; + const usersBySegment: Record> = {}; + + mockEvents.forEach(event => { + const segment = event.userSegment || 'general_user'; + + if (!groupedData[segment]) { + groupedData[segment] = { + events: [], + eventCounts: {}, + userCount: 0, + }; + } + + groupedData[segment].events.push(event); + groupedData[segment].eventCounts[event.eventName] = + (groupedData[segment].eventCounts[event.eventName] || 0) + 1; + + if (event.userId) { + if (!usersBySegment[segment]) { + usersBySegment[segment] = new Set(); + } + usersBySegment[segment].add(event.userId); + } + }); + + // Set user counts + Object.keys(usersBySegment).forEach(segment => { + groupedData[segment].userCount = usersBySegment[segment].size; + }); + + // Verify grouping worked correctly + expect(groupedData['healthcare_professional'].events).toHaveLength(2); + expect(groupedData['cosmetic_user'].events).toHaveLength(1); + expect(groupedData['healthcare_professional'].eventCounts['scan_attempt']).toBe(1); + expect(groupedData['healthcare_professional'].eventCounts['scan_success']).toBe(1); + expect(groupedData['healthcare_professional'].userCount).toBe(1); + expect(groupedData['cosmetic_user'].userCount).toBe(1); + }); + + test('should export CSV format correctly', () => { + const mockGroupedData = { + 'healthcare_professional': { + events: [{}, {}], // 2 events + eventCounts: { 'scan_attempt': 1, 'scan_success': 1 }, + userCount: 1 + }, + 'cosmetic_user': { + events: [{}], // 1 event + eventCounts: { 'upgrade_success': 1 }, + userCount: 1 + } + }; + + const csvRows: string[] = []; + csvRows.push('User Segment,Event Name,Event Count,Total Events,Unique Users'); + + Object.entries(mockGroupedData).forEach(([segment, segmentData]) => { + const totalEvents = segmentData.events.length; + const uniqueUsers = segmentData.userCount; + + Object.entries(segmentData.eventCounts).forEach(([eventName, count]) => { + csvRows.push(`${segment},${eventName},${count},${totalEvents},${uniqueUsers}`); + }); + }); + + const csvData = csvRows.join('\n'); + + expect(csvData).toContain('User Segment,Event Name,Event Count,Total Events,Unique Users'); + expect(csvData).toContain('healthcare_professional,scan_attempt,1,2,1'); + expect(csvData).toContain('healthcare_professional,scan_success,1,2,1'); + expect(csvData).toContain('cosmetic_user,upgrade_success,1,1,1'); + }); +}); \ No newline at end of file diff --git a/lib/analytics.test.ts b/lib/analytics.test.ts new file mode 100644 index 00000000..0d6d7004 --- /dev/null +++ b/lib/analytics.test.ts @@ -0,0 +1,178 @@ +import { + logAnalyticsEventWithStorage, + exportGroupedAnalytics, + exportAnalyticsAsCSV, + exportAnalyticsSummary, + clearAnalyticsStore, + USER_SEGMENTS, + ANALYTICS_EVENTS +} from '../lib/analytics'; + +describe('Group Analyzer Export Functionality', () => { + beforeEach(() => { + clearAnalyticsStore(); + }); + + describe('logAnalyticsEventWithStorage', () => { + test('should store analytics events for export', () => { + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + { method: 'camera' }, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_1' + ); + + const groupedData = exportGroupedAnalytics(); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(1); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events[0].eventName).toBe(ANALYTICS_EVENTS.SCAN_ATTEMPT); + }); + + test('should handle events without user segment', () => { + logAnalyticsEventWithStorage(ANALYTICS_EVENTS.SIGN_IN_SUCCESS, { method: 'email' }); + + const groupedData = exportGroupedAnalytics(); + expect(groupedData[USER_SEGMENTS.GENERAL_USER].events).toHaveLength(1); + }); + }); + + describe('exportGroupedAnalytics', () => { + test('should group analytics by user segments', () => { + // Add events for different user segments + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + { method: 'camera' }, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_1' + ); + + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.UPGRADE_SUCCESS, + { plan: 'plus' }, + USER_SEGMENTS.COSMETIC_USER, + 'user_2' + ); + + const groupedData = exportGroupedAnalytics(); + + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(1); + expect(groupedData[USER_SEGMENTS.COSMETIC_USER].events).toHaveLength(1); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].eventCounts[ANALYTICS_EVENTS.SCAN_ATTEMPT]).toBe(1); + expect(groupedData[USER_SEGMENTS.COSMETIC_USER].eventCounts[ANALYTICS_EVENTS.UPGRADE_SUCCESS]).toBe(1); + }); + + test('should count unique users per segment', () => { + // Add multiple events for same user + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + {}, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_1' + ); + + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_SUCCESS, + {}, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_1' + ); + + // Add event for different user + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + {}, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_2' + ); + + const groupedData = exportGroupedAnalytics(); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].userCount).toBe(2); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(3); + }); + + test('should filter events by date range', () => { + const pastDate = new Date('2023-01-01'); + const futureDate = new Date('2025-01-01'); + + // This should be included + logAnalyticsEventWithStorage(ANALYTICS_EVENTS.SCAN_ATTEMPT, {}, USER_SEGMENTS.GENERAL_USER, 'user_1'); + + const groupedData = exportGroupedAnalytics(pastDate, futureDate); + expect(groupedData[USER_SEGMENTS.GENERAL_USER].events).toHaveLength(1); + + // Filter to exclude current events + const veryFutureDate = new Date('2030-01-01'); + const filteredData = exportGroupedAnalytics(veryFutureDate); + expect(filteredData[USER_SEGMENTS.GENERAL_USER].events).toHaveLength(0); + }); + }); + + describe('exportAnalyticsAsCSV', () => { + test('should export analytics data as CSV format', () => { + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_ATTEMPT, + {}, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_1' + ); + + logAnalyticsEventWithStorage( + ANALYTICS_EVENTS.SCAN_SUCCESS, + {}, + USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, + 'user_1' + ); + + const csvData = exportAnalyticsAsCSV(); + + expect(csvData).toContain('User Segment,Event Name,Event Count,Total Events,Unique Users'); + expect(csvData).toContain(`${USER_SEGMENTS.HEALTHCARE_PROFESSIONAL},${ANALYTICS_EVENTS.SCAN_ATTEMPT},1,2,1`); + expect(csvData).toContain(`${USER_SEGMENTS.HEALTHCARE_PROFESSIONAL},${ANALYTICS_EVENTS.SCAN_SUCCESS},1,2,1`); + }); + + test('should handle empty data', () => { + const csvData = exportAnalyticsAsCSV(); + expect(csvData).toBe('User Segment,Event Name,Event Count,Total Events,Unique Users'); + }); + }); + + describe('exportAnalyticsSummary', () => { + test('should export analytics summary with top events', () => { + // Add multiple events with different frequencies + logAnalyticsEventWithStorage(ANALYTICS_EVENTS.SCAN_ATTEMPT, {}, USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, 'user_1'); + logAnalyticsEventWithStorage(ANALYTICS_EVENTS.SCAN_ATTEMPT, {}, USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, 'user_2'); + logAnalyticsEventWithStorage(ANALYTICS_EVENTS.SCAN_SUCCESS, {}, USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, 'user_1'); + + const summary = exportAnalyticsSummary(); + + expect(summary.totalSegments).toBe(4); // All user segments are included + expect(summary.segmentSummary[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].totalEvents).toBe(3); + expect(summary.segmentSummary[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].uniqueUsers).toBe(2); + expect(summary.segmentSummary[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].topEvents[0].eventName).toBe(ANALYTICS_EVENTS.SCAN_ATTEMPT); + expect(summary.segmentSummary[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].topEvents[0].count).toBe(2); + }); + + test('should include date range in summary', () => { + const fromDate = new Date('2023-01-01'); + const toDate = new Date('2023-12-31'); + + const summary = exportAnalyticsSummary(fromDate, toDate); + + expect(summary.dateRange.from).toBe(fromDate.toISOString()); + expect(summary.dateRange.to).toBe(toDate.toISOString()); + }); + }); + + describe('clearAnalyticsStore', () => { + test('should clear all stored analytics data', () => { + logAnalyticsEventWithStorage(ANALYTICS_EVENTS.SCAN_ATTEMPT, {}, USER_SEGMENTS.HEALTHCARE_PROFESSIONAL, 'user_1'); + + let groupedData = exportGroupedAnalytics(); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(1); + + clearAnalyticsStore(); + + groupedData = exportGroupedAnalytics(); + expect(groupedData[USER_SEGMENTS.HEALTHCARE_PROFESSIONAL].events).toHaveLength(0); + }); + }); +}); \ No newline at end of file diff --git a/lib/analytics.ts b/lib/analytics.ts index 9989c456..f88e041c 100644 --- a/lib/analytics.ts +++ b/lib/analytics.ts @@ -94,4 +94,171 @@ export const setPersonalizationUserProperties = (profile: any) => { [USER_PROPERTIES.IS_COSMETIC_USE]: profile.isCosmeticUse, [USER_PROPERTIES.USER_SEGMENT]: userSegment, }); +}; + +// User segment definitions for grouping +export const USER_SEGMENTS = { + HEALTHCARE_PROFESSIONAL: 'healthcare_professional', + COSMETIC_USER: 'cosmetic_user', + PERSONAL_MEDICAL_USER: 'personal_medical_user', + GENERAL_USER: 'general_user', +} as const; + +// Group analyzer export functionality +export interface AnalyticsEventData { + eventName: string; + parameters?: Record; + timestamp: Date; + userSegment?: string; + userId?: string; +} + +export interface GroupedAnalyticsData { + [segment: string]: { + events: AnalyticsEventData[]; + eventCounts: Record; + userCount: number; + properties: Record; + }; +} + +// Storage for analytics data (in a real app, this would be persisted) +let analyticsEventStore: AnalyticsEventData[] = []; + +// Enhanced logging function that stores events for export +export const logAnalyticsEventWithStorage = ( + eventName: string, + parameters?: Record, + userSegment?: string, + userId?: string +) => { + // Store event for export functionality + const eventData: AnalyticsEventData = { + eventName, + parameters, + timestamp: new Date(), + userSegment, + userId, + }; + analyticsEventStore.push(eventData); + + // Call the original analytics logging + logAnalyticsEvent(eventName, parameters); +}; + +// Export analytics data grouped by user segments +export const exportGroupedAnalytics = ( + dateFrom?: Date, + dateTo?: Date +): GroupedAnalyticsData => { + const filteredEvents = analyticsEventStore.filter(event => { + if (dateFrom && event.timestamp < dateFrom) return false; + if (dateTo && event.timestamp > dateTo) return false; + return true; + }); + + const groupedData: GroupedAnalyticsData = {}; + + // Initialize groups for all segments + Object.values(USER_SEGMENTS).forEach(segment => { + groupedData[segment] = { + events: [], + eventCounts: {}, + userCount: 0, + properties: {}, + }; + }); + + // Group events by user segment + const usersBySegment: Record> = {}; + + filteredEvents.forEach(event => { + const segment = event.userSegment || USER_SEGMENTS.GENERAL_USER; + + if (!groupedData[segment]) { + groupedData[segment] = { + events: [], + eventCounts: {}, + userCount: 0, + properties: {}, + }; + } + + groupedData[segment].events.push(event); + groupedData[segment].eventCounts[event.eventName] = + (groupedData[segment].eventCounts[event.eventName] || 0) + 1; + + // Track unique users per segment + if (event.userId) { + if (!usersBySegment[segment]) { + usersBySegment[segment] = new Set(); + } + usersBySegment[segment].add(event.userId); + } + }); + + // Set user counts + Object.keys(usersBySegment).forEach(segment => { + groupedData[segment].userCount = usersBySegment[segment].size; + }); + + return groupedData; +}; + +// Export analytics data as CSV format +export const exportAnalyticsAsCSV = ( + groupedData?: GroupedAnalyticsData, + dateFrom?: Date, + dateTo?: Date +): string => { + const data = groupedData || exportGroupedAnalytics(dateFrom, dateTo); + + const csvRows: string[] = []; + csvRows.push('User Segment,Event Name,Event Count,Total Events,Unique Users'); + + Object.entries(data).forEach(([segment, segmentData]) => { + const totalEvents = segmentData.events.length; + const uniqueUsers = segmentData.userCount; + + Object.entries(segmentData.eventCounts).forEach(([eventName, count]) => { + csvRows.push(`${segment},${eventName},${count},${totalEvents},${uniqueUsers}`); + }); + }); + + return csvRows.join('\n'); +}; + +// Export analytics summary by segment +export const exportAnalyticsSummary = ( + dateFrom?: Date, + dateTo?: Date +): Record => { + const groupedData = exportGroupedAnalytics(dateFrom, dateTo); + + const summary: Record = { + dateRange: { + from: dateFrom?.toISOString() || 'beginning', + to: dateTo?.toISOString() || 'now', + }, + totalSegments: Object.keys(groupedData).length, + segmentSummary: {}, + }; + + Object.entries(groupedData).forEach(([segment, data]) => { + summary.segmentSummary[segment] = { + totalEvents: data.events.length, + uniqueUsers: data.userCount, + topEvents: Object.entries(data.eventCounts) + .sort(([,a], [,b]) => b - a) + .slice(0, 5) + .map(([eventName, count]) => ({ eventName, count })), + }; + }); + + return summary; +}; + +// Clear stored analytics data (for testing or maintenance) +export const clearAnalyticsStore = (): void => { + analyticsEventStore = []; }; \ No newline at end of file