mcpField
R
Report Builder

Report Builder

github.com/jmsktm文档处理10228 次安装40 周前更新
代码仓库

关于 Report Builder

Report Builder 是一项面向业务团队的报告生成技能,可整理多来源数据并制作含分析、图表和摘要的报告。支持多种业务报告类型及 PDF、Word、HTML 等格式导出。

主要功能

  1. 01生成业务与分析报告
  2. 02整合多来源数据
  3. 03制作图表、表格和趋势分析
  4. 04生成执行摘要与建议
  5. 05导出 PDF、Word 或 HTML
  6. 06安排定期报告生成

适用场景

  1. 01制作月度业绩报告
  2. 02汇总项目进度、里程碑和风险
  3. 03编制财务分析报告
  4. 04自动生成并分发定期报告

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jmsktm/claude-settings --skill report-builder

安装后在 Claude Code 中输入/report-builder确认已加载。

详细介绍

报告构建器

报告构建器技能可自动创建专业的业务报告,包含数据分析、可视化内容、执行摘要和格式化章节。它支持多种报告类型,包括财务报告、项目状态更新、市场分析、绩效评估和分析报告。该技能可整合来自多个来源的数据,并以清晰、可执行的形式呈现。

生成包含图表、表格、趋势分析和关键见解的 PDF、Word 或 HTML 格式报告。非常适用于定期报告、自动化仪表板和数据驱动的决策文档。

核心工作流

工作流 1:生成执行报告

目的: 创建包含摘要、关键指标和建议的高层级报告

步骤:

  1. 从各种来源收集数据
  2. 计算关键绩效指标(KPI)
  3. 生成突出关键要点的执行摘要
  4. 创建数据可视化内容(图表、示意图)
  5. 添加包含支持数据的详细章节
  6. 纳入建议和行动事项
  7. 采用专业版式进行排版
  8. 添加品牌标识并导出为 PDF

实现:

const PDFDocument = require('pdfkit');
const ChartJS = require('chartjs-node-canvas');
const fs = require('fs');

