critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

StatsCacheManager implements IStatsCacheManager and is registered as a singleton Riverpod provider
Cache key is a deterministic string derived from StatsScope type+id and StatsFilter fields — same inputs always produce the same key
get(key) returns the cached ViewModel if the entry exists and was stored within the last 15 minutes; returns null otherwise
set(key, value) stores the ViewModel with the current timestamp as metadata
invalidate(key) removes the specific cache entry; subsequent get(key) returns null
invalidateAll() clears all entries — used when a new proxy_activity is registered
Cache entries serialize to JSON using the ViewModel's toJson methods and deserialize correctly via fromJson
TTL expiry is evaluated lazily on get() — no background timer is required
StatsCacheManager is covered by unit tests that verify TTL expiry, key collision prevention, and invalidation correctness
The manager does not depend on Flutter (no BuildContext) — it is a pure Dart service

Technical Requirements

frameworks
Dart
Riverpod
data models
CoordinatorStatsViewModel
PersonalStatsViewModel
StatsFilter
StatsScope
performance requirements
get and set operations must complete in O(1) time using a HashMap
Cache must not grow unboundedly — implement a maximum of 50 entries with LRU eviction or periodic cleanup on invalidateAll
security requirements
Cache keys must not embed PII — use scope IDs and filter hashes, not user names or contact details
Cache is in-memory only for MVP; if persistence is added later, ensure the storage medium is encrypted on device

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Inject the current time via a `DateTime Function() nowFn` constructor parameter defaulting to `DateTime.now` — this makes TTL logic fully testable without real delays. Store entries as `_CacheEntry` objects containing `T value` and `DateTime storedAt`. The cache key function should use a stable hash of the serialised StatsFilter JSON plus the StatsScope type and ID — use Dart's `Object.hash` or a simple string concatenation that is guaranteed stable across Dart versions. Do not use external caching libraries unless they are already in pubspec.yaml.

For LRU eviction, a simple `LinkedHashMap` with insertion-order tracking is sufficient. The invalidateAll hook should be called from the BLoC or service layer whenever a proxy_activity registration event is emitted — wire this up in the CoordinatorStatsBloc, not inside the cache manager itself, to preserve single responsibility.

Testing Requirements

Unit test with flutter_test: (1) Store a value and retrieve within TTL — expect the original value returned. (2) Store a value and simulate TTL expiry by injecting a mock clock that returns now + 16 minutes — expect null. (3) Call invalidate on a stored key — expect null on subsequent get. (4) Call invalidateAll with three entries — expect all return null.

(5) Verify key determinism: construct two identical StatsFilter objects and assert their cache keys are equal. (6) Verify JSON round-trip: serialize a stored entry and deserialize it back — assert equality. Use a fake/mock DateTime provider injected via constructor to enable time-travel in tests without real sleeps.

Component
Coordinator Statistics Service
service high
Epic Risks (3)
medium impact medium prob technical

fl_chart's default colour palette may not meet WCAG 2.2 AA contrast requirements when rendered on the app's dark or light backgrounds. If segment colours are insufficient, the donut chart will fail accessibility audits, which is a compliance blocker for all three organisations.

Mitigation & Contingency

Mitigation: Define all chart colours in the design token system with pre-validated contrast ratios. Run the contrast-ratio-validator against every chart colour during the adapter's unit tests. Use the contrast-safe-color-palette as the source palette.

Contingency: If a colour fails validation, replace with the nearest compliant token. If activity types exceed the available token set, implement a deterministic hashing algorithm that maps activity type IDs to compliant colours.

medium impact medium prob technical

StatsBloc subscribing to the activity registration stream creates a long-lived subscription. If the subscription is not disposed correctly when the dashboard is closed, it will cause a stream leak and potentially trigger re-fetches on a disposed BLoC, resulting in uncaught errors in production.

Mitigation & Contingency

Mitigation: Implement subscription disposal in the BLoC's close() override. Write a widget test that navigates away from the dashboard and asserts no BLoC events are emitted after disposal.

Contingency: If leaks are detected in QA, add a mounted check guard before emitting states from async callbacks, and audit all other BLoC stream subscriptions in the codebase for the same pattern.

low impact low prob scope

PersonalStatsService's Phase 4 gamification data structure is designed against an assumed future schema. If the Phase 4 Spotify Wrapped feature defines a different data contract when it is developed, the structure built now will require a breaking change and migration.

Mitigation & Contingency

Mitigation: Document the contribution data structure with explicit field semantics and versioning comments. Keep the Phase 4 fields as optional/nullable so they do not break existing consumers if the schema evolves.

Contingency: If the Phase 4 schema diverges significantly, the personal stats data can be re-mapped in a thin adapter layer without changing PersonalStatsService's core implementation.