high priority low complexity database pending database specialist Tier 0

Acceptance Criteria

RecognitionTierRepository exposes: fetchTierDefinitions() → Future<List<TierDefinition>>, fetchCurrentTier(String peerMentorId) → Future<TierAssignment?>, and invalidateCache(String peerMentorId)
TierDefinition model includes: id, displayName, iconKey, awardPeriod (enum: annual, biannual, quarterly), thresholdCriteria (Map<String, dynamic>), and sortOrder (int)
TierAssignment model includes: peerMentorId, tierId, assignedAt (DateTime), validUntil (DateTime?), and organizationId
fetchCurrentTier returns null (not an exception) when no tier is assigned to the peer mentor — UI handles the null case as 'no tier yet'
Tier definitions are cached in memory per session; current tier assignment is cached per peerMentorId and invalidated by invalidateCache
RLS on the tier_assignments Supabase table enforces that a peer mentor may only read their own assignment
Unit tests cover all methods including null return path and cache invalidation
Repository is registered as a Riverpod provider

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase REST API
Supabase RLS
data models
TierDefinition
TierAssignment
PeerMentor
performance requirements
fetchCurrentTier must complete within 2 seconds on a standard mobile connection
Tier definitions cache hit must be near-instant (< 5ms) after initial load
security requirements
RLS policy must prevent a peer mentor from reading another peer mentor's tier assignment
Organization admins may read all tier assignments within their organization — this must be reflected in RLS policy design
No write operations are exposed through this repository — tier assignments are written by backend services only

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use two separate cache maps: one for tier definitions (Map keyed by id, shared across all callers) and one for current assignments (Map keyed by peerMentorId). The nullable value in the assignment cache is intentional — it distinguishes 'cached as no tier' from 'not yet fetched'. Supabase tables: tier_definitions (id, display_name, icon_key, award_period, threshold_criteria jsonb, sort_order) and tier_assignments (id, peer_mentor_id, tier_id, assigned_at, valid_until, organization_id). The awardPeriod enum should map from the string values stored in Supabase ('annual', 'biannual', 'quarterly') using a fromString factory.

Register as Riverpod Provider. Note that the RecognitionTierBanner widget (component 547) will be the primary consumer of fetchCurrentTier — ensure the provider is scoped appropriately to avoid unnecessary re-fetches when the banner re-renders.

Testing Requirements

Unit tests using flutter_test with mocked Supabase client. Test cases: (1) fetchTierDefinitions returns correctly typed list from Supabase response, (2) fetchCurrentTier returns TierAssignment when a row exists, (3) fetchCurrentTier returns null when no row exists for peer mentor, (4) invalidateCache causes next fetchCurrentTier to re-fetch from Supabase, (5) Supabase error on fetchCurrentTier throws RecognitionTierRepositoryException, (6) validUntil is correctly parsed as null when the column is null. Verify sortOrder field drives correct ordering of tier definitions.

Component
Recognition Tier Banner
ui low
Epic Risks (2)
medium impact medium prob integration

The badge-earned-celebration overlay must appear within 2 seconds of the triggering activity being saved, but badge evaluation runs server-side in an edge function triggered by a database webhook. Network latency, edge function cold start, and Supabase Realtime delivery delays could cause the overlay to appear late or not at all, breaking the motivational loop.

Mitigation & Contingency

Mitigation: Implement an optimistic UI path: after activity save, badge-bloc immediately checks whether any badge thresholds are crossed client-side using cached stats and badge definitions, showing the overlay speculatively before server confirmation. The server result then reconciles. Subscribe to Supabase Realtime on the earned_badges table for authoritative confirmation.

Contingency: If Realtime delivery is unreliable in production, add a polling fallback: badge-bloc polls for new earned badges 3 seconds after an activity save and shows the overlay if a new record is detected, accepting up to 5-second latency as a fallback SLA.

high impact low prob technical

The celebration overlay uses animation for positive reinforcement, but motion sensitivity (prefers-reduced-motion) and screen reader users require a non-animated or text-only alternative. Failing to handle this risks excluding Blindeforbundet users or triggering vestibular discomfort for motion-sensitive volunteers.

Mitigation & Contingency

Mitigation: Check MediaQuery.disableAnimations in badge-earned-celebration-overlay and skip animation entirely when true, showing a static card instead. Add an ExcludeSemantics wrapper around the decorative animation widget and a separate Semantics node with a live region announcement of the badge name and congratulatory message.

Contingency: If accessibility issues are identified in TestFlight testing with Blindeforbundet's test group, fast-track a patch that defaults to the static card path and gates the animation behind a user preference setting in notification preferences.