/**
* NCP Visual Analytics Formatter
* Enhanced terminal output with CLI charts and graphs
*/
import chalk from 'chalk';
import { AnalyticsReport } from './log-parser.js';
export class VisualAnalyticsFormatter {
/**
* Format analytics dashboard with visual charts
*/
static async formatVisualDashboard(report: AnalyticsReport): Promise<string> {
const output: string[] = [];
// Header with enhanced styling
output.push('');
output.push(chalk.bold.cyan('π NCP Impact Analytics Dashboard (Visual)'));
output.push(chalk.dim('β'.repeat(60)));
output.push('');
// Overview Section with Key Metrics
output.push(chalk.bold.white('π KEY METRICS OVERVIEW'));
output.push('');
const days = Math.ceil((report.timeRange.end.getTime() - report.timeRange.start.getTime()) / (1000 * 60 * 60 * 24));
const period = days <= 1 ? 'today' : `last ${days} days`;
// Create metrics display with visual bars
const metrics = [
{ label: 'Total Sessions', value: report?.totalSessions || 0, unit: 'sessions', color: chalk.green },
{ label: 'Unique MCPs', value: report?.uniqueMCPs || 0, unit: 'servers', color: chalk.cyan },
{ label: 'Success Rate', value: Math.round(report?.successRate || 0), unit: '%', color: chalk.yellow },
{ label: 'Response Data', value: Math.round((report?.totalResponseSize || 0) / 1024 / 1024), unit: 'MB', color: chalk.blue }
];
const maxMetricValue = metrics.length > 0 ? Math.max(...metrics.map(m => m?.value || 0)) : 1;
for (const metric of metrics) {
const bar = this.createHorizontalBar(metric.value, maxMetricValue, 25);
output.push(`${metric.color(metric.label.padEnd(15))}: ${bar} ${metric.color(metric.value.toLocaleString())} ${chalk.dim(metric.unit)}`);
}
output.push('');
// Usage Trends Chart
if (Object.keys(report?.dailyUsage || {}).length > 3) {
output.push(chalk.bold.white('π DAILY USAGE TRENDS'));
output.push('');
const dailyData = Object.entries(report?.dailyUsage || {})
.sort(([a], [b]) => a.localeCompare(b))
.map(([_, usage]) => usage);
if (dailyData.length > 1) {
// Create simple ASCII line chart
const chart = this.createLineChart(dailyData, 8, 40);
output.push(chalk.green(chart));
output.push(chalk.dim(' ββ Sessions per day over time'));
}
output.push('');
}
// Top MCPs Usage Chart
if ((report?.topMCPsByUsage || []).length > 0) {
output.push(chalk.bold.white('π₯ TOP MCP USAGE DISTRIBUTION'));
output.push('');
const topMCPs = (report?.topMCPsByUsage || []).slice(0, 8);
const maxSessions = topMCPs.length > 0 ? Math.max(...topMCPs.map(mcp => mcp?.sessions || 0)) : 1;
for (const mcp of topMCPs) {
const percentage = (((mcp?.sessions || 0) / (report?.totalSessions || 1)) * 100).toFixed(1);
const bar = this.createColorfulBar(mcp?.sessions || 0, maxSessions, 30);
const successIcon = (mcp?.successRate || 0) >= 95 ? 'β
' : (mcp?.successRate || 0) >= 80 ? 'β οΈ' : 'β';
output.push(`${chalk.cyan((mcp?.name || 'unknown').padEnd(20))} ${bar} ${chalk.white((mcp?.sessions || 0).toString().padStart(3))} ${chalk.dim(`(${percentage}%)`)} ${successIcon}`);
}
output.push('');
}
// Performance Distribution
if ((report?.performanceMetrics?.fastestMCPs || []).length > 0) {
output.push(chalk.bold.white('β‘ PERFORMANCE DISTRIBUTION'));
output.push('');
// Create performance buckets
const performanceData = (report?.performanceMetrics?.fastestMCPs || []).concat(report?.performanceMetrics?.slowestMCPs || []);
const durations = performanceData.map(mcp => mcp?.avgDuration || 0).filter(d => d > 0);
if (durations.length > 3) {
// Create performance distribution chart
const chart = this.createLineChart(durations.slice(0, 20), 6, 35);
output.push(chalk.yellow(chart));
output.push(chalk.dim(' ββ Response times across MCPs (ms)'));
}
output.push('');
}
// Value Delivered Section with Visual Impact
output.push(chalk.bold.white('π° VALUE IMPACT VISUALIZATION (ESTIMATES)'));
output.push('');
// Calculate savings
const estimatedTokensWithoutNCP = report.totalSessions * report.uniqueMCPs * 100;
const estimatedTokensWithNCP = report.totalSessions * 50;
const tokenSavings = estimatedTokensWithoutNCP - estimatedTokensWithNCP;
const costSavings = (tokenSavings / 1000) * 0.002;
// Visual representation of savings
const savingsData = [
{ label: 'Without NCP', value: estimatedTokensWithoutNCP, color: chalk.red },
{ label: 'With NCP', value: estimatedTokensWithNCP, color: chalk.green }
];
const maxTokens = Math.max(...savingsData.map(s => s.value));
for (const saving of savingsData) {
const bar = this.createHorizontalBar(saving.value, maxTokens, 40);
output.push(`${saving.label.padEnd(12)}: ${bar} ${saving.color((saving.value / 1000000).toFixed(1))}M tokens`);
}
output.push('');
output.push(`π ${chalk.bold.green((tokenSavings / 1000000).toFixed(1))}M tokens saved = ${chalk.bold.green('$' + costSavings.toFixed(2))} cost reduction`);
output.push(`π§ ${chalk.bold.green((((report.uniqueMCPs - 1) / report.uniqueMCPs) * 100).toFixed(1) + '%')} cognitive load reduction`);
output.push('');
// Environmental Impact with Visual Scale
output.push(chalk.bold.white('π± ENVIRONMENTAL IMPACT SCALE (ROUGH ESTIMATES)'));
output.push('');
const sessionsWithoutNCP = report.totalSessions * report.uniqueMCPs;
const computeReduction = sessionsWithoutNCP - report.totalSessions;
const estimatedEnergyKWh = computeReduction * 0.0002;
const estimatedCO2kg = estimatedEnergyKWh * 0.5;
// Visual representation of environmental savings
const envData = [
{ label: 'Energy Saved', value: estimatedEnergyKWh, unit: 'kWh', icon: 'β‘' },
{ label: 'COβ Avoided', value: estimatedCO2kg, unit: 'kg', icon: 'π' },
{ label: 'Connections Saved', value: computeReduction / 1000, unit: 'k', icon: 'π' }
];
const maxEnvValue = Math.max(...envData.map(e => e.value));
for (const env of envData) {
const bar = this.createGreenBar(env.value, maxEnvValue, 25);
output.push(`${env.icon} ${env.label.padEnd(18)}: ${bar} ${chalk.green(env.value.toFixed(1))} ${chalk.dim(env.unit)}`);
}
output.push('');
// Footer with enhanced tips
output.push(chalk.bold.white('π‘ INTERACTIVE COMMANDS'));
output.push('');
output.push(chalk.dim(' π ') + chalk.cyan('ncp analytics performance') + chalk.dim(' - Detailed performance metrics'));
output.push(chalk.dim(' π ') + chalk.cyan('ncp analytics export') + chalk.dim(' - Export data to CSV'));
output.push(chalk.dim(' π ') + chalk.cyan('ncp analytics overview --visual') + chalk.dim(' - Refresh this view'));
output.push('');
return output.join('\n');
}
/**
* Create horizontal progress bar with custom styling
*/
private static createHorizontalBar(value: number, max: number, width: number): string {
const percentage = max > 0 ? value / max : 0;
const filled = Math.round(percentage * width);
const empty = width - filled;
const filledChar = 'β';
const emptyChar = 'β';
return chalk.green(filledChar.repeat(filled)) + chalk.dim(emptyChar.repeat(empty));
}
/**
* Create colorful bar with gradient effect
*/
private static createColorfulBar(value: number, max: number, width: number): string {
const percentage = max > 0 ? value / max : 0;
const filled = Math.round(percentage * width);
const empty = width - filled;
// Create gradient effect based on value
let coloredBar = '';
for (let i = 0; i < filled; i++) {
const progress = i / width;
if (progress < 0.3) {
coloredBar += chalk.red('β');
} else if (progress < 0.6) {
coloredBar += chalk.yellow('β');
} else {
coloredBar += chalk.green('β');
}
}
return coloredBar + chalk.dim('β'.repeat(empty));
}
/**
* Create green-themed bar for environmental metrics
*/
private static createGreenBar(value: number, max: number, width: number): string {
const percentage = max > 0 ? value / max : 0;
const filled = Math.round(percentage * width);
const empty = width - filled;
const filledBar = chalk.bgGreen.black('β'.repeat(filled));
const emptyBar = chalk.dim('β'.repeat(empty));
return filledBar + emptyBar;
}
/**
* Format performance report with enhanced visuals
*/
static async formatVisualPerformance(report: AnalyticsReport): Promise<string> {
const output: string[] = [];
output.push('');
output.push(chalk.bold.cyan('β‘ NCP Performance Analytics (Visual)'));
output.push(chalk.dim('β'.repeat(50)));
output.push('');
// Performance Overview with Gauges
output.push(chalk.bold.white('π― PERFORMANCE GAUGES'));
output.push('');
const performanceMetrics = [
{ label: 'Success Rate', value: report?.successRate || 0, max: 100, unit: '%', color: chalk.green },
{ label: 'Avg Response', value: report?.avgSessionDuration || 5000, max: 10000, unit: 'ms', color: chalk.yellow },
{ label: 'MCPs Active', value: report?.uniqueMCPs || 0, max: 2000, unit: 'servers', color: chalk.cyan }
];
for (const metric of performanceMetrics) {
const gauge = this.createGauge(metric.value, metric.max);
output.push(`${metric.label.padEnd(15)}: ${gauge} ${metric.color((metric.value || 0).toFixed(1))}${metric.unit}`);
}
output.push('');
// Performance Leaderboard with Visual Ranking
if ((report?.performanceMetrics?.fastestMCPs || []).length > 0) {
output.push(chalk.bold.white('π SPEED CHAMPIONS PODIUM'));
output.push('');
const topPerformers = (report?.performanceMetrics?.fastestMCPs || []).slice(0, 5);
const medals = ['π₯', 'π₯', 'π₯', 'π
', 'ποΈ'];
for (let i = 0; i < topPerformers.length; i++) {
const mcp = topPerformers[i];
const medal = medals[i] || 'β';
const speedBar = this.createSpeedBar(mcp?.avgDuration || 0, 10000);
output.push(`${medal} ${chalk.cyan((mcp?.name || 'unknown').padEnd(20))} ${speedBar} ${chalk.bold.green((mcp?.avgDuration || 0).toFixed(0))}ms`);
}
output.push('');
}
// Reliability Champions
if ((report?.performanceMetrics?.mostReliable || []).length > 0) {
output.push(chalk.bold.white('π‘οΈ RELIABILITY CHAMPIONS'));
output.push('');
const reliablePerformers = (report?.performanceMetrics?.mostReliable || []).slice(0, 5);
for (let i = 0; i < reliablePerformers.length; i++) {
const mcp = reliablePerformers[i];
const reliabilityBar = this.createReliabilityBar(mcp?.successRate || 0);
const shield = (mcp?.successRate || 0) >= 99 ? 'π‘οΈ' : (mcp?.successRate || 0) >= 95 ? 'π°' : 'β‘';
output.push(`${shield} ${chalk.cyan((mcp?.name || 'unknown').padEnd(20))} ${reliabilityBar} ${chalk.bold.green((mcp?.successRate || 0).toFixed(1))}%`);
}
output.push('');
}
return output.join('\n');
}
/**
* Create gauge visualization
*/
private static createGauge(value: number, max: number): string {
const percentage = Math.min(value / max, 1);
const gaugeWidth = 20;
const filled = Math.round(percentage * gaugeWidth);
// Create gauge with different colors based on performance
let gauge = '[';
for (let i = 0; i < gaugeWidth; i++) {
if (i < filled) {
if (percentage > 0.8) gauge += chalk.green('β');
else if (percentage > 0.5) gauge += chalk.yellow('β');
else gauge += chalk.red('β');
} else {
gauge += chalk.dim('β');
}
}
gauge += ']';
return gauge;
}
/**
* Create speed bar (faster = more green)
*/
private static createSpeedBar(duration: number, maxDuration: number): string {
const speed = Math.max(0, 1 - (duration / maxDuration)); // Invert: faster = higher score
const barWidth = 15;
const filled = Math.round(speed * barWidth);
return chalk.green('β'.repeat(filled)) + chalk.dim('β'.repeat(barWidth - filled));
}
/**
* Create reliability bar
*/
private static createReliabilityBar(successRate: number): string {
const barWidth = 15;
const filled = Math.round((successRate / 100) * barWidth);
return chalk.blue('β'.repeat(filled)) + chalk.dim('β'.repeat(barWidth - filled));
}
/**
* Create simple ASCII line chart
*/
private static createLineChart(data: number[], height: number, width: number): string {
if (data.length === 0) return '';
const min = Math.min(...data);
const max = Math.max(...data);
const range = max - min || 1;
const lines: string[] = [];
// Create chart grid
for (let row = 0; row < height; row++) {
const threshold = max - (row / (height - 1)) * range;
let line = ' ';
for (let col = 0; col < Math.min(data.length, width); col++) {
const value = data[col];
const prevValue = col > 0 ? data[col - 1] : value;
// Determine character based on value relative to threshold
if (value >= threshold) {
// Different characters for trends
if (col > 0) {
if (value > prevValue) line += 'β±'; // Rising
else if (value < prevValue) line += 'β²'; // Falling
else line += 'β'; // Flat
} else {
line += 'β'; // Start point
}
} else {
line += ' '; // Empty space
}
}
lines.push(line);
}
// Add axis
const axis = ' ' + 'β'.repeat(Math.min(data.length, width));
lines.push(axis);
return lines.join('\n');
}
}