critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Service accepts an `OutlierDetectionConfig` object containing `orgId`, `period`, `minActivityThreshold` (int), and `maxActivityThreshold` (int)
Service returns an `OutlierReport` containing two lists: `underactive` (peers below min threshold) and `overloaded` (peers above max threshold), each entry including `userId`, `displayName`, `activityCount`, and `outlierType`
Peers with activity count exactly equal to a threshold boundary are NOT classified as outliers (strictly less than / strictly greater than)
Thresholds are configurable per organisation — service reads them from an `organisation_settings` table in Supabase, not hardcoded
Service calls OrgIsolationGuard before any data access
Service delegates raw data retrieval entirely to the ActivityAggregationRepository — it contains no direct Supabase query logic
When thresholds are not configured for an organisation, service returns an empty report and logs a warning — it does not crash
OutlierReport is persisted to Supabase (`outlier_reports` table) after generation for coordinator review
Coordinator-facing outlier report includes a human-readable label for the period (e.g., 'H1 2025') not just date boundaries
Service run for an organisation with 500 peer mentors completes within 5 seconds

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
Riverpod
apis
Supabase REST API (organisation_settings, outlier_reports tables)
data models
PeerMentor
Activity
ActivityAggregateResult
OutlierReport
OutlierEntry
OrganisationSettings
performance requirements
Full outlier evaluation for 500 peer mentors completes within 5 seconds
Threshold configuration is fetched once per service run, not per peer mentor evaluation
security requirements
OrgIsolationGuard must be called at service entry — only peer mentors of the specified org are evaluated
Outlier reports stored in Supabase must be RLS-protected so only coordinators and admins of the same org can read them
Do not expose activity details of individual peer mentors beyond count and duration in the outlier report

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Model the detection algorithm as a pure function `List classify(List peers, OutlierDetectionConfig config)` — this separates the algorithm from I/O concerns and makes it trivially testable. The service orchestrates: (1) fetch thresholds from Supabase, (2) fetch per-peer activity aggregates from ActivityAggregationRepository, (3) call the pure classify function, (4) persist the OutlierReport. Per-peer aggregates should be fetched in a single batch query (all peer mentors of the org for the period) — avoid calling the repository once per peer mentor. Use Riverpod `Provider` for service instantiation with the ActivityAggregationRepository and OrgIsolationGuard injected.

Store persisted outlier reports as JSONB in Supabase for flexibility, with a typed Dart model for deserialization. The `OutlierDetectionConfig` thresholds represent activity event counts, consistent with how HLF tracks 380 individual registrations per year.

Testing Requirements

Unit tests with flutter_test using mocked ActivityAggregationRepository and mocked Supabase client: test underactive detection (activity count < min); test overloaded detection (activity count > max); test boundary values (count == min and count == max are not outliers); test that empty organisation (zero peer mentors) returns empty report; test missing threshold config returns empty report with warning. Integration tests: seed a Supabase test project with 20 peer mentors at varying activity levels and verify outlier classification is correct. Minimum 95% branch coverage on detection logic.

Component
Outlier Detection Service
service medium
Epic Risks (4)
high impact medium prob technical

Supabase pg_cron or Edge Function retries could trigger multiple concurrent generation runs for the same period and organisation, producing duplicate summaries and sending multiple push notifications to users — a serious UX regression.

Mitigation & Contingency

Mitigation: Implement a database-level run-lock using an INSERT … ON CONFLICT DO NOTHING pattern keyed on (organisation_id, period_type, period_start). Only the first successful insert proceeds; subsequent attempts read the existing lock and exit early. Test with concurrent invocations in a Deno test suite.

Contingency: If duplicate summaries are detected post-deployment, add a deduplication cleanup job that removes all but the most recent summary per (user_id, period_type, period_start) and sends a corrective push notification.

medium impact low prob integration

FCM and APNs have different payload structures and size limits. An oversized or malformed payload could cause silent notification drops on iOS or delivery failures on Android, meaning mentors never learn their summary is ready.

Mitigation & Contingency

Mitigation: Build the PushNotificationDispatcher with separate FCM and APNs payload constructors, enforce a 256-byte body limit on the preview text, and run integration tests against the Firebase Emulator and a test APNs sandbox.

Contingency: Fall back to a generic 'Your periodic summary is ready' message if personalised preview text construction fails, ensuring delivery even when the personalisation pipeline encounters an error.

medium impact high prob scope

Outlier thresholds that are too tight will flag most mentors as outliers (alert fatigue for coordinators), while thresholds that are too loose will miss genuinely underactive mentors — directly undermining HLF's follow-up goal.

Mitigation & Contingency

Mitigation: Implement thresholds as configurable per-organisation database settings rather than hardcoded constants. Provide sensible defaults (underactive < 2 sessions/period, overloaded > 20 sessions/period) and document the tuning process for coordinators in the admin portal.

Contingency: If coordinators report threshold miscalibration after launch, expose a threshold configuration UI in the coordinator admin screen and allow real-time threshold adjustment without requiring a code deployment.

low impact high prob scope

The app may not have 12 months of historical activity data for all organisations at launch, making year-over-year comparison impossible for most users and rendering the comparison widget empty, which could disappoint users expecting Wrapped-style insights.

Mitigation & Contingency

Mitigation: Design the generation service to gracefully handle missing prior-year data by setting the yoy_delta field to null rather than zero. The UI must treat null as 'no comparison available' with appropriate placeholder copy rather than showing a misleading 0% delta.

Contingency: If historical data import from legacy Excel/Word sources becomes feasible, add a one-time backfill Edge Function that populates prior-year activity records from imported spreadsheets. Until then, explicitly communicate the data-availability limitation in the first summary each user receives.