critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

createPeriod(PeriodType type, DateTime start, DateTime end, {String? label}) persists a new ReportingPeriod to Supabase table reporting_periods and returns the created entity
createPeriod() rejects periods that overlap with any existing non-custom period of the same type; overlap check runs inside a Supabase transaction to prevent race conditions
createPeriod() for PeriodType.custom allows overlapping dates but requires a non-empty label
getActivePeriod() returns the ReportingPeriod whose interval contains DateTime.now().toUtc(); if multiple match (custom overlap), returns the most recently created one
getActivePeriod() returns null (not an exception) when no active period exists
listAvailablePeriods({PeriodType? filterType, int limit = 50, int offset = 0}) returns a paginated list ordered by startDate descending
All methods throw a typed ReportingPeriodException (not a generic Exception) with a machine-readable code (overlap_detected, invalid_boundary, not_found) and a human-readable message in English
Service is stateless between calls — no in-memory cache that could return stale data across Supabase updates
A Supabase Row Level Security (RLS) policy ensures only organization admins can call createPeriod(); the service layer validates the caller's role before issuing the insert

Technical Requirements

frameworks
supabase_flutter
riverpod (for service provider)
flutter_test + supabase mock client
apis
Supabase PostgREST API (reporting_periods table)
Supabase Auth (for role validation)
data models
ReportingPeriod
PeriodType
ReportingPeriodException
performance requirements
listAvailablePeriods() must return within 500ms for up to 500 stored periods
createPeriod() overlap check must use a database-side query (not load all periods into memory)
getActivePeriod() must use an indexed date-range query on reporting_periods
security requirements
RLS policy on reporting_periods table restricts INSERT to users with org_admin role claim in JWT
Service validates JWT role claim before issuing write operations (defense in depth)
Period IDs must not be sequentially guessable — use UUID v4 from Supabase gen_random_uuid()

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement as a Riverpod Provider (final reportingPeriodServiceProvider = Provider((ref) => ReportingPeriodService(ref.watch(supabaseClientProvider)))). Keep the service thin: it should translate between domain objects (ReportingPeriod) and Supabase row maps, delegate all business rules to the domain model (task-001), and handle database errors by wrapping them in typed ReportingPeriodException. For the overlap check, use a Supabase RPC (PostgreSQL function) rather than a client-side query to avoid time-of-check/time-of-use races: create a check_period_overlap(start, end, type) SQL function that returns a boolean and call it via supabase.rpc(). Bufdir reporting cycles for Norwegian grants: always align to calendar boundaries (task-001 named constructors), never accept a quarterly period that spans a year boundary.

Testing Requirements

Unit tests with a mocked Supabase client (MockSupabaseClient) and integration tests against a local Supabase emulator. Unit tests cover: createPeriod() success, createPeriod() with overlapping period (expect ReportingPeriodException with code overlap_detected), createPeriod() custom with empty label (expect validation error), getActivePeriod() with matching period, getActivePeriod() with no active period (expect null), listAvailablePeriods() pagination (verify offset/limit are passed to the query). Integration tests (run against Supabase local emulator): createPeriod() round-trip, overlap constraint enforced at database level, RLS blocks unauthorized insert. Target 90%+ branch coverage on service logic.

Component
Reporting Period Service
service medium
Epic Risks (4)
high impact high prob integration

NHF members can belong to up to 5 local chapters. When a participant has activities registered under different chapter IDs within the same reporting period, deduplication requires a reliable cross-chapter identity key. If national IDs are absent for some members (a known data quality issue in NHF's systems), the deduplication service may fail to identify duplicates, resulting in inflated counts submitted to Bufdir.

Mitigation & Contingency

Mitigation: Implement a multi-attribute identity matching strategy: primary match on national_id, fallback to (full_name + birth_year + municipality) composite key. Expose a low-confidence match list in DeduplicationAnomalyReport that coordinators can review and manually resolve before submission.

Contingency: If identity data quality is too poor for reliable automated deduplication for specific organisations, add an organisation-level config flag that disables cross-chapter deduplication for that org and requires coordinators to manually review the anomaly report before submitting.

high impact medium prob integration

The geographic distribution algorithm must resolve NHF's 1,400 local chapter hierarchy to regional aggregates. If the organizational unit hierarchy in the database is incomplete (missing parent-child relationships for some chapters), the geographic service will silently drop activities from unmapped chapters, producing an understated geographic breakdown.

Mitigation & Contingency

Mitigation: Add a hierarchy completeness validation step in GeographicDistributionService that counts activities without a resolvable region assignment and surfaces them as an 'unmapped_activities' field in the distribution result. Block export if unmapped_activities > 0.

Contingency: Provide a 'national' fallback bucket for activities from chapters with no region assignment, clearly labelled in the preview screen so coordinators are alerted to fix the org hierarchy data before re-running aggregation.

high impact low prob technical

BufdirAggregationService orchestrates four dependent services. If one service (e.g., GeographicDistributionService) throws mid-pipeline, the partially assembled metrics payload may be silently cached or returned as if complete, resulting in a Bufdir submission missing the geographic breakdown section.

Mitigation & Contingency

Mitigation: Implement the orchestrator as a transactional pipeline using Dart's Result type pattern: each stage returns Either<AggregationError, PartialResult>, and the orchestrator only proceeds if all stages succeed. The final payload is only assembled and persisted when all stages return success.

Contingency: If a partial failure state reaches the UI, the AggregationProgressIndicator must display a specific stage failure message with a retry option that re-runs only the failed stage rather than the full pipeline.

medium impact medium prob scope

Internal activity types that have no corresponding Bufdir category in the mapping configuration will cause the aggregation to silently exclude those activities from the final counts. Coordinators may not notice the omission until Bufdir queries why submission totals are lower than expected.

Mitigation & Contingency

Mitigation: BufdirAggregationService must produce an unmapped_activity_types list as part of its output. If any internal activity types are unmapped, display a blocking warning in the AggregationSummaryWidget listing the unmapped types before allowing the coordinator to proceed to export.

Contingency: Allow coordinators to temporarily assign unmapped activity types to a Bufdir 'other' catch-all category as an emergency workaround, with an audit flag indicating manual override was applied for that submission.