Real-Time Sentiment Analysis with Azure Event Grid and OpenAI – Part 4: Dashboard, Alerting, and User Experience

Real-Time Sentiment Analysis with Azure Event Grid and OpenAI – Part 4: Dashboard, Alerting, and User Experience

Welcome to Part 4 of our real-time sentiment analysis series! In Part 1, we built the architecture foundation, Part 2 implemented Azure OpenAI integration, and Part 3 created high-performance stream processing. Now, let’s focus on transforming our real-time sentiment insights into engaging, actionable user experiences through live dashboards, automated workflows, and seamless integrations.

This part explores how to build responsive, real-time interfaces that bring sentiment analysis data to life for different stakeholders across your organization.

Real-Time Dashboard Architecture

Modern sentiment analysis dashboards must provide instant insights across multiple dimensions while maintaining excellent user experience even with high-velocity data streams.

graph TB
    A[Real-Time Sentiment Stream] --> B[SignalR Hub]
    B --> C[Dashboard Router]
    
    C --> D[Executive Dashboard
High-level KPIs] C --> E[Operations Dashboard
Real-time alerts] C --> F[Customer Service Dashboard
Individual interactions] C --> G[Product Dashboard
Product-specific insights] subgraph "Executive View" D --> H[Sentiment Trends
NPS Score
Customer Satisfaction] end subgraph "Operations View" E --> I[Critical Alerts
Escalation Queue
Response Times] end subgraph "Customer Service View" F --> J[Customer Timeline
Sentiment History
Suggested Actions] end subgraph "Product View" G --> K[Product Ratings
Feature Feedback
Quality Issues] end style B fill:#4ECDC4 style C fill:#FFB6C1 style E fill:#FF6B6B

SignalR Hub Implementation

SignalR provides the foundation for real-time dashboard updates. Let’s implement a scalable SignalR architecture:

public class SentimentAnalyticsHub : Hub
{
    private readonly ISentimentSubscriptionService _subscriptionService;
    private readonly IUserAuthService _authService;
    private readonly ILogger _logger;
    
    public async Task JoinDashboardGroup(string dashboardType, DashboardFilters filters)
    {
        try
        {
            var user = await _authService.GetCurrentUserAsync(Context.User);
            if (!await _authService.CanAccessDashboard(user, dashboardType))
            {
                throw new HubException("Unauthorized access to dashboard");
            }
            
            var groupName = GenerateGroupName(dashboardType, filters, user.Id);
            await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
            
            await _subscriptionService.SubscribeToStreams(groupName, filters);
            
            var initialData = await GetInitialDashboardData(dashboardType, filters);
            await Clients.Caller.SendAsync("InitialDataLoaded", initialData);
            
            _logger.LogInformation("User {UserId} joined dashboard group {GroupName}", 
                user.Id, groupName);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to join dashboard group");
            await Clients.Caller.SendAsync("Error", "Failed to join dashboard");
        }
    }
    
    public async Task RequestCustomerDetails(string customerId)
    {
        var user = await _authService.GetCurrentUserAsync(Context.User);
        
        if (!await _authService.CanViewCustomerData(user, customerId))
        {
            await Clients.Caller.SendAsync("Error", "Unauthorized customer access");
            return;
        }
        
        var customerDetails = await GetRealTimeCustomerSentiment(customerId);
        await Clients.Caller.SendAsync("CustomerDetailsLoaded", customerDetails);
    }
}

Interactive Dashboard Components

Build responsive, interactive dashboard components that provide actionable insights:

class RealTimeSentimentChart {
    constructor(containerId, options = {}) {
        this.container = document.getElementById(containerId);
        this.options = {
            maxDataPoints: 100,
            updateInterval: 1000,
            chartType: 'line',
            ...options
        };
        
        this.chartData = {
            labels: [],
            datasets: [{
                label: 'Sentiment Score',
                data: [],
                borderColor: 'rgb(75, 192, 192)',
                backgroundColor: 'rgba(75, 192, 192, 0.2)',
                tension: 0.4
            }]
        };
        
        this.initializeChart();
        this.setupSignalRConnection();
    }
    
