Skip to main content

Scheduler Models

1. Introduction and Background

1.1 Wrkbelt Platform Overview

Wrkbelt is a comprehensive suite of digital tools designed to empower service businesses with operational efficiency. The platform addresses the challenges of managing customer interactions, scheduling, and service delivery through intuitive digital interfaces and seamless integrations with industry-standard services like ServiceTitan.

1.2 The Scheduler Tool

The Wrkbelt Scheduler is a core component that enables service businesses to create customizable booking experiences for their customers. It provides a step-based approach to gathering information, scheduling appointments, and capturing customer details through an intuitive, conversation-like interface.

Each booking flow consists of a sequence of steps that may include:

  • Zip code validation (to confirm service area)
  • Service selection (to determine what service the customer needs)
  • Detail collection (to gather context about the service request)
  • Calendar selection (to schedule the appointment)
  • Customer information collection (to identify new or existing customers)
  • Summary and confirmation (to review and finalize the booking)

These steps guide customers through a structured process that culminates in a confirmed service appointment, which is then synchronized with the business's ServiceTitan account.

1.3 Evolution of the Scheduler

The Scheduler has evolved to address the need for more natural, conversational booking experiences. Rather than treating service selection and information gathering as strictly sequential steps, our refined model offers a graph-based approach that combines these elements into intuitive decision trees. This allows businesses to create dynamic flows that adapt based on customer inputs while ensuring every path identifies exactly one service type to be booked.

2. Scheduler Data Model

2.1 Architecture Overview

The Scheduler data model is built on a modular architecture with distinct but interconnected entities:

  1. Configuration Layer - Defines how the scheduler appears and behaves in different contexts
  2. Flow Structure Layer - Specifies the sequence and behavior of booking flow steps
  3. Content Layer - Provides the reusable content elements like questions, answers, and services
  4. Session Layer - Tracks and records customer interactions with booking flows using an event-sourcing approach

This layered approach creates a flexible system that can be customized to meet the needs of different service businesses while maintaining consistent core functionality.

2.2 Core Concepts

2.2.1 Dynamic Flow Routing

The Scheduler implements a sophisticated URL-based routing system using the BookingFlowRouter entity. This enables organizations to dynamically direct users to different booking flows based on various parameters:

  • URL Path Matching: Route users based on specific URL paths (e.g., /services/hvac vs /services/plumbing)
  • Query Parameter Matching: Route based on query string parameters (e.g., ?location=downtown)
  • Campaign Tracking: Route based on UTM parameters for campaign-specific experiences
  • Priority-Based Resolution: Resolve conflicts using a strict priority hierarchy

The routing system follows a rules engine architecture that encapsulates matching logic and provides validation tools to prevent routing conflicts. This allows organizations to implement sophisticated traffic management strategies without code changes.

2.2.2 Graph-Based Structure for Service Selection

The Service Selection step is modeled as a directed acyclic graph (DAG) with the following key elements:

  • Questions: Standalone entities that present information requests to users
  • Answers: Possible responses to questions that guide the flow direction
  • Services: Specific service offerings that map to ServiceTitan job types
  • Answer Categories: Grouping mechanisms for organizing related answers or services

The graph always begins with a single root question and branches based on user selections, with every possible path including exactly one service selection.

2.2.2 Path Requirements

For a valid Service Selection graph:

  1. There must be exactly one root question node as the starting point
  2. Every possible path through the graph must include exactly one service type node
  3. A service type can appear anywhere in the path (not just at the end)
  4. Paths may continue with additional questions after service selection
  5. Terminal nodes (those with no outgoing connections) conceptually connect to the next configured step in the booking flow

2.2.3 Composable Configuration

The Scheduler's design embraces composability through:

  • SchedulerConfig: Links organization, booking flow, and branding into a unified experience
  • ContentConfig Pattern: Provides consistent structure for text and visual elements
  • BrandKit: Defines reusable visual styling that can be applied across scheduler instances

