critical priority high complexity backend pending backend specialist Tier 0

Acceptance Criteria

ParticipantDeduplicationService exposes a resolveProxyDuplicates(List<ActivityRecord> records) method returning a DeduplicationResult with a deduplicated participant set and an audit trail
When coordinator C registers activity A on behalf of peer mentor P, and P also registers activity A independently, only one record is counted
Participant ID normalization handles: uppercase/lowercase variation, leading/trailing whitespace, and Norwegian characters (Γ¦, ΓΈ, Γ₯) in names or IDs
Audit trail includes: original record count, deduplicated count, list of removed duplicate record IDs, and reason code ('proxy_registration')
Algorithm is deterministic: given the same input list in any order, output is always identical
No participant is incorrectly removed when there is no actual duplication (zero false positives in baseline tests)
Method runs in O(n log n) time or better β€” verified by timing test with 10,000 records completing in under 500ms on a mid-tier device
All Supabase queries used by the service are parameterized (no string interpolation) to prevent SQL injection
Service is a pure Dart class with no Flutter UI dependencies, enabling standalone unit testing

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase REST API
Supabase client (activity_records table, participants table)
data models
ActivityRecord
Participant
ProxyRegistration
DeduplicationResult
DuplicateAuditEntry
performance requirements
Deduplication of 10,000 records completes in under 500ms
Identity resolution uses an in-memory HashMap for O(1) lookups per participant
Supabase queries are batched β€” no per-record round trips
security requirements
All Supabase queries use parameterized values, never string interpolation
Service must not log participant names or personal identifiers to console or crash logs
Access to activity_records table must be gated by Supabase RLS policies ensuring org-level isolation

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

The core risk is defining 'same participant' correctly. Coordinate with the data team to confirm whether participant identity is resolved by: (a) Supabase user UUID (preferred β€” stable), (b) member number from Consio/Cornerstone, or (c) name + birthdate composite (fragile β€” avoid). Build the normalization step as a static `normalizeParticipantId(String raw)` utility function so it can be tested in isolation. For proxy detection: a ProxyRegistration record in the database links coordinator_user_id β†’ target_peer_mentor_user_id.

Query all proxy relationships for the org in a single Supabase call before processing records β€” do not query per-record. Use a `Set` of normalized participant IDs to build the deduplicated set in a single pass. Store the removed record IDs in a `List` returned in the result object β€” this is mandatory for Bufdir audit compliance. Register ParticipantDeduplicationService as a Riverpod Provider so it can be injected and mocked in tests.

Testing Requirements

Covered by task-008. During implementation, write at minimum: (1) a baseline test with zero duplicates asserting no records are removed, (2) a simple proxy test with one coordinator+peer-mentor pair asserting exactly one record removed, (3) an ID normalization test with mixed-case and whitespace variants. These serve as TDD anchors before task-008 writes the full suite.

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.