    addDataPoint(timestamp, sentimentScore) {
        const time = new Date(timestamp);
        
        this.chartData.labels.push(time);
        this.chartData.datasets[0].data.push({
            x: time,
            y: sentimentScore
        });
        
        if (this.chartData.labels.length > this.options.maxDataPoints) {
            this.chartData.labels.shift();
            this.chartData.datasets[0].data.shift();
        }
        
        this.chart.update('active');
        this.updateChartStyling(sentimentScore);
    }
    
    updateChartStyling(latestScore) {
        const recentData = this.chartData.datasets[0].data.slice(-10);
        const averageRecent = recentData.reduce((sum, point) => sum + point.y, 0) / recentData.length;
        
        let borderColor, backgroundColor;
        if (averageRecent > 0.3) {
            borderColor = 'rgb(34, 197, 94)';
            backgroundColor = 'rgba(34, 197, 94, 0.2)';
        } else if (averageRecent < -0.3) {
            borderColor = 'rgb(239, 68, 68)';
            backgroundColor = 'rgba(239, 68, 68, 0.2)';
        } else {
            borderColor = 'rgb(156, 163, 175)';
            backgroundColor = 'rgba(156, 163, 175, 0.2)';
        }
        
        this.chartData.datasets[0].borderColor = borderColor;
        this.chartData.datasets[0].backgroundColor = backgroundColor;
    }
}

Automated Alerting System

Transform sentiment insights into automated actions that improve customer experience:

graph TB
    A[Sentiment Analysis Result] --> B[Alert Rule Engine]
    B --> C{Alert Conditions}
    
    C -->|High-Value Customer
Negative Sentiment| D[VIP Escalation] C -->|Product Quality Issue
Multiple Complaints| E[Product Team Alert] C -->|Service Outage
Sentiment Spike| F[Operations Alert] C -->|Competitive Mention
Positive Competitor| G[Marketing Alert] D --> H[Customer Success Manager
Immediate Outreach] E --> I[Product Manager
Quality Investigation] F --> J[Operations Team
Incident Response] G --> K[Marketing Team
Competitive Analysis] style D fill:#FF6B6B style E fill:#FFA500 style F fill:#FF4444 style G fill:#4ECDC4
public class IntelligentAlertSystem
{
    private readonly IAlertRuleEngine _ruleEngine;
    private readonly IWorkflowOrchestrator _workflowOrchestrator;
    private readonly INotificationService _notificationService;
    
    public async Task ProcessSentimentForAlerts(EnrichedSentimentEvent sentimentEvent)
    {
        var triggeredRules = await _ruleEngine.EvaluateRules(sentimentEvent);
        
        foreach (var rule in triggeredRules)
        {
            var alert = CreateAlert(rule, sentimentEvent);
            
            await SendImmediateNotifications(alert);
            await _workflowOrchestrator.TriggerWorkflow(alert.WorkflowId, alert);
            await LogAlert(alert);
        }
    }
    
    private async Task SendImmediateNotifications(SentimentAlert alert)
    {
        var notificationTasks = new List();
        
        switch (alert.Severity)
        {
            case AlertSeverity.Critical:
                notificationTasks.Add(_notificationService.SendSMSAlert(alert));
                notificationTasks.Add(_notificationService.SendEmailAlert(alert));
                notificationTasks.Add(_notificationService.SendTeamsAlert(alert));
                break;
                
            case AlertSeverity.High:
                notificationTasks.Add(_notificationService.SendEmailAlert(alert));
                notificationTasks.Add(_notificationService.SendTeamsAlert(alert));
                break;
                
            case AlertSeverity.Medium:
                notificationTasks.Add(_notificationService.SendEmailAlert(alert));
                break;
                
            case AlertSeverity.Low:
                notificationTasks.Add(_notificationService.SendDashboardAlert(alert));
                break;
        }
        
        await Task.WhenAll(notificationTasks);
    }
}

Customer Service Integration

Integrate sentiment insights directly into customer service workflows for contextual, emotion-aware support:

public class CustomerServiceSentimentIntegration
{
    private readonly ICustomerServicePlatform _servicePlatform;
    private readonly ISentimentHistoryService _sentimentHistory;
    
