Skip to content

Commit

Permalink
Adds functionality for last_consultation_admitted_bed_type_list in …
Browse files Browse the repository at this point in the history
…discharge patient filters (#2204)

Adds functionality for `last_consultation_admitted_bed_type_list` in discharge patient filters (#2204)

---------

Co-authored-by: Shivank Kacker <shivank@Shivanks-MacBook-Air.local>
Co-authored-by: Aakash Singh <mail@singhaakash.dev>
Co-authored-by: Vignesh Hari <14056798+vigneshhari@users.noreply.github.com>
  • Loading branch information
4 people authored Sep 23, 2024
1 parent e029196 commit c7637ea
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 24 deletions.
3 changes: 2 additions & 1 deletion care/facility/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,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.facility import FacilityHubSpoke
from care.facility.models.file_upload import FileUpload
from care.facility.models.patient_consultation import (
Expand Down Expand Up @@ -237,6 +237,7 @@ class Meta:
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)
100 changes: 78 additions & 22 deletions care/facility/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,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,
Expand Down Expand Up @@ -97,6 +97,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")
Expand All @@ -109,14 +112,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(
Expand Down Expand Up @@ -168,24 +171,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):
Expand All @@ -204,21 +207,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={
label: value for value, label in DailyRound.VentilatorInterfaceType.choices
},
Expand Down Expand Up @@ -633,19 +636,74 @@ def transfer(self, request, *args, **kwargs):
return Response(data=response_serializer.data, status=status.HTTP_200_OK)


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(
last_consultation_bed_ids.values_list("consultation_id", flat=True)
)
)
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)
), # 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)
)
)

return queryset.filter(filter_q)


@extend_schema_view(tags=["patient"])
class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin):
permission_classes = (IsAuthenticated, DRYPermissions)
lookup_field = "external_id"
serializer_class = PatientListSerializer
filter_backends = (
PatientDRYFilter,
filters.DjangoFilterBackend,
rest_framework_filters.OrderingFilter,
PatientCustomOrderingFilter,
)
filterset_class = PatientFilterSet
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",
Expand All @@ -656,8 +714,6 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin):
"facility__local_body",
"facility__district",
"facility__state",
"last_consultation",
"last_consultation__assigned_to",
"last_edited",
"created_by",
)
Expand Down Expand Up @@ -702,9 +758,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 = [
Expand All @@ -713,7 +769,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",
]

Expand Down
152 changes: 151 additions & 1 deletion care/facility/tests/test_patient_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
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

Expand Down Expand Up @@ -728,6 +728,156 @@ def test_filter_by_has_consents(self):
self.assertEqual(res.json()["count"], 3)


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.user = cls.create_user(
"user", cls.district, user_type=15, home_facility=cls.facility
)
cls.location = cls.create_asset_location(cls.facility)

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

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)

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 (
"/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"]

res = self.client.get(
self.get_base_url(),
{"last_consultation_admitted_bed_type_list": ",".join([choices[0]])},
)

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[1:3])},
)

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_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)

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)

# 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(
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": "1"},
)

self.assertContains(res, self.patient_icu.external_id)

res = self.client.get(
self.get_base_url(),
{"last_consultation_admitted_bed_type_list": "2"},
)

self.assertNotContains(res, self.patient_icu.external_id)


class PatientTransferTestCase(TestUtils, APITestCase):
@classmethod
def setUpTestData(cls):
Expand Down

0 comments on commit c7637ea

Please sign in to comment.