high priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

getCachedPreview() returns the BufdirPreviewModel from the most recent successful assemblePreview() call without triggering a repository fetch
getCachedPreview() returns null when no preview has been assembled in the current session or after invalidate() is called
invalidate() clears the cached model and resets the state to an initial/empty state
Changing the selected period (different periodId) automatically triggers invalidation before the next assembly
Switching the authenticated organisation automatically triggers cache invalidation
A second call to assemblePreview() with the same periodId and orgId within the same session returns the cached model and does NOT call the repository again
The cache is in-memory only and does not survive app restart or hot-reload in production builds
State changes (loading, loaded, error, invalidated) are exposed as a Riverpod StateNotifier stream that UI layers can watch
The StateNotifier or BLoC holding cache state is registered in the Riverpod provider tree with correct scope (not global singleton across orgs)
Code-level documentation (doc comments) describes cache lifetime, invalidation triggers, and thread-safety guarantees

Technical Requirements

frameworks
Flutter
Riverpod
BLoC
apis
BufdirPreviewService.assemblePreview()
BufdirPreviewService.getCachedPreview()
BufdirPreviewService.invalidate()
Riverpod StateNotifierProvider or BLoC Cubit
data models
BufdirPreviewModel
BufdirPreviewSessionState (loading | loaded | error | invalidated)
performance requirements
getCachedPreview() must return synchronously (no async overhead) when cache is populated
State updates must propagate to listening widgets within one frame (no artificial delay)
security requirements
Cache must be invalidated immediately on authentication change or org switch — never serve a previous org's data to a newly authenticated user
In-memory cache must not be persisted to disk in any form — no shared_preferences or Hive writes for this cache layer

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement a BufdirPreviewSessionState sealed class with variants: Initial, Loading, Loaded(BufdirPreviewModel model, String periodId, String orgId), and Error(Object error). Use a Riverpod StateNotifier rather than mixing state into BufdirPreviewService directly — this keeps the service a pure orchestrator and the notifier a pure state holder. The notifier wraps assemblePreview() and manages transitions. Cache hit detection: compare incoming (periodId, orgId) against the Loaded state's stored IDs before deciding to fetch.

Expose getCachedPreview() as a synchronous selector on the current state. Invalidate() simply emits Initial(). Document explicitly that this cache is a performance optimisation for the preview→export navigation flow and must not be used as a source of truth for any write operations.

Testing Requirements

Unit tests using flutter_test: (1) getCachedPreview returns null before first assembly, (2) getCachedPreview returns model after successful assembly, (3) invalidate clears cache and subsequent getCachedPreview returns null, (4) second assemblePreview with same args does not invoke repository, (5) period change invalidates cache before new assembly, (6) org switch invalidates cache. Use fakeAsync and mocktail. Test StateNotifier/Cubit state stream emissions for each lifecycle transition (loading → loaded → invalidated). Aim for 100% statement coverage on the session state class.

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.