Report Builder について
Report Builderは、複数のデータソースを分析し、チャートや要約を含む業務レポートを作成するスキルです。さまざまなレポートに対応し、PDF、Word、HTML形式で出力できます。
主な機能
- 01業務・分析レポートの作成
- 02複数データソースの統合
- 03チャート、表、傾向分析の作成
- 04エグゼクティブサマリーと提案の生成
- 05PDF、Word、HTMLへの出力
- 06定期レポート生成のスケジュール設定
活用シーン
- 01月次業績レポートの作成
- 02プロジェクトの進捗、マイルストーン、リスクの集約
- 03財務分析レポートの作成
- 04定期レポートの自動生成と配布
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ jmsktm/ claude-settings --skill report-builderインストール後、Claude Code で/report-builderを入力して読み込まれたか確認します。
詳細
レポート作成
レポート作成スキルは、データ分析、可視化、エグゼクティブサマリー、書式設定されたセクションを備えた、プロフェッショナルなビジネスレポートの作成を自動化します。財務レポート、プロジェクト状況報告、市場分析、パフォーマンスレビュー、分析レポートなど、さまざまな種類のレポートに対応します。このスキルは複数のソースからデータを統合し、明確で実行につながる形式で提示します。
チャート、表、トレンド分析、重要な知見を盛り込んだレポートを、PDF、Word、HTML形式で作成します。定期レポート、自動ダッシュボード、データに基づく意思決定の文書化に最適です。
主なワークフロー
ワークフロー 1:エグゼクティブレポートの作成
目的: サマリー、主要な指標、推奨事項を含む、概要レベルのレポートを作成します
手順:
- さまざまなソースからデータを収集します
- 主要業績評価指標(KPI)を計算します
- 重要なポイントを強調したエグゼクティブサマリーを作成します
- データを可視化します(チャート、グラフ)
- 裏付けとなるデータを含む詳細なセクションを追加します
- 推奨事項とアクションアイテムを含めます
- プロフェッショナルなレイアウトに整えます
- ブランド要素を適用して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:月次パフォーマンスレポート
目的: 指標、トレンド、比較を含む月次レポートを自動作成します
手順:
- 当月および過去の期間のデータを照会します
- 前月比および前年比の変化を計算します
- トレンドチャートを作成します
- 業績上位の項目と懸念事項を特定します
- 比較表を作成します
- データの傾向に基づく説明と知見を追加します
- レポートを整えて配布します
実装:
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:プロジェクト状況報告
目的: マイルストーン、タスク、リスクを含むプロジェクト進捗レポートを作成します
手順:
- プロジェクトデータ(タスク、マイルストーン、チームメンバー)を収集します
- 完了率とスケジュールの状況を計算します
- 完了済み、進行中、期限超過の項目を特定します
- リスクと課題を列挙します
- ガントチャートまたはタイムラインの可視化を作成します
- チームメンバーの貢献を追加します
- 次のステップとアクションアイテムを含めます
- 関係者に提出できる形式に整えます
実装:
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:財務レポートの作成
目的: 財務諸表と分析レポートを作成します
手順:
- 会計システムから財務データを抽出します
- 財務比率と指標を計算します
- 貸借対照表、損益計算書、キャッシュフロー計算書を作成します
- トレンド分析と比較を行います
- 売上、費用、利益率のチャートを追加します
- 差異に関する注記と説明を含めます
- 会計基準に従って書式を整えます
- 適切な書式でPDFにエクスポートします
ワークフロー 5:レポートの自動スケジュール設定
目的: 定期レポートをスケジュールし、自動作成します
手順:
- レポートのスケジュール(日次、週次、月次)を定義します
- データソースへの接続を設定します
- レポートのテンプレートを作成します
- レポート作成ジョブをスケジュールします
- 指定した間隔で自動作成します
- メールまたはファイルストレージを通じて配布します
- 作成したレポートをアーカイブします
- 完了時に通知を送信します
実装:
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'
});
クイックリファレンス
| アクション | コマンド/トリガー |
|---|---|
| レポートを作成 | 「[期間]の[レポートの種類]を作成」 |
| エグゼクティブレポート | 「エグゼクティブサマリーを作成」 |
| 月次レポート | 「月次パフォーマンスレポートを作成」 |
| プロジェクト状況 | 「プロジェクト状況報告を作成」 |
| チャートを追加 | 「レポートに[チャートの種類]を含める」 |
| レポートをスケジュール | 「[レポート]の作成を自動化」 |
| カスタムレポート | 「[セクション]を含むカスタムレポートを作成」 |
ベストプラクティス
- データの正確性: レポートを生成する前に、すべてのデータソースを検証します
- 一貫した形式: 繰り返し使用するレポートの種類にはテンプレートを使用します
- エグゼクティブサマリー: 忙しい経営幹部向けに、必ず重要なポイントから始めます
- ビジュアルを優先: トレンドをすばやく伝えるために、チャートやグラフを使用します
- 実行可能なインサイト: データだけでなく、具体的な提案を含めます
- バージョン管理: レポートのバージョンと変更を記録します
- 配布: 対象読者を把握し、それに応じて内容の詳しさを調整します
- 適時性: 一貫したスケジュールでレポートを生成します
- コンテキスト: 比較情報を提示します(月次、前年比、目標比)
- プロフェッショナルなレイアウト: ブランドと書式を一貫して保ちます
- アクセシビリティ: すべてのデバイスでレポートを読みやすくします
- アーカイブ: トレンド分析のために過去のレポートを保管します
よくあるパターン
四半期ビジネスレビュー:
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 対応