Skip to content

Steer-05: Matching Pipeline — Qdrant → Neo4j → PostgreSQL → LLM

Read first: specs/sdd-mvp/06-matching-engine.md, specs/sdd-mvp/04-agent-pipeline.md, specs/sdd-mvp/03-data-architecture.md

Context & Goal

Implement the four-stage matching pipeline with pluggable strategy pattern, shadow mode, and A/B testing. Integrates with EHR Builder for patient data and event system for real-time progress updates.


Backend (curaway-ai/curaway-backend)

Step 1: Stage 0 — Qdrant Semantic Discovery

Create app/services/matching/qdrant_stage.py: - Build patient clinical embedding: concatenate conditions + procedures + preferences as text - Call Voyage AI to generate embedding - Query Qdrant provider_profiles collection for top-K similar providers (K=30) - Return candidate provider IDs with similarity scores

Step 2: Stage 1 — Neo4j Hard Constraint Filtering

Create app/services/matching/neo4j_stage.py: - Execute Cypher query: find providers that have required procedure, relevant accreditation, active visa corridor for patient nationality - Hard pass/fail — no partial scoring - Return filtered candidate list

Step 3: Stage 2 — PostgreSQL Weighted Scoring

Update app/services/matching/weighted_scoring.py: - Input: filtered candidates from Stage 1 - Load patient FHIR resources via EHR Builder - Score each provider across 5 domains with configurable weights (from Flagsmith) - Calculate confidence score based on data completeness - Return ranked list with per-domain score breakdown

Step 4: Stage 3 — LLM Re-Ranking (Feature Flagged)

Update app/agents/match/agent.py: - When agent_enhanced_matching flag enabled: 1. analyze_clinical_picture (Claude Sonnet): review all FHIR data, note comorbidity interactions 2. rerank_edge_cases (Claude Sonnet): review top 5, check contraindications 3. generate_explanations (Claude Haiku): natural language per provider in patient locale - When disabled: skip to template explanations

Step 5: Pipeline Orchestration

Update app/services/matching_engine.py: - execute() runs all 4 stages in sequence - Emits match_progress events at each stage transition (for SSE) - Stores results in match_results table - Emits match_completed event

Step 6: Shadow Mode

Add shadow mode support: - When matching_shadow_strategy flag is set, run shadow strategy in parallel - Log shadow results to events table with shadow: true flag - Only serve primary strategy results to patient - Metabase dashboard compares primary vs shadow scores

Step 7: Tests

  • Test each stage independently with mock data
  • Test full pipeline end-to-end with Maria's profile (clean per-persona account — see docs/runbook/test-data-hygiene.md)
  • Test feature flag: disabled → stages 0+3 skipped
  • Test shadow mode logs but doesn't serve

Backend Verification

python -m pytest tests/test_matching_pipeline.py -v
# Manual: trigger match for a clean persona, verify 4 stages execute.
# (#1194 E4): pass case_id so the matcher only sees this case's FHIR.
curl -X POST "http://localhost:8000/api/v1/patients/demo-patient-maria-001/match?case_id=<case-uuid>" \
  -H "Authorization: Bearer $TOKEN" -H "X-Tenant-ID: tenant-curaway-patients"

Frontend (curaway-ai/curaway-frontend)

Step 1: Match Progress Indicator

Create src/components/match/MatchProgress.tsx: - Shows in conversation thread when matching is active - Stage indicators: Discovery → Filtering → Scoring → AI Analysis - Each stage lights up as SSE match_progress events arrive - Animated transitions between stages

Step 2: Provider Result Cards

Create src/components/match/ProviderMatchCard.tsx: - Provider name, location, accreditations - Total score with confidence indicator (opacity based on data completeness) - Domain score breakdown (horizontal stacked bar or radar) - Cost estimate with currency conversion - Key differentiators (2–3 bullet points) - AI-generated reasoning paragraph (from Explanation Agent) - "Learn More" → navigates to provider detail page

Step 3: Match Results in Conversation

When match_completed SSE event arrives: - Replace progress indicator with result cards - Show top 3–5 providers as scrollable cards in conversation - "See all results" expands to full list - Follow-up prompt: "Would you like to know more about any of these providers?"

Frontend Verification

npm run build
npx tsc --noEmit
# Manual: trigger match, verify progress indicator → result cards transition

Checklist

  • [ ] Alembic migration: match_results table if not exists
  • [ ] Seed data: ensure 14+ TKR-capable providers with varied data completeness
  • [ ] Error codes: MATCH_QDRANT_001, MATCH_NEO4J_001, MATCH_SCORING_001, MATCH_LLM_001
  • [ ] Feature flags: agent_enhanced_matching, matching_shadow_strategy
  • [ ] Env vars: QDRANT_URL, VOYAGE_AI_API_KEY, NEO4J_URI
  • [ ] Swagger: match endpoint response schema documented
  • [ ] Unit tests: per-stage tests + full pipeline
  • [ ] PostHog: match_triggered, match_result_viewed, provider_card_clicked
  • [ ] Loading states: match progress indicator
  • [ ] Mobile: provider cards stack vertically, scrollable
  • [ ] CLAUDE.md: update matching pipeline description
  • [ ] Rollback: disable agent_enhanced_matching → pure deterministic scoring