Implement Badge Evaluation Service
epic-achievement-badges-ui-task-015 — Build the badge-evaluation-service that evaluates all active badge definitions against the peer mentor's current stats from peer-mentor-stats-aggregator and determines which badges are newly earnable. Coordinate with badge-repository to exclude already-earned badges and with badge-definition-repository for criteria definitions. Return a list of newly earned badge IDs for consumption by badge-award-service.
Acceptance Criteria
Technical Requirements
Execution Context
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
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.
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.
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.