high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

A pure function sortMentorsByDistance(LatLng origin, List<MentorLocation> mentors) returns a new list sorted by ascending haversine distance in kilometres
The original input list is not mutated
Distance is calculated using the haversine formula with Earth radius 6371.0 km
A MentorLocation with coordinates identical to origin has distance 0.0 km and appears first
Mentors with null latitude or longitude are placed at the end of the sorted list with distance double.infinity
Unit test: Oslo (59.9139, 10.7522) to Bergen (60.3913, 5.3221) returns approximately 305 km (±2 km tolerance)
Unit test: Oslo to Tromsø (69.6489, 18.9551) returns approximately 1150 km (±5 km tolerance)
Unit test: empty input list returns empty list without error
Unit test: single-element list returns that element
Unit test: list with a null-coordinate mentor places it last
The utility is accessible as a static or top-level function and has no dependency on Flutter widgets or platform channels (pure Dart)

Technical Requirements

frameworks
Flutter
flutter_test
data models
MentorLocation
LatLng
performance requirements
Sort must complete in under 10 ms for lists of up to 500 mentors on a mid-range device
No external package required — haversine formula implemented in pure Dart using dart:math
security requirements
Origin coordinates must be validated (latitude in -90..90, longitude in -180..180) before computation to prevent NaN propagation

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as a static method on a HaversineUtils class or as a top-level function in a distance_utils.dart file within the service layer. The formula: a = sin²(Δlat/2) + cos(lat1)·cos(lat2)·sin²(Δlon/2); c = 2·atan2(√a, √(1−a)); d = R·c. All angles must be converted from degrees to radians using the pi constant from dart:math. Use List.sort with a custom comparator that handles null coordinates by treating them as double.infinity.

Do not use geolocator or latlong2 packages for the calculation itself — keep the utility dependency-free. The MentorLocationService can expose this as sortByDistanceFromOrigin(LatLng origin) as a convenience wrapper that delegates to the static utility.

Testing Requirements

All tests in flutter_test (no widget environment needed — pure unit tests). Include parameterised tests for the known Norwegian city pairs listed in the acceptance criteria. Test boundary values: antipodal points (~20004 km), same point (0 km), equator-to-pole. Test null coordinate handling.

Test list stability: two mentors at equal distance should retain their original relative order (stable sort). Verify the function does not modify the input list by checking reference equality before and after the call. Target 100% line coverage on the haversine utility function.

Component
Mentor Location Service
service high
Epic Risks (2)
medium impact medium prob technical

The dual BLoC state machines (map view state + filter state) may introduce subtle synchronisation bugs where filter changes do not correctly re-trigger viewport queries, causing stale data to appear on the map.

Mitigation & Contingency

Mitigation: Define all BLoC state transitions in a state diagram before implementation. Use flutter_bloc's BlocObserver in development mode to log every state transition. Write explicit unit tests for filter-change → re-query transitions.

Contingency: If state synchronisation bugs appear in integration testing, refactor to a single unified BLoC that owns both map viewport state and filter state, eliminating cross-BLoC dependencies.

low impact medium prob scope

Cached mentor location data may become stale (mentors move, pause, or revoke consent) and coordinators in offline mode could be shown incorrect mentor information, leading to wasted outreach.

Mitigation & Contingency

Mitigation: Display a clear timestamp on cached data indicating when it was last synced. Set cache TTL to 24 hours and show an 'offline — data from [date]' banner. Revoked consent removes the mentor from the cache on next successful sync via contact-cache-sync-repository.

Contingency: If cache staleness causes user complaints, reduce TTL to 4 hours and implement background sync on app foreground. Accept that very-recently-revoked mentors may appear briefly in offline mode — document this as a known limitation in the privacy policy.