Define BenefitCalculationResult model
epic-benefit-calculator-core-logic-task-001 — Create the immutable BenefitCalculationResult data class that holds all computed output fields: hours_saved, travel_cost_avoided, public_health_offset, and total_societal_value. Include a copyWith method and equality overrides. This model is consumed by both the service and the BLoC.
Acceptance Criteria
Technical Requirements
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.
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.
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.