critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

StatsFilter is an immutable Dart class with fields: DateTimeRange? dateRange, String? activityType, String? chapterId, and supports copyWith, equality, and hashCode
KpiCardViewModel contains: String label, int value, String? trend (e.g. '+12%'), String accentColor sourced from design tokens — no hardcoded hex values
BarChartSeriesViewModel contains: String label, List<BarChartDataPoint> dataPoints where BarChartDataPoint has double value and String periodLabel
DonutSegmentViewModel contains: String label, double percentage, int count, String color from design tokens
CoordinatorStatsViewModel aggregates: List<KpiCardViewModel> kpiCards, List<BarChartSeriesViewModel> barSeries, List<DonutSegmentViewModel> donutSegments, ContributionData contributionData
PersonalStatsViewModel mirrors CoordinatorStatsViewModel but scoped to a single peer mentor's data
ContributionData contains: int totalActivities, int totalHours, int uniqueContacts, DateTime? lastActivityDate
StatsScope is a sealed class with two subtypes: ChapterScope(chapterId) and OrganisationScope(orgId)
Abstract IStatsRepository interface defines: Future<CoordinatorStatsViewModel> getCoordinatorStats(StatsScope scope, StatsFilter filter) and Future<PersonalStatsViewModel> getPersonalStats(String peerId, StatsFilter filter)
Abstract IStatsCacheManager interface defines: get, set, invalidate(String key), and invalidateAll operations with typed signatures
All classes are pure Dart with no Flutter widget imports — safe for use in service and repository layers
All model files are placed under lib/features/statistics/domain/models/ and interfaces under lib/features/statistics/domain/repositories/

Technical Requirements

frameworks
Flutter
Dart
Riverpod
data models
proxy_activities
peer_mentors
chapters
organisations
user_roles
performance requirements
All model classes must be immutable to support safe caching and BLoC state comparison
ViewModel construction from raw aggregation rows must complete in under 1ms for up to 500 rows
security requirements
No PII (names, contact details) stored directly in ViewModel classes — use anonymised IDs only
StatsScope must not be constructable from client-supplied strings without server-side role validation

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart's built-in `==` and `hashCode` via `Object` or the `equatable` package if already in the project dependencies — do not introduce new packages without team approval. Prefer sealed classes over abstract classes for StatsScope so the compiler enforces exhaustive pattern matching in switch expressions (Dart 3+). Keep ViewModels flat — avoid nested ViewModels more than two levels deep to simplify BLoC state diffing. Use `const` constructors wherever possible.

The color fields in KpiCardViewModel and DonutSegmentViewModel should reference design token identifiers (e.g., `AppColors.accent`) not raw strings, so enforce this via a custom typedef or enum rather than plain String if the design token system supports it. Align field naming with the existing codebase conventions (camelCase, no Hungarian notation).

Testing Requirements

Write unit tests using flutter_test for every model class: verify fromJson/toJson round-trips, equality checks (two instances with same data are equal), and copyWith correctness. Test that StatsScope sealed class exhaustive switching compiles without warnings. Test that CoordinatorStatsViewModel and PersonalStatsViewModel correctly aggregate their child ViewModels. No mocking required for this task — pure data class tests only.

Target 100% line coverage for all model files.

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.