Chapter 36 Exercises: Automated Report Generation

These exercises build from foundational Jinja2 concepts up to full report pipelines. Work through each tier in order — later tiers assume the skills from earlier ones.


Tier 1 — Foundations

Get comfortable with the core tools before combining them.

Exercise 1.1 — Your First Jinja2 Template

Create a Jinja2 template string (not a file) that displays a simple invoice header. The template should use {{ }} to display: - Company name - Invoice number - Invoice date - Client name

Write the Python code to render the template with sample data and print the output.

Expected output (approximate):

ACME CORP
Invoice #1042
Date: January 15, 2025
Billed to: Sandra Chen

Exercise 1.2 — Loops in Templates

You have a list of sales data for five products. Write a Jinja2 template that renders an HTML table with columns for Product Name, Units Sold, and Revenue. Use a {% for %} loop in the template.

The Python side should provide a list of dictionaries. Render the template and print the resulting HTML.


Exercise 1.3 — Jinja2 Conditionals

Extend Exercise 1.2's template to add a fourth column: "Status". If a product's revenue is above $10,000, show "Strong". If between $5,000 and $10,000, show "Moderate". If below $5,000, show "Needs Attention".

Use {% if %}, {% elif %}, and {% else %} inside the table row.


Exercise 1.4 — Custom Filters

Register three custom Jinja2 filters: 1. currency — formats a number as $1,234.56 2. thousands — formats an integer with comma separators: 12,345 3. change_arrow — takes a float and returns "▲ 5.2%" for positive or "▼ 2.1%" for negative values

Test each filter in a short template that displays one sample value through each filter.


Exercise 1.5 — Template Files

Move Exercise 1.3's template from a Python string into an actual file (templates/product_table.html). Use FileSystemLoader to load it. The Python script should: 1. Create the templates/ directory if it doesn't exist 2. Write the template to the file 3. Use Environment(loader=FileSystemLoader("templates")) to render it 4. Print the output


Tier 2 — Applied

Build real, working report components.

Exercise 2.1 — HTML Report with Embedded CSS

Build a complete, styled HTML report file for a fictional company's quarterly sales summary. Requirements: - Must have a proper <!DOCTYPE html> structure - Inline CSS that makes the report look professional (at minimum: a dark header bar, alternating row colors in tables, positive numbers in green, negative in red) - One table with at least five rows - A section showing three KPI values prominently displayed

Save the output as quarterly_report.html.


Exercise 2.2 — Matplotlib Chart to Base64

Write a function sales_bar_chart(categories, values, title) that: 1. Creates a matplotlib bar chart 2. Sets matplotlib.use("Agg") so it works without a display 3. Converts the figure to a base64-encoded PNG string 4. Returns the data URI string (data:image/png;base64,...)

Then embed the result in an HTML <img> tag and save to a file. Verify it opens in a browser with the chart visible.


Exercise 2.3 — Excel Report with openpyxl

Create an Excel workbook with two sheets: - Sheet 1 ("Summary"): Company name in a large bold font, five KPI values with currency or percentage formatting, and a total row - Sheet 2 ("Detail"): A table of at least 8 rows with headers, alternating row fills (white and light blue), and column widths auto-sized to content

Save as business_report.xlsx and verify it opens correctly.


Exercise 2.4 — Excel Chart

Extend Exercise 2.3 to add a bar chart on Sheet 2 that visualizes one of the numeric columns. The chart should: - Have a title - Be positioned next to the data (not on top of it) - Use column chart type (vertical bars)


Exercise 2.5 — Report Parameterization

Write a ReportParameters dataclass with the following fields: - report_title: str - start_date: date - end_date: date - include_charts: bool = True - output_format: Literal["html", "excel", "both"] = "both"

Add a period_label property that returns a human-readable string like "January 2025" (for single-month ranges) or "Q1 2025" (for quarter ranges — detect this if start is Jan 1, Apr 1, Jul 1, or Oct 1 and end is 3 months later).

Write three test cases showing the property returning correct values for different date ranges.


Tier 3 — Integration

Connect reports to data sources and delivery systems.

Exercise 3.1 — Data Pipeline to Report

Write a complete script that: 1. Reads sales data from a CSV file (create the sample CSV as part of the exercise) 2. Aggregates it (total revenue per region, average order value) 3. Passes the aggregated data to a Jinja2 template 4. Renders and saves an HTML report

The CSV should have columns: date, region, product, units, revenue.


Exercise 3.2 — Auto-Generated Executive Summary

