critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

StatsCacheManager class is implemented as a singleton or injectable service with a configurable LRU capacity (default 100 entries)
Cache keys are generated deterministically from the combination of org_id, coordinator_id, period (start+end dates), and activity_type_id — same inputs always produce the same key regardless of parameter order
Entries expire exactly 15 minutes after insertion; expired entries are never returned to callers
LRU eviction removes the least-recently-used entry when the cache reaches capacity, before inserting a new entry
get(key) returns null for missing or expired entries; put(key, value) stores entry with timestamp; invalidate(key) and invalidateByPrefix(prefix) remove entries synchronously
invalidateByOrgId(orgId) clears all entries matching that org partition
Cache operations complete in O(1) average time; no blocking I/O
Memory footprint is bounded: maximum stored payload per entry is documented and enforced (e.g., 200 KB)
Thread safety is guaranteed — concurrent Riverpod providers accessing the cache do not produce race conditions
StatsCacheManager exposes a diagnostic method returning hit/miss counters for debug builds

Technical Requirements

frameworks
Flutter
Dart
Riverpod
data models
activity
activity_type
annual_summary
performance requirements
Cache lookup must complete in <1 ms on device
LRU eviction must not block the UI thread
Total cache memory must not exceed 20 MB on low-end devices
TTL check must use monotonic clock (Stopwatch or DateTime.now()) to avoid wall-clock drift issues
security requirements
Cache must be scoped per org_id — entries from one organisation must never be accessible by a session from another
Cache is in-memory only; no persistence to disk to avoid leaking aggregated PII across sessions
Cache must be fully cleared on user sign-out event

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement using a LinkedHashMap where _CacheEntry holds (value, insertedAt). Override [] and []= operators for ergonomic access. For LRU, move accessed entries to the end of the LinkedHashMap on every get. Use a private _prune() method called on every put to evict expired and over-capacity entries.

Provide a Riverpod Provider so StatsRepository can inject it. Avoid using Flutter's compute() — all operations are synchronous and lightweight. Key generation: use a sorted, canonicalized string like 'org:{orgId}|coord:{coordinatorId}|period:{start}_{end}|type:{activityTypeId}' — sort multi-value fields alphabetically before joining.

Testing Requirements

Unit tests using flutter_test covering: (1) TTL expiration — entry inserted at T returns value at T+14m, returns null at T+15m; (2) LRU eviction — inserting N+1 entries evicts the least-recently-used; (3) deterministic key generation — identical params in different order produce the same key; (4) invalidateByPrefix removes only matching entries; (5) cache cleared on sign-out; (6) concurrent access simulation with Future.wait to verify no race conditions. Target 100% branch coverage on all cache decision paths.

Component
Statistics Cache Manager
data low
Epic Risks (3)
high impact medium prob technical

Pre-aggregated Supabase views may still be slow for orgs with very large activity datasets (NHF with 1,400 chapters). If the view query plan performs sequential scans, dashboard load times could exceed acceptable thresholds and degrade the perceived value of the feature.

Mitigation & Contingency

Mitigation: Design views with composite indexes on (org_id, coordinator_id, month) from the start. Run EXPLAIN ANALYZE during development against a seeded dataset of realistic scale. Add materialized view refresh strategy if needed.

Contingency: If live view performance is insufficient, convert to materialized views refreshed on a schedule or on activity-write triggers. Expose the refresh delay transparently in the UI with a 'last updated' timestamp.

high impact low prob security

Supabase RLS policies for the stats views may not be configured correctly during initial migration, potentially allowing cross-coordinator data leakage before the RoleAccessValidator layer is reached. This is a security and compliance risk.

Mitigation & Contingency

Mitigation: Write RLS integration tests as part of this epic that explicitly verify a coordinator JWT cannot read another coordinator's stats rows. Apply RLS policies in the migration script itself, not as a manual step.

Contingency: If an RLS gap is discovered post-deployment, immediately disable the stats screen via a feature flag, apply the corrected RLS migration, and re-enable after verification. Log and audit all queries that ran during the gap window.

medium impact medium prob integration

Cache invalidation logic may not be triggered correctly when a new activity is registered by a peer mentor or when an expense approval is granted. Stale data could cause coordinators to make decisions based on outdated KPIs, undermining trust in the dashboard.

Mitigation & Contingency

Mitigation: Define explicit invalidation event contracts with the activity registration and expense approval pipelines. Implement an event bus subscription within StatsCacheManager. Document the invalidation contract in code.

Contingency: If event-driven invalidation proves unreliable, add a manual 'Refresh' pull-to-refresh gesture on the dashboard and reduce TTL to 5 minutes as a fallback degradation strategy.