Conversion Funnel Metrics System

Introduction
The Wrkbelt Conversion Funnel Metrics System provides comprehensive analytics for understanding customer journey optimization in service booking flows. Unlike simple funnel visualizations that only show aggregate conversion rates, our system delivers actionable insights through three integrated metric categories: Executive Metrics (high-level performance indicators), Step Conversion Rates (detailed funnel progression analysis), and Step Dropoff Analysis (dimensional breakdown of abandonment patterns).
The system transforms raw booking session data into precise business intelligence by analyzing user step progression, engagement patterns, and dimensional performance breakdowns. Each metric includes period-over-period trend analysis with statistical validation to ensure meaningful insights surface only when there's sufficient data for reliable conclusions.
Architecture Overview
Statistical Foundation
Our conversion funnel metrics system builds on the same statistical rigor as our insights system:
- Data Threshold Validation: Minimum 10 sessions per period for reliable trend calculations
- Period-over-Period Comparison: Equivalent timeframe comparison for trend analysis
- Engagement Time Outlier Filtering: Percentile-based filtering (2.5th-97.5th for n>100, IQR for smaller datasets)
- ObjectId Normalization: Consistent string conversion for step ID matching across MongoDB operations
Multi-Layered Architecture
Core Components
1. ConversionFunnelService
The primary orchestrator for all conversion funnel analytics, implementing business logic for step analysis and trend calculations:
@Injectable()
export class ConversionFunnelService extends BaseAnalyticsService {
/**
* Business Logic Principles:
* - Repository layer: Pure data access, no business logic
* - Service layer: All business logic, statistical analysis, and trend calculations
* - Step completion rules: Applied consistently across all metrics
* - Engagement time: Calculated from session timestamps with outlier filtering
*/
async getExecutiveMetrics(
organizationId: string,
queryDto: ConversionFunnelExecutiveMetricsQueryDto
): Promise<ConversionFunnelExecutiveMetricsResultDto>
async getStepConversionRates(
organizationId: string,
queryDto: StepConversionRatesQueryDto
): Promise<StepConversionRatesResultDto>
async getStepDropoffTable(
organizationId: string,
queryDto: StepDropoffTableQueryDto
): Promise<StepDropoffTableResultDto>
}
2. ConversionFunnelRepository
Extends BaseAnalyticsRepository and integrates with CommonAnalyticsRepository for consistent session counting:
@Injectable()
export class ConversionFunnelRepository extends BaseAnalyticsRepository {
/**
* Data Access Principles:
* - No business logic - pure data retrieval
* - Uses centralized unique tab session pipeline for consistency
* - Ensures session counts match executive metrics
* - Prioritizes sessions with booking_id within each tab_session_id group
* - Returns raw data for service layer processing
* - Handles ObjectId/string conversions at MongoDB level
*/
constructor(
@InjectConnection() connection: Connection,
protected override readonly marketingSourcePipelineService: MarketingSourcePipelineService,
private readonly commonAnalyticsRepository: CommonAnalyticsRepository
)
async getSessionAndStepRawData(
organizationId: string,
dateRange: DateRange,
filters?: AnalyticsFilterDto
): Promise<{ bookingSessions: BookingSessionRawData[]; steps: StepDefinition[]; }>
async getEngagementTimeRawData(
organizationId: string,
dateRange: DateRange,
filters?: AnalyticsFilterDto
): Promise<number[]>
}
Session Counting Consistency
Centralized Unique Tab Session Logic
To ensure consistent session counts between executive metrics and conversion funnel data, both systems use a centralized unique session pipeline:
/**
* CommonAnalyticsRepository.createUniqueTabSessionsPipeline()
*
* Purpose: Ensures consistent session counting across all analytics modules
*
* Logic:
* 1. Groups sessions by tab_session_id to ensure uniqueness
* 2. Within each group, prioritizes sessions with booking_id
* 3. Falls back to most recent session if no booking_id exists
* 4. Returns one representative session per unique tab session
*/
// Pipeline stages:
[
...standardFilterPipeline,
{
$sort: {
tab_session_id: 1,
booking_id: -1, // Sessions with booking_id first
createdAt: -1 // Then by most recent
}
},
{
$group: {
_id: '$tab_session_id',
session: { $first: '$$ROOT' } // Take prioritized session
}
},
{
$replaceRoot: {
newRoot: '$session'
}
}
]
Integration Points:
- Executive Metrics: Uses
CommonAnalyticsService.calculateTotalVisitsMetric() - Conversion Funnel: Uses
CommonAnalyticsRepository.createUniqueTabSessionsPipeline()inConversionFunnelRepository - Result: Identical session counts across both analytics views
Executive Metrics Analysis
1. Total Visits
Source: Reuses CommonAnalyticsService.calculateTotalVisitsMetric()
Calculation: Count of unique booking sessions within date range and filters using centralized unique session logic
Trend Analysis: Period-over-period comparison with data threshold validation
2. Booking Rate
Source: Reuses CommonAnalyticsService.calculateBookingRateMetric()
Calculation: (Completed Bookings / Total Visits) × 100
Business Logic:
- Leverages already-computed total visits and completed bookings data
- Avoids redundant database calls through data reuse
- Handles zero-baseline scenarios with appropriate messaging
3. Average Engagement Time
Purpose: Measures how long users spend in the booking flow before completing or abandoning
Calculation Method:
// Repository: Calculate engagement time from session timestamps
engagement_time_ms = {
$subtract: [
{
$ifNull: [
'$timestamps.booking_session_completed',
'$timestamps.booking_session_abandoned'
]
},
'$createdAt'
]
}
// Service: Apply outlier filtering and convert to seconds
const filteredTimes = this.applyOutlierFiltering(rawEngagementTimes);
const avgEngagementTimeSeconds = filteredTimes.reduce((sum, time) => sum + time, 0) / filteredTimes.length / 1000;
Key Assumptions:
- Engagement Start: Session
createdAttimestamp - Engagement End: Either
booking_session_completedORbooking_session_abandoned(one will exist, one won't) - Data Quality: Only sessions with valid end timestamps included
- Outlier Handling: Percentile-based filtering for statistical reliability
Outlier Filtering Strategy:
- Large datasets (n > 100): Remove top/bottom 2.5% (2.5th-97.5th percentile)
- Small datasets (n ≤ 100): IQR-based filtering (Q1 - 1.5×IQR to Q3 + 1.5×IQR)
- Rationale: Eliminates sessions left open for hours/days that skew average calculations
4. Step with Biggest Dropoff
Calculation: Analyzes all steps in booking flow to identify highest dropoff rate Business Logic:
- Processes step conversion rates for all steps
- Identifies step with maximum
(sessions_reached - sessions_completed) / sessions_reached × 100 - Includes trend analysis comparing current vs. previous period performance
Step Conversion Rates Analysis
Core Business Logic
Step conversion analysis follows specific business rules for determining completion:
/**
* Step Completion Determination Rules:
* 1. Sessions Reached: step_id appears in session_step_state.step_path
* 2. Sessions Completed:
* - If step is LAST in all_steps_in_flow → booking_id must exist
* - If step is NOT last → must have subsequent steps in step_path
* 3. Dropoff Rate: (reached - completed) / reached × 100
*/
private isStepCompleted(
session: BookingSessionRawData,
stepId: string,
stepPositionMap: Map<string, number>
): boolean {
const stepPosition = stepPositionMap.get(stepId);
const allStepsInFlow = session.session_step_state.all_steps_in_flow;
const stepPath = session.session_step_state.step_path;
// If step is the last step in flow → check booking_id exists
if (stepPosition === allStepsInFlow.length - 1) {
return Boolean(session.booking_id);
}
// If step is not last → check if any subsequent steps are in step_path
const subsequentSteps = allStepsInFlow.slice(stepPosition + 1);
return subsequentSteps.some(subsequentStepId =>
stepPath.includes(subsequentStepId)
);
}
Step Position Mapping
Purpose: Determines step order within booking flow for completion analysis
Calculation: Uses all_steps_in_flow array to find step positions
Methodology: Median position across sessions for robustness against outliers
private buildStepPositionMap(
bookingSessions: BookingSessionRawData[],
steps: StepDefinition[]
): Map<string, number> {
// For each step, collect all positions across sessions
// Use median position for statistical robustness
// Handles cases where steps appear at different positions
}
Data Processing Flow
- Raw Data Retrieval: Get booking sessions and step definitions
- Step Position Mapping: Build position map from
all_steps_in_flow - Session Analysis: For each step, calculate reached and completed sessions
- Metric Calculation: Compute dropoff rates and average positions
- Sorting: Order steps by average position for proper funnel visualization
Key Assumptions
- User Journey Tracking:
step_pathcontains actual user progression - Flow Definition:
all_steps_in_flowdefines complete booking flow structure - Completion Criteria: Different rules for last step (booking required) vs. intermediate steps (subsequent progression)
- ObjectId Handling: Consistent string conversion for step ID matching
Step Dropoff Analysis
Dimensional Breakdown Methodology
The step dropoff analysis extends basic conversion rates with dimensional analysis to identify patterns:
/**
* Dimensional Analysis Components:
* 1. Worst Source: Traffic source with highest dropoff rate for this step
* 2. Worst Device: Device type with highest dropoff rate for this step
* 3. Worst Time: Time period with highest dropoff rate for this step
* 4. Period Trend: Current vs. previous period dropoff rate comparison
*/
private calculateWorstPerformersForStep(
bookingSessions: BookingSessionRawData[],
stepId: string
): {
source: { name: string; rate: number };
device: { name: string; rate: number };
time: { from_datetime: string; to_datetime: string; rate: number };
}
Dimensional Grouping Strategy
Traffic Source Dimension:
- Field:
attributed_sourcefrom session data - Grouping: Sessions grouped by source value
- Dropoff Calculation: Within each source group, calculate completion rate for the step
Device Type Dimension:
- Field:
initialization_context.user_agent.device_type - Grouping: Sessions grouped by device type (mobile, desktop, tablet)
- Dropoff Calculation: Device-specific completion rates
Time Dimension:
- Field:
time_blockfrom session data - Grouping: Sessions grouped by hour blocks (e.g., "09:00-09:59")
- Output Format: Converted to datetime ranges for UI consumption
Trend Analysis Implementation
Period-over-Period Comparison:
// Calculate trend using centralized method
const metricComparison = this.calculateMetricWithComparison(
currentDropoffRate,
previousDropoffRate,
currentSessionsReached,
previousSessionsReached
);
// Result includes:
// - trend: UP/DOWN/STABLE based on statistical significance
// - delta_percentage: Magnitude of change
// - Automatic "Not enough data" handling for insufficient sample sizes
Data Threshold Validation:
- Minimum Sample Size: 10 sessions per period for reliable trend calculation
- Graceful Degradation: Shows "Not enough data" when thresholds not met
- Statistical Reliability: Prevents misleading trends from small sample sizes
Business Context Integration
The analysis provides actionable context for each step:
interface StepDropoffTableRow {
step_id: string;
step_name: string;
step_type: BookingFlowStepType;
dropoff_count: number; // Absolute number of dropoffs
dropoff_rate: number; // Percentage dropoff rate
worst_source: { name: string; rate: number; };
worst_device: { name: string; rate: number; };
worst_time: { from_datetime: string; to_datetime: string; rate: number; };
vs_last_period_trend: {
trend: Trend;
rate: number;
};
}
Data Quality and Validation
ObjectId Normalization
Challenge: MongoDB ObjectIds vs. string comparisons in business logic Solution: Consistent conversion to strings before analysis
convertObjectIdArrayToStringArray(objectIds: (ObjectId | string)[]): string[] {
return objectIds.map((objectId) => objectId.toString());
}
// Applied to:
// - session_step_state.step_path (user journey tracking)
// - session_step_state.all_steps_in_flow (flow definition)
// - Step ID comparisons throughout analysis
Engagement Time Data Quality
Filtering Strategy:
- Valid Sessions Only: Must have either completion or abandonment timestamp
- Non-negative Values: Engagement time ≥ 0 (filters invalid timestamp calculations)
- Outlier Removal: Statistical filtering to remove extreme values that skew averages
MongoDB Aggregation:
// Calculate engagement time in repository
{
$addFields: {
engagement_time_ms: {
$subtract: [
{
$ifNull: [
'$timestamps.booking_session_completed',
'$timestamps.booking_session_abandoned'
]
},
'$createdAt'
]
}
}
}
// Filter valid calculations
{
$match: {
engagement_time_ms: { $exists: true, $gte: 0 }
}
}
Step Data Consistency
Step Definition Matching:
- Extract unique step IDs from all sessions'
all_steps_in_flow - Fetch step definitions only for steps actually used in sessions
- Handles cases where flow definitions change over time
Position Calculation Robustness:
- Uses median position across sessions for each step
- Handles steps that may appear at different positions in different flows
- Provides stable position mapping for completion analysis
Performance Optimizations
Parallel Execution Strategy
Executive Metrics:
// Parallel calculation of independent metrics
const [totalVisits, completedBookings, avgEngagementTime] = await Promise.all([
this.commonAnalyticsService.calculateTotalVisitsMetric(...),
this.commonAnalyticsService.calculateCompletedBookingsMetric(...),
this.calculateAvgEngagementTimeMetric(...)
]);
// Compose dependent metrics from already-computed data
const bookingRate = this.commonAnalyticsService.calculateBookingRateMetric({
totalVisits,
completedBookings
});
Step Analysis:
// Parallel current and previous period analysis
const [currentStepMetrics, previousStepMetrics] = await Promise.all([
this.processStepConversionRates(organizationId, dateRange, filters),
this.processStepConversionRates(organizationId, comparisonDateRange, filters)
]);
Data Reuse Strategy
Repository Efficiency:
- Single
getSessionAndStepRawData()call provides data for multiple analyses - Step conversion rates and dropoff analysis share same base dataset
- Avoids redundant database queries through intelligent data sharing
Service Layer Optimization:
- Executive metrics reuse
CommonAnalyticsServicecalculations - Booking rate leverages already-computed visits and bookings data
- Step position mapping calculated once and reused across analyses
Caching Implementation
Analytics Query Caching:
@CachedAnalyticsQuery({ ttlSeconds: 600 }) // 10 minutes
async getExecutiveMetrics(organizationId: string, queryDto: ConversionFunnelExecutiveMetricsQueryDto)
@CachedAnalyticsQuery({ ttlSeconds: 1200 }) // 20 minutes
async getStepConversionRates(organizationId: string, queryDto: StepConversionRatesQueryDto)
@CachedAnalyticsQuery({ ttlSeconds: 1200 }) // 20 minutes
async getStepDropoffTable(organizationId: string, queryDto: StepDropoffTableQueryDto)
Cache Strategy Rationale:
- Executive metrics: Higher refresh rate for dashboard overview
- Detailed analyses: Longer cache for complex calculations
- Redis integration: Automatic cache key generation and graceful fallback
Business Benefits
1. Comprehensive Funnel Intelligence
Traditional Approach: Simple conversion rate percentages
- Limited insight into where and why users drop off
- No dimensional context for optimization decisions
- Basic before/after comparisons without statistical validation
Our Approach: Multi-dimensional funnel analysis
- Precise identification of problematic steps and user segments
- Statistical trend validation prevents false positive alerts
- Actionable context for targeted optimization efforts
2. Engagement Time Insights
Traditional Approach: Time on page or session duration
- Often inflated by users leaving browser tabs open
- No filtering for unrealistic engagement times
- Difficult to benchmark against meaningful standards
Our Approach: Statistically filtered engagement analysis
- Outlier removal provides realistic engagement time averages
- Period-over-period trending reveals engagement pattern changes
- Booking flow specific timing for optimization insights
3. Dimensional Troubleshooting
Traditional Approach: "Step X has a 40% dropoff rate"
- No context about which users or conditions contribute to problems
- Optimization efforts lack direction and focus
- Difficult to prioritize improvement initiatives
Our Approach: "Step X has 40% dropoff, worst on mobile devices during evening hours from social media traffic"
- Specific user segments and conditions identified for targeted fixes
- Clear prioritization framework based on data volume and impact
- Contextual insights enable precise optimization strategies
Implementation Examples
Complete Executive Metrics Flow
async getExecutiveMetrics(
organizationId: string,
queryDto: ConversionFunnelExecutiveMetricsQueryDto
): Promise<ConversionFunnelExecutiveMetricsResultDto> {
// 1. Validate inputs and derive date ranges
this.validateOrganizationId(organizationId, 'conversion funnel executive metrics');
const dateRange = queryDto.filters.date_range;
const comparisonDateRange = this.derivePrecedingDateRange(dateRange);
// 2. Parallel calculation of independent metrics
const [totalVisits, completedBookings, avgEngagementTime] = await Promise.all([
this.commonAnalyticsService.calculateTotalVisitsMetric(organizationId, dateRange, comparisonDateRange, queryDto.filters),
this.commonAnalyticsService.calculateCompletedBookingsMetric(organizationId, dateRange, comparisonDateRange, queryDto.filters),
this.calculateAvgEngagementTimeMetric(organizationId, dateRange, comparisonDateRange, queryDto.filters)
]);
// 3. Compose dependent metrics from already-computed data
const bookingRate = this.commonAnalyticsService.calculateBookingRateMetric({ totalVisits, completedBookings });
// 4. Calculate step-specific metrics
const stepWithBiggestDropoff = await this.calculateStepWithBiggestDropoff(organizationId, dateRange, comparisonDateRange, queryDto.filters);
return {
total_visits: totalVisits,
booking_rate: bookingRate,
avg_engagement_time_in_seconds: avgEngagementTime,
step_with_biggest_dropoff: stepWithBiggestDropoff
};
}
Step Conversion Analysis Flow
private async processStepConversionRates(
organizationId: string,
dateRange: DateRange,
filters?: AnalyticsFilterDto
): Promise<StepConversionRatesData[]> {
// 1. Get raw data from repository (pure data access)
const { bookingSessions, steps } = await this.conversionFunnelRepository.getSessionAndStepRawData(organizationId, dateRange, filters);
// 2. Build step position map for completion logic
const stepPositionMap = this.buildStepPositionMap(bookingSessions, steps);
// 3. Process each step with business logic
const stepMetrics: StepConversionRatesData[] = steps.map((step) => {
const stepId = step._id;
// Calculate sessions reached: sessions where step is in step_path
const sessionsReached = bookingSessions.filter((session) =>
this.convertObjectIdArrayToStringArray(session.session_step_state.step_path).includes(stepId)
);
// Calculate sessions completed using business logic
const sessionsCompleted = sessionsReached.filter((session) =>
this.isStepCompleted(session, stepId, stepPositionMap)
);
// Calculate dropoff rate
const dropoffRate = sessionsReached.length > 0
? ((sessionsReached.length - sessionsCompleted.length) / sessionsReached.length) * 100
: 0;
return {
step_id: stepId,
step_name: step.step_name,
step_type: step.step_type,
sessions_reached: sessionsReached.length,
sessions_completed: sessionsCompleted.length,
dropoff_rate: dropoffRate,
avg_step_position: stepPositionMap.get(stepId) || 0,
};
});
// 4. Sort by step position for proper funnel order
return stepMetrics.sort((a, b) => a.avg_step_position - b.avg_step_position);
}
Future Enhancements
1. Advanced Completion Analytics
Planned Features:
- Conversion Path Analysis: Track most common successful user journeys
- Abandonment Pattern Detection: Identify recurring dropout sequences
- Session Recovery Tracking: Analyze users who return to complete bookings
2. Enhanced Dimensional Analysis
Planned Features:
- Geographic Breakdown: Regional performance analysis for location-based services
- Temporal Patterns: Day-of-week and seasonal trend analysis
- Marketing Attribution: Channel-specific funnel performance tracking
3. Predictive Analytics Integration
Planned Features:
- Dropoff Risk Scoring: Real-time user likelihood to abandon analysis
- Completion Time Prediction: Expected booking flow duration estimates
- Optimization Recommendations: AI-driven improvement suggestions
4. A/B Testing Integration
Planned Features:
- Flow Variant Analysis: Compare different booking flow configurations
- Step Order Optimization: Test different step sequences for conversion impact
- Statistical Significance Testing: Automated experiment result validation
Conclusion
The Wrkbelt Conversion Funnel Metrics System provides service businesses with unprecedented visibility into their booking flow performance. By combining precise step-by-step analysis with dimensional breakdowns and statistical trend validation, the system transforms raw user behavior data into actionable optimization intelligence.
The architecture ensures scalability and maintainability while the business logic accurately reflects real user journey patterns. Through careful attention to data quality, statistical rigor, and performance optimization, the system delivers reliable insights that businesses can trust for strategic decision-making.
As booking flows become more sophisticated and user expectations evolve, this system provides the analytical foundation necessary to continuously optimize conversion performance and enhance customer experience.
References
Statistical Methods
- Central Limit Theorem: Sample size validation for trend reliability
- Percentile-based Outlier Detection: Engagement time filtering methodology
- Period-over-Period Analysis: Trend calculation with data adequacy validation
Implementation Resources
- NestJS Documentation - Framework architecture
- MongoDB Aggregation Framework - Data processing pipelines
- TypeScript Documentation - Type system implementation
Business Intelligence Research
- Funnel Analysis Best Practices: Multi-dimensional conversion optimization
- User Journey Analytics: Session-based behavior tracking methodologies
- Service Industry Benchmarks: Booking flow optimization standards