Fix mypy type errors in reporting module
This commit is contained in:
50
.dockerignore
Normal file
50
.dockerignore
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitea
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
*.egg-info
|
||||||
|
.eggs
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
.ruff_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
|
||||||
|
# Tests
|
||||||
|
tests/
|
||||||
|
pytest.ini
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
docs/
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Development
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
venv
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Data (will be created fresh in container)
|
||||||
|
data/
|
||||||
|
*.db
|
||||||
|
*.parquet
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.gitea/
|
||||||
@@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Fly.io deployment configuration for public demo
|
||||||
|
- Dockerfile with Python 3.11-slim and WeasyPrint dependencies
|
||||||
|
- fly.toml with scale-to-zero configuration (London region)
|
||||||
|
- .dockerignore to exclude tests, docs, and development files
|
||||||
- PDF Report Generation (Sprint 18)
|
- PDF Report Generation (Sprint 18)
|
||||||
- Professional PDF reports from test results with charts and styling
|
- Professional PDF reports from test results with charts and styling
|
||||||
- `ReportGenerator` class orchestrating data gathering, chart generation, and PDF output
|
- `ReportGenerator` class orchestrating data gathering, chart generation, and PDF output
|
||||||
|
|||||||
39
Dockerfile
Normal file
39
Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies for WeasyPrint (PDF generation)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libpango-1.0-0 \
|
||||||
|
libpangocairo-1.0-0 \
|
||||||
|
libgdk-pixbuf2.0-0 \
|
||||||
|
libffi-dev \
|
||||||
|
shared-mime-info \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
COPY pyproject.toml .
|
||||||
|
COPY src/ src/
|
||||||
|
COPY config/ config/
|
||||||
|
|
||||||
|
# Install package with reports dependencies (for PDF export)
|
||||||
|
RUN pip install --no-cache-dir -e ".[reports]"
|
||||||
|
|
||||||
|
# Create data directory for SQLite and measurements
|
||||||
|
RUN mkdir -p /app/data/measurements /app/data/reports
|
||||||
|
|
||||||
|
# Streamlit configuration
|
||||||
|
RUN mkdir -p ~/.streamlit
|
||||||
|
RUN echo '[server]\n\
|
||||||
|
headless = true\n\
|
||||||
|
address = "0.0.0.0"\n\
|
||||||
|
port = 8080\n\
|
||||||
|
enableXsrfProtection = false\n\
|
||||||
|
enableCORS = false\n\
|
||||||
|
\n\
|
||||||
|
[browser]\n\
|
||||||
|
gatherUsageStats = false\n' > ~/.streamlit/config.toml
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["streamlit", "run", "src/py_dvt_ate/app/dashboard/app.py"]
|
||||||
460
docs/05_sprint_18_report_generation.md
Normal file
460
docs/05_sprint_18_report_generation.md
Normal file
@@ -0,0 +1,460 @@
|
|||||||
|
# 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:
|
||||||
|
- `<head>` 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 <run_id>
|
||||||
|
|
||||||
|
# With options
|
||||||
|
py-dvt-ate export-report <run_id> -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**
|
||||||
16
fly.toml
Normal file
16
fly.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
app = "py-dvt-ate"
|
||||||
|
primary_region = "lhr"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
|
||||||
|
[http_service]
|
||||||
|
internal_port = 8080
|
||||||
|
force_https = true
|
||||||
|
auto_stop_machines = "stop"
|
||||||
|
auto_start_machines = true
|
||||||
|
min_machines_running = 0
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
cpu_kind = "shared"
|
||||||
|
cpus = 1
|
||||||
|
memory_mb = 512
|
||||||
@@ -84,6 +84,8 @@ disallow_incomplete_defs = true
|
|||||||
module = [
|
module = [
|
||||||
"streamlit.*",
|
"streamlit.*",
|
||||||
"plotly.*",
|
"plotly.*",
|
||||||
|
"weasyprint.*",
|
||||||
|
"matplotlib.*",
|
||||||
]
|
]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Charts are rendered to base64-encoded PNG images for embedding in HTML/PDF.
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@@ -27,9 +28,9 @@ class ChartGenerator:
|
|||||||
dpi: Resolution for chart images (dots per inch).
|
dpi: Resolution for chart images (dots per inch).
|
||||||
"""
|
"""
|
||||||
self.dpi = dpi
|
self.dpi = dpi
|
||||||
self._plt: type | None = None
|
self._plt: tuple[Any, Any] | None = None
|
||||||
|
|
||||||
def _get_matplotlib(self) -> tuple:
|
def _get_matplotlib(self) -> tuple[Any, Any]:
|
||||||
"""Lazy-load matplotlib to avoid import errors when not installed.
|
"""Lazy-load matplotlib to avoid import errors when not installed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ class PDFRenderer:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
HTML = self._get_weasyprint()
|
HTML = self._get_weasyprint()
|
||||||
return HTML(string=html).write_pdf()
|
result: bytes = HTML(string=html).write_pdf()
|
||||||
|
return result
|
||||||
except PDFConversionError:
|
except PDFConversionError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user