high priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

fetchMentorsInBoundingBox always calls MentorFilterService.validate() before the Supabase query is issued
If MentorFilterService.validate() throws a ValidationException, fetchMentorsInBoundingBox propagates it without touching the repository
FilterCriteria.availabilityStatus is translated to a Supabase .eq('status', ...) filter on the query builder
FilterCriteria.specialisationTags (non-empty list) is translated to a Supabase .overlaps('tags', [...]) or .contains filter, as appropriate for the column type
An empty or null FilterCriteria bypasses all filter clauses and returns all mentors in the bounding box
MentorLocationService unit tests mock MentorFilterService and assert it is called exactly once per fetchMentorsInBoundingBox invocation
Integration test confirms that a FilterCriteria with availabilityStatus=paused excludes paused mentors from the returned list
No raw SQL strings are constructed — all filters are appended via the Supabase PostgREST query builder DSL
The method signature of fetchMentorsInBoundingBox remains backward-compatible (FilterCriteria parameter is nullable/optional with a sensible default)

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
Supabase PostgREST REST API
Supabase Dart client query builder
data models
MentorLocation
FilterCriteria
BoundingBox
performance requirements
Filter translation must add less than 1 ms overhead to query construction
No additional network round-trips introduced by filter validation step
Supabase query must apply all active filters server-side to minimise payload size
security requirements
FilterCriteria values must be passed as parameterised query arguments — never string-interpolated into query URLs
Supabase Row Level Security (RLS) policies must remain authoritative; client-side filters are additive, not a bypass
Validated and normalised criteria only — reject criteria containing characters outside the allowed vocabulary before issuing the query

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Inject MentorFilterService into MentorLocationService via constructor to keep the dependency explicit and testable. Define a private _applyFiltersToQuery(SupabaseQueryBuilder q, FilterCriteria? criteria) helper that translates each FilterCriteria dimension to a Supabase builder call. This keeps fetchMentorsInBoundingBox readable and makes each filter dimension independently testable.

Prefer .overlaps() for array columns if mentor tags are stored as a Postgres array; prefer .contains() if stored as JSONB. Confirm column type with the database schema before choosing. Do not call MentorFilterService.validate() inside _applyFiltersToQuery — validation must happen at the entry point of fetchMentorsInBoundingBox so that invalid criteria are rejected before any query builder state is mutated.

Testing Requirements

Unit tests (flutter_test): mock MentorFilterService using Mockito/mocktail and verify it is invoked with the exact FilterCriteria passed by the caller; verify that filter translation produces the correct Supabase query builder chain using a fake QueryBuilder spy. Integration tests: use Supabase local emulator or a dedicated test schema; verify that active/paused/all availability filters return the correct subset of rows; verify bounding-box + tag combination filters. Edge-case tests: null FilterCriteria, empty tag list, all-paused dataset. Minimum 90% branch coverage on MentorLocationService filter pipeline path.

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.