high priority high complexity infrastructure pending infrastructure specialist Tier 6

Acceptance Criteria

A Supabase edge function named aggregate_subtree_participants is deployed and callable via supabase.rpc('aggregate_subtree_participants', {unit_id, start_date, end_date})
The function executes a recursive CTE that traverses all descendant nodes of the given unit_id without loading individual rows into the application tier
Participant deduplication is performed in SQL (DISTINCT ON member_id or equivalent) so that members active in multiple sub-units are counted once
The returned JSON payload matches the Bufdir breakdown API contract: { unit_id, period: { start, end }, total_participants, breakdown_by_subunit: [{unit_id, unit_name, count}] }
The function accepts ISO 8601 date strings for start_date and end_date and validates them server-side, returning a 400 error with message 'Invalid date range' on malformed input
Response time is under 800ms for a subtree of up to 200 descendant nodes with up to 10,000 activity records in the time window
Row-Level Security (RLS) is enforced: callers can only aggregate units they have read access to; unauthorized requests return 403
The function is covered by at least 3 Supabase integration tests (via supabase-js test client or pg_tap) covering: valid aggregation, empty subtree, and invalid date range
Deployment is documented with a supabase/functions/aggregate_subtree_participants/README.md describing parameters and response shape

Technical Requirements

frameworks
Supabase Edge Functions (Deno runtime)
PostgreSQL recursive CTE
supabase-js (for integration testing)
apis
Supabase RPC API
Bufdir breakdown API contract (output schema)
data models
OrganizationUnit
ActivityRecord
Participant
HierarchyNode
performance requirements
Recursive CTE must complete in under 500ms for trees up to 200 nodes on Supabase free/pro tier
Response payload must be under 50KB for standard Bufdir reporting windows
Use PostgreSQL indexes on parent_id and activity date columns to support CTE traversal performance
security requirements
RLS policies must be active on all tables accessed by the edge function
Edge function must validate JWT from the Authorization header before executing queries
No raw SQL string interpolation — use parameterized queries or Supabase RPC parameter binding exclusively
Sensitive unit membership data must not be returned in error messages

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

The recursive CTE pattern for adjacency-list trees in PostgreSQL is: WITH RECURSIVE subtree AS (SELECT id FROM organization_units WHERE id = $unit_id UNION ALL SELECT u.id FROM organization_units u JOIN subtree s ON u.parent_id = s.id) SELECT ... FROM activities WHERE unit_id IN (SELECT id FROM subtree). Deduplication: wrap in SELECT DISTINCT ON (member_id) to count unique participants. For NHF's 1,400-chapter tree, depth can exceed 4 levels — ensure the CTE has no artificial depth cap.

Deploy via supabase functions deploy aggregate_subtree_participants. The Deno runtime has no pg driver; use the Supabase client initialized with the service role key from environment variables (never hardcode). Return Content-Type: application/json in all cases. Cache the aggregated result in the database (a materialized view or a cache table refreshed on activity write) if response times exceed the 800ms SLA in load testing.

Testing Requirements

Write integration tests using the Supabase local development stack (supabase start). Test scenarios: (1) valid unit_id with a 3-level subtree returns correct total and per-subunit breakdown; (2) unit_id with no descendants returns { total_participants: 0, breakdown_by_subunit: [] }; (3) invalid date string returns HTTP 400 with correct error message; (4) calling with a unit_id the authenticated user does not own returns HTTP 403. Use seed SQL fixtures to populate test hierarchy data. Run tests in CI using the Supabase CLI (supabase db reset && supabase test db).

Also write a manual smoke test script that calls the deployed staging function end-to-end and validates response shape against the Bufdir contract JSON schema.

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

Recursive aggregation queries across four hierarchy levels (national → region → local) with 1,400 leaf nodes may be too slow for real-time dashboard requests, exceeding the 200ms target and causing spinner timeouts.

Mitigation & Contingency

Mitigation: Implement aggregation as a Supabase RPC using a single recursive CTE rather than multiple round-trip queries. Pre-compute aggregations nightly via a scheduled Edge Function and cache results. For real-time needs, aggregate only the immediate subtree on demand.

Contingency: Surface a 'Refreshing...' indicator and serve stale cached aggregations immediately. Queue an async recalculation and push updated data via Supabase Realtime when ready, avoiding blocking the admin dashboard.

medium impact medium prob scope

The 5-chapter limit and primary-assignment constraint are NHF-specific. Applying these rules globally may break HLF and Blindeforbundet configurations where different limits apply, requiring per-organization configuration that was not initially scoped.

Mitigation & Contingency

Mitigation: Make the maximum assignment count a configurable value stored in the organization's feature-flag or settings table rather than a hardcoded constant. Design the assignment service to read this limit at runtime per organization.

Contingency: Default the limit to a high value (e.g., 100) for organizations other than NHF, effectively making it non-restrictive, while keeping the enforcement logic intact for when per-org configuration is fully implemented.

medium impact low prob technical

The searchable parent dropdown in HierarchyNodeEditor must search across up to 1,400 units efficiently. Client-side filtering of the full hierarchy may be slow; server-side search adds complexity and latency.

Mitigation & Contingency

Mitigation: Use the in-memory hierarchy cache as the search corpus — since the cache already holds the flat unit list, client-side filtering with a debounced input is sufficient and avoids extra Supabase calls. Pre-build a search index on cache load.

Contingency: Cap the dropdown to showing the 50 most recently accessed units by default, with a 'search all' option that triggers a server-side full-text query. This keeps the common case fast while supporting edge cases.