critical priority low complexity database pending database specialist Tier 0

Acceptance Criteria

SummaryPeriodRepository class is implemented with createPeriod, getPeriod, listPeriods, updatePeriod, and deletePeriod methods
createPeriod accepts period metadata (organisation_id, period_type, start_date, end_date, idempotency_key) and persists to Supabase
getPeriod retrieves a single period by ID and returns null (not throws) when not found
listPeriods supports filtering by organisation_id and period_type and returns results ordered by start_date descending
Idempotency key is stored as a unique constraint in the database — a second createPeriod call with the same key returns the existing record rather than inserting a duplicate
updatePeriod supports updating status field (pending, generating, completed, failed) with optimistic concurrency using an updated_at check
All Supabase calls are wrapped in try/catch and surface typed exceptions (SummaryPeriodNotFoundException, SummaryPeriodConflictException)
Repository is injectable via Riverpod Provider and tested in isolation using a Supabase test client or interface abstraction
Period boundary dates are stored as UTC ISO-8601 strings in Supabase and parsed to Dart DateTime with UTC flag preserved
All public methods are covered by integration tests against a Supabase local dev instance

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Dart SDK
apis
Supabase REST API
Supabase Realtime (optional for status updates)
data models
SummaryPeriod
SummaryPeriodStatus
SummaryPeriodType
performance requirements
listPeriods query returns in under 200ms for up to 500 periods per organisation (index on organisation_id + start_date required)
createPeriod with idempotency check completes in a single upsert operation — no separate select + insert
security requirements
Row-Level Security (RLS) policy on summary_periods table must restrict reads and writes to the authenticated user's organisation_id
Idempotency keys must not contain personally identifiable information

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Supabase's upsert with onConflict: 'idempotency_key' and ignoreDuplicates: false so the existing row is returned on conflict — this avoids a read-before-write race condition. Model SummaryPeriodStatus as a Dart enum with a fromString factory to guard against unexpected database values. Define the SummaryPeriod data class as immutable (final fields, copyWith) and derive equality with == and hashCode for use in Riverpod state. Register the repository with a riverpod Provider that takes SupabaseClient as a dependency — never access Supabase.instance.client directly inside the repository class.

The Supabase table should include columns: id (uuid pk), organisation_id (uuid fk), period_type (text), start_date (timestamptz), end_date (timestamptz), status (text), idempotency_key (text unique), created_at, updated_at. This repository underpins the gamification 'Spotify Wrapped' style summaries discussed in the workshop.

Testing Requirements

Integration tests against a Supabase local instance (supabase start) covering: successful create, idempotent create returning existing record, getPeriod for existing and missing ID, listPeriods with organisation filter, updatePeriod status transition, and RLS rejection when accessing another organisation's periods. Unit tests for the repository class using a mock Supabase client (implement the SupabaseClient interface or use a hand-written fake) covering error mapping to typed exceptions.

Component
Summary Period Repository
data low
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.