From 39e2c855c227157e6a0b177e62d09a2a829ce89c Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 17 Jan 2024 16:36:35 +0530 Subject: [PATCH 1/6] Adds support for retrieving ICD11 Diagnosis by ID --- care/facility/api/viewsets/icd.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/care/facility/api/viewsets/icd.py b/care/facility/api/viewsets/icd.py index 8bcfc0d25b..8a42df5268 100644 --- a/care/facility/api/viewsets/icd.py +++ b/care/facility/api/viewsets/icd.py @@ -1,5 +1,6 @@ from re import IGNORECASE +from django.http import Http404 from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.viewsets import ViewSet @@ -29,3 +30,11 @@ def list(self, request): label=queryset.re_match(r".*" + query + r".*", IGNORECASE) ) # can accept regex from FE if needed. return Response(serailize_data(queryset[0:100])) + + def retrieve(self, request, pk): + from care.facility.static_data.icd11 import get_icd11_diagnosis_object_by_id + + obj = get_icd11_diagnosis_object_by_id(pk, as_dict=True) + if not obj: + raise Http404 + return Response(obj) From 5e7374b829c94f076dfe651adbf2cc3ad010135c Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 17 Jan 2024 16:37:19 +0530 Subject: [PATCH 2/6] Adds support for filtering patients by diagnoses of last consultation --- care/facility/api/viewsets/patient.py | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 73b2563a48..839974dac4 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,37 @@ 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 + + diagnosis_ids = value.split(",") + queryset = queryset.filter( + last_consultation__diagnoses__diagnosis_id__in=diagnosis_ids + ) + + if name == "diagnoses": + return queryset.exclude( + last_consultation__diagnoses__verification_status__in=INACTIVE_CONDITION_VERIFICATION_STATUSES + ) + + verification_status = { + "diagnoses_unconfirmed": ConditionVerificationStatus.UNCONFIRMED, + "diagnoses_provisional": ConditionVerificationStatus.PROVISIONAL, + "diagnoses_differential": ConditionVerificationStatus.DIFFERENTIAL, + "diagnoses_confirmed": ConditionVerificationStatus.CONFIRMED, + }[name] + return queryset.filter( + last_consultation__diagnoses__verification_status=verification_status + ) + class PatientDRYFilter(DRYPermissionFiltersBase): def filter_queryset(self, request, queryset, view): From 36e2d69a102af4701d57fbf293ef4a986400b71b Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 17 Jan 2024 17:19:17 +0530 Subject: [PATCH 3/6] Tests for ICD11 retrieve by ID API --- .../tests/{test_icd11_search.py => test_icd11_api.py} | 11 +++++++++++ 1 file changed, 11 insertions(+) rename care/facility/tests/{test_icd11_search.py => test_icd11_api.py} (81%) diff --git a/care/facility/tests/test_icd11_search.py b/care/facility/tests/test_icd11_api.py similarity index 81% rename from care/facility/tests/test_icd11_search.py rename to care/facility/tests/test_icd11_api.py index 933ed1b74e..b2d6ecee56 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) From f7d8c81536f923cef17e1abd0f96dbb8973d8ab6 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 17 Jan 2024 17:22:46 +0530 Subject: [PATCH 4/6] Refactor PatientFilterTestCase to use base URL --- care/facility/tests/test_patient_api.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 0167300fb4..f4512c1ed3 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -245,9 +245,12 @@ def setUpTestData(cls): cls.patient.last_consultation = cls.consultation cls.patient.save() + 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 +260,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) From 505cbfd8dc79bf5e06ab29f2be5a07db74909ba2 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Wed, 17 Jan 2024 18:53:07 +0530 Subject: [PATCH 5/6] Tests for filter patients by Consultation Diagnoses --- care/facility/api/viewsets/patient.py | 30 ++++----- care/facility/tests/test_patient_api.py | 83 +++++++++++++++++++++++++ care/utils/tests/test_utils.py | 21 +++++++ 3 files changed, 117 insertions(+), 17 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 839974dac4..46cceb621c 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -224,26 +224,22 @@ def filter_bed_not_null(self, queryset, name, value): def filter_by_diagnoses(self, queryset, name, value): if not value: return queryset - - diagnosis_ids = value.split(",") - queryset = queryset.filter( - last_consultation__diagnoses__diagnosis_id__in=diagnosis_ids - ) - + filter_q = Q(last_consultation__diagnoses__diagnosis_id__in=value.split(",")) if name == "diagnoses": - return queryset.exclude( + filter_q &= ~Q( last_consultation__diagnoses__verification_status__in=INACTIVE_CONDITION_VERIFICATION_STATUSES ) - - verification_status = { - "diagnoses_unconfirmed": ConditionVerificationStatus.UNCONFIRMED, - "diagnoses_provisional": ConditionVerificationStatus.PROVISIONAL, - "diagnoses_differential": ConditionVerificationStatus.DIFFERENTIAL, - "diagnoses_confirmed": ConditionVerificationStatus.CONFIRMED, - }[name] - return queryset.filter( - last_consultation__diagnoses__verification_status=verification_status - ) + 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): diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index f4512c1ed3..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,6 +248,27 @@ 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/" @@ -272,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) From 7ea5f0497af246c74d147bd8dcdeeb83153675ce Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 18 Jan 2024 11:36:49 +0530 Subject: [PATCH 6/6] update imports --- care/facility/api/viewsets/icd.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/care/facility/api/viewsets/icd.py b/care/facility/api/viewsets/icd.py index 40c24affb9..8551774943 100644 --- a/care/facility/api/viewsets/icd.py +++ b/care/facility/api/viewsets/icd.py @@ -4,7 +4,7 @@ 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 @@ -15,8 +15,6 @@ def serialize_data(self, objects: list[ICD11]): return [diagnosis.get_representation() for diagnosis in objects] def retrieve(self, request, pk): - from care.facility.static_data.icd11 import get_icd11_diagnosis_object_by_id - obj = get_icd11_diagnosis_object_by_id(pk, as_dict=True) if not obj: raise Http404