critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

ColumnMappingConfig is an immutable Dart class with fields: orgId (String), columnDefinitions (List<ColumnDefinition>), schemaVersion (String), and effectiveFrom (DateTime)
ColumnDefinition includes: bufdirColumnName (String), internalFieldPath (String), isRequired (bool), allowedTypes (List<BufdirColumnType>), and nullPolicy (NullValuePolicy)
NullValuePolicy is a sealed class with three variants: Omit, DefaultValue(dynamic value), and Placeholder(String text)
OrgColumnOverride allows per-organisation field-level overrides with orgId, columnName, and override NullValuePolicy
MappedRow is an immutable value object containing: rowIndex (int), mappedFields (Map<String, dynamic>), sourceActivityId (String), and orgId (String)
Abstract interface ColumnMappingConfigRepository exposes Future<ColumnMappingConfig> getConfig(String orgId) and throws ConfigNotFoundException for unknown org IDs
Abstract interface BufdirColumnMapperService exposes Future<List<MappedRow>> mapRows(List<ActivityRow> rows, String orgId) signature
All classes are in the bufdir_export domain layer with no Flutter or Supabase imports
Dart equatable or manual == and hashCode implemented on all value objects
All models include copyWith methods for immutable updates

Technical Requirements

frameworks
Dart (pure domain layer, no Flutter dependencies)
data models
ActivityRow
ColumnMappingConfig
MappedRow
NullValuePolicy
OrgColumnOverride
ColumnDefinition
performance requirements
Data models must be allocation-efficient — avoid deep nesting beyond 3 levels
Sealed classes used for NullValuePolicy to enable exhaustive switch without runtime overhead
security requirements
No PII stored directly in mapping config — configs reference field paths only
ColumnMappingConfig must be treated as read-only after construction (immutable pattern)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Place all types under lib/features/bufdir_export/domain/models/. Use Dart sealed classes (Dart 3+) for NullValuePolicy — this enables compile-time exhaustive handling in the mapper engine. Do NOT use json_serializable for domain models; implement fromJson/toJson manually to avoid code generation coupling in the domain layer. Define BufdirColumnType as an enum covering: string, integer, decimal, date_iso8601, boolean.

ColumnMappingConfigRepository should be abstract so the infrastructure layer (Supabase) can be swapped. Keep MappedRow free of business logic — it is a pure data carrier. Follow clean architecture: domain layer must compile with zero external package imports beyond equatable.

Testing Requirements

Unit tests for all model constructors, equality checks, copyWith correctness, and JSON serialization round-trips. Test NullValuePolicy sealed class exhaustive switch coverage. Test OrgColumnOverride merging logic with NHF, Blindeforbundet, and HLF fixture configs. Target 100% line coverage on domain models.

No integration tests required at this layer.

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.