high priority low complexity backend pending backend specialist Tier 5

Acceptance Criteria

DistancePrefillService.updatePrefill() is called with the exact route and distance values from the successfully persisted claim
If updatePrefill() throws any exception (network error, Supabase timeout, unexpected error), the exception is caught and logged at warning level with the claim ID and error message
The SubmissionOutcome returned after a cache update failure is identical in type and content to a successful cache update scenario — callers cannot distinguish between the two
Persistence success is fully decoupled: a cache update failure never causes submitClaim() to return a failure outcome
The try-catch block is scoped only to the DistancePrefillService.updatePrefill() call — persistence logic is not inside the same try-catch
Log output on cache failure includes: claim ID, route origin/destination, distance, and the error message or stack trace
No user-facing error, toast, or UI indication is triggered when cache update fails silently
The updatePrefill() call does not block the return of SubmissionOutcome — if async, it is awaited within the try-catch before returning but does not extend the critical path in terms of failure propagation

Technical Requirements

frameworks
Flutter
Dart
apis
DistancePrefillService.updatePrefill(route, distance)
data models
MileageClaim
RouteData
SubmissionOutcome
performance requirements
Cache update call must not add more than 500ms to total submission latency under normal conditions
In the failure path, the catch block must complete within 50ms (logging only, no retries)
security requirements
Logged error must not include personally identifiable information beyond claim ID
Cache update failure must not expose internal error details to the UI layer

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Place the cache update call immediately after the persistence await, inside a dedicated try-catch that does not share scope with the persistence try-catch. Pattern: `try { await _distancePrefillService.updatePrefill(route: claim.route, distance: claim.distanceKm); } catch (e, stackTrace) { _logger.warning('Cache update failed for claim ${claim.id}', e, stackTrace); }` — then fall through to return the success SubmissionOutcome unconditionally. Avoid using `unawaited()` here as it makes it harder to test and reason about ordering. The key architectural constraint is that the cache is a best-effort side effect, not a requirement for correctness.

Ensure DistancePrefillService is injected (not constructed inline) so tests can swap in a mock.

Testing Requirements

Unit tests (covered in task-007) must verify the try-catch isolation. For this task, manually verify with a mock DistancePrefillService that throws: (1) a generic Exception, (2) a network-specific error, and (3) a null/unexpected state. In all three cases assert that the returned SubmissionOutcome is a success type. Also verify the log call is made with the correct arguments using a mock logger.

No integration tests required at this task level — those are handled in task-008.

Component
Mileage Claim Service
service medium
Epic Risks (2)
high impact medium prob integration

The auto-approval rule requires checking whether any additional expense lines are attached to the claim. The interface between the mileage claim and any co-submitted expense items is not fully defined within this feature's component scope. If the domain model does not include an explicit additionalExpenses collection, the evaluator cannot make a correct determination, which could auto-approve claims that should require manual review.

Mitigation & Contingency

Mitigation: Define the MileageClaim domain object interface with an explicit additionalExpenses: List field (nullable/empty for mileage-only claims) before implementing the service. Coordinate with the Expense Type Selection feature team to agree on the shared domain contract.

Contingency: If the cross-feature contract cannot be finalised before implementation, implement the evaluator to treat any non-null additionalExpenses list as requiring manual review and document the assumption for review during integration testing.

medium impact medium prob technical

A peer mentor who taps the submit button multiple times rapidly (e.g. due to slow network) could cause MileageClaimService to be invoked concurrently, resulting in duplicate claim records being persisted with the same trip data.

Mitigation & Contingency

Mitigation: Implement a submission-in-progress guard in MileageClaimService using a BLoC/Cubit state flag that prevents re-entrant calls. The UI layer (implemented in Epic 4) will also disable the submit button during processing.

Contingency: Add a Supabase-level unique constraint or idempotency key on (user_id, origin, distance, submitted_at truncated to minute) to prevent duplicate rows reaching the database even if the application guard fails.