high priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

ReferralStats is an immutable Dart class (final fields, const constructor) with fields: linksIssued (int), clicks (int), confirmedRegistrations (int), conversionRate (double, 0.0–1.0)
DateRangeFilter is an immutable value object with fields: startDate (DateTime), endDate (DateTime) and a validate() method that throws ArgumentError if endDate is before startDate
ReferralStats.fromJson(Map<String, dynamic>) factory constructor correctly parses camelCase and snake_case keys from the Supabase response
ReferralStats.toJson() produces a map suitable for logging and analytics (no sensitive fields)
ReferralStats includes an == override and hashCode for value equality (or uses equatable package)
DateRangeFilter includes a copyWith() method for immutable updates (e.g., changing only the start date)
A MentorReferralStats wrapper model exists that pairs a mentorId (String) with a ReferralStats instance for per-mentor aggregation in the dashboard
All models are located in a dedicated `models/recruitment/` directory and exported via a barrel file
Null safety is fully enforced — no dynamic, no Object?, no late fields without initialization

Technical Requirements

frameworks
Flutter
Dart
apis
Supabase REST API — referral_stats aggregate view or RPC function response shape
data models
ReferralStats
DateRangeFilter
MentorReferralStats
performance requirements
Models must be const-constructible where all fields are known at compile time to enable widget const optimization
fromJson must handle both int and double types for numeric fields gracefully (Supabase may return numeric as double)
security requirements
conversionRate must be clamped to 0.0–1.0 in the constructor to prevent display anomalies from malformed API data
No PII fields (member names, emails) may be stored in ReferralStats — only aggregate numeric data

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the `equatable` package for value equality rather than manually writing == and hashCode — it reduces boilerplate and is already likely in the project dependencies. Define conversionRate clamping in the constructor body: `conversionRate = rate.clamp(0.0, 1.0)`. For DateRangeFilter, consider providing factory constructors for common ranges: `DateRangeFilter.lastThirtyDays()`, `DateRangeFilter.currentMonth()`, `DateRangeFilter.currentYear()` — these will be immediately useful in the dashboard BLoC (task-002). Keep the models free of any Flutter dependency (no BuildContext, no widgets) so they can be tested as pure Dart without a widget environment.

The Supabase RPC function returning aggregated stats may return conversion_rate as a Postgres numeric — handle both int and double in fromJson with `(json['conversion_rate'] as num).toDouble()`.

Testing Requirements

Write unit tests in flutter_test covering: (1) ReferralStats.fromJson correctly parses a valid Supabase response map, (2) fromJson handles null/missing optional fields with sensible defaults (0 for counts, 0.0 for rate), (3) ReferralStats value equality — two instances with same field values are equal, (4) DateRangeFilter.validate() throws ArgumentError when endDate < startDate, (5) DateRangeFilter.validate() passes when dates are equal (same-day range), (6) MentorReferralStats groups mentor ID with stats correctly, (7) conversionRate is clamped to [0.0, 1.0] for out-of-range input. Target 100% line coverage for these models — they are pure data classes with no async logic.

Component
Coordinator Recruitment Dashboard
ui 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.