high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

AdminStatisticsService.getKpiTrends(nodeId) returns an AdminKpiTrendSnapshot extending AdminKpiSnapshot with per-metric trend data
Each KpiTrend contains: currentValue (int), previousValue (int), delta (int, can be negative), percentageChange (double, rounded to 1 decimal), direction (TrendDirection.up / .down / .flat)
TrendDirection.flat is used when delta == 0 OR when previousValue == 0 AND currentValue == 0
When previousValue == 0 and currentValue > 0, percentageChange is reported as double.infinity and direction is up (no division-by-zero crash)
Previous month values are fetched using the same parallel Future.wait pattern as current month
Previous month cache uses a separate cache key ('${nodeId}_prev_${year}_${month}') with a longer TTL (30 minutes — previous month data is immutable)
Trend data for metrics that are point-in-time snapshots (activePeerMentors, pausedMentors) compares end-of-previous-month count vs current count
Trend data for flow metrics (monthlyActivities, pendingReimbursements) compares total for previous calendar month vs total for current calendar month
Unit tests: positive trend, negative trend, flat trend, zero-to-nonzero transition, previous month with no data

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase PostgREST: same tables as task-004 with additional date range filters for previous month
data models
KpiTrend (currentValue, previousValue, delta, percentageChange, direction)
TrendDirection (enum: up, down, flat)
AdminKpiTrendSnapshot (extends AdminKpiSnapshot, adds Map<String, KpiTrend> trends)
performance requirements
Previous month queries cached with 30-minute TTL (immutable historical data)
Combined current + previous fetch (10 parallel queries): < 5s on 4G
security requirements
Same RLS constraints as task-004 apply to previous month queries
ui components
KpiTrendBadge widget (arrow icon + percentage label, colour-coded: green up / red down / grey flat)
AdminDashboardKpiRow updated to display KpiTrendBadge alongside each stat

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Compute previous month date range in pure Dart: use DateTime(now.year, now.month, 1).subtract(Duration(days: 1)) to get last day of previous month, then DateTime(prevLast.year, prevLast.month, 1) for first day. Always use UTC to avoid daylight saving boundary issues. The percentage change formula: ((current - previous) / previous * 100).toStringAsFixed(1) — guard against previous == 0 before dividing. Consider a shared _computeTrend(int current, int previous) helper to centralise the logic and avoid duplication across 5 metrics.

For point-in-time metrics (active/paused mentors), the 'previous month end' value can be approximated as the count on the last day of the previous month using a .lte('updated_at', lastDayOfPrevMonth) filter — document this approximation clearly in code comments.

Testing Requirements

Unit tests (flutter_test): parameterised test matrix covering all TrendDirection cases and the division-by-zero edge case. Test that previous month queries use correct date boundaries (first and last day of previous calendar month, UTC). Test cache key differentiation between current and previous month entries. Test that 10 queries (5 current + 5 previous) are issued in a single Future.wait call, not two sequential batches.

Component
Admin Statistics Service
service high
Epic Risks (4)
medium impact high prob technical

OrgHierarchyNavigator rendering NHF's full 1,400-chapter tree in a single widget may cause Flutter frame-rate drops below 60 fps on mid-range devices, making the navigator unusable for NHF national admins.

Mitigation & Contingency

Mitigation: Implement lazy expansion: only load immediate children on node expand rather than the full tree upfront. Use virtual scrolling for long sibling lists. Test with a synthetic 1,400-node dataset on a low-end Android device during development.

Contingency: If lazy expansion is insufficient, replace the tree widget with a paginated drill-down navigator (select level → select child) that avoids rendering more than 50 nodes at a time.

medium impact medium prob dependency

Bufdir may update their required export column structure or file format during or after development. If the AdminExportService hardcodes the current Bufdir schema, any format change requires a code release rather than a config update.

Mitigation & Contingency

Mitigation: Drive the Bufdir column mapping from a configuration repository rather than hardcoded constants. Abstract column definitions into a named schema config so that format changes require only a config update and re-deployment without service logic changes.

Contingency: If Bufdir format changes post-launch, release a config update within one sprint. If the change is structural (new required sections), scope a targeted service update and communicate timeline to partner organisations.

high impact medium prob integration

Role transition side-effects in UserManagementService (e.g., certification expiry removing mentor from chapter listing, pause triggering coordinator notification) may interact with external services like HLF's website sync. Incomplete side-effect handling could leave the system in an inconsistent state.

Mitigation & Contingency

Mitigation: Model side-effects as explicit domain events published after the primary state change is persisted. Implement event handlers as idempotent operations so re-processing is safe. Write integration tests that assert all side-effects fire correctly for each role transition type.

Contingency: If a side-effect fails after the primary change is persisted, log the failure with full context and trigger a manual reconciliation alert to the on-call team. Provide an admin-accessible re-trigger action for failed side-effects.

medium impact medium prob scope

If AdminStatisticsService cache TTL is set too long, org_admin may see significantly stale KPI values (e.g., a mentor newly paused an hour ago still appears as active), undermining trust in the dashboard.

Mitigation & Contingency

Mitigation: Default cache TTL to 5 minutes with a manual refresh action on the dashboard. Implement cache invalidation triggered by UserManagementService write operations that affect counted entities.

Contingency: If staleness causes org admin complaints post-launch, reduce TTL to 60 seconds and introduce a real-time Supabase subscription for high-impact counters (paused mentors, expiring certifications).