From cf6902ba60491d3849754c603745072c1b8394d2 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 3 May 2024 00:11:29 +0530 Subject: [PATCH 1/6] Annotate CSV export fields with patient age for sample test and shifting (#2101) * Annotate CSV export fields with patient age for sample test and shifting * use consistent naming --- care/facility/api/viewsets/patient_sample.py | 6 ++- care/facility/api/viewsets/shifting.py | 6 ++- care/facility/models/patient.py | 42 +++++++++++++++++++- care/facility/models/patient_sample.py | 5 +++ care/facility/models/shifting.py | 5 +++ 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/care/facility/api/viewsets/patient_sample.py b/care/facility/api/viewsets/patient_sample.py index 1dfb0ffab1..d6b5f9d3b3 100644 --- a/care/facility/api/viewsets/patient_sample.py +++ b/care/facility/api/viewsets/patient_sample.py @@ -124,8 +124,10 @@ def list(self, request, *args, **kwargs): raise PermissionDenied() if settings.CSV_REQUEST_PARAMETER in request.GET: - queryset = self.filter_queryset(self.get_queryset()).values( - *PatientSample.CSV_MAPPING.keys() + queryset = ( + self.filter_queryset(self.get_queryset()) + .annotate(**PatientSample.CSV_ANNOTATE_FIELDS) + .values(*PatientSample.CSV_MAPPING.keys()) ) return render_to_csv_response( queryset, diff --git a/care/facility/api/viewsets/shifting.py b/care/facility/api/viewsets/shifting.py index 5e62a1766d..3d87f822fd 100644 --- a/care/facility/api/viewsets/shifting.py +++ b/care/facility/api/viewsets/shifting.py @@ -176,8 +176,10 @@ def transfer(self, request, *args, **kwargs): def list(self, request, *args, **kwargs): if settings.CSV_REQUEST_PARAMETER in request.GET: - queryset = self.filter_queryset(self.get_queryset()).values( - *ShiftingRequest.CSV_MAPPING.keys() + queryset = ( + self.filter_queryset(self.get_queryset()) + .annotate(**ShiftingRequest.CSV_ANNOTATE_FIELDS) + .values(*ShiftingRequest.CSV_MAPPING.keys()) ) return render_to_csv_response( queryset, diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index bfefeb4b75..786ff584b7 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -4,7 +4,8 @@ from django.contrib.postgres.aggregates import ArrayAgg from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models -from django.db.models import JSONField +from django.db.models import Case, F, Func, JSONField, Value, When +from django.db.models.functions import Coalesce, Now from django.utils import timezone from simple_history.models import HistoricalRecords @@ -773,3 +774,42 @@ class PatientNotesEdit(models.Model): class Meta: ordering = ["-edited_date"] + + +class PatientAgeFunc(Func): + """ + Expression to calculate the age of a patient based on date of birth/year of + birth and death date time. + + Eg: + + ``` + PatientSample.objects.annotate(patient_age=PatientAgeFunc()) + ``` + """ + + function = "date_part" + + def __init__(self) -> None: + super().__init__( + Value("year"), + Func( + Case( + When(patient__death_datetime__isnull=True, then=Now()), + default=F("patient__death_datetime__date"), + ), + Coalesce( + "patient__date_of_birth", + Func( + F("patient__year_of_birth"), + Value(1), + Value(1), + function="MAKE_DATE", + output_field=models.DateField(), + ), + output_field=models.DateField(), + ), + function="age", + ), + output_field=models.IntegerField(), + ) diff --git a/care/facility/models/patient_sample.py b/care/facility/models/patient_sample.py index 999d435fba..f588ac8599 100644 --- a/care/facility/models/patient_sample.py +++ b/care/facility/models/patient_sample.py @@ -1,6 +1,7 @@ from django.db import models from care.facility.models import FacilityBaseModel, PatientRegistration, reverse_choices +from care.facility.models.patient import PatientAgeFunc from care.users.models import User SAMPLE_TYPE_CHOICES = [ @@ -125,6 +126,10 @@ class PatientSample(FacilityBaseModel): "date_of_result": "Date of Result", } + CSV_ANNOTATE_FIELDS = { + "patient__age": PatientAgeFunc(), + } + CSV_MAKE_PRETTY = { "sample_type": (lambda x: REVERSE_SAMPLE_TYPE_CHOICES.get(x, "-")), "status": ( diff --git a/care/facility/models/shifting.py b/care/facility/models/shifting.py index 815ce51479..2a3dd20315 100644 --- a/care/facility/models/shifting.py +++ b/care/facility/models/shifting.py @@ -3,6 +3,7 @@ from care.facility.models import ( FACILITY_TYPES, FacilityBaseModel, + PatientAgeFunc, pretty_boolean, reverse_choices, ) @@ -137,6 +138,10 @@ class ShiftingRequest(FacilityBaseModel): "reason": "Reason for Shifting", } + CSV_ANNOTATE_FIELDS = { + "patient__age": PatientAgeFunc(), + } + CSV_MAKE_PRETTY = { "status": (lambda x: REVERSE_SHIFTING_STATUS_CHOICES.get(x, "-")), "is_up_shift": pretty_boolean, From 059b8efcd714aa1efd9fe0c5357fe6b48235c9e0 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 8 May 2024 14:20:10 +0530 Subject: [PATCH 2/6] Exact patient no. in filters (#2136) exact patient no for search --- 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 e7c5d618af..02ff220a44 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -109,7 +109,7 @@ 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="icontains" + field_name="last_consultation__patient_no", lookup_expr="iexact" ) gender = filters.NumberFilter(field_name="gender") age = filters.NumberFilter(field_name="age") From 848117569f9f8df0eb13576644152d0ec88b20d2 Mon Sep 17 00:00:00 2001 From: Devdeep Ghosh <63492939+thedevildude@users.noreply.github.com> Date: Wed, 8 May 2024 14:21:07 +0530 Subject: [PATCH 3/6] Serialized gender into UserAssignedSerializer (#2118) serialized gender into UserAssignedSerializer Co-authored-by: Vignesh Hari --- care/users/api/serializers/user.py | 1 + 1 file changed, 1 insertion(+) diff --git a/care/users/api/serializers/user.py b/care/users/api/serializers/user.py index 3530d695bd..9e267ac98e 100644 --- a/care/users/api/serializers/user.py +++ b/care/users/api/serializers/user.py @@ -388,6 +388,7 @@ class Meta: "alt_phone_number", "user_type", "last_login", + "gender", "home_facility_object", "doctor_qualification", "doctor_experience_commenced_on", From 0bf4d1d604df7f170caf1a562fa27b54d6e98222 Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Wed, 8 May 2024 14:21:48 +0530 Subject: [PATCH 4/6] Add medicine filter to prescriptions (#2068) * Log Prescriptions * pass medicine object instead of just id * refactor * add tests * update tests --------- Co-authored-by: Vignesh Hari --- care/facility/api/viewsets/prescription.py | 1 + care/facility/tests/test_prescriptions_api.py | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/care/facility/api/viewsets/prescription.py b/care/facility/api/viewsets/prescription.py index 537faae98c..e8322aebee 100644 --- a/care/facility/api/viewsets/prescription.py +++ b/care/facility/api/viewsets/prescription.py @@ -91,6 +91,7 @@ class ConsultationPrescriptionFilter(filters.FilterSet): dosage_type = MultiSelectFilter() prescription_type = CareChoiceFilter(choice_dict=inverse_prescription_type) discontinued = filters.BooleanFilter() + medicine = filters.UUIDFilter(field_name="medicine__external_id") class ConsultationPrescriptionViewSet( diff --git a/care/facility/tests/test_prescriptions_api.py b/care/facility/tests/test_prescriptions_api.py index 87d5fa3c1d..a357e51a39 100644 --- a/care/facility/tests/test_prescriptions_api.py +++ b/care/facility/tests/test_prescriptions_api.py @@ -20,6 +20,7 @@ def setUp(self) -> None: super().setUp() self.consultation = self.create_consultation(self.patient, self.facility) self.medicine = MedibaseMedicine.objects.first() + self.medicine2 = MedibaseMedicine.objects.all()[1] self.normal_prescription_data = { "medicine": self.medicine.external_id, @@ -29,6 +30,14 @@ def setUp(self) -> None: "dosage_type": "REGULAR", } + self.normal_prescription_data2 = { + "medicine": self.medicine2.external_id, + "prescription_type": "REGULAR", + "base_dosage": "1 mg", + "frequency": "OD", + "dosage_type": "REGULAR", + } + def test_create_normal_prescription(self): response = self.client.post( f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/", @@ -113,3 +122,30 @@ def test_create_prn_prescription(self): prn_prescription_data, ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_medicine_filter_for_prescription(self): + # post 2 prescriptions with different medicines + self.client.post( + f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/", + self.normal_prescription_data, + ) + self.client.post( + f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/", + self.normal_prescription_data2, + ) + + # get all prescriptions without medicine filter + response = self.client.get( + f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/", + ) + self.assertEqual(response.data["count"], 2) + + # get all prescriptions with medicine filter + response = self.client.get( + f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/?medicine={self.medicine.external_id}", + ) + + for prescription in response.data["results"]: + self.assertEqual( + prescription["medicine_object"]["name"], self.medicine.name + ) From 52985adbc8619715b8466bf181ac559da079ebe2 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 8 May 2024 14:23:47 +0530 Subject: [PATCH 5/6] Painscale changed from 0 - 5 to 0 - 10 (#1967) * changes to painscale * reverted pipfile * optimized * Update care/facility/migrations/0420_auto_20240312_2318.py Co-authored-by: Rithvik Nishad * Update care/facility/migrations/0420_auto_20240312_2318.py Co-authored-by: Rithvik Nishad * correct indentations * update migrations * fix migrations * merged migrations * squashed migrations * Update 0429_double_pain_scale.py --------- Co-authored-by: Rithvik Nishad Co-authored-by: Aakash Singh Co-authored-by: Vignesh Hari --- .../migrations/0429_double_pain_scale.py | 92 +++++++++++++++++++ .../models/json_schema/daily_round.py | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 care/facility/migrations/0429_double_pain_scale.py diff --git a/care/facility/migrations/0429_double_pain_scale.py b/care/facility/migrations/0429_double_pain_scale.py new file mode 100644 index 0000000000..d008c75b85 --- /dev/null +++ b/care/facility/migrations/0429_double_pain_scale.py @@ -0,0 +1,92 @@ +# Generated by Django 4.2.10 on 2024-03-12 17:48 + +from math import ceil + +from django.core.paginator import Paginator +from django.db import migrations, models + +import care.utils.models.validators + + +def double_pain_scale(apps, schema_editor): + DailyRound = apps.get_model("facility", "DailyRound") + + page = Paginator( + DailyRound.objects.only("id", "pain_scale_enhanced") + .exclude(pain_scale_enhanced__exact=[]) + .order_by("id"), + 2000, + ) + + for page_num in page.page_range: + records_to_update = [] + for daily_round in page.page(page_num): + for obj in daily_round.pain_scale_enhanced: + try: + obj["scale"] *= 2 + except KeyError: + pass + records_to_update.append(daily_round) + DailyRound.objects.bulk_update(records_to_update, ["pain_scale_enhanced"]) + + +def halve_pain_scale(apps, schema_editor): + DailyRound = apps.get_model("facility", "DailyRound") + page = Paginator( + DailyRound.objects.only("id", "pain_scale_enhanced") + .exclude(pain_scale_enhanced__exact=[]) + .order_by("id"), + 2000, + ) + + for page_num in page.page_range: + records_to_update = [] + for daily_round in page.page(page_num): + for obj in daily_round.pain_scale_enhanced: + try: + obj["scale"] = ceil(obj["scale"] / 2) + except KeyError: + pass + records_to_update.append(daily_round) + DailyRound.objects.bulk_update(records_to_update, ["pain_scale_enhanced"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("facility", "0428_alter_patientmetainfo_occupation"), + ] + + operations = [ + migrations.AlterField( + model_name="dailyround", + name="pain_scale_enhanced", + field=models.JSONField( + default=list, + validators=[ + care.utils.models.validators.JSONFieldSchemaValidator( + { + "$schema": "http://json-schema.org/draft-07/schema#", + "items": [ + { + "additionalProperties": False, + "properties": { + "description": {"type": "string"}, + "region": {"type": "string"}, + "scale": { + "maximum": 10, + "minimum": 1, + "type": "number", + }, + }, + "required": ["region", "scale"], + "type": "object", + } + ], + "type": "array", + } + ) + ], + ), + ), + migrations.RunPython(double_pain_scale, reverse_code=halve_pain_scale), + ] diff --git a/care/facility/models/json_schema/daily_round.py b/care/facility/models/json_schema/daily_round.py index 633e9e7bf5..2ae76ff9a4 100644 --- a/care/facility/models/json_schema/daily_round.py +++ b/care/facility/models/json_schema/daily_round.py @@ -115,7 +115,7 @@ "type": "object", "properties": { "region": {"type": "string"}, - "scale": {"type": "number", "minimum": 1, "maximum": 5}, + "scale": {"type": "number", "minimum": 1, "maximum": 10}, "description": {"type": "string"}, }, "additionalProperties": False, From a57add01a85dfa174f12f4e0d28b5986b267bfe9 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Fri, 10 May 2024 18:07:16 +0530 Subject: [PATCH 6/6] fixes review_interval not being updated for "no review" --- care/facility/api/serializers/daily_round.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 3f14980056..5b9a005f0d 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -268,8 +268,8 @@ def create(self, validated_data): review_interval = validated_data.pop( "consultation__review_interval" ) + validated_data["consultation"].review_interval = review_interval if review_interval >= 0: - validated_data["consultation"].review_interval = review_interval patient.review_time = localtime(now()) + timedelta( minutes=review_interval )