Write a function generate_executive_summary(data: dict) -> str that takes a report data dictionary and returns an HTML string with: - Whether revenue hit target (and by how much) - The best-performing region/product - The most concerning metric (whatever is furthest from its target) - A forward-looking sentence based on trends (e.g., if revenue increased 3 months in a row, say so)

Test it with three different data scenarios: exceeding targets, meeting targets, and missing targets.


Exercise 3.3 — Multi-Format Report Generator

Write a class ReportGenerator with methods: - to_html(params, data) -> Path - to_excel(params, data) -> Path - to_pdf(params, data) -> Path (use WeasyPrint if installed, otherwise save HTML with a .pdf placeholder extension)

All three methods should use the same underlying data and produce consistent reports in different formats.


Exercise 3.4 — Report Email Assembly

Write a function build_report_email(report_data: dict, pdf_path: Path) -> MIMEMultipart that: 1. Takes report data and a PDF file path 2. Builds an HTML email body that includes the executive summary and a link to the full report 3. Attaches the PDF file 4. Returns the complete MIMEMultipart object (don't send it — just build and return it)

Write a test that calls the function and verifies the MIME object has the correct parts (HTML body and PDF attachment).


Exercise 3.5 — Template Inheritance

Create a base template (base.html) with the overall report structure and three child templates: - executive_report.html — Just the summary and KPIs - operational_report.html — Full tables and charts - client_report.html — Stripped down, friendly language for external clients

Each child template overrides at least two {% block %} sections. Render all three with the same data and compare the outputs.


Tier 4 — Challenge

Harder problems that require design thinking and multiple components.

Exercise 4.1 — Scheduled Monthly Report

Write a complete report pipeline that: 1. Detects the current date and calculates the prior month's date range 2. Loads data (from a SQLite database or CSV files — your choice) 3. Generates both HTML and PDF versions 4. Saves them to an organized directory structure: reports/YYYY/MM/ 5. Logs each run to a file with timestamp, status, and file paths

Include error handling so that if any step fails, the error is logged and (if on a non-first day of the month) the script exits gracefully without generating a partial report.


Exercise 4.2 — Comparison Report

Build a report that shows two periods side by side: this month vs. last month, or this year vs. last year. Requirements: - Each metric shows both values and the change (absolute and percentage) - Green/red color coding based on whether the change is favorable - A trend sparkline (tiny chart) for each metric - The report correctly handles edge cases like division by zero (when the prior period value was 0)


Exercise 4.3 — Dynamic Report Sections

Build a report system where the sections that appear depend on the data. Specifically: - If any region has YoY revenue decline > 5%, add a "Regions Requiring Attention" section - If new customer acquisition exceeded target by > 20%, add a "Growth Spotlight" callout box - If gross margin improved vs. prior month, show a margin trend section; if it declined, show a margin analysis section with more detail

Use Jinja2 conditionals and Python logic working together to control what appears.


Exercise 4.4 — Excel Dashboard Sheet

Build an Excel report that includes a "Dashboard" sheet designed to print on one page (A4 landscape). Requirements: - Company logo placeholder (a colored rectangle with text) - Three KPI value boxes with large fonts and color coding - Two charts on the same sheet - A small summary table - Print area set to the Dashboard sheet only - Page setup configured for one-page landscape printing


Tier 5 — Stretch

Open-ended problems for learners who want to go further.

Exercise 5.1 — Report Template Library

Design and implement a ReportTemplateLibrary class that manages a collection of report templates. It should: - Store templates as Jinja2 template strings or file paths - Support template versioning (keep prior versions, render any version by number) - Allow templates to be rendered with validation (required context keys checked before rendering) - Export the full library to a JSON manifest for documentation

Exercise 5.2 — Automated Report Audit Trail

Every time a report is generated, write a JSON record to a log file with: timestamp, report type, parameters, data hash (MD5 of the data dictionary), output files, and generation time in milliseconds. Build a ReportAuditViewer that reads the log and produces a summary: how many reports were generated, average generation time, which parameters were most common.

Exercise 5.3 — Self-Service Report Builder

Build a command-line interface (using argparse) for report generation with the following options:

python generate_report.py --type monthly --year 2024 --month 11 --region northeast --format pdf,excel --email priya@acmecorp.com

All parameters should have sensible defaults. The script should validate inputs before attempting generation and provide helpful error messages for invalid combinations.

Exercise 5.4 — Report Diff

Write a function compare_reports(data_this_period: dict, data_prior_period: dict) -> dict that produces a "diff dictionary" showing what changed between two reports. Then write a Jinja2 template that renders this diff as a compact "What Changed" summary — useful for stakeholders who already read last month's report and only want the delta.