2.2.4 Event-Sourcing Architecture for Session Tracking

The enhanced session tracking system follows an event-sourcing approach with:

  • Events as Source of Truth: All user interactions captured as immutable events
  • State Derivation: Current session state derived from the event stream
  • Discriminated Unions: Type-safe access to status-specific fields based on session status
  • Enhanced Step Tracking: Detailed tracking of step progression and drop-off points

3. Scheduler Entity Relationship Diagram

4. Entity Descriptions

4.1 Configuration Entities

4.1.1 SchedulerConfig

The SchedulerConfig entity is the central configuration component that ties together initialization settings for our scheduler. This entity is the main entry point for fetching a booking when the bundled scheduler initially loads. The resulting response will include the SchedulerConfig entity enriched with the appropriate BookingFlow (given the routing context) as well as the respective Organization's BrandKit.

Key fields:

  • target_click_elements: Array of selectors for elements that trigger the scheduler
  • fallback_phone_number_screen: Optional configuration for when online booking is unavailable
    • phone_number: Alternative contact method for customers
    • content: Instructions and messaging for the fallback screen

This entity empowers organizations to create multiple scheduler configurations for different contexts (e.g., website booking, mobile app, embedded widget) while reusing the same underlying booking flows and service definitions.

4.1.2 BrandKit

The BrandKit entity defines the visual styling for a scheduler instance. It enables consistent branding across an organization's digital presence.

Key fields:

  • logo_file_id: References the organization's logo image
  • colors: Defines the color scheme for the scheduler interface
    • primary: Main brand color used for key UI elements
    • secondary: Supporting color for additional UI components
    • accent: Highlight color for important actions and elements

4.2 Booking Flow Entities

4.2.1 BookingFlow

The BookingFlow entity represents a complete booking experience consisting of multiple steps in a defined sequence.

Key fields:

  • content: Contains the flow's title and other display information
  • steps: Array of references to BookingFlowStep entities in sequence order
  • status: Enumerates the flow's publication state (draft, published, deleted)

A booking flow is the central element of the scheduler that orchestrates the customer journey from initial interaction to completed booking.

4.2.2 BookingFlowStep

The BookingFlowStep entity defines an individual step within a booking flow. Each step has a specific type that determines its behavior and configuration.

Key fields:

  • step_type: Identifies the step's purpose and behavior (e.g., zipcode, service_selection, details)
  • step_config: Contains display information like title and description
  • type_config: Type-specific configuration based on the step_type value

Step types include:

  • Zipcode: Validates if a customer is within the service area
  • Service Selection: Implements the graph-based decision tree for service selection
  • Details: Collects additional information about the service request
  • Timeslot Selection: Allows scheduling of the service appointment
  • Customer: Captures customer contact information
  • Summary: Reviews all collected information before confirmation

For service selection steps, the type_config includes:

  • root_node_id: Identifies the starting question in the graph
  • nodes: Array of question, answer, and service nodes
  • edges: Array of connections between nodes that define possible paths

4.3 Content Entities

4.3.1 BookingService

The BookingService entity defines a specific service offering that can be booked by customers. It includes both customer-facing content and vendor integration details.

Key fields:

  • service_content: Display information like title, description, and icon
  • vendor_data: Integration information for connecting to external systems
    • vendor_type: Identifies the integration provider (e.g., servicetitan)
    • job_type_id: External identifier for the service in the vendor system
    • job_type_name: Human-readable name from the vendor system
    • vendor_sync_status: Status of synchronization with the vendor system

This entity bridges the gap between customer-facing service offerings and the underlying service management systems used by the business.

4.3.2 BookingQuestion

The BookingQuestion entity defines a reusable question that can be presented to customers during the booking process.

Key fields:

  • question_content: Display information including title, unique name, and help text
  • answers: Array of possible answers to the question

Questions are fundamental building blocks in the service selection graph, representing decision points in the customer journey.

