high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

milestones.json asset is registered in pubspec.yaml under flutter.assets and can be loaded at runtime without throwing
MilestoneDefinition model includes fields: id (String), threshold_type (enum: total_hours, contacts_helped, streak_days), threshold_value (num), significance_rank (int), description_template (String), icon_key (String)
MilestoneConfigRepository.loadAll() returns a non-empty List<MilestoneDefinition> parsed from the bundled asset
Returned list is sorted by significance_rank descending (highest significance first)
All four hours thresholds (10h, 50h, 100h, 500h) are present as separate definitions in the JSON
All three contacts-helped milestones (1st, 10th, 100th) are present
Streak achievements (e.g., 7-day, 30-day) are present
Malformed JSON in the asset throws a descriptive MilestoneConfigException rather than a generic FormatException
Repository is implemented as a singleton or registered as a lazy singleton via the DI container so the asset is parsed once per app lifecycle
All MilestoneDefinition fields are non-nullable where logically required; nullable fields have documented rationale

Technical Requirements

frameworks
Flutter
BLoC
apis
Flutter rootBundle (services.dart)
data models
annual_summary
performance requirements
Asset parsed once at startup and cached in memory; subsequent calls return cached list in O(1)
Total parse time must not exceed 50ms on a mid-range Android device
security requirements
Asset is read-only bundled data; no user-supplied input is deserialized
No PII is stored in milestone definitions

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use rootBundle.loadString('assets/milestones.json') inside an async factory. Wrap json.decode in a try/catch and rethrow as a typed MilestoneConfigException with the raw error message for debuggability. Use jsonDecode + List.from + map to MilestoneDefinition.fromJson. Sort with list.sort((a, b) => b.significanceRank.compareTo(a.significanceRank)) after parsing.

Register the repository via get_it or Riverpod Provider — do NOT use a global variable. Keep milestones.json human-readable with comments stripped (Dart's json.decode does not support JSON5). Define threshold_type as a Dart enum with a fromJson factory using a switch-case so unknown values throw early rather than silently defaulting.

Testing Requirements

Unit tests using flutter_test: (1) parse valid milestones.json fixture and assert correct MilestoneDefinition count and field values; (2) assert list is sorted by significance_rank descending; (3) parse JSON missing required fields and assert MilestoneConfigException is thrown; (4) call loadAll() twice and assert identical list reference (cache hit). No integration tests required for this task. Coverage target: 100% of MilestoneConfigRepository public methods.

Component
Milestone Detection Service
service medium
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.