high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

When summary generation is triggered for a period that already has a completed record in the summary_periods table, the service returns the cached summary without invoking any aggregation or AI logic
When summary generation is triggered for a period with status 'in_progress', the service detects the in-flight run and returns a conflict/pending response rather than spawning a duplicate execution
When summary generation is triggered for a period with status 'failed', the service allows a fresh retry run and does not skip execution
Idempotency check is scoped per organisation_id + period_type + period_start + period_end composite key
The cached result returned for a completed period is identical in structure to a freshly generated summary response
Idempotency logic is exercised before any Supabase database writes or downstream service calls occur
All idempotency decisions are logged with organisation_id, period key, and outcome (skipped, pending, retry-allowed)
Unit tests cover all four state transitions: no record, completed, in_progress, failed

Technical Requirements

frameworks
Dart
Supabase Edge Functions (Deno/TypeScript)
apis
Supabase PostgREST REST API
summary_periods table read endpoint
data models
SummaryPeriod
SummaryGenerationResult
Organisation
performance requirements
Idempotency check must complete within 100ms (single indexed DB read)
No additional round-trips introduced for the happy-path (already-completed) case beyond the initial check query
security requirements
Idempotency queries must enforce Row Level Security (RLS) scoped to the invoking organisation
Service role key used only server-side; never exposed to client
Organisation isolation: one org's completed record must never short-circuit another org's run

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Model the idempotency check as a guard clause at the very top of the `generateSummary(orgId, periodKey)` function before any business logic executes. Use a Supabase `.select().eq('organisation_id', orgId).eq('period_key', periodKey).single()` query against the `summary_periods` table. Represent period status as a Dart enum (or TypeScript string union in the Edge Function): `pending | in_progress | completed | failed`. Use an optimistic lock pattern: set status to `in_progress` via an upsert with a `match` on `status = null OR status = failed` to prevent race conditions when two scheduler instances fire simultaneously.

Return early with the cached `summary_data` JSONB column value when status is `completed`. Ensure the period key is a deterministic string (e.g. `2025-H1` or `2025-Q3`) constructed identically in both the scheduler and the check logic to prevent key mismatches.

Testing Requirements

Unit tests (flutter_test / Deno test): test all four period status branches (null, completed, in_progress, failed). Integration test: trigger the Edge Function twice for the same organisation+period and assert the second call returns a cached result with no new DB writes. Assert the summary_periods table has exactly one record after two identical trigger calls. Test concurrent invocations using parallel async calls to verify in_progress detection prevents duplicate rows.

Verify that a failed period record is overwritten correctly on retry.

Component
Summary Generation Service
service high
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.