Cara Balas WA Otomatis dengan Analytics

Tutorial track performa auto reply WhatsApp. Response time, conversion, engagement metrics. Data-driven optimization!

Cara Balas WA Otomatis dengan Analytics
Cara Balas WA Otomatis dengan Analytics

Analytics = Know what works!

Tanpa data, kamu tidak tahu apakah auto reply bekerja dengan baik. Analytics membantu measure, analyze, dan improve.


Key Metrics

📊 METRICS PENTING:

VOLUME:
- Total messages (in/out)
- Unique conversations
- New vs returning customers

RESPONSE:
- First response time
- Average response time
- Response rate

ENGAGEMENT:
- Reply rate
- Conversation length
- Drop-off points

CONVERSION:
- Chat to lead rate
- Lead to customer rate
- Revenue from chat

SATISFACTION:
- CSAT score
- Resolution rate
- Escalation rate

Event Tracking

Track Everything:

javascript

// Analytics event types
const EventTypes = {
    MESSAGE_RECEIVED: 'message_received',
    MESSAGE_SENT: 'message_sent',
    CONVERSATION_STARTED: 'conversation_started',
    CONVERSATION_RESOLVED: 'conversation_resolved',
    BOT_RESPONSE: 'bot_response',
    HUMAN_TAKEOVER: 'human_takeover',
    KEYWORD_TRIGGERED: 'keyword_triggered',
    MENU_SELECTED: 'menu_selected',
    LINK_CLICKED: 'link_clicked',
    CONVERSION: 'conversion'
};

// Track event
async function trackEvent(eventType, data) {
    const event = {
        id: generateId(),
        type: eventType,
        timestamp: new Date(),
        ...data
    };
    
    await db.analyticsEvents.insertOne(event);
    
    // Real-time dashboard update
    io.to('analytics').emit('new-event', event);
}

// Usage examples
await trackEvent(EventTypes.MESSAGE_RECEIVED, {
    conversationId: conv.id,
    customerId: from,
    messageType: 'text',
    messageLength: message.length
});

await trackEvent(EventTypes.BOT_RESPONSE, {
    conversationId: conv.id,
    triggeredBy: 'keyword',
    keyword: 'harga',
    responseTime: 150 // ms
});

await trackEvent(EventTypes.CONVERSION, {
    conversationId: conv.id,
    customerId: from,
    type: 'order',
    value: 500000
});

Response Time Tracking

Calculate Response Time:

javascript

async function trackResponseTime(conversationId, direction) {
    if (direction === 'incoming') {
        // Customer mengirim pesan, catat waktu
        await db.responseTracking.updateOne(
            { conversationId },
            { 
                $set: { 
                    lastIncomingAt: new Date(),
                    awaitingResponse: true
                }
            },
            { upsert: true }
        );
    }
    
    if (direction === 'outgoing') {
        // Bot/admin membalas
        const tracking = await db.responseTracking.findOne({ conversationId });
        
        if (tracking?.awaitingResponse) {
            const responseTime = Date.now() - tracking.lastIncomingAt.getTime();
            
            await trackEvent('response_time', {
                conversationId,
                responseTimeMs: responseTime,
                responder: 'bot' // atau 'human'
            });
            
            await db.responseTracking.updateOne(
                { conversationId },
                { $set: { awaitingResponse: false } }
            );
        }
    }
}

// Calculate averages
async function getAverageResponseTime(startDate, endDate) {
    const result = await db.analyticsEvents.aggregate([
        {
            $match: {
                type: 'response_time',
                timestamp: { $gte: startDate, $lte: endDate }
            }
        },
        {
            $group: {
                _id: '$responder',
                avgResponseTime: { $avg: '$responseTimeMs' },
                minResponseTime: { $min: '$responseTimeMs' },
                maxResponseTime: { $max: '$responseTimeMs' },
                count: { $sum: 1 }
            }
        }
    ]).toArray();
    
    return result;
}

Conversation Analytics

Conversation Flow Analysis:

javascript

