critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

PeerMentorStats class is immutable (all fields final), contains AssignmentCount, StreakData, TrainingCompletion, and HonorarMilestone as fields
AssignmentCount class has fields: totalCount (int), countByType (Map<String, int>), and periodCounts (Map<String, int> for weekly/monthly aggregates)
StreakData class has fields: currentStreakWeeks (int), longestStreakWeeks (int), lastActivityDate (DateTime?), and isActive (bool)
TrainingCompletion class has fields: completedCourseIds (List<String>), completedCount (int), and completionDates (Map<String, DateTime>)
HonorarMilestone class has fields: totalAssignmentCount (int), hasReachedThirdAssignment (bool), hasReachedFifteenthAssignment (bool), and thresholdsMet (List<int>)
All classes implement fromJson(Map<String, dynamic>) factory constructor and toJson() method
All classes implement copyWith() method with all fields as optional named parameters
All classes override == and hashCode using field-based equality
All classes override toString() for debug readability
Dart files placed in lib/features/badges/data/models/ or equivalent feature-layer path
Unit tests for fromJson/toJson round-trip pass for each class with both full and partial/nullable field data

Technical Requirements

frameworks
Flutter
Dart
data models
PeerMentorStats
AssignmentCount
StreakData
TrainingCompletion
HonorarMilestone
performance requirements
Model instantiation must be allocation-efficient — avoid unnecessary object copies in fromJson
toJson must produce Supabase-compatible JSON (snake_case keys matching column names)
security requirements
No PII fields in stats models — use mentor_id (UUID) references only, not names or contact details

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use const constructors where possible for compile-time immutability guarantees. For DateTime serialization, use ISO 8601 strings (toIso8601String() / DateTime.parse()) to stay compatible with Supabase's timestamptz columns. Use snake_case JSON keys to match Supabase column naming (e.g., 'current_streak_weeks' not 'currentStreakWeeks'). Do NOT use code generation (json_serializable/freezed) unless already established in the project — hand-write serialization to match existing project conventions.

The HonorarMilestone model specifically serves Blindeforbundet's requirement for tracking the 3rd and 15th assignment thresholds that trigger honorar payments — ensure the threshold logic is data-driven (thresholdsMet list) to support future threshold additions without model changes.

Testing Requirements

Write unit tests using flutter_test for each model class: (1) fromJson correctly maps all fields including nested objects, (2) toJson produces the expected Map with correct key names, (3) fromJson/toJson round-trip preserves all values, (4) copyWith returns a new instance with only the specified fields changed and all others preserved, (5) equality check: two instances with identical fields are equal, (6) null/optional field handling in fromJson does not throw. Place tests in test/features/badges/data/models/. No mocking required.

Component
Peer Mentor Stats Aggregator
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.