Files
sales-data-analysis/report_generator.py
Jonathan Pressnell cf0b596449 Initial commit: sales analysis template
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 09:16:34 -05:00

229 lines
7.1 KiB
Python

"""
Report generation utility
Combines multiple charts and data into a PDF report
Usage:
from report_generator import generate_pdf_report
# Generate PDF report
generate_pdf_report(
charts=['chart1.png', 'chart2.png'],
title='Sales Analysis Report',
summary_data={'Total Revenue': 1000000}
)
"""
from pathlib import Path
from datetime import datetime
from config import COMPANY_NAME, OUTPUT_DIR, REPORTS_DIR, ensure_directories
def generate_pdf_report(
charts,
title=None,
summary_data=None,
output_filename=None,
output_dir=None
):
"""
Generate PDF report from charts and summary data
Args:
charts: List of chart file paths (PNG files)
title: Report title (defaults to company name + date)
summary_data: Dictionary of summary metrics
output_filename: Output PDF filename (defaults to report_YYYYMMDD_HHMMSS.pdf)
output_dir: Output directory (defaults to config.REPORTS_DIR)
Returns:
Path: Path to generated PDF file
Raises:
ImportError: If reportlab is not installed
"""
try:
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle, PageBreak
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
except ImportError:
raise ImportError(
"reportlab is required for PDF generation. Install with: pip install reportlab"
)
if output_dir is None:
output_dir = REPORTS_DIR
else:
output_dir = Path(output_dir)
ensure_directories()
output_dir.mkdir(exist_ok=True)
# Default filename
if output_filename is None:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
output_filename = f"report_{timestamp}.pdf"
output_path = output_dir / output_filename
# Create PDF document
doc = SimpleDocTemplate(
str(output_path),
pagesize=letter,
rightMargin=0.75*inch,
leftMargin=0.75*inch,
topMargin=0.75*inch,
bottomMargin=0.75*inch
)
# Container for PDF elements
story = []
# Styles
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
'CustomTitle',
parent=styles['Heading1'],
fontSize=20,
textColor=colors.HexColor('#2E86AB'),
spaceAfter=30,
alignment=TA_CENTER
)
heading_style = ParagraphStyle(
'CustomHeading',
parent=styles['Heading2'],
fontSize=14,
textColor=colors.HexColor('#2E86AB'),
spaceAfter=12
)
# Title
if title is None:
title = f"{COMPANY_NAME} Sales Analysis Report"
story.append(Paragraph(title, title_style))
story.append(Spacer(1, 0.2*inch))
# Report metadata
metadata_text = f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
story.append(Paragraph(metadata_text, styles['Normal']))
story.append(Spacer(1, 0.3*inch))
# Summary data table
if summary_data:
story.append(Paragraph("Summary", heading_style))
# Create table
table_data = [['Metric', 'Value']]
for key, value in summary_data.items():
# Format value
if isinstance(value, (int, float)):
if abs(value) >= 1e6:
formatted_value = f"${value / 1e6:.2f}m"
elif abs(value) >= 1e3:
formatted_value = f"${value / 1e3:.2f}k"
else:
formatted_value = f"${value:.2f}"
else:
formatted_value = str(value)
table_data.append([key, formatted_value])
table = Table(table_data, colWidths=[3*inch, 2*inch])
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2E86AB')),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
('GRID', (0, 0), (-1, -1), 1, colors.black),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey])
]))
story.append(table)
story.append(Spacer(1, 0.3*inch))
# Add charts
if charts:
story.append(Paragraph("Charts", heading_style))
for i, chart_path in enumerate(charts, 1):
chart_path = Path(chart_path)
if not chart_path.exists():
print(f"Warning: Chart not found: {chart_path}")
continue
# Add chart title
chart_title = f"Chart {i}: {chart_path.stem.replace('_', ' ').title()}"
story.append(Paragraph(chart_title, styles['Heading3']))
story.append(Spacer(1, 0.1*inch))
# Add image
try:
img = Image(str(chart_path), width=6*inch, height=4*inch)
story.append(img)
except Exception as e:
error_msg = f"Error loading chart: {e}"
story.append(Paragraph(error_msg, styles['Normal']))
# Add page break between charts (except last one)
if i < len(charts):
story.append(PageBreak())
# Build PDF
doc.build(story)
print(f"PDF report generated: {output_path}")
return output_path
def generate_simple_report(charts, title=None, output_filename=None):
"""
Generate a simple PDF report (wrapper with defaults)
Args:
charts: List of chart file paths
title: Report title
output_filename: Output filename
Returns:
Path: Path to generated PDF
"""
return generate_pdf_report(
charts=charts,
title=title,
output_filename=output_filename
)
# ============================================================================
# EXAMPLE USAGE
# ============================================================================
if __name__ == "__main__":
"""Example usage"""
from config import OUTPUT_DIR
# Find charts in output directory
chart_files = list(OUTPUT_DIR.glob('*.png'))
if chart_files:
print(f"Found {len(chart_files)} charts")
# Generate report
report_path = generate_pdf_report(
charts=[str(f) for f in chart_files[:5]], # Limit to 5 charts
title="Sales Analysis Report",
summary_data={
'Total Charts': len(chart_files),
'Report Date': datetime.now().strftime('%Y-%m-%d')
}
)
print(f"Report saved to: {report_path}")
else:
print("No charts found in output directory")