critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

BufdirReportData and BufdirAggregatedField Dart classes are defined with fromJson() factory constructors that map all Supabase column names to typed fields
At least one Supabase RPC function or database view is defined and named to return aggregated activity data for a given (period_id, org_id) pair
The query layer correctly filters by both period_id and org_id parameters — no cross-organisation data leakage is possible at the query level
Row-level security policies are documented (or implemented if within scope) so that the Supabase JWT's organisation claim gates access even if query parameters are manipulated
fetchAggregatedData(String periodId, String orgId) returns a Future<BufdirReportData> and throws a typed BufdirRepositoryException on Supabase errors
fetchPriorPeriodData(String periodId, String orgId) returns Future<BufdirReportData?> — nullable to handle cases where no prior period exists
All Supabase column-to-Dart field mappings are covered by a fromJson round-trip unit test using sample JSON fixtures
The query layer compiles without errors and passes flutter test
A code comment explains which Bufdir reporting fields map to which Supabase columns/aggregation functions
No Supabase client is instantiated inside repository methods — the client is injected via constructor

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase Dart SDK (supabase_flutter)
Supabase RPC (.rpc())
Supabase table query builder (.from().select())
Supabase Row Level Security policies
data models
BufdirReportData
BufdirAggregatedField
BufdirRepositoryException
performance requirements
Supabase RPC or view query must return aggregated results server-side — do not fetch raw activity rows and aggregate in Dart
Query must return in under 2 seconds for organisations with up to 10,000 activity records in the period
security requirements
RLS policies must restrict access so a coordinator can only query their own organisation's data even with a crafted orgId parameter
Supabase service role key must never be used in the Flutter client — only anon/user JWT tokens
All Supabase errors must be caught and re-thrown as BufdirRepositoryException — never expose raw Supabase error messages to the UI

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Prefer a Supabase database view or RPC function over complex client-side query builders — this keeps aggregation logic in SQL where it is testable independently and benefits from database query optimisation. Name the RPC function bufdir_get_aggregated_report(p_period_id text, p_org_id uuid) following Supabase naming conventions. BufdirReportData should be an immutable class (use @immutable annotation and final fields). BufdirAggregatedField should carry: fieldId (String), fieldLabel (String), value (num), unit (String?), and isRequired (bool).

The distinction between required and optional Bufdir fields is important for downstream validation. Define BufdirRepositoryException with a code (String) and message (String) to allow UI layers to show specific error messages without depending on Supabase internals.

Testing Requirements

Write unit tests for BufdirReportData.fromJson() and BufdirAggregatedField.fromJson() covering: valid full response, missing optional fields defaulting correctly, null values for nullable fields, and unknown keys being ignored. Use flutter_test with hardcoded JSON fixture maps — no live Supabase connection needed. Also write a contract test documenting the expected Supabase RPC/view response shape so schema changes are caught early. Aim for 100% coverage on fromJson() factories.

Component
Bufdir Preview Repository
data medium
Epic Risks (2)
high impact medium prob integration

The preview repository depends on aggregated data produced by the Bufdir Data Aggregation feature. If the aggregation RPC schema or Supabase view columns change during parallel development, the repository's typed Dart models will break, causing compile errors or runtime null-dereference failures.

Mitigation & Contingency

Mitigation: Define a shared Dart interface (abstract class) for the aggregated data contract early and have both features code against it. Use Supabase typed generated clients so schema mismatches surface at code generation time rather than runtime.

Contingency: If the aggregation schema changes after the repository is complete, run `supabase gen types dart` immediately, update the repository model, and run repository unit tests before unblocking UI development. Keep a mock data fixture so UI work can continue during the fix.

high impact medium prob scope

The BufdirReportStructureMapper must map internal activity category IDs to the exact label strings used on the official Bufdir reporting form. If the mapping is incomplete or uses outdated labels, coordinators will see mismatches when cross-referencing the preview with the paper form, potentially leading to incorrect submissions.

Mitigation & Contingency

Mitigation: Obtain the current Bufdir reporting form PDF directly from Bufdir (Norse Digital Products has an existing Bufdir dialogue). Extract all field labels and section names into a static constants file reviewed by at least one coordinator from NHF or HLF before implementation begins.

Contingency: If incorrect labels are discovered during UAT on TestFlight, update the static constants file and redeploy. Because the mapper is a pure Dart class with no database storage, corrections require no migration — only a new build.