critical priority medium complexity backend pending backend specialist Tier 6

Acceptance Criteria

An ActivityCategoryMappingService (or configuration class) exists that maps every internal activity type ID to exactly one Bufdir category code
Bufdir category codes supported include at minimum: individual_guidance, group_activity, phone_contact, digital_contact, and any other codes required by the current Bufdir reporting schema
If an internal activity type ID has no mapping defined, the service throws a typed UnmappedActivityTypeException rather than silently dropping the activity
The mapping is data-driven (loaded from a configuration source — Supabase table or hardcoded const map) and does not require code changes to add a new mapping
BufdirAggregationService.runAggregationPipeline applies the mapping to the aggregated activity counts before assembling the final result
categoryMappedTotals in BufdirAggregationResult contains a map of Bufdir category code → count, covering all Bufdir categories (zero counts explicitly included for empty categories)
The total count across all Bufdir categories equals the total deduplicated activity count (no activities are lost or double-counted by the mapping step)
Mapping is applied consistently for all three organizations (NHF, Blindeforbundet, HLF) — orgs may have different internal activity types, all must map to the same Bufdir category codes
A mapping validation function can be called in tests to assert all known internal activity type IDs have a valid mapping

Technical Requirements

frameworks
Flutter
Dart
Riverpod
Supabase Dart SDK
apis
Supabase PostgREST API (if mapping is stored in a Supabase table)
ActivityCategoryMappingService (internal)
data models
ActivityType
BufdirCategoryCode
ActivityCategoryMapping
CategoryMappedTotals
performance requirements
Mapping lookup must be O(1) per activity type — use a Dart Map<String, BufdirCategoryCode> loaded once at service initialization
If mapping is loaded from Supabase, cache it in memory for the duration of the aggregation run to avoid repeated DB calls
security requirements
The mapping configuration must be read-only for non-admin users — only administrators can modify Bufdir category mappings
Mapping changes must be audit-logged (who changed which mapping, when) to support grant compliance traceability

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Define BufdirCategoryCode as a Dart enum with values matching official Bufdir nomenclature. Define ActivityCategoryMapping as a const Map or load from a Supabase table 'bufdir_category_mappings'. Prefer a Supabase-backed mapping for production flexibility (allows adding new mappings without a code release), but initialize with a hardcoded fallback const map for offline/test environments. In ActivityCategoryMappingService, implement applyMapping(Map activityTypeCounts) → Map.

Ensure all BufdirCategoryCode values appear in the output, defaulting to 0. Throw UnmappedActivityTypeException (a typed exception class) for unmapped types — this makes errors visible during testing rather than silently skewing Bufdir submissions. Add a validateAllMappingsExist(List knownActivityTypeIds) method for use in validation tests.

Testing Requirements

Unit tests: test that all known internal activity type IDs map to the correct Bufdir category code. Test that an unmapped type ID throws UnmappedActivityTypeException. Test that categoryMappedTotals sums equal deduplicated activity total (conservation test). Test zero-count categories are present in the output map.

Test that mapping is applied correctly for activity mixes from NHF, Blindeforbundet, and HLF internal type IDs. Use flutter_test. Validation test: call the mapping validation function with a complete set of seeded activity types and assert zero unmapped types. Target 95% branch coverage on mapping logic.

Component
Bufdir Aggregation Service
service high
Epic Risks (4)
high impact high prob integration

NHF members can belong to up to 5 local chapters. When a participant has activities registered under different chapter IDs within the same reporting period, deduplication requires a reliable cross-chapter identity key. If national IDs are absent for some members (a known data quality issue in NHF's systems), the deduplication service may fail to identify duplicates, resulting in inflated counts submitted to Bufdir.

Mitigation & Contingency

Mitigation: Implement a multi-attribute identity matching strategy: primary match on national_id, fallback to (full_name + birth_year + municipality) composite key. Expose a low-confidence match list in DeduplicationAnomalyReport that coordinators can review and manually resolve before submission.

Contingency: If identity data quality is too poor for reliable automated deduplication for specific organisations, add an organisation-level config flag that disables cross-chapter deduplication for that org and requires coordinators to manually review the anomaly report before submitting.

high impact medium prob integration

The geographic distribution algorithm must resolve NHF's 1,400 local chapter hierarchy to regional aggregates. If the organizational unit hierarchy in the database is incomplete (missing parent-child relationships for some chapters), the geographic service will silently drop activities from unmapped chapters, producing an understated geographic breakdown.

Mitigation & Contingency

Mitigation: Add a hierarchy completeness validation step in GeographicDistributionService that counts activities without a resolvable region assignment and surfaces them as an 'unmapped_activities' field in the distribution result. Block export if unmapped_activities > 0.

Contingency: Provide a 'national' fallback bucket for activities from chapters with no region assignment, clearly labelled in the preview screen so coordinators are alerted to fix the org hierarchy data before re-running aggregation.

high impact low prob technical

BufdirAggregationService orchestrates four dependent services. If one service (e.g., GeographicDistributionService) throws mid-pipeline, the partially assembled metrics payload may be silently cached or returned as if complete, resulting in a Bufdir submission missing the geographic breakdown section.

Mitigation & Contingency

Mitigation: Implement the orchestrator as a transactional pipeline using Dart's Result type pattern: each stage returns Either<AggregationError, PartialResult>, and the orchestrator only proceeds if all stages succeed. The final payload is only assembled and persisted when all stages return success.

Contingency: If a partial failure state reaches the UI, the AggregationProgressIndicator must display a specific stage failure message with a retry option that re-runs only the failed stage rather than the full pipeline.

medium impact medium prob scope

Internal activity types that have no corresponding Bufdir category in the mapping configuration will cause the aggregation to silently exclude those activities from the final counts. Coordinators may not notice the omission until Bufdir queries why submission totals are lower than expected.

Mitigation & Contingency

Mitigation: BufdirAggregationService must produce an unmapped_activity_types list as part of its output. If any internal activity types are unmapped, display a blocking warning in the AggregationSummaryWidget listing the unmapped types before allowing the coordinator to proceed to export.

Contingency: Allow coordinators to temporarily assign unmapped activity types to a Bufdir 'other' catch-all category as an emergency workaround, with an audit flag indicating manual override was applied for that submission.