high priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

BufdirRequestMapper.map() accepts the canonical internal BufdirPayload type and returns a BufdirApiRequest with no nullable required fields left unset
All required Bufdir API fields (organisation ID, reporting period, activity counts, participant breakdown) are present and correctly mapped in the output
Optional sections (travel expenses, attachment references) are omitted from the output when not present in the internal payload, rather than serialised as null
Field-level validation rejects payloads where activity_count < 0, reporting_period dates are reversed, or organisation_id is empty, throwing a typed BufdirMappingException
Mapper is a pure Dart class with no dependency on Dio, Supabase, or any I/O — injectable via constructor for unit testing
Unit tests cover: happy-path full payload, minimal payload (required fields only), optional sections omitted, each validation error path individually
Mapper correctly handles Norwegian date formatting required by Bufdir API (ISO 8601 with timezone offset)
Field name casing and nesting in output matches Bufdir API schema exactly (verified against schema doc or sandbox response)
Mapper does not mutate the input payload object
All mapped numeric totals (hours, activities, participants) match the sum of constituent records in the internal payload within floating-point tolerance of 0.001

Technical Requirements

frameworks
Flutter
Dart
BLoC
Riverpod
apis
Bufdir Reporting API
data models
bufdir_column_schema
bufdir_export_audit_log
activity
activity_type
annual_summary
performance requirements
Mapping of a payload with up to 500 activity records must complete in under 50ms on a mid-range device
No memory allocations larger than 2MB for payload transformation
security requirements
Mapper must not log any PII fields (names, personnummer, contact details) — only structural metadata
Organisation credentials never passed through or stored by the mapper — credentials handled exclusively in the HTTP layer
Input payload validated before any field access to prevent null-dereference on malformed internal data
Bufdir API schema version must be pinned in the mapper to prevent silent schema drift

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Integration Task

Handles integration between different epics or system components. Requires coordination across multiple development streams.

Implementation Notes

Define a sealed BufdirApiRequest class (or use freezed) so all output fields are type-safe and non-nullable at the type level. Place the mapper in `lib/features/bufdir/data/mappers/bufdir_request_mapper.dart`. Keep the Bufdir API schema version in a single constant (e.g., `BufdirApiSchemaVersion.v2_1`) so version upgrades are a one-line change. For optional sections, use a dedicated `_mapTravelExpenses()` private method that returns null when the list is empty — the caller then uses `if (expenses != null) request.travelExpenses = expenses`.

Avoid dynamic maps; use strongly-typed freezed DTOs for both input and output. Register a BufdirColumnSchema lookup so field mappings can be driven by the org-level bufdir_column_schema config rather than being hardcoded, supporting future schema versions without code changes.

Testing Requirements

Unit tests using flutter_test covering: (1) full happy-path mapping with all optional sections populated, (2) minimal payload with required fields only, (3) all three validation error scenarios as typed exceptions, (4) Norwegian date formatting edge cases (DST boundary, year-end period), (5) float summation accuracy across 500 records. No integration or widget tests needed — mapper is pure logic. Target 100% branch coverage. Tests must be runnable with `flutter test` without network or database access.

Component
Bufdir API Client
service high
Epic Risks (2)
medium impact high prob dependency

Norse Digital Products has not yet completed API negotiations with Bufdir. If negotiations stall or Bufdir's API design diverges significantly from expectations, the API client may need substantial rework, or the epic may be blocked indefinitely.

Mitigation & Contingency

Mitigation: Implement the client against a locally defined stub of the expected Bufdir API schema. Isolate all Bufdir-specific schema mapping in a single adapter class so that changes to the actual API schema require changes in only one place. Keep the epic in 'interface-ready' status until real API credentials are available for integration testing.

Contingency: If API negotiations are not completed within the planned window, defer this epic without impact on any other epic — the PDF/CSV fallback path from Epics 1–4 delivers full standalone value. Mark the epic as blocked and resurface when negotiations conclude.

high impact low prob security

Bufdir API credentials stored in the application or edge function environment could be exposed through misconfigured secrets management, log leakage, or a compromised deployment pipeline, allowing unauthorised Bufdir submissions on behalf of the organisation.

Mitigation & Contingency

Mitigation: Store all Bufdir API credentials exclusively in Supabase Vault (or the integration credential vault component), never in client-side code or environment variables accessible to the Flutter app. Transmit credentials only from within the edge function, not from the Flutter client. Implement credential rotation support from the outset.

Contingency: If a credential leak is detected, immediately revoke and rotate the affected API credentials through Bufdir's credential management portal, audit submission logs for any unauthorised calls, and notify Bufdir's technical contact per the API agreement's security incident clause.