import pytest import numpy as np from unittest.mock import patch from intent_classification.intent_router_ml import route_intent from intent_classification.intent_classification_ml import classify_intent def mock_classify_ready(label="PATIENT_EVIDENCE_QUERY", confidence=0.85): """Returns a mock confident classification result.""" return {"status": "READY", "label": label, "confidence": confidence} def mock_classify_uncertain(label="PATIENT_EVIDENCE_QUERY", confidence=0.40): """Returns a mock uncertain classification result.""" return {"status": "UNCERTAIN", "label": label, "confidence": confidence} #──── route_intent ──────────────────────────────────────────────────────────────────────────────── class TestRouteIntent: def test_confident_classification_returns_route_status(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_ready()): result = route_intent("patient has persistent cough", 1, 1) assert result["status"] == "ROUTE" def test_confident_classification_returns_correct_intent(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_ready()): result = route_intent("patient has persistent cough", 1, 1) assert result["intent"] == "PATIENT_EVIDENCE_QUERY" def test_confident_classification_returns_confidence(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_ready()): result = route_intent("patient has persistent cough", 1, 1) assert result["confidence"] == 0.85 def test_uncertain_classification_returns_needs_clarification(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_uncertain()): result = route_intent("something ambiguous", 1, 1) assert result["status"] == "NEEDS_CLARIFICATION" def test_uncertain_classification_returns_options(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_uncertain()): result = route_intent("something ambiguous", 1, 1) assert "options" in result assert isinstance(result["options"], list) assert len(result["options"]) > 0 def test_uncertain_classification_options_are_strings(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_uncertain()): result = route_intent("something ambiguous", 1, 1) for opt in result["options"]: assert isinstance(opt, str) def test_uncertain_classification_returns_confidence(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_uncertain()): result = route_intent("something ambiguous", 1, 1) assert result["confidence"] == 0.40 def test_clarification_options_contain_expected_labels(self): with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_uncertain()): result = route_intent("something ambiguous", 1, 1) assert "Add new evidence" in result["options"] assert "Ask for explanation" in result["options"] assert "Request source" in result["options"] assert "General help" in result["options"] def test_all_intents_route_correctly(self): intents = [ "PATIENT_EVIDENCE_QUERY", "FOLLOW_UP_EXPLANATION", "SOURCE_REQUEST", "HELP_OR_OTHER" ] for intent in intents: with patch("intent_classification.intent_router_ml.classify_intent", return_value=mock_classify_ready(label=intent)): result = route_intent("some input", 1, 1) assert result["intent"] == intent #────── classify_intent──────────────────────────────────────────────────────────── class TestClassifyIntent: @patch("intent_classification.intent_classification_ml.embedder") @patch("intent_classification.intent_classification_ml.classifier") def test_status_is_ready_when_confident(self, mock_classifier, mock_embedder): mock_embedder.encode.return_value = np.zeros((1, 384)) mock_probs = np.array([0.05, 0.85, 0.05, 0.05]) mock_classifier.predict_proba.return_value = [mock_probs] mock_id_to_label = {0: "HELP_OR_OTHER", 1: "PATIENT_EVIDENCE_QUERY", 2: "FOLLOW_UP_EXPLANATION", 3: "SOURCE_REQUEST"} with patch("intent_classification.intent_classification_ml.id_to_label", mock_id_to_label): result = classify_intent("patient has a fever") assert result["status"] == "READY" @patch("intent_classification.intent_classification_ml.embedder") @patch("intent_classification.intent_classification_ml.classifier") def test_status_is_uncertain_when_low_confidence(self, mock_classifier, mock_embedder): mock_embedder.encode.return_value = np.zeros((1, 384)) mock_probs = np.array([0.30, 0.35, 0.20, 0.15]) mock_classifier.predict_proba.return_value = [mock_probs] mock_id_to_label = {0: "HELP_OR_OTHER", 1: "PATIENT_EVIDENCE_QUERY", 2: "FOLLOW_UP_EXPLANATION", 3: "SOURCE_REQUEST"} with patch("intent_classification.intent_classification_ml.id_to_label", mock_id_to_label): result = classify_intent("patient has a cough") assert result["status"] == "UNCERTAIN" @patch("intent_classification.intent_classification_ml.embedder") @patch("intent_classification.intent_classification_ml.classifier") def test_returns_correct_label(self, mock_classifier, mock_embedder): mock_embedder.encode.return_value = np.zeros((1, 384)) mock_probs = np.array([0.05, 0.05, 0.85, 0.05]) mock_classifier.predict_proba.return_value = [mock_probs] mock_id_to_label = {0: "HELP_OR_OTHER", 1: "PATIENT_EVIDENCE_QUERY", 2: "FOLLOW_UP_EXPLANATION", 3: "SOURCE_REQUEST"} with patch("intent_classification.intent_classification_ml.id_to_label", mock_id_to_label): result = classify_intent("why does smoking cause cancer?") assert result["label"] == "FOLLOW_UP_EXPLANATION" @patch("intent_classification.intent_classification_ml.embedder") @patch("intent_classification.intent_classification_ml.classifier") def test_exactly_at_threshold_is_uncertain(self, mock_classifier, mock_embedder): """Confidence exactly at CONFIDENCE_THRESHOLD (0.55) should be UNCERTAIN since the check is confidence < CONFIDENCE_THRESHOLD.""" mock_embedder.encode.return_value = np.zeros((1, 384)) mock_probs = np.array([0.15, 0.55, 0.15, 0.15]) mock_classifier.predict_proba.return_value = [mock_probs] mock_id_to_label = {0: "HELP_OR_OTHER", 1: "PATIENT_EVIDENCE_QUERY", 2: "FOLLOW_UP_EXPLANATION", 3: "SOURCE_REQUEST"} with patch("intent_classification.intent_classification_ml.id_to_label", mock_id_to_label): result = classify_intent("some borderline threshold input") assert result["status"] == "READY" @patch("intent_classification.intent_classification_ml.embedder") @patch("intent_classification.intent_classification_ml.classifier") def test_below_threshold_is_uncertain(self, mock_classifier, mock_embedder): mock_embedder.encode.return_value = np.zeros((1, 384)) mock_probs = np.array([0.18, 0.54, 0.16, 0.12]) mock_classifier.predict_proba.return_value = [mock_probs] mock_id_to_label = {0: "HELP_OR_OTHER", 1: "PATIENT_EVIDENCE_QUERY", 2: "FOLLOW_UP_EXPLANATION", 3: "SOURCE_REQUEST"} with patch("intent_classification.intent_classification_ml.id_to_label", mock_id_to_label): result = classify_intent("borderline input") assert result["status"] == "UNCERTAIN"