critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

PeriodType enum is defined with exactly three values: rollingTwelveMonths, lastSixMonths, and custom — no additional values without a schema change ticket
SummaryPeriod is an immutable Dart class (or freezed data class) with fields: periodType (PeriodType), startDate (DateTime), endDate (DateTime), and a factory constructor for each PeriodType that computes dates relative to DateTime.now()
SummaryPeriodChanged is defined as a BLoC event (or equivalent Riverpod notifier method signature) that carries a SummaryPeriod payload
SummaryPeriodState (or equivalent) exposes the currently selected SummaryPeriod and is equatable so BLoC/Riverpod only emits on real changes
All classes are placed under lib/features/annual_summary/domain/models/ and exported via a barrel file
A companion SummaryPeriodExtension provides a human-readable label string for each PeriodType, used by the UI widget
Unit tests cover: factory constructors produce correct date ranges for rollingTwelveMonths and lastSixMonths relative to a fixed reference date; custom period preserves caller-supplied dates; equality check works correctly on SummaryPeriod instances
No UI, BLoC, or Supabase imports exist in the model file — pure Dart domain layer

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
freezed (if immutable codegen is used)
data models
SummaryPeriod
PeriodType
SummaryPeriodChanged
SummaryPeriodState
performance requirements
Date computation in factory constructors must be O(1) — no database or network calls
Equality checks on SummaryPeriod must complete in O(1) using field comparison
security requirements
No PII stored in period models — they carry only date ranges
Custom date range must enforce startDate <= endDate — throw ArgumentError otherwise

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Create this as a pure domain-layer file with zero framework dependencies. Use either plain Dart immutable classes with const constructors, or add freezed if the project already uses it — do not introduce freezed solely for this task. Place date arithmetic for rolling periods in static factory methods (SummaryPeriod.rollingTwelveMonths(), SummaryPeriod.lastSixMonths()) so callers never compute dates manually and future logic changes are centralised. The BLoC event/state definitions should live in a separate file (summary_period_bloc.dart) but import the domain models — keep the event thin (just carries a SummaryPeriod).

Document the contract decision with a brief // Contract: comment block at the top of each file so future developers understand this is a stable interface shared across multiple components.

Testing Requirements

Unit tests only (flutter_test). Three test groups: (1) PeriodType factory correctness — mock DateTime.now() to a fixed date and assert rollingTwelveMonths spans exactly 365 days back and lastSixMonths spans exactly 180 days back; (2) SummaryPeriod equality — two instances with identical fields must be equal and have identical hashCodes; (3) Edge cases — custom period with startDate == endDate is valid (single-day period), startDate > endDate throws ArgumentError. Target 100% line coverage for these three files as they are foundational contracts.

Component
Summary Period Selector
ui low
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.