async function generateExecutiveReport(reportData, outputPath) {
  const doc = new PDFDocument({ margin: 50 });
  const stream = fs.createWriteStream(outputPath);
  doc.pipe(stream);

  // Cover page
  doc.fontSize(32)
     .fillColor('#2C3E50')
     .text(reportData.title, { align: 'center' })
     .moveDown()
     .fontSize(18)
     .fillColor('#7F8C8D')
     .text(reportData.subtitle, { align: 'center' })
     .moveDown(2)
     .fontSize(12)
     .text(`Reporting Period: ${reportData.period}`, { align: 'center' })
     .text(`Generated: ${new Date().toLocaleDateString()}`, { align: 'center' });

  // Company logo if provided
  if (reportData.logo) {
    doc.image(reportData.logo, doc.page.width / 2 - 50, 150, { width: 100 });
  }

  // Executive Summary
  doc.addPage();
  doc.fontSize(24)
     .fillColor('#2C3E50')
     .text('Executive Summary', { underline: true })
     .moveDown();

  doc.fontSize(11)
     .fillColor('#34495E')
     .text(reportData.executiveSummary, { align: 'justify', lineGap: 4 });

  // Key Metrics Section
  doc.moveDown(2);
  doc.fontSize(18)
     .fillColor('#2C3E50')
     .text('Key Performance Indicators');

  doc.moveDown();

  // KPI Cards (2x2 grid)
  const kpiWidth = 220;
  const kpiHeight = 100;
  const spacing = 20;

  reportData.kpis.forEach((kpi, index) => {
    const row = Math.floor(index / 2);
    const col = index % 2;
    const x = 50 + col * (kpiWidth + spacing);
    const y = doc.y + row * (kpiHeight + spacing);

    // KPI Card background
    doc.fillColor(kpi.trend === 'up' ? '#27AE60' : kpi.trend === 'down' ? '#E74C3C' : '#3498DB')
       .opacity(0.1)
       .rect(x, y, kpiWidth, kpiHeight)
       .fill();

    doc.opacity(1);

    // KPI Label
    doc.fontSize(10)
       .fillColor('#7F8C8D')
       .text(kpi.label, x + 10, y + 10, { width: kpiWidth - 20 });

    // KPI Value
    doc.fontSize(28)
       .fillColor('#2C3E50')
       .text(kpi.value, x + 10, y + 30);

    // KPI Change
    const changeColor = kpi.trend === 'up' ? '#27AE60' : kpi.trend === 'down' ? '#E74C3C' : '#7F8C8D';
    doc.fontSize(12)
       .fillColor(changeColor)
       .text(kpi.change, x + 10, y + 70);
  });

  // Adjust Y position after KPI grid
  doc.y += Math.ceil(reportData.kpis.length / 2) * (kpiHeight + spacing) + spacing;

  // Detailed Sections
  reportData.sections.forEach(async (section) => {
    doc.addPage();

    // Section Title
    doc.fontSize(20)
       .fillColor('#2C3E50')
       .text(section.title, { underline: true })
       .moveDown();

    // Section Content
    doc.fontSize(11)
       .fillColor('#34495E')
       .text(section.content, { align: 'justify', lineGap: 4 });

    // Add chart if section has data
    if (section.chartData) {
      doc.moveDown();
      const chartImage = await generateChart(section.chartData);
      doc.image(chartImage, 100, doc.y, { width: 400 });
      doc.moveDown(15); // Space for chart
    }

    // Add table if section has table data
    if (section.tableData) {
      doc.moveDown();
      addTable(doc, section.tableData);
    }
  });

  // Recommendations
  doc.addPage();
  doc.fontSize(20)
     .fillColor('#2C3E50')
     .text('Recommendations', { underline: true })
     .moveDown();

  reportData.recommendations.forEach((rec, index) => {
    doc.fontSize(11)
       .fillColor('#34495E')
       .text(`${index + 1}. ${rec}`, { lineGap: 6 });
    doc.moveDown(0.5);
  });

  // Footer on all pages
  const pages = doc.bufferedPageRange();
  for (let i = 0; i < pages.count; i++) {
    doc.switchToPage(i);
    doc.fontSize(8)
       .fillColor('#95A5A6')
       .text(
         `${reportData.company} | Confidential | Page ${i + 1} of ${pages.count}`,
         50,
         doc.page.height - 50,
         { align: 'center', width: doc.page.width - 100 }
       );
  }

  doc.end();

  return new Promise((resolve, reject) => {
    stream.on('finish', () => resolve(outputPath));
    stream.on('error', reject);
  });
}

async function generateChart(chartData) {
  const chartCanvas = new ChartJS.ChartJSNodeCanvas({ width: 800, height: 400 });

  const configuration = {
    type: chartData.type || 'bar',
    data: {
      labels: chartData.labels,
      datasets: [{
        label: chartData.label,
        data: chartData.values,
        backgroundColor: chartData.colors || '#3498DB',
        borderColor: '#2C3E50',
        borderWidth: 1
      }]
    },
    options: {
      responsive: true,
      scales: {
        y: {
          beginAtZero: true
        }
      }
    }
  };

  return await chartCanvas.renderToBuffer(configuration);
}

function addTable(doc, tableData) {
  const tableTop = doc.y;
  const cellPadding = 8;
  const rowHeight = 25;
  const colWidth = (doc.page.width - 100) / tableData.headers.length;

  // Header row
  doc.fillColor('#34495E')
     .rect(50, tableTop, doc.page.width - 100, rowHeight)
     .fill();

  doc.fillColor('#FFFFFF')
     .fontSize(10);

  tableData.headers.forEach((header, i) => {
    doc.text(header, 50 + i * colWidth + cellPadding, tableTop + cellPadding, {
      width: colWidth - cellPadding * 2
    });
  });

  // Data rows
  doc.fillColor('#34495E');

  tableData.rows.forEach((row, rowIndex) => {
    const y = tableTop + (rowIndex + 1) * rowHeight;

    // Alternate row colors
    if (rowIndex % 2 === 1) {
      doc.fillColor('#F8F9FA')
         .rect(50, y, doc.page.width - 100, rowHeight)
         .fill();
    }

    doc.fillColor('#2C3E50');

    row.forEach((cell, cellIndex) => {
      doc.text(cell, 50 + cellIndex * colWidth + cellPadding, y + cellPadding, {
        width: colWidth - cellPadding * 2
      });
    });
  });

  doc.y = tableTop + (tableData.rows.length + 1) * rowHeight + 10;
}
工作流 2:月度绩效报告

