Files
livedash-node/lib/metrics.ts
Kaj Kowalski 5317b2aa39 Enhances dashboard with new metrics and charts
Improves the dashboard with additional metrics and visualizations
to provide a more comprehensive overview of application usage and performance.

Adds new charts, including:
- Word cloud for category analysis
- Geographic map for user distribution (simulated data)
- Response time distribution chart

Refactors existing components for improved clarity and reusability,
including the introduction of a generic `MetricCard` component.

Improves error handling and user feedback during data refresh and
session loading.

Adds recommended VSCode extensions for ESLint and Prettier.
2025-05-22 04:04:50 +02:00

136 lines
3.6 KiB
TypeScript

// Functions to calculate metrics over sessions
import {
ChatSession,
DayMetrics,
CategoryMetrics,
LanguageMetrics,
MetricsResult,
} from "./types";
interface CompanyConfig {
sentimentAlert?: number;
}
export function sessionMetrics(
sessions: ChatSession[],
companyConfig: CompanyConfig = {}
): MetricsResult {
const total = sessions.length;
const byDay: DayMetrics = {};
const byCategory: CategoryMetrics = {};
const byLanguage: LanguageMetrics = {};
const tokensByDay: DayMetrics = {};
const tokensCostByDay: DayMetrics = {};
let escalated = 0,
forwarded = 0;
let totalSentiment = 0,
sentimentCount = 0;
let totalResponse = 0,
responseCount = 0;
let totalTokens = 0,
totalTokensEur = 0;
// For sentiment distribution
let sentimentPositive = 0,
sentimentNegative = 0,
sentimentNeutral = 0;
// Calculate total session duration in minutes
let totalDuration = 0;
let durationCount = 0;
sessions.forEach((s) => {
const day = s.startTime.toISOString().slice(0, 10);
byDay[day] = (byDay[day] || 0) + 1;
if (s.category) byCategory[s.category] = (byCategory[s.category] || 0) + 1;
if (s.language) byLanguage[s.language] = (byLanguage[s.language] || 0) + 1;
// Process token usage by day
if (s.tokens) {
tokensByDay[day] = (tokensByDay[day] || 0) + s.tokens;
}
// Process token cost by day
if (s.tokensEur) {
tokensCostByDay[day] = (tokensCostByDay[day] || 0) + s.tokensEur;
}
if (s.endTime) {
const duration =
(s.endTime.getTime() - s.startTime.getTime()) / (1000 * 60); // minutes
totalDuration += duration;
durationCount++;
}
if (s.escalated) escalated++;
if (s.forwardedHr) forwarded++;
if (s.sentiment != null) {
totalSentiment += s.sentiment;
sentimentCount++;
// Classify sentiment
if (s.sentiment > 0.3) {
sentimentPositive++;
} else if (s.sentiment < -0.3) {
sentimentNegative++;
} else {
sentimentNeutral++;
}
}
if (s.avgResponseTime != null) {
totalResponse += s.avgResponseTime;
responseCount++;
}
totalTokens += s.tokens || 0;
totalTokensEur += s.tokensEur || 0;
});
// Now add sentiment alert logic:
let belowThreshold = 0;
const threshold = companyConfig.sentimentAlert ?? null;
if (threshold != null) {
for (const s of sessions) {
if (s.sentiment != null && s.sentiment < threshold) belowThreshold++;
}
}
// Calculate average sessions per day
const dayCount = Object.keys(byDay).length;
const avgSessionsPerDay = dayCount > 0 ? total / dayCount : 0;
// Calculate average session length
const avgSessionLength =
durationCount > 0 ? totalDuration / durationCount : null;
return {
totalSessions: total,
avgSessionsPerDay,
avgSessionLength,
days: byDay,
languages: byLanguage,
categories: byCategory,
belowThresholdCount: belowThreshold,
// Additional metrics not in the interface - using type assertion
escalatedCount: escalated,
forwardedCount: forwarded,
avgSentiment: sentimentCount ? totalSentiment / sentimentCount : undefined,
avgResponseTime: responseCount ? totalResponse / responseCount : undefined,
totalTokens,
totalTokensEur,
sentimentThreshold: threshold,
lastUpdated: Date.now(), // Add current timestamp
// New metrics for enhanced dashboard
sentimentPositiveCount: sentimentPositive,
sentimentNeutralCount: sentimentNeutral,
sentimentNegativeCount: sentimentNegative,
tokensByDay,
tokensCostByDay,
};
}