    public async Task EnrichCustomerInteraction(string customerId, string interactionId)
    {
        var sentimentProfile = await _sentimentHistory.GetCustomerSentimentProfile(customerId);
        var recentInteractions = await _sentimentHistory.GetRecentInteractions(customerId, TimeSpan.FromDays(30));
        
        var customerContext = new SentimentAwareCustomerContext
        {
            CustomerId = customerId,
            CurrentSentimentScore = sentimentProfile.CurrentSentimentScore,
            SentimentTrend = sentimentProfile.SentimentTrend,
            ChurnRisk = CalculateChurnRisk(sentimentProfile),
            EscalationProbability = CalculateEscalationProbability(recentInteractions),
            RecommendedTone = GetRecommendedTone(sentimentProfile),
            SuggestedApproach = GetSuggestedApproach(sentimentProfile),
            KeySensitivities = IdentifyKeySensitivities(recentInteractions)
        };
        
        await _servicePlatform.UpdateCustomerContext(interactionId, customerContext);
        await ProvideAgentGuidance(interactionId, customerContext);
    }
    
    private async Task ProvideAgentGuidance(string interactionId, SentimentAwareCustomerContext context)
    {
        var guidance = new AgentGuidance
        {
            CustomerMood = InterpretCustomerMood(context.CurrentSentimentScore),
            RecommendedActions = GenerateRecommendedActions(context),
            ThingsToAvoid = GenerateThingsToAvoid(context),
            SuggestedResolutions = GetSuggestedResolutions(context)
        };
        
        await _servicePlatform.DisplayAgentGuidance(interactionId, guidance);
    }
    
    private string InterpretCustomerMood(double sentimentScore)
    {
        return sentimentScore switch
        {
            > 0.5 => "Very Happy - Customer is extremely satisfied",
            > 0.2 => "Happy - Customer is satisfied, good opportunity for upsell",
            > -0.2 => "Neutral - Customer is neither satisfied nor dissatisfied",
            > -0.5 => "Frustrated - Customer needs careful handling and empathy",
            _ => "Angry - Customer requires immediate escalation and senior attention"
        };
    }
}

Mobile-Responsive Dashboard

Create mobile-optimized dashboards for on-the-go sentiment monitoring:

/* Mobile-first responsive dashboard styling */
.sentiment-dashboard {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    padding: 1rem;
    max-width: 100%;
}

@media (min-width: 768px) {
    .sentiment-dashboard {
        grid-template-columns: repeat(2, 1fr);
        padding: 2rem;
    }
}

@media (min-width: 1024px) {
    .sentiment-dashboard {
        grid-template-columns: repeat(3, 1fr);
        gap: 2rem;
    }
}

.metric-card {
    background: white;
    border-radius: 12px;
    padding: 1.5rem;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.metric-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 15px rgba(0, 0, 0, 0.15);
}

.metric-value {
    font-size: 2.5rem;
    font-weight: bold;
    margin: 0.5rem 0;
}

.metric-value.positive {
    color: #10b981;
}

.metric-value.negative {
    color: #ef4444;
}

.metric-value.neutral {
    color: #6b7280;
}

.alert-count.has-alerts {
    color: #dc2626;
    animation: pulse 2s infinite;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.7; }
}

/* Real-time chart container */
.chart-container {
    position: relative;
    height: 300px;
    width: 100%;
    margin: 1rem 0;
}

@media (max-width: 767px) {
    .chart-container {
        height: 200px;
    }
}

Power BI Embedded Analytics

Integrate Power BI for advanced sentiment analytics and business intelligence:

class PowerBISentimentAnalytics {
    constructor(containerId, powerBIConfig) {
        this.container = document.getElementById(containerId);
        this.config = powerBIConfig;
        this.initializePowerBI();
    }
    
    async initializePowerBI() {
        // Get Power BI embed token
        const embedToken = await this.getEmbedToken();
        
        const config = {
            type: 'report',
            id: this.config.reportId,
            embedUrl: this.config.embedUrl,
            accessToken: embedToken,
            tokenType: models.TokenType.Embed,
            settings: {
                filterPaneEnabled: true,
                navContentPaneEnabled: true,
                background: models.BackgroundType.Transparent,
                customLayout: {
                    displayOption: models.DisplayOption.FitToPage
                }
            }
        };
        
        // Embed the report
        this.report = powerbi.embed(this.container, config);
        
        // Handle events
        this.report.on('loaded', () => {
            console.log('Power BI Sentiment Report loaded');
            this.setupRealTimeUpdates();
        });
        
        this.report.on('error', (event) => {
            console.error('Power BI Error:', event.detail);
        });
    }
    