目的: 自动生成包含指标、趋势和比较的月度报告

步骤:

  1. 查询本月和之前各期的数据
  2. 计算环比和同比变化
  3. 生成趋势图
  4. 找出表现最佳者和需要关注的领域
  5. 创建比较表格
  6. 根据数据模式添加叙述性见解
  7. 排版并分发报告

实现:

async function generateMonthlyReport(month, year, dataSource) {
  // Fetch data
  const currentData = await dataSource.getMonthData(month, year);
  const previousMonthData = await dataSource.getMonthData(month - 1, year);
  const lastYearData = await dataSource.getMonthData(month, year - 1);

  // Calculate metrics
  const metrics = {
    revenue: {
      current: currentData.revenue,
      momChange: calculatePercentChange(currentData.revenue, previousMonthData.revenue),
      yoyChange: calculatePercentChange(currentData.revenue, lastYearData.revenue)
    },
    customers: {
      current: currentData.customers,
      momChange: calculatePercentChange(currentData.customers, previousMonthData.customers),
      yoyChange: calculatePercentChange(currentData.customers, lastYearData.customers)
    },
    avgOrderValue: {
      current: currentData.revenue / currentData.orders,
      momChange: calculatePercentChange(
        currentData.revenue / currentData.orders,
        previousMonthData.revenue / previousMonthData.orders
      )
    }
  };

  // Generate report data
  const reportData = {
    title: 'Monthly Performance Report',
    subtitle: `${getMonthName(month)} ${year}`,
    period: `${getMonthName(month)} 1-${new Date(year, month, 0).getDate()}, ${year}`,
    company: 'Your Company',

    executiveSummary: generateExecutiveSummary(metrics, currentData),

    kpis: [
      {
        label: 'Total Revenue',
        value: formatCurrency(metrics.revenue.current),
        change: `${metrics.revenue.momChange > 0 ? '+' : ''}${metrics.revenue.momChange.toFixed(1)}% MoM`,
        trend: metrics.revenue.momChange > 0 ? 'up' : 'down'
      },
      {
        label: 'New Customers',
        value: metrics.customers.current.toLocaleString(),
        change: `${metrics.customers.yoyChange > 0 ? '+' : ''}${metrics.customers.yoyChange.toFixed(1)}% YoY`,
        trend: metrics.customers.yoyChange > 0 ? 'up' : 'down'
      },
      {
        label: 'Avg Order Value',
        value: formatCurrency(metrics.avgOrderValue.current),
        change: `${metrics.avgOrderValue.momChange > 0 ? '+' : ''}${metrics.avgOrderValue.momChange.toFixed(1)}% MoM`,
        trend: metrics.avgOrderValue.momChange > 0 ? 'up' : 'down'
      },
      {
        label: 'Customer Satisfaction',
        value: `${currentData.satisfaction}%`,
        change: 'Stable',
        trend: 'neutral'
      }
    ],

    sections: [
      {
        title: 'Revenue Analysis',
        content: `Revenue for ${getMonthName(month)} totaled ${formatCurrency(metrics.revenue.current)}, representing a ${Math.abs(metrics.revenue.momChange).toFixed(1)}% ${metrics.revenue.momChange > 0 ? 'increase' : 'decrease'} compared to last month...`,
        chartData: {
          type: 'line',
          labels: getLast6Months(month, year),
          values: await dataSource.getRevenueHistory(6),
          label: 'Revenue Trend'
        }
      },
      {
        title: 'Customer Acquisition',
        content: `Customer acquisition efforts resulted in ${metrics.customers.current} new customers this month...`,
        tableData: {
          headers: ['Channel', 'Customers', 'Cost', 'CAC'],
          rows: currentData.acquisitionChannels.map(channel => [
            channel.name,
            channel.customers.toString(),
            formatCurrency(channel.cost),
            formatCurrency(channel.cost / channel.customers)
          ])
        }
      },
      {
        title: 'Product Performance',
        content: 'Top-performing products and categories for the period...',
        chartData: {
          type: 'bar',
          labels: currentData.topProducts.map(p => p.name),
          values: currentData.topProducts.map(p => p.revenue),
          label: 'Revenue by Product'
        }
      }
    ],

    recommendations: generateRecommendations(metrics, currentData)
  };

  return await generateExecutiveReport(reportData, `./reports/monthly-${year}-${month}.pdf`);
}

