critical priority medium complexity integration pending integration specialist Tier 5

Acceptance Criteria

PostgisSpatialAdapter class exists with a single public method: Future<List<MentorLocationResult>> queryMentorsInBounds(LatLngBounds bounds, String orgId)
MentorLocationResult is a typed Dart class/freezed model with fields: mentorId (String), latitude (double), longitude (double), displayName (String), consentLevel (String)
MentorLocationResult.fromJson(Map<String, dynamic>) factory constructor handles all fields from the RPC response
LatLngBounds is correctly translated: bounds.south → min_lat, bounds.west → min_lng, bounds.north → max_lat, bounds.east → max_lng
The adapter calls supabase.rpc('find_mentors_in_bounds', params: {...}) with the correct parameter names matching the SQL function signature
Raw RPC response (List<dynamic>) is mapped to List<MentorLocationResult> using MentorLocationResult.fromJson()
Network errors (SocketException, TimeoutException) are caught and re-thrown as SpatialQueryException with message and original cause
PostgrestException from Supabase is caught and re-thrown as SpatialQueryException
Empty bounding box result (empty list from RPC) is returned as an empty List<MentorLocationResult> without error
Adapter is registered as a Riverpod Provider injecting the Supabase client
Class has no mutable state and is safe to use as a singleton provider

Technical Requirements

frameworks
Flutter
Riverpod
Supabase PostgreSQL 15
flutter_map
apis
Supabase RPC rpc('find_mentors_in_bounds')
flutter_map LatLngBounds API (latlong2 package)
data models
assignment
performance requirements
Adapter must not add latency beyond the RPC call itself — parameter mapping is synchronous and trivially fast
Response mapping (List<dynamic> to List<MentorLocationResult>) must complete in under 10ms for up to 500 results
Do not retry failed requests automatically — retry logic belongs in the BLoC/Notifier layer
security requirements
orgId parameter must not be empty string — throw ArgumentError before RPC call if orgId.isEmpty
Do not log latitude/longitude values in production — guard any coordinate logging with kDebugMode
RPC response data is treated as trusted server data but still validated via fromJson() — malformed rows should be skipped with a warning log, not crash the list

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Place at lib/features/map/data/postgis_spatial_adapter.dart. The adapter is a thin translation layer — keep it under 100 lines. The distinction from MentorLocationRepository: the adapter owns the RPC invocation detail (parameter names, response shape), while the repository owns domain logic (consent validation, caching policy). This separation allows swapping the spatial backend (e.g.

to a different RPC or REST endpoint) without touching repository logic. For LatLngBounds from flutter_map/latlong2: bounds.southWest.latitude = min_lat, bounds.southWest.longitude = min_lng, bounds.northEast.latitude = max_lat, bounds.northEast.longitude = max_lng. Define SpatialQueryException as: class SpatialQueryException implements Exception { final String message; final Object? cause; const SpatialQueryException(this.message, {this.cause}); }.

For malformed rows in the response, use a try/catch around fromJson() inside the map() call and log the error with debugPrint in kDebugMode — return null and then whereType() to filter nulls.

Testing Requirements

Unit tests (flutter_test with mocktail): (1) queryMentorsInBounds maps LatLngBounds correctly to RPC params (assert south→min_lat, west→min_lng, north→max_lat, east→max_lng), (2) successful RPC response maps to List, (3) empty RPC response returns empty list, (4) PostgrestException is wrapped in SpatialQueryException, (5) SocketException is wrapped in SpatialQueryException, (6) empty orgId throws ArgumentError before any RPC call. MentorLocationResult.fromJson() unit test: valid JSON, missing nullable fields, type coercion of lat/lng from num to double.

Component
PostGIS Spatial Query Adapter
infrastructure medium
Epic Risks (3)
high impact medium prob integration

Supabase's hosted PostGIS extension behaviour may differ from the local emulator for spatial RPC functions, causing bounding-box queries to return incorrect results or fail in production while passing locally.

Mitigation & Contingency

Mitigation: Write integration tests against the Supabase emulator from the start and run the same test suite against a staging Supabase project before merging. Use ST_DWithin and ST_MakeEnvelope in plain SQL first, validate with psql, then wrap as RPC.

Contingency: If PostGIS RPC proves unreliable, fall back to client-side bounding box filtering on a full fetch of consented mentor locations (acceptable for up to ~200 mentors per chapter) until the spatial query is stabilised.

medium impact low prob dependency

OpenStreetMap tile usage may require attribution handling and rate limiting. Switching to Google Maps Flutter plugin mid-implementation would require significant rework of the map-provider-integration abstraction.

Mitigation & Contingency

Mitigation: Define the map-provider-integration abstraction interface before selecting the SDK so that the concrete implementation is swappable. Implement OSM first with correct attribution. Document Google Maps as the alternate with its API key setup steps.

Contingency: If OSM tiles are rejected by stakeholders or tile server limits are hit, activate the Google Maps Flutter plugin implementation behind the same interface without touching any UI or service code.

high impact low prob security

Incorrect RLS configuration could allow a coordinator to query mentor locations from a different organisation, constituting a GDPR data breach.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests with two isolated test organisations and assert that cross-organisation queries return zero rows. Include these tests in CI. Have a second developer review all RLS policy SQL before migration is applied.

Contingency: If a cross-organisation data leak is discovered post-deployment, immediately disable the map feature via the organisation feature flag, revoke the affected Supabase RLS policy, and notify the data protection officer per the organisation's GDPR incident response procedure.