critical priority low complexity integration pending integration specialist Tier 3

Acceptance Criteria

All three StatsRepository query methods check StatsCacheManager before executing a Supabase query
On a cache hit, the cached result is returned without any network call
On a cache miss, Supabase is queried and the result is stored in the cache before returning to the caller
Cache keys are identical for logically equivalent queries regardless of parameter construction order
All three methods accept a forceRefresh: bool = false parameter; when true, the cache lookup is skipped and the result replaces the stale cache entry
A Supabase error on a force-refresh does not evict the existing valid cache entry — stale data is preserved as fallback
StatsCacheManager is injected into StatsRepository (not instantiated internally) to allow test replacement
Debug-mode logging records cache hit/miss per query method without logging PII

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgreSQL 15
data models
activity
activity_type
annual_summary
performance requirements
Cache hit path must add <1 ms latency versus uncached path
force-refresh must not double-fetch — exactly one Supabase call per invocation
security requirements
forceRefresh bypass must still call RoleAccessValidator — bypassing cache must not bypass access control
Cache entries must include the org_id in the key to prevent cross-tenant data leakage on shared instances

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Wrap each repository method with a private _withCache(String key, Future Function() fetcher, {bool forceRefresh = false}) helper: if (!forceRefresh) { final cached = _cache.get(key); if (cached != null) return cached; } final result = await fetcher(); if (result is Success) _cache.put(key, result); return result;. This centralizes caching logic and avoids duplicating the pattern across three methods. The forceRefresh flag should be threaded from the public method signature through to _withCache. Ensure the cache key builder is a static method on StatsRepository so it can be called from CacheInvalidationService without creating a circular dependency.

Testing Requirements

Unit tests: (1) cache hit — Supabase client never called; (2) cache miss — Supabase called once, result stored; (3) forceRefresh=true — Supabase called even when cache has valid entry; (4) Supabase error during force-refresh — original cache entry preserved; (5) identical parameters in different construction order produce same cache key; (6) RoleAccessValidator still called on force-refresh path. Use flutter_test with mock StatsCacheManager and mock SupabaseClient. Verify interaction counts with verify() matchers.

Component
Stats Repository
data medium
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.