critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

`GeneratedReportsRepository` class exists in `lib/data/repositories/generated_reports_repository.dart` and is injectable via Riverpod provider
`createAuditRecord(ExportRequest request)` inserts a row with status=pending and returns the new `GeneratedReport` model including the server-generated `report_id`
`updateStatus(String reportId, ReportStatus status, {String? errorMessage, String? storagePath, int? fileSizeBytes})` updates only the specified fields on the matching record and returns the updated model
`getReportsByOrg(String orgId, {DateTimeRange? dateRange, int limit = 50, int offset = 0})` returns a paginated list of `GeneratedReport` models filtered to the given org, ordered by `created_at DESC`
`getReportById(String reportId)` returns a single `GeneratedReport` or null if not found
`deleteOldReports(int retentionDays)` deletes records older than the retention period where status is `completed` or `failed` — does NOT delete `pending` records
A `GeneratedReport` Dart model class exists with all fields typed correctly (UUID as String, dates as DateTime, status as `ReportStatus` enum, nullable fields as nullable types)
A `ReportStatus` enum exists with values `pending`, `completed`, `failed`
All methods throw typed `RepositoryException` on Supabase errors rather than leaking raw `PostgrestException`
Repository is covered by unit tests with a mocked Supabase client achieving >90% method coverage

Technical Requirements

frameworks
Flutter (Dart)
Riverpod (for repository provider registration)
supabase_flutter package (Supabase Dart client)
flutter_test (for unit tests)
apis
Supabase PostgREST REST API via supabase_flutter client (generated_reports table)
data models
GeneratedReport Dart model
ExportRequest Dart model (input to createAuditRecord)
ReportStatus enum
generated_reports Supabase table
performance requirements
getReportsByOrg must use server-side pagination (limit/offset) — never fetch all records client-side
All repository methods must complete within 3 seconds under normal network conditions
security requirements
Repository must use the authenticated user's Supabase client — never the service role key from Flutter code
RLS on the database table enforces org scoping — the repository must not add client-side org filtering as a substitute
report_id must always be server-generated (UUID from database) — never client-generated

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Follow the existing repository pattern in the project (check other repository classes for naming conventions and Supabase client usage). Use `supabase.from('generated_reports').insert(data).select().single()` for createAuditRecord to get the server-generated UUID back in one round trip. For updateStatus, use `.update(data).eq('report_id', reportId).select().single()` — do not do a separate fetch after update. The `deleteOldReports` method is only called by a background maintenance process — it should use the service role client (passed as a parameter or injected via a separate provider) not the user-auth client, because RLS blocks DELETE for user roles.

Model fromJson/toJson should handle ISO 8601 date strings from Supabase, converting to Dart `DateTime` with `DateTime.parse()`. Register as a Riverpod `Provider` that depends on the `supabaseClientProvider`.

Testing Requirements

Unit tests using flutter_test with a mocked SupabaseClient. Test each method: (1) createAuditRecord — verify correct INSERT payload is sent and returned model is correctly deserialized, (2) updateStatus — verify only specified fields are included in the UPDATE, (3) getReportsByOrg — verify query includes correct org_id filter, ordering, and pagination params, (4) getReportById — verify query filters by report_id and handles null return, (5) deleteOldReports — verify DELETE filters by date threshold and status IN ('completed', 'failed'). Test error paths: simulate PostgrestException and assert RepositoryException is thrown with a meaningful message. Place tests in `test/data/repositories/generated_reports_repository_test.dart`.

Component
Generated Reports Repository
data low
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.