diff --git a/care/abdm/api/viewsets/consent.py b/care/abdm/api/viewsets/consent.py index eda7b2f47b..7315b1a1ca 100644 --- a/care/abdm/api/viewsets/consent.py +++ b/care/abdm/api/viewsets/consent.py @@ -140,30 +140,6 @@ def fetch(self, request, pk): ConsentRequestSerializer(consent).data, status=status.HTTP_200_OK ) - def list(self, request, *args, **kwargs): - if ratelimit(request, "consent__list", [request.user.username]): - raise CaptchaRequiredException( - detail={ - "status": 429, - "detail": f"Request limit reached. Try after {USER_READABLE_RATE_LIMIT_TIME}", - }, - code=status.HTTP_429_TOO_MANY_REQUESTS, - ) - - return super().list(request, *args, **kwargs) - - def retrieve(self, request, *args, **kwargs): - if ratelimit(request, "consent__retrieve", [kwargs["pk"]]): - raise CaptchaRequiredException( - detail={ - "status": 429, - "detail": f"Request limit reached. Try after {USER_READABLE_RATE_LIMIT_TIME}", - }, - code=status.HTTP_429_TOO_MANY_REQUESTS, - ) - - return super().retrieve(request, *args, **kwargs) - class ConsentCallbackViewSet(GenericViewSet): permission_classes = (IsAuthenticated,) diff --git a/care/abdm/api/viewsets/health_information.py b/care/abdm/api/viewsets/health_information.py index f6233b026e..7f28b84945 100644 --- a/care/abdm/api/viewsets/health_information.py +++ b/care/abdm/api/viewsets/health_information.py @@ -23,15 +23,6 @@ class HealthInformationViewSet(GenericViewSet): permission_classes = (IsAuthenticated,) def retrieve(self, request, pk): - if ratelimit(request, "health_information__retrieve", [pk]): - raise CaptchaRequiredException( - detail={ - "status": 429, - "detail": f"Request limit reached. Try after {USER_READABLE_RATE_LIMIT_TIME}", - }, - code=status.HTTP_429_TOO_MANY_REQUESTS, - ) - files = FileUpload.objects.filter( Q(internal_name=f"{pk}.json") | Q(associating_id=pk), file_type=FileUpload.FileType.ABDM_HEALTH_INFORMATION.value, diff --git a/care/facility/api/viewsets/bed.py b/care/facility/api/viewsets/bed.py index cdda8af3c1..2f994f108d 100644 --- a/care/facility/api/viewsets/bed.py +++ b/care/facility/api/viewsets/bed.py @@ -38,6 +38,21 @@ class BedFilter(filters.FilterSet): facility = filters.UUIDFilter(field_name="facility__external_id") location = filters.UUIDFilter(field_name="location__external_id") bed_type = CareChoiceFilter(choice_dict=inverse_bed_type) + not_occupied_by_asset_type = filters.CharFilter( + method="filter_bed_is_not_occupied_by_asset_type" + ) + + def filter_bed_is_not_occupied_by_asset_type(self, queryset, name, value): + if value: + return queryset.filter( + ~Exists( + AssetBed.objects.filter( + bed__id=OuterRef("id"), + asset__asset_class=value, + ) + ) + ) + return queryset class BedViewSet( diff --git a/care/facility/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index 0b8e9000a8..a79360d76c 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -602,55 +602,6 @@ def transfer(self, request, *args, **kwargs): return Response(data=response_serializer.data, status=status.HTTP_200_OK) -class FacilityDischargedPatientFilterSet(filters.FilterSet): - disease_status = CareChoiceFilter(choice_dict=DISEASE_STATUS_DICT) - phone_number = filters.CharFilter(field_name="phone_number") - emergency_phone_number = filters.CharFilter(field_name="emergency_phone_number") - name = filters.CharFilter(field_name="name", lookup_expr="icontains") - 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") - created_date = filters.DateFromToRangeFilter(field_name="created_date") - modified_date = filters.DateFromToRangeFilter(field_name="modified_date") - srf_id = filters.CharFilter(field_name="srf_id") - is_declared_positive = filters.BooleanFilter(field_name="is_declared_positive") - date_declared_positive = filters.DateFromToRangeFilter( - field_name="date_declared_positive" - ) - date_of_result = filters.DateFromToRangeFilter(field_name="date_of_result") - last_vaccinated_date = filters.DateFromToRangeFilter( - field_name="last_vaccinated_date" - ) - is_antenatal = filters.BooleanFilter(field_name="is_antenatal") - last_menstruation_start_date = filters.DateFromToRangeFilter( - field_name="last_menstruation_start_date" - ) - date_of_delivery = filters.DateFromToRangeFilter(field_name="date_of_delivery") - # Location Based Filtering - district = filters.NumberFilter(field_name="district__id") - district_name = filters.CharFilter( - field_name="district__name", lookup_expr="icontains" - ) - local_body = filters.NumberFilter(field_name="local_body__id") - local_body_name = filters.CharFilter( - field_name="local_body__name", lookup_expr="icontains" - ) - state = filters.NumberFilter(field_name="state__id") - state_name = filters.CharFilter(field_name="state__name", lookup_expr="icontains") - # Vaccination Filters - covin_id = filters.CharFilter(field_name="covin_id") - is_vaccinated = filters.BooleanFilter(field_name="is_vaccinated") - number_of_doses = filters.NumberFilter(field_name="number_of_doses") - last_consultation__new_discharge_reason = filters.ChoiceFilter( - field_name="last_consultation__new_discharge_reason", - choices=NewDischargeReasonEnum.choices, - ) - last_consultation_discharge_date = filters.DateFromToRangeFilter( - field_name="last_consultation__discharge_date" - ) - - @extend_schema_view(tags=["patient"]) class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): permission_classes = (IsAuthenticated, DRYPermissions) @@ -661,7 +612,7 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): rest_framework_filters.OrderingFilter, PatientCustomOrderingFilter, ) - filterset_class = FacilityDischargedPatientFilterSet + filterset_class = PatientFilterSet queryset = ( PatientRegistration.objects.select_related( "local_body", @@ -714,11 +665,25 @@ class FacilityDischargedPatientViewSet(GenericViewSet, mixins.ListModelMixin): ) ) + date_range_fields = [ + "created_date", + "modified_date", + "date_declared_positive", + "date_of_result", + "last_vaccinated_date", + "last_consultation_encounter_date", + "last_consultation_discharge_date", + "last_consultation_symptoms_onset_date", + ] + ordering_fields = [ "id", "name", "created_date", "modified_date", + "review_time", + "last_consultation__current_bed__bed__name", + "date_declared_positive", ] def get_queryset(self) -> QuerySet: diff --git a/care/facility/management/commands/load_event_types.py b/care/facility/management/commands/load_event_types.py index 06a82709ca..f0cc50d186 100644 --- a/care/facility/management/commands/load_event_types.py +++ b/care/facility/management/commands/load_event_types.py @@ -62,9 +62,14 @@ class Command(BaseCommand): "fields": ("course_in_facility",), }, { - "name": "TREATING_PHYSICIAN", - "fields": ("treating_physician",), + "name": "INVESTIGATION", + "fields": ("investigation",), }, + # disabling until we have a better way to serialize user objects + # { + # "name": "TREATING_PHYSICIAN", + # "fields": ("treating_physician",), + # }, ), }, { @@ -240,6 +245,7 @@ class Command(BaseCommand): "RESPIRATORY", "INTAKE_OUTPUT", "VENTILATOR_MODES", + "TREATING_PHYSICIAN", ) def create_objects( diff --git a/care/facility/migrations/0437_alter_dailyround_rounds_type.py b/care/facility/migrations/0437_alter_dailyround_rounds_type.py new file mode 100644 index 0000000000..95ed6611d2 --- /dev/null +++ b/care/facility/migrations/0437_alter_dailyround_rounds_type.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.8 on 2024-05-17 04:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0436_remove_dailyround_temperature_measured_at"), + ] + + operations = [ + migrations.AlterField( + model_name="dailyround", + name="rounds_type", + field=models.IntegerField( + choices=[ + (0, "NORMAL"), + (50, "DOCTORS_LOG"), + (100, "VENTILATOR"), + (200, "ICU"), + (300, "AUTOMATED"), + (400, "TELEMEDICINE"), + ], + default=0, + ), + ), + ] diff --git a/care/facility/migrations/0438_alter_dailyround_patient_category_and_more.py b/care/facility/migrations/0438_alter_dailyround_patient_category_and_more.py new file mode 100644 index 0000000000..021802db0b --- /dev/null +++ b/care/facility/migrations/0438_alter_dailyround_patient_category_and_more.py @@ -0,0 +1,58 @@ +# Generated by Django 4.2.10 on 2024-05-21 12:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + def rename_categories_in_events(apps, schema_editor): + PatientConsultationEvent = apps.get_model( + "facility", "PatientConsultationEvent" + ) + + PatientConsultationEvent.objects.filter( + event_type__name="CATEGORY", value__category="Stable" + ).update(value={"category": "Mild"}) + PatientConsultationEvent.objects.filter( + event_type__name="PATIENT_CATEGORY", value__category="Stable" + ).update(value={"patient_category": "Mild"}) + PatientConsultationEvent.objects.filter( + event_type__name="CATEGORY", value__category="Abnormal" + ).update(value={"category": "Moderate"}) + PatientConsultationEvent.objects.filter( + event_type__name="PATIENT_CATEGORY", value__category="Abnormal" + ).update(value={"patient_category": "Moderate"}) + + dependencies = [ + ("facility", "0437_alter_dailyround_rounds_type"), + ] + + operations = [ + migrations.AlterField( + model_name="dailyround", + name="patient_category", + field=models.CharField( + choices=[ + ("Comfort", "Comfort Care"), + ("Stable", "Mild"), + ("Moderate", "Moderate"), + ("Critical", "Critical"), + ], + max_length=8, + null=True, + ), + ), + migrations.AlterField( + model_name="patientconsultation", + name="category", + field=models.CharField( + choices=[ + ("Comfort", "Comfort Care"), + ("Stable", "Mild"), + ("Moderate", "Moderate"), + ("Critical", "Critical"), + ], + max_length=8, + null=True, + ), + ), + ] diff --git a/care/facility/models/daily_round.py b/care/facility/models/daily_round.py index 7958bc9b39..afb05147fb 100644 --- a/care/facility/models/daily_round.py +++ b/care/facility/models/daily_round.py @@ -34,6 +34,7 @@ class DailyRound(PatientBaseModel): class RoundsType(enum.Enum): NORMAL = 0 + DOCTORS_LOG = 50 VENTILATOR = 100 ICU = 200 AUTOMATED = 300 diff --git a/care/facility/models/patient_base.py b/care/facility/models/patient_base.py index 73bbcfaacb..976b38d320 100644 --- a/care/facility/models/patient_base.py +++ b/care/facility/models/patient_base.py @@ -70,8 +70,8 @@ def reverse_choices(choices): CATEGORY_CHOICES = [ ("Comfort", "Comfort Care"), - ("Stable", "Stable"), - ("Moderate", "Abnormal"), + ("Stable", "Mild"), + ("Moderate", "Moderate"), ("Critical", "Critical"), ] diff --git a/care/facility/tasks/asset_monitor.py b/care/facility/tasks/asset_monitor.py index ef095c1c71..df302b399e 100644 --- a/care/facility/tasks/asset_monitor.py +++ b/care/facility/tasks/asset_monitor.py @@ -69,8 +69,8 @@ def check_asset_status(): ) else: result = asset_class.api_get(asset_class.get_url("devices/status")) - except Exception: - logger.warn(f"Middleware {resolved_middleware} is down", exc_info=True) + except Exception as e: + logger.warn(f"Middleware {resolved_middleware} is down", e) # If no status is returned, setting default status as down if not result or "error" in result: @@ -116,5 +116,5 @@ def check_asset_status(): status=new_status.value, timestamp=status_record.get("time", timezone.now()), ) - except Exception: - logger.error("Error in Asset Status Check", exc_info=True) + except Exception as e: + logger.error("Error in Asset Status Check", e) diff --git a/care/facility/tasks/location_monitor.py b/care/facility/tasks/location_monitor.py index e3ff104187..de89d5b451 100644 --- a/care/facility/tasks/location_monitor.py +++ b/care/facility/tasks/location_monitor.py @@ -53,8 +53,8 @@ def check_location_status(): if result: new_status = AvailabilityStatus.OPERATIONAL - except Exception: - logger.warn(f"Middleware {resolved_middleware} is down", exc_info=True) + except Exception as e: + logger.warn(f"Middleware {resolved_middleware} is down", e) # Fetching the last record of the location last_record = ( @@ -75,5 +75,5 @@ def check_location_status(): timestamp=timezone.now(), ) logger.info(f"Location {location.external_id} status: {new_status.value}") - except Exception: - logger.error("Error in Location Status Check", exc_info=True) + except Exception as e: + logger.error("Error in Location Status Check", e) diff --git a/care/facility/tests/test_bed_api.py b/care/facility/tests/test_bed_api.py index 973db41a96..92ea35dae4 100644 --- a/care/facility/tests/test_bed_api.py +++ b/care/facility/tests/test_bed_api.py @@ -2,6 +2,8 @@ from rest_framework.test import APITestCase from care.facility.models import Bed +from care.facility.models.bed import AssetBed +from care.utils.assetintegration.asset_classes import AssetClasses from care.utils.tests.test_utils import TestUtils @@ -36,3 +38,29 @@ def test_list_beds(self): with self.assertNumQueries(5): response = self.client.get("/api/v1/bed/") self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_list_non_occupied_beds(self): + linked_bed = Bed.objects.create( + name="linked_bed", + location=self.asset_location, + facility=self.facility, + ) + asset = self.create_asset( + self.asset_location, asset_class=AssetClasses.HL7MONITOR.name + ) + AssetBed.objects.create(bed=linked_bed, asset=asset) + + # 4 beds 1 linked with HL7MONITOR and 3 created in setup + + response = self.client.get("/api/v1/bed/") + + # Assert list returns 4 beds + self.assertEqual(response.json()["count"], 4) + + response_with_not_occupied_bed = self.client.get( + "/api/v1/bed/", + {"not_occupied_by_asset_type": "HL7MONITOR"}, + ) + + # Assert count of unoccupied beds is 3 + self.assertEqual(response_with_not_occupied_bed.json()["count"], 3) diff --git a/care/facility/tests/test_patient_daily_rounds_api.py b/care/facility/tests/test_patient_daily_rounds_api.py index 7c5686275a..06195fecb0 100644 --- a/care/facility/tests/test_patient_daily_rounds_api.py +++ b/care/facility/tests/test_patient_daily_rounds_api.py @@ -95,3 +95,10 @@ def test_log_update_without_bed_for_domiciliary( format="json", ) self.assertEqual(response.status_code, status.HTTP_201_CREATED) + + def test_doctors_log_update(self): + response = self.client.post( + f"/api/v1/consultation/{self.consultation_with_bed.external_id}/daily_rounds/", + data={**self.log_update, "rounds_type": "DOCTORS_LOG"}, + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED)