high priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

Service exposes a single public method: BenefitCalculationResultModel calculate(int sessionCount, int durationMinutes, BenefitCalculatorConfig config)
volunteerHours computed as (sessionCount * durationMinutes) / 60.0, rounded to one decimal place
contactsReached computed as sessionCount * config.averageContactsPerSession, returned as int
livesTouched computed as contactsReached * config.rippleEffectMultiplier, returned as int
economicValueEquivalent computed as volunteerHours * config.hourlyEconomicValueNOK, rounded to nearest integer (NOK)
All four output fields are populated in the returned BenefitCalculationResultModel
Passing sessionCount=0 or durationMinutes=0 returns a result model with all fields set to zero — no division by zero or exception
Passing negative values throws ArgumentError with a descriptive message
Service class has no instance state — all methods are either static or the class holds only the injected config dependency
BenefitCalculatorConfig is a separate immutable value object (final fields, const constructor) with fields: averageContactsPerSession (double), rippleEffectMultiplier (double), hourlyEconomicValueNOK (double)
BenefitCalculationResultModel is an immutable value object with copyWith and equality support
No Flutter framework imports — pure Dart; can run in a non-Flutter test environment

Technical Requirements

frameworks
Dart
data models
annual_summary
activity
performance requirements
calculate() executes in O(1) time — no loops, no async, no I/O
Suitable for calling on every keystroke from BLoC without debouncing at the service layer
security requirements
Economic value figures are illustrative estimates only — must not be presented as official financial data in the UI

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Keep this as a plain Dart class with no Flutter dependencies. Place in lib/features/benefit_calculator/services/benefit_calculation_service.dart. Define BenefitCalculatorConfig and BenefitCalculationResultModel in the same feature folder under models/. Use double arithmetic throughout and only convert to int at the final step for integer fields to minimise rounding error accumulation.

The config values (multipliers) should be loaded from remote config or a constants file by the caller (BLoC) — the service itself only does the math. Annotate the class with @visibleForTesting on any helpers if added; keep public API surface minimal.

Testing Requirements

Pure Dart unit tests (flutter_test without widget test runner): test nominal calculation with known inputs and verify exact output values. Test zero-input edge cases return zero-result model. Test negative input throws ArgumentError. Test fractional volunteer hours are correctly rounded (e.g., 7 sessions × 45 min = 5.25h → 5.3h).

Test config multiplier of 1.0 acts as identity for contacts/lives. Achieve 100% line and branch coverage — service has no external dependencies to mock.

Component
Benefit Calculator Screen
ui low
Epic Risks (3)
high impact medium prob technical

The RepaintBoundary PNG capture approach for sharing the results card may produce blurry or oversized images on high-DPI devices, or may silently fail on certain Android OEM configurations that restrict off-screen rendering. A failed share would break one of the core use cases (recruitment tool).

Mitigation & Contingency

Mitigation: Implement the capture using the established screenshot-capture-utility pattern already present in the Wrapped summary feature (component 542-screenshot-capture-utility). Test on a range of iOS and Android devices including Samsung and Huawei OEM builds during development. Set explicit pixel ratio (3.0) when calling toImage() to guarantee resolution.

Contingency: If image capture fails on a platform, the BenefitShareService falls back to sharing the plain-text summary only, with a user-facing message explaining the image could not be generated. This ensures the share flow never fully blocks.

high impact medium prob technical

Implementing full WCAG 2.2 AA compliance for the results card and metric tiles — including live regions, focus management, and semantic labels that read naturally in Norwegian — requires deep familiarity with Flutter semantics APIs. Gaps may only surface during screen reader testing on physical devices, late in the sprint.

Mitigation & Contingency

Mitigation: Use the existing semantics-wrapper-widget (606) and live-region-announcer (608) components from the accessibility feature rather than implementing custom semantics. Assign screen reader testing on a physical iPhone with VoiceOver as a mandatory acceptance gate, not an afterthought. Write widget tests using Flutter's AccessibilityGuideline matchers early in development.

Contingency: If screen reader issues are found late, the pure semantic markup approach (no Canvas numbers, all Semantics wrappers) limits the blast radius to label text corrections. Escalate to the accessibility feature team for a pairing session to resolve complex focus management issues.

medium impact low prob integration

The BenefitResultsCard must match the Wrapped design language used in the annual summary feature. If design tokens or widget patterns are inconsistent between the two features, the results card will look out of place and undermine the intended emotional impact for sharing.

Mitigation & Contingency

Mitigation: Review the existing Wrapped summary screen (529-wrapped-summary-screen) and stat card widget (530-stat-card-widget) implementations before building the results card. Reuse design tokens from the existing design-token-theme (200) system. Involve the designer in a review of the results card mock-up against the Wrapped language before implementation begins.

Contingency: If design parity issues are discovered post-implementation, isolate visual adjustments to the BenefitResultsCard widget. The feature's business logic and accessibility compliance are unaffected by visual polish changes.