@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
949 lines • 38.1 kB
JavaScript
import { EventEmitter } from "events";
import { writeFile, readFile, mkdir, readdir } from "fs/promises";
import { join } from "path";
import { Logger } from "../core/logger.js";
import { ConfigManager } from "../core/config.js";
export class AnalyticsManager extends EventEmitter {
metrics = new Map();
dashboards = new Map();
insights = new Map();
models = new Map();
reports = new Map();
analyticsPath;
logger;
config;
configuration;
constructor(analyticsPath = "./analytics", logger, config) {
super();
this.analyticsPath = analyticsPath;
this.logger = logger || new Logger({ level: "info", format: "text", destination: "console" });
this.config = config || ConfigManager.getInstance();
this.configuration = this.getDefaultConfiguration();
}
async initialize() {
try {
await mkdir(this.analyticsPath, { recursive: true });
await mkdir(join(this.analyticsPath, "metrics"), { recursive: true });
await mkdir(join(this.analyticsPath, "dashboards"), { recursive: true });
await mkdir(join(this.analyticsPath, "insights"), { recursive: true });
await mkdir(join(this.analyticsPath, "models"), { recursive: true });
await mkdir(join(this.analyticsPath, "reports"), { recursive: true });
await this.loadConfigurations();
await this.initializeDefaultDashboards();
await this.startMetricsCollection();
this.logger.info("Analytics Manager initialized successfully");
}
catch (error) {
this.logger.error("Failed to initialize Analytics Manager", { error });
throw error;
}
}
async recordMetric(metric) {
const fullMetric = {
id: `metric-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date(),
...metric,
};
const key = `${metric.category}-${metric.name}`;
if (!this.metrics.has(key)) {
this.metrics.set(key, []);
}
const metricArray = this.metrics.get(key);
metricArray.push(fullMetric);
// Keep only recent metrics in memory (configurable retention)
const retentionPeriod = 24 * 60 * 60 * 1000; // 24 hours
const cutoff = Date.now() - retentionPeriod;
const filteredMetrics = metricArray.filter(m => m.timestamp.getTime() > cutoff);
this.metrics.set(key, filteredMetrics);
// Persist to disk for longer-term storage
await this.persistMetric(fullMetric);
this.emit("metric:recorded", fullMetric);
// Check for anomalies and generate insights
await this.checkForAnomalies(key, fullMetric);
}
async queryMetrics(query) {
const results = {};
for (const metricName of query.metrics) {
const key = metricName.includes("-") ? metricName : `*-${metricName}`;
const matchingKeys = Array.from(this.metrics.keys()).filter(k => key === "*" || k.includes(key.replace("*-", "")) || k === key);
let allMetrics = [];
for (const k of matchingKeys) {
const keyMetrics = this.metrics.get(k) || [];
allMetrics.push(...keyMetrics);
}
// Filter by time range
allMetrics = allMetrics.filter(m => m.timestamp >= query.timeRange.start &&
m.timestamp <= query.timeRange.end);
// Apply filters
if (query.filters) {
for (const [field, value] of Object.entries(query.filters)) {
allMetrics = allMetrics.filter(m => {
if (field === "tags") {
return Object.entries(value).every(([k, v]) => m.tags[k] === v);
}
return m[field] === value;
});
}
}
// Group by if specified
if (query.groupBy && query.groupBy.length > 0) {
const grouped = this.groupMetrics(allMetrics, query.groupBy);
for (const [group, metrics] of Object.entries(grouped)) {
const aggregated = this.aggregateMetrics(metrics, query.aggregation || "avg");
results[`${metricName}-${group}`] = aggregated;
}
}
else {
const aggregated = this.aggregateMetrics(allMetrics, query.aggregation || "avg");
results[metricName] = aggregated;
}
}
return results;
}
async createDashboard(dashboardData) {
const dashboard = {
id: `dashboard-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
name: dashboardData.name,
description: dashboardData.description,
type: dashboardData.type,
widgets: dashboardData.widgets.map((widget, index) => ({
id: `widget-${Date.now()}-${index}`,
...widget,
})),
layout: {
columns: 12,
rows: 8,
grid: true,
responsive: true,
},
permissions: {
viewers: [],
editors: [],
public: false,
...dashboardData.permissions,
},
schedule: {
autoRefresh: true,
refreshInterval: 30,
},
filters: [],
createdAt: new Date(),
updatedAt: new Date(),
createdBy: "system",
};
this.dashboards.set(dashboard.id, dashboard);
await this.saveDashboard(dashboard);
this.emit("dashboard:created", dashboard);
this.logger.info(`Dashboard created: ${dashboard.name} (${dashboard.id})`);
return dashboard;
}
async generateInsights(scope = {}) {
const insights = [];
// Default time range: last 24 hours
const timeRange = scope.timeRange || {
start: new Date(Date.now() - 24 * 60 * 60 * 1000),
end: new Date(),
};
// Anomaly detection
const anomalies = await this.detectAnomalies(timeRange, scope.metrics);
insights.push(...anomalies);
// Trend analysis
const trends = await this.analyzeTrends(timeRange, scope.metrics);
insights.push(...trends);
// Performance insights
const performance = await this.analyzePerformance(timeRange);
insights.push(...performance);
// Usage insights
const usage = await this.analyzeUsage(timeRange);
insights.push(...usage);
// Cost optimization insights
const costOptimizations = await this.analyzeCostOptimization(timeRange);
insights.push(...costOptimizations);
// Store insights
for (const insight of insights) {
this.insights.set(insight.id, insight);
await this.saveInsight(insight);
}
this.emit("insights:generated", { insights, scope });
this.logger.info(`Generated ${insights.length} insights`);
return insights;
}
async trainPredictiveModel(modelConfig) {
const model = {
id: `model-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
name: modelConfig.name,
description: modelConfig.description,
type: modelConfig.type,
algorithm: modelConfig.algorithm,
features: modelConfig.features,
target: modelConfig.target,
accuracy: 0,
confidence: 0,
trainedAt: new Date(),
trainingData: {
samples: 0,
features: modelConfig.features.length,
timeRange: modelConfig.trainingPeriod,
},
performance: {
precision: 0,
recall: 0,
f1Score: 0,
},
predictions: [],
status: "training",
};
try {
// Collect training data
const trainingData = await this.collectTrainingData(model);
// Train the model (simplified implementation)
const trained = await this.executeModelTraining(model, trainingData);
Object.assign(model, trained);
model.status = "ready";
this.models.set(model.id, model);
await this.saveModel(model);
this.emit("model:trained", model);
this.logger.info(`Predictive model trained: ${model.name} (${model.id}) - Accuracy: ${model.accuracy}%`);
}
catch (error) {
model.status = "error";
this.logger.error(`Model training failed: ${model.name}`, { error });
throw error;
}
return model;
}
async makePrediction(modelId, input) {
const model = this.models.get(modelId);
if (!model) {
throw new Error(`Model not found: ${modelId}`);
}
if (model.status !== "ready") {
throw new Error(`Model is not ready for predictions: ${model.status}`);
}
// Simple prediction logic (would be replaced with actual ML inference)
const prediction = await this.executePrediction(model, input);
const result = {
id: `prediction-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
modelId,
input,
prediction: prediction.value,
confidence: prediction.confidence,
timestamp: new Date(),
};
model.predictions.push(result);
model.lastPrediction = new Date();
await this.saveModel(model);
this.emit("prediction:made", { model, result });
this.logger.debug(`Prediction made: ${modelId} - ${JSON.stringify(result.prediction)}`);
return result;
}
async getPerformanceMetrics(timeRange) {
const range = timeRange || {
start: new Date(Date.now() - 60 * 60 * 1000), // Last hour
end: new Date(),
};
const systemMetrics = await this.queryMetrics({
metrics: ["cpu-usage", "memory-usage", "disk-usage", "network-io"],
timeRange: range,
aggregation: "avg",
});
const appMetrics = await this.queryMetrics({
metrics: ["response-time", "request-rate", "error-rate", "uptime"],
timeRange: range,
aggregation: "avg",
});
const dbMetrics = await this.queryMetrics({
metrics: ["db-connections", "query-time", "db-size"],
timeRange: range,
aggregation: "avg",
});
// Construct performance metrics (simplified)
return {
system: {
cpu: {
usage: this.getLatestValue(systemMetrics["cpu-usage"]) || 0,
cores: 8, // Would be detected from system
loadAverage: [1.2, 1.5, 1.8], // Would be collected from system
},
memory: {
used: this.getLatestValue(systemMetrics["memory-usage"]) || 0,
free: 4000000000, // Would be calculated
total: 8000000000,
usage: 50,
},
disk: {
used: this.getLatestValue(systemMetrics["disk-usage"]) || 0,
free: 100000000000,
total: 500000000000,
usage: 20,
iops: 1000,
},
network: {
bytesIn: 1000000,
bytesOut: 2000000,
packetsIn: 5000,
packetsOut: 6000,
errors: 5,
},
},
application: {
responseTime: {
avg: this.getLatestValue(appMetrics["response-time"]) || 0,
p50: 150,
p95: 500,
p99: 1000,
},
throughput: {
requestsPerSecond: this.getLatestValue(appMetrics["request-rate"]) || 0,
transactionsPerSecond: 50,
},
errors: {
rate: this.getLatestValue(appMetrics["error-rate"]) || 0,
count: 10,
types: { "500": 5, "404": 3, "400": 2 },
},
availability: {
uptime: this.getLatestValue(appMetrics["uptime"]) || 0,
sla: 99.9,
incidents: 2,
},
},
database: {
connections: {
active: 25,
idle: 75,
max: 100,
},
queries: {
avgExecutionTime: this.getLatestValue(dbMetrics["query-time"]) || 0,
slowQueries: 5,
deadlocks: 0,
},
storage: {
size: this.getLatestValue(dbMetrics["db-size"]) || 0,
growth: 1000000, // bytes per day
fragmentation: 5,
},
},
infrastructure: {
containers: {
running: 12,
stopped: 2,
restarts: 3,
},
services: {
healthy: 15,
unhealthy: 1,
degraded: 0,
},
},
};
}
async getUsageMetrics(timeRange) {
const range = timeRange || {
start: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
end: new Date(),
};
const usageData = await this.queryMetrics({
metrics: ["active-users", "sessions", "api-calls", "feature-usage"],
timeRange: range,
aggregation: "sum",
});
return {
users: {
total: 10000,
active: this.getLatestValue(usageData["active-users"]) || 0,
new: 50,
returning: 1500,
churn: 25,
},
sessions: {
total: this.getLatestValue(usageData["sessions"]) || 0,
duration: {
avg: 15 * 60, // 15 minutes
median: 12 * 60,
},
bounceRate: 25,
pagesPerSession: 4.5,
},
features: {
adoption: {
"dashboard": { users: 800, usage: 5000, retention: 85 },
"reports": { users: 600, usage: 2000, retention: 70 },
"analytics": { users: 400, usage: 1500, retention: 60 },
},
mostUsed: ["dashboard", "reports", "search"],
leastUsed: ["advanced-filters", "export", "integrations"],
},
api: {
calls: this.getLatestValue(usageData["api-calls"]) || 0,
uniqueConsumers: 150,
avgResponseTime: 250,
errorRate: 2.5,
rateLimits: {
hit: 5,
consumed: 75,
},
},
content: {
created: 100,
modified: 250,
deleted: 25,
views: 5000,
},
};
}
async getBusinessMetrics(timeRange) {
// This would integrate with business systems (CRM, billing, etc.)
return {
revenue: {
total: 1000000,
recurring: 800000,
growth: 15,
arpu: 100,
ltv: 2400,
},
customers: {
total: 500,
new: 25,
retained: 450,
churned: 10,
satisfaction: 4.2,
},
conversion: {
leads: 1000,
qualified: 400,
opportunities: 200,
closed: 50,
rate: 5,
},
support: {
tickets: 150,
resolved: 140,
avgResolutionTime: 4 * 60 * 60, // 4 hours
satisfaction: 4.5,
},
};
}
// Private helper methods
getDefaultConfiguration() {
return {
collection: {
enabled: true,
samplingRate: 1.0,
batchSize: 1000,
flushInterval: 60000,
},
storage: {
retention: {
raw: "7d",
aggregated: "90d",
summary: "1y",
},
compression: true,
encryption: false,
},
processing: {
realTime: true,
batchProcessing: true,
aggregationIntervals: ["1m", "5m", "1h", "1d"],
},
alerts: {
enabled: true,
channels: ["email", "slack"],
escalation: {
levels: 3,
intervals: [5, 15, 30], // minutes
},
},
privacy: {
anonymization: true,
gdprCompliant: true,
dataMinimization: true,
},
integrations: {},
};
}
async loadConfigurations() {
try {
// Load dashboards
const dashboardFiles = await readdir(join(this.analyticsPath, "dashboards"));
for (const file of dashboardFiles.filter(f => f.endsWith(".json"))) {
const content = await readFile(join(this.analyticsPath, "dashboards", file), "utf-8");
const dashboard = JSON.parse(content);
this.dashboards.set(dashboard.id, dashboard);
}
// Load insights
const insightFiles = await readdir(join(this.analyticsPath, "insights"));
for (const file of insightFiles.filter(f => f.endsWith(".json"))) {
const content = await readFile(join(this.analyticsPath, "insights", file), "utf-8");
const insight = JSON.parse(content);
this.insights.set(insight.id, insight);
}
// Load models
const modelFiles = await readdir(join(this.analyticsPath, "models"));
for (const file of modelFiles.filter(f => f.endsWith(".json"))) {
const content = await readFile(join(this.analyticsPath, "models", file), "utf-8");
const model = JSON.parse(content);
this.models.set(model.id, model);
}
this.logger.info(`Loaded ${this.dashboards.size} dashboards, ${this.insights.size} insights, ${this.models.size} models`);
}
catch (error) {
this.logger.warn("Failed to load some analytics configurations", { error });
}
}
async initializeDefaultDashboards() {
const defaultDashboards = [
{
name: "System Performance",
description: "Real-time system performance metrics",
type: "operational",
widgets: [
{
title: "CPU Usage",
type: "gauge",
size: "medium",
position: { x: 0, y: 0, width: 6, height: 3 },
dataSource: {
query: "cpu-usage",
metrics: ["cpu-usage"],
aggregation: "avg",
timeRange: "1h",
groupBy: [],
},
visualization: {
chartType: "pie",
options: { max: 100, unit: "%" },
thresholds: { warning: 70, critical: 90 },
},
alerts: { enabled: true, conditions: [] },
},
{
title: "Memory Usage",
type: "gauge",
size: "medium",
position: { x: 6, y: 0, width: 6, height: 3 },
dataSource: {
query: "memory-usage",
metrics: ["memory-usage"],
aggregation: "avg",
timeRange: "1h",
groupBy: [],
},
visualization: {
chartType: "pie",
options: { max: 100, unit: "%" },
thresholds: { warning: 80, critical: 95 },
},
alerts: { enabled: true, conditions: [] },
},
{
title: "Response Time",
type: "chart",
size: "large",
position: { x: 0, y: 3, width: 12, height: 4 },
dataSource: {
query: "response-time",
metrics: ["response-time"],
aggregation: "avg",
timeRange: "24h",
groupBy: ["service"],
},
visualization: {
chartType: "line",
options: { unit: "ms" },
},
alerts: { enabled: false, conditions: [] },
},
],
},
{
name: "Business KPIs",
description: "Key business performance indicators",
type: "executive",
widgets: [
{
title: "Active Users",
type: "metric",
size: "small",
position: { x: 0, y: 0, width: 3, height: 2 },
dataSource: {
query: "active-users",
metrics: ["active-users"],
aggregation: "count",
timeRange: "24h",
groupBy: [],
},
visualization: {
options: { unit: "users" },
},
alerts: { enabled: false, conditions: [] },
},
],
},
];
for (const dashboardData of defaultDashboards) {
if (!Array.from(this.dashboards.values()).some(d => d.name === dashboardData.name)) {
await this.createDashboard(dashboardData);
}
}
}
async startMetricsCollection() {
// Start collecting system metrics
setInterval(async () => {
await this.collectSystemMetrics();
}, 60000); // Every minute
// Start collecting application metrics
setInterval(async () => {
await this.collectApplicationMetrics();
}, 30000); // Every 30 seconds
this.logger.info("Started automatic metrics collection");
}
async collectSystemMetrics() {
try {
// Mock system metrics collection
await this.recordMetric({
name: "cpu-usage",
description: "CPU usage percentage",
type: "gauge",
category: "performance",
unit: "percent",
value: Math.random() * 100,
tags: { host: "localhost", service: "system" },
source: "system-monitor",
metadata: {},
});
await this.recordMetric({
name: "memory-usage",
description: "Memory usage percentage",
type: "gauge",
category: "performance",
unit: "percent",
value: Math.random() * 100,
tags: { host: "localhost", service: "system" },
source: "system-monitor",
metadata: {},
});
await this.recordMetric({
name: "disk-usage",
description: "Disk usage percentage",
type: "gauge",
category: "performance",
unit: "percent",
value: Math.random() * 100,
tags: { host: "localhost", service: "system" },
source: "system-monitor",
metadata: {},
});
}
catch (error) {
this.logger.error("Failed to collect system metrics", { error });
}
}
async collectApplicationMetrics() {
try {
// Mock application metrics collection
await this.recordMetric({
name: "response-time",
description: "Average response time",
type: "gauge",
category: "performance",
unit: "milliseconds",
value: Math.random() * 1000 + 100,
tags: { service: "api", endpoint: "/users" },
source: "application",
metadata: {},
});
await this.recordMetric({
name: "request-rate",
description: "Requests per second",
type: "counter",
category: "usage",
unit: "requests/sec",
value: Math.random() * 100 + 10,
tags: { service: "api" },
source: "application",
metadata: {},
});
await this.recordMetric({
name: "error-rate",
description: "Error rate percentage",
type: "gauge",
category: "performance",
unit: "percent",
value: Math.random() * 5,
tags: { service: "api" },
source: "application",
metadata: {},
});
}
catch (error) {
this.logger.error("Failed to collect application metrics", { error });
}
}
async persistMetric(metric) {
const date = metric.timestamp.toISOString().split("T")[0];
const filePath = join(this.analyticsPath, "metrics", `${date}.json`);
try {
let existingData = [];
try {
const content = await readFile(filePath, "utf-8");
existingData = JSON.parse(content);
}
catch {
// File doesn't exist yet
}
existingData.push(metric);
await writeFile(filePath, JSON.stringify(existingData, null, 2));
}
catch (error) {
this.logger.error("Failed to persist metric", { error, metric: metric.id });
}
}
async checkForAnomalies(metricKey, metric) {
const historical = this.metrics.get(metricKey) || [];
if (historical.length < 10)
return; // Need enough data for baseline
const recent = historical.slice(-10);
const average = recent.reduce((sum, m) => sum + m.value, 0) / recent.length;
const stdDev = Math.sqrt(recent.reduce((sum, m) => sum + Math.pow(m.value - average, 2), 0) / recent.length);
const threshold = 2; // 2 standard deviations
const deviation = Math.abs(metric.value - average) / stdDev;
if (deviation > threshold) {
const insight = {
id: `insight-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
title: `Anomaly detected in ${metric.name}`,
description: `The metric ${metric.name} has deviated significantly from its normal pattern`,
type: "anomaly",
category: metric.category === "business" || metric.category === "technical" ? "performance" : metric.category,
confidence: Math.min(95, deviation * 20),
impact: deviation > 3 ? "high" : "medium",
priority: deviation > 3 ? "high" : "medium",
data: {
metrics: [metric.name],
timeRange: { start: recent[0].timestamp, end: metric.timestamp },
values: { current: metric.value, average, stdDev },
baseline: { average, stdDev },
deviation,
},
recommendations: [
{
action: "Investigate the cause of the anomaly",
effort: "medium",
impact: "Identify potential issues before they become critical",
implementation: [
"Check recent deployments or configuration changes",
"Review system logs for errors or warnings",
"Monitor related metrics for correlation",
],
},
],
status: "new",
createdAt: new Date(),
updatedAt: new Date(),
};
this.insights.set(insight.id, insight);
await this.saveInsight(insight);
this.emit("anomaly:detected", { metric, insight, deviation });
this.logger.warn(`Anomaly detected in ${metric.name}`, {
current: metric.value,
average,
deviation,
});
}
}
async detectAnomalies(timeRange, metrics) {
// Simplified anomaly detection
return [];
}
async analyzeTrends(timeRange, metrics) {
// Simplified trend analysis
return [];
}
async analyzePerformance(timeRange) {
const insights = [];
// Check response time trends
const responseTimeData = await this.queryMetrics({
metrics: ["response-time"],
timeRange,
aggregation: "avg",
});
if (responseTimeData["response-time"]?.length > 0) {
const values = responseTimeData["response-time"].map((d) => d.value);
const recent = values.slice(-5);
const earlier = values.slice(0, -5);
if (recent.length > 0 && earlier.length > 0) {
const recentAvg = recent.reduce((sum, v) => sum + v, 0) / recent.length;
const earlierAvg = earlier.reduce((sum, v) => sum + v, 0) / earlier.length;
const change = ((recentAvg - earlierAvg) / earlierAvg) * 100;
if (Math.abs(change) > 20) {
insights.push({
id: `perf-insight-${Date.now()}`,
title: `Response time ${change > 0 ? "increased" : "decreased"} by ${Math.abs(change).toFixed(1)}%`,
description: "Response time has changed significantly in the recent period",
type: "trend",
category: "performance",
confidence: 80,
impact: Math.abs(change) > 50 ? "high" : "medium",
priority: Math.abs(change) > 50 ? "high" : "medium",
data: {
metrics: ["response-time"],
timeRange,
values: { recent: recentAvg, earlier: earlierAvg, change },
},
recommendations: change > 0 ? [
{
action: "Investigate performance degradation",
effort: "medium",
impact: "Restore optimal response times",
implementation: [
"Check for increased load or traffic",
"Review recent code deployments",
"Analyze database query performance",
"Monitor resource utilization",
],
},
] : [
{
action: "Document performance improvement",
effort: "low",
impact: "Understand what caused the improvement",
implementation: [
"Identify recent optimizations",
"Document best practices",
"Monitor sustainability",
],
},
],
status: "new",
createdAt: new Date(),
updatedAt: new Date(),
});
}
}
}
return insights;
}
async analyzeUsage(timeRange) {
// Simplified usage analysis
return [];
}
async analyzeCostOptimization(timeRange) {
// Simplified cost optimization analysis
return [];
}
async collectTrainingData(model) {
// Collect historical data for training
const data = await this.queryMetrics({
metrics: model.features,
timeRange: model.trainingData.timeRange,
aggregation: "avg",
});
// Transform data for ML training (simplified)
return Object.values(data).flat();
}
async executeModelTraining(model, data) {
// Simplified model training
return {
accuracy: 85 + Math.random() * 10,
confidence: 80 + Math.random() * 15,
performance: {
precision: 0.85,
recall: 0.82,
f1Score: 0.83,
},
trainingData: {
...model.trainingData,
samples: data.length,
},
};
}
async executePrediction(model, input) {
// Simplified prediction logic
const value = Math.random() * 100;
const confidence = 70 + Math.random() * 25;
return { value, confidence };
}
groupMetrics(metrics, groupBy) {
const groups = {};
for (const metric of metrics) {
const key = groupBy.map(field => {
if (field === "tags") {
return Object.entries(metric.tags).map(([k, v]) => `${k}:${v}`).join(",");
}
return metric[field] || "unknown";
}).join("-");
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(metric);
}
return groups;
}
aggregateMetrics(metrics, aggregation) {
if (metrics.length === 0)
return [];
// Group by time buckets for time series aggregation
const buckets = {};
for (const metric of metrics) {
const bucket = new Date(Math.floor(metric.timestamp.getTime() / 60000) * 60000).toISOString();
if (!buckets[bucket]) {
buckets[bucket] = [];
}
buckets[bucket].push(metric);
}
return Object.entries(buckets).map(([timestamp, bucketMetrics]) => {
const values = bucketMetrics.map(m => m.value);
let aggregatedValue;
switch (aggregation) {
case "sum":
aggregatedValue = values.reduce((sum, v) => sum + v, 0);
break;
case "min":
aggregatedValue = Math.min(...values);
break;
case "max":
aggregatedValue = Math.max(...values);
break;
case "count":
aggregatedValue = values.length;
break;
case "p95":
values.sort((a, b) => a - b);
aggregatedValue = values[Math.floor(values.length * 0.95)];
break;
case "p99":
values.sort((a, b) => a - b);
aggregatedValue = values[Math.floor(values.length * 0.99)];
break;
case "avg":
default:
aggregatedValue = values.reduce((sum, v) => sum + v, 0) / values.length;
}
return {
timestamp: new Date(timestamp),
value: aggregatedValue,
count: values.length,
};
}).sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
}
getLatestValue(dataPoints) {
if (!dataPoints || dataPoints.length === 0)
return 0;
return dataPoints[dataPoints.length - 1]?.value || 0;
}
async saveDashboard(dashboard) {
const filePath = join(this.analyticsPath, "dashboards", `${dashboard.id}.json`);
await writeFile(filePath, JSON.stringify(dashboard, null, 2));
}
async saveInsight(insight) {
const filePath = join(this.analyticsPath, "insights", `${insight.id}.json`);
await writeFile(filePath, JSON.stringify(insight, null, 2));
}
async saveModel(model) {
const filePath = join(this.analyticsPath, "models", `${model.id}.json`);
await writeFile(filePath, JSON.stringify(model, null, 2));
}
}
//# sourceMappingURL=analytics-manager.js.map