Implement MentorFilterService — core filter logic
epic-geographic-peer-mentor-map-core-services-task-002 — Implement MentorFilterService that translates UI FilterState (availability, specialisation tags) into a validated FilterCriteria object. Include input validation, default passthrough when no filters are active, and a method signature consumable by both the location service and the list fallback view. Write unit tests covering all filter combinations.
Acceptance Criteria
Technical Requirements
Execution Context
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
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.
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.
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.