Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions docs/analytics-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<string, any>,
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<string, any>
```

### 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:
Expand Down
203 changes: 203 additions & 0 deletions lib/analytics-export.test.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>;
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<string, any> = {};

// 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<string, Set<string>> = {};

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<string, any>) => {
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);
});
});
Loading