From 9b8a776caa437076e3d5f36c96d7e3475bc836c1 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Fri, 14 Oct 2022 13:52:26 +0530 Subject: [PATCH 1/2] feat(consultation): added depth in symptoms field --- .../api/serializers/patient_consultation.py | 50 +++++++- .../migrations/0322_auto_20220930_1825.py | 119 ++++++++++++++++++ .../migrations/0325_merge_20221014_1415.py | 14 +++ care/facility/models/patient.py | 61 ++++++--- care/facility/models/patient_consultation.py | 63 ++++++++-- 5 files changed, 277 insertions(+), 30 deletions(-) create mode 100644 care/facility/migrations/0322_auto_20220930_1825.py create mode 100644 care/facility/migrations/0325_merge_20221014_1415.py diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 61f51a8dab..fba7350cd0 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -21,7 +21,10 @@ SYMPTOM_CHOICES, SuggestionChoices, ) -from care.facility.models.patient_consultation import PatientConsultation +from care.facility.models.patient_consultation import ( + PatientConsultation, + PatientConsultationSymptom, +) from care.users.api.serializers.user import ( UserAssignedSerializer, UserBaseMinimumSerializer, @@ -33,6 +36,19 @@ from config.serializers import ChoiceField +class PatientConsultationSymptomSerializer(serializers.ModelSerializer): + id = serializers.CharField(source="external_id", read_only=True) + consultation = ExternalIdSerializerField( + queryset=PatientConsultation.objects.all(), + required=False, + ) + symptom = ChoiceField(choices=SYMPTOM_CHOICES) + + class Meta: + model = PatientConsultationSymptom + exclude = ("deleted",) + + class PatientConsultationSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) facility_name = serializers.CharField(source="facility.name", read_only=True) @@ -42,7 +58,9 @@ class PatientConsultationSerializer(serializers.ModelSerializer): source="suggestion", ) - symptoms = serializers.MultipleChoiceField(choices=SYMPTOM_CHOICES) + patient_symptoms = serializers.ListSerializer( + child=PatientConsultationSymptomSerializer(), required=False + ) deprecated_covid_category = ChoiceField( choices=COVID_CATEGORY_CHOICES, required=False ) @@ -170,9 +188,23 @@ def update(self, instance, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) _temp = instance.assigned_to - + symptoms = validated_data.pop("patient_symptoms", []) consultation = super().update(instance, validated_data) + PatientConsultationSymptom.objects.filter(consultation=consultation).update( + deleted=True + ) + if consultation.is_asymptomatic is False: + symptoms_list = [] + for symptom in symptoms: + symptoms_list.append( + PatientConsultationSymptom(consultation=consultation, **symptom) + ) + if symptoms_list: + PatientConsultationSymptom.objects.bulk_create( + symptoms_list, ignore_conflicts=True + ) + if "assigned_to" in validated_data: if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: NotificationGenerator( @@ -238,6 +270,7 @@ def create(self, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) bed = validated_data.pop("bed", None) + symptoms = validated_data.pop("patient_symptoms", []) validated_data["facility_id"] = validated_data[ "patient" @@ -257,6 +290,17 @@ def create(self, validated_data): consultation.current_bed = consultation_bed consultation.save(update_fields=["current_bed"]) + if consultation.is_asymptomatic is False: + symptoms_list = [] + for symptom in symptoms: + symptoms_list.append( + PatientConsultationSymptom(consultation=consultation, **symptom) + ) + if symptoms_list: + PatientConsultationSymptom.objects.bulk_create( + symptoms_list, ignore_conflicts=True + ) + patient = consultation.patient if consultation.suggestion == SuggestionChoices.OP: consultation.discharge_date = localtime(now()) diff --git a/care/facility/migrations/0322_auto_20220930_1825.py b/care/facility/migrations/0322_auto_20220930_1825.py new file mode 100644 index 0000000000..aa480deb59 --- /dev/null +++ b/care/facility/migrations/0322_auto_20220930_1825.py @@ -0,0 +1,119 @@ +# Generated by Django 2.2.11 on 2022-09-30 12:55 + +from django.db import migrations, models +import django.db.models.deletion +import partial_index + + +def populate_data(apps, schema_editor): + patient_consultation = apps.get_model("facility", "PatientConsultation") + patients_cons = patient_consultation.objects.all() + + patient_consultation_symptom = apps.get_model( + "facility", "PatientConsultationSymptom" + ) + patient_symptom_objs = [] + for patient_cons in patients_cons: + for patient_symptom in patient_cons.symptoms: + patient_symptom_objs.append( + patient_consultation_symptom( + consultation=patient_cons, + symptom=patient_symptom, + symptom_onset_date=patient_cons.symptoms_onset_date.date(), + ) + ) + + patient_consultation_symptom.objects.bulk_create(patient_symptom_objs) + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0321_merge_20220921_2255"), + ] + + operations = [ + migrations.CreateModel( + name="PatientConsultationSymptom", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "symptom", + models.IntegerField( + blank=True, + choices=[ + (1, "ASYMPTOMATIC"), + (2, "FEVER"), + (3, "SORE THROAT"), + (4, "COUGH"), + (5, "BREATHLESSNESS"), + (6, "MYALGIA"), + (7, "ABDOMINAL DISCOMFORT"), + (8, "VOMITING/DIARRHOEA"), + (9, "OTHERS"), + (10, "SARI"), + (11, "SPUTUM"), + (12, "NAUSEA"), + (13, "CHEST PAIN"), + (14, "HEMOPTYSIS"), + (15, "NASAL DISCHARGE"), + (16, "BODY ACHE"), + ], + default=1, + null=True, + ), + ), + ( + "symptom_onset_date", + models.DateField(blank=True, null=True), + ), + ( + "deleted", + models.BooleanField(default=False), + ), + ( + "consultation", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="patient_symptoms", + to="facility.PatientConsultation", + ), + ), + ], + ), + migrations.AddIndex( + model_name="patientconsultationsymptom", + index=partial_index.PartialIndex( + fields=["consultation", "symptom"], + name="facility_pa_consult_6f7ce1_partial", + unique=True, + where=partial_index.PQ(deleted=False), + ), + ), + migrations.RunPython(populate_data, migrations.RunPython.noop), + migrations.AddField( + model_name="patientconsultation", + name="is_asymptomatic", + field=models.BooleanField(default=False), + ), + migrations.RemoveField( + model_name="patientconsultation", + name="other_symptoms", + ), + migrations.RemoveField( + model_name="patientconsultation", + name="symptoms", + ), + migrations.RemoveField( + model_name="patientconsultation", + name="symptoms_onset_date", + ), + ] diff --git a/care/facility/migrations/0325_merge_20221014_1415.py b/care/facility/migrations/0325_merge_20221014_1415.py new file mode 100644 index 0000000000..4a8fe4f1d2 --- /dev/null +++ b/care/facility/migrations/0325_merge_20221014_1415.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-10-14 08:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0322_auto_20220930_1825'), + ('facility', '0324_merge_20221013_0053'), + ] + + operations = [ + ] diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 618cc5929e..41a5c85749 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -137,7 +137,9 @@ class TestTypeEnum(enum.Enum): max_length=255, default="", verbose_name="Nationality of Patient" ) passport_no = models.CharField( - max_length=255, default="", verbose_name="Passport Number of Foreign Patients" + max_length=255, + default="", + verbose_name="Passport Number of Foreign Patients", ) # aadhar_no = models.CharField(max_length=255, default="", verbose_name="Aadhar Number of Patient") @@ -172,7 +174,9 @@ class TestTypeEnum(enum.Enum): editable=False, ) countries_travelled = JSONField( - null=True, blank=True, verbose_name="Countries Patient has Travelled to" + null=True, + blank=True, + verbose_name="Countries Patient has Travelled to", ) date_of_return = models.DateTimeField( blank=True, @@ -188,7 +192,9 @@ class TestTypeEnum(enum.Enum): default="", blank=True, verbose_name="Patient's Current Health Details" ) ongoing_medication = models.TextField( - default="", blank=True, verbose_name="Already pescribed medication if any" + default="", + blank=True, + verbose_name="Already pescribed medication if any", ) has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" @@ -249,7 +255,10 @@ class TestTypeEnum(enum.Enum): ) created_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="patient_created_by" + User, + on_delete=models.SET_NULL, + null=True, + related_name="patient_created_by", ) is_active = models.BooleanField( default=True, @@ -260,7 +269,9 @@ class TestTypeEnum(enum.Enum): help_text="FKey to PatientSearch", null=True ) date_of_receipt_of_information = models.DateTimeField( - null=True, blank=True, verbose_name="Patient's information received date" + null=True, + blank=True, + verbose_name="Patient's information received date", ) test_id = models.CharField(default="", max_length=100, null=True, blank=True) @@ -329,19 +340,29 @@ class TestTypeEnum(enum.Enum): blank=True, ) date_of_result = models.DateTimeField( - null=True, blank=True, default=None, verbose_name="Patient's result Date" + null=True, + blank=True, + default=None, + verbose_name="Patient's result Date", ) number_of_primary_contacts = models.IntegerField( - null=True, default=None, blank=True, verbose_name="Number of Primary Contacts" + null=True, + default=None, + blank=True, + verbose_name="Number of Primary Contacts", ) number_of_secondary_contacts = models.IntegerField( - null=True, default=None, blank=True, verbose_name="Number of Secondary Contacts" + null=True, + default=None, + blank=True, + verbose_name="Number of Secondary Contacts", ) # IDSP Requirements End # Vaccination Fields is_vaccinated = models.BooleanField( - default=False, verbose_name="Is the Patient Vaccinated Against COVID-19" + default=False, + verbose_name="Is the Patient Vaccinated Against COVID-19", ) number_of_doses = models.PositiveIntegerField( default=0, @@ -350,7 +371,11 @@ class TestTypeEnum(enum.Enum): validators=[MinValueValidator(0), MaxValueValidator(3)], ) vaccine_name = models.CharField( - choices=vaccineChoices, default=None, null=True, blank=False, max_length=15 + choices=vaccineChoices, + default=None, + null=True, + blank=False, + max_length=15, ) covin_id = models.CharField( @@ -547,8 +572,8 @@ def save(self, *args, **kwargs) -> None: "last_vaccinated_date": "Last Vaccinated Date", # Consultation Data "last_consultation__admission_date": "Date of Admission", - "last_consultation__symptoms_onset_date": "Date of Onset of Symptoms", - "last_consultation__symptoms": "Symptoms at time of consultation", + # "last_consultation__symptoms_onset_date": "Date of Onset of Symptoms", + # "last_consultation__symptoms": "Symptoms at time of consultation", "last_consultation__category": "Category", "last_consultation__examination_details": "Examination Details", "last_consultation__suggestion": "Suggestion", @@ -673,7 +698,9 @@ class ModeOfContactEnum(enum.IntEnum): ModeOfContactChoices = [(item.value, item.name) for item in ModeOfContactEnum] patient = models.ForeignKey( - PatientRegistration, on_delete=models.PROTECT, related_name="contacted_patients" + PatientRegistration, + on_delete=models.PROTECT, + related_name="contacted_patients", ) patient_in_contact = models.ForeignKey( PatientRegistration, @@ -698,7 +725,9 @@ class ModeOfContactEnum(enum.IntEnum): class Disease(models.Model): patient = models.ForeignKey( - PatientRegistration, on_delete=models.CASCADE, related_name="medical_history" + PatientRegistration, + on_delete=models.CASCADE, + related_name="medical_history", ) disease = models.IntegerField(choices=DISEASE_CHOICES) details = models.TextField(blank=True, null=True) @@ -709,7 +738,9 @@ class Disease(models.Model): class Meta: indexes = [ PartialIndex( - fields=["patient", "disease"], unique=True, where=PQ(deleted=False) + fields=["patient", "disease"], + unique=True, + where=PQ(deleted=False), ) ] diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index 2e5885d0f3..3de8e51c85 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -1,7 +1,7 @@ from django.contrib.postgres.fields import ArrayField, JSONField from django.core.validators import MinValueValidator from django.db import models -from multiselectfield import MultiSelectField +from partial_index import PQ, PartialIndex from care.facility.models import ( CATEGORY_CHOICES, @@ -20,6 +20,7 @@ reverse_choices, ) from care.users.models import User +from care.utils.models.base import BaseManager class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): @@ -33,10 +34,13 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): REVERSE_SUGGESTION_CHOICES = reverse_choices(SUGGESTION_CHOICES) patient = models.ForeignKey( - "PatientRegistration", on_delete=models.CASCADE, related_name="consultations" + "PatientRegistration", + on_delete=models.CASCADE, + related_name="consultations", ) ip_no = models.CharField(max_length=100, default="", null=True, blank=True) + is_asymptomatic = models.BooleanField(default=False) facility = models.ForeignKey( "Facility", on_delete=models.CASCADE, related_name="consultations" @@ -48,11 +52,6 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): icd11_diagnoses = ArrayField( models.CharField(max_length=100), default=[], blank=True, null=True ) - symptoms = MultiSelectField( - choices=SYMPTOM_CHOICES, default=1, null=True, blank=True - ) - other_symptoms = models.TextField(default="", blank=True) - symptoms_onset_date = models.DateTimeField(null=True, blank=True) deprecated_covid_category = models.CharField( choices=COVID_CATEGORY_CHOICES, max_length=8, @@ -102,7 +101,10 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): last_updated_by_telemedicine = models.BooleanField(default=False) # Deprecated assigned_to = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="patient_assigned_to" + User, + on_delete=models.SET_NULL, + null=True, + related_name="patient_assigned_to", ) verified_by = models.TextField(default="", null=True, blank=True) @@ -112,11 +114,17 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): ) last_edited_by = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="last_edited_user" + User, + on_delete=models.SET_NULL, + null=True, + related_name="last_edited_user", ) last_daily_round = models.ForeignKey( - "facility.DailyRound", on_delete=models.SET_NULL, null=True, default=None + "facility.DailyRound", + on_delete=models.SET_NULL, + null=True, + default=None, ) current_bed = models.ForeignKey( @@ -160,8 +168,8 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): CSV_MAPPING = { "consultation_created_date": "Date of Consultation", "admission_date": "Date of Admission", - "symptoms_onset_date": "Date of Onset of Symptoms", - "symptoms": "Symptoms at time of consultation", + # "symptoms_onset_date": "Date of Onset of Symptoms", + # "symptoms": "Symptoms at time of consultation", "deprecated_covid_category": "Covid Category", "category": "Category", "examination_details": "Examination Details", @@ -213,3 +221,34 @@ class Meta: check=models.Q(admitted=False) | models.Q(admission_date__isnull=False), ), ] + + +class PatientConsultationSymptom(models.Model): + consultation = models.ForeignKey( + PatientConsultation, + on_delete=models.CASCADE, + related_name="patient_symptoms", + ) + symptom = models.IntegerField( + choices=SYMPTOM_CHOICES, default=1, null=True, blank=True + ) + symptom_onset_date = models.DateField(null=True, blank=True) + deleted = models.BooleanField(default=False) + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["consultation", "symptom"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return ( + self.patient_consultation.patient.name + " - " + self.get_symptom_display() + ) + + def get_symptom_display(self): + return SYMPTOM_CHOICES[self.symptom - 1][1] From fbe19a23a454e4a1aa35e981959f8c2dfee0e647 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Thu, 27 Oct 2022 20:24:49 +0530 Subject: [PATCH 2/2] fix(consultation): added new fields to symptom model --- .../api/serializers/patient_consultation.py | 7 +++- .../migrations/0322_auto_20220930_1825.py | 6 +++- .../migrations/0327_merge_20221027_1959.py | 14 ++++++++ .../migrations/0328_auto_20221027_1959.py | 35 +++++++++++++++++++ care/facility/models/patient_consultation.py | 2 +- 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 care/facility/migrations/0327_merge_20221027_1959.py create mode 100644 care/facility/migrations/0328_auto_20221027_1959.py diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index fba7350cd0..058cb05270 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -46,7 +46,12 @@ class PatientConsultationSymptomSerializer(serializers.ModelSerializer): class Meta: model = PatientConsultationSymptom - exclude = ("deleted",) + fields = ( + "id", + "symptom", + "symptom_onset_date", + "consultation", + ) class PatientConsultationSerializer(serializers.ModelSerializer): diff --git a/care/facility/migrations/0322_auto_20220930_1825.py b/care/facility/migrations/0322_auto_20220930_1825.py index aa480deb59..7008c85511 100644 --- a/care/facility/migrations/0322_auto_20220930_1825.py +++ b/care/facility/migrations/0322_auto_20220930_1825.py @@ -1,5 +1,6 @@ # Generated by Django 2.2.11 on 2022-09-30 12:55 +from datetime import datetime from django.db import migrations, models import django.db.models.deletion import partial_index @@ -15,11 +16,14 @@ def populate_data(apps, schema_editor): patient_symptom_objs = [] for patient_cons in patients_cons: for patient_symptom in patient_cons.symptoms: + onset_date = datetime.now() + if patient_cons.symptoms_onset_date(): + onset_date = patient_cons.symptoms_onset_date.date() patient_symptom_objs.append( patient_consultation_symptom( consultation=patient_cons, symptom=patient_symptom, - symptom_onset_date=patient_cons.symptoms_onset_date.date(), + symptom_onset_date=onset_date, ) ) diff --git a/care/facility/migrations/0327_merge_20221027_1959.py b/care/facility/migrations/0327_merge_20221027_1959.py new file mode 100644 index 0000000000..f08d1b4cdb --- /dev/null +++ b/care/facility/migrations/0327_merge_20221027_1959.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-10-27 14:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0326_auto_20221018_1526'), + ('facility', '0322_auto_20220930_1825'), + ] + + operations = [ + ] diff --git a/care/facility/migrations/0328_auto_20221027_1959.py b/care/facility/migrations/0328_auto_20221027_1959.py new file mode 100644 index 0000000000..daa38a9873 --- /dev/null +++ b/care/facility/migrations/0328_auto_20221027_1959.py @@ -0,0 +1,35 @@ +# Generated by Django 2.2.11 on 2022-10-27 14:29 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0327_merge_20221027_1959"), + ] + + operations = [ + migrations.AddField( + model_name="patientconsultationsymptom", + name="created_date", + field=models.DateTimeField( + auto_now_add=True, db_index=True, null=True + ), + ), + migrations.AddField( + model_name="patientconsultationsymptom", + name="external_id", + field=models.UUIDField( + db_index=True, default=uuid.uuid4, unique=True + ), + ), + migrations.AddField( + model_name="patientconsultationsymptom", + name="modified_date", + field=models.DateTimeField( + auto_now=True, db_index=True, null=True + ), + ), + ] diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index 3de8e51c85..4067f88997 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -223,7 +223,7 @@ class Meta: ] -class PatientConsultationSymptom(models.Model): +class PatientConsultationSymptom(PatientBaseModel, PatientRelatedPermissionMixin): consultation = models.ForeignKey( PatientConsultation, on_delete=models.CASCADE,