critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Repository exposes a method `getAggregatedActivities({orgId, periodStart, periodEnd})` returning total activity count, total duration, and breakdown by activity type
Half-year window queries (H1: Jan–Jun, H2: Jul–Dec) return correct results when validated against raw activity records in Supabase
Quarterly window queries (Q1–Q4) return correct results when validated against raw activity records
Year-over-year comparison method returns current period metrics alongside same-period metrics from the previous year
All queries include an `organisation_id` filter; queries without this filter are rejected with an assertion error
Repository returns an empty-safe result object (zero values) when no activities exist for a given period — never null or throws
Supabase RLS policies are respected; repository does not bypass row-level security
Repository is fully unit-testable via Supabase mock client injection
Aggregation logic handles timezone-aware date boundaries using UTC internally with display conversion at the presentation layer
Performance: a full half-year aggregation query for an organisation with 10,000 activity records completes in under 500ms

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
Riverpod
apis
Supabase PostgREST REST API
Supabase RPC (for aggregation stored procedures if needed)
data models
Activity
Organisation
PeerMentor
SummaryPeriod
performance requirements
Aggregation queries must complete within 500ms for organisations with up to 10,000 records
Use indexed columns (organisation_id, created_at) in all WHERE clauses
Avoid N+1 queries; use a single aggregation query per period window
security requirements
All queries must include organisation_id scoping — never query across organisations
Rely on Supabase RLS as the enforcement layer; do not bypass via service role key in client code
Do not log raw activity content — only aggregate counts and durations

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Model the repository as a pure Dart class with a SupabaseClient injected via constructor for testability. Use Riverpod `Provider` to expose it — do not use a singleton. Period boundaries should be passed as UTC `DateTime` objects computed by the PeriodCalculatorService (task-005). For aggregations, prefer Supabase RPC stored functions (Postgres functions) over client-side aggregation to keep data transfer minimal and push computation to the database tier.

Define a `ActivityAggregateResult` value object with `totalCount`, `totalDurationMinutes`, `breakdownByType` (Map), and `periodStart`/`periodEnd` fields. Year-over-year comparison should return a `YoYComparisonResult` wrapping two `ActivityAggregateResult` instances. Avoid DateTime.now() inside the repository — all time inputs must come from callers to keep the class deterministic and testable.

Testing Requirements

Unit tests using flutter_test with a mock Supabase client: verify correct SQL/PostgREST filter construction for H1, H2, Q1–Q4 windows; verify year-over-year date arithmetic; verify empty-period handling returns zero-value structs. Integration tests against a Supabase test project: run aggregation queries over seeded datasets and assert totals match expected values. Edge cases: activities spanning period boundaries (use created_at, not end_at), organisations with zero activities, single-activity organisations. Minimum 90% branch coverage on the repository class.

Component
Activity Aggregation Repository
data 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.