high priority medium complexity backend pending backend specialist Tier 5

Acceptance Criteria

The service exposes a single public function `Future<Uint8List> generatePdf(BufdirPayload payload, {Uint8List? logoBytes})` returning the complete PDF as bytes
Peer mentor rows in each category table are sorted alphabetically by peer_mentor_name for easy scanning
Category subtotals rows are visually distinct: bold font, light grey background fill
Grand totals row on the summary page is bold with a darker background fill
The reporting period header displays as 'Rapporteringsperiode: DD.MM.YYYY – DD.MM.YYYY' in Norwegian
Org metadata (name, org number) appear on the cover/first page header
Duplicate warnings appendix table columns: Dato, Likeperson, Kontaktidentifikator, Kategori — with Norwegian headers
Long duplicate warnings lists (>30 rows) automatically flow across multiple pages without truncation
Tables with more than 25 peer mentor rows per category automatically add a continuation header on the next page
The service returns a valid PDF Uint8List; calling `PdfDocument.openData()` on the result does not throw
The suggested filename is `bufdir_rapport_{org_number}_{YYYY-MM}.pdf` returned alongside the bytes
Total PDF file size for a typical report (50 peer mentors, 6 categories, 10 warnings) is under 500 KB

Technical Requirements

frameworks
Dart `pdf` package
Dart (Supabase Edge Function / Deno runtime)
dart:typed_data
apis
Internal PdfReportTemplate API (from task-009)
data models
BufdirPayload
CategoryGroup
PeerMentorTotal
DuplicateWarning
OrgMetadata
performance requirements
Full PDF generation (template + data binding + serialisation to bytes) must complete in under 3 seconds for a 50-mentor, 6-category report
Memory usage during generation must stay under 128 MB — use streaming page construction via pw.MultiPage rather than building all pages in memory simultaneously
security requirements
Contact identifiers in the warnings appendix must be partially masked (show last 4 characters only) to limit PII exposure in the downloadable report
The PDF must not contain embedded metadata beyond the declared document properties (no author email, no file paths, no Supabase URLs)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

The data binding layer instantiates `PdfReportTemplate` (task-009) and calls its builder methods, passing resolved data. Keep data transformation out of the template — sort and format data before passing it to the template builder, not inside it. For partial masking of contact identifiers in the warnings appendix, apply a helper: `String maskIdentifier(String id) => '*' * (id.length - 4) + id.substring(id.length - 4)` — guard against strings shorter than 4 characters. Use `pw.MultiPage` with the `build` callback returning all content widgets so the pdf package handles page breaks automatically.

Test page break behaviour by using fixture data with >25 rows in a single category. The `generatePdf` function should be async only if logo bytes are fetched internally; if the caller pre-fetches logos, the function can be synchronous. Align the return type with `generateCsv` — both return `Uint8List` — so the edge function's storage upload path is identical for both export types.

Testing Requirements

Unit tests (dart:test): (1) generatePdf() with a well-formed payload returns non-empty Uint8List, (2) returned bytes start with the PDF magic number (%PDF), (3) payload with no duplicate warnings produces a PDF without an appendix page (verify via page count compared to a warnings-present payload), (4) payload with 40 duplicate warnings produces a multi-page appendix without truncation (assert page count > warnings-absent baseline), (5) peer mentor rows are sorted alphabetically (inspect generated pw.Document structure before serialisation), (6) null logoBytes parameter is handled without exception. Integration test: invoke the full edge function end-to-end with fixture data and verify HTTP 200 with Content-Type application/pdf and body length > 1024 bytes. Visual regression: manually review generated PDF against design mockup for layout fidelity before merging.

Component
PDF Generation Service
service medium
Epic Risks (3)
high impact medium prob technical

Supabase Edge Functions have a default execution timeout. For large national-scope exports aggregating tens of thousands of activities across 1,400 chapters, the edge function may time out before completing, leaving coordinators with a failed export and no partial output.

Mitigation & Contingency

Mitigation: Optimise the aggregation SQL using pre-materialised aggregation views or RPC functions that run inside the database rather than iterating records in Deno. Profile query execution time against realistic production data volumes early. Request an elevated timeout limit from Supabase if needed. Implement progress checkpointing so the export can be resumed from the last completed aggregation batch.

Contingency: For organisations exceeding a configurable threshold (e.g. >5,000 activities), switch to an asynchronous export pattern: the edge function writes a 'pending' audit record and enqueues the job; the client polls for completion and is notified via Supabase Realtime when the file is ready.

medium impact medium prob technical

Server-side PDF generation in a Deno Edge Function environment restricts library choices. Many popular PDF libraries require Node.js APIs not available in Deno, or produce large bundle sizes that exceed edge function limits. Choosing the wrong library could block the entire PDF generation path.

Mitigation & Contingency

Mitigation: Spike PDF library selection as the first task of this epic, evaluating at least two Deno-compatible options (e.g. pdf-lib, jsPDF with Deno compatibility shim). Test bundle size and basic rendering before committing to an implementation. Document the chosen library's constraints.

Contingency: If no suitable Deno-native PDF library is found, generate a well-structured HTML report from the edge function and use a headless Chromium service (e.g. Browserless, Gotenberg) for HTML-to-PDF conversion, or temporarily ship CSV-only export while the PDF path is resolved.

high impact high prob technical

Peer mentors affiliated with multiple chapters (a documented NHF scenario) must not be double-counted in participant totals. Incorrect deduplication logic would overreport participation figures to Bufdir, which could be discovered during audit and damage organisational credibility.

Mitigation & Contingency

Mitigation: Define and document the deduplication contract explicitly before coding: deduplication is per-person per-period, not per-activity. Build dedicated unit tests with fixtures containing the exact multi-chapter membership patterns described in NHF's documentation. Have a NHF representative validate test fixture outputs against known-good manual counts.

Contingency: If deduplication logic produces results that cannot be verified against manual counts before launch, surface a deduplication warning in the export preview listing the affected peer mentor IDs, and require explicit coordinator acknowledgement before finalising the export.