4.3.3 BookingQuestionAnswer

The BookingQuestionAnswer entity represents a possible response to a booking question. Answers can either reference a service or provide plain text content.

Key fields:

  • type: Distinguishes between service references and plain text answers
  • category_id: Optional reference to a category for grouping related answers
  • service_id: Reference to a service (required for service_reference type)
  • content: Display information for plain text answers

The dual nature of answers (service or plain text) enables the graph to both guide service selection and gather contextual information in a unified flow.

4.3.4 BookingAnswerCategory

The BookingAnswerCategory entity provides a way to group related answers for better organization and presentation.

Key fields:

  • title: Display name for the category
  • description: Detailed explanation of the category's purpose
  • icon: Visual identifier for the category

Categories help create logical groupings of answers, improving the user experience by organizing options in meaningful ways.

4.4 Session Tracking Entities

4.4.1 BookingSession

The BookingSession entity records a customer's interaction with a booking flow using an event-sourcing approach combined with state management to provide comprehensive journey tracking. The model is structured to provide both operational access to current state and analytical insights through outcome tracking.

Key structural components:

  1. Core Identification and References

    • organization_id: References the parent organization
    • booking_flow_id: References the booking flow being used
    • booking_session_event_ids: Chronological array of references to event entities
  2. Context and Analytics Data

    • session_context: Marketing and user context data (UTM parameters, device info, etc.)
    • step_progression: Tracking statistics for step completion and furthest progress
    • metrics: Session metrics like duration and navigation patterns
  3. Current State Management

    • current_state: Container for active session operational data
    • current_state_step_id: Current active step
    • current_state_form_data: Current form values
    • current_state_navigation_state: Navigation status information
    • current_state_service_selection_path: Path taken through service selection
    • current_state_last_event_id: Reference to the most recent event
    • current_state_temp_selections: Temporary selections (service, timeslot, etc.)
    • current_state_last_activity_at: Timestamp of most recent activity
  4. Outcome Tracking

    • booking_outcome: Container for session outcome data
    • booking_outcome_status: Session status (active, completed, abandoned)
    • Status-specific fields depending on outcome:
      • For completed sessions: booking_outcome_completed_at, booking_outcome_booking_id, booking_outcome_selected_service_id, booking_outcome_selected_timeslot
      • For abandoned sessions: booking_outcome_abandoned_at, booking_outcome_drop_off_step_id

This structured approach provides clear separation of concerns between operational state and outcome tracking, while enabling comprehensive analysis of user journeys through the booking process.

4.4.2 BookingSessionEvent

The BookingSessionEvent entity implements an event-sourcing pattern to capture all interactions within a booking session as immutable events. These events collectively form the complete history of a booking session and can be used to reconstruct its state at any point in time.

Key fields:

  • booking_session_id: References the parent booking session
  • event_type: Identifies the nature of the interaction
  • timestamp: When the event occurred
  • step_id: The step context where the event took place
  • data: Event-specific data structured as a discriminated union based on event_type

Event types include:

  • SESSION_STARTED: Initial creation of the booking session
  • STEP_ENTERED: User navigated to a specific step
  • STEP_COMPLETED: User successfully completed a step
  • FIELD_UPDATED: Form field value was changed
  • VALIDATION_ERROR: Form validation failed
  • QUESTION_ANSWERED: Response provided to a service selection question
  • SERVICE_SELECTED: Service was chosen by the user
  • NAVIGATION_BACK: User navigated backwards in the flow
  • TIMESLOT_SELECTED: Appointment time was selected
  • SESSION_ABANDONED: User prematurely ended the session
  • SESSION_COMPLETED: Booking was successfully finalized

This event-based approach provides several benefits:

  • Complete auditability of the user journey
  • Ability to reconstruct session state at any point
  • Detailed insights into user behavior and drop-off points
  • Support for session recovery in case of disconnection
  • Rich analytics capabilities for optimization

