high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

PersonalStatsService exposes a single getPersonalStats(String mentorId, StatsFilter filter) method returning a typed result object
Service fetches activity rows from StatsRepository scoped strictly to the requesting mentor's peer_mentor_id — no cross-mentor data leakage
StatsCacheManager 15-minute TTL cache is checked before any Supabase query; cache key must incorporate mentorId + filter period hash
On cache miss, repository is queried and result is stored in cache with correct TTL before returning
KpiCardViewModel is populated with: total activities count, total hours, contacts reached count, and current-period vs previous-period delta percentages
BarChartSeriesViewModel groups activities by month within the selected filter period, producing one data point per calendar month
ContributionData structure contains: Map<ActivityTypeId, Map<YearMonth, int>> counts, sorted chronologically, covering at least 12 months of history for Phase 4 Wrapped compatibility
Service returns a Result<PersonalStatsData> (success/failure) — it never throws; all Supabase errors are caught and mapped to failure results
When filter period is changed, cache for previous period is NOT invalidated — only the requested period key is fetched/cached independently
Empty activity history (new mentor) returns valid empty view models with zero values, not errors
ContributionData month keys use ISO year-month format (e.g. '2025-03') to ensure serialization stability across locales

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
Supabase PostgreSQL 15 (via StatsRepository)
Supabase Auth (JWT for RLS enforcement)
data models
activity
activity_type
assignment
annual_summary
performance requirements
Cache hit must resolve in under 5ms (in-memory lookup only)
Cache miss Supabase query must complete within 2 seconds on 4G mobile connection
ContributionData map construction must handle up to 500 activity rows without UI jank (compute isolate if >200 rows)
Delta percentage calculations must be O(1) given pre-aggregated repository results
security requirements
Supabase RLS on activities table ensures mentorId filter is enforced server-side even if client passes wrong ID
JWT claims validated server-side — service must pass auth token from Supabase Auth session to repository layer
ContributionData must not include contact names or PII — only aggregate counts by type and month
Cache keys must not be predictable across mentors (include mentorId as non-guessable UUID component)

Execution Context

Execution Tier
Tier 2

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 pattern so expiry is checked lazily on access. For ContributionData forward-compatibility with Phase 4 Wrapped: agree on the exact Map key format now (ISO year-month string) and document it in the class — changing this later will break serialized Wrapped summaries. Delta percentage calculation: fetch two periods from repository in one batch query using a UNION or two parallel futures, not two sequential calls.

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.

Component
Personal Statistics Service
service 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.