critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

MentorFilterService exposes a method buildFilterCriteria(FilterState state) -> FilterCriteria that is pure (no side effects, no async)
When FilterState has no active filters (all defaults), buildFilterCriteria returns a FilterCriteria with all nullable fields null and specialisationTags as empty list — acting as a passthrough
Input validation: if FilterState contains an unrecognised AvailabilityStatus value, the method returns a ValidationFailure (via Either or throws a typed exception — be consistent with the project's error handling pattern)
Specialisation tag validation: empty strings and duplicate tags are stripped before producing FilterCriteria
The method signature accepts only FilterState and optional BoundingBox so it is callable from both the map BLoC and the list fallback BLoC without coupling
MentorFilterService is a plain Dart class with no Flutter imports, injectable via Riverpod or constructor injection
Unit tests cover: all-null/default state produces passthrough criteria, single availability filter, multiple specialisation tags (including deduplication), combined availability + tags + bounding box, invalid availability value produces correct failure
applyFilter(List<MentorLocation> mentors, FilterCriteria criteria) method filters an in-memory list correctly — used by the list fallback view

Technical Requirements

frameworks
Flutter (Dart only — domain layer)
Riverpod (for DI registration)
BLoC (consumed by map and list BLoCs)
data models
FilterCriteria
FilterState
MentorLocation
AvailabilityStatus
performance requirements
applyFilter must process a list of 500 MentorLocation objects in under 10ms on a mid-range device
buildFilterCriteria must be synchronous and return in under 1ms
security requirements
Do not log FilterState contents — specialisation tags may indirectly reveal sensitive disability categories

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

FilterState is the BLoC/UI state type; FilterCriteria is the domain type — keep them separate and let MentorFilterService be the translation boundary. This prevents the domain layer from depending on BLoC. Define FilterState in the presentation layer (or a shared state layer) and FilterCriteria in the domain layer. Use a sealed Either return type if the project uses dartz or fpdart; otherwise use a simple result wrapper class for consistency.

The applyFilter method should use List.where with early-exit conditions ordered from cheapest to most expensive check (availability enum comparison before string contains). Document the tag matching strategy: exact match vs. contains — agree with the team before implementing.

Testing Requirements

Unit tests in test/features/geographic_map/domain/services/mentor_filter_service_test.dart. Test matrix: (1) empty FilterState → passthrough FilterCriteria, (2) availability only, (3) specialisationTags only (single, multiple, with duplicates), (4) boundingBox only, (5) all three combined, (6) invalid/unknown availability value → ValidationFailure, (7) applyFilter with empty mentor list returns empty, (8) applyFilter filters by availability correctly, (9) applyFilter filters by specialisation tag correctly, (10) applyFilter with passthrough criteria returns all mentors unchanged. No async, no mocking required.

Component
Mentor Filter Service
service low
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.