high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

A Riverpod `Provider<BadgeAwardService>` named `badgeAwardServiceProvider` is defined and exported from a dedicated provider file
`badgeAwardServiceProvider` reads `supabaseClientProvider` (from foundation) and `badgeRepositoryProvider` as dependencies — no direct Supabase client instantiation inside the provider definition
An async method `awardBadge(mentorId, badgeDefinitionId, orgId)` is accessible via the provider and returns `Future<EarnedBadge>`
`getAwardedBadgesForMentor(mentorId)` returns `Future<List<EarnedBadge>>` and reads from `badgeRepositoryProvider`
After a successful `awardBadge` call, the provider invalidates `peerMentorStatsProvider` for the affected mentorId so the UI reflects updated milestone counts without requiring a manual refresh
After a successful `awardBadge` call, the provider invalidates the relevant badge list cache in `badgeRepositoryProvider` so `getAwardedBadgesForMentor` returns fresh data on the next call
If `awardBadge` throws a `BadgeAwardException`, no cache invalidation occurs and the exception propagates to the caller unchanged
The provider does not introduce any in-memory state of its own — all state lives in `badgeRepositoryProvider` and `peerMentorStatsProvider`
The provider is testable via `ProviderContainer` with overridden dependencies and does not require a live Supabase connection in tests

Technical Requirements

frameworks
Flutter
Riverpod (Provider, ref.invalidate)
Dart
apis
BadgeAwardService (task-009)
badgeRepositoryProvider from foundation epic
peerMentorStatsProvider (task-006)
data models
EarnedBadge
BadgeDefinition
performance requirements
Provider construction (synchronous) must complete in < 5 ms
Cache invalidation after awardBadge must trigger within one async event loop cycle
getAwardedBadgesForMentor must use badgeRepositoryProvider's cache and not issue duplicate Supabase calls
security requirements
The provider must not expose raw Supabase client references to consumers
awardBadge must validate that mentorId and badgeDefinitionId are non-empty strings before delegating to BadgeAwardService

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use `Provider` (synchronous, not Async) because BadgeAwardService construction is synchronous — only its methods are async. Expose the mutating operations (`awardBadge`) as methods on a thin wrapper class or directly via a ref-scoped function rather than as provider state, to avoid making the provider itself async. For cache invalidation, inject `Ref` into the service wrapper so it can call `ref.invalidate(peerMentorStatsProvider(mentorId))` after a successful award — this keeps the invalidation logic co-located with the operation that triggers it. Avoid using `ref.refresh` (which eagerly reloads) in favour of `ref.invalidate` (which marks stale and reloads on next read) to prevent unnecessary background fetches.

Keep this provider file small — its sole responsibility is wiring BadgeAwardService to Riverpod and coordinating post-award invalidation. All business logic stays in BadgeAwardService and the repository.

Testing Requirements

Widget/unit tests using ProviderContainer: verify `badgeAwardServiceProvider` builds without error when given mock dependencies; verify `awardBadge` delegates to the mock BadgeAwardService and returns EarnedBadge; verify that after a successful awardBadge, `peerMentorStatsProvider` is invalidated (spy on ref.invalidate or observe a rebuild); verify that on BadgeAwardException, no invalidation occurs; verify `getAwardedBadgesForMentor` calls through to badgeRepositoryProvider and returns the correct list. Use `ProviderContainer.overrideWithValue` for all dependencies.

Component
Badge Award 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.