critical priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

RecognitionTierRepository is a Dart class with IRecognitionTierRepository abstract interface
fetchAllTiers(String organisationId) returns List<RecognitionTier> ordered by threshold ascending
fetchTierById(String id, String organisationId) returns RecognitionTier? — null if not found
createTier(CreateTierParams params) inserts a new tier and returns the created RecognitionTier with server-generated id
updateTier(String id, UpdateTierParams params) updates the mutable fields (name, icon_ref, colour_token) and returns updated RecognitionTier
RecognitionTier domain model has fields: id (String), organisationId (String), name (String), threshold (int), iconRef (String), colourToken (String), createdAt (DateTime)
threshold field validation: createTier throws InvalidTierException if threshold < 0
fetchAllTiers returns empty list (not null or exception) when no tiers exist for the organisation
Organisation scoping applied in all queries — no tier from another organisation is ever returned
TierNotFoundException thrown by fetchTierById when result is empty
Supabase PostgrestException mapped to TierRepositoryException with original error preserved as cause

Technical Requirements

frameworks
Flutter
Supabase
Riverpod
apis
Supabase PostgREST client
data models
RecognitionTier
CreateTierParams
UpdateTierParams
performance requirements
fetchAllTiers uses .order('threshold', ascending: true) server-side to avoid client-side sorting
Select only required columns in all queries
security requirements
Recognition tier configuration is org-admin only for writes — document this assumption; RLS enforces it
colour_token must reference a design token string, not a raw hex color, to prevent arbitrary color injection into the UI
threshold must be validated as a non-negative integer before persistence

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

This is the simplest of the three repositories. Follow the exact same pattern as BadgeRepository for consistency — same exception hierarchy, same interface pattern, same Riverpod provider registration. The `colourToken` field should be a validated string matching the project's design token naming convention (e.g., 'tier_bronze', 'tier_silver', 'tier_gold') — consider defining a `TierColourToken` enum or constant class rather than accepting arbitrary strings. For `updateTier`, construct the update map by only including non-null fields from `UpdateTierParams` to avoid overwriting fields the caller did not intend to change.

Use a `toUpdateJson()` method on `UpdateTierParams` that returns `Map` with only non-null entries. Since threshold ordering is critical for tier resolution logic (awarding the correct tier based on assignment count), add a comment in `fetchAllTiers` explaining why server-side ordering is mandatory here.

Testing Requirements

Unit tests with mocked SupabaseClient: (1) fetchAllTiers returns list sorted by threshold ascending from mock data, (2) fetchAllTiers returns empty list when Supabase returns empty array, (3) fetchTierById returns null when not found, (4) createTier with threshold < 0 throws InvalidTierException before calling Supabase, (5) updateTier sends only non-null fields to Supabase update, (6) Supabase PostgrestException is wrapped in TierRepositoryException. Integration test verifying create → fetch → update cycle against local Supabase. Minimum 85% line coverage.

Component
Recognition Tier Repository
data low
Epic Risks (2)
high impact medium prob scope

Badge criteria are stored as structured JSON in badge_definitions. If the JSON schema for criteria (threshold counts, streak lengths, training completion flags) is not well-defined upfront, the evaluation service will be built against a moving target, requiring costly migrations and refactors.

Mitigation & Contingency

Mitigation: Define and document the criteria JSON schema in a shared type file before any repository code is written. Review the schema with all three organisations' badge requirements — especially Blindeforbundet's honorar thresholds — and version the JSON schema using a 'criteria_version' field from day one.

Contingency: If the criteria schema must change after services are built, write a Supabase migration to backfill existing rows and add a migration version column. Keep the evaluation service criteria parser isolated behind an interface so only one function needs updating.

medium impact medium prob dependency

Badge icon assets may not yet exist or may fail WCAG 2.2 AA contrast validation (minimum 3:1 for graphical objects) when rendered over design-token backgrounds. Missing or non-compliant icons could block UI epic delivery for Blindeforbundet, for whom screen reader and visual accessibility is non-negotiable.

Mitigation & Contingency

Mitigation: During this epic, implement the contrast-ratio validator in badge-icon-asset-manager and run it as a Flutter test against all candidate icon assets early. Coordinate with the design team to provide WCAG-compliant SVG icons in both locked and unlocked variants before the UI epic begins.

Contingency: If assets are late or fail contrast checks, ship placeholder icons that are guaranteed compliant (solid design-token colour fills with text labels) and swap in final assets post-QA without requiring a code change.