function calculatePercentChange(current, previous) {
  if (previous === 0) return 0;
  return ((current - previous) / previous) * 100;
}

function formatCurrency(amount) {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount);
}

function generateExecutiveSummary(metrics, data) {
  const revenueDirection = metrics.revenue.momChange > 0 ? 'increased' : 'decreased';
  const revenuePercent = Math.abs(metrics.revenue.momChange).toFixed(1);

  return `
    Revenue ${revenueDirection} by ${revenuePercent}% compared to the previous month,
    driven primarily by ${data.primaryGrowthDriver}. Customer acquisition
    ${metrics.customers.momChange > 0 ? 'exceeded' : 'fell short of'} targets, with
    ${metrics.customers.current} new customers. The average order value ${metrics.avgOrderValue.momChange > 0 ? 'improved' : 'declined'}
    by ${Math.abs(metrics.avgOrderValue.momChange).toFixed(1)}%, indicating
    ${metrics.avgOrderValue.momChange > 0 ? 'successful upselling efforts' : 'price pressure in the market'}.
  `.trim();
}

function generateRecommendations(metrics, data) {
  const recommendations = [];

  if (metrics.revenue.momChange < -5) {
    recommendations.push('Consider launching promotional campaigns to boost short-term revenue');
  }

  if (metrics.customers.momChange < 0) {
    recommendations.push('Increase marketing spend in top-performing acquisition channels');
  }

  if (metrics.avgOrderValue.momChange < 0) {
    recommendations.push('Implement cross-selling and bundling strategies to increase order values');
  }

  if (data.satisfaction < 80) {
    recommendations.push('Focus on improving customer satisfaction through enhanced support and service quality');
  }

  return recommendations;
}
工作流 3:项目状态报告

目的: 生成包含里程碑、任务和风险的项目进度报告

步骤:

  1. 收集项目数据(任务、里程碑、团队成员)
  2. 计算完成百分比和时间进度状态
  3. 找出已完成、进行中和逾期的事项
  4. 列出风险和问题
  5. 创建甘特图或时间线可视化内容
  6. 添加团队成员的贡献
  7. 纳入后续步骤和行动事项
  8. 排版成适合利益相关者阅读的报告

实现:

async function generateProjectReport(projectData, outputPath) {
  const completionRate = calculateCompletionRate(projectData.tasks);
  const scheduleStatus = assessScheduleStatus(projectData);

  const reportData = {
    title: `Project Status Report: ${projectData.name}`,
    subtitle: `Status as of ${new Date().toLocaleDateString()}`,
    period: `${projectData.startDate} - ${projectData.endDate}`,
    company: projectData.organization,

    executiveSummary: `
      The ${projectData.name} project is currently ${completionRate}% complete.
      The project is ${scheduleStatus.status} schedule by ${scheduleStatus.variance} days.
      ${projectData.risks.length} risks have been identified and are being actively managed.
    `,

    kpis: [
      {
        label: 'Completion',
        value: `${completionRate}%`,
        change: `${projectData.tasksCompletedThisWeek} tasks completed this week`,
        trend: completionRate > 50 ? 'up' : 'neutral'
      },
      {
        label: 'Schedule Status',
        value: scheduleStatus.status,
        change: `${Math.abs(scheduleStatus.variance)} days ${scheduleStatus.variance > 0 ? 'ahead' : 'behind'}`,
        trend: scheduleStatus.variance >= 0 ? 'up' : 'down'
      },
      {
        label: 'Budget',
        value: formatCurrency(projectData.spentBudget),
        change: `${((projectData.spentBudget / projectData.totalBudget) * 100).toFixed(1)}% of total`,
        trend: projectData.spentBudget <= projectData.totalBudget ? 'up' : 'down'
      },
      {
        label: 'Team Health',
        value: projectData.teamMorale,
        change: 'Based on last survey',
        trend: 'neutral'
      }
    ],

    sections: [
      {
        title: 'Milestones',
        content: 'Current status of project milestones:',
        tableData: {
          headers: ['Milestone', 'Due Date', 'Status', 'Completion'],
          rows: projectData.milestones.map(m => [
            m.name,
            m.dueDate,
            m.status,
            `${m.completion}%`
          ])
        }
      },
      {
        title: 'Completed This Period',
        content: projectData.completedTasks.map(t => `• ${t.name}`).join('\n')
      },
      {
        title: 'In Progress',
        content: projectData.inProgressTasks.map(t => `• ${t.name} (${t.assignee})`).join('\n')
      },
      {
        title: 'Risks and Issues',
        content: projectData.risks.length > 0
          ? projectData.risks.map((r, i) => `${i + 1}. ${r.description} (Impact: ${r.impact}, Mitigation: ${r.mitigation})`).join('\n\n')
          : 'No significant risks identified at this time.'
      }
    ],

    recommendations: [
      ...projectData.nextSteps.map(step => `Complete: ${step}`),
      ...projectData.risks.filter(r => r.impact === 'High').map(r => `Address high-impact risk: ${r.description}`)
    ]
  };

  return await generateExecutiveReport(reportData, outputPath);
}

