critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

BenefitMultiplierConfig is an immutable Dart class with three double fields: hourly_value_rate, travel_cost_per_km, and health_offset_factor
BenefitMultiplierConfigRepository exposes a single async method: Future<BenefitMultiplierConfig> fetchConfig()
The repository first checks a Supabase remote config table (or equivalent) for organisation-specific overrides keyed by organisation_id
If the remote call fails or returns no row, the repository falls back to a bundled JSON asset (e.g., assets/config/benefit_multipliers.json)
The local asset file exists, is valid JSON, and contains sensible Norwegian default values (e.g., hourly_value_rate aligned with NAV's published value-of-time rate)
The repository is injectable/mockable via an abstract interface IBenefitMultiplierConfigRepository so tests can provide a stub
Network errors are propagated as typed exceptions (ConfigFetchException) rather than generic exceptions
The repository does not cache results in memory — caching is the BLoC's responsibility
The repository is registered in the app's dependency injection setup (Riverpod provider or BLoC provider, following existing patterns)
The asset file is declared in pubspec.yaml under the flutter/assets section

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
apis
Supabase REST API — remote config table query
data models
BenefitMultiplierConfig
OrganisationConfig
performance requirements
Remote fetch must complete within 3 seconds on a typical 4G connection; timeout after 5 seconds
Asset fallback must load synchronously within the async call with no perceptible delay
security requirements
Organisation ID used as the query key must be validated against the authenticated session — never taken from untrusted input
Supabase row-level security (RLS) must restrict config rows to the authenticated user's organisation
Config values must be validated as positive doubles before returning — reject and fall back to asset if values are zero or negative

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define IBenefitMultiplierConfigRepository as an abstract class with a single fetchConfig() method so tests can inject a FakeBenefitMultiplierConfigRepository without hitting Supabase. The concrete implementation uses the app's existing Supabase client singleton. For the asset fallback, use rootBundle.loadString() inside the repository. Keep the JSON schema flat: { "hourly_value_rate": 247.0, "travel_cost_per_km": 4.03, "health_offset_factor": 0.12 } — these default values align with Norwegian public sector estimates (NAV time value ~247 NOK/hr, Statsforvalteren km rate).

Register the repository as a Riverpod Provider or equivalent to match the app's DI pattern.

Testing Requirements

Unit tests using flutter_test with a mocked Supabase client. Test scenarios: (1) successful remote fetch returns correct BenefitMultiplierConfig; (2) remote returns empty result — falls back to asset and returns defaults; (3) network timeout — falls back to asset; (4) remote returns invalid values (zero, negative) — falls back to asset; (5) asset JSON is malformed — throws ConfigFetchException; (6) returned config has all three fields populated with positive values. Use Mockito or mocktail to stub the Supabase client.

Component
Benefit Calculation Service
service low
Epic Risks (2)
medium impact medium prob scope

The exact formulas for SROI social value (public health system cost offset) may not be agreed upon with the client organisations or Bufdir. If formulas are disputed post-implementation, the service and all downstream tests will need to be revised.

Mitigation & Contingency

Mitigation: Document the two formulas and their multiplier inputs explicitly in the BenefitCalculationService source file and obtain sign-off from the product owner before implementation begins. Store formula multipliers exclusively in the Supabase config table so adjustments require only a config update, not a code deployment.

Contingency: If formulas are revised after implementation, the pure-function architecture means changes are isolated to BenefitCalculationService. Update the service, adjust unit tests, and re-run the test suite. No UI components need modification.

medium impact low prob technical

The BLoC must handle the asynchronous config fetch from the multiplier repository during initialisation. Race conditions between the config loading state and the first InputChanged event could result in calculations running against null or stale multiplier values.

Mitigation & Contingency

Mitigation: Guard all InputChanged event handlers in the BLoC with a null check on the loaded config state. Emit BenefitCalculationLoading until config resolves. Write a BLoC test that fires InputChanged before config loads and asserts the state remains BenefitCalculationLoading.

Contingency: If race conditions surface in integration testing, add an explicit config-loaded flag and queue InputChanged events until the flag is set, draining the queue on config resolution.