5. Data Modeling Patterns

5.1 ContentConfig Pattern

The Scheduler data model uses a consistent ContentConfig pattern for display-related information across multiple entities:

type ContentConfig = {
title: string;
description?: string;
icon?: LucideIconKey;
};

This pattern appears in various forms throughout the model:

  • service_content in BookingService
  • question_content in BookingQuestion
  • step_config in BookingFlowStep
  • content in fallback phone number screen

Using this consistent pattern simplifies the implementation of UI components and ensures uniform presentation across the application.

5.2 Discriminated Union Types

The model employs discriminated unions to handle entities with multiple possible structures based on a type field. For the BookingSession entity, this is implemented through the booking_outcome_status field:

// For BookingSession based on booking_outcome_status
export enum BookingSessionStatus {
ACTIVE = 'active',
COMPLETED = 'completed',
ABANDONED = 'abandoned'
}

// Base fields for all session types
export interface BookingSessionBase {
_id: string;
organization_id: string;
booking_flow_id: string;
booking_session_event_ids: string[];
session_context: SessionContext;
step_progression: StepProgression;
metrics: SessionMetrics;
current_state: CurrentState;
booking_outcome: {
status: BookingSessionStatus;
// Additional fields based on status
};
// ...other common fields
}

// Active session type
export interface ActiveBookingSession extends BookingSessionBase {
booking_outcome: {
status: BookingSessionStatus.ACTIVE;
};
}

// Completed session type with additional fields
export interface CompletedBookingSession extends BookingSessionBase {
booking_outcome: {
status: BookingSessionStatus.COMPLETED;
completed_at: Date;
booking_id: string;
selected_service_id: string;
selected_timeslot: {
start_time: Date;
end_time: Date;
};
};
}

// Abandoned session type with abandonment-specific fields
export interface AbandonedBookingSession extends BookingSessionBase {
booking_outcome: {
status: BookingSessionStatus.ABANDONED;
abandoned_at: Date;
drop_off_step_id: string;
// Optional - these might have been selected before abandonment
selected_service_id?: string;
selected_timeslot?: {
start_time: Date;
end_time: Date;
};
};
}

// Union type
export type BookingSession =
| ActiveBookingSession
| CompletedBookingSession
| AbandonedBookingSession;

Similarly, for BookingSessionEvent data:

export enum BookingSessionEventType {
SESSION_STARTED = 'session_started',
STEP_ENTERED = 'step_entered',
// ...other event types
}

// Base event structure
export interface BookingSessionEvent {
_id: string;
booking_session_id: string;
organization_id: string;
event_type: BookingSessionEventType;
timestamp: Date;
step_id: string;
data: BookingSessionEventData;
}

// Discriminated union for event data
export type BookingSessionEventData =
| StepNavigationEventData
| FieldUpdateEventData
| QuestionAnswerEventData
// ...other event data types

This pattern enables type-safe handling of different entity variants while maintaining a consistent base structure, providing both flexibility and compile-time type checking.

5.3 Type Guards for Safe Access

The model includes type guard functions to provide type-safe access to status-specific fields:

export function isActiveSession(session: BookingSession): session is ActiveBookingSession {
return session.booking_outcome.status === BookingSessionStatus.ACTIVE;
}

export function isCompletedSession(session: BookingSession): session is CompletedBookingSession {
return session.booking_outcome.status === BookingSessionStatus.COMPLETED;
}

export function isAbandonedSession(session: BookingSession): session is AbandonedBookingSession {
return session.booking_outcome.status === BookingSessionStatus.ABANDONED;
}

These type guards enable safe access to status-specific fields with proper TypeScript type narrowing:

