Initial commit: sales analysis template
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
228
report_generator.py
Normal file
228
report_generator.py
Normal file
@@ -0,0 +1,228 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user