critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

StatsSnapshot is defined as a Dart class annotated with @freezed (or equivalent immutability mechanism) in its own file under lib/features/stats/
StatsSnapshot exposes: totalActivityCount (int), activityCountByType (Map<String, int>), peerMentorSummaries (List<PeerMentorStatsSummary>), selectedTimeWindow (TimeWindow), windowStart (DateTime), windowEnd (DateTime), generatedAt (DateTime)
PeerMentorStatsSummary is a nested freezed class with: peerMentorId (String), displayName (String), totalActivities (int), activityCountByType (Map<String, int>)
StatsSnapshot.empty() factory constructor returns a valid zero-state instance usable as initial state
Equality check: two StatsSnapshot instances with identical field values are equal via == without manual override (provided by freezed)
StatsSnapshot is JSON-serializable via fromJson/toJson (generated by json_serializable through freezed)
The file compiles without errors after running `flutter pub run build_runner build --delete-conflicting-outputs`
A unit test file with at least 3 test cases is created: equality, empty factory, and JSON round-trip

Technical Requirements

frameworks
flutter
riverpod
freezed
json_serializable
build_runner
data models
StatsSnapshot
PeerMentorStatsSummary
TimeWindow
performance requirements
Model instantiation must complete in under 1ms for typical snapshot sizes (≤ 50 peer mentors)
JSON serialization round-trip must complete in under 5ms
security requirements
Model must not store raw user PII beyond displayName — no email, phone, or national ID fields
peerMentorId must reference an opaque UUID, not a human-readable identifier

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use freezed with json_serializable for immutability and serialization — this is consistent with the project's existing model pattern. Place StatsSnapshot in `lib/features/stats/domain/models/stats_snapshot.dart` and PeerMentorStatsSummary in the same file or a sibling file. The Map fields must use `@JsonKey` if the JSON keys differ from Dart naming conventions. Do NOT include summaries or UI-specific formatting fields in this model — it is a pure domain object.

Riverpod's AsyncValue equality works correctly only if StatsSnapshot implements ==; freezed provides this automatically. Register the new files in `build.yaml` if the project uses selective code generation targets.

Testing Requirements

Unit tests using flutter_test. Test cases: (1) Two StatsSnapshot instances with identical data are equal (==) and have identical hashCodes. (2) StatsSnapshot.empty() returns an instance where totalActivityCount == 0 and peerMentorSummaries is an empty list. (3) A StatsSnapshot can be serialized to JSON and deserialized back to an equal instance.

(4) activityCountByType correctly handles an empty map without throwing. Run with `flutter test` — no mocking required for this pure data model.

Component
Stats Async Notifier
service medium
Epic Risks (2)
medium impact medium prob technical

Supabase realtime channel subscriptions that are not properly disposed on screen close can accumulate in memory across navigation events, causing duplicate invalidation calls, ghost fetches, and eventual memory leaks on long sessions.

Mitigation & Contingency

Mitigation: Implement StatsCacheInvalidator as a Riverpod provider with an explicit ref.onDispose callback that cancels the realtime channel subscription. Write a widget test that navigates away and back multiple times and asserts that only one subscription is active at any given time.

Contingency: If subscription leaks are found in production, add a global subscription registry that enforces at-most-one subscription per channel key, and schedule a dispose sweep on app background events.

medium impact low prob scope

Debouncing rapid inserts may swallow the invalidation signal if the debounce window outlasts the Supabase realtime event delivery window, resulting in the dashboard showing stale totals after a bulk registration completes.

Mitigation & Contingency

Mitigation: Set the debounce window to 800ms (shorter than the typical Supabase realtime delivery latency of 1-2s for batched events) and ensure the leading-edge invalidation fires immediately while trailing duplicates are suppressed. Integration-test with a 20-record bulk insert.

Contingency: If debounce timing proves unreliable, replace debounce with a trailing-edge timer reset on each event and add a guaranteed invalidation 5 seconds after the last event regardless of subsequent events.