high priority medium complexity testing pending testing specialist Tier 5

Acceptance Criteria

All tests reside in test/annual_stats_aggregation_service_test.dart and run with `flutter test` without additional setup
Total hours: test with 0 activities, 1 activity, mixed activity types, and fractional hours (e.g., 0.5 h) — all produce correct sums
Unique contact deduplication: a contact appearing in 3 activities is counted once; contacts across overlapping periods are not double-counted
Activity type distribution: percentages sum to exactly 100.0 (or 99.9–100.0 with floating-point tolerance of ±0.1); empty activity list produces empty map, not division-by-zero error
Streak calculation: consecutive days produce correct streak length; a single missed day resets streak to 0; re-starting after a gap starts new streak from 1
Offline cache fallback: when Supabase client throws SocketException or returns null, the mock cache repository is queried and its data is returned; if cache is also empty, AnnualStats.empty() is returned
Period boundary — rolling 12 months: activities exactly at the start boundary (today minus 365 days) are included; activities one day before are excluded
Period boundary — half-year: activities within the 6-month window are included; activities on day 183 are excluded
All tests are isolated — no shared mutable state between test cases
Test suite achieves ≥90% branch coverage as reported by `flutter test --coverage`

Technical Requirements

frameworks
Flutter
flutter_test
apis
Supabase (mocked)
Cache repository (mocked)
data models
AnnualStats
ActivityRecord
AnnualSummaryPeriod
ActivityTypeDistribution
performance requirements
Full test suite must complete in under 10 seconds
No async tests should use real timers — use fake_async or FakeDateTime for time-dependent logic
security requirements
Test fixtures must not contain real personal data (use synthetic names and IDs)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

If AnnualStatsAggregationService uses DateTime.now() internally, inject a Clock or DateTimeProvider abstraction so tests can control the current time deterministically. For floating-point distribution assertions use closeTo(expected, 0.1) matcher rather than exact equality. When testing streak logic, build activity lists using a helper factory that generates records N days apart — this keeps test data readable. For offline fallback, verify both that the Supabase mock was called once and then the cache mock was called once (interaction verification, not just return value).

Ensure the mock setup mirrors the actual SupabaseClient interface used in production code to avoid false passes.

Testing Requirements

Pure unit tests using flutter_test. Use Mockito (@GenerateMocks) or hand-written fakes for SupabaseClient and CacheRepository. Organise tests in group() blocks: 'total hours', 'contact deduplication', 'distribution normalisation', 'streak calculation', 'offline fallback', 'period boundaries'. Each group should have at least 3 test cases including happy path, edge case, and error case.

Use setUp() for shared fixture construction. Avoid test interdependencies. Run `flutter test --coverage` and verify lcov report shows ≥90% branch coverage on the service file.

Component
Annual Stats Aggregation Service
service high
Epic Risks (3)
high impact medium prob integration

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.

medium impact high prob scope

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.

medium impact medium prob technical

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.