critical priority high complexity backend pending backend specialist Tier 1

Acceptance Criteria

A deduplicate() method (or equivalent) accepts a List<BufdirActivityRecord> and returns a deduplicated List<BufdirActivityRecord>
Fingerprint is computed as a stable hash of: peerMentorId + activityType + activityDate (date only, not time) + durationMinutes
When two records share a fingerprint, exactly one record is retained — the record from the chapter with the lowest ID (or earliest created_at) to ensure determinism
Deduplication result is auditable: the service emits a DeduplicationSummary object listing how many records were removed and which chapter pairs had conflicts
A peer mentor with N chapters who registers the same activity in all N chapters produces exactly 1 record in the output
Activities from different peer mentors with identical fields are NOT deduplicated (fingerprint is peer-mentor-scoped)
The algorithm handles lists of up to 2000 records (HLF: 380 registrations per person * ~5 peak mentors) without timeout
Deduplication is a pure function — same input always produces same output regardless of call order
Unit tests cover: no duplicates (passthrough), single duplicate pair, three-way duplicate, duplicate detection across non-adjacent list positions, empty input

Technical Requirements

frameworks
Dart
Flutter
data models
BufdirActivityRecord
DeduplicationSummary
ActivityFingerprint
performance requirements
Deduplication of 2000 records must complete in under 200ms on a mid-range mobile device
Use a HashMap<String, BufdirActivityRecord> keyed on fingerprint for O(n) deduplication — never O(n²) nested loops
Fingerprint string construction must avoid string concatenation in a loop — use StringBuffer or join()
security requirements
Fingerprints must not be persisted or transmitted — they are transient computation artifacts
DeduplicationSummary logs must not include personal data from the activity records (only counts and chapter IDs)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement deduplication as a separate BufdirDeduplicator class rather than embedding it in BufdirActivityQueryService — this keeps concerns separated and makes the algorithm independently testable. The fingerprint key should be: '${record.peerMentorId}|${record.activityType}|${record.activityDate.toIso8601String().substring(0, 10)}|${record.durationMinutes}'. Use a LinkedHashMap to preserve insertion order of surviving records. When a collision is detected, retain the record with the lexicographically smaller chapterId for full determinism.

Output the DeduplicationSummary as a simple value object: {originalCount, deduplicatedCount, removedCount, conflictingChapterPairs: List}. This summary should be logged at debug level and surfaced in the UI export confirmation screen. Be careful with activityDate: normalise to midnight UTC before fingerprinting to handle timezone edge cases where the same activity appears on different calendar dates in UTC vs local time.

Testing Requirements

Unit tests using flutter_test only — no Supabase dependency needed since deduplication is a pure function. Required test cases: (1) list with no duplicates returns identical list, (2) two records with identical fingerprint → one removed, (3) three records from three chapters with same fingerprint → one remains, (4) two peer mentors with same activity fields but different peerMentorId → both retained, (5) empty list returns empty list, (6) list with 1000+ records (performance test with stopwatch assertion), (7) determinism test: shuffle input and assert same output. Use table-driven test style with a helper that builds BufdirActivityRecord test fixtures. Target 100% branch coverage on the deduplication method.

Component
Bufdir Activity Query Service
service high
Epic Risks (3)
high impact medium prob technical

NHF contacts can belong to up to five local chapters simultaneously. If the deduplication logic in the activity query service incorrectly attributes cross-chapter activities, organisations will either under-report or over-report to Bufdir, which could trigger grant clawback or compliance investigations.

Mitigation & Contingency

Mitigation: Implement deduplication using the existing multi-chapter membership service as the source of truth for chapter affiliation. Write test fixtures covering all known multi-chapter edge cases and validate outputs against manually prepared reference exports from NHF.

Contingency: If deduplication cannot be made deterministic for complex hierarchies before release, gate the export behind an org-level feature flag and require NHF to validate a preview export against their manual Excel before enabling in production.

medium impact medium prob dependency

Server-side Dart libraries for Excel generation are less mature than equivalents in Node.js or Python. The chosen library may lack support for Bufdir-required formatting features (merged cells, data validation, specific date formats), requiring significant workaround effort or a library switch mid-implementation.

Mitigation & Contingency

Mitigation: Evaluate the top two Dart xlsx libraries (excel, spreadsheet_decoder) against a Bufdir template sample file before committing. Identify all required formatting features and verify library support in a spike.

Contingency: If no Dart library meets requirements, implement the Excel generation as a Supabase Edge Function in TypeScript using the well-supported ExcelJS library, exposing it to the Dart backend via an internal RPC call.

medium impact medium prob integration

The attachment bundler must retrieve documents from Supabase Storage that were uploaded by the document attachments feature. If storage paths, RLS policies, or signed URL expiry have not been standardised across features, the bundler may fail to retrieve attachments at export time.

Mitigation & Contingency

Mitigation: Audit the document attachments feature's storage schema and RLS policies before implementing the bundler. Agree on a stable internal service-account access pattern for cross-feature storage reads.

Contingency: If cross-feature storage access cannot be made reliable, implement the bundler to include only attachments that can be retrieved successfully and produce a manifest listing any attachments that could not be bundled, rather than failing the entire export.