critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

BenefitCalculationResult is a Dart class annotated as immutable (all fields are final)
Class exposes four double fields: hours_saved, travel_cost_avoided, public_health_offset, and total_societal_value
total_societal_value is computed as the sum of the other three fields (either as a computed getter or set during construction)
A copyWith method is present and allows overriding any subset of fields while preserving the rest
Equality is overridden via == and hashCode using all four fields so two instances with the same values are equal
toString() returns a human-readable representation useful for debugging and logging
All fields are non-negative; the constructor or a factory constructor validates that no value is negative and throws an ArgumentError otherwise
The class has no dependency on Flutter widgets, BLoC, or Supabase — it is a plain Dart data class
A const constructor is provided so instances can be used as compile-time constants where needed
The file is placed in the benefit_calculator feature directory following the existing feature-folder convention

Technical Requirements

frameworks
Flutter
BLoC
data models
BenefitCalculationResult
performance requirements
Instantiation must be O(1) with no heap allocation beyond the object itself
Equality check must be O(1)
security requirements
No personal data stored in this model — it contains only numeric aggregates
Model must not be serialised to persistent storage without explicit intent

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart's @immutable annotation from package:meta and implement copyWith manually (code generation is overkill for four fields). Prefer a single positional-named-parameters constructor. total_societal_value should be a late final or computed in the constructor body rather than a getter so it is captured in equality/hashCode. Follow the existing model conventions in the app (check for existing data classes such as ActivityInput to mirror the pattern).

Do not use freezed or json_serializable unless those packages are already in pubspec.yaml — keep dependencies minimal.

Testing Requirements

Unit tests using flutter_test. Test: (1) default construction with all fields set; (2) const construction; (3) copyWith preserves unchanged fields and updates changed ones; (4) equality — two instances with identical values are equal, differing instances are not; (5) hashCode consistency; (6) negative-value guard throws ArgumentError; (7) total_societal_value equals sum of components. No mocks required.

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.