critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

MileageCalculationService.calculate(distance, ratePerKm) returns a double representing NOK amount rounded to nearest øre (2 decimal places, round-half-up)
calculate(0.0, ratePerKm) returns 0.00 without throwing
calculate(distance, 0.0) returns 0.00 without throwing
Norwegian rounding: 0.005 rounds up to 0.01, 0.004 rounds down to 0.00
Service is stateless — no mutable fields, safe to call from multiple isolates simultaneously
All parameters and return type are typed as double; no dynamic types used
Method is synchronous and completes in < 1 ms on any supported device
Service exposes a single public factory constructor or const constructor (no dependencies injected)
No external packages or platform channels are used — pure Dart arithmetic only
Public API documented with dartdoc comments including parameter descriptions and rounding behaviour note

Technical Requirements

frameworks
Flutter
Dart
data models
MileageClaim
OrgConfiguration
performance requirements
Synchronous execution — zero async overhead
No heap allocations beyond the return value
Must complete in under 1 ms on mid-range Android devices
security requirements
No logging of financial amounts in release builds
Pure function — no side effects, no state mutation

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Norwegian rounding ('avrunding til nærmeste øre') uses round-half-up semantics, which Dart's built-in double arithmetic does NOT guarantee due to floating-point representation. Implement using integer arithmetic: convert both operands to integers in units of 1/100 øre (i.e., multiply by 10000), perform multiplication as int, then round the last two digits using integer division with remainder check ((remainder >= 5) → round up). Return (intResult / 100.0). This avoids all floating-point drift.

Place the class in lib/features/mileage/domain/services/mileage_calculation_service.dart. Make the class final and the constructor const. No dependency injection is needed — callers instantiate directly or the BLoC/Riverpod layer holds a single instance.

Testing Requirements

Unit tests only (flutter_test). Tests must be deterministic and pass without any network, platform channel, or SharedPreferences access. Minimum coverage: 100% of the calculate() method branches. Test suite must run in < 500 ms.

See task-008 for the full test matrix.

Epic Risks (3)
high impact medium prob security

Supabase Row Level Security policies for mileage_claims may require complex join conditions to distinguish peer mentor (own claims only) from coordinator (chapter-scoped claims) access. If the RLS policy is misconfigured, coordinators could see claims outside their chapter scope or peer mentors could read other users' data, causing a data privacy incident.

Mitigation & Contingency

Mitigation: Write RLS policy SQL as part of this epic with explicit test cases for each role. Use Supabase's built-in policy testing tools and add integration tests that assert cross-user data isolation before merging.

Contingency: If RLS configuration proves too complex to test reliably within the epic, add an application-layer guard in the adapter that filters query results by authenticated user ID as a defence-in-depth measure while the policy is corrected.

medium impact low prob scope

Norwegian tax authority reimbursement rounding rules may change or may not be publicly documented in machine-readable form. Using the wrong rounding convention could cause systematic over- or under-payment, leading to compliance issues for the organisation.

Mitigation & Contingency

Mitigation: Source the exact rounding specification from HLF's finance team before implementing MileageCalculationService. Document the rule as a comment in the source code and link to the authoritative reference.

Contingency: If the rule is ambiguous, implement both truncation and half-up rounding behind a configuration flag so the organisation can switch without a code release.

low impact low prob technical

SharedPreferences reads and writes are asynchronous. If the distance prefill service (built in the next epic) calls LocalDistanceCache concurrently with a post-submission write, a race condition could result in the cache returning a stale or partially written value, causing the next form load to show an incorrect default.

Mitigation & Contingency

Mitigation: Wrap all SharedPreferences access in LocalDistanceCache with sequential async operations and document the non-concurrent usage contract in the class API.

Contingency: If race conditions are observed in testing, introduce a simple mutex pattern using a Completer to serialise cache operations.