critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

evaluate(claim, orgConfig) returns ClaimStatus.auto_approved when claim.distanceKm < orgConfig.autoApprovalThresholdKm AND claim.attachments is empty
evaluate(claim, orgConfig) returns ClaimStatus.pending_review when claim.distanceKm >= orgConfig.autoApprovalThresholdKm regardless of attachments
evaluate(claim, orgConfig) returns ClaimStatus.pending_review when claim.attachments is non-empty regardless of distance
evaluate(claim, orgConfig) returns ClaimStatus.pending_review when orgConfig is null (safe default — no accidental auto-approvals)
evaluate(claim, orgConfig) returns ClaimStatus.pending_review when claim.distanceKm == orgConfig.autoApprovalThresholdKm (boundary: at-threshold → pending)
evaluate(claim, orgConfig) returns ClaimStatus.auto_approved when claim.distanceKm == 0.0 AND attachments is empty AND threshold > 0
Service is stateless — evaluate() is a pure function with no side effects
ClaimStatus enum values are auto_approved and pending_review only; no other statuses returned by this service
Boundary threshold comparison uses >= (not >) to match HLF requirement spec
All decision paths documented with inline comments referencing the HLF business rule source

Technical Requirements

frameworks
Flutter
Dart
data models
MileageClaim
OrgConfiguration
ClaimStatus
performance requirements
Synchronous execution, pure function
No allocations beyond ClaimStatus return value
security requirements
Default to pending_review (most restrictive) for any null/missing config — never default to auto-approved
No financial data logged in release mode

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The decision logic has exactly three independent conditions: (1) distance < threshold, (2) distance == threshold (boundary), (3) attachments non-empty. Implement as a single evaluate() method with early returns for the non-auto-approved paths — this keeps the happy path (auto_approved) as the final return and makes all rejection conditions explicit. Guard clause order: null orgConfig check first, then attachment check, then distance check. Place in lib/features/mileage/domain/services/auto_approval_evaluator.dart.

Make the class final and const-constructible. The ClaimStatus enum should be defined in the domain layer (lib/features/mileage/domain/entities/claim_status.dart) and shared with the persistence layer — do not duplicate it.

Testing Requirements

Unit tests only (flutter_test). Full decision-tree coverage: all 6 documented paths must have at least one test each. Boundary value tests required for the threshold (below, equal, above). Null orgConfig must be tested explicitly.

No async, no platform access. See task-009 for the complete test matrix.

Component
Auto-Approval Threshold Evaluator
service low
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.