function calculateCompletionRate(tasks) {
  const total = tasks.length;
  const completed = tasks.filter(t => t.status === 'completed').length;
  return Math.round((completed / total) * 100);
}

function assessScheduleStatus(projectData) {
  const today = new Date();
  const expectedCompletion = new Date(projectData.expectedCompletionDate);
  const originalEnd = new Date(projectData.endDate);

  const daysVariance = Math.round((expectedCompletion - originalEnd) / (1000 * 60 * 60 * 24));

  return {
    status: daysVariance <= 0 ? 'On' : 'Behind',
    variance: daysVariance
  };
}
工作流 4:财务报告生成

目的: 创建财务报表和分析报告

步骤:

  1. 从会计系统提取财务数据
  2. 计算财务比率和指标
  3. 生成资产负债表、损益表和现金流量表
  4. 创建趋势分析和比较
  5. 添加收入、支出和利润率图表
  6. 对差异添加注释和说明
  7. 按照会计准则排版
  8. 规范排版后导出为 PDF
工作流 5:自动化报告排期

目的: 安排并自动生成定期报告

步骤:

  1. 定义报告排期(每日、每周、每月)
  2. 设置数据源连接
  3. 创建报告模板
  4. 安排报告生成任务
  5. 按指定间隔自动生成报告
  6. 通过电子邮件或文件存储进行分发
  7. 归档生成的报告
  8. 完成后发送通知

实现:

const cron = require('node-cron');
const nodemailer = require('nodemailer');

class ReportScheduler {
  constructor() {
    this.schedules = [];
  }

  scheduleReport(config) {
    const job = cron.schedule(config.cronExpression, async () => {
      try {
        console.log(`Generating ${config.reportType} report...`);

        // Generate report
        const reportPath = await this.generateReport(config);

        // Send report
        if (config.emailRecipients) {
          await this.emailReport(reportPath, config.emailRecipients, config);
        }

        // Archive
        if (config.archivePath) {
          await this.archiveReport(reportPath, config.archivePath);
        }

        console.log(`Report generated successfully: ${reportPath}`);
      } catch (error) {
        console.error(`Report generation failed: ${error.message}`);
        this.notifyFailure(config, error);
      }
    });

    this.schedules.push({ config, job });
  }

  async generateReport(config) {
    switch (config.reportType) {
      case 'monthly':
        return await generateMonthlyReport(new Date().getMonth(), new Date().getFullYear(), config.dataSource);
      case 'project':
        return await generateProjectReport(await config.dataSource.getProjectData(), config.outputPath);
      default:
        throw new Error(`Unknown report type: ${config.reportType}`);
    }
  }

  async emailReport(reportPath, recipients, config) {
    const transporter = nodemailer.createTransporter(config.emailConfig);

    await transporter.sendMail({
      from: config.emailConfig.from,
      to: recipients.join(', '),
      subject: `${config.reportName} - ${new Date().toLocaleDateString()}`,
      html: `
        <p>Please find attached the automated ${config.reportName}.</p>
        <p>This report was generated on ${new Date().toLocaleString()}.</p>
      `,
      attachments: [
        {
          filename: `${config.reportName}.pdf`,
          path: reportPath
        }
      ]
    });
  }

