high priority low complexity infrastructure pending backend specialist Tier 2

Acceptance Criteria

BLoC defines events: BenefitCalculatorLoaded (triggers initial data fetch), BenefitInputChanged({int sessionCount, int durationMinutes})
BLoC defines states: BenefitCalculatorInitial, BenefitCalculatorLoading, BenefitCalculatorReady({int sessionCount, int durationMinutes, BenefitCalculationResultModel result}), BenefitCalculatorError(String message)
On BenefitCalculatorLoaded event, BLoC calls ActivitySummaryAggregator to fetch aggregated totals; on success emits BenefitCalculatorReady with pre-populated values; on failure emits BenefitCalculatorReady with default values (not BenefitCalculatorError) so calculator still works offline
On BenefitInputChanged event, BLoC calls BenefitCalculationService.calculate() synchronously and emits BenefitCalculatorReady immediately — no async gap, no loading flicker
BenefitCalculationService is injected via constructor for testability
ActivitySummaryAggregator is injected via constructor for testability
BLoC does not hold mutable state outside of the emitted state object
Closing the BLoC cancels any in-flight ActivitySummaryAggregator futures via StreamSubscription.cancel()
BenefitCalculatorReady state is equatable — identical result emissions are deduplicated by BLoC's built-in equality check
Error from ActivitySummaryAggregator is logged (non-crashingly) and falls back to defaults — calculator is never blocked by a backend failure

Technical Requirements

frameworks
Flutter
BLoC
data models
activity
annual_summary
performance requirements
BenefitInputChanged handler is synchronous — emit happens in the same microtask as the event receipt
No redundant state emissions when inputs have not changed (Equatable comparison)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use flutter_bloc's Bloc base class (not Cubit) to maintain explicit event traceability. Place in lib/features/benefit_calculator/bloc/. Make all state classes extend Equatable and list all fields in props to enable deduplication. For the initial load, wrap the aggregator call in a try/catch inside the event handler, not a transformer, to keep error handling explicit.

Use on(handler, transformer: droppable()) to prevent duplicate load calls if the screen is rebuilt. The synchronous calculation path for BenefitInputChanged should not use any transformer — default sequential is correct here. Inject dependencies via positional constructor parameters so tests can pass mocks without a DI container.

Testing Requirements

BLoC unit tests using bloc_test package: test BenefitCalculatorLoaded emits Loading then Ready with pre-populated values when aggregator returns data. Test BenefitCalculatorLoaded emits Ready with defaults when aggregator throws. Test BenefitInputChanged emits Ready with correct recalculated result. Test rapid BenefitInputChanged events produce correct final state.

Mock BenefitCalculationService and ActivitySummaryAggregator. Achieve ≥ 95% branch coverage.

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.