Build AnnualStatsAggregationService core logic
epic-annual-impact-summary-core-services-task-005 — Implement the AnnualStatsAggregationService class with methods to: (1) accept a SummaryPeriod and user ID, (2) query Supabase for raw activity records via the repository layer, (3) compute total hours from duration fields, (4) count unique contacts, (5) calculate activity type distribution percentages normalised to 100%, and (6) derive current streak in days. Service must fall back to offline cache when Supabase is unreachable.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Structure the service as a class in lib/features/annual_summary/domain/services/annual_stats_aggregation_service.dart. Extract the distribution normalisation into a pure static method _normaliseDistribution(Map
Inject dependencies via constructor with default Riverpod providers so integration is seamless; use @riverpod annotation if the project uses riverpod_generator. Consider using Result
Testing Requirements
Unit tests (flutter_test) with mocked dependencies. Test groups: (1) Happy path — mock repository returns a known raw map; assert AnnualStatsResult fields match expected computed values including distribution normalisation and streak; (2) Distribution edge cases — single activity type maps to 100.0%; empty activity_type_counts produces empty distribution map; floating-point remainder is handled so values sum to exactly 100.0; (3) Streak calculation — 5 consecutive days = streak of 5; gap on day 3 = streak of 2; no activities = streak of 0; (4) Fallback — repository throws networkError; mock cache returns a prior result; assert getStats returns the cached result; (5) Cache miss — repository throws networkError AND cache returns null; assert getStats rethrows AnnualStatsException. Target 90% line coverage.
Activity records may contain duplicate entries (as evidenced by the duplicate-detection feature dependency) or proxy-registered activities that should be attributed differently. Including duplicates or mis-attributed records would produce inflated stats, undermining trust in the summary.
Mitigation & Contingency
Mitigation: Implement the aggregation query to join against the deduplication-reviewed-flag on activity records and filter out unresolved duplicates. Coordinate with the duplicate-detection feature team to confirm the authoritative flag field before implementing the RPC. Include a data-quality warning in the summary when unresolved duplicates are detected.
Contingency: If deduplication state is unreliable at release time, add a prominent disclaimer in the summary UI noting that figures reflect all registered activities and may include duplicates pending review. Track a follow-up task to re-aggregate after deduplication runs.
Each organisation wants to define their own milestone thresholds (e.g., NHF's counting model differs from HLF's certification model). Implementing configurable thresholds may expand scope significantly if the configuration UI is expected in this epic.
Mitigation & Contingency
Mitigation: Scope this epic strictly to the evaluation engine and a hardcoded default threshold set. Define the MilestoneDefinition interface with an organisation_id discriminator so per-org configs can be loaded from the database in a later sprint. Build the admin configuration UI as a separate follow-on task outside this epic.
Contingency: If stakeholders require per-org milestone configuration before launch, deliver a JSON-based configuration file per org as an interim solution, loaded from Supabase storage, until a full admin UI is built.
Android 13+ restricts access to media collections and requires READ_MEDIA_IMAGES permission for gallery saves, while older Android versions use WRITE_EXTERNAL_STORAGE. Handling both permission models correctly across the device matrix is error-prone.
Mitigation & Contingency
Mitigation: Use the permission_handler Flutter package with version-aware permission requests abstracted behind the summary-share-service interface. Write platform-specific unit tests for both Android API levels in the test harness. Test on a minimum of three Android versions (API 29, 32, 34) in CI.
Contingency: If gallery save is broken on specific Android versions at launch, disable the 'Save to gallery' option on affected API levels and surface only clipboard and system share sheet, which require no media permissions.