critical priority high complexity backend pending backend specialist Tier 3

Acceptance Criteria

assemblePreview(periodId, orgId) returns a BufdirPreviewModel containing non-empty ordered sections when data exists for the given period and org
The returned BufdirPreviewModel contains a non-null ValidationResult with at least one validation entry per mandatory Bufdir field
When BufdirPreviewRepository throws a typed error, assemblePreview propagates it as the same typed error without wrapping in a generic exception
When prior-period data is unavailable, assemblePreview completes successfully with ValidationResult reflecting no anomaly comparisons rather than throwing
Sections in the returned model are ordered according to BufdirReportStructureMapper's canonical section order (not raw database order)
BufdirFieldValidationService.validate() is called exactly once per assemblePreview invocation with both current and prior models passed as arguments
All three dependencies (repository, mapper, validationService) are accepted via constructor injection and not instantiated internally
assemblePreview is an async method returning Future<BufdirPreviewModel> and never blocks the UI isolate
Method signature matches: Future<BufdirPreviewModel> assemblePreview(String periodId, String orgId)
If orgId does not match the authenticated user's organisation, the method throws an UnauthorisedAccessError before any data fetch

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
apis
Supabase RLS-enforced RPC or view queries
BufdirPreviewRepository.fetchAggregatedData()
BufdirPreviewRepository.fetchPriorPeriodData()
BufdirReportStructureMapper.map()
BufdirFieldValidationService.validate()
data models
BufdirPreviewModel
BufdirReportData
BufdirAggregatedField
ValidationResult
BufdirReportSection
performance requirements
assemblePreview must complete within 3 seconds on a 4G connection for a typical organisation dataset
Prior-period fetch must run concurrently with current-period mapping (use Future.wait) to avoid sequential latency
Mapping and validation must execute in under 100 ms for datasets with up to 500 aggregated fields
security requirements
orgId must be validated against the authenticated Supabase session's organisation claim before any data access
No raw Supabase row data may be logged — only sanitised field counts and period identifiers
Row-level security must be relied upon at the database level as a second layer; service-level check is the first layer

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use Future.wait([repository.fetchAggregatedData(periodId, orgId), repository.fetchPriorPeriodData(periodId, orgId)]) to parallelise the two network calls — do not await them sequentially. Wrap the prior-period fetch result in a nullable type so the absence of prior data is a first-class state, not an exception. BufdirReportStructureMapper.map() should receive only the current-period data; prior-period data is passed separately to the validation service. The ValidationResult should be embedded as a field on BufdirPreviewModel (not a side-effect).

Follow the repository pattern: the service must not construct any Supabase clients or queries — all data access goes through the injected repository. Register BufdirPreviewService as a Riverpod Provider (not StateNotifier at this stage — session state is handled in task-009).

Testing Requirements

Write unit tests using flutter_test with mockito or mocktail mocks for all three injected dependencies. Cover: (1) happy path returns correct BufdirPreviewModel shape, (2) repository error propagation, (3) prior-period unavailable path skips anomaly comparisons, (4) section ordering matches mapper output, (5) validate() is called with both current and prior models. Verify Future.wait parallelism by asserting both repository calls are initiated before either resolves. Use fake async to control timing.

Aim for 90%+ branch coverage on assemblePreview.

Component
Bufdir Preview Service
service high
Epic Risks (2)
medium impact medium prob scope

The exact minimum threshold values required by Bufdir guidelines (e.g., minimum participant counts per section) may not be formally documented in machine-readable form. If thresholds must be researched or negotiated during implementation, the validation service will be delayed and may launch with incomplete rules, reducing its effectiveness.

Mitigation & Contingency

Mitigation: Compile threshold rules from the Bufdir reporting guidelines PDF before sprint start. Store rules in a separate configuration file (not hardcoded in the service class) so they can be updated without a service rewrite. Treat unknown thresholds as 'no minimum' to avoid false errors.

Contingency: Launch with completeness and anomaly validation only, shipping threshold compliance rules as a follow-on config update once rules are confirmed with Bufdir. Flag this as a known limitation in the coordinator help text.

high impact low prob technical

BufdirPreviewService coordinates three async operations (fetch aggregated data, map structure, run validation). Race conditions or partial failures in this chain could produce an inconsistent preview model — e.g., a model with field values but no validation results — which would silently mislead coordinators into thinking the report is clean.

Mitigation & Contingency

Mitigation: Model the orchestration as a single BLoC/Cubit state machine with explicit states (Loading, Loaded, Error) and ensure validation is always run atomically after mapping, never in parallel. Write integration tests that simulate network failure at each step of the chain.

Contingency: If a partial failure state reaches production, detect it via the missing validation summary field in the preview model and show a full-screen error state rather than an incomplete preview, prompting the coordinator to retry.