critical priority high complexity backend pending backend specialist Tier 3

Acceptance Criteria

The serializer accepts the raw aggregation output (list of ActivityAggregateRow) and returns a typed BufdirPayload object
Activities are grouped by Bufdir category code, producing one CategoryGroup per code present in the input
Each CategoryGroup contains a list of PeerMentorTotal records: { peer_mentor_id, peer_mentor_name, activity_count, participant_count }
Grand totals (sum across all categories and all peer mentors) are computed and embedded at the payload root level
Org-level metadata fields are populated: organisation_number (9-digit Norwegian org nr), reporting_period_start (ISO date), reporting_period_end (ISO date), generated_at (ISO datetime UTC)
Duplicate warnings from the aggregation step are embedded as a top-level `duplicate_warnings` array in the payload
Running the serializer twice on the same input produces byte-identical JSON output (deterministic sort order, no timestamps injected inside records)
The payload schema version field is set to a constant string (e.g. '1.0') to allow future migrations
If the input aggregation list is empty, the serializer returns a valid payload with zero-value totals and an empty category groups array rather than throwing
All numeric fields use integer types — no floating-point representations for counts

Technical Requirements

frameworks
Dart (Supabase Edge Function / Deno runtime)
dart:convert for JSON serialisation
apis
Supabase Edge Function invocation context
Bufdir payload schema v1.0
data models
ActivityAggregateRow
BufdirPayload
CategoryGroup
PeerMentorTotal
DuplicateWarning
OrgMetadata
performance requirements
Serialization of up to 500 peer mentors across 10 categories must complete in under 200 ms
Memory footprint must remain under 50 MB — avoid loading activity raw rows; operate only on the aggregated input
Use a single-pass groupBy algorithm, not nested loops
security requirements
Org number must be validated as a 9-digit string before embedding — reject malformed values with a clear error
No personally identifiable data beyond peer_mentor_name and org-level data should appear in the payload root; sensitive detail stays in PeerMentorTotal records only
Payload must not include internal database IDs or Supabase UUIDs in any exported field

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Define Dart data classes (or freezed if already used in the project) for BufdirPayload, CategoryGroup, PeerMentorTotal. Use `groupBy` from `package:collection` to avoid manual map-building loops. Sort output deterministically: category groups sorted by Bufdir code (alphabetical), peer mentor rows sorted by peer_mentor_id. This ensures idempotency.

The `generated_at` timestamp should be passed in as a parameter (not read from `DateTime.now()` inside the function) so tests can control it and the output remains deterministic. Keep the serializer as a pure Dart function with no Supabase client dependency — it receives a typed input and returns a typed output. The edge function handler (separate file) is responsible for fetching data and calling the serializer. This separation makes the serializer trivially unit-testable without mocking Supabase.

Testing Requirements

Unit tests (dart:test) are the primary testing vehicle. Required scenarios: (1) single peer mentor, single category — verify exact output shape, (2) multiple peer mentors across multiple categories — verify grouping and totals, (3) duplicate warnings passthrough — verify they appear verbatim in output, (4) empty input list — verify valid zero-payload returned, (5) two runs on identical input produce identical JSON string (idempotency test using jsonEncode comparison), (6) org number validation — invalid format returns error not panic, (7) missing optional fields in input default gracefully. Integration test: wire serializer to the mock aggregation output from task-003 and assert the resulting BufdirPayload can be re-parsed from JSON without loss (round-trip test). Target 90%+ line coverage.

Component
Bufdir Format Serializer
data 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.