Implement Personal Stats Service
epic-coordinator-statistics-dashboard-core-logic-task-006 — Implement PersonalStatsService that fetches a single peer mentor's activity history from StatsRepository, applies the 15-minute cache via StatsCacheManager, transforms raw rows into KpiCardViewModel and BarChartSeriesViewModel for the personal stats view, and builds the Phase 4-ready ContributionData structure containing activity counts by type and month designed to be forward-compatible with the Spotify Wrapped gamification feature.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Model PersonalStatsService as a plain Dart class (not a BLoC) injected via Riverpod provider or passed to BLoC constructor. StatsCacheManager should use a Map
Keep ActivityTypeId as a String (not an enum) in ContributionData so new activity types added by coordinators don't require a code change. Avoid business logic in the repository layer — transformation belongs here in the service.
Testing Requirements
Unit tests required for all transformation logic: KpiCardViewModel builder, BarChartSeriesViewModel grouping, ContributionData map construction. Use flutter_test with mock StatsRepository (mockito/mocktail). Test cases: (1) normal activity history with 3 months of data, (2) empty history — new mentor, (3) single activity in period, (4) cache hit path returns cached value without calling repository, (5) cache miss path calls repository and stores result, (6) Supabase error mapped to Result.failure without throwing, (7) delta percentage correctly computes positive/negative/zero deltas, (8) filter change produces different cache key. Integration test: verify RLS prevents cross-mentor data access using two test mentor accounts in Supabase test environment.
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.
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.
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.