high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

The notification payload builder produces a well-formed FCM/APNs-compatible push notification payload for each peer mentor who has a completed summary
Notification title is personalised with the peer mentor's first name (e.g. 'Your H1 2025 summary is ready, Anna!')
Notification body includes at least two key metric highlights extracted from the summary (e.g. total activities count and whether the mentor is above/below average)
The period label in the notification matches the canonical format used in the UI (e.g. 'H1 2025', 'Q3 2025')
The notification data payload contains a `deep_link` field with a URI that routes to the specific summary card screen in the Flutter app (e.g. `app://summary/2025-H1`)
Deep-link URI scheme is validated: must include period_key and organisation_id as parameters
Notification is only dispatched if the peer mentor has push notifications enabled in their profile settings
If a peer mentor has no FCM/APNs token on record, the builder logs a warning and skips that mentor without failing the batch
Notification payloads respect the organisation's custom terminology (e.g. 'likeperson' vs 'peer supporter') from the organisation labels system
All notification text is in the peer mentor's preferred language (Norwegian Bokmål default, with internationalisation hook for future languages)

Technical Requirements

frameworks
Dart
Flutter (for deep-link URI scheme definition)
Supabase Edge Functions (Deno/TypeScript)
apis
FCM (Firebase Cloud Messaging) HTTP v1 API
APNs (Apple Push Notification service) HTTP/2 API
peer_mentors table (name, push token, notification preferences)
organisation_labels table (custom terminology)
summary_periods table (completed summary data)
data models
PeerMentor
SummaryPeriod
Organisation
NotificationPayload
OrganisationLabel
performance requirements
Payload construction must complete in under 10ms per mentor (pure computation, no additional DB reads beyond the initial mentor+summary batch load)
FCM dispatch batch size must not exceed 500 messages per HTTP request
security requirements
FCM server key and APNs private key must be stored in Supabase vault secrets
Push tokens must never be logged in plaintext
Notification payload must not include sensitive personal data (medical, financial) — only the summary metrics

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the payload builder as a pure function `buildSummaryNotificationPayload(mentor: PeerMentor, summary: SummaryData, orgLabels: OrganisationLabels): NotificationPayload | null` — pure functions are easy to unit test. Define the deep-link URI scheme in the Flutter app's `AndroidManifest.xml` and `Info.plist` using the `app_links` or `go_router` deep-link configuration — the scheme `app://summary/{period_key}?org={org_id}` is a good starting point. Extract the two most impactful metric highlights using a priority list: (1) total activity count vs period average, (2) outlier classification if present, (3) year-over-year delta. Keep notification bodies under 100 characters for APNs visibility on lock screen.

The organisation labels system already exists in the app architecture — query it once per scheduler run and pass the resolved labels map into the payload builder to avoid per-mentor DB reads. For Norwegian: use `l10n` ARB files with `nb` locale as the default.

Testing Requirements

Unit tests: test payload construction with a mock peer mentor and summary object — verify title personalisation, metric highlight extraction, deep-link URI format, and language string resolution. Test mentor with notifications disabled produces no payload. Test mentor with missing push token logs warning and returns null. Test organisation label substitution replaces 'peer mentor' with org-specific term.

Integration test: send a test notification to a TestFlight device and verify it appears with correct title, body, and that tapping it deep-links to the correct summary card screen in the Flutter app. Test Norwegian Bokmål strings for correctness with a native speaker review checkpoint.

Component
Push Notification Dispatcher
infrastructure 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.