high priority high complexity backend pending backend specialist Tier 2

Acceptance Criteria

Service evaluates all active badge definitions from badge-definition-repository and returns only IDs for badges not yet earned by the peer mentor
Service correctly applies each badge criteria type (e.g., session count threshold, streak, specific activity type) against peer mentor stats
Already-earned badge IDs from badge-repository are excluded from the returned list even if criteria are still met
Service returns an empty list (not null or an error) when no new badges are earnable
Service is stateless and pure: given the same stats and definitions, it always returns the same result
Service does not perform any database writes — read-only responsibility
All evaluation logic is covered by unit tests with ≥ 90% branch coverage
Service handles a null or empty stats object gracefully, returning an empty list
Service handles a badge-definition-repository response with zero active definitions, returning an empty list
Evaluation completes within 200ms for a peer mentor with up to 100 active badge definitions
Service exposes a clearly typed interface: Future<List<String>> evaluate(PeerMentorStats stats, String mentorId)

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
badge-definition-repository.getActiveDefinitions()
badge-repository.getEarnedBadgeIds(mentorId)
peer-mentor-stats-aggregator.getStats(mentorId)
data models
BadgeDefinition
BadgeCriteria
PeerMentorStats
EarnedBadge
performance requirements
Evaluation of up to 100 badge definitions completes in under 200ms
No blocking I/O inside the evaluation loop — all data fetched before evaluation starts
security requirements
Service must only evaluate badges for the authenticated peer mentor — mentorId must be validated against the current session
No badge definition data containing sensitive thresholds should be logged at info level

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Structure the service as a Dart class BadgeEvaluationService with a single public async method evaluate(). Fetch all required data in parallel using Future.wait([getActiveDefinitions(), getEarnedBadgeIds(mentorId), getStats(mentorId)]) before entering the evaluation loop — never fetch inside the loop. Implement criteria evaluation as a strategy/visitor pattern: a BadgeCriteriaEvaluator interface with concrete implementations per criteria type (SessionCountCriteriaEvaluator, StreakCriteriaEvaluator, etc.) — this makes adding new criteria types trivial and keeps each evaluator independently testable. Convert earned badge IDs to a Set before the loop for O(1) exclusion lookups.

If Riverpod is used in the dependency injection layer, register BadgeEvaluationService as a Provider or FutureProvider for easy override in tests. Never log badge criteria thresholds or stats values at info level; use debug-only logging.

Testing Requirements

Write unit tests (flutter_test): (1) a mentor with qualifying stats for badge A and not badge B returns only badge A's ID, (2) already-earned badges are excluded even when stats still qualify, (3) empty stats returns empty list, (4) zero active definitions returns empty list, (5) each supported criteria type (session count, streak, activity type) is evaluated correctly for both pass and fail cases. Use mockito to mock badge-definition-repository, badge-repository, and peer-mentor-stats-aggregator. Achieve ≥ 90% branch coverage measured with flutter test --coverage. Write at least one integration test connecting to a local Supabase test instance to verify end-to-end evaluation flow.

Component
Badge BLoC
infrastructure medium
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.