critical priority medium complexity infrastructure pending infrastructure specialist Tier 4

Acceptance Criteria

The Edge Function is registered as a Supabase scheduled function triggered via pg_cron on the last day of each quarter (March 31, June 30, September 30, December 31) at 23:00 UTC
The function correctly identifies half-year periods (H1: Jan–Jun, H2: Jul–Dec) and quarterly periods (Q1–Q4) based on the trigger date
The function queries all active organisations from the database and invokes the summary generation service for each one
Fan-out execution is performed with controlled concurrency (max 5 parallel org invocations) to avoid overwhelming downstream services
Each scheduler run is recorded in a `scheduler_runs` table with: run_id, trigger_time, period_type, period_key, organisations_targeted, organisations_succeeded, organisations_failed, duration_ms, status
Individual organisation failures do not abort the scheduler run — the function continues processing remaining organisations and records the failure count
The function is idempotency-safe: if triggered multiple times for the same date, duplicate summary generation is prevented by the idempotency layer (task-009)
A manual trigger endpoint (HTTP POST with `{ organisation_id, period_key }` body) allows ops teams to re-run generation for a specific org without waiting for the cron schedule
The function returns a JSON response summarising the run outcome even when some orgs fail

Technical Requirements

frameworks
Supabase Edge Functions (Deno/TypeScript)
Supabase pg_cron extension
apis
Supabase PostgREST REST API (organisations query)
Internal summary generation service invocation (HTTP or direct function call)
scheduler_runs table write endpoint
data models
Organisation
SchedulerRun
SummaryPeriod
performance requirements
Full scheduler run for up to 50 organisations must complete within the Supabase Edge Function timeout (150 seconds)
Concurrency limit of 5 parallel org invocations to balance throughput and resource usage
Period boundary detection must be pure computation with zero DB reads
security requirements
Edge Function must authenticate via Supabase service role key stored as a secret (never hardcoded)
Manual trigger endpoint must validate a Bearer token against an ops-only Supabase role
Scheduler logs must not contain personally identifiable information (PII)

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement period boundary detection as a pure function `detectPeriods(triggerDate: Date): PeriodDescriptor[]` — on June 30 it returns both `{ type: 'quarterly', key: '2025-Q2' }` and `{ type: 'half_year', key: '2025-H1' }`. Use `Promise.allSettled` with a batching utility to enforce the concurrency limit during org fan-out (avoid `Promise.all` which has no concurrency cap). Store the service role key in Supabase vault secrets and inject via `Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')`. Structure the function as two entry points: `handleCron()` for scheduled invocation and `handleManualTrigger(req)` for HTTP POST, both calling a shared `runScheduler(orgIds, periods)` core.

Log duration using `performance.now()` before and after the fan-out. Register the pg_cron job via a Supabase migration file to keep it version-controlled: `SELECT cron.schedule('summary-scheduler', '0 23 31 3,6,9,12 *', $$SELECT net.http_post(...)$$)`.

Testing Requirements

Unit tests: test period boundary detection for all 4 quarter-end dates and verify correct period_type and period_key strings are produced. Test that June 30 produces both H1 and Q2 records. Test fan-out logic with a mock list of 10 organisations and verify concurrency cap. Integration test (Supabase local dev): deploy the function, trigger it manually via HTTP POST, assert scheduler_runs record is created, assert summary generation service is invoked per org.

Test failure isolation: mock one org's generation to throw an error and assert other orgs still complete and the run record reflects partial success. Test duplicate trigger on the same date returns a no-op with the existing scheduler_run record.

Component
Summary Generation Scheduler
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.