critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

BenefitCalculationResult is a Dart class marked as immutable (@immutable) with all fields declared as final.
Fields: hourlyRateEquivalent (double), travelCostTotal (double), publicHealthSystemValue (double), totalSocietalValue (double), sessionCount (int), averageDurationMinutes (double).
copyWith() method returns a new instance with selectively overridden fields; unspecified fields retain original values.
== operator returns true when all fields are equal between two instances.
hashCode is computed from all fields (Object.hash or equatable package).
toString() returns a readable representation including all field names and values.
toJson() returns a Map<String, dynamic> with all fields serialized (doubles remain doubles, int remains int).
fromJson(Map<String, dynamic>) factory constructor correctly deserializes all fields and throws a descriptive FormatException for missing or wrong-type keys.
A BenefitCalculationResult.empty() or zero-value factory is provided for initial state use.
Unit tests cover: equality, copyWith partial override, toJson round-trip (toJson → fromJson → equality), and fromJson with missing key triggering FormatException.

Technical Requirements

frameworks
Flutter
flutter_test
data models
BenefitCalculationResult
performance requirements
Model instantiation and serialization complete in under 1ms for typical usage.
security requirements
No PII stored in this model — all fields are computed numeric values.
toJson output must not include any fields beyond the defined schema.

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Place the model in lib/features/benefit_calculator/models/benefit_calculation_result.dart. Use @immutable from package:meta. For equality and hashCode, prefer the equatable package (already common in BLoC projects) or manually implement using Object.hash(). Ensure fromJson uses safe casting: (json['sessionCount'] as num).toInt() rather than direct cast to avoid runtime type errors when Supabase or JSON returns numeric types inconsistently.

The totalSocietalValue field should be validated as the sum of other value components in tests to document the business invariant even if the model itself does not enforce it. Avoid depending on any other feature models to keep this class maximally reusable.

Testing Requirements

Unit tests using flutter_test. Test file: test/models/benefit_calculation_result_test.dart. Cover: default construction, copyWith (full and partial), equality reflexivity and symmetry, hashCode consistency, toJson produces correct keys and types, fromJson round-trip fidelity, fromJson with null value throws FormatException, BenefitCalculationResult.empty() returns all-zero/zero-int instance. No integration or widget tests needed.

Epic Risks (2)
medium impact medium prob integration

Supabase organisation configuration table may not yet have the five multiplier columns, requiring a migration. If the migration is not coordinated with other teams touching the same table, schema conflicts could delay delivery.

Mitigation & Contingency

Mitigation: Add multiplier columns in a dedicated, non-destructive ALTER TABLE migration script. Review the organisation config table schema with the backend team before writing the migration. Use nullable columns with defaults so existing rows are unaffected.

Contingency: If the migration cannot be deployed in time, stub the repository to return hardcoded default multiplier values from a local config file, allowing parallel development. Swap in the real Supabase fetch once the migration is live.

low impact medium prob scope

The ActivitySummaryAggregator depends on activity records already persisted in the database. For newly onboarded peer mentors with no activity history, the aggregator will return zero counts, which could make the calculator appear broken on first use.

Mitigation & Contingency

Mitigation: Design the pre-fill value object to distinguish between 'no data yet' and 'zero activities'. The calculator input panel should display empty inputs (not zero) when no history exists, with placeholder text guiding the user to enter values manually.

Contingency: If the distinction cannot be surfaced cleanly in the UI timeline, fall back to always showing empty inputs and document the manual-entry path as the primary UX until activity data accumulates.