async function analyzeConversationFlows(startDate, endDate) {
    // Get all conversations
    const conversations = await db.conversations.find({
        createdAt: { $gte: startDate, $lte: endDate }
    }).toArray();
    
    const analysis = {
        totalConversations: conversations.length,
        avgMessagesPerConversation: 0,
        avgDuration: 0,
        resolutionRate: 0,
        botOnlyResolved: 0,
        humanInvolved: 0,
        dropOffPoints: {}
    };
    
    let totalMessages = 0;
    let totalDuration = 0;
    let resolved = 0;
    
    for (const conv of conversations) {
        const messages = await db.messages.find({ 
            conversationId: conv.id 
        }).toArray();
        
        totalMessages += messages.length;
        
        if (conv.status === 'resolved') {
            resolved++;
            totalDuration += conv.resolvedAt - conv.createdAt;
            
            // Check if bot-only or human involved
            const humanMessages = messages.filter(m => 
                m.direction === 'outgoing' && m.senderId !== 'bot'
            );
            
            if (humanMessages.length === 0) {
                analysis.botOnlyResolved++;
            } else {
                analysis.humanInvolved++;
            }
        }
        
        // Analyze drop-off
        const lastMessage = messages[messages.length - 1];
        if (conv.status !== 'resolved' && lastMessage.direction === 'outgoing') {
            // Customer didn't reply - analyze last bot message
            const dropPoint = extractDropPoint(lastMessage.content);
            analysis.dropOffPoints[dropPoint] = (analysis.dropOffPoints[dropPoint] || 0) + 1;
        }
    }
    
    analysis.avgMessagesPerConversation = totalMessages / conversations.length;
    analysis.avgDuration = totalDuration / resolved; // in ms
    analysis.resolutionRate = (resolved / conversations.length * 100).toFixed(1);
    
    return analysis;
}

Keyword Performance

javascript

async function getKeywordPerformance(startDate, endDate) {
    const result = await db.analyticsEvents.aggregate([
        {
            $match: {
                type: 'keyword_triggered',
                timestamp: { $gte: startDate, $lte: endDate }
            }
        },
        {
            $group: {
                _id: '$keyword',
                triggerCount: { $sum: 1 },
                uniqueUsers: { $addToSet: '$customerId' }
            }
        },
        {
            $project: {
                keyword: '$_id',
                triggerCount: 1,
                uniqueUsers: { $size: '$uniqueUsers' }
            }
        },
        {
            $sort: { triggerCount: -1 }
        },
        {
            $limit: 20
        }
    ]).toArray();
    
    return result;
}

// Track keyword that didn't match
async function trackUnmatchedKeywords(message) {
    await db.unmatchedKeywords.insertOne({
        message: message.substring(0, 100),
        timestamp: new Date()
    });
}

// Analyze unmatched to find new keywords to add
async function getTopUnmatchedKeywords(limit = 20) {
    // Simple word frequency analysis
    const unmatched = await db.unmatchedKeywords.find({
        timestamp: { $gte: daysAgo(30) }
    }).toArray();
    
    const wordFreq = {};
    
    for (const item of unmatched) {
        const words = item.message.toLowerCase().split(/\s+/);
        for (const word of words) {
            if (word.length > 3) { // Ignore short words
                wordFreq[word] = (wordFreq[word] || 0) + 1;
            }
        }
    }
    
    return Object.entries(wordFreq)
        .sort((a, b) => b[1] - a[1])
        .slice(0, limit)
        .map(([word, count]) => ({ word, count }));
}

Conversion Tracking

javascript

// Track funnel
const funnelStages = [
    'conversation_started',
    'product_viewed',
    'added_to_cart',
    'checkout_started',
    'payment_initiated',
    'order_completed'
];

async function trackFunnelEvent(conversationId, stage, metadata = {}) {
    await db.funnelEvents.insertOne({
        conversationId,
        stage,
        stageIndex: funnelStages.indexOf(stage),
        timestamp: new Date(),
        ...metadata
    });
}

async function getFunnelAnalytics(startDate, endDate) {
    const funnelData = {};
    
    for (const stage of funnelStages) {
        const count = await db.funnelEvents.countDocuments({
            stage,
            timestamp: { $gte: startDate, $lte: endDate }
        });
        
        funnelData[stage] = count;
    }
    
    // Calculate conversion rates between stages
    const conversionRates = {};
    
    for (let i = 1; i < funnelStages.length; i++) {
        const prevStage = funnelStages[i - 1];
        const currStage = funnelStages[i];
        
        if (funnelData[prevStage] > 0) {
            conversionRates[`${prevStage}_to_${currStage}`] = 
                (funnelData[currStage] / funnelData[prevStage] * 100).toFixed(1);
        }
    }
    
    return { funnelData, conversionRates };
}

Dashboard Reports

Daily Summary:

javascript

async function generateDailyReport(date) {
    const startOfDay = new Date(date);
    startOfDay.setHours(0, 0, 0, 0);
    
    const endOfDay = new Date(date);
    endOfDay.setHours(23, 59, 59, 999);
    
    const report = {
        date: date.toISOString().split('T')[0],
        messages: {
            received: await countEvents('message_received', startOfDay, endOfDay),
            sent: await countEvents('message_sent', startOfDay, endOfDay)
        },
        conversations: {
            started: await countEvents('conversation_started', startOfDay, endOfDay),
            resolved: await countEvents('conversation_resolved', startOfDay, endOfDay)
        },
        responseTime: await getAverageResponseTime(startOfDay, endOfDay),
        topKeywords: await getKeywordPerformance(startOfDay, endOfDay),
        conversions: await getConversions(startOfDay, endOfDay),
        peakHours: await getPeakHours(startOfDay, endOfDay)
    };
    
    return report;
}