function processSession(session: BookingSession) {
if (isCompletedSession(session)) {
// TypeScript knows this is a CompletedBookingSession
console.log(`Booking completed at ${session.booking_outcome.completed_at} with ID ${session.booking_outcome.booking_id}`);
} else if (isAbandonedSession(session)) {
// TypeScript knows this is an AbandonedBookingSession
console.log(`Booking abandoned at ${session.booking_outcome.abandoned_at} during step ${session.booking_outcome.drop_off_step_id}`);
} else {
// TypeScript knows this is an ActiveBookingSession
console.log(`Booking active with current step ${session.current_state.step_id}`);
}
}

5.4 Array-Based Event References

The BookingSession entity uses an array of ObjectId references (booking_session_event_ids) to track all related events in chronological order:

interface BookingSessionBase {
// ...other fields
booking_session_event_ids: string[]; // Array of BookingSessionEvent _id values
}

This approach provides several benefits:

  • Efficient retrieval of chronological events
  • Direct access to specific events by ID
  • Ability to track event sequence without complex queries
  • Avoids embedding a potentially large array of events directly in the session document

5.5 Step-Specific Configuration

The BookingFlowStep entity continues to use type-specific configuration based on the step_type:

export type BookingFlowServiceSelectionStep = BookingFlowStepBase & {
step_type: StepType.SERVICE_SELECTION;
type_config: ServiceSelectionTypeStepConfig;
};

export type BookingFlowZipcodeStep = BookingFlowStepBase & {
step_type: StepType.ZIPCODE;
type_config: ZipcodeTypeStepConfig;
};

This approach enables each step type to have its own specialized configuration while sharing common base fields, creating a flexible but structured modeling pattern.

6. Service Selection Implementation

6.1 Graph Structure

The service selection step employs a graph structure defined by nodes and edges:

Node Types

type ServiceSelectionNode = {
_id: string;
type: 'question_reference' | 'answer_reference' | 'service_reference';

// For question references
question_id?: string;

// For answer nodes
answer_content?: {
text: string;
description?: string;
};
answer_display?: {
icon?: string;
style?: string;
};
answer_category_id?: string;

// For service references
service_id?: string;
};

Edge Definition

type ServiceSelectionEdge = {
_id: string;
source_node_id: string;
target_node_id: string;
};

6.2 Path Resolution

When a customer interacts with a service selection step, the system:

  1. Starts from the root question node
  2. Presents the question with its possible answers
  3. When an answer is selected:
    • If the answer is a service reference, records the service selection
    • If the answer leads to another question, continues the flow
  4. Tracks the full path taken through the graph via BookingSessionEvents
  5. Ensures exactly one service is selected before proceeding to the next step

This approach creates an adaptive, conversation-like experience while maintaining structured data capture for business operations.

7. Event-Sourcing Architecture

7.1 Core Principles

The enhanced session tracking system follows an event-sourcing architecture with these core principles:

  1. Events as Source of Truth: All user interactions are captured as immutable events in the BookingSessionEvent collection
  2. State Derivation: Current session state is derived from the event stream
  3. Array-Based Event References: Sessions maintain an ordered array of event references for efficient retrieval
  4. Separation of Concerns: Operational data (current state) is separated from historical/analytical data (event history)
  5. Enhanced Step Tracking: Detailed tracking of step progression and drop-off points
  6. Status-Based Type Discrimination: Proper use of discriminated unions based on session status

7.2 Event Processing Flow

The session tracking system processes events in the following sequence:

  1. Client application emits events representing user interactions
  2. Session service processes the event
    • Stores the event in the BookingSessionEvent collection
    • Updates the BookingSession document with a reference to the event
    • Updates the current state snapshot based on the event
  3. For status transitions (completing or abandoning a session):
    • Creates the appropriate status transition event
    • Transforms the session to the appropriate status type with status-specific fields
    • Ensures all temporary selections are properly stored in the appropriate fields

7.3 Temporary Selection Management

During an active session, selections that haven't been committed to an outcome are tracked in the current_state_temp_selections field:

