critical priority high complexity database pending database specialist Tier 1

Acceptance Criteria

A Supabase Edge Function (or Dart service class backed by Supabase RPC) exposes an `aggregateActivities({required String orgId, required DateTime periodStart, required DateTime periodEnd, required ScopeLevel scope})` method that returns a `BufdirPayload` for chapter scope
The query filters exclusively on `org_id = orgId` and `activity_date BETWEEN periodStart AND periodEnd` — no cross-org data is returned
Results are grouped by `peer_mentor_id`; each group contains the sum of `session_count` (or equivalent field) and the sum of `duration_minutes` across all activity records in the group
Each peer mentor group includes a breakdown by `activity_type_id` with per-type session count and total minutes
The returned `BufdirPayload` is a valid instance of the model defined in task-001 with all required fields populated
When no activity records exist for the given org and period, the function returns a `BufdirPayload` with an empty `peer_mentor_records` list rather than throwing an error
Row-Level Security (RLS) on the activities table is respected — the query runs under the caller's auth context and cannot access records for organisations the caller is not authorised to view
Query execution time for an org with up to 500 activity records in the period is under 2 seconds (measured in the Supabase dashboard query analyser or equivalent)
A database index exists (or is confirmed to already exist) on `(org_id, activity_date)` to support the query efficiently; if it does not exist, it is created in a migration file
The implementation is covered by unit tests (Dart) using a mocked Supabase client that verifies the correct query parameters and correct mapping of the response rows to `BufdirPayload`

Technical Requirements

frameworks
Dart (latest null-safe)
supabase_flutter (Dart Supabase client)
Supabase Edge Functions (Deno/TypeScript) if aggregation logic runs server-side
flutter_test + mocktail/mockito for unit tests
apis
Supabase PostgREST API (activities table, peer_mentors table, activity_types table)
Supabase RPC (if a server-side SQL function is used for the GROUP BY aggregation)
data models
BufdirPayload
BufdirPeerMentorRecord
BufdirActivityTypeBreakdown
Activity (existing Supabase table model)
PeerMentor (existing Supabase table model)
ActivityType (existing Supabase table model)
performance requirements
Query must complete in under 2 seconds for orgs with up to 500 activity records in the period
Use a single aggregating SQL query or RPC rather than N+1 per peer mentor
Composite index on (org_id, activity_date) required
security requirements
Query must run under authenticated Supabase session — no service_role key usage in client-side code
RLS policies on the activities table must be validated to prevent cross-org data access
org_id parameter must be validated against the caller's JWT claims before executing the query to prevent IDOR

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Prefer a Supabase RPC (SQL function) for the GROUP BY aggregation rather than fetching raw rows and aggregating in Dart — this avoids transferring all raw records over the network and is significantly more efficient for large orgs. Define the SQL function in a Supabase migration file so it is version-controlled. The Dart service class should call `.rpc('aggregate_activities_for_org', params: {...})` and map the returned JSON to `BufdirPayload`. If the project does not yet use RPC functions, document this pattern decision.

Validate the `orgId` against the authenticated user's `organisation_id` JWT claim at the start of the method and throw an `UnauthorisedAccessException` if they do not match — this is the application-level guard complementing RLS. Ensure the `period_start` and `period_end` are passed as ISO 8601 strings to Supabase to avoid timezone ambiguity; document the expected timezone convention (UTC) in a comment.

Testing Requirements

Unit tests (Dart, flutter_test): mock the Supabase client to return a scripted list of activity rows and assert that the aggregation logic correctly sums sessions and minutes per peer mentor and per activity type. Test edge cases: single peer mentor with one activity, multiple peer mentors, activity records with null duration (should be treated as 0 or excluded with a warning), empty result set. Integration tests (optional but recommended): use Supabase's local development environment (`supabase start`) to run the actual query against a seeded test database and assert the returned payload matches expected totals. Performance test: seed the test database with 500 rows for a single org and confirm the query returns in under 2 seconds.

Component
Activity Aggregation Service
service high
Epic Risks (3)
high impact medium prob technical

Supabase Edge Functions have a default execution timeout. For large national-scope exports aggregating tens of thousands of activities across 1,400 chapters, the edge function may time out before completing, leaving coordinators with a failed export and no partial output.

Mitigation & Contingency

Mitigation: Optimise the aggregation SQL using pre-materialised aggregation views or RPC functions that run inside the database rather than iterating records in Deno. Profile query execution time against realistic production data volumes early. Request an elevated timeout limit from Supabase if needed. Implement progress checkpointing so the export can be resumed from the last completed aggregation batch.

Contingency: For organisations exceeding a configurable threshold (e.g. >5,000 activities), switch to an asynchronous export pattern: the edge function writes a 'pending' audit record and enqueues the job; the client polls for completion and is notified via Supabase Realtime when the file is ready.

medium impact medium prob technical

Server-side PDF generation in a Deno Edge Function environment restricts library choices. Many popular PDF libraries require Node.js APIs not available in Deno, or produce large bundle sizes that exceed edge function limits. Choosing the wrong library could block the entire PDF generation path.

Mitigation & Contingency

Mitigation: Spike PDF library selection as the first task of this epic, evaluating at least two Deno-compatible options (e.g. pdf-lib, jsPDF with Deno compatibility shim). Test bundle size and basic rendering before committing to an implementation. Document the chosen library's constraints.

Contingency: If no suitable Deno-native PDF library is found, generate a well-structured HTML report from the edge function and use a headless Chromium service (e.g. Browserless, Gotenberg) for HTML-to-PDF conversion, or temporarily ship CSV-only export while the PDF path is resolved.

high impact high prob technical

Peer mentors affiliated with multiple chapters (a documented NHF scenario) must not be double-counted in participant totals. Incorrect deduplication logic would overreport participation figures to Bufdir, which could be discovered during audit and damage organisational credibility.

Mitigation & Contingency

Mitigation: Define and document the deduplication contract explicitly before coding: deduplication is per-person per-period, not per-activity. Build dedicated unit tests with fixtures containing the exact multi-chapter membership patterns described in NHF's documentation. Have a NHF representative validate test fixture outputs against known-good manual counts.

Contingency: If deduplication logic produces results that cannot be verified against manual counts before launch, surface a deduplication warning in the export preview listing the affected peer mentor IDs, and require explicit coordinator acknowledgement before finalising the export.