critical priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

resolveMultiChapterDuplicates(List<ActivityRecord> records, String bufdirCategory) method added to ParticipantDeduplicationService
A member affiliated with chapters C1, C2, C3 contributing activity records from all three chapters is counted as exactly 1 unique participant in the given Bufdir category
Multi-chapter deduplication is scoped to a single Bufdir category — a member counted in category 'youth' and category 'disability-sport' contributes 1 to each, not 1 total
The maximum of 5 chapter affiliations per NHF member is enforced as a hard constraint — log a warning if a member has more than 5 affiliations but do not crash
Deduplication result includes updated audit trail with reason code 'multi_chapter_affiliation' and list of chapter IDs that contributed records for each deduplicated member
Set-union logic operates correctly when a member has activity records from only 1 chapter (no incorrect deduplication)
Method is composable with proxy deduplication: calling both passes in sequence on the same record list produces a correctly fully-deduplicated result
A member removed by proxy deduplication in task-005 is not processed again in multi-chapter deduplication (no double-removal errors)
Performance: 50,000 records across 1,400 chapters deduplicated in under 2 seconds

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase REST API (member_chapter_affiliations table, chapters table)
data models
ActivityRecord
Participant
ChapterAffiliation
DeduplicationResult
DuplicateAuditEntry
performance requirements
50,000 records processed in under 2 seconds using in-memory Set operations
Chapter affiliation map loaded once per org per deduplication run (not per record)
Supabase affiliation query uses .in_() filter with chapter IDs, not individual lookups
security requirements
Chapter affiliation data loaded only for the requesting org (RLS enforced)
Audit trail must not expose member personal data — use anonymized participant IDs only

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Build this as a second deduplication pass that runs after proxy deduplication. The input to this pass should be the already-proxy-deduplicated record list from task-005, not the raw records — this prevents interference between the two passes. The chapter affiliation map should be a `Map>` where key = normalized participant ID and value = set of chapter IDs that member belongs to. Pre-load this map once from Supabase using a batch query for all members in the org.

The set-union logic itself is then a single-pass scan: for each activity record, check if the participant's normalized ID is already in the output `Set` — if yes, add to audit trail and skip; if no, add to output set. Bufdir category scoping means you maintain one output Set per Bufdir category (use a `Map>` keyed by category). Document the composition order clearly: ProxyDedup → MultiChapterDedup → getUniqueParticipantCount (task-007). This ordering must be enforced in the calling code, not left to chance.

Testing Requirements

Covered by task-008. Implement TDD anchors: (1) single-chapter member — no deduplication applied, (2) two-chapter member with activity in both — count reduces to 1, (3) five-chapter member (maximum) — count reduces to 1 across all chapters, (4) two members each in two different chapters with no overlap — both counted, count stays 2, (5) category scoping — same member in two Bufdir categories returns count 2 (one per category). Run these locally before submitting the PR.

Component
Participant Deduplication 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.