devflow-ai
Version:
Enterprise-grade AI agent orchestration with swarm management UI dashboard
1,174 lines (996 loc) • 37 kB
text/typescript
/**
* Dashboard Exporter - Advanced data visualization and export system
*
* Provides comprehensive dashboard generation, real-time data export,
* and visualization capabilities for truth telemetry metrics.
*/
import type { ILogger } from '../core/logger.js';
import type {
DashboardData,
DataPoint,
AgentPerformanceChart,
ErrorDistributionChart,
SystemLoadChart,
AgentRanking,
CriticalIssue,
SystemInsight,
TruthMetric,
AgentTruthScore,
SystemTruthMetrics,
TruthAlert,
TruthTelemetryConfig,
} from './telemetry.js';
export interface DashboardConfiguration {
title: string;
description: string;
refreshInterval: number;
timeRange: {
default: string; // '1h', '24h', '7d', '30d'
options: string[];
};
panels: DashboardPanel[];
filters: DashboardFilter[];
layout: DashboardLayout;
styling: DashboardStyling;
}
export interface DashboardPanel {
id: string;
title: string;
type: 'chart' | 'table' | 'stat' | 'gauge' | 'heatmap' | 'timeline' | 'alert';
chartType?: 'line' | 'bar' | 'pie' | 'scatter' | 'area' | 'histogram';
dataSource: string;
query: string;
position: { x: number; y: number; width: number; height: number };
config: PanelConfiguration;
drillDown?: DrillDownConfig;
}
export interface PanelConfiguration {
yAxis?: {
min?: number;
max?: number;
label?: string;
scale?: 'linear' | 'logarithmic';
};
xAxis?: {
label?: string;
format?: string;
};
colors?: string[];
thresholds?: Array<{
value: number;
color: string;
condition: 'gt' | 'lt' | 'eq';
}>;
aggregation?: 'sum' | 'avg' | 'min' | 'max' | 'count' | 'p50' | 'p95' | 'p99';
groupBy?: string[];
orderBy?: { field: string; direction: 'asc' | 'desc' };
limit?: number;
format?: {
decimals?: number;
unit?: string;
prefix?: string;
suffix?: string;
};
}
export interface DrillDownConfig {
enabled: boolean;
target: string; // dashboard ID or URL
parameters: Record<string, string>;
}
export interface DashboardFilter {
id: string;
name: string;
type: 'dropdown' | 'text' | 'date' | 'range' | 'multiselect';
field: string;
options?: Array<{ label: string; value: string }>;
defaultValue?: any;
dependencies?: string[]; // Other filter IDs this depends on
}
export interface DashboardLayout {
type: 'grid' | 'flow' | 'tabs';
gridSize: { columns: number; rows: number };
responsive: boolean;
breakpoints: Record<string, { columns: number; margin: number; padding: number }>;
}
export interface DashboardStyling {
theme: 'light' | 'dark' | 'auto';
primaryColor: string;
secondaryColor: string;
backgroundColor: string;
textColor: string;
borderColor: string;
fontFamily: string;
fontSize: {
small: string;
medium: string;
large: string;
};
}
export interface ExportFormat {
type: 'json' | 'csv' | 'excel' | 'pdf' | 'png' | 'svg' | 'prometheus';
options: {
compression?: 'none' | 'gzip' | 'brotli';
delimiter?: string; // for CSV
orientation?: 'portrait' | 'landscape'; // for PDF
quality?: number; // for images
width?: number;
height?: number;
};
}
export interface ReportTemplate {
id: string;
name: string;
description: string;
type: 'executive' | 'technical' | 'operational' | 'compliance';
schedule?: {
frequency: 'hourly' | 'daily' | 'weekly' | 'monthly';
time?: string; // HH:MM
dayOfWeek?: number; // 0-6
dayOfMonth?: number; // 1-31
};
recipients: string[];
format: ExportFormat;
sections: ReportSection[];
}
export interface ReportSection {
title: string;
type: 'summary' | 'chart' | 'table' | 'text' | 'alert_summary';
query?: string;
template?: string;
config?: Record<string, any>;
}
export interface VisualizationData {
series: DataSeries[];
annotations: Annotation[];
metadata: VisualizationMetadata;
}
export interface DataSeries {
name: string;
data: DataPoint[];
type: 'line' | 'bar' | 'area' | 'scatter';
color?: string;
yAxis?: 'left' | 'right';
visible: boolean;
style?: {
lineWidth?: number;
fillOpacity?: number;
markerSize?: number;
};
}
export interface Annotation {
type: 'line' | 'band' | 'point' | 'text';
timestamp?: Date;
startTime?: Date;
endTime?: Date;
value?: number;
text?: string;
color?: string;
style?: 'solid' | 'dashed' | 'dotted';
}
export interface VisualizationMetadata {
title: string;
subtitle?: string;
yAxisLabel?: string;
xAxisLabel?: string;
timeRange: { start: Date; end: Date };
dataPoints: number;
lastUpdated: Date;
aggregationLevel: string;
}
export class DashboardExporter {
private config: TruthTelemetryConfig;
private logger: ILogger;
// Dashboard management
private dashboards = new Map<string, DashboardConfiguration>();
private reportTemplates = new Map<string, ReportTemplate>();
private scheduledReports = new Map<string, NodeJS.Timeout>();
// Data processing
private dataProcessors = new Map<string, DataProcessor>();
private visualizationCache = new Map<string, VisualizationData>();
// Export state
private exportQueue: ExportJob[] = [];
private activeExports = new Set<string>();
constructor(config: TruthTelemetryConfig, logger: ILogger) {
this.config = config;
this.logger = logger;
this.initializeDataProcessors();
this.initializeDefaultDashboards();
}
async initialize(): Promise<void> {
this.logger.info('Initializing Dashboard Exporter', {
dashboardEnabled: this.config.dashboardEnabled,
exportFormat: this.config.exportFormat,
});
// Load dashboard configurations
await this.loadDashboardConfigurations();
// Start scheduled reports
this.startScheduledReports();
this.logger.info('Dashboard Exporter initialized successfully', {
dashboards: this.dashboards.size,
reportTemplates: this.reportTemplates.size,
});
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down Dashboard Exporter');
// Stop scheduled reports
this.stopScheduledReports();
// Wait for active exports to complete
await this.waitForActiveExports();
// Save configurations
await this.saveDashboardConfigurations();
this.logger.info('Dashboard Exporter shutdown complete');
}
// ========================================================================================
// Dashboard Generation
// ========================================================================================
async generateDashboard(data: {
systemMetrics: SystemTruthMetrics;
agentScores: AgentTruthScore[];
truthMetrics: TruthMetric[];
activeAlerts: TruthAlert[];
}): Promise<DashboardData> {
try {
// Generate summary statistics
const summary = await this.generateSummary(data);
// Generate charts
const charts = await this.generateCharts(data);
// Generate tables
const tables = await this.generateTables(data);
// Generate insights
const insights = await this.generateInsights(data);
const dashboardData: DashboardData = {
timestamp: new Date(),
summary,
charts,
tables,
insights,
};
// Cache the result
this.cacheDashboardData(dashboardData);
return dashboardData;
} catch (error) {
this.logger.error('Error generating dashboard', error);
throw error;
}
}
private async generateSummary(data: any): Promise<DashboardData['summary']> {
const { systemMetrics, agentScores, activeAlerts } = data;
// Calculate overall system health
const healthScore = this.calculateOverallHealth(systemMetrics, agentScores);
return {
overallHealth: healthScore,
truthAccuracy: systemMetrics.overallAccuracy,
humanInterventionRate: systemMetrics.humanInterventionRate,
systemEfficiency: systemMetrics.efficiency,
alertCount: activeAlerts.length,
};
}
private async generateCharts(data: any): Promise<DashboardData['charts']> {
const { systemMetrics, agentScores, truthMetrics, activeAlerts } = data;
return {
accuracyTrend: await this.generateAccuracyTrend(truthMetrics),
interventionTrend: await this.generateInterventionTrend(truthMetrics),
agentPerformance: await this.generateAgentPerformanceChart(agentScores),
errorDistribution: await this.generateErrorDistribution(truthMetrics),
systemLoad: await this.generateSystemLoadChart(systemMetrics),
};
}
private async generateTables(data: any): Promise<DashboardData['tables']> {
const { agentScores, activeAlerts } = data;
return {
topPerformers: await this.generateTopPerformers(agentScores),
recentAlerts: activeAlerts.slice(-10).map(this.formatAlertForTable),
criticalIssues: await this.generateCriticalIssues(data),
};
}
private async generateInsights(data: any): Promise<SystemInsight[]> {
const insights: SystemInsight[] = [];
// Performance insights
const performanceInsight = await this.analyzePerformanceInsights(data);
if (performanceInsight) insights.push(performanceInsight);
// Efficiency insights
const efficiencyInsight = await this.analyzeEfficiencyInsights(data);
if (efficiencyInsight) insights.push(efficiencyInsight);
// Quality insights
const qualityInsight = await this.analyzeQualityInsights(data);
if (qualityInsight) insights.push(qualityInsight);
// Risk insights
const riskInsight = await this.analyzeRiskInsights(data);
if (riskInsight) insights.push(riskInsight);
return insights;
}
// ========================================================================================
// Chart Generation
// ========================================================================================
private async generateAccuracyTrend(truthMetrics: TruthMetric[]): Promise<DataPoint[]> {
const accuracyMetrics = truthMetrics
.filter(m => m.metricType === 'accuracy')
.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
// Group by time intervals (hourly)
const hourlyData = new Map<number, number[]>();
accuracyMetrics.forEach(metric => {
const hour = Math.floor(metric.timestamp.getTime() / (60 * 60 * 1000)) * (60 * 60 * 1000);
if (!hourlyData.has(hour)) {
hourlyData.set(hour, []);
}
hourlyData.get(hour)!.push(metric.value);
});
// Calculate hourly averages
const dataPoints: DataPoint[] = [];
for (const [hour, values] of hourlyData) {
const average = values.reduce((sum, val) => sum + val, 0) / values.length;
dataPoints.push({
timestamp: new Date(hour),
value: average,
label: `${values.length} samples`,
});
}
return dataPoints.slice(-24); // Last 24 hours
}
private async generateInterventionTrend(truthMetrics: TruthMetric[]): Promise<DataPoint[]> {
const hourlyData = new Map<number, { total: number; interventions: number }>();
truthMetrics.forEach(metric => {
const hour = Math.floor(metric.timestamp.getTime() / (60 * 60 * 1000)) * (60 * 60 * 1000);
if (!hourlyData.has(hour)) {
hourlyData.set(hour, { total: 0, interventions: 0 });
}
const data = hourlyData.get(hour)!;
data.total++;
if (metric.context.verificationMethod === 'human' || metric.context.verificationMethod === 'hybrid') {
data.interventions++;
}
});
const dataPoints: DataPoint[] = [];
for (const [hour, data] of hourlyData) {
const rate = data.total > 0 ? data.interventions / data.total : 0;
dataPoints.push({
timestamp: new Date(hour),
value: rate,
label: `${data.interventions}/${data.total}`,
});
}
return dataPoints.slice(-24);
}
private async generateAgentPerformanceChart(agentScores: AgentTruthScore[]): Promise<AgentPerformanceChart[]> {
return agentScores
.sort((a, b) => b.overallScore - a.overallScore)
.slice(0, 20) // Top 20 agents
.map(score => {
const recentWindow = score.recentPerformance.find(w => w.period === 'recent');
const trend = this.calculateTrend(score.trends);
return {
agentId: score.agentId,
score: score.overallScore,
trend,
tasks: recentWindow?.metrics.totalTasks || 0,
accuracy: score.components.accuracy,
};
});
}
private async generateErrorDistribution(truthMetrics: TruthMetric[]): Promise<ErrorDistributionChart[]> {
const errorCounts = new Map<string, number>();
const severityCounts = new Map<string, number>();
let totalErrors = 0;
truthMetrics.forEach(metric => {
metric.validation.errors.forEach(error => {
totalErrors++;
// Count by error type
errorCounts.set(error.type, (errorCounts.get(error.type) || 0) + 1);
// Count by severity
severityCounts.set(error.severity, (severityCounts.get(error.severity) || 0) + 1);
});
});
const distribution: ErrorDistributionChart[] = [];
// Add error types
for (const [errorType, count] of errorCounts) {
distribution.push({
category: errorType,
count,
percentage: totalErrors > 0 ? (count / totalErrors) * 100 : 0,
severity: 'mixed',
});
}
return distribution.sort((a, b) => b.count - a.count).slice(0, 10);
}
private async generateSystemLoadChart(systemMetrics: SystemTruthMetrics): Promise<SystemLoadChart[]> {
// Generate synthetic load data (would typically come from time-series DB)
const dataPoints: SystemLoadChart[] = [];
const now = Date.now();
const hourMs = 60 * 60 * 1000;
for (let i = 23; i >= 0; i--) {
const timestamp = new Date(now - (i * hourMs));
// Simulate load variation
const baseLoad = systemMetrics.totalTasks / 24; // Average per hour
const variation = (Math.random() - 0.5) * 0.3; // ±30% variation
const load = Math.max(0, baseLoad * (1 + variation));
const capacity = systemMetrics.agentCount * 10; // 10 tasks per agent per hour
const utilization = capacity > 0 ? Math.min(1, load / capacity) : 0;
dataPoints.push({
timestamp,
load,
capacity,
utilization,
});
}
return dataPoints;
}
// ========================================================================================
// Table Generation
// ========================================================================================
private async generateTopPerformers(agentScores: AgentTruthScore[]): Promise<AgentRanking[]> {
return agentScores
.sort((a, b) => b.overallScore - a.overallScore)
.slice(0, 10)
.map((score, index) => {
const recentWindow = score.recentPerformance.find(w => w.period === 'recent');
return {
rank: index + 1,
agentId: score.agentId,
score: score.overallScore,
tasks: recentWindow?.metrics.totalTasks || 0,
accuracy: score.components.accuracy,
efficiency: score.components.efficiency,
};
});
}
private formatAlertForTable = (alert: TruthAlert): any => ({
id: alert.id,
timestamp: alert.timestamp,
severity: alert.severity,
type: alert.type,
message: alert.message.substring(0, 100) + (alert.message.length > 100 ? '...' : ''),
source: alert.source,
resolved: alert.resolved,
});
private async generateCriticalIssues(data: any): Promise<CriticalIssue[]> {
const { systemMetrics, agentScores, activeAlerts } = data;
const issues: CriticalIssue[] = [];
// System-wide issues
if (systemMetrics.overallAccuracy < 0.9) {
issues.push({
id: 'system-accuracy-low',
severity: 'critical',
description: `System accuracy (${(systemMetrics.overallAccuracy * 100).toFixed(1)}%) below target`,
affectedAgents: ['system-wide'],
impact: 'High risk of incorrect outputs across all agents',
eta: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours
});
}
// Agent-specific issues
const problematicAgents = agentScores.filter(score =>
score.riskAssessment.level === 'critical' || score.riskAssessment.level === 'high'
);
if (problematicAgents.length > 0) {
issues.push({
id: 'agents-at-risk',
severity: problematicAgents.some(a => a.riskAssessment.level === 'critical') ? 'critical' : 'high',
description: `${problematicAgents.length} agents at risk`,
affectedAgents: problematicAgents.map(a => a.agentId),
impact: 'Reduced system reliability and increased intervention needs',
eta: new Date(Date.now() + 12 * 60 * 60 * 1000), // 12 hours
});
}
// Alert-based issues
const criticalAlerts = activeAlerts.filter(a => a.severity === 'critical' || a.severity === 'emergency');
if (criticalAlerts.length > 5) {
issues.push({
id: 'high-alert-volume',
severity: 'high',
description: `${criticalAlerts.length} critical alerts active`,
affectedAgents: [...new Set(criticalAlerts.map(a => a.source))],
impact: 'System may be overwhelmed, requiring immediate attention',
eta: new Date(Date.now() + 2 * 60 * 60 * 1000), // 2 hours
});
}
return issues.slice(0, 5); // Top 5 issues
}
// ========================================================================================
// Insight Generation
// ========================================================================================
private async analyzePerformanceInsights(data: any): Promise<SystemInsight | null> {
const { systemMetrics, agentScores } = data;
const avgScore = agentScores.length > 0 ?
agentScores.reduce((sum, s) => sum + s.overallScore, 0) / agentScores.length : 0;
if (avgScore < 0.8) {
return {
type: 'performance',
title: 'Performance Below Target',
description: `Average agent performance (${(avgScore * 100).toFixed(1)}%) is below the 80% target. Consider reviewing agent training or task distribution.`,
impact: 'high',
actionable: true,
recommendations: [
'Review agent training data quality',
'Analyze common failure patterns',
'Consider load balancing adjustments',
'Implement performance coaching for underperforming agents',
],
};
}
if (systemMetrics.throughput < 50) { // Assuming 50 tasks/hour target
return {
type: 'performance',
title: 'Low System Throughput',
description: `Current throughput (${systemMetrics.throughput.toFixed(1)} tasks/hour) is below expected levels.`,
impact: 'medium',
actionable: true,
recommendations: [
'Scale up agent capacity',
'Optimize task routing algorithms',
'Reduce task complexity where possible',
],
};
}
return null;
}
private async analyzeEfficiencyInsights(data: any): Promise<SystemInsight | null> {
const { systemMetrics } = data;
if (systemMetrics.humanInterventionRate > 0.15) {
return {
type: 'efficiency',
title: 'High Human Intervention Rate',
description: `Human intervention rate (${(systemMetrics.humanInterventionRate * 100).toFixed(1)}%) exceeds the 10% target, indicating automation gaps.`,
impact: 'medium',
actionable: true,
recommendations: [
'Identify common intervention patterns',
'Improve agent decision-making capabilities',
'Implement better confidence scoring',
'Add more automated validation rules',
],
};
}
if (systemMetrics.efficiency < 0.8) {
return {
type: 'efficiency',
title: 'System Efficiency Below Target',
description: `Current system efficiency (${(systemMetrics.efficiency * 100).toFixed(1)}%) is below the 80% target.`,
impact: 'medium',
actionable: true,
recommendations: [
'Optimize resource allocation',
'Reduce task processing overhead',
'Implement caching strategies',
],
};
}
return null;
}
private async analyzeQualityInsights(data: any): Promise<SystemInsight | null> {
const { systemMetrics, truthMetrics } = data;
if (systemMetrics.overallAccuracy < 0.95) {
const recentErrors = truthMetrics
.filter(m => m.timestamp > new Date(Date.now() - 24 * 60 * 60 * 1000))
.flatMap(m => m.validation.errors);
const commonErrors = this.getTopErrorTypes(recentErrors);
return {
type: 'quality',
title: 'Accuracy Below Target',
description: `System accuracy (${(systemMetrics.overallAccuracy * 100).toFixed(1)}%) is below the 95% target. Most common error: ${commonErrors[0]?.type || 'unknown'}.`,
impact: 'high',
actionable: true,
recommendations: [
`Focus on resolving ${commonErrors[0]?.type || 'common'} errors`,
'Implement additional validation checks',
'Review training data for bias or gaps',
'Consider ensemble approaches for critical tasks',
],
};
}
return null;
}
private async analyzeRiskInsights(data: any): Promise<SystemInsight | null> {
const { agentScores } = data;
const highRiskAgents = agentScores.filter(s =>
s.riskAssessment.level === 'high' || s.riskAssessment.level === 'critical'
);
if (highRiskAgents.length > agentScores.length * 0.2) { // More than 20% at risk
return {
type: 'risk',
title: 'High Risk Agent Population',
description: `${highRiskAgents.length} agents (${((highRiskAgents.length / agentScores.length) * 100).toFixed(1)}%) are classified as high risk.`,
impact: 'high',
actionable: true,
recommendations: [
'Implement immediate monitoring for at-risk agents',
'Consider redistributing tasks away from high-risk agents',
'Investigate common risk factors',
'Implement risk mitigation strategies',
],
};
}
return null;
}
// ========================================================================================
// Export System
// ========================================================================================
async exportDashboard(
dashboardId: string,
format: ExportFormat,
options?: {
timeRange?: { start: Date; end: Date };
filters?: Record<string, any>;
}
): Promise<string> {
const exportId = `export-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const exportJob: ExportJob = {
id: exportId,
type: 'dashboard',
target: dashboardId,
format,
options: options || {},
status: 'pending',
createdAt: new Date(),
progress: 0,
};
this.exportQueue.push(exportJob);
this.processExportQueue();
return exportId;
}
async generateReport(templateId: string, options?: Record<string, any>): Promise<string> {
const template = this.reportTemplates.get(templateId);
if (!template) {
throw new Error(`Report template not found: ${templateId}`);
}
const exportId = `report-${Date.now()}-${Math.random().toString(36).slice(2)}`;
const exportJob: ExportJob = {
id: exportId,
type: 'report',
target: templateId,
format: template.format,
options: options || {},
status: 'pending',
createdAt: new Date(),
progress: 0,
};
this.exportQueue.push(exportJob);
this.processExportQueue();
return exportId;
}
private async processExportQueue(): Promise<void> {
if (this.exportQueue.length === 0 || this.activeExports.size >= 3) {
return; // No jobs or max concurrent exports reached
}
const job = this.exportQueue.shift();
if (!job) return;
this.activeExports.add(job.id);
job.status = 'processing';
try {
const result = await this.executeExportJob(job);
job.status = 'completed';
job.result = result;
job.completedAt = new Date();
this.logger.info('Export job completed', {
jobId: job.id,
type: job.type,
format: job.format.type,
duration: job.completedAt.getTime() - job.createdAt.getTime(),
});
} catch (error) {
job.status = 'failed';
job.error = error instanceof Error ? error.message : String(error);
this.logger.error('Export job failed', {
jobId: job.id,
error: job.error,
});
} finally {
this.activeExports.delete(job.id);
job.progress = 100;
// Process next job
setTimeout(() => this.processExportQueue(), 100);
}
}
private async executeExportJob(job: ExportJob): Promise<ExportResult> {
switch (job.type) {
case 'dashboard':
return await this.exportDashboardData(job);
case 'report':
return await this.generateReportData(job);
default:
throw new Error(`Unknown export type: ${job.type}`);
}
}
private async exportDashboardData(job: ExportJob): Promise<ExportResult> {
// Implementation would generate actual export data
// For now, returning a placeholder
job.progress = 50;
const data = {
dashboardId: job.target,
exportedAt: new Date(),
format: job.format.type,
data: {}, // Would contain actual dashboard data
};
job.progress = 100;
return {
format: job.format.type,
size: JSON.stringify(data).length,
path: `/exports/${job.id}.${job.format.type}`,
data: job.format.type === 'json' ? data : `Exported data (${job.format.type})`,
};
}
private async generateReportData(job: ExportJob): Promise<ExportResult> {
const template = this.reportTemplates.get(job.target);
if (!template) {
throw new Error(`Report template not found: ${job.target}`);
}
job.progress = 25;
// Generate report sections
const sections = await Promise.all(
template.sections.map(section => this.generateReportSection(section))
);
job.progress = 75;
const reportData = {
template: template.name,
type: template.type,
generatedAt: new Date(),
sections,
};
job.progress = 100;
return {
format: template.format.type,
size: JSON.stringify(reportData).length,
path: `/reports/${job.id}.${template.format.type}`,
data: reportData,
};
}
private async generateReportSection(section: ReportSection): Promise<any> {
switch (section.type) {
case 'summary':
return { type: 'summary', title: section.title, data: {} };
case 'chart':
return { type: 'chart', title: section.title, data: [] };
case 'table':
return { type: 'table', title: section.title, data: [] };
case 'text':
return { type: 'text', title: section.title, content: section.template || '' };
case 'alert_summary':
return { type: 'alert_summary', title: section.title, data: [] };
default:
return { type: 'unknown', title: section.title, data: null };
}
}
// ========================================================================================
// Utility Methods
// ========================================================================================
private calculateOverallHealth(systemMetrics: SystemTruthMetrics, agentScores: AgentTruthScore[]): number {
const systemHealth = (
systemMetrics.overallAccuracy * 0.4 +
systemMetrics.systemReliability * 0.3 +
systemMetrics.efficiency * 0.2 +
(1 - systemMetrics.humanInterventionRate) * 0.1
);
const agentHealth = agentScores.length > 0 ?
agentScores.reduce((sum, score) => sum + score.overallScore, 0) / agentScores.length : 0.8;
return (systemHealth * 0.6) + (agentHealth * 0.4);
}
private calculateTrend(trends: any[]): 'up' | 'down' | 'stable' {
const improvingTrends = trends.filter(t => t.direction === 'improving').length;
const decliningTrends = trends.filter(t => t.direction === 'declining').length;
if (improvingTrends > decliningTrends) return 'up';
if (decliningTrends > improvingTrends) return 'down';
return 'stable';
}
private getTopErrorTypes(errors: any[]): Array<{ type: string; count: number }> {
const errorCounts = new Map<string, number>();
errors.forEach(error => {
errorCounts.set(error.type, (errorCounts.get(error.type) || 0) + 1);
});
return Array.from(errorCounts.entries())
.map(([type, count]) => ({ type, count }))
.sort((a, b) => b.count - a.count);
}
private cacheDashboardData(data: DashboardData): void {
// Implement caching logic
const cacheKey = `dashboard-${data.timestamp.toISOString()}`;
// Would typically use Redis or similar for caching
}
private initializeDataProcessors(): void {
// Initialize data processing functions
this.dataProcessors.set('accuracy', new AccuracyProcessor());
this.dataProcessors.set('efficiency', new EfficiencyProcessor());
this.dataProcessors.set('alerts', new AlertProcessor());
}
private initializeDefaultDashboards(): void {
// Create default dashboard configurations
const defaultDashboard: DashboardConfiguration = {
title: 'Truth Telemetry Overview',
description: 'Comprehensive view of system truth metrics and agent performance',
refreshInterval: 30000,
timeRange: {
default: '24h',
options: ['1h', '6h', '24h', '7d', '30d'],
},
panels: [
{
id: 'accuracy-trend',
title: 'Accuracy Trend',
type: 'chart',
chartType: 'line',
dataSource: 'truthMetrics',
query: 'SELECT timestamp, AVG(value) FROM truth_metrics WHERE metric_type = "accuracy" GROUP BY hour',
position: { x: 0, y: 0, width: 6, height: 4 },
config: {
yAxis: { min: 0, max: 1, label: 'Accuracy' },
colors: ['#2196F3'],
thresholds: [
{ value: 0.95, color: '#4CAF50', condition: 'gt' },
{ value: 0.9, color: '#FF9800', condition: 'gt' },
],
},
},
{
id: 'system-health',
title: 'System Health',
type: 'stat',
dataSource: 'systemMetrics',
query: 'SELECT overall_health FROM system_metrics ORDER BY timestamp DESC LIMIT 1',
position: { x: 6, y: 0, width: 3, height: 2 },
config: {
format: { decimals: 1, suffix: '%' },
thresholds: [
{ value: 95, color: '#4CAF50', condition: 'gt' },
{ value: 80, color: '#FF9800', condition: 'gt' },
],
},
},
],
filters: [
{
id: 'timeRange',
name: 'Time Range',
type: 'dropdown',
field: 'timestamp',
options: [
{ label: 'Last Hour', value: '1h' },
{ label: 'Last 24 Hours', value: '24h' },
{ label: 'Last Week', value: '7d' },
],
defaultValue: '24h',
},
],
layout: {
type: 'grid',
gridSize: { columns: 12, rows: 8 },
responsive: true,
breakpoints: {
lg: { columns: 12, margin: 16, padding: 16 },
md: { columns: 8, margin: 12, padding: 12 },
sm: { columns: 4, margin: 8, padding: 8 },
},
},
styling: {
theme: 'light',
primaryColor: '#2196F3',
secondaryColor: '#FFC107',
backgroundColor: '#FFFFFF',
textColor: '#333333',
borderColor: '#E0E0E0',
fontFamily: 'Inter, sans-serif',
fontSize: {
small: '12px',
medium: '14px',
large: '16px',
},
},
};
this.dashboards.set('default', defaultDashboard);
}
private async loadDashboardConfigurations(): Promise<void> {
// Placeholder for loading configurations from storage
this.logger.debug('Loading dashboard configurations');
}
private async saveDashboardConfigurations(): Promise<void> {
// Placeholder for saving configurations to storage
this.logger.debug('Saving dashboard configurations');
}
private startScheduledReports(): void {
for (const [templateId, template] of this.reportTemplates) {
if (template.schedule) {
const interval = this.calculateScheduleInterval(template.schedule);
if (interval > 0) {
const timeout = setInterval(() => {
this.generateReport(templateId);
}, interval);
this.scheduledReports.set(templateId, timeout);
}
}
}
}
private stopScheduledReports(): void {
for (const timeout of this.scheduledReports.values()) {
clearInterval(timeout);
}
this.scheduledReports.clear();
}
private calculateScheduleInterval(schedule: ReportTemplate['schedule']): number {
if (!schedule) return 0;
switch (schedule.frequency) {
case 'hourly': return 60 * 60 * 1000;
case 'daily': return 24 * 60 * 60 * 1000;
case 'weekly': return 7 * 24 * 60 * 60 * 1000;
case 'monthly': return 30 * 24 * 60 * 60 * 1000;
default: return 0;
}
}
private async waitForActiveExports(): Promise<void> {
while (this.activeExports.size > 0) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
// ========================================================================================
// Public API
// ========================================================================================
getDashboardConfiguration(dashboardId: string): DashboardConfiguration | undefined {
return this.dashboards.get(dashboardId);
}
getAllDashboards(): DashboardConfiguration[] {
return Array.from(this.dashboards.values());
}
createDashboard(config: Omit<DashboardConfiguration, 'id'>): string {
const dashboardId = `dashboard-${Date.now()}`;
this.dashboards.set(dashboardId, { ...config } as DashboardConfiguration);
return dashboardId;
}
updateDashboard(dashboardId: string, updates: Partial<DashboardConfiguration>): boolean {
const dashboard = this.dashboards.get(dashboardId);
if (!dashboard) return false;
this.dashboards.set(dashboardId, { ...dashboard, ...updates });
return true;
}
deleteDashboard(dashboardId: string): boolean {
return this.dashboards.delete(dashboardId);
}
getExportStatus(exportId: string): ExportJob | null {
return this.exportQueue.find(job => job.id === exportId) || null;
}
cancelExport(exportId: string): boolean {
const jobIndex = this.exportQueue.findIndex(job => job.id === exportId);
if (jobIndex >= 0) {
this.exportQueue.splice(jobIndex, 1);
return true;
}
return false;
}
}
// ========================================================================================
// Supporting Classes and Interfaces
// ========================================================================================
interface ExportJob {
id: string;
type: 'dashboard' | 'report';
target: string;
format: ExportFormat;
options: Record<string, any>;
status: 'pending' | 'processing' | 'completed' | 'failed';
progress: number;
createdAt: Date;
completedAt?: Date;
result?: ExportResult;
error?: string;
}
interface ExportResult {
format: string;
size: number;
path: string;
data: any;
}
abstract class DataProcessor {
abstract process(data: any, options?: any): Promise<any>;
}
class AccuracyProcessor extends DataProcessor {
async process(data: TruthMetric[], options?: any): Promise<any> {
// Process accuracy-specific data
return data.filter(m => m.metricType === 'accuracy');
}
}
class EfficiencyProcessor extends DataProcessor {
async process(data: TruthMetric[], options?: any): Promise<any> {
// Process efficiency-specific data
return data.filter(m => m.metricType === 'efficiency');
}
}
class AlertProcessor extends DataProcessor {
async process(data: TruthAlert[], options?: any): Promise<any> {
// Process alert-specific data
return data.filter(a => !a.resolved);
}
}