critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

AnnualStatsResult is an immutable class with fields: totalHours (double), uniqueContactsHelped (int), activityTypeDistribution (Map<String, double>), streakDays (int), periodStart (DateTime), periodEnd (DateTime); all fields are non-nullable
activityTypeDistribution values must sum to 100.0 (±0.01 floating-point tolerance) — enforced by an assertion in the constructor that fires in debug mode
MilestoneDefinition has fields: id (String, unique slug), threshold (double), metricKey (String referencing a field of AnnualStatsResult), significanceRank (int, 1 = highest), descriptionTemplate (String with {value} placeholder for interpolation)
MilestoneResult has fields: definition (MilestoneDefinition), isNewlyUnlocked (bool), formattedDescription (String — pre-interpolated at construction time)
AnnualStatsResult.empty() factory returns a zero-state instance (0.0 hours, 0 contacts, empty map, 0 streak) for use as initial BLoC state
All three classes implement == and hashCode correctly (or use freezed/equatable)
Classes are pure Dart — no Flutter, BLoC, Supabase, or UI imports
Unit tests verify: empty factory produces correct zeros; distribution assertion fires on invalid input in debug; MilestoneResult.formattedDescription correctly interpolates {value} in the template; equality checks on all three classes

Technical Requirements

frameworks
Dart
freezed or equatable (if already in project)
data models
AnnualStatsResult
MilestoneDefinition
MilestoneResult
performance requirements
All constructors must be O(1) — no sorting or aggregation at construction time
Map<String,double> for activityTypeDistribution must be unmodifiable (Map.unmodifiable) to prevent accidental mutation
security requirements
totalHours must be >= 0.0 — enforce with assert in debug mode
streakDays must be >= 0 — enforce with assert
metricKey in MilestoneDefinition must be validated against a known set of field names at construction time in debug mode to catch typos early

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place all three classes in lib/features/annual_summary/domain/models/annual_stats_result.dart (or separate files per class if the project convention is one class per file). Use Map.unmodifiable() when assigning activityTypeDistribution so the getter always returns a read-only view. For MilestoneResult, compute formattedDescription in the constructor (or a factory) by calling descriptionTemplate.replaceAll('{value}', value.toStringAsFixed(1)) — this keeps the presentation logic in one place. Consider defining a MetricKey class (or enum) with constants matching the AnnualStatsResult field names (e.g.

MetricKey.totalHours = 'totalHours') so MilestoneDefinition.metricKey is type-safe. This small addition eliminates an entire class of runtime bugs when the detection service evaluates thresholds via reflection or switch statements.

Testing Requirements

Unit tests only (flutter_test). Four test groups: (1) AnnualStatsResult — constructor with valid data; empty() factory; assert fires when distribution does not sum to ~100.0; equality and hashCode; (2) MilestoneDefinition — basic construction and equality; (3) MilestoneResult — formattedDescription correctly substitutes {value} placeholder with threshold formatted to one decimal place; (4) Immutability — verify activityTypeDistribution cannot be modified after construction (access the map and attempt mutation, expect UnsupportedError). Target 100% branch coverage for these model files.

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.