Implement AutoApprovalEvaluator threshold logic
epic-mileage-reimbursement-entry-infrastructure-task-007 — Implement the stateless AutoApprovalEvaluator service that accepts a MileageClaim and OrgConfiguration and returns a deterministic ClaimStatus (auto_approved, pending_review). Logic must handle: distance below threshold without receipt → auto_approved; distance at or above threshold → pending_review; any expense attachment present → pending_review regardless of distance.
Acceptance Criteria
Technical Requirements
Execution Context
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.
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.
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.
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.