  async archiveReport(reportPath, archivePath) {
    const fs = require('fs').promises;
    const path = require('path');

    const timestamp = new Date().toISOString().replace(/:/g, '-');
    const archiveFilename = `report-${timestamp}.pdf`;

    await fs.copyFile(reportPath, path.join(archivePath, archiveFilename));
  }
}

// Usage:
const scheduler = new ReportScheduler();

scheduler.scheduleReport({
  reportType: 'monthly',
  reportName: 'Monthly Performance Report',
  cronExpression: '0 9 1 * *', // 9 AM on 1st of each month
  dataSource: myDataSource,
  outputPath: './reports/monthly-latest.pdf',
  emailRecipients: ['[email protected]', '[email protected]'],
  emailConfig: { /* nodemailer config */ },
  archivePath: './reports/archive'
});

快速参考

操作命令/触发语
生成报告"create [report type] for [period]"
执行报告"generate executive summary"
月度报告"create monthly performance report"
项目状态"generate project status report"
添加图表"include [chart type] in report"
安排报告"automate [report] generation"
自定义报告"build custom report with [sections]"

最佳实践

  • 数据准确性: 生成报告前验证所有数据源
  • 格式一致: 为定期报告类型使用模板
  • 执行摘要: 始终先呈现关键要点,方便繁忙的高管快速了解
  • 可视化优先: 使用图表快速传达趋势
  • 可执行的洞察: 提供具体建议,而不只是展示数据
  • 版本控制: 跟踪报告版本和更改
  • 分发: 了解受众并相应调整内容深度
  • 及时性: 按固定周期生成报告
  • 背景信息: 提供对比(环比、同比、与目标对比)
  • 专业布局: 保持品牌形象和格式一致
  • 无障碍性: 确保报告在所有设备上均易于阅读
  • 归档: 保留历史报告以进行趋势分析

常见模式

季度业务回顾:

const qbrData = {
  title: 'Q4 2025 Business Review',
  sections: ['Financial Performance', 'Operational Metrics', 'Market Position', 'Strategic Initiatives'],
  compareAgainst: ['Q3 2025', 'Q4 2024'],
  includeForecast: true,
  forecastPeriod: 'Q1 2026'
};

销售仪表板:

const salesReport = {
  kpis: ['Revenue', 'Deals Closed', 'Pipeline Value', 'Conversion Rate'],
  breakdowns: ['by Region', 'by Product', 'by Sales Rep'],
  trends: ['Last 12 months', 'Year over Year'],
  targets: true
};

依赖项

安装所需的软件包:

npm install pdfkit chartjs-node-canvas
npm install node-cron nodemailer  # For scheduling and email
npm install exceljs                # For Excel export option
npm install handlebars             # For templating

错误处理

  • 数据缺失: 妥善处理 null/undefined 值
  • 数据源故障: 为 API/数据库查询设置重试逻辑
  • 图表生成: 如果图表生成失败,则回退为表格
  • 文件系统: 写入文件前检查权限
  • 邮件发送失败: 记录错误并重试发送
  • 计划冲突: 避免报告生成任务重叠

性能提示

  • 缓存图表图像以便重复使用
  • 对大型数据集使用流式处理
  • 尽可能并行生成图表
  • 嵌入前压缩图像
  • 对超大型报告使用分页
  • 预先聚合数据以提升性能

高级功能

交互式 HTML 报告:

const html = `
  <html>
    <head>
      <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    </head>
    <body>
      <h1>${reportData.title}</h1>
      <canvas id="chart"></canvas>
      <script>
        // Interactive Chart.js visualization
      </script>
    </body>
  </html>
`;

多格式导出:

async function exportReport(reportData, formats = ['pdf', 'html', 'xlsx']) {
  const outputs = {};
  if (formats.includes('pdf')) outputs.pdf = await generatePDF(reportData);
  if (formats.includes('html')) outputs.html = await generateHTML(reportData);
  if (formats.includes('xlsx')) outputs.xlsx = await generateExcel(reportData);
  return outputs;
}

评论

支持 Markdown