diff --git a/care/facility/api/viewsets/icd.py b/care/facility/api/viewsets/icd.py index 9d1f722886..8551774943 100644 --- a/care/facility/api/viewsets/icd.py +++ b/care/facility/api/viewsets/icd.py @@ -1,9 +1,10 @@ +from django.http import Http404 from redis_om import FindQuery from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ViewSet -from care.facility.static_data.icd11 import ICD11 +from care.facility.static_data.icd11 import ICD11, get_icd11_diagnosis_object_by_id from care.utils.static_data.helpers import query_builder @@ -13,6 +14,12 @@ class ICDViewSet(ViewSet): def serialize_data(self, objects: list[ICD11]): return [diagnosis.get_representation() for diagnosis in objects] + def retrieve(self, request, pk): + obj = get_icd11_diagnosis_object_by_id(pk, as_dict=True) + if not obj: + raise Http404 + return Response(obj) + def list(self, request): try: limit = min(int(request.query_params.get("limit")), 20) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 73b2563a48..46cceb621c 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -48,6 +48,10 @@ ) from care.facility.models.base import covert_choice_dict from care.facility.models.bed import AssetBed +from care.facility.models.icd11_diagnosis import ( + INACTIVE_CONDITION_VERIFICATION_STATUSES, + ConditionVerificationStatus, +) from care.facility.models.notification import Notification from care.facility.models.patient_base import ( DISEASE_STATUS_DICT, @@ -210,6 +214,33 @@ def filter_bed_not_null(self, queryset, name, value): last_consultation__discharge_date__isnull=True, ) + # Filter consultations by ICD-11 Diagnoses + diagnoses = MultiSelectFilter(method="filter_by_diagnoses") + diagnoses_unconfirmed = MultiSelectFilter(method="filter_by_diagnoses") + diagnoses_provisional = MultiSelectFilter(method="filter_by_diagnoses") + diagnoses_differential = MultiSelectFilter(method="filter_by_diagnoses") + diagnoses_confirmed = MultiSelectFilter(method="filter_by_diagnoses") + + def filter_by_diagnoses(self, queryset, name, value): + if not value: + return queryset + filter_q = Q(last_consultation__diagnoses__diagnosis_id__in=value.split(",")) + if name == "diagnoses": + filter_q &= ~Q( + last_consultation__diagnoses__verification_status__in=INACTIVE_CONDITION_VERIFICATION_STATUSES + ) + else: + verification_status = { + "diagnoses_unconfirmed": ConditionVerificationStatus.UNCONFIRMED, + "diagnoses_provisional": ConditionVerificationStatus.PROVISIONAL, + "diagnoses_differential": ConditionVerificationStatus.DIFFERENTIAL, + "diagnoses_confirmed": ConditionVerificationStatus.CONFIRMED, + }[name] + filter_q &= Q( + last_consultation__diagnoses__verification_status=verification_status + ) + return queryset.filter(filter_q) + class PatientDRYFilter(DRYPermissionFiltersBase): def filter_queryset(self, request, queryset, view): diff --git a/care/facility/tests/test_icd11_search.py b/care/facility/tests/test_icd11_api.py similarity index 80% rename from care/facility/tests/test_icd11_search.py rename to care/facility/tests/test_icd11_api.py index 582eac2fb5..69bacc7029 100644 --- a/care/facility/tests/test_icd11_search.py +++ b/care/facility/tests/test_icd11_api.py @@ -1,3 +1,4 @@ +from rest_framework import status from rest_framework.test import APITestCase from care.utils.tests.test_utils import TestUtils @@ -40,3 +41,13 @@ def test_search_with_disease_code(self): res = self.search_icd11("1A00 Cholera") self.assertContains(res, "1A00 Cholera") + + def test_get_icd11_by_valid_id(self): + res = self.client.get("/api/v1/icd/133207228/") + self.assertEqual( + res.data["label"], "CA22 Chronic obstructive pulmonary disease" + ) + + def test_get_icd11_by_invalid_id(self): + res = self.client.get("/api/v1/icd/invalid/") + self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 0167300fb4..633f5ede75 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -4,6 +4,10 @@ from rest_framework import status from rest_framework.test import APITestCase +from care.facility.models.icd11_diagnosis import ( + ConditionVerificationStatus, + ICD11Diagnosis, +) from care.facility.models.patient_base import NewDischargeReasonEnum from care.utils.tests.test_utils import TestUtils @@ -244,10 +248,34 @@ def setUpTestData(cls): cls.consultation.save() cls.patient.last_consultation = cls.consultation cls.patient.save() + cls.diagnoses = ICD11Diagnosis.objects.filter(is_leaf=True)[10:15] + cls.create_consultation_diagnosis( + cls.consultation, + cls.diagnoses[0], + verification_status=ConditionVerificationStatus.CONFIRMED, + ) + cls.create_consultation_diagnosis( + cls.consultation, + cls.diagnoses[1], + verification_status=ConditionVerificationStatus.DIFFERENTIAL, + ) + cls.create_consultation_diagnosis( + cls.consultation, + cls.diagnoses[2], + verification_status=ConditionVerificationStatus.PROVISIONAL, + ) + cls.create_consultation_diagnosis( + cls.consultation, + cls.diagnoses[3], + verification_status=ConditionVerificationStatus.UNCONFIRMED, + ) + + def get_base_url(self) -> str: + return "/api/v1/patient/" def test_filter_by_patient_no(self): self.client.force_authenticate(user=self.user) - response = self.client.get("/api/v1/patient/?patient_no=IP5678") + response = self.client.get(self.get_base_url(), {"patient_no": "IP5678"}) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["count"], 1) self.assertEqual( @@ -257,7 +285,11 @@ def test_filter_by_patient_no(self): def test_filter_by_location(self): self.client.force_authenticate(user=self.user) response = self.client.get( - f"/api/v1/patient/?facility={self.facility.external_id}&location={self.location.external_id}" + self.get_base_url(), + { + "facility": self.facility.external_id, + "location": self.location.external_id, + }, ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["count"], 1) @@ -265,6 +297,64 @@ def test_filter_by_location(self): response.data["results"][0]["id"], str(self.patient.external_id) ) + def test_filter_by_diagnoses(self): + self.client.force_authenticate(user=self.user) + res = self.client.get( + self.get_base_url(), + {"diagnoses": ",".join([str(x.id) for x in self.diagnoses])}, + ) + self.assertContains(res, self.patient.external_id) + res = self.client.get(self.get_base_url(), {"diagnoses": self.diagnoses[4].id}) + self.assertNotContains(res, self.patient.external_id) + + def test_filter_by_diagnoses_unconfirmed(self): + self.client.force_authenticate(user=self.user) + res = self.client.get( + self.get_base_url(), + {"diagnoses_unconfirmed": self.diagnoses[3].id}, + ) + self.assertContains(res, self.patient.external_id) + res = self.client.get( + self.get_base_url(), {"diagnoses_unconfirmed": self.diagnoses[2].id} + ) + self.assertNotContains(res, self.patient.external_id) + + def test_filter_by_diagnoses_provisional(self): + self.client.force_authenticate(user=self.user) + res = self.client.get( + self.get_base_url(), + {"diagnoses_provisional": self.diagnoses[2].id}, + ) + self.assertContains(res, self.patient.external_id) + res = self.client.get( + self.get_base_url(), {"diagnoses_provisional": self.diagnoses[3].id} + ) + self.assertNotContains(res, self.patient.external_id) + + def test_filter_by_diagnoses_differential(self): + self.client.force_authenticate(user=self.user) + res = self.client.get( + self.get_base_url(), + {"diagnoses_differential": self.diagnoses[1].id}, + ) + self.assertContains(res, self.patient.external_id) + res = self.client.get( + self.get_base_url(), {"diagnoses_differential": self.diagnoses[0].id} + ) + self.assertNotContains(res, self.patient.external_id) + + def test_filter_by_diagnoses_confirmed(self): + self.client.force_authenticate(user=self.user) + res = self.client.get( + self.get_base_url(), + {"diagnoses_confirmed": self.diagnoses[0].id}, + ) + self.assertContains(res, self.patient.external_id) + res = self.client.get( + self.get_base_url(), {"diagnoses_confirmed": self.diagnoses[2].id} + ) + self.assertNotContains(res, self.patient.external_id) + class PatientTransferTestCase(TestUtils, APITestCase): @classmethod diff --git a/care/utils/tests/test_utils.py b/care/utils/tests/test_utils.py index 4110f70631..55140dfc4e 100644 --- a/care/utils/tests/test_utils.py +++ b/care/utils/tests/test_utils.py @@ -26,6 +26,11 @@ from care.facility.models.asset import Asset, AssetLocation from care.facility.models.bed import Bed, ConsultationBed from care.facility.models.facility import FacilityUser +from care.facility.models.icd11_diagnosis import ( + ConditionVerificationStatus, + ConsultationDiagnosis, + ICD11Diagnosis, +) from care.users.models import District, State @@ -389,6 +394,22 @@ def create_consultation_bed( data.update(kwargs) return ConsultationBed.objects.create(**data) + @classmethod + def create_consultation_diagnosis( + cls, + consultation: PatientConsultation, + diagnosis: ICD11Diagnosis, + verification_status: ConditionVerificationStatus, + **kwargs, + ): + data = { + "consultation": consultation, + "diagnosis": diagnosis, + "verification_status": verification_status, + } + data.update(kwargs) + return ConsultationDiagnosis.objects.create(**data) + @classmethod def clone_object(cls, obj, save=True): new_obj = obj._meta.model.objects.get(pk=obj.id)