| 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} |
|
|
|
|
|
|
| |
|
|
| 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 |
|
|
|
|
| |
|
|
| 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" |