critical priority low complexity infrastructure pending backend specialist Tier 1

Acceptance Criteria

A sealed class BenefitCalculatorEvent (or abstract class following app convention) with three concrete subtypes: InputChanged, ResetRequested, ShareRequested
InputChanged carries an ActivityInput payload representing the current form values
ResetRequested carries no payload
ShareRequested carries no payload (the BLoC emits the current result for the UI to handle sharing)
A sealed class BenefitCalculatorState with four concrete subtypes: BenefitCalculatorInitial, BenefitCalculatorLoading, BenefitCalculatorSuccess, BenefitCalculatorError
Every state class includes a final BenefitCalculationResult? result field (nullable) and a final bool isConfigLoading field
BenefitCalculatorInitial has result = null and isConfigLoading = true (config fetch is pending on startup)
BenefitCalculatorLoading has an optional result (carries the last result during re-calculation) and isConfigLoading = false
BenefitCalculatorSuccess has a non-null result and isConfigLoading = false
BenefitCalculatorError has a final String errorMessage, result may be null, isConfigLoading = false
All event and state classes are immutable with const constructors where possible
Equality and hashCode are overridden on all state classes so BLoC can detect unchanged emissions
Files follow the existing feature folder structure: lib/features/benefit_calculator/bloc/

Technical Requirements

frameworks
Flutter
BLoC
data models
BenefitCalculationResult
ActivityInput
performance requirements
State classes must be lightweight value objects — no heavy computation in constructors
security requirements
ActivityInput carried in InputChanged must not contain raw personal identifiers — only numeric measurement fields

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use Dart 3 sealed classes if the project targets Dart 3+ (enabling exhaustive switch); otherwise use the abstract-class pattern matching the rest of the app's BLoC files. Do NOT use the freezed package unless it is already in pubspec.yaml. If the app uses an Equatable base class for states (common in bloc-based Flutter apps), extend Equatable and override props rather than implementing == manually. Keep events and states in separate files (benefit_calculator_event.dart and benefit_calculator_state.dart) or co-locate in benefit_calculator_bloc.dart — follow the existing convention.

The isConfigLoading flag is specifically needed so the UI can show a loading spinner on first render without knowing if the user has interacted yet.

Testing Requirements

Unit tests using flutter_test. Verify: (1) InputChanged correctly stores the ActivityInput payload; (2) each state can be instantiated with its required fields; (3) equality check: two BenefitCalculatorSuccess instances with the same result are equal; (4) two BenefitCalculatorError instances with different errorMessage values are not equal; (5) isConfigLoading defaults match spec for Initial state; (6) copyWith (if provided) preserves unchanged fields. No mocks needed.

Component
Benefit Calculator BLoC
infrastructure 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.