high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

A Riverpod `AsyncNotifierProvider` named `peerMentorStatsProvider` is defined and exported from a dedicated provider file
`getStatsForMentor(mentorId)` is callable from any widget or other provider and returns an `AsyncValue<PeerMentorStats>`
`getMilestoneStatus(mentorId, orgId)` returns an `AsyncValue<MilestoneStatus>` containing crossed threshold integers and the next threshold to be reached
Provider integrates with `badgeRepositoryProvider` from the foundation epic — milestone status reads badge definitions via that provider, not via a new Supabase call
After a new activity registration event is emitted (via a Riverpod `ref.invalidate` or a dedicated event notifier), the `peerMentorStatsProvider` cache is invalidated and reloaded within one event loop cycle
Provider exposes a loading state correctly (AsyncLoading) while data is being fetched, and an error state (AsyncError) with a user-presentable message on failure
Provider does not re-fetch when the same `mentorId` is requested within the cache TTL window (use `keepAlive` or equivalent)
No direct Supabase client calls exist in the provider layer — all data access is delegated to PeerMentorStatsAggregator
Provider is scoped to the current authenticated user's accessible mentors; attempting to read stats for an out-of-scope mentorId returns an AsyncError with a permission message

Technical Requirements

frameworks
Flutter
Riverpod (AsyncNotifierProvider, ref.invalidate, keepAlive)
Dart
apis
Indirect Supabase access via PeerMentorStatsAggregator
badgeRepositoryProvider from foundation epic
data models
PeerMentorStats
MilestoneStatus
BadgeDefinition
performance requirements
Provider build (cold start) must resolve within 500 ms on a normal connection
Cache must prevent duplicate Supabase fetches for the same mentorId within a 60-second window
Invalidation after activity registration must complete provider rebuild within 200 ms of the event
security requirements
Provider must read the current user's role from the auth provider before exposing stats; non-authorised access returns AsyncError
mentorId parameter must be validated (non-empty string, UUID format) before dispatching to the aggregator

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use `AsyncNotifierProvider.family` if multiple mentors need to be observed simultaneously (e.g. coordinator view). For activity event integration, consider a lightweight `activityRegisteredProvider` (a `StateProvider` acting as a revision counter) that `peerMentorStatsProvider` watches — incrementing the counter automatically invalidates the stats cache without tight coupling. Use `ref.keepAlive()` with a timer to implement TTL-based cache expiry rather than relying on widget disposal.

Avoid exposing the raw `PeerMentorStatsAggregator` instance outside of this provider file; consumers should only interact with the Riverpod API. Ensure `getMilestoneStatus` merges the threshold data from the aggregator with badge definition metadata from `badgeRepositoryProvider` in a single derived computation, not in the UI layer.

Testing Requirements

Write widget tests (flutter_test + ProviderContainer) that verify: provider returns AsyncLoading immediately on first build; provider returns correct AsyncData after mock aggregator resolves; provider returns AsyncError when aggregator throws; invalidation after a mock activity event triggers a rebuild and returns fresh data. Use `ProviderContainer.overrideWithValue` to inject a mock PeerMentorStatsAggregator. Verify that cache prevents a second fetch within TTL. Verify that out-of-scope mentorId results in AsyncError.

Component
Peer Mentor Stats Aggregator
service medium
Epic Risks (3)
high impact medium prob technical

peer-mentor-stats-aggregator must compute streaks and threshold counts across potentially hundreds of activity records per peer mentor. Naive queries (full table scans or N+1 patterns) will cause slow badge evaluation, especially when triggered on every activity save for all active peer mentors.

Mitigation & Contingency

Mitigation: Design aggregation queries using Supabase RPCs with window functions or materialised views from the start. Add database indexes on (peer_mentor_id, activity_date, activity_type) before writing any service code. Profile all aggregation queries against a dataset of 500+ activities during development.

Contingency: If query performance is insufficient at launch, implement incremental stat caching: maintain a peer_mentor_stats snapshot table updated on each activity insert via a database trigger, so the aggregator reads from pre-computed values rather than scanning raw activity rows.

medium impact low prob technical

badge-award-service must be idempotent, but if two concurrent edge function invocations evaluate the same peer mentor simultaneously (e.g., from a rapid double-save), both could pass the uniqueness check before either commits, resulting in duplicate badge records.

Mitigation & Contingency

Mitigation: Rely on the database-level uniqueness constraint (peer_mentor_id, badge_definition_id) as the final guard. In the service layer, use an upsert with ON CONFLICT DO NOTHING and return the existing record. Add a Postgres advisory lock or serialisable transaction for the award sequence during the edge function integration epic.

Contingency: If duplicate records are discovered in production, run a deduplication migration to remove extras (keeping earliest earned_at) and add a unique index if not already present. Alert engineering via Supabase database webhook on constraint violations.

medium impact medium prob scope

The badge-configuration-service must validate org admin-supplied criteria JSON on save, but the full range of valid criteria types (threshold, streak, training-completion, tier-based) may not be fully enumerated during development, leading to either over-permissive or over-restrictive validation that frustrates admins.

Mitigation & Contingency

Mitigation: Define a versioned Dart sealed class hierarchy for CriteriaType before writing the validation logic. Review the hierarchy with product against all known badge types across NHF, Blindeforbundet, and HLF before implementation. Build the validator against the sealed class so new criteria types require an explicit code addition.

Contingency: If admins encounter validation rejections for legitimate criteria, expose a 'criteria_raw' escape hatch (JSON passthrough, admin-only) with a product warning, and schedule a sprint to formalise the new criteria type properly.