medium priority low complexity backend pending backend specialist Tier 6

Acceptance Criteria

A Riverpod AsyncNotifierProvider named recognitionTierProvider is defined and exported from the recognition_tier_service library
getCurrentTier(mentorId) returns the active TierAssignment for the mentor or null if none exists
getEligibilitySummary(mentorId) returns a TierEligibility object (reusing evaluateEligibility from task-011)
The provider rebuilds automatically when the underlying PeerMentorStatsAggregator provider or BadgeConfigurationService provider emits a new value
Consumer widgets receive AsyncValue<TierAssignment?> and can handle loading, data, and error states
Provider is properly scoped and does not leak between different mentor contexts (uses mentorId as a family parameter if needed)
Calling invalidate() on the provider triggers a fresh evaluation
Provider does not hold stale state after a tier assignment or revocation operation completes

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase REST API
data models
TierAssignment
TierEligibility
performance requirements
Provider state is cached — repeated calls to getCurrentTier with the same mentorId must not trigger duplicate Supabase queries within the same provider lifecycle
Re-evaluation triggered by dependency invalidation must complete within 1 second
security requirements
Provider must not expose tier data for a mentorId that does not belong to the authenticated user unless the authenticated user is a coordinator or admin

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Use Riverpod's family modifier if the provider must be keyed by mentorId: `recognitionTierProvider = AsyncNotifierProvider.family`. Define TierState as a value class holding both getCurrentTier result and getEligibilitySummary result to avoid two separate provider subscriptions in consumer widgets. Watch the stats provider and config provider using ref.watch inside the AsyncNotifier's build() method so Riverpod handles dependency tracking automatically. Avoid manual cache invalidation where possible — prefer reactive re-computation.

Export provider via a providers.dart barrel file so consumers have a single import path.

Testing Requirements

Widget tests and provider unit tests (flutter_test + Riverpod ProviderContainer) covering: (1) getCurrentTier returns correct AsyncValue.data when RecognitionTierService returns a TierAssignment, (2) getCurrentTier returns AsyncValue.data(null) when no active assignment exists, (3) getEligibilitySummary returns correct TierEligibility, (4) provider rebuilds when a dependency provider is invalidated, (5) provider emits AsyncValue.error when RecognitionTierService throws. Use ProviderContainer with overrideWithValue to inject mock RecognitionTierService.

Component
Recognition Tier Service
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.