critical priority medium complexity infrastructure pending frontend specialist Tier 3

Acceptance Criteria

StatsBloc extends Bloc<StatsEvent, StatsState> with sealed event and state hierarchies
Events: LoadCoordinatorStats (triggers initial or manual refresh), FilterChanged (carries new StatsFilter)
States: StatsInitial, StatsLoading, StatsLoaded (carries CoordinatorStatsViewModel + active StatsFilter), StatsError (carries displayable message string)
On LoadCoordinatorStats: emit StatsLoading, call CoordinatorStatsService.getCoordinatorStats(), emit StatsLoaded on success or StatsError on failure
On FilterChanged: update active filter field, emit StatsLoading, re-fetch via service, emit StatsLoaded or StatsError
StatsLoaded.viewModel contains: kpiCards, monthlyBarSeries, activityTypeDonut, mentorStatsList — all fields non-null
BLoC constructor accepts CoordinatorStatsService as required parameter — no service instantiation inside BLoC
Active StatsFilter is stored as a mutable field on the BLoC, defaulting to StatsFilter.last30Days()
BLoC emits StatsLoading (not StatsInitial) on FilterChanged — widgets should show loading indicator not empty state
Error state message is human-readable and suitable for display in a SnackBar or error card
StatsBloc is extensible for task-010 (auto-refresh) and task-011 (filter propagation) without modifying this task's logic

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
CoordinatorStatsService (injected dependency)
data models
activity
activity_type
assignment
contact_chapter
annual_summary
performance requirements
State emission must not block the event queue — all async work in async event handlers
BLoC must handle rapid FilterChanged events without queuing duplicate loads — use transformer: droppable() or debounce
security requirements
userId passed to service must come from Supabase Auth current session — not from event payload
No sensitive mentor PII should appear in BLoC debug logs
ui components
KpiCardWidget (reads kpiCards from StatsLoaded)
BarChartWidget (reads monthlyBarSeries)
DonutChartWidget (reads activityTypeDonut)
MentorStatsListWidget (reads mentorStatsList)
PeriodFilterSelector (emits FilterChanged)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

StatsBloc is the primary BLoC for the coordinator dashboard screen — it will be extended in tasks 010 and 011. Design the class with extension points in mind: use protected-style comments marking where the Realtime subscription (task-010) and filter propagation (task-011) will be added. For FilterChanged debouncing, use `on((e, emit) async {...}, transformer: debounce(const Duration(milliseconds: 300)))` from the bloc_concurrency package. Store `StatsFilter _activeFilter` as a BLoC field (not in state) to allow task-011 to read and mutate it.

Keep event handlers small — the actual logic lives in CoordinatorStatsService. Use `on(..., transformer: restartable())` so a new manual reload cancels any in-flight fetch.

Testing Requirements

Unit tests using flutter_test + bloc_test. Scenarios: (1) initial state is StatsInitial, (2) LoadCoordinatorStats → [StatsLoading, StatsLoaded] on service success, (3) LoadCoordinatorStats → [StatsLoading, StatsError] on service failure, (4) FilterChanged → [StatsLoading, StatsLoaded] with updated filter, (5) FilterChanged while already loading — handled gracefully (no duplicate states), (6) StatsLoaded.viewModel is fully populated (no null fields), (7) StatsError carries a displayable string message not a raw exception, (8) default filter is last30Days. Mock CoordinatorStatsService with mocktail. Verify transformer behavior: send two FilterChanged events in rapid succession and assert only one load completes.

Component
Coordinator Statistics BLoC
infrastructure medium
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.