interface ActiveBookingSession extends BookingSessionBase {
booking_outcome: {
status: BookingSessionStatus.ACTIVE;
};
current_state: {
// ...other fields
temp_selections?: {
service_id?: string;
timeslot?: {
start_time: Date;
end_time: Date;
}
}
};
}

When a session transitions to completed or abandoned status, these temporary selections are moved to the appropriate outcome-specific fields and the temporary storage is removed:

// When transitioning from active to completed
function completeSession(session: ActiveBookingSession, bookingId: string): CompletedBookingSession {
return {
...session,
booking_outcome: {
status: BookingSessionStatus.COMPLETED,
completed_at: new Date(),
booking_id: bookingId,
selected_service_id: session.current_state.temp_selections?.service_id || '',
selected_timeslot: session.current_state.temp_selections?.timeslot || {
start_time: new Date(),
end_time: new Date()
}
},
current_state: {
...session.current_state,
// Remove temp_selections
temp_selections: undefined
}
};
}

7.4 Event Reference Management

To prevent issues with MongoDB's document size limits, the booking_session_event_ids array is managed in one of two ways:

  1. Rolling Window: Keeping only the most recent N events while maintaining the first event for context
  2. Significant Events: Storing only significant events that are essential for reconstructing the session

This approach ensures that session documents remain within MongoDB's size limits while preserving the most important events for analysis and state reconstruction.

8. Analytics Capabilities

8.1 Step-Based Abandonment Analysis

The enhanced session tracking model enables detailed analysis of where users abandon the booking process:

// Get step-specific abandonment rates
async function getStepAbandonmentRates(organizationId, dateRange) {
return await bookingSessionRepository.aggregate([
// Match abandoned sessions for the given organization and date range
{ $match: {
organization_id: organizationId,
created_at: { $gte: dateRange.start, $lte: dateRange.end },
'booking_outcome.status': BookingSessionStatus.ABANDONED
}},

// Group by drop-off step
{ $group: {
_id: '$booking_outcome.drop_off_step_id',
count: { $sum: 1 }
}},

// Lookup step details
{ $lookup: {
from: 'booking_flow_steps',
localField: '_id',
foreignField: '_id',
as: 'step_info'
}},

// Format results
{ $project: {
step_id: '$_id',
step_name: '$step_info.step_config.title',
step_type: '$step_info.step_type',
abandonment_count: '$count'
}}
]);
}

8.2 Progression Analysis

The step_progression field enables analysis of how far users progress through booking flows:

// Get completion metrics by steps completed
async function getProgressionMetrics(organizationId, dateRange) {
return await bookingSessionRepository.aggregate([
// Match sessions for the given organization and date range
{ $match: {
organization_id: organizationId,
created_at: { $gte: dateRange.start, $lte: dateRange.end }
}},

// Group by status and steps completed
{ $group: {
_id: {
status: '$booking_outcome.status',
steps_completed: '$step_progression.completed_count',
total_steps: '$step_progression.total_in_flow'
},
count: { $sum: 1 }
}},

// Calculate completion percentage
{ $project: {
status: '$_id.status',
steps_completed: '$_id.steps_completed',
total_steps: '$_id.total_steps',
completion_percentage: {
$multiply: [
{ $divide: ['$_id.steps_completed', '$_id.total_steps'] },
100
]
},
count: 1
}}
]);
}

8.3 Event Analysis

The event-sourcing architecture enables detailed analysis of user interactions:

// Get event frequency analysis
async function getEventFrequencyAnalysis(bookingSessionId) {
// Get the session to access event IDs
const session = await bookingSessionRepository.findById(bookingSessionId);

// Get all events for this session
const events = await bookingSessionEventRepository.find({
_id: { $in: session.booking_session_event_ids }
}, { sort: { timestamp: 1 } });

// Analyze event frequency
const eventCounts = {};
events.forEach(event => {
eventCounts[event.event_type] = (eventCounts[event.event_type] || 0) + 1;
});

// Calculate time between events
const eventTimings = [];
for (let i = 1; i < events.length; i++) {
const timeDiff = (events[i].timestamp - events[i-1].timestamp) / 1000; // in seconds
eventTimings.push({
event_type: events[i].event_type,
time_since_previous: timeDiff
});
}

return {
event_counts: eventCounts,
event_timings: eventTimings,
total_events: events.length
};
}

