Initial commit
This commit is contained in:
256
skills/reportlab/assets/invoice_template.py
Normal file
256
skills/reportlab/assets/invoice_template.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Invoice Template - Complete example of a professional invoice
|
||||
|
||||
This template demonstrates:
|
||||
- Company header with logo placement
|
||||
- Client information
|
||||
- Invoice details table
|
||||
- Calculations (subtotal, tax, total)
|
||||
- Professional styling
|
||||
- Terms and conditions footer
|
||||
"""
|
||||
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image
|
||||
)
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def create_invoice(
|
||||
filename,
|
||||
invoice_number,
|
||||
invoice_date,
|
||||
due_date,
|
||||
company_info,
|
||||
client_info,
|
||||
items,
|
||||
tax_rate=0.0,
|
||||
notes="",
|
||||
terms="Payment due within 30 days.",
|
||||
logo_path=None
|
||||
):
|
||||
"""
|
||||
Create a professional invoice PDF.
|
||||
|
||||
Args:
|
||||
filename: Output PDF filename
|
||||
invoice_number: Invoice number (e.g., "INV-2024-001")
|
||||
invoice_date: Date of invoice (datetime or string)
|
||||
due_date: Payment due date (datetime or string)
|
||||
company_info: Dict with company details
|
||||
{'name': 'Company Name', 'address': 'Address', 'phone': 'Phone', 'email': 'Email'}
|
||||
client_info: Dict with client details (same structure as company_info)
|
||||
items: List of dicts with item details
|
||||
[{'description': 'Item', 'quantity': 1, 'unit_price': 100.00}, ...]
|
||||
tax_rate: Tax rate as decimal (e.g., 0.08 for 8%)
|
||||
notes: Additional notes to client
|
||||
terms: Payment terms
|
||||
logo_path: Path to company logo image (optional)
|
||||
"""
|
||||
# Create document
|
||||
doc = SimpleDocTemplate(filename, pagesize=letter,
|
||||
rightMargin=0.5*inch, leftMargin=0.5*inch,
|
||||
topMargin=0.5*inch, bottomMargin=0.5*inch)
|
||||
|
||||
# Container for elements
|
||||
story = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Create custom styles
|
||||
title_style = ParagraphStyle(
|
||||
'InvoiceTitle',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=24,
|
||||
textColor=colors.HexColor('#2C3E50'),
|
||||
spaceAfter=12,
|
||||
)
|
||||
|
||||
header_style = ParagraphStyle(
|
||||
'Header',
|
||||
parent=styles['Normal'],
|
||||
fontSize=10,
|
||||
textColor=colors.HexColor('#34495E'),
|
||||
)
|
||||
|
||||
# --- HEADER SECTION ---
|
||||
header_data = []
|
||||
|
||||
# Company info (left side)
|
||||
company_text = f"""
|
||||
<b><font size="14">{company_info['name']}</font></b><br/>
|
||||
{company_info.get('address', '')}<br/>
|
||||
Phone: {company_info.get('phone', '')}<br/>
|
||||
Email: {company_info.get('email', '')}
|
||||
"""
|
||||
|
||||
# Invoice title and number (right side)
|
||||
invoice_text = f"""
|
||||
<b><font size="16" color="#2C3E50">INVOICE</font></b><br/>
|
||||
<font size="10">Invoice #: {invoice_number}</font><br/>
|
||||
<font size="10">Date: {invoice_date}</font><br/>
|
||||
<font size="10">Due Date: {due_date}</font>
|
||||
"""
|
||||
|
||||
if logo_path:
|
||||
logo = Image(logo_path, width=1.5*inch, height=1*inch)
|
||||
header_data = [[logo, Paragraph(company_text, header_style), Paragraph(invoice_text, header_style)]]
|
||||
header_table = Table(header_data, colWidths=[1.5*inch, 3*inch, 2.5*inch])
|
||||
else:
|
||||
header_data = [[Paragraph(company_text, header_style), Paragraph(invoice_text, header_style)]]
|
||||
header_table = Table(header_data, colWidths=[4.5*inch, 2.5*inch])
|
||||
|
||||
header_table.setStyle(TableStyle([
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
|
||||
]))
|
||||
|
||||
story.append(header_table)
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# --- CLIENT INFORMATION ---
|
||||
client_label = Paragraph("<b>Bill To:</b>", header_style)
|
||||
client_text = f"""
|
||||
<b>{client_info['name']}</b><br/>
|
||||
{client_info.get('address', '')}<br/>
|
||||
Phone: {client_info.get('phone', '')}<br/>
|
||||
Email: {client_info.get('email', '')}
|
||||
"""
|
||||
client_para = Paragraph(client_text, header_style)
|
||||
|
||||
client_table = Table([[client_label, client_para]], colWidths=[1*inch, 6*inch])
|
||||
story.append(client_table)
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# --- ITEMS TABLE ---
|
||||
# Table header
|
||||
items_data = [['Description', 'Quantity', 'Unit Price', 'Amount']]
|
||||
|
||||
# Calculate items
|
||||
subtotal = 0
|
||||
for item in items:
|
||||
desc = item['description']
|
||||
qty = item['quantity']
|
||||
price = item['unit_price']
|
||||
amount = qty * price
|
||||
subtotal += amount
|
||||
|
||||
items_data.append([
|
||||
desc,
|
||||
str(qty),
|
||||
f"${price:,.2f}",
|
||||
f"${amount:,.2f}"
|
||||
])
|
||||
|
||||
# Create items table
|
||||
items_table = Table(items_data, colWidths=[3.5*inch, 1*inch, 1.5*inch, 1*inch])
|
||||
|
||||
items_table.setStyle(TableStyle([
|
||||
# Header row
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
|
||||
# Data rows
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
|
||||
('ALIGN', (0, 1), (0, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 10),
|
||||
('TOPPADDING', (0, 1), (-1, -1), 6),
|
||||
('BOTTOMPADDING', (0, 1), (-1, -1), 6),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
]))
|
||||
|
||||
story.append(items_table)
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# --- TOTALS SECTION ---
|
||||
tax_amount = subtotal * tax_rate
|
||||
total = subtotal + tax_amount
|
||||
|
||||
totals_data = [
|
||||
['Subtotal:', f"${subtotal:,.2f}"],
|
||||
]
|
||||
|
||||
if tax_rate > 0:
|
||||
totals_data.append([f'Tax ({tax_rate*100:.1f}%):', f"${tax_amount:,.2f}"])
|
||||
|
||||
totals_data.append(['<b>Total:</b>', f"<b>${total:,.2f}</b>"])
|
||||
|
||||
totals_table = Table(totals_data, colWidths=[5*inch, 2*inch])
|
||||
totals_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
|
||||
('FONTNAME', (0, 0), (-1, -2), 'Helvetica'),
|
||||
('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 11),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 6),
|
||||
('LINEABOVE', (1, -1), (1, -1), 2, colors.HexColor('#34495E')),
|
||||
]))
|
||||
|
||||
story.append(totals_table)
|
||||
story.append(Spacer(1, 0.4*inch))
|
||||
|
||||
# --- NOTES ---
|
||||
if notes:
|
||||
notes_style = ParagraphStyle('Notes', parent=styles['Normal'], fontSize=9)
|
||||
story.append(Paragraph(f"<b>Notes:</b><br/>{notes}", notes_style))
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# --- TERMS ---
|
||||
terms_style = ParagraphStyle('Terms', parent=styles['Normal'],
|
||||
fontSize=9, textColor=colors.grey)
|
||||
story.append(Paragraph(f"<b>Payment Terms:</b><br/>{terms}", terms_style))
|
||||
|
||||
# Build PDF
|
||||
doc.build(story)
|
||||
return filename
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Sample data
|
||||
company = {
|
||||
'name': 'Acme Corporation',
|
||||
'address': '123 Business St, Suite 100\nNew York, NY 10001',
|
||||
'phone': '(555) 123-4567',
|
||||
'email': 'info@acme.com'
|
||||
}
|
||||
|
||||
client = {
|
||||
'name': 'John Doe',
|
||||
'address': '456 Client Ave\nLos Angeles, CA 90001',
|
||||
'phone': '(555) 987-6543',
|
||||
'email': 'john@example.com'
|
||||
}
|
||||
|
||||
items = [
|
||||
{'description': 'Web Design Services', 'quantity': 1, 'unit_price': 2500.00},
|
||||
{'description': 'Content Writing (10 pages)', 'quantity': 10, 'unit_price': 50.00},
|
||||
{'description': 'SEO Optimization', 'quantity': 1, 'unit_price': 750.00},
|
||||
{'description': 'Hosting Setup', 'quantity': 1, 'unit_price': 200.00},
|
||||
]
|
||||
|
||||
create_invoice(
|
||||
filename="sample_invoice.pdf",
|
||||
invoice_number="INV-2024-001",
|
||||
invoice_date="January 15, 2024",
|
||||
due_date="February 15, 2024",
|
||||
company_info=company,
|
||||
client_info=client,
|
||||
items=items,
|
||||
tax_rate=0.08,
|
||||
notes="Thank you for your business! We appreciate your prompt payment.",
|
||||
terms="Payment due within 30 days. Late payments subject to 1.5% monthly fee.",
|
||||
logo_path=None # Set to your logo path if available
|
||||
)
|
||||
|
||||
print("Invoice created: sample_invoice.pdf")
|
||||
343
skills/reportlab/assets/report_template.py
Normal file
343
skills/reportlab/assets/report_template.py
Normal file
@@ -0,0 +1,343 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Report Template - Complete example of a professional multi-page report
|
||||
|
||||
This template demonstrates:
|
||||
- Cover page
|
||||
- Table of contents
|
||||
- Multiple sections with headers
|
||||
- Charts and graphs integration
|
||||
- Tables with data
|
||||
- Headers and footers
|
||||
- Professional styling
|
||||
"""
|
||||
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (
|
||||
BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer,
|
||||
Table, TableStyle, PageBreak, KeepTogether, TableOfContents
|
||||
)
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.graphics.charts.linecharts import HorizontalLineChart
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def header_footer(canvas, doc):
|
||||
"""Draw header and footer on each page (except cover)"""
|
||||
canvas.saveState()
|
||||
|
||||
# Skip header/footer on cover page (page 1)
|
||||
if doc.page > 1:
|
||||
# Header
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.setFillColor(colors.grey)
|
||||
canvas.drawString(inch, letter[1] - 0.5*inch, "Quarterly Business Report")
|
||||
canvas.line(inch, letter[1] - 0.55*inch, letter[0] - inch, letter[1] - 0.55*inch)
|
||||
|
||||
# Footer
|
||||
canvas.drawString(inch, 0.5*inch, f"Generated: {datetime.now().strftime('%B %d, %Y')}")
|
||||
canvas.drawRightString(letter[0] - inch, 0.5*inch, f"Page {doc.page - 1}")
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
def create_report(filename, report_data):
|
||||
"""
|
||||
Create a comprehensive business report.
|
||||
|
||||
Args:
|
||||
filename: Output PDF filename
|
||||
report_data: Dict containing report information
|
||||
{
|
||||
'title': 'Report Title',
|
||||
'subtitle': 'Report Subtitle',
|
||||
'author': 'Author Name',
|
||||
'date': 'Date',
|
||||
'sections': [
|
||||
{
|
||||
'title': 'Section Title',
|
||||
'content': 'Section content...',
|
||||
'subsections': [...],
|
||||
'table': {...},
|
||||
'chart': {...}
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
"""
|
||||
# Create document with custom page template
|
||||
doc = BaseDocTemplate(filename, pagesize=letter,
|
||||
rightMargin=72, leftMargin=72,
|
||||
topMargin=inch, bottomMargin=inch)
|
||||
|
||||
# Define frame for content
|
||||
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 0.5*inch, id='normal')
|
||||
|
||||
# Create page template with header/footer
|
||||
template = PageTemplate(id='normal', frames=[frame], onPage=header_footer)
|
||||
doc.addPageTemplates([template])
|
||||
|
||||
# Get styles
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Custom styles
|
||||
title_style = ParagraphStyle(
|
||||
'ReportTitle',
|
||||
parent=styles['Title'],
|
||||
fontSize=28,
|
||||
textColor=colors.HexColor('#2C3E50'),
|
||||
spaceAfter=20,
|
||||
alignment=TA_CENTER,
|
||||
)
|
||||
|
||||
subtitle_style = ParagraphStyle(
|
||||
'ReportSubtitle',
|
||||
parent=styles['Normal'],
|
||||
fontSize=14,
|
||||
textColor=colors.grey,
|
||||
alignment=TA_CENTER,
|
||||
spaceAfter=30,
|
||||
)
|
||||
|
||||
heading1_style = ParagraphStyle(
|
||||
'CustomHeading1',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=18,
|
||||
textColor=colors.HexColor('#2C3E50'),
|
||||
spaceAfter=12,
|
||||
spaceBefore=12,
|
||||
)
|
||||
|
||||
heading2_style = ParagraphStyle(
|
||||
'CustomHeading2',
|
||||
parent=styles['Heading2'],
|
||||
fontSize=14,
|
||||
textColor=colors.HexColor('#34495E'),
|
||||
spaceAfter=10,
|
||||
spaceBefore=10,
|
||||
)
|
||||
|
||||
body_style = ParagraphStyle(
|
||||
'ReportBody',
|
||||
parent=styles['BodyText'],
|
||||
fontSize=11,
|
||||
alignment=TA_JUSTIFY,
|
||||
spaceAfter=12,
|
||||
leading=14,
|
||||
)
|
||||
|
||||
# Build story
|
||||
story = []
|
||||
|
||||
# --- COVER PAGE ---
|
||||
story.append(Spacer(1, 2*inch))
|
||||
story.append(Paragraph(report_data['title'], title_style))
|
||||
story.append(Paragraph(report_data.get('subtitle', ''), subtitle_style))
|
||||
story.append(Spacer(1, inch))
|
||||
|
||||
# Cover info table
|
||||
cover_info = [
|
||||
['Prepared by:', report_data.get('author', '')],
|
||||
['Date:', report_data.get('date', datetime.now().strftime('%B %d, %Y'))],
|
||||
['Period:', report_data.get('period', 'Q4 2023')],
|
||||
]
|
||||
|
||||
cover_table = Table(cover_info, colWidths=[2*inch, 4*inch])
|
||||
cover_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
|
||||
('ALIGN', (1, 0), (1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 11),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 6),
|
||||
]))
|
||||
|
||||
story.append(cover_table)
|
||||
story.append(PageBreak())
|
||||
|
||||
# --- TABLE OF CONTENTS ---
|
||||
toc = TableOfContents()
|
||||
toc.levelStyles = [
|
||||
ParagraphStyle(name='TOCHeading1', fontSize=14, leftIndent=20, spaceBefore=10, spaceAfter=5),
|
||||
ParagraphStyle(name='TOCHeading2', fontSize=12, leftIndent=40, spaceBefore=3, spaceAfter=3),
|
||||
]
|
||||
|
||||
story.append(Paragraph("Table of Contents", heading1_style))
|
||||
story.append(toc)
|
||||
story.append(PageBreak())
|
||||
|
||||
# --- SECTIONS ---
|
||||
for section in report_data.get('sections', []):
|
||||
# Section heading
|
||||
section_title = section['title']
|
||||
story.append(Paragraph(f'<a name="{section_title}"/>{section_title}', heading1_style))
|
||||
|
||||
# Add to TOC
|
||||
toc.addEntry(0, section_title, doc.page)
|
||||
|
||||
# Section content
|
||||
if 'content' in section:
|
||||
for para in section['content'].split('\n\n'):
|
||||
if para.strip():
|
||||
story.append(Paragraph(para.strip(), body_style))
|
||||
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# Subsections
|
||||
for subsection in section.get('subsections', []):
|
||||
story.append(Paragraph(subsection['title'], heading2_style))
|
||||
|
||||
if 'content' in subsection:
|
||||
story.append(Paragraph(subsection['content'], body_style))
|
||||
|
||||
story.append(Spacer(1, 0.1*inch))
|
||||
|
||||
# Add table if provided
|
||||
if 'table_data' in section:
|
||||
table = create_section_table(section['table_data'])
|
||||
story.append(table)
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# Add chart if provided
|
||||
if 'chart_data' in section:
|
||||
chart = create_section_chart(section['chart_data'])
|
||||
story.append(chart)
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# Build PDF (twice for TOC to populate)
|
||||
doc.multiBuild(story)
|
||||
return filename
|
||||
|
||||
|
||||
def create_section_table(table_data):
|
||||
"""Create a styled table for report sections"""
|
||||
data = table_data['data']
|
||||
table = Table(data, colWidths=table_data.get('colWidths'))
|
||||
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 10),
|
||||
]))
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def create_section_chart(chart_data):
|
||||
"""Create a chart for report sections"""
|
||||
chart_type = chart_data.get('type', 'bar')
|
||||
drawing = Drawing(400, 200)
|
||||
|
||||
if chart_type == 'bar':
|
||||
chart = VerticalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 30
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
chart.data = chart_data['data']
|
||||
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
|
||||
chart.valueAxis.valueMin = 0
|
||||
|
||||
# Style bars
|
||||
for i in range(len(chart_data['data'])):
|
||||
chart.bars[i].fillColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
|
||||
|
||||
elif chart_type == 'line':
|
||||
chart = HorizontalLineChart()
|
||||
chart.x = 50
|
||||
chart.y = 30
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
chart.data = chart_data['data']
|
||||
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
|
||||
|
||||
# Style lines
|
||||
for i in range(len(chart_data['data'])):
|
||||
chart.lines[i].strokeColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
|
||||
chart.lines[i].strokeWidth = 2
|
||||
|
||||
drawing.add(chart)
|
||||
return drawing
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
report = {
|
||||
'title': 'Quarterly Business Report',
|
||||
'subtitle': 'Q4 2023 Performance Analysis',
|
||||
'author': 'Analytics Team',
|
||||
'date': 'January 15, 2024',
|
||||
'period': 'October - December 2023',
|
||||
'sections': [
|
||||
{
|
||||
'title': 'Executive Summary',
|
||||
'content': """
|
||||
This report provides a comprehensive analysis of our Q4 2023 performance.
|
||||
Overall, the quarter showed strong growth across all key metrics, with
|
||||
revenue increasing by 25% year-over-year and customer satisfaction
|
||||
scores reaching an all-time high of 4.8/5.0.
|
||||
|
||||
Key highlights include the successful launch of three new products,
|
||||
expansion into two new markets, and the completion of our digital
|
||||
transformation initiative.
|
||||
""",
|
||||
'subsections': [
|
||||
{
|
||||
'title': 'Key Achievements',
|
||||
'content': 'Successfully launched Product X with 10,000 units sold in first month.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'title': 'Financial Performance',
|
||||
'content': """
|
||||
The financial results for Q4 exceeded expectations across all categories.
|
||||
Revenue growth was driven primarily by strong product sales and increased
|
||||
market share in key regions.
|
||||
""",
|
||||
'table_data': {
|
||||
'data': [
|
||||
['Metric', 'Q3 2023', 'Q4 2023', 'Change'],
|
||||
['Revenue', '$2.5M', '$3.1M', '+24%'],
|
||||
['Profit', '$500K', '$680K', '+36%'],
|
||||
['Expenses', '$2.0M', '$2.4M', '+20%'],
|
||||
],
|
||||
'colWidths': [2*inch, 1.5*inch, 1.5*inch, 1*inch]
|
||||
},
|
||||
'chart_data': {
|
||||
'type': 'bar',
|
||||
'data': [[2.5, 3.1], [0.5, 0.68], [2.0, 2.4]],
|
||||
'categories': ['Q3', 'Q4']
|
||||
}
|
||||
},
|
||||
{
|
||||
'title': 'Market Analysis',
|
||||
'content': """
|
||||
Market conditions remained favorable throughout the quarter, with
|
||||
strong consumer confidence and increasing demand for our products.
|
||||
""",
|
||||
'chart_data': {
|
||||
'type': 'line',
|
||||
'data': [[100, 120, 115, 140, 135, 150]],
|
||||
'categories': ['Oct', 'Nov', 'Dec', 'Oct', 'Nov', 'Dec']
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
create_report("sample_report.pdf", report)
|
||||
print("Report created: sample_report.pdf")
|
||||
Reference in New Issue
Block a user