high priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

DistancePrefillService is a plain Dart class with no Flutter widget dependencies
getLastDistance(String userId) returns double? (null if no stored value for that userId)
saveLastDistance(String userId, double distanceKm) persists the value under a key scoped to userId (e.g., 'distance_prefill_$userId')
clearOnLogout(String userId) removes the stored key for the given userId from LocalDistanceCache
getLastDistance returns null after clearOnLogout is called for the same userId
distanceKm values must be positive (> 0); saveLastDistance throws ArgumentError for non-positive values
LocalDistanceCache is injected via constructor — no direct instantiation inside the service (dependency inversion)
Service handles cache read/write exceptions from LocalDistanceCache gracefully — getLastDistance returns null on read failure; saveLastDistance logs and rethrows or returns silently based on team convention
All public methods are async-compatible (Future<T>) to accommodate future migration from in-memory to persistent storage

Technical Requirements

frameworks
Dart (latest)
Flutter
data models
LocalDistanceCache (abstraction over shared_preferences or in-memory store)
performance requirements
Cache reads must complete synchronously or near-synchronously (shared_preferences reads are typically sub-millisecond after init)
No network I/O in this service — purely local storage
security requirements
Cache key must be scoped per userId to prevent one user's prefill value leaking to another user on the same device
Distance value is non-sensitive, but key naming must not expose userId in a way that reveals PII in device storage logs

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define a LocalDistanceCache abstract interface with read(String key) → Future and write(String key, double value) → Future and delete(String key) → Future. Implement it with SharedPreferencesDistanceCache in the data layer. This layering allows the unit tests to use a simple Map-backed fake without initializing SharedPreferences. Key naming convention: 'distance_prefill_v1_$userId' — the v1 prefix enables future key migration.

The clearOnLogout method in this task is a simple imperative method; the auth stream subscription that calls it is wired in task-008 (the Riverpod provider layer). Keep this service unaware of Riverpod.

Testing Requirements

Unit tests (flutter_test): test getLastDistance returns null for a userId with no stored value; test saveLastDistance followed by getLastDistance returns the saved value; test clearOnLogout removes the value so subsequent getLastDistance returns null; test that different userId keys are independent (save for user A, get for user B returns null); test that saveLastDistance throws ArgumentError for distanceKm <= 0; test that getLastDistance returns null when LocalDistanceCache throws. Use a fake in-memory implementation of LocalDistanceCache (a simple Map-backed fake is sufficient).

Component
Distance Prefill Service
service low
Epic Risks (2)
medium impact medium prob integration

If OrgRateConfigRepository caches the per-km rate aggressively and an admin updates the rate mid-session, ongoing form interactions will show the old rate until the Stream emits. This could result in the UI showing a rate that differs from what is stored when the claim is submitted, causing confusion or disputes.

Mitigation & Contingency

Mitigation: Subscribe to a Supabase Realtime channel for the org_configuration table so config changes propagate within seconds. Document the eventual-consistency window in code comments.

Contingency: If Realtime subscription proves unreliable in test, add a polling fallback with a configurable interval (default 5 minutes) and display a 'rate updated' toast when the stream emits a changed value.

medium impact medium prob scope

The correction window within which a claim can be deleted or voided is not explicitly specified in the feature documentation. Implementing the wrong window (e.g. 24 hours vs 7 days) could lock users out of corrections or allow inappropriate post-approval modifications.

Mitigation & Contingency

Mitigation: Raise the correction window definition as a blocking question to the HLF product owner before implementing the delete/void path in MileageClaimRepository. Implement the window duration as an org-level configuration value rather than a hardcoded constant.

Contingency: If the question cannot be resolved before implementation, default to 24 hours as the most conservative option and flag the value for review in the first user-acceptance test.