    setupRealTimeUpdates() {
        // Connect to SignalR for real-time data updates
        this.connection = new signalR.HubConnectionBuilder()
            .withUrl("/sentimentHub")
            .build();
            
        this.connection.start().then(() => {
            this.connection.invoke("JoinDashboardGroup", "analytics", {});
        });
        
        // Update Power BI data when new sentiment data arrives
        this.connection.on("SentimentDataUpdate", async (data) => {
            await this.updatePowerBIData(data);
        });
    }
    
    async updatePowerBIData(sentimentData) {
        // Update Power BI dataset with new data
        const updateData = {
            rows: sentimentData.map(item => ({
                Timestamp: item.timestamp,
                SentimentScore: item.sentimentScore,
                Source: item.source,
                CustomerTier: item.customerTier,
                ProductCategory: item.productCategory
            }))
        };
        
        try {
            await this.report.refresh();
        } catch (error) {
            console.error('Failed to refresh Power BI report:', error);
        }
    }
}

Real-Time Notification System

Implement browser notifications and in-app alerts for immediate attention:

class SentimentNotificationSystem {
    constructor() {
        this.requestNotificationPermission();
        this.setupSignalRConnection();
        this.notificationQueue = [];
    }
    
    async requestNotificationPermission() {
        if ('Notification' in window) {
            const permission = await Notification.requestPermission();
            this.notificationsEnabled = permission === 'granted';
        }
    }
    
    setupSignalRConnection() {
        this.connection = new signalR.HubConnectionBuilder()
            .withUrl("/sentimentHub")
            .build();
            
        this.connection.start().then(() => {
            this.connection.invoke("JoinDashboardGroup", "notifications", {});
        });
        
        this.connection.on("CriticalSentimentAlert", (alert) => {
            this.showCriticalAlert(alert);
        });
        
        this.connection.on("SentimentTrendAlert", (alert) => {
            this.showTrendAlert(alert);
        });
    }
    
    showCriticalAlert(alert) {
        // Browser notification
        if (this.notificationsEnabled) {
            new Notification(`Critical Sentiment Alert: ${alert.customerTier} Customer`, {
                body: `Sentiment score: ${alert.sentimentScore.toFixed(2)} - Immediate attention required`,
                icon: '/icons/alert-critical.png',
                tag: `sentiment-${alert.alertId}`,
                requireInteraction: true
            });
        }
        
        // In-app notification
        this.showInAppNotification({
            type: 'critical',
            title: 'Critical Sentiment Alert',
            message: `${alert.customerTier} customer requires immediate attention`,
            actions: [
                { label: 'View Details', action: () => this.viewAlertDetails(alert.alertId) },
                { label: 'Assign Agent', action: () => this.assignAgent(alert.customerId) }
            ],
            autoHide: false
        });
    }
    
    showInAppNotification(notification) {
        const notificationElement = document.createElement('div');
        notificationElement.className = `notification notification-${notification.type}`;
        notificationElement.innerHTML = `
            

${notification.title}

${notification.message}

${notification.actions.map(action => `` ).join('')}
`; document.body.appendChild(notificationElement); if (notification.autoHide !== false) { setTimeout(() => { notificationElement.remove(); }, 5000); } } }

Coming Up in Part 5

In the final part of our series, we'll cover advanced patterns and production operations. We'll explore:

  • Multi-language and cultural context handling
  • Advanced analytics with emotion detection and topic modeling
  • Data privacy, compliance, and ethical AI considerations
  • Comprehensive monitoring, logging, and observability
  • Cost optimization and scaling strategies for enterprise deployment

Stay tuned for the comprehensive finale that will provide you with production-ready patterns and enterprise-grade deployment guidance!


Are you using real-time dashboards in your applications? What challenges have you faced with live data visualization and user engagement? Share your experiences in the comments!

Written by:

343 Posts

View All Posts
Follow Me :