critical priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

BufdirColumnMapper implements BufdirColumnMapperService and accepts ColumnMappingConfigRepository via constructor injection
mapRows fetches ColumnMappingConfig for the given orgId once per invocation and applies it to all rows without re-fetching
Field path resolution supports dot-notation for nested ActivityRow fields (e.g., 'contact.birthYear')
NullValuePolicy.Omit removes the column key from the mapped output entirely for that row
NullValuePolicy.DefaultValue(value) substitutes the configured value when the source field is null
NullValuePolicy.Placeholder(text) inserts the placeholder string into the mapped output
OrgColumnOverride entries are applied after base config, overriding the column's NullValuePolicy only — not its name or type
BufdirColumnMapper does not contain any if/switch on orgId — all org differences are driven exclusively by ColumnMappingConfig data
mapRows processes rows in order and preserves rowIndex from the input list
ConfigNotFoundException from the repository propagates as-is without wrapping
mapRows completes within 2 seconds for batches of up to 500 ActivityRow inputs on mid-range hardware

Technical Requirements

frameworks
Dart
Riverpod (for DI wiring at app layer — mapper itself is pure)
data models
ActivityRow
ColumnMappingConfig
ColumnDefinition
NullValuePolicy
OrgColumnOverride
MappedRow
performance requirements
Config fetched once per mapRows call — cache within the call, not across calls (stateless service)
Dot-notation path resolver must avoid reflection — use pre-built field accessor maps keyed by path string
Process 500 rows in under 2 seconds synchronously (no isolate needed at this scale)
security requirements
Do not log field values during mapping — log column names and row indices only
Null field values must not propagate as the string 'null' without an explicit NullValuePolicy.Placeholder

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place implementation under lib/features/bufdir_export/infrastructure/mapping/bufdir_column_mapper.dart. Build a FieldAccessor abstraction: a Map populated at mapper construction time from the config's column definitions. This avoids reflection and is testable. Dot-notation resolution: split path by '.' and traverse ActivityRow.toMap() recursively — define ActivityRow.toMap() as a canonical field map in the domain model.

Override application: merge base ColumnDefinition list with OrgColumnOverride list using Map keyed by bufdirColumnName — override entries win. NullValuePolicy handling should be a private _applyNullPolicy(NullValuePolicy policy, dynamic value) method returning an Option-like result to signal omission vs substitution. Avoid Future.wait on per-row async — batch the single config fetch before the row loop.

Testing Requirements

Unit tests with mock ColumnMappingConfigRepository. Test NHF, Blindeforbundet, and HLF fixture configs (use realistic JSON fixtures, not hand-crafted mocks). Test each NullValuePolicy variant with null and non-null source values. Test dot-notation path resolution for 1, 2, and 3 levels of nesting.

Test OrgColumnOverride application order (override must win). Test that no org-specific code branches exist by verifying all three org configs produce correctly differentiated output from a single shared input list. Test empty row list returns empty output. Test ConfigNotFoundException propagation.

Performance test: assert 500 rows map in under 2000ms. Target 95%+ line coverage.

Component
Bufdir Column Mapper
service high
Epic Risks (2)
medium impact medium prob scope

Bufdir's column schema may have per-field business rules (conditional required fields, cross-field validation, organisation-specific category taxonomies) that cannot be expressed in a simple key-value mapping configuration. If the configuration model is too simple, supporting NHF's specific requirements will require hardcoded organisation logic, undermining the configuration-driven design.

Mitigation & Contingency

Mitigation: Design the column configuration schema as a full JSON document supporting field-level transformation rules, conditional expressions, and org-specific value enumerations. Validate the design against a real NHF Bufdir Excel template before implementation begins.

Contingency: If the configuration model cannot express all required rules, implement a thin transformation plugin interface where org-specific logic can be added as a named Dart class registered against the organisation ID, with the JSON config covering only the common cases.

high impact medium prob technical

For large organisations like NHF with potentially tens of thousands of activity records, the full export pipeline (query + map + generate + bundle + upload) may exceed Supabase Edge Function execution time limits (typically 150s), causing silent timeouts that leave audit records in a pending state indefinitely.

Mitigation & Contingency

Mitigation: Implement the orchestrator as a background Dart isolate with progress streaming rather than a synchronous Edge Function call. Use chunked processing for the query and mapping phases to reduce peak memory usage. Profile against realistic NHF data volumes in a staging environment.

Contingency: If processing time cannot be reduced below the timeout threshold, implement an asynchronous job model where the export is queued, processed in the background, and the user is notified via push notification when the download is ready — treating it as an eventual rather than synchronous operation.