9. Performance Optimization

9.1 Database Indexing

Critical indexes for the enhanced data model:

// BookingSession collection indexes
db.booking_sessions.createIndex({ organization_id: 1, 'booking_outcome.status': 1, created_at: -1 });
db.booking_sessions.createIndex({ 'booking_outcome.booking_id': 1 }, { sparse: true });
db.booking_sessions.createIndex({ 'booking_outcome.status': 1, 'booking_outcome.drop_off_step_id': 1 });

// BookingSessionEvent collection indexes
db.booking_session_events.createIndex({ booking_session_id: 1, timestamp: 1 });
db.booking_session_events.createIndex({ organization_id: 1, event_type: 1, timestamp: 1 });

9.2 Array Size Management

To prevent the booking_session_event_ids array from causing document size issues, we implement strategies such as:

  1. Monitoring array size distribution across sessions
  2. Implementing a rolling window to keep only the most recent events
  3. Selectively storing only significant events
  4. Applying TTL index to automatically clean up old events

9.3 Batched Updates

For high-traffic scenarios, batch processing is implemented to reduce database write operations:

// Event batch processor for high-volume scenarios
class EventBatchProcessor {
private eventQueue: {event: any, sessionId: string}[] = [];

queueEvent(event: any, sessionId: string) {
this.eventQueue.push({ event, sessionId });

// Process immediately if queue gets large
if (this.eventQueue.length >= 100) {
this.processBatch();
}
}

async processBatch() {
if (this.eventQueue.length === 0) return;

const batch = [...this.eventQueue];
this.eventQueue = [];

try {
// Group by session ID to minimize session document updates
const sessionGroups = new Map<string, {events: any[], sessionId: string}>();

batch.forEach(({ event, sessionId }) => {
if (!sessionGroups.has(sessionId)) {
sessionGroups.set(sessionId, { events: [], sessionId });
}
sessionGroups.get(sessionId)!.events.push(event);
});

// Process each session group
for (const [_, { events, sessionId }] of sessionGroups.entries()) {
// Insert all events in bulk
const savedEvents = await this.eventRepository.insertMany(events);

// Update session with event IDs in a single operation
const eventIds = savedEvents.map(e => e._id);
await this.sessionRepository.updateOne(
{ _id: sessionId },
{
$push: {
booking_session_event_ids: { $each: eventIds }
},
$set: {
'current_state.last_activity_at': new Date()
}
}
);
}
} catch (error) {
console.error('Error processing event batch:', error);
// Add events back to queue
this.eventQueue = [...batch, ...this.eventQueue];
}
}
}

10. Conclusion

The Wrkbelt Scheduler data model represents a sophisticated approach to online service booking that balances flexibility with structure. The event-sourcing architecture for session tracking provides comprehensive insights into user journeys while maintaining scalable performance.

Key strengths of the model include:

  1. Comprehensive Journey Tracking: Complete visibility into user paths through event sourcing
  2. Structured State Management: Clear separation between current state and outcome data
  3. Type-Safe State Transitions: Discriminated unions ensure proper state handling and type safety
  4. Detailed Abandonment Analysis: Precise understanding of where and why users drop off
  5. Performance Optimization: Efficient data structures and processing patterns for scale
  6. Rich Analytics Capabilities: Detailed insights for conversion optimization
  7. Alignment with Existing Patterns: Follows established conventions in the codebase

This robust data model forms the foundation of a scheduler tool that bridges the gap between intuitive customer experiences and robust backend business operations, with particular emphasis on understanding and optimizing the user journey through detailed session tracking.