high priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

RecruitmentMilestoneEvent is an immutable Dart class (use @immutable or final fields) with required fields: mentorId (String), milestoneType (MilestoneType enum), confirmedCount (int), occurredAt (DateTime)
MilestoneType is a Dart enum with values: first_referral, referral_count_5, referral_count_10, and at minimum one generic threshold value (e.g. referral_count_custom) for extensibility
RecruitmentMilestoneEvent implements == and hashCode based on all fields (use equatable package or manual override)
RecruitmentMilestoneEvent has a copyWith method for immutable updates
RecruitmentMilestoneEvent has a toJson() and fromJson() factory for serialization (for logging and debugging purposes)
The abstract subscription interface (e.g. RecruitmentMilestoneSubscriber) defines a single method: void onMilestoneReached(RecruitmentMilestoneEvent event)
ReferralAttributionService exposes a method to register a RecruitmentMilestoneSubscriber (e.g. addMilestoneSubscriber(RecruitmentMilestoneSubscriber subscriber))
All types are placed in a dedicated file path following project layer conventions (e.g. lib/domain/recruitment/ or lib/models/recruitment/)
Unit tests verify: RecruitmentMilestoneEvent equality, copyWith correctness, toJson/fromJson round-trip
No circular dependencies introduced — the model/interface file must not import from badge or BLoC layers

Technical Requirements

frameworks
Flutter
Dart
equatable (if used in project for value equality)
data models
badge_definition
assignment
performance requirements
Event class must be lightweight — no heavy computations in constructor or equality checks
security requirements
mentorId must be a validated UUID string — add assertion or factory validation
confirmedCount must be non-negative — add assert(confirmedCount >= 0) in constructor

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep this file purely as a domain model — zero Flutter widget or Supabase imports. Use Dart's enum with no extra dependencies. For equatable, check if the project already uses the equatable package before adding it; if not, implement == and hashCode manually or use Dart records (Dart 3+) if the project targets Dart 3. The subscription interface should use Dart abstract class, not a typedef callback, to allow BadgeCriteriaIntegration to implement it cleanly and be testable via mocking.

File naming convention: recruitment_milestone_event.dart for the model, recruitment_milestone_subscriber.dart for the interface. Keep both files under 60 lines each.

Testing Requirements

Unit tests (flutter_test) required: (1) two RecruitmentMilestoneEvent instances with identical fields are equal (== returns true), (2) instances with differing fields are not equal, (3) copyWith produces a new instance with the changed field and all others unchanged, (4) toJson produces the expected map structure, (5) fromJson(toJson(event)) round-trips correctly without data loss, (6) constructing an event with negative confirmedCount throws AssertionError. No integration tests needed for this task — pure domain model.

Component
Badge Criteria Integration
service medium
Epic Risks (3)
medium impact high prob dependency

BadgeCriteriaIntegration must reference specific badge definition IDs from the badge-definition-repository for recruitment badges. If those badge definitions have not been created in the database when this epic is implemented, the integration will silently fail to award badges.

Mitigation & Contingency

Mitigation: As the first task of this epic, create the four recruitment badge definitions (seed data migration) with known, stable IDs. BadgeCriteriaIntegration hardcodes these IDs as constants. Include an assertion in the integration tests that verifies the badge definition records exist in the test database.

Contingency: If the badge definitions system does not support seeding at migration time, store the badge definition IDs in a feature-flag-style config table and look them up at runtime, falling back to a no-op with a warning log if they are absent.

medium impact medium prob technical

The coordinator dashboard aggregates referral stats across all peer mentors in an organisation. For large organisations (HLF has many peer mentors nationally), the aggregation query may be slow, causing the dashboard to feel unresponsive.

Mitigation & Contingency

Mitigation: Implement the aggregation as a Supabase database view or RPC that runs server-side with appropriate indexes on (mentor_id, org_id, created_at, event_type). Add a composite index on referral_events during the foundation epic's migration. Cache the result in the Riverpod provider with a 5-minute TTL.

Contingency: If query performance remains unacceptable at scale, materialise the aggregation in a nightly pg_cron job into a stats_cache table, and serve the dashboard from the cache with a 'last updated' timestamp shown to the coordinator.

medium impact low prob integration

The existing badge award service is implemented by the achievement-badges feature. If that feature's public API (BadgeAwardService interface) changes while this epic is in progress, the BadgeCriteriaIntegration will break at compile time or behave incorrectly at runtime.

Mitigation & Contingency

Mitigation: Confirm the BadgeAwardService interface is stable and document the exact method signatures this integration depends on. Write a narrow integration test that constructs the real BadgeAwardService against a test database to detect breaking changes immediately.

Contingency: If the badge service interface changes, adapt the BadgeCriteriaIntegration adapter class to match the new contract. The adapter pattern used here isolates the change to a single class.