From 7ae7872b9df0f610a8948918ea46b82cf275130a Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 28 May 2024 20:25:30 +0530 Subject: [PATCH 01/15] fixes admitted-to filter for discharged patients --- care/facility/api/viewsets/patient.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index f61b7ffb0e..3734bfe032 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -7,6 +7,7 @@ from django.db import models from django.db.models import ( Case, + Exists, ExpressionWrapper, F, Func, @@ -67,7 +68,7 @@ ShiftingRequest, ) from care.facility.models.base import covert_choice_dict -from care.facility.models.bed import AssetBed +from care.facility.models.bed import AssetBed, ConsultationBed from care.facility.models.icd11_diagnosis import ( INACTIVE_CONDITION_VERIFICATION_STATUSES, ConditionVerificationStatus, @@ -599,6 +600,28 @@ def transfer(self, request, *args, **kwargs): return Response(data=response_serializer.data, status=status.HTTP_200_OK) +class DischargePatientFilterSet(PatientFilterSet): + def filter_by_bed_type(self, queryset, name, value): + if not value: + return queryset + + values = value.split(",") + filter_q = Q() + + consultation_bed = ConsultationBed.objects.filter( + end_date__isnull=False, + consultation__in=queryset.values("last_consultation"), + ) + + if "None" in values: + filter_q |= ~Exists(consultation_bed) + values.remove("None") + if values: + filter_q |= Exists(consultation_bed.filter(bed__bed_type__in=values)) + + return queryset.filter(filter_q) + + @extend_schema_view(tags=["patient"]) class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): permission_classes = (IsAuthenticated, DRYPermissions) @@ -609,7 +632,7 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): rest_framework_filters.OrderingFilter, PatientCustomOrderingFilter, ) - filterset_class = PatientFilterSet + filterset_class = DischargePatientFilterSet queryset = ( PatientRegistration.objects.select_related( "local_body", From 383dba50de5e6a2bf2f7e3d63ad2c020c67fb610 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 28 May 2024 22:21:21 +0530 Subject: [PATCH 02/15] added test --- care/facility/tests/test_patient_api.py | 67 +++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index bf0af8838d..016a894e62 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -5,6 +5,7 @@ from rest_framework.test import APITestCase from care.facility.models import PatientNoteThreadChoices +from care.facility.models.bed import ConsultationBed from care.facility.models.icd11_diagnosis import ( ConditionVerificationStatus, ICD11Diagnosis, @@ -432,6 +433,72 @@ def test_filter_by_review_missed(self): self.assertIsNone(patient["review_time"]) +class DischargePatientFilterTestCase(PatientFilterTestCase): + def setUp(self): + super().setUp() + + self.consultation.discharge_date = now() + self.consultation.save() + + self.consultation_bed = ConsultationBed.objects.filter( + consultation=self.consultation, + ).first() + + self.consultation_bed.end_date = now() + self.consultation_bed.save() + + def get_base_url(self) -> str: + return ( + "/api/v1/facility/" + + str(self.facility.external_id) + + "/discharged_patients/" + ) + + def test_filter_by_admitted_to_bed(self): + self.client.force_authenticate(user=self.user) + choices = ["1", "2", "6", "7", "None"] + + self.consultation_bed.bed.bed_type = int(choices[1]) + self.consultation_bed.bed.save() + + res = self.client.get( + self.get_base_url(), + {"last_consultation_admitted_bed_type_list": ",".join([choices[1]])}, + ) + + self.assertContains(res, self.patient.external_id) + + res = self.client.get( + self.get_base_url(), + {"last_consultation_admitted_bed_type_list": ",".join(choices[2:3])}, + ) + + self.assertNotContains(res, self.patient.external_id) + + res = self.client.get( + self.get_base_url(), + {"last_consultation_admitted_bed_type_list": ",".join(choices)}, + ) + + self.assertContains(res, self.patient.external_id) + + self.consultation_bed.delete() + + res = self.client.get( + self.get_base_url(), + {"last_consultation_admitted_bed_type_list": ",".join([choices[0]])}, + ) + + self.assertNotContains(res, self.patient.external_id) + + res = self.client.get( + self.get_base_url(), + {"last_consultation_admitted_bed_type_list": ",".join([choices[4]])}, + ) + + self.assertContains(res, self.patient.external_id) + + class PatientTransferTestCase(TestUtils, APITestCase): @classmethod def setUpTestData(cls): From 370332dc08899234e1dce6972c56faa2cf041ea6 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 4 Jun 2024 20:12:21 +0530 Subject: [PATCH 03/15] . --- care/facility/api/viewsets/patient.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 3734bfe032..177b208c87 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -7,7 +7,6 @@ from django.db import models from django.db.models import ( Case, - Exists, ExpressionWrapper, F, Func, @@ -605,21 +604,16 @@ def filter_by_bed_type(self, queryset, name, value): if not value: return queryset - values = value.split(",") - filter_q = Q() - - consultation_bed = ConsultationBed.objects.filter( + last_consultation_bed = ConsultationBed.objects.filter( + consultation=OuterRef("last_consultation"), end_date__isnull=False, - consultation__in=queryset.values("last_consultation"), - ) + ).order_by("-end_date") - if "None" in values: - filter_q |= ~Exists(consultation_bed) - values.remove("None") - if values: - filter_q |= Exists(consultation_bed.filter(bed__bed_type__in=values)) + q = queryset.annotate( + last_consultation__current_bed=Subquery(last_consultation_bed[:1]) + ) - return queryset.filter(filter_q) + return super().filter_by_bed_type(q, name, value) @extend_schema_view(tags=["patient"]) From 3ab9e0948b6f84497905b633ac0e54a5860bbd6e Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 5 Jun 2024 08:56:53 +0530 Subject: [PATCH 04/15] fixed --- care/facility/api/viewsets/patient.py | 29 ++++-- care/facility/tests/test_patient_api.py | 129 +++++++++++++++++++----- 2 files changed, 125 insertions(+), 33 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 177b208c87..18a88161ca 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -604,16 +604,29 @@ def filter_by_bed_type(self, queryset, name, value): if not value: return queryset - last_consultation_bed = ConsultationBed.objects.filter( - consultation=OuterRef("last_consultation"), - end_date__isnull=False, - ).order_by("-end_date") - - q = queryset.annotate( - last_consultation__current_bed=Subquery(last_consultation_bed[:1]) + values = value.split(",") + filter_q = Q() + last_consultation_bed = ( + ConsultationBed.objects.filter(end_date__isnull=False) + .order_by("consultation__patient_id", "-end_date") + .distinct("consultation__patient_id") + .values_list("id", "consultation_id") ) + if "None" in values: + filter_q |= ~Q( + last_consultation__id__in=[x[1] for x in last_consultation_bed] + ) + values.remove("None") - return super().filter_by_bed_type(q, name, value) + if values: + filter_q |= Q( + last_consultation__id__in=ConsultationBed.objects.filter( + id__in=[x[0] for x in last_consultation_bed], + bed__bed_type__in=values, + ).values("consultation_id") + ) + + return queryset.filter(filter_q) @extend_schema_view(tags=["patient"]) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 51dfb3d833..35b4117a37 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -1,11 +1,10 @@ from enum import Enum -from django.utils.timezone import now +from django.utils.timezone import now, timedelta from rest_framework import status from rest_framework.test import APITestCase from care.facility.models import PatientNoteThreadChoices -from care.facility.models.bed import ConsultationBed from care.facility.models.icd11_diagnosis import ( ConditionVerificationStatus, ICD11Diagnosis, @@ -433,19 +432,75 @@ def test_filter_by_review_missed(self): self.assertIsNone(patient["review_time"]) -class DischargePatientFilterTestCase(PatientFilterTestCase): - def setUp(self): - super().setUp() +class DischargePatientFilterTestCase(TestUtils, APITestCase): + @classmethod + def setUpTestData(cls): + cls.state = cls.create_state() + cls.district = cls.create_district(cls.state) + cls.local_body = cls.create_local_body(cls.district) + cls.super_user = cls.create_super_user("su", cls.district) + cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) + cls.location = cls.create_asset_location(cls.facility) + cls.user = cls.create_user("user", cls.district, user_type=15) - self.consultation.discharge_date = now() - self.consultation.save() + cls.iso_bed = cls.create_bed(cls.facility, cls.location, bed_type=1, name="ISO") + cls.icu_bed = cls.create_bed(cls.facility, cls.location, bed_type=2, name="ICU") + cls.oxy_bed = cls.create_bed(cls.facility, cls.location, bed_type=6, name="OXY") + cls.nor_bed = cls.create_bed(cls.facility, cls.location, bed_type=7, name="NOR") - self.consultation_bed = ConsultationBed.objects.filter( - consultation=self.consultation, - ).first() + cls.patient_iso = cls.create_patient(cls.district, cls.facility) + cls.patient_icu = cls.create_patient(cls.district, cls.facility) + cls.patient_oxy = cls.create_patient(cls.district, cls.facility) + cls.patient_nor = cls.create_patient(cls.district, cls.facility) + cls.patient_nb = cls.create_patient(cls.district, cls.facility) - self.consultation_bed.end_date = now() - self.consultation_bed.save() + cls.consultation_iso = cls.create_consultation( + patient=cls.patient_iso, + facility=cls.facility, + discharge_date=now(), + ) + cls.consultation_icu = cls.create_consultation( + patient=cls.patient_icu, + facility=cls.facility, + discharge_date=now(), + ) + cls.consultation_oxy = cls.create_consultation( + patient=cls.patient_oxy, + facility=cls.facility, + discharge_date=now(), + ) + cls.consultation_nor = cls.create_consultation( + patient=cls.patient_nor, + facility=cls.facility, + discharge_date=now(), + ) + + cls.consultation_nb = cls.create_consultation( + patient=cls.patient_nb, + facility=cls.facility, + discharge_date=now(), + ) + + cls.consultation_bed_iso = cls.create_consultation_bed( + cls.consultation_iso, + cls.iso_bed, + end_date=now(), + ) + cls.consultation_bed_icu = cls.create_consultation_bed( + cls.consultation_icu, + cls.icu_bed, + end_date=now(), + ) + cls.consultation_bed_oxy = cls.create_consultation_bed( + cls.consultation_oxy, + cls.oxy_bed, + end_date=now(), + ) + cls.consultation_bed_nor = cls.create_consultation_bed( + cls.consultation_nor, + cls.nor_bed, + end_date=now(), + ) def get_base_url(self) -> str: return ( @@ -458,45 +513,69 @@ def test_filter_by_admitted_to_bed(self): self.client.force_authenticate(user=self.user) choices = ["1", "2", "6", "7", "None"] - self.consultation_bed.bed.bed_type = int(choices[1]) - self.consultation_bed.bed.save() - res = self.client.get( self.get_base_url(), - {"last_consultation_admitted_bed_type_list": ",".join([choices[1]])}, + {"last_consultation_admitted_bed_type_list": ",".join([choices[0]])}, ) - self.assertContains(res, self.patient.external_id) + self.assertContains(res, self.patient_iso.external_id) + self.assertNotContains(res, self.patient_icu.external_id) + self.assertNotContains(res, self.patient_oxy.external_id) + self.assertNotContains(res, self.patient_nor.external_id) + self.assertNotContains(res, self.patient_nb.external_id) res = self.client.get( self.get_base_url(), - {"last_consultation_admitted_bed_type_list": ",".join(choices[2:3])}, + {"last_consultation_admitted_bed_type_list": ",".join(choices[1:3])}, ) - self.assertNotContains(res, self.patient.external_id) + self.assertNotContains(res, self.patient_iso.external_id) + self.assertContains(res, self.patient_icu.external_id) + self.assertContains(res, self.patient_oxy.external_id) + self.assertNotContains(res, self.patient_nor.external_id) + self.assertNotContains(res, self.patient_nb.external_id) res = self.client.get( self.get_base_url(), {"last_consultation_admitted_bed_type_list": ",".join(choices)}, ) - self.assertContains(res, self.patient.external_id) + self.assertContains(res, self.patient_iso.external_id) + self.assertContains(res, self.patient_icu.external_id) + self.assertContains(res, self.patient_oxy.external_id) + self.assertContains(res, self.patient_nor.external_id) + self.assertContains(res, self.patient_nb.external_id) - self.consultation_bed.delete() + res = self.client.get( + self.get_base_url(), + {"last_consultation_admitted_bed_type_list": ",".join(choices[3:])}, + ) + + self.assertNotContains(res, self.patient_iso.external_id) + self.assertNotContains(res, self.patient_icu.external_id) + self.assertNotContains(res, self.patient_oxy.external_id) + self.assertContains(res, self.patient_nor.external_id) + self.assertContains(res, self.patient_nb.external_id) + + def test_admitted_to_bed_after_readmission(self): + self.client.force_authenticate(user=self.user) + self.create_consultation_bed( + self.consultation_icu, self.iso_bed, end_date=now() + timedelta(days=1) + ) res = self.client.get( self.get_base_url(), - {"last_consultation_admitted_bed_type_list": ",".join([choices[0]])}, + {"last_consultation_admitted_bed_type_list": "1"}, ) - self.assertNotContains(res, self.patient.external_id) + self.assertContains(res, self.patient_icu.external_id) res = self.client.get( self.get_base_url(), - {"last_consultation_admitted_bed_type_list": ",".join([choices[4]])}, + {"last_consultation_admitted_bed_type_list": "2"}, ) - self.assertContains(res, self.patient.external_id) + self.assertNotContains(res, self.patient_icu.external_id) class PatientTransferTestCase(TestUtils, APITestCase): From 00f81f0cb36fea9d74b3c0be93da52dbe8e7c6a8 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Fri, 14 Jun 2024 21:44:28 +0530 Subject: [PATCH 05/15] . --- care/facility/api/viewsets/patient.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 18a88161ca..94d16fd841 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -690,6 +690,15 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): output_field=models.IntegerField(), ), ) + .annotate( + last_consultation=Subquery( + PatientConsultation.objects.filter( + patient=OuterRef("pk"), discharge_date__isnull=False + ) + .order_by("-discharge_date") + .values("id")[:1] + ) + ) ) date_range_fields = [ From e663efc5000dd2cb0bb93eff7a2fabf336e9acde Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Sat, 15 Jun 2024 15:50:24 +0530 Subject: [PATCH 06/15] revert --- care/facility/api/viewsets/patient.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 94d16fd841..18a88161ca 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -690,15 +690,6 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): output_field=models.IntegerField(), ), ) - .annotate( - last_consultation=Subquery( - PatientConsultation.objects.filter( - patient=OuterRef("pk"), discharge_date__isnull=False - ) - .order_by("-discharge_date") - .values("id")[:1] - ) - ) ) date_range_fields = [ From 9c6f4e6b598bd4a3c37325353a949ad0796bea70 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Sat, 15 Jun 2024 16:40:51 +0530 Subject: [PATCH 07/15] fixed --- care/facility/admin.py | 3 +- care/facility/api/viewsets/patient.py | 60 +++++++++++++++++---------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/care/facility/admin.py b/care/facility/admin.py index e48c3a6c00..898e79670b 100644 --- a/care/facility/admin.py +++ b/care/facility/admin.py @@ -5,7 +5,7 @@ from care.facility.models.ambulance import Ambulance, AmbulanceDriver from care.facility.models.asset import Asset -from care.facility.models.bed import AssetBed, Bed +from care.facility.models.bed import AssetBed, Bed, ConsultationBed from care.facility.models.file_upload import FileUpload from care.facility.models.patient_consultation import ( PatientConsent, @@ -214,6 +214,7 @@ class FacilityUserAdmin(DjangoQLSearchMixin, admin.ModelAdmin, ExportCsvMixin): admin.site.register(AssetBed) admin.site.register(Asset) admin.site.register(Bed) +admin.site.register(ConsultationBed) admin.site.register(PatientConsent) admin.site.register(FileUpload) admin.site.register(PatientConsultation) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index a38482911b..ba18fed7af 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -98,6 +98,9 @@ class PatientFilterSet(filters.FilterSet): + + last_consultation_field = "last_consultation" + source = filters.ChoiceFilter(choices=PatientRegistration.SourceChoices) disease_status = CareChoiceFilter(choice_dict=DISEASE_STATUS_DICT) facility = filters.UUIDFilter(field_name="facility__external_id") @@ -110,14 +113,14 @@ class PatientFilterSet(filters.FilterSet): allow_transfer = filters.BooleanFilter(field_name="allow_transfer") name = filters.CharFilter(field_name="name", lookup_expr="icontains") patient_no = filters.CharFilter( - field_name="last_consultation__patient_no", lookup_expr="iexact" + field_name=f"{last_consultation_field}__patient_no", lookup_expr="iexact" ) gender = filters.NumberFilter(field_name="gender") age = filters.NumberFilter(field_name="age") age_min = filters.NumberFilter(field_name="age", lookup_expr="gte") age_max = filters.NumberFilter(field_name="age", lookup_expr="lte") deprecated_covid_category = filters.ChoiceFilter( - field_name="last_consultation__deprecated_covid_category", + field_name=f"{last_consultation_field}__deprecated_covid_category", choices=COVID_CATEGORY_CHOICES, ) category = filters.ChoiceFilter( @@ -169,24 +172,24 @@ def filter_by_category(self, queryset, name, value): state = filters.NumberFilter(field_name="state__id") state_name = filters.CharFilter(field_name="state__name", lookup_expr="icontains") # Consultation Fields - is_kasp = filters.BooleanFilter(field_name="last_consultation__is_kasp") + is_kasp = filters.BooleanFilter(field_name=f"{last_consultation_field}__is_kasp") last_consultation_kasp_enabled_date = filters.DateFromToRangeFilter( - field_name="last_consultation__kasp_enabled_date" + field_name=f"{last_consultation_field}__kasp_enabled_date" ) last_consultation_encounter_date = filters.DateFromToRangeFilter( - field_name="last_consultation__encounter_date" + field_name=f"{last_consultation_field}__encounter_date" ) last_consultation_discharge_date = filters.DateFromToRangeFilter( - field_name="last_consultation__discharge_date" + field_name=f"{last_consultation_field}__discharge_date" ) last_consultation_admitted_bed_type_list = MultiSelectFilter( method="filter_by_bed_type", ) last_consultation_medico_legal_case = filters.BooleanFilter( - field_name="last_consultation__medico_legal_case" + field_name=f"{last_consultation_field}__medico_legal_case" ) last_consultation_current_bed__location = filters.UUIDFilter( - field_name="last_consultation__current_bed__bed__location__external_id" + field_name=f"{last_consultation_field}__current_bed__bed__location__external_id" ) def filter_by_bed_type(self, queryset, name, value): @@ -205,21 +208,21 @@ def filter_by_bed_type(self, queryset, name, value): return queryset.filter(filter_q) last_consultation_admitted_bed_type = CareChoiceFilter( - field_name="last_consultation__current_bed__bed__bed_type", + field_name=f"{last_consultation_field}__current_bed__bed__bed_type", choice_dict=REVERSE_BED_TYPES, ) last_consultation__new_discharge_reason = filters.ChoiceFilter( - field_name="last_consultation__new_discharge_reason", + field_name=f"{last_consultation_field}__new_discharge_reason", choices=NewDischargeReasonEnum.choices, ) last_consultation_assigned_to = filters.NumberFilter( - field_name="last_consultation__assigned_to" + field_name=f"{last_consultation_field}__assigned_to" ) last_consultation_is_telemedicine = filters.BooleanFilter( - field_name="last_consultation__is_telemedicine" + field_name=f"{last_consultation_field}__is_telemedicine" ) ventilator_interface = CareChoiceFilter( - field_name="last_consultation__last_daily_round__ventilator_interface", + field_name=f"{last_consultation_field}__last_daily_round__ventilator_interface", choice_dict=VENTILATOR_CHOICES, ) @@ -600,6 +603,9 @@ def transfer(self, request, *args, **kwargs): class DischargePatientFilterSet(PatientFilterSet): + + last_consultation_field = "last_discharge_consultation" + def filter_by_bed_type(self, queryset, name, value): if not value: return queryset @@ -614,13 +620,15 @@ def filter_by_bed_type(self, queryset, name, value): ) if "None" in values: filter_q |= ~Q( - last_consultation__id__in=[x[1] for x in last_consultation_bed] + last_discharge_consultation__id__in=[ + x[1] for x in last_consultation_bed + ] ) values.remove("None") if values: filter_q |= Q( - last_consultation__id__in=ConsultationBed.objects.filter( + last_discharge_consultation__id__in=ConsultationBed.objects.filter( id__in=[x[0] for x in last_consultation_bed], bed__bed_type__in=values, ).values("consultation_id") @@ -641,7 +649,17 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): ) filterset_class = DischargePatientFilterSet queryset = ( - PatientRegistration.objects.select_related( + PatientRegistration.objects.annotate( + last_discharge_consultation__id=Subquery( + PatientConsultation.objects.filter( + patient_id=OuterRef("id"), + discharge_date__isnull=False, + ) + .order_by("-discharge_date") + .values("id")[:1] + ) + ) + .select_related( "local_body", "district", "state", @@ -652,8 +670,6 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): "facility__local_body", "facility__district", "facility__state", - "last_consultation", - "last_consultation__assigned_to", "last_edited", "created_by", ) @@ -698,9 +714,9 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): "date_declared_positive", "date_of_result", "last_vaccinated_date", - "last_consultation_encounter_date", - "last_consultation_discharge_date", - "last_consultation_symptoms_onset_date", + "last_discharge_consultation_encounter_date", + "last_discharge_consultation_discharge_date", + "last_discharge_consultation_symptoms_onset_date", ] ordering_fields = [ @@ -709,7 +725,7 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): "created_date", "modified_date", "review_time", - "last_consultation__current_bed__bed__name", + "last_discharge_consultation__current_bed__bed__name", "date_declared_positive", ] From 25aabbbbae0038ce36e57cadfd70f353023f6cf8 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 2 Jul 2024 20:33:13 +0530 Subject: [PATCH 08/15] Filter permissions --- care/facility/api/viewsets/patient.py | 1 + 1 file changed, 1 insertion(+) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index ba18fed7af..b2f37566c4 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -643,6 +643,7 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): lookup_field = "external_id" serializer_class = PatientListSerializer filter_backends = ( + PatientDRYFilter, filters.DjangoFilterBackend, rest_framework_filters.OrderingFilter, PatientCustomOrderingFilter, From 27d115c831b388d1036e0b123062d82af3bd07f1 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 2 Jul 2024 21:24:48 +0530 Subject: [PATCH 09/15] Fixed tests --- care/facility/tests/test_patient_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 35b4117a37..1ea9a9d2f7 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -510,7 +510,7 @@ def get_base_url(self) -> str: ) def test_filter_by_admitted_to_bed(self): - self.client.force_authenticate(user=self.user) + self.client.force_authenticate(user=self.super_user) choices = ["1", "2", "6", "7", "None"] res = self.client.get( @@ -558,7 +558,7 @@ def test_filter_by_admitted_to_bed(self): self.assertContains(res, self.patient_nb.external_id) def test_admitted_to_bed_after_readmission(self): - self.client.force_authenticate(user=self.user) + self.client.force_authenticate(user=self.super_user) self.create_consultation_bed( self.consultation_icu, self.iso_bed, end_date=now() + timedelta(days=1) ) From 49e7bd6340a83366f39e8f892452bdc659ccbcc3 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 3 Jul 2024 20:48:46 +0530 Subject: [PATCH 10/15] updated tests --- care/facility/tests/test_patient_api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 1ea9a9d2f7..1e1ad6fc7e 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -440,8 +440,10 @@ def setUpTestData(cls): cls.local_body = cls.create_local_body(cls.district) cls.super_user = cls.create_super_user("su", cls.district) cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body) + cls.user = cls.create_user( + "user", cls.district, user_type=15, home_facility=cls.facility + ) cls.location = cls.create_asset_location(cls.facility) - cls.user = cls.create_user("user", cls.district, user_type=15) cls.iso_bed = cls.create_bed(cls.facility, cls.location, bed_type=1, name="ISO") cls.icu_bed = cls.create_bed(cls.facility, cls.location, bed_type=2, name="ICU") @@ -510,7 +512,7 @@ def get_base_url(self) -> str: ) def test_filter_by_admitted_to_bed(self): - self.client.force_authenticate(user=self.super_user) + self.client.force_authenticate(user=self.user) choices = ["1", "2", "6", "7", "None"] res = self.client.get( @@ -558,7 +560,7 @@ def test_filter_by_admitted_to_bed(self): self.assertContains(res, self.patient_nb.external_id) def test_admitted_to_bed_after_readmission(self): - self.client.force_authenticate(user=self.super_user) + self.client.force_authenticate(user=self.user) self.create_consultation_bed( self.consultation_icu, self.iso_bed, end_date=now() + timedelta(days=1) ) From a18cd4099347b7fd89cee40bb2d021a8e7b31d4b Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 9 Jul 2024 02:34:23 +0530 Subject: [PATCH 11/15] optimizations --- care/facility/api/viewsets/patient.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index b2f37566c4..b6b8a04e1b 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -612,26 +612,30 @@ def filter_by_bed_type(self, queryset, name, value): values = value.split(",") filter_q = Q() - last_consultation_bed = ( + last_consultation_bed_ids = ( ConsultationBed.objects.filter(end_date__isnull=False) .order_by("consultation__patient_id", "-end_date") .distinct("consultation__patient_id") - .values_list("id", "consultation_id") ) + if "None" in values: filter_q |= ~Q( - last_discharge_consultation__id__in=[ - x[1] for x in last_consultation_bed - ] + last_discharge_consultation__id__in=Subquery( + last_consultation_bed_ids.values_list("consultation_id", flat=True) + ) ) values.remove("None") - if values: + if type(values) == list and len(values) > 0: filter_q |= Q( - last_discharge_consultation__id__in=ConsultationBed.objects.filter( - id__in=[x[0] for x in last_consultation_bed], - bed__bed_type__in=values, - ).values("consultation_id") + last_discharge_consultation__id__in=Subquery( + ConsultationBed.objects.filter( + id__in=Subquery( + last_consultation_bed_ids.values_list("id", flat=True) + ), + bed__bed_type__in=values, + ).values_list("consultation_id", flat=True) + ) ) return queryset.filter(filter_q) From 7e12e98d0c22305ed734247529447139eba68b91 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 9 Jul 2024 02:46:34 +0530 Subject: [PATCH 12/15] trying to fix lint --- care/facility/api/viewsets/patient.py | 1 - 1 file changed, 1 deletion(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index b6b8a04e1b..ca413ce250 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -603,7 +603,6 @@ def transfer(self, request, *args, **kwargs): class DischargePatientFilterSet(PatientFilterSet): - last_consultation_field = "last_discharge_consultation" def filter_by_bed_type(self, queryset, name, value): From 743d7f8d438eb2ce4e84ff2ce1147b7f4ab565e4 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 9 Jul 2024 02:49:58 +0530 Subject: [PATCH 13/15] trying to fix lint --- care/facility/tests/test_patient_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/care/facility/tests/test_patient_api.py b/care/facility/tests/test_patient_api.py index 1e1ad6fc7e..28582feeab 100644 --- a/care/facility/tests/test_patient_api.py +++ b/care/facility/tests/test_patient_api.py @@ -559,6 +559,8 @@ def test_filter_by_admitted_to_bed(self): self.assertContains(res, self.patient_nor.external_id) self.assertContains(res, self.patient_nb.external_id) + # if patient is readmitted to another bed type, only the latest admission should be considered + def test_admitted_to_bed_after_readmission(self): self.client.force_authenticate(user=self.user) self.create_consultation_bed( From ea9b637bf7ab7eacfa2bd49c71c67cc360a8495f Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Tue, 9 Jul 2024 16:06:03 +0530 Subject: [PATCH 14/15] fix lint error --- care/facility/api/viewsets/patient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index ca413ce250..e7c69b34b7 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -625,7 +625,7 @@ def filter_by_bed_type(self, queryset, name, value): ) values.remove("None") - if type(values) == list and len(values) > 0: + if isinstance(values, list) and len(values) > 0: filter_q |= Q( last_discharge_consultation__id__in=Subquery( ConsultationBed.objects.filter( From 5514ce91dc7dc9a76736bd1435ba3fcbb15e378d Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 17 Sep 2024 12:40:13 +0530 Subject: [PATCH 15/15] added documentation --- care/facility/api/viewsets/patient.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 6422ecc308..98e806fd07 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -639,18 +639,23 @@ def transfer(self, request, *args, **kwargs): class DischargePatientFilterSet(PatientFilterSet): last_consultation_field = "last_discharge_consultation" + # Filters patients by the type of bed they have been assigned to. def filter_by_bed_type(self, queryset, name, value): if not value: return queryset values = value.split(",") filter_q = Q() + + # Get the latest consultation bed records for each patient by ordering by patient_id + # and the end_date of the consultation, then selecting distinct patient entries. last_consultation_bed_ids = ( ConsultationBed.objects.filter(end_date__isnull=False) .order_by("consultation__patient_id", "-end_date") .distinct("consultation__patient_id") ) + # patients whose last consultation did not include a bed if "None" in values: filter_q |= ~Q( last_discharge_consultation__id__in=Subquery( @@ -659,14 +664,15 @@ def filter_by_bed_type(self, queryset, name, value): ) values.remove("None") + # If the values list contains valid bed types, apply the filtering for those bed types. if isinstance(values, list) and len(values) > 0: filter_q |= Q( last_discharge_consultation__id__in=Subquery( ConsultationBed.objects.filter( id__in=Subquery( last_consultation_bed_ids.values_list("id", flat=True) - ), - bed__bed_type__in=values, + ), # Filter by consultation beds that are part of the latest records for each patient. + bed__bed_type__in=values, # Match the bed types from the provided values list. ).values_list("consultation_id", flat=True) ) )