// Get peak activity hours
async function getPeakHours(startDate, endDate) {
    const result = await db.analyticsEvents.aggregate([
        {
            $match: {
                type: 'message_received',
                timestamp: { $gte: startDate, $lte: endDate }
            }
        },
        {
            $group: {
                _id: { $hour: '$timestamp' },
                count: { $sum: 1 }
            }
        },
        {
            $sort: { count: -1 }
        }
    ]).toArray();
    
    return result.map(r => ({
        hour: r._id,
        messageCount: r.count
    }));
}

Weekly Report:

javascript

async function generateWeeklyReport(weekStartDate) {
    const days = [];
    
    for (let i = 0; i < 7; i++) {
        const date = new Date(weekStartDate);
        date.setDate(date.getDate() + i);
        
        days.push(await generateDailyReport(date));
    }
    
    // Calculate week totals and trends
    const weekTotal = {
        messages: days.reduce((sum, d) => sum + d.messages.received + d.messages.sent, 0),
        conversations: days.reduce((sum, d) => sum + d.conversations.started, 0),
        conversions: days.reduce((sum, d) => sum + d.conversions.count, 0),
        revenue: days.reduce((sum, d) => sum + d.conversions.value, 0)
    };
    
    // Compare to previous week
    const prevWeekStart = new Date(weekStartDate);
    prevWeekStart.setDate(prevWeekStart.getDate() - 7);
    
    const prevWeek = await generateWeeklySummary(prevWeekStart);
    
    const trends = {
        messages: calculateGrowth(weekTotal.messages, prevWeek.messages),
        conversations: calculateGrowth(weekTotal.conversations, prevWeek.conversations),
        conversions: calculateGrowth(weekTotal.conversions, prevWeek.conversions),
        revenue: calculateGrowth(weekTotal.revenue, prevWeek.revenue)
    };
    
    return {
        weekStart: weekStartDate,
        days,
        totals: weekTotal,
        trends,
        insights: generateInsights(days, trends)
    };
}

Visualization

Chart Data Formats:

javascript

// Line chart: Messages over time
async function getMessagesChartData(days = 30) {
    const data = [];
    
    for (let i = days - 1; i >= 0; i--) {
        const date = new Date();
        date.setDate(date.getDate() - i);
        
        const dayStart = new Date(date);
        dayStart.setHours(0, 0, 0, 0);
        
        const dayEnd = new Date(date);
        dayEnd.setHours(23, 59, 59, 999);
        
        const received = await countEvents('message_received', dayStart, dayEnd);
        const sent = await countEvents('message_sent', dayStart, dayEnd);
        
        data.push({
            date: date.toISOString().split('T')[0],
            received,
            sent
        });
    }
    
    return data;
}

// Pie chart: Message sources
async function getSourceDistribution(startDate, endDate) {
    return await db.analyticsEvents.aggregate([
        {
            $match: {
                type: 'conversation_started',
                timestamp: { $gte: startDate, $lte: endDate }
            }
        },
        {
            $group: {
                _id: '$source',
                count: { $sum: 1 }
            }
        }
    ]).toArray();
}

Automated Insights

javascript

function generateInsights(data, trends) {
    const insights = [];
    
    // Response time insight
    if (data.avgResponseTime > 5 * 60 * 1000) { // > 5 menit
        insights.push({
            type: 'warning',
            message: 'Response time rata-rata > 5 menit. Pertimbangkan tambah auto-reply.'
        });
    }
    
    // Traffic trend
    if (trends.messages > 20) {
        insights.push({
            type: 'positive',
            message: `Traffic naik ${trends.messages}% dari minggu lalu!`
        });
    }
    
    // Conversion insight
    if (trends.conversions < -10) {
        insights.push({
            type: 'warning',
            message: `Conversion turun ${Math.abs(trends.conversions)}%. Review bot flow.`
        });
    }
    
    // Peak hour insight
    const peakHour = data.peakHours[0];
    insights.push({
        type: 'info',
        message: `Peak traffic jam ${peakHour.hour}:00. Pastikan coverage maksimal.`
    });
    
    return insights;
}

Best Practices

DO ✅

- Track everything
- Set baseline metrics
- Review daily/weekly
- Act on insights
- A/B test changes
- Share with team

DON'T ❌

- Track tanpa action
- Ignore trends
- Review jarang
- Data hoarding
- Skip testing
- Keep data siloed

FAQ

Metric mana yang paling penting?

Response time dan conversion rate. Langsung impact customer experience dan revenue.

Perlu tools khusus?

Bisa custom atau pakai tools seperti Metabase, Grafana untuk visualization.


Kesimpulan

Analytics = Continuous improvement!

No AnalyticsWith Analytics
GuessingData-driven
No improvementContinuous improve
Blind spotsFull visibility
ReactiveProactive

Setup Analytics Dashboard →


Artikel Terkait