# Sprint 18: PDF Report Generation | Document ID | DEV-002 | |-------------|---------| | Version | 1.0.0 | | Status | Draft | | Author | Kai Chappell | | Created | 2026-01-29 | | Last Updated | 2026-01-29 | --- ## Purpose This document defines **Sprint 18** of py-dvt-ate development: automated PDF report generation from test results. Reports are designed to be professional and well-presented for recruiters/clients evaluating the simulation platform. For project context, see: - `01_requirements.md` - What the system must do - `02_technical_specification.md` - How to implement - `03_architecture_decisions.md` - Why decisions were made - `04_development_plan.md` - Phase 1 MVP sprints (1-17) --- ## Feature Overview Add automated PDF report generation with: - Professional, well-presented layout suitable for external stakeholders - Clean UX with easy download from CLI and dashboard - Test metadata, results table with pass/fail status, and measurement charts - Configurable company branding --- ## Design Principles Following existing project patterns: 1. **Small, Focused Commits** - Each task = 1 commit, ~50-150 lines changed 2. **Stubs First** - Define interfaces/types before implementation 3. **Test Alongside** - Write tests immediately after implementation 4. **UK English** - characterisation, behaviour, colour 5. **Minimal Context** - Each task completable with knowledge of 1-3 files --- ## Task Breakdown ### Task 18.1: Add reporting dependencies to pyproject.toml - Add `matplotlib>=3.8` to reports optional dependency group - Verify jinja2 and weasyprint already present - **Files:** `pyproject.toml` - **Commit:** "Add matplotlib to reports dependencies" --- ### Task 18.2: Create report data models - Create `src/py_dvt_ate/reporting/models.py` - Define `ReportConfig` dataclass: - `company_name: str` - Company name for header - `logo_path: Path | None` - Optional logo image path - `include_charts: bool` - Whether to include charts - `chart_dpi: int` - Chart resolution - Define `ReportData` dataclass: - `run: TestRun` - Test run metadata - `results: list[TestResult]` - Scalar results with limits - `measurements: pd.DataFrame | None` - Time-series data - `charts: dict[str, str]` - Chart name to base64 PNG - **Files:** `src/py_dvt_ate/reporting/models.py` - **Commit:** "Add report data models" --- ### Task 18.3: Create reporting exceptions - Create `src/py_dvt_ate/reporting/exceptions.py` - Define exception hierarchy: - `ReportingError` - Base exception - `ReportGenerationError` - General generation failure - `TemplateRenderError` - HTML rendering failure - `PDFConversionError` - HTML to PDF conversion failure - `ChartGenerationError` - Chart generation failure - **Files:** `src/py_dvt_ate/reporting/exceptions.py` - **Commit:** "Add reporting exception classes" --- ### Task 18.4: Create CSS stylesheet for reports - Create `src/py_dvt_ate/reporting/templates/styles.css` - Professional styling: - A4 page setup with margins - Header with company branding - Footer with page numbers - Data tables with borders - Status badges (pass=green, fail=red, info=blue) - Summary cards with colour coding - Chart containers - Print-optimised with page breaks - **Files:** `src/py_dvt_ate/reporting/templates/styles.css` - **Commit:** "Add professional CSS stylesheet for reports" --- ### Task 18.5: Create base HTML template - Create `src/py_dvt_ate/reporting/templates/base.html` - Jinja2 base template with: - `` with CSS include - Header block with company name, logo, report metadata - Content block (for child templates) - Footer block with confidentiality notice, page numbers - WeasyPrint `@page` rules for PDF pagination - **Files:** `src/py_dvt_ate/reporting/templates/base.html` - **Commit:** "Add base HTML report template" --- ### Task 18.6: Create test report template - Create `src/py_dvt_ate/reporting/templates/test_report.html` - Extends `base.html` with sections: - **Test Overview**: name, description, status, timestamps, duration, operator - **Results Summary**: total/pass/fail cards with counts - **Results Table**: parameter, value, unit, limits, pass/fail badge - **Charts**: voltage vs temperature (if available) - **Configuration**: test config JSON (optional) - Jinja2 filters for formatting (floats, dates) - **Files:** `src/py_dvt_ate/reporting/templates/test_report.html` - **Commit:** "Add test report HTML template" --- ### Task 18.7: Implement HTML renderer - Create `src/py_dvt_ate/reporting/renderers/__init__.py` - Create `src/py_dvt_ate/reporting/renderers/html.py` - `HTMLRenderer` class: - Constructor takes `ReportConfig` - Uses `jinja2.Environment` with `PackageLoader` - `render(report_data: ReportData) -> str` method - Custom filters for number formatting - Template loading from `py_dvt_ate.reporting.templates` package - **Files:** `src/py_dvt_ate/reporting/renderers/html.py`, `src/py_dvt_ate/reporting/renderers/__init__.py` - **Commit:** "Implement HTML renderer with Jinja2" --- ### Task 18.8: Implement PDF renderer - Create `src/py_dvt_ate/reporting/renderers/pdf.py` - `PDFRenderer` class: - `render_to_file(html: str, output_path: Path) -> None` - `render_to_bytes(html: str) -> bytes` - Use WeasyPrint `HTML(string=html).write_pdf()` - Handle WeasyPrint warnings gracefully - **Files:** `src/py_dvt_ate/reporting/renderers/pdf.py` - **Commit:** "Implement PDF renderer with WeasyPrint" --- ### Task 18.9: Implement chart generator - Create `src/py_dvt_ate/reporting/charts/__init__.py` - Create `src/py_dvt_ate/reporting/charts/matplotlib_charts.py` - `ChartGenerator` class: - Constructor takes `ReportConfig` (for DPI) - `_setup_style()` - Configure matplotlib for professional appearance - `generate_voltage_vs_temperature(measurements: DataFrame) -> str` - Scatter plot with trend line - Calculate and display slope (ppm/C) - Return base64-encoded PNG - `generate_all(run, results, measurements) -> dict[str, str]` - Dispatch to appropriate chart methods based on test type - Use `matplotlib.use('Agg')` for non-interactive backend - **Files:** `src/py_dvt_ate/reporting/charts/matplotlib_charts.py`, `src/py_dvt_ate/reporting/charts/__init__.py` - **Commit:** "Implement matplotlib chart generator" --- ### Task 18.10: Implement ReportGenerator class - Create `src/py_dvt_ate/reporting/generator.py` - `IReportGenerator` Protocol: - `generate(run_id: UUID, output_path: Path | None) -> Path` - `generate_bytes(run_id: UUID) -> bytes` - `ReportGenerator` class: - Constructor: `repository`, `config`, `output_dir` - Private: `_html_renderer`, `_pdf_renderer`, `_chart_generator` - `_gather_data(run_id: UUID) -> ReportData` - Fetch run, results, measurements from repository - Generate charts if measurements available - `_generate_output_path(run: TestRun) -> Path` - Format: `{test_name}_{run_id_short}_{timestamp}.pdf` - Error handling with appropriate exception types - **Files:** `src/py_dvt_ate/reporting/generator.py` - **Commit:** "Implement ReportGenerator class" --- ### Task 18.11: Update reporting module exports - Update `src/py_dvt_ate/reporting/__init__.py` - Export public API: - `ReportGenerator`, `IReportGenerator` - `ReportConfig`, `ReportData` - All exception classes - Add module docstring with usage example - Lazy imports to handle missing optional dependencies - **Files:** `src/py_dvt_ate/reporting/__init__.py` - **Commit:** "Update reporting module public API" --- ### Task 18.12: Add ReportingConfig to app config - Update `src/py_dvt_ate/app/config.py` - Add `ReportingConfig` Pydantic model: - `company_name: str = "DVT Engineering"` - `logo_path: str | None = None` - `include_charts: bool = True` - `chart_dpi: int = 150` - Add `reporting: ReportingConfig` to `AppConfig` - **Files:** `src/py_dvt_ate/app/config.py` - **Commit:** "Add ReportingConfig to application config" --- ### Task 18.13: Add reporting section to default.yaml - Update `config/default.yaml` - Add `reporting:` section with all options - Document each option with comments - **Files:** `config/default.yaml` - **Commit:** "Add reporting configuration to default.yaml" --- ### Task 18.14: Add list-runs CLI command - Update `src/py_dvt_ate/app/cli.py` - Add `list-runs` command: - `--limit` option (default 10) - `--config` option for config file - Output format: `{id:8} {test_name:15} {status:8} {timestamp}` - Load repository from config - **Files:** `src/py_dvt_ate/app/cli.py` - **Commit:** "Add list-runs CLI command" --- ### Task 18.15: Add export-report CLI command - Update `src/py_dvt_ate/app/cli.py` - Add `export-report` command: - `run_id` argument (required) - `--output` / `-o` option for output path - `--company` option for company name override - `--config` option for config file - Support short (8-char) and full UUID lookup - Display progress and result path - **Files:** `src/py_dvt_ate/app/cli.py` - **Commit:** "Add export-report CLI command" --- ### Task 18.16: Add PDF download to dashboard - Update `src/py_dvt_ate/app/dashboard/app.py` - In results viewer page, add: - "Generate PDF Report" button (primary) - `st.download_button` for PDF download - Progress spinner during generation - Error handling for missing dependencies - Store generated PDF in `st.session_state` - **Files:** `src/py_dvt_ate/app/dashboard/app.py` - **Commit:** "Add PDF download button to dashboard" --- ### Task 18.17: Add reporting unit tests - Create `tests/unit/reporting/__init__.py` - Create `tests/unit/reporting/test_models.py` - Test ReportConfig and ReportData creation - Test default values - Create `tests/unit/reporting/test_html_renderer.py` - Test template rendering with mock data - Test custom filters - Create `tests/unit/reporting/test_chart_generator.py` - Test chart generation produces valid base64 - Test with sample DataFrame - **Files:** `tests/unit/reporting/` - **Commit:** "Add reporting unit tests" --- ### Task 18.18: Add reporting integration test - Create `tests/integration/test_report_generation.py` - End-to-end test: - Create test run with sample results in repository - Generate PDF report - Verify PDF file created and non-empty - Optionally verify PDF structure (page count) - Use pytest fixtures for repository setup - **Files:** `tests/integration/test_report_generation.py` - **Commit:** "Add report generation integration test" --- ### Task 18.19: Update CHANGELOG - Update `CHANGELOG.md` - Add `## [Unreleased]` section if not present - Document: - New `export-report` CLI command - New `list-runs` CLI command - Dashboard PDF download feature - Reporting module with PDF/HTML generation - **Files:** `CHANGELOG.md` - **Commit:** "Update CHANGELOG with report generation feature" --- ## File Structure (New Files) ``` src/py_dvt_ate/reporting/ ├── __init__.py # Task 18.11 - Public API ├── models.py # Task 18.2 - ReportConfig, ReportData ├── exceptions.py # Task 18.3 - Exception hierarchy ├── generator.py # Task 18.10 - ReportGenerator ├── renderers/ │ ├── __init__.py # Task 18.7 │ ├── html.py # Task 18.7 - HTMLRenderer │ └── pdf.py # Task 18.8 - PDFRenderer ├── charts/ │ ├── __init__.py # Task 18.9 │ └── matplotlib_charts.py # Task 18.9 - ChartGenerator └── templates/ ├── styles.css # Task 18.4 - CSS ├── base.html # Task 18.5 - Base template └── test_report.html # Task 18.6 - Report template tests/unit/reporting/ ├── __init__.py # Task 18.17 ├── test_models.py # Task 18.17 ├── test_html_renderer.py # Task 18.17 └── test_chart_generator.py # Task 18.17 tests/integration/ └── test_report_generation.py # Task 18.18 ``` --- ## Files to Modify | File | Tasks | Changes | |------|-------|---------| | `pyproject.toml` | 18.1 | Add matplotlib to reports deps | | `src/py_dvt_ate/app/config.py` | 18.12 | Add ReportingConfig | | `config/default.yaml` | 18.13 | Add reporting section | | `src/py_dvt_ate/app/cli.py` | 18.14, 18.15 | Add list-runs, export-report | | `src/py_dvt_ate/app/dashboard/app.py` | 18.16 | Add PDF download | | `CHANGELOG.md` | 18.19 | Document new features | --- ## Dependencies | Package | Version | Purpose | |---------|---------|---------| | jinja2 | >=3.1 | HTML template rendering | | weasyprint | >=60.0 | HTML to PDF conversion | | matplotlib | >=3.8 | Chart generation | All in `[project.optional-dependencies] reports` group. Install with: `pip install py-dvt-ate[reports]` --- ## Verification ### CLI Verification ```bash # List recent test runs py-dvt-ate list-runs # Generate PDF report py-dvt-ate export-report # With options py-dvt-ate export-report -o ./my_report.pdf --company "Acme Corp" # View generated PDF xdg-open ./data/reports/*.pdf ``` ### Dashboard Verification ```bash # Start dashboard streamlit run src/py_dvt_ate/app/dashboard/app.py # In browser: # 1. Navigate to Results Viewer # 2. Select a test run # 3. Click "Generate PDF Report" # 4. Click "Download PDF Report" # 5. Open downloaded PDF ``` ### Test Verification ```bash # Run unit tests pytest tests/unit/reporting/ -v # Run integration test pytest tests/integration/test_report_generation.py -v # Check coverage pytest tests/unit/reporting/ --cov=py_dvt_ate.reporting --cov-report=term-missing ``` --- ## Task Progress | Task | Status | Description | |------|--------|-------------| | 18.1 | pending | Add matplotlib dependency | | 18.2 | pending | Report data models | | 18.3 | pending | Reporting exceptions | | 18.4 | pending | CSS stylesheet | | 18.5 | pending | Base HTML template | | 18.6 | pending | Test report template | | 18.7 | pending | HTML renderer | | 18.8 | pending | PDF renderer | | 18.9 | pending | Chart generator | | 18.10 | pending | ReportGenerator class | | 18.11 | pending | Module exports | | 18.12 | pending | App config update | | 18.13 | pending | default.yaml update | | 18.14 | pending | list-runs CLI | | 18.15 | pending | export-report CLI | | 18.16 | pending | Dashboard download | | 18.17 | pending | Unit tests | | 18.18 | pending | Integration test | | 18.19 | pending | CHANGELOG update | --- **End of Sprint 18 Plan**