← back to Daily Reporting
slack-formatter.ts
296 lines
/**
* Slack Message Formatter for Daily Reports
*
* Formats daily reports into clean Slack markdown with charts and metrics
*/
import type { DailyReport } from './report-generator';
/**
* Generate a health bar visualization
*/
function generateHealthBar(percentage: number, width: number = 20): string {
const filled = Math.round((percentage / 100) * width);
const empty = width - filled;
const filledChar = percentage >= 80 ? '🟢' : percentage >= 60 ? '🟡' : '🔴';
const emptyChar = '⚪';
return filledChar.repeat(Math.max(0, filled)) + emptyChar.repeat(Math.max(0, empty)) + ` ${percentage}%`;
}
/**
* Format severity emoji
*/
function getSeverityEmoji(severity: string): string {
switch (severity) {
case 'critical': return '🚨';
case 'high': return '⚠️';
case 'medium': return '⚡';
case 'low': return 'ℹ️';
default: return '•';
}
}
/**
* Format status emoji
*/
function getStatusEmoji(status: string): string {
switch (status) {
case 'online': return '✅';
case 'offline': return '❌';
case 'degraded': return '⚠️';
default: return '❓';
}
}
/**
* Format report header
*/
function formatHeader(report: DailyReport): string {
const emoji = report.reportType === 'morning' ? '☀️' : '🌙';
const title = report.reportType === 'morning' ? 'MORNING STANDUP REPORT' : 'END-OF-DAY SUMMARY';
const date = new Date(report.timestamp).toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
return `${emoji} *${title}*\n${date}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
}
/**
* Format executive summary
*/
function formatExecutiveSummary(summary: DailyReport['executiveSummary']): string {
let text = '*📊 EXECUTIVE SUMMARY*\n\n';
text += `*System Health:* ${generateHealthBar(summary.systemHealth, 15)}\n`;
text += `*Agents Online:* ${summary.agentsOnline}/${summary.totalAgents}\n`;
text += `*Tasks Completed Today:* ${summary.tasksCompletedToday}\n`;
text += `*Critical Issues:* ${summary.criticalIssues === 0 ? '✅ None' : `🚨 ${summary.criticalIssues}`}\n\n`;
if (summary.topAchievements.length > 0) {
text += '*🏆 Top Achievements:*\n';
summary.topAchievements.forEach((achievement, index) => {
text += ` ${index + 1}. ${achievement}\n`;
});
}
return text + '\n';
}
/**
* Format department reports
*/
function formatDepartmentReports(reports: DailyReport['departmentReports']): string {
let text = '*🏢 DEPARTMENT REPORTS*\n\n';
// Group by executive, department heads, and support
const sortedReports = [...reports].sort((a, b) => {
const order: Record<string, number> = {
'Executive': 0,
'Legal': 1,
'Finance': 2,
'Marketing': 3,
'Operations': 4,
'Strategy': 5,
'Customer Service': 6,
'Sales': 7,
'IT Operations': 8,
'Product': 9
};
return (order[a.department] || 99) - (order[b.department] || 99);
});
for (const dept of sortedReports) {
const healthPercentage = Math.round((dept.onlineCount / dept.agentsCount) * 100);
text += `*${dept.department}* (${dept.onlineCount}/${dept.agentsCount} online)\n`;
text += `${generateHealthBar(healthPercentage, 10)}\n`;
text += `Tasks Completed: ${dept.totalTasks}\n`;
// Show agent details
dept.metrics.forEach(metric => {
text += ` ${getStatusEmoji(metric.status)} _${metric.agentName}_`;
if (metric.status === 'online') {
text += ` • ${metric.tasksCompleted} tasks`;
if (metric.errorsToday > 0) {
text += ` • ⚠️ ${metric.errorsToday} errors`;
}
}
text += '\n';
});
// Highlights
if (dept.highlights.length > 0) {
text += ' ✨ *Highlights:*\n';
dept.highlights.forEach(h => text += ` • ${h}\n`);
}
// Issues
if (dept.issues.length > 0) {
text += ' ⚠️ *Issues:*\n';
dept.issues.forEach(i => text += ` • ${i}\n`);
}
text += '\n';
}
return text;
}
/**
* Format issues requiring attention
*/
function formatIssues(issues: DailyReport['issuesRequiringAttention']): string {
if (issues.length === 0) {
return '*🎉 ISSUES REQUIRING ATTENTION*\n\n✅ *No critical issues detected!* All systems operational.\n\n';
}
let text = '*🚨 ISSUES REQUIRING ATTENTION*\n\n';
issues.forEach((issue, index) => {
text += `${getSeverityEmoji(issue.severity)} *[${issue.severity.toUpperCase()}]* ${issue.agent}\n`;
text += ` _Issue:_ ${issue.issue}\n`;
text += ` _Action:_ ${issue.actionRequired}\n\n`;
});
return text;
}
/**
* Format action items
*/
function formatActionItems(actionItems: string[]): string {
if (actionItems.length === 0) {
return '*✅ ACTION ITEMS*\n\nNo pending action items.\n\n';
}
let text = '*✅ ACTION ITEMS*\n\n';
actionItems.forEach((item, index) => {
text += `${index + 1}. ${item}\n`;
});
return text + '\n';
}
/**
* Format Q&A readiness status
*/
function formatQnaStatus(qnaStatus: DailyReport['qnaStatus']): string {
let text = '*💬 Q&A READINESS STATUS*\n\n';
text += `*Overall Readiness:* ${generateHealthBar(qnaStatus.overallReadiness, 15)}\n\n`;
text += '*Agent Readiness Scores:*\n';
qnaStatus.agentReadiness.slice(0, 10).forEach(agent => {
const emoji = agent.score >= 80 ? '🟢' : agent.score >= 60 ? '🟡' : '🔴';
text += `${emoji} ${agent.agent}: ${agent.score}%\n`;
});
return text + '\n';
}
/**
* Format footer
*/
function formatFooter(report: DailyReport): string {
const nextReport = report.reportType === 'morning'
? 'End-of-Day Summary at 5:00 PM'
: 'Morning Standup at 6:00 AM';
return `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n` +
`📅 Next Report: ${nextReport}\n` +
`🔗 Master Hub: http://45.61.58.125:9893\n` +
`Generated at ${new Date(report.timestamp).toLocaleTimeString()}`;
}
/**
* Format complete report for Slack
*/
export function formatSlackReport(report: DailyReport, mentionChannel: boolean = false): string {
let message = '';
// Mention channel if there are critical issues
if (mentionChannel && report.executiveSummary.criticalIssues > 0) {
message += '<!channel> *Attention Required* - Critical issues detected!\n\n';
}
message += formatHeader(report);
message += formatExecutiveSummary(report.executiveSummary);
message += formatDepartmentReports(report.departmentReports);
message += formatIssues(report.issuesRequiringAttention);
message += formatActionItems(report.actionItems);
message += formatQnaStatus(report.qnaStatus);
message += formatFooter(report);
return message;
}
/**
* Format report as JSON for Slack blocks (advanced formatting)
*/
export function formatSlackBlocks(report: DailyReport): any[] {
const blocks: any[] = [];
// Header
blocks.push({
type: 'header',
text: {
type: 'plain_text',
text: report.reportType === 'morning' ? '☀️ Morning Standup Report' : '🌙 End-of-Day Summary',
emoji: true
}
});
blocks.push({ type: 'divider' });
// Executive Summary
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: `*📊 EXECUTIVE SUMMARY*\n\n` +
`*System Health:* ${report.executiveSummary.systemHealth}%\n` +
`*Agents Online:* ${report.executiveSummary.agentsOnline}/${report.executiveSummary.totalAgents}\n` +
`*Tasks Completed:* ${report.executiveSummary.tasksCompletedToday}\n` +
`*Critical Issues:* ${report.executiveSummary.criticalIssues}`
}
});
// Critical issues alert
if (report.issuesRequiringAttention.length > 0) {
blocks.push({
type: 'section',
text: {
type: 'mrkdwn',
text: `*🚨 ${report.issuesRequiringAttention.length} Issues Require Attention*`
}
});
}
blocks.push({ type: 'divider' });
// Action button
blocks.push({
type: 'actions',
elements: [
{
type: 'button',
text: {
type: 'plain_text',
text: '📊 View Full Dashboard',
emoji: true
},
url: 'http://45.61.58.125:9893',
style: 'primary'
}
]
});
return blocks;
}