← back to Daily Reporting

report-generator.ts

336 lines

/**
 * Daily Report Generator for DW-Agents
 *
 * Generates comprehensive morning and end-of-day reports
 */

import fetch from 'node-fetch';
import { AGENT_HIERARCHY, getAgentsByDepartment, getAgentsByLevel, type Agent } from './agent-hierarchy';

export interface AgentMetrics {
  agentId: string;
  agentName: string;
  status: 'online' | 'offline' | 'degraded';
  uptime: number;
  responseTime: number;
  tasksCompleted: number;
  errorsToday: number;
  customMetrics: Record<string, any>;
  lastActivity: string;
  qnaReadiness: number; // 0-100 score
}

export interface DepartmentReport {
  department: string;
  agentsCount: number;
  onlineCount: number;
  totalTasks: number;
  issues: string[];
  highlights: string[];
  metrics: AgentMetrics[];
}

export interface DailyReport {
  reportType: 'morning' | 'eod';
  timestamp: string;
  executiveSummary: {
    systemHealth: number;
    agentsOnline: number;
    totalAgents: number;
    criticalIssues: number;
    tasksCompletedToday: number;
    topAchievements: string[];
  };
  departmentReports: DepartmentReport[];
  issuesRequiringAttention: Array<{
    severity: 'critical' | 'high' | 'medium' | 'low';
    agent: string;
    issue: string;
    actionRequired: string;
  }>;
  actionItems: string[];
  qnaStatus: {
    overallReadiness: number;
    agentReadiness: Array<{ agent: string; score: number; recentActivities: string[] }>;
  };
}

/**
 * Fetch metrics from an agent
 */
