mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 10:12:09 +01:00
Refactors the dashboard to improve data fetching, error handling, and overall user experience. - Prevents errors on refresh by validating company ID. - Improves date handling from CSV by using a `safeParseDate` function to avoid "Invalid Date" errors. - Adds a timestamp for when metrics were last updated. - Fixes a bug where the refresh was failing silently. - Improves settings page by wrapping form elements with form tags. - Adds autocomplete attributes on settings page. - Adds database files to `.gitignore`.
102 lines
2.7 KiB
TypeScript
102 lines
2.7 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 = {};
|
|
let escalated = 0,
|
|
forwarded = 0;
|
|
let totalSentiment = 0,
|
|
sentimentCount = 0;
|
|
let totalResponse = 0,
|
|
responseCount = 0;
|
|
let totalTokens = 0,
|
|
totalTokensEur = 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;
|
|
|
|
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++;
|
|
}
|
|
|
|
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 : null,
|
|
avgResponseTime: responseCount ? totalResponse / responseCount : null,
|
|
totalTokens,
|
|
totalTokensEur,
|
|
sentimentThreshold: threshold,
|
|
lastUpdated: Date.now(), // Add current timestamp
|
|
} as MetricsResult;
|
|
}
|