From 01c4aa0eecc7ac997d52e42de24ed63683a70a59 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad <rithvik.nishad@egovernments.org>
Date: Mon, 20 May 2024 21:45:08 +0530
Subject: [PATCH 1/7] Adds support for "Doctors Log Update" round type (#2173)

* Adds support for "Doctors Log Update" round type

* add tests
---
 .../0437_alter_dailyround_rounds_type.py      | 27 +++++++++++++++++++
 care/facility/models/daily_round.py           |  1 +
 .../tests/test_patient_daily_rounds_api.py    |  7 +++++
 3 files changed, 35 insertions(+)
 create mode 100644 care/facility/migrations/0437_alter_dailyround_rounds_type.py

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

From 5c715f86a74844033586b7c970a389d5fa766565 Mon Sep 17 00:00:00 2001
From: Aakash Singh <mail@singhaakash.dev>
Date: Mon, 20 May 2024 22:38:33 +0530
Subject: [PATCH 2/7] Fix: Log only error messages in uptime monitor tasks
 (#2181)

---
 care/facility/tasks/asset_monitor.py    | 8 ++++----
 care/facility/tasks/location_monitor.py | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

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)

From ba986563e8d5de1c3470cf2b5a5b6df1c0cbe191 Mon Sep 17 00:00:00 2001
From: Khavin Shankar <khavinshankar@gmail.com>
Date: Wed, 22 May 2024 13:05:02 +0530
Subject: [PATCH 3/7] Renamed patient category labels (#2187)

* rename patient category labels

* rename the categories in the events

* rename categories for patient_category events
---
 ...er_dailyround_patient_category_and_more.py | 58 +++++++++++++++++++
 care/facility/models/patient_base.py          |  4 +-
 2 files changed, 60 insertions(+), 2 deletions(-)
 create mode 100644 care/facility/migrations/0438_alter_dailyround_patient_category_and_more.py

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/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"),
 ]
 

From 0214e4e6580bf1178fa3225199dac15a4c4eff65 Mon Sep 17 00:00:00 2001
From: Shivank Kacker <kacker.shivank@gmail.com>
Date: Wed, 22 May 2024 13:06:21 +0530
Subject: [PATCH 4/7] Removed Discharge Filters, reused patient filters (#2176)

* Removed Discharge Filters, reused patient filters

* Update patient.py

---------

Co-authored-by: Vignesh Hari <vichuhari100@gmail.com>
---
 care/facility/api/viewsets/patient.py | 65 +++++++--------------------
 1 file changed, 15 insertions(+), 50 deletions(-)

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:

From 5670c81e07763bcc155dc555cf87decf0d257127 Mon Sep 17 00:00:00 2001
From: Ankur Prabhu <85862184+AnkurPrabhu@users.noreply.github.com>
Date: Wed, 22 May 2024 13:07:21 +0530
Subject: [PATCH 5/7] Disable the Bed List of Already occupied Beds (back-end
 change) (#2116)

* backend changes

* adding suggested changes

* adding suggested change

* add suggested changes

* add tets

* Update care/facility/tests/test_bed_api.py

Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com>

* fix lint

---------

Co-authored-by: Aakash Singh <mail@singhaakash.dev>
Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com>
Co-authored-by: rithviknishad <mail@rithviknishad.dev>
Co-authored-by: Vignesh Hari <vichuhari100@gmail.com>
---
 care/facility/api/viewsets/bed.py   | 15 +++++++++++++++
 care/facility/tests/test_bed_api.py | 28 ++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+)

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

From 62c436442918a9fe4c71c44983c720df3699b302 Mon Sep 17 00:00:00 2001
From: Khavin Shankar <khavinshankar@gmail.com>
Date: Wed, 22 May 2024 13:08:18 +0530
Subject: [PATCH 6/7] Remove ratelimit for internal get apis (#2177)

remove ratelimit for internal get apis

Co-authored-by: Khavin Shankar <khavinshankar@Khavins-MacBook-Air.local>
Co-authored-by: Vignesh Hari <vichuhari100@gmail.com>
---
 care/abdm/api/viewsets/consent.py            | 24 --------------------
 care/abdm/api/viewsets/health_information.py |  9 --------
 2 files changed, 33 deletions(-)

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,

From bafac12fe03d8044c4c9c450a16c32e307db144d Mon Sep 17 00:00:00 2001
From: Aakash Singh <mail@singhaakash.dev>
Date: Wed, 22 May 2024 23:12:43 +0530
Subject: [PATCH 7/7] add investigations and disable treating physician from
 consultation events (#2189)

---
 care/facility/management/commands/load_event_types.py | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

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(