critical priority high complexity backend pending backend specialist Tier 0

Acceptance Criteria

buildExportQuery(orgIds, periodStart, periodEnd) returns a fully populated ExportDataSet with no null required fields
Joining activities with activity_type_configuration correctly resolves the Bufdir category code for every activity row
org_hierarchy join resolves all levels (chapter, region, national) and includes only activities within the provided orgIds scope
user_roles join attaches the role at time-of-activity rather than current role, preventing retroactive data mutation
contact join includes only non-deleted, non-anonymised contacts and omits PII fields not required by Bufdir schema
Date range filter is inclusive on both periodStart and periodEnd boundaries
Returns empty ExportDataSet (not null or error) when no activities exist for given scope and period
Query executes within 3 seconds for a scope of 1,400 org IDs and a full-year date range on production data volumes
All Supabase PostgREST calls use parameterised filters — no string interpolation in query predicates
ExportDataSet model includes a metadata block: org count, activity count, period covered, query timestamp
Unit test suite covers: empty result, single org, multi-level org list, boundary dates, missing activity_type_configuration mapping
Invalid or empty orgIds list returns ExportDataSet with empty rows and a warning in metadata rather than throwing

Technical Requirements

frameworks
Flutter
Supabase Dart client (supabase_flutter)
Riverpod (for service DI)
apis
Supabase PostgREST REST API
Supabase RPC (for complex joins if PostgREST chaining is insufficient)
data models
activities
activity_type_configuration
org_hierarchy
user_roles
contacts
ExportDataSet (domain model)
performance requirements
Full-year query for up to 1,400 org IDs must complete within 3 seconds
Use Supabase .select() with explicit column lists — never SELECT * — to minimise payload
Prefer server-side Supabase RPC function for the multi-table join to avoid N+1 round trips from client
Paginate internally if result set exceeds 10,000 rows and merge before returning ExportDataSet
security requirements
All query parameters must be passed as bind variables through the Supabase client — no raw SQL string building on the client
Row-Level Security (RLS) policies on the Supabase side must restrict org scope; client-side orgIds list is an additional filter, not the sole guard
PII fields (full name, address, personal ID) must be excluded from ExportDataSet unless explicitly required by Bufdir schema
Service must be invocable only by authenticated sessions with coordinator or admin role claims

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Implement the core join logic as a Supabase RPC (PostgreSQL function) rather than chaining multiple PostgREST calls from Flutter — this keeps network round trips to one and allows the database query planner to optimise the join. The Dart service layer calls `.rpc('build_export_dataset', params: {...})` and deserialises the JSON response into ExportDataSet. Define ExportDataSet as a freezed data class with fromJson/toJson. Keep the RPC function under version control in `supabase/functions/` or a migrations SQL file so schema changes are tracked.

The user_roles join should snapshot the role at `activity.created_at` — query the `user_role_history` table (or audit log) if available, otherwise use current role with a documented assumption. Add a `warnings` list to ExportDataSet metadata to surface unmapped activity types, which BufdirCategoryMapper (task-007) will also populate. This shared warnings channel avoids duplicate alerting logic.

Testing Requirements

Unit tests (flutter_test) for ExportDataQueryBuilder covering: (1) correct SQL/PostgREST filter construction for single and multi-org scopes; (2) inclusive boundary date handling; (3) graceful empty result; (4) missing activity_type_configuration mapping produces warning in metadata not exception; (5) invalid orgIds returns empty dataset. Integration tests against a Supabase local dev instance seeded with known fixture data, asserting row counts and field values. Performance test with a fixture of 1,400 org IDs and 50,000 activities must complete under 3 seconds. No mocking of the Supabase client in integration tests — use the real client against the local instance.

Component
Export Data Query Builder
data high
Epic Risks (3)
high impact medium prob technical

NHF's three-level hierarchy (national / region / chapter) with 1,400 chapters may have edge cases such as chapters belonging to multiple regions, orphaned nodes, or missing parent links in the database. Incorrect scope expansion would silently under- or over-report activities, which could invalidate a Bufdir submission.

Mitigation & Contingency

Mitigation: Obtain a full hierarchy fixture export from NHF before implementation begins. Write exhaustive unit tests covering boundary cases: single chapter, full national roll-up, chapters with no activities, and chapters assigned to multiple regions. Validate resolver output against a known-good manual count.

Contingency: If hierarchy data quality is too poor for automated resolution at launch, implement a manual scope override in the coordinator UI that allows the coordinator to explicitly select org units from a tree picker, bypassing the resolver.

medium impact high prob dependency

The activity_type_configuration table may not cover all activity types currently in use, leaving a subset unmapped at launch. Bufdir submissions with unmapped categories will be incomplete and may be rejected by Bufdir.

Mitigation & Contingency

Mitigation: Run a query against production activity data before implementation to enumerate all distinct activity type IDs. Cross-reference with Bufdir's published category schema (request from Norse Digital Products). Flag every gap as a known issue and build the warning surface into the preview panel.

Contingency: Implement a fallback 'Other' category bucket for unmapped types and surface a prominent warning in the export preview requiring coordinator acknowledgement before proceeding. Log unmapped types for post-launch cleanup.

high impact low prob security

Supabase RLS policies on generated_reports and the storage bucket must enforce strict org isolation. A misconfigured policy could allow a coordinator from one organisation to read another organisation's export files, creating a serious data breach with GDPR implications.

Mitigation & Contingency

Mitigation: Write RLS integration tests that attempt cross-org reads with explicitly different JWT tokens and assert that all attempts return empty sets or 403 errors. Include RLS policy review in the pull request checklist. Use Supabase's built-in policy tester during development.

Contingency: If a policy gap is discovered post-deployment, immediately revoke all signed URLs for affected exports, audit the access log for unauthorised reads, and issue a coordinated disclosure to affected organisations per GDPR breach notification requirements.