critical priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

BadgeDefinitionRepository is a Dart class with IBadgeDefinitionRepository abstract interface
fetchAllDefinitions(String organisationId) returns List<BadgeDefinition> for the given organisation, ordered by name
fetchDefinitionById(String id, String organisationId) returns BadgeDefinition? — returns null if not found, throws if organisationId mismatch
createDefinition(CreateBadgeDefinitionParams params) inserts a new record and returns the created BadgeDefinition with server-generated id and created_at
updateDefinition(String id, UpdateBadgeDefinitionParams params) updates mutable fields and returns the updated BadgeDefinition
BadgeCriteria is a strongly typed Dart model with fields: type (BadgeCriteriaType enum: assignmentCount, hourCount), threshold (int), honorarLevel (String?)
BadgeCriteria.fromJson correctly parses {"type": "assignment_count", "threshold": 3} and {"type": "assignment_count", "threshold": 15, "honorar_level": "standard"}
BadgeCriteria.toJson produces valid JSON stored correctly in the Supabase JSONB column
fetchAllDefinitions returns only is_active = true definitions by default; an optional parameter includeInactive: true returns all
All write operations include organisation_id from the params object — never derived from the Supabase session alone
BadgeDefinitionNotFoundException thrown when fetchDefinitionById returns null
Supabase exceptions mapped to domain exceptions using the same pattern as BadgeRepository

Technical Requirements

frameworks
Flutter
Supabase
Riverpod
apis
Supabase PostgREST client
Dart json_annotation (optional) or manual fromJson/toJson
data models
BadgeDefinition
BadgeCriteria
BadgeCriteriaType
CreateBadgeDefinitionParams
UpdateBadgeDefinitionParams
performance requirements
fetchAllDefinitions should use .select() with explicit column list to avoid fetching unused columns
For organisations with many definitions, support pagination via range() parameters in a future iteration — design the method signature to be extensible (optional limit/offset params)
security requirements
criteria JSON must be validated against the expected schema before persistence — reject unknown type values
badge definitions are organisational configuration — only org_admin and coordinator roles should be able to create/update them (enforced by RLS, but document this assumption in code)
Do not allow criteria threshold values below 1 — validate in the domain model constructor

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define `BadgeCriteriaType` as a Dart enum with `assignmentCount` and `hourCount` values, with a static `fromString` factory that maps snake_case JSON values ('assignment_count') to enum values. Use `freezed` or a simple immutable class for `BadgeCriteria`. The `criteria` field on `BadgeDefinition` must never be nullable — default to `BadgeCriteria.empty()` if the DB returns null. For `updateDefinition`, use `.update(params.toJson()).eq('id', id).eq('organisation_id', organisationId)` to ensure organisation scoping even on updates.

The `UpdateBadgeDefinitionParams` should use Dart's named optional parameters with no required fields (all nullable) so callers only send changed fields. Use `json_serializable` code generation if the project already uses it, otherwise manual fromJson/toJson is sufficient for these small models. Document the criteria JSON schema shape in the BadgeCriteria class docstring for future maintainers.

Testing Requirements

Unit tests with mocked SupabaseClient covering: (1) fetchAllDefinitions returns correctly mapped BadgeDefinition list with parsed BadgeCriteria, (2) BadgeCriteria.fromJson correctly parses both threshold=3 and threshold=15 variants including honorar_level field, (3) BadgeCriteria.toJson round-trips correctly (parse then serialise produces identical JSON), (4) createDefinition passes criteria as JSON to Supabase insert, (5) fetchDefinitionById returns null when Supabase returns empty list, (6) fetchAllDefinitions default call includes is_active filter. Integration test: create a definition with criteria, fetch it back, verify criteria fields match. Test BadgeCriteriaType enum exhaustiveness — all enum values must have corresponding fromJson handling. 90%+ coverage target.

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