critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

`CertificationStatusService.derive({required DateTime expiryDate, required bool isPaused, int warningThresholdDays = 30, DateTime? now})` is a pure function (or method on a stateless class) returning `CertificationStatusResult`
The optional `now` parameter defaults to `DateTime.now()` when null, enabling deterministic unit testing with injected dates
Priority order is strictly enforced: `paused` check runs first (before expiry check), so a paused mentor with an expired certificate still shows `paused`
`expired` is returned when `expiryDate` is strictly before `now` (day-level granularity: strip time component via `DateUtils.dateOnly` or equivalent)
`expiringSoon` is returned when `daysRemaining >= 0 && daysRemaining <= warningThresholdDays`
`active` is returned when `daysRemaining > warningThresholdDays`
`daysRemaining` is computed as `expiryDate.difference(now).inDays` (floored, not rounded) using date-only values to avoid time-of-day drift
The service has no Flutter imports, no Supabase imports, and no Riverpod imports — it is a plain Dart class testable with `dart test`
Edge case: expiry is exactly today returns `expiringSoon` with `daysRemaining = 0` (not `expired`)
Edge case: `warningThresholdDays = 0` means only today triggers `expiringSoon`, tomorrow and beyond returns `active`

Technical Requirements

frameworks
Dart (pure — no Flutter dependency)
data models
CertificationStatusResult
MentorStatus
performance requirements
All computation is O(1) arithmetic — no collections, no async, no I/O
security requirements
The service must not expose pause state logic to UI as raw booleans — all status decisions are centralized here to prevent UI-layer duplication that could lead to inconsistent security/access decisions

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Strip time-of-day from both `now` and `expiryDate` before comparison using `DateTime(date.year, date.month, date.day)` to avoid edge cases where a 23:59 'now' treats the same calendar day as expired. Inject `now` as an optional parameter rather than a constructor dependency to keep the API simple while enabling testing. Do not add a Riverpod `Provider` wrapper in this task — that is a separate concern for the feature's provider layer. The business rule priority order (paused → expired → expiringSoon → active) must be implemented as a strict `if/else if` chain, not as parallel conditions, to guarantee the documented precedence.

Consider adding a `debugLabel` getter to `CertificationStatusService` for easier logging during development. Avoid using `Duration` arithmetic directly on dates with time zones — always normalize to date-only `DateTime` first.

Testing Requirements

Pure Dart unit tests covering all branches. Required test cases: (1) isPaused=true, valid future expiry → status=paused, daysRemaining=positive. (2) isPaused=true, already expired → status=paused (pause takes priority). (3) expiry yesterday → status=expired, daysRemaining=-1.

(4) expiry today → status=expiringSoon, daysRemaining=0. (5) expiry in 15 days, threshold=30 → status=expiringSoon, daysRemaining=15. (6) expiry in 31 days, threshold=30 → status=active, daysRemaining=31. (7) threshold=0, expiry in 1 day → status=active.

(8) threshold=0, expiry today → status=expiringSoon, daysRemaining=0. All tests inject `now` parameter — no test relies on `DateTime.now()` directly. Aim for 100% branch coverage on this service.

Epic Risks (2)
high impact medium prob integration

The design token theme extension may conflict with existing ThemeData extensions already registered in the app, causing runtime assertion errors or token resolution failures across all screens that consume the tokens.

Mitigation & Contingency

Mitigation: Audit all existing ThemeData extensions before implementation. Use a unique extension key namespace and add integration tests that instantiate the combined theme in a test app harness.

Contingency: If conflicts arise, isolate design tokens behind a dedicated provider singleton (Riverpod) rather than a ThemeData extension, updating all consuming widgets to read from the provider instead.

medium impact medium prob scope

The 30-day warning threshold for expiring_soon status may differ between HLF's stated requirement in workshops (60 days mentioned in user stories) and the 30-day value in component documentation, causing disagreement during acceptance testing.

Mitigation & Contingency

Mitigation: Explicitly confirm the threshold value with HLF stakeholder before implementation. Make the threshold a named constant (kCertificationWarnDays) so it can be updated without logic changes.

Contingency: If stakeholder confirms 60 days post-implementation, update the constant and re-run the unit test suite — no architectural change required.