Implement Coordinator Stats Service
epic-coordinator-statistics-dashboard-core-logic-task-007 — Implement CoordinatorStatsService that fetches aggregated statistics from StatsRepository using the scope determined by RoleAccessValidator (coordinator vs org-admin), applies the 15-minute cache, and transforms raw rows into CoordinatorStatsViewModel containing: KPI card values (total activities, active mentors, contacts reached), monthly BarChartSeriesViewModel for the bar chart, and activity-type DonutSegmentViewModel list for the donut chart. Service must handle both coordinator and org-admin scope without code duplication.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
The scope abstraction is the critical design challenge. Model it as a sealed class: `sealed class StatsScope { const factory StatsScope.chapter(String chapterId) = ChapterScope; const factory StatsScope.organization(String organizationId) = OrganizationScope; }` — this is passed to the repository and mapped to the appropriate SQL WHERE clause without branching in the service. RoleAccessValidator should be async (it may need to query the database for chapter assignments). Cache key hashing: use a stable toString or toJson on StatsScope + StatsFilter period so keys are deterministic.
Donut percentage: compute raw percentages first, then apply largest-remainder method to ensure exactly 100%. For MentorStatsList, consider a separate repository method that returns pre-sorted rows to avoid in-memory sorting of large lists.
Testing Requirements
Unit tests required using flutter_test with mocked StatsRepository and RoleAccessValidator. Test cases: (1) coordinator role → scoped query called with chapter filter, (2) org-admin role → full-org query called without chapter filter, (3) DonutSegmentViewModel percentages sum exactly to 100.0 with rounding correction, (4) zero-activity types excluded from donut, (5) active mentors count uses distinct logic, (6) MentorStatsList sorted descending by activity count, (7) cache hit prevents repository call, (8) cache miss stores result, (9) Supabase error → Result.failure, (10) coordinator cannot access org-admin scope even if they pass org-admin userId. Integration test: deploy two Supabase test accounts (coordinator and org-admin) and verify scope isolation via actual RLS enforcement.
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.