async function fetchAgentMetrics(agent: Agent): Promise<AgentMetrics> {
  try {
    const response = await fetch(`${agent.url}/api/metrics`, {
      timeout: 5000,
      headers: { 'Accept': 'application/json' }
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    const data = await response.json() as any;

    return {
      agentId: agent.id,
      agentName: agent.name,
      status: 'online',
      uptime: data.uptime || 0,
      responseTime: data.responseTime || 0,
      tasksCompleted: data.tasksCompleted || 0,
      errorsToday: data.errorsToday || 0,
      customMetrics: data.customMetrics || {},
      lastActivity: data.lastActivity || new Date().toISOString(),
      qnaReadiness: data.qnaReadiness || 75
    };
  } catch (error) {
    console.error(`Failed to fetch metrics from ${agent.name}:`, error);

    // Return offline status with default values
    return {
      agentId: agent.id,
      agentName: agent.name,
      status: 'offline',
      uptime: 0,
      responseTime: 0,
      tasksCompleted: 0,
      errorsToday: 0,
      customMetrics: {},
      lastActivity: 'N/A',
      qnaReadiness: 0
    };
  }
}

/**
 * Fetch metrics from all agents concurrently
 */
async function fetchAllAgentMetrics(): Promise<Map<string, AgentMetrics>> {
  const metricsMap = new Map<string, AgentMetrics>();

  const promises = AGENT_HIERARCHY.map(agent =>
    fetchAgentMetrics(agent).then(metrics => {
      metricsMap.set(agent.id, metrics);
    })
  );

  await Promise.allSettled(promises);

  return metricsMap;
}

/**
 * Generate department reports
 */
function generateDepartmentReports(metricsMap: Map<string, AgentMetrics>): DepartmentReport[] {
  const departments = getAgentsByDepartment();
  const reports: DepartmentReport[] = [];

  for (const [department, agents] of departments) {
    const metrics = agents
      .map(agent => metricsMap.get(agent.id))
      .filter(m => m !== undefined) as AgentMetrics[];

    const onlineCount = metrics.filter(m => m.status === 'online').length;
    const totalTasks = metrics.reduce((sum, m) => sum + m.tasksCompleted, 0);

    const issues: string[] = [];
    const highlights: string[] = [];

    metrics.forEach(m => {
      if (m.status === 'offline') {
        issues.push(`${m.agentName} is offline`);
      } else if (m.errorsToday > 10) {
        issues.push(`${m.agentName} has ${m.errorsToday} errors today`);
      }

      if (m.tasksCompleted > 50) {
        highlights.push(`${m.agentName} completed ${m.tasksCompleted} tasks`);
      }
    });

    reports.push({
      department,
      agentsCount: agents.length,
      onlineCount,
      totalTasks,
      issues,
      highlights,
      metrics
    });
  }

  return reports;
}

/**
 * Generate executive summary
 */
function generateExecutiveSummary(
  metricsMap: Map<string, AgentMetrics>,
  departmentReports: DepartmentReport[]
): DailyReport['executiveSummary'] {
  const allMetrics = Array.from(metricsMap.values());
  const onlineCount = allMetrics.filter(m => m.status === 'online').length;
  const totalTasks = allMetrics.reduce((sum, m) => sum + m.tasksCompleted, 0);

  const criticalIssues = departmentReports.reduce((sum, dept) => {
    return sum + dept.issues.filter(i => i.includes('offline') || i.includes('critical')).length;
  }, 0);

  const topAchievements: string[] = [];
  departmentReports.forEach(dept => {
    if (dept.highlights.length > 0) {
      topAchievements.push(...dept.highlights.slice(0, 2));
    }
  });

  const systemHealth = Math.round((onlineCount / allMetrics.length) * 100);

  return {
    systemHealth,
    agentsOnline: onlineCount,
    totalAgents: allMetrics.length,
    criticalIssues,
    tasksCompletedToday: totalTasks,
    topAchievements: topAchievements.slice(0, 5)
  };
}

/**
 * Identify issues requiring attention
 */
function identifyIssues(
  metricsMap: Map<string, AgentMetrics>,
  departmentReports: DepartmentReport[]
): DailyReport['issuesRequiringAttention'] {
  const issues: DailyReport['issuesRequiringAttention'] = [];

  for (const [agentId, metrics] of metricsMap) {
    if (metrics.status === 'offline') {
      issues.push({
        severity: 'critical',
        agent: metrics.agentName,
        issue: 'Agent is offline',
        actionRequired: 'Restart agent and investigate cause'
      });
    }

    if (metrics.errorsToday > 20) {
      issues.push({
        severity: 'high',
        agent: metrics.agentName,
        issue: `${metrics.errorsToday} errors detected today`,
        actionRequired: 'Review error logs and fix underlying issues'
      });
    }

    if (metrics.responseTime > 2000) {
      issues.push({
        severity: 'medium',
        agent: metrics.agentName,
        issue: `Slow response time (${metrics.responseTime}ms)`,
        actionRequired: 'Optimize performance or scale resources'
      });
    }

    if (metrics.qnaReadiness < 50) {
      issues.push({
        severity: 'medium',
        agent: metrics.agentName,
        issue: 'Low Q&A readiness score',
        actionRequired: 'Update agent knowledge base and test responses'
      });
    }
  }

  return issues.sort((a, b) => {
    const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
    return severityOrder[a.severity] - severityOrder[b.severity];
  });
}

/**
 * Generate Q&A readiness status
 */
function generateQnaStatus(metricsMap: Map<string, AgentMetrics>): DailyReport['qnaStatus'] {
  const allMetrics = Array.from(metricsMap.values());
  const avgReadiness = allMetrics.reduce((sum, m) => sum + m.qnaReadiness, 0) / allMetrics.length;

  const agentReadiness = allMetrics
    .map(m => ({
      agent: m.agentName,
      score: m.qnaReadiness,
      recentActivities: [
        `${m.tasksCompleted} tasks completed`,
        `Last active: ${m.lastActivity}`,
        `Status: ${m.status}`
      ]
    }))
    .sort((a, b) => b.score - a.score);

  return {
    overallReadiness: Math.round(avgReadiness),
    agentReadiness
  };
}

/**
 * Generate action items
 */
function generateActionItems(
  departmentReports: DepartmentReport[],
  issues: DailyReport['issuesRequiringAttention']
): string[] {
  const actionItems: string[] = [];

  // Critical actions from issues
  issues.slice(0, 5).forEach(issue => {
    actionItems.push(`[${issue.severity.toUpperCase()}] ${issue.agent}: ${issue.actionRequired}`);
  });

  // Department-specific actions
  departmentReports.forEach(dept => {
    if (dept.onlineCount < dept.agentsCount) {
      actionItems.push(`${dept.department}: Bring ${dept.agentsCount - dept.onlineCount} offline agents back online`);
    }
  });

  return actionItems;
}

/**
 * Generate complete daily report
 */
export async function generateDailyReport(reportType: 'morning' | 'eod'): Promise<DailyReport> {
  console.log(`Generating ${reportType} report...`);

  // Fetch all metrics
  const metricsMap = await fetchAllAgentMetrics();

  // Generate department reports
  const departmentReports = generateDepartmentReports(metricsMap);

  // Generate executive summary
  const executiveSummary = generateExecutiveSummary(metricsMap, departmentReports);

  // Identify issues
  const issuesRequiringAttention = identifyIssues(metricsMap, departmentReports);

  // Generate Q&A status
  const qnaStatus = generateQnaStatus(metricsMap);

  // Generate action items
  const actionItems = generateActionItems(departmentReports, issuesRequiringAttention);

  return {
    reportType,
    timestamp: new Date().toISOString(),
    executiveSummary,
    departmentReports,
    issuesRequiringAttention,
    actionItems,
    qnaStatus
  };
}