From f711fb81d95912fdac8645bfe2470967599c03d9 Mon Sep 17 00:00:00 2001 From: cp-Coder Date: Wed, 19 Oct 2022 11:54:41 +0530 Subject: [PATCH] Updated health_details and medical_history API * Added vaccine, medical history nad disease model * Added the migrations with function to migrate the data * Added required serializers * Reformatted the code --- care/facility/admin.py | 4 +- .../api/serializers/medical_history.py | 79 ++++++++ care/facility/api/serializers/patient.py | 12 +- .../api/serializers/patient_consultation.py | 153 ++++++++------ .../api/serializers/patient_health_details.py | 80 ++++++-- .../migrations/0322_patienthealthdetails.py | 111 ++++++----- .../migrations/0324_auto_20221014_1535.py | 187 ++++++++++++++++++ .../0325_medicalhistory_consultation.py | 19 ++ .../models/json_schema/consultation.py | 21 -- care/facility/models/patient.py | 135 ++++++++----- care/facility/models/patient_consultation.py | 8 + .../tasks/patient/discharge_report.py | 9 +- care/utils/tests/test_base.py | 9 +- 13 files changed, 615 insertions(+), 212 deletions(-) create mode 100644 care/facility/api/serializers/medical_history.py create mode 100644 care/facility/migrations/0324_auto_20221014_1535.py create mode 100644 care/facility/migrations/0325_medicalhistory_consultation.py diff --git a/care/facility/admin.py b/care/facility/admin.py index 945bbfab8a..ee73ef2139 100644 --- a/care/facility/admin.py +++ b/care/facility/admin.py @@ -11,7 +11,7 @@ from .models import ( Building, - Disease, + Diseases, Facility, FacilityCapacity, FacilityInventoryItem, @@ -201,7 +201,7 @@ class FacilityUserAdmin(DjangoQLSearchMixin, admin.ModelAdmin, ExportCsvMixin): admin.site.register(PatientRegistration, PatientAdmin) admin.site.register(PatientTeleConsultation) admin.site.register(PatientSample, PatientSampleAdmin) -admin.site.register(Disease) +admin.site.register(Diseases) admin.site.register(FacilityInventoryUnit) admin.site.register(FacilityInventoryUnitConverter) admin.site.register(FacilityInventoryItem) diff --git a/care/facility/api/serializers/medical_history.py b/care/facility/api/serializers/medical_history.py new file mode 100644 index 0000000000..802e8cf0c6 --- /dev/null +++ b/care/facility/api/serializers/medical_history.py @@ -0,0 +1,79 @@ +from django.db import transaction +from rest_framework import serializers + +from care.facility.models import MedicalHistory, PatientRegistration +from care.facility.models.patient import Diseases +from care.facility.models.patient_consultation import PatientConsultation +from care.utils.serializer.external_id_field import ExternalIdSerializerField + + +class DiseaseSerializer(serializers.ModelSerializer): + date = serializers.DateField() + + class Meta: + model = Diseases + fields = ( + "disease", + "details", + "date", + "precision", + ) + + +class MedicalHistorySerializer(serializers.ModelSerializer): + id = serializers.CharField(source="external_id", read_only=True) + + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all(), required=False + ) + + consultation = ExternalIdSerializerField( + queryset=PatientConsultation.objects.all(), required=False + ) + + patient_diseases = serializers.ListSerializer( + child=DiseaseSerializer(), + required=False, + ) + + class Meta: + model = MedicalHistory + exclude = ("deleted", "external_id") + + def create(self, validated_data): + with transaction.atomic(): + consultation = validated_data["consultation"] + patient_diseases = validated_data.pop("patient_diseases", []) + medical_history = super().create(validated_data) + diseases = [] + for disease in patient_diseases: + diseases.append( + Diseases( + medical_history=medical_history, + **disease, + ) + ) + if diseases: + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) + consultation.last_medical_history = medical_history + consultation.save(update_fields=["last_medical_history"]) + return medical_history + + def update(self, instance, validated_data): + with transaction.atomic(): + patient_diseases = validated_data.pop("patient_diseases", []) + medical_history = super().update(instance, validated_data) + Diseases.objects.filter(medical_history=medical_history).update( + deleted=True + ) + diseases = [] + for disease in patient_diseases: + diseases.append( + Diseases( + medical_history=medical_history, + **disease, + ) + ) + if diseases: + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) + return medical_history diff --git a/care/facility/api/serializers/patient.py b/care/facility/api/serializers/patient.py index da2be7bdaa..80b983f3bc 100644 --- a/care/facility/api/serializers/patient.py +++ b/care/facility/api/serializers/patient.py @@ -15,7 +15,7 @@ from care.facility.models import ( DISEASE_CHOICES, GENDER_CHOICES, - Disease, + Diseases, Facility, FacilityPatientStatsHistory, PatientContactDetails, @@ -259,9 +259,9 @@ def create(self, validated_data): diseases = [] for disease in medical_history: - diseases.append(Disease(patient=patient, **disease)) + diseases.append(Diseases(patient=patient, **disease)) if diseases: - Disease.objects.bulk_create(diseases, ignore_conflicts=True) + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) if meta_info: meta_info_obj = PatientMetaInfo.objects.create(**meta_info) @@ -305,12 +305,12 @@ def update(self, instance, validated_data): self.check_external_entry(validated_data["srf_id"]) patient = super().update(instance, validated_data) - Disease.objects.filter(patient=patient).update(deleted=True) + Diseases.objects.filter(patient=patient).update(deleted=True) diseases = [] for disease in medical_history: - diseases.append(Disease(patient=patient, **disease)) + diseases.append(Diseases(patient=patient, **disease)) if diseases: - Disease.objects.bulk_create(diseases, ignore_conflicts=True) + Diseases.objects.bulk_create(diseases, ignore_conflicts=True) if meta_info: for key, value in meta_info.items(): diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 6bead9ea92..87bba3baa3 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -8,6 +8,7 @@ from care.facility.api.serializers.bed import ConsultationBedSerializer from care.facility.api.serializers.daily_round import DailyRoundSerializer from care.facility.api.serializers.facility import FacilityBasicInfoSerializer +from care.facility.api.serializers.medical_history import MedicalHistorySerializer from care.facility.api.serializers.patient_health_details import ( PatientHealthDetailsSerializer, ) @@ -19,7 +20,7 @@ ) from care.facility.models.bed import Bed, ConsultationBed from care.facility.models.notification import Notification -from care.facility.models.patient import PatientHealthDetails +from care.facility.models.patient import MedicalHistory, PatientHealthDetails from care.facility.models.patient_base import ( DISCHARGE_REASON_CHOICES, SYMPTOM_CHOICES, @@ -39,9 +40,7 @@ class PatientConsultationSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) - facility_name = serializers.CharField( - source="facility.name", read_only=True - ) + facility_name = serializers.CharField(source="facility.name", read_only=True) suggestion_text = ChoiceField( choices=PatientConsultation.SUGGESTION_CHOICES, read_only=True, @@ -60,14 +59,10 @@ class PatientConsultationSerializer(serializers.ModelSerializer): referred_to = ExternalIdSerializerField( queryset=Facility.objects.all(), required=False ) - patient = ExternalIdSerializerField( - queryset=PatientRegistration.objects.all() - ) + patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all()) facility = ExternalIdSerializerField(read_only=True) - assigned_to_object = UserAssignedSerializer( - source="assigned_to", read_only=True - ) + assigned_to_object = UserAssignedSerializer(source="assigned_to", read_only=True) assigned_to = serializers.PrimaryKeyRelatedField( queryset=User.objects.all(), required=False, allow_null=True @@ -84,16 +79,13 @@ class PatientConsultationSerializer(serializers.ModelSerializer): required=False, ) - review_time = serializers.IntegerField( - default=-1, write_only=True, required=False - ) + review_time = serializers.IntegerField(default=-1, write_only=True, required=False) last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) last_daily_round = DailyRoundSerializer(read_only=True) - health_details = ExternalIdSerializerField( + last_health_details = ExternalIdSerializerField( queryset=PatientHealthDetails.objects.all(), - source="last_health_details", required=False, ) @@ -104,6 +96,18 @@ class PatientConsultationSerializer(serializers.ModelSerializer): new_health_details = PatientHealthDetailsSerializer(required=False) + last_medical_history = ExternalIdSerializerField( + queryset=MedicalHistory.objects.all(), + required=False, + ) + + medical_history_object = MedicalHistorySerializer( + source="last_medical_history", + read_only=True, + ) + + new_medical_history = MedicalHistorySerializer(required=False) + current_bed = ConsultationBedSerializer(read_only=True) bed = ExternalIdSerializerField(queryset=Bed.objects.all(), required=False) @@ -127,9 +131,7 @@ def get_icd11_diagnoses_objects_by_ids(self, diagnoses_ids): return diagnosis_objects def get_icd11_diagnoses_object(self, consultation): - return self.get_icd11_diagnoses_objects_by_ids( - consultation.icd11_diagnoses - ) + return self.get_icd11_diagnoses_objects_by_ids(consultation.icd11_diagnoses) def get_icd11_provisional_diagnoses_object(self, consultation): return self.get_icd11_diagnoses_objects_by_ids( @@ -145,7 +147,10 @@ class Meta: "created_by", "kasp_enabled_date", ) - exclude = ("deleted", "external_id", "last_health_details") + exclude = ( + "deleted", + "external_id", + ) def validate_bed_number(self, bed_number): try: @@ -160,11 +165,7 @@ def update(self, instance, validated_data): if instance.discharge_date: raise ValidationError( - { - "consultation": [ - "Discharged Consultation data cannot be updated" - ] - } + {"consultation": ["Discharged Consultation data cannot be updated"]} ) if instance.suggestion == SuggestionChoices.OP: @@ -196,6 +197,7 @@ def update(self, instance, validated_data): _temp = instance.assigned_to health_details_data = validated_data.pop("new_health_details", None) + medical_history_data = validated_data.pop("new_medical_history", None) consultation = super().update(instance, validated_data) @@ -204,7 +206,7 @@ def update(self, instance, validated_data): consultation=consultation ) - serializer = PatientHealthDetailsSerializer( + health_details_serializer = PatientHealthDetailsSerializer( last_health_details_object, data={ "patient": consultation.patient.external_id.hex, @@ -216,17 +218,32 @@ def update(self, instance, validated_data): "request": self.context["request"], }, ) - serializer.is_valid(raise_exception=True) - serializer.save() + health_details_serializer.is_valid(raise_exception=True) + health_details_serializer.save() + + last_medical_history_object = MedicalHistory.objects.get( + consultation=consultation + ) + + medical_history_serializer = MedicalHistorySerializer( + last_medical_history_object, + data={ + "patient": consultation.patient.external_id.hex, + "consultation": consultation.external_id.hex, + **medical_history_data, + }, + context={ + "request": self.context["request"], + }, + ) + medical_history_serializer.is_valid(raise_exception=True) + medical_history_serializer.save() except KeyError: pass if "assigned_to" in validated_data: - if ( - validated_data["assigned_to"] != _temp - and validated_data["assigned_to"] - ): + if validated_data["assigned_to"] != _temp and validated_data["assigned_to"]: NotificationGenerator( event=Notification.Event.PATIENT_CONSULTATION_ASSIGNMENT, caused_by=self.context["request"].user, @@ -258,16 +275,12 @@ def create(self, validated_data): # Authorisation Check - allowed_facilities = get_home_facility_queryset( - self.context["request"].user - ) + allowed_facilities = get_home_facility_queryset(self.context["request"].user) if not allowed_facilities.filter( id=self.validated_data["patient"].facility.id ).exists(): raise ValidationError( - { - "facility": "Consultation creates are only allowed in home facility" - } + {"facility": "Consultation creates are only allowed in home facility"} ) # End Authorisation Checks @@ -286,9 +299,7 @@ def create(self, validated_data): if validated_data["patient"].last_consultation: if not validated_data["patient"].last_consultation.discharge_date: raise ValidationError( - { - "consultation": "Exists please Edit Existing Consultation" - } + {"consultation": "Exists please Edit Existing Consultation"} ) if "is_kasp" in validated_data: @@ -296,8 +307,9 @@ def create(self, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) bed = validated_data.pop("bed", None) - health_details_data = validated_data.pop("new_health_details", None) - + print(validated_data) + health_details_data = validated_data.pop("new_health_details", []) + medical_history_data = validated_data.pop("new_medical_history", []) validated_data["facility_id"] = validated_data[ "patient" ].facility_id # Coercing facility as the patient's facility @@ -307,7 +319,7 @@ def create(self, validated_data): consultation.save() try: - serializer = PatientHealthDetailsSerializer( + health_details_serializer = PatientHealthDetailsSerializer( data={ "patient": consultation.patient.external_id.hex, "facility": consultation.facility.external_id.hex, @@ -318,9 +330,20 @@ def create(self, validated_data): "request": self.context["request"], }, ) - serializer.is_valid(raise_exception=True) - health_details = serializer.save() - consultation.health_details = health_details.external_id.hex + health_details_serializer.is_valid(raise_exception=True) + health_details = health_details_serializer.save() + consultation.last_health_details = health_details + + medical_history_serializer = MedicalHistorySerializer( + data={ + "patient": consultation.patient.external_id.hex, + "consultation": consultation.external_id.hex, + **medical_history_data, + } + ) + medical_history_serializer.is_valid(raise_exception=True) + medical_history = medical_history_serializer.save() + consultation.last_medical_history = medical_history except KeyError as error: if consultation.patient.last_consultation is None: @@ -328,14 +351,24 @@ def create(self, validated_data): { "health_details": [ "Please provide the health details of the patient" + ], + }, + { + "medical_history": [ + "Please provide the medical history of the patient" ] - } + }, ) from error consultation.last_health_details = ( consultation.patient.last_consultation.last_health_details ) - consultation.save(update_fields=["last_health_details"]) + consultation.last_medical_history = ( + consultation.patient.last_consultation.last_medical_history + ) + consultation.save( + update_fields=["last_health_details", "last_medical_history"] + ) if bed: consultation_bed = ConsultationBed( @@ -360,9 +393,7 @@ def create(self, validated_data): if action != -1: patient.action = action if review_time > 0: - patient.review_time = localtime(now()) + timedelta( - minutes=review_time - ) + patient.review_time = localtime(now()) + timedelta(minutes=review_time) patient.save() NotificationGenerator( @@ -391,9 +422,9 @@ def validate(self, attrs): # TODO Add Bed Authorisation Validation if "suggestion" in validated: - if validated[ - "suggestion" - ] is SuggestionChoices.R and not validated.get("referred_to"): + if validated["suggestion"] is SuggestionChoices.R and not validated.get( + "referred_to" + ): raise ValidationError( { "referred_to": [ @@ -426,11 +457,7 @@ def validate(self, attrs): ) if validated["review_time"] <= 0: raise ValidationError( - { - "review_time": [ - "This field value is must be greater than 0." - ] - } + {"review_time": ["This field value is must be greater than 0."]} ) from care.facility.static_data.icd11 import ICDDiseases @@ -464,12 +491,8 @@ def validate(self, attrs): class PatientConsultationIDSerializer(serializers.ModelSerializer): - consultation_id = serializers.UUIDField( - source="external_id", read_only=True - ) - patient_id = serializers.UUIDField( - source="patient.external_id", read_only=True - ) + consultation_id = serializers.UUIDField(source="external_id", read_only=True) + patient_id = serializers.UUIDField(source="patient.external_id", read_only=True) class Meta: model = PatientConsultation diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py index c8fdd7534a..360bc00eea 100644 --- a/care/facility/api/serializers/patient_health_details.py +++ b/care/facility/api/serializers/patient_health_details.py @@ -1,3 +1,4 @@ +from django.db import transaction from django.forms import ChoiceField from rest_framework import serializers @@ -7,11 +8,22 @@ PatientRegistration, ) from care.facility.models.facility import Facility -from care.facility.models.patient_base import BLOOD_GROUP_CHOICES +from care.facility.models.patient import VaccinationHistory +from care.facility.models.patient_base import BLOOD_GROUP_CHOICES, VACCINE_CHOICES from care.utils.queryset.facility import get_home_facility_queryset from care.utils.serializer.external_id_field import ExternalIdSerializerField +class VaccinationHistorySerializer(serializers.ModelSerializer): + vaccine = serializers.ChoiceField(choices=VACCINE_CHOICES) + doses = serializers.IntegerField(required=False, default=0) + date = serializers.DateField() + + class Meta: + model = VaccinationHistory + fields = ("vaccine", "doses", "date", "precision") + + class PatientHealthDetailsSerializer(serializers.ModelSerializer): id = serializers.CharField(source="external_id", read_only=True) @@ -27,30 +39,60 @@ class PatientHealthDetailsSerializer(serializers.ModelSerializer): ) blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) + vaccination_history = serializers.ListSerializer( + child=VaccinationHistorySerializer(), required=False + ) + class Meta: model = PatientHealthDetails exclude = ("deleted", "external_id") def create(self, validated_data): - consultation = validated_data["consultation"] - allowed_facilities = get_home_facility_queryset(self.context["request"].user) - if not allowed_facilities.filter( - id=self.validated_data["patient"].facility.id - ).exists(): - raise serializers.ValidationError( - { - "patient": "Patient Health Details creates are only allowed in home facility" - } + with transaction.atomic(): + consultation = validated_data["consultation"] + vaccination_history = validated_data.pop("vaccination_history", []) + allowed_facilities = get_home_facility_queryset( + self.context["request"].user ) + if not allowed_facilities.filter( + id=self.validated_data["patient"].facility.id + ).exists(): + raise serializers.ValidationError( + { + "patient": "Patient Health Details creates are only allowed in home facility" + } + ) - health_details = super().create(validated_data) - consultation.last_health_details = health_details - consultation.save(update_fields=["last_health_details"]) - return health_details + health_details = super().create(validated_data) + vaccines = [] + for vaccine in vaccination_history: + vaccines.append( + VaccinationHistory( + health_details=consultation.last_health_details, + **vaccine, + ) + ) + if vaccines: + VaccinationHistory.objects.bulk_create(vaccines, ignore_conflicts=True) + consultation.last_health_details = health_details + consultation.save(update_fields=["last_health_details"]) + return health_details def update(self, instance, validated_data): - consultation = validated_data["consultation"] - health_details = super().update(instance, validated_data) - consultation.last_health_details = health_details - consultation.save(update_fields=["last_health_details"]) - return health_details + with transaction.atomic(): + vaccination_history = validated_data.pop("vaccination_history", []) + health_details = super().update(instance, validated_data) + VaccinationHistory.objects.filter(health_details=health_details).update( + deleted=True + ) + vaccines = [] + for vaccine in vaccination_history: + vaccines.append( + VaccinationHistory( + health_details=health_details, + **vaccine, + ) + ) + if vaccines: + VaccinationHistory.objects.bulk_create(vaccines, ignore_conflicts=True) + return health_details diff --git a/care/facility/migrations/0322_patienthealthdetails.py b/care/facility/migrations/0322_patienthealthdetails.py index 8667516ab0..afaf8f7fc2 100644 --- a/care/facility/migrations/0322_patienthealthdetails.py +++ b/care/facility/migrations/0322_patienthealthdetails.py @@ -2,12 +2,12 @@ from django.db import migrations, models import django.db.models.deletion import django.core.validators +import partial_index import care.facility.models.mixins.permissions.patient -import django.contrib.postgres.fields.jsonb import care.utils.models.validators -def populate_data(apps, schema_editor): +def create_health_details(apps, schema_editor): health_details = apps.get_model("facility", "PatientHealthDetails") patient_registration = apps.get_model("facility", "PatientRegistration") patients = patient_registration.objects.all() @@ -47,13 +47,6 @@ def populate_data(apps, schema_editor): has_allergy=has_allergy, allergies=allergies, blood_group=blood_group, - vaccination_history=[ - { - "vaccine": patient.vaccine_name, - "doses": patient.number_of_doses, - "last_vaccinated_date": patient.last_vaccinated_date, - } - ], ) health_details_objs.append(health_details_obj) @@ -73,6 +66,30 @@ def link_data(apps, schema_editor): patient_cons.save(update_fields=["last_health_details"]) +def create_vaccination_history(apps, schema_editor): + vaccination_history = apps.get_model("facility", "VaccinationHistory") + patient_registration = apps.get_model("facility", "PatientRegistration") + patients = patient_registration.objects.all() + + vaccine_objs = [] + for patient in patients: + if ( + patient.vaccine_name + and patient.last_consultation.last_health_details + ): + vaccine_objs.append( + vaccination_history( + health_details=patient.last_consultation.last_health_details, + vaccine=patient.vaccine_name, + doses=patient.number_of_doses, + date=patient.last_vaccinated_date, + precision=0, + ) + ) + + vaccination_history.objects.bulk_create(vaccine_objs) + + class Migration(migrations.Migration): dependencies = [ @@ -196,41 +213,6 @@ class Migration(migrations.Migration): verbose_name="Patient's Height in CM", ), ), - ( - "vaccination_history", - django.contrib.postgres.fields.jsonb.JSONField( - default=list, - validators=[ - care.utils.models.validators.JSONFieldSchemaValidator( - { - "$schema": "http://json-schema.org/draft-07/schema#", - "items": [ - { - "additionalProperties": False, - "properties": { - "doses": { - "default": 0, - "type": "number", - }, - "last_vaccinated_date": { - "type": "string" - }, - "vaccine": {"type": "string"}, - }, - "required": [ - "vaccine", - "doses", - "last_vaccinated_date", - ], - "type": "object", - } - ], - "type": "array", - } - ) - ], - ), - ), ], options={ "abstract": False, @@ -240,6 +222,33 @@ class Migration(migrations.Migration): care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, ), ), + migrations.CreateModel( + name="VaccinationHistory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("vaccine", models.CharField(max_length=100)), + ("doses", models.IntegerField(default=0)), + ("date", models.DateField(blank=True, null=True)), + ("precision", models.IntegerField(default=0)), + ("deleted", models.BooleanField(default=False)), + ( + "health_details", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="vaccination_history", + to="facility.PatientHealthDetails", + ), + ), + ], + ), migrations.AddField( model_name="patientconsultation", name="last_health_details", @@ -250,6 +259,18 @@ class Migration(migrations.Migration): to="facility.PatientHealthDetails", ), ), - migrations.RunPython(populate_data, migrations.RunPython.noop), + migrations.AddIndex( + model_name="vaccinationhistory", + index=partial_index.PartialIndex( + fields=["health_details", "vaccine"], + name="facility_va_health__5bb62d_partial", + unique=True, + where=partial_index.PQ(deleted=False), + ), + ), + migrations.RunPython(create_health_details, migrations.RunPython.noop), migrations.RunPython(link_data, migrations.RunPython.noop), + migrations.RunPython( + create_vaccination_history, migrations.RunPython.noop + ), ] diff --git a/care/facility/migrations/0324_auto_20221014_1535.py b/care/facility/migrations/0324_auto_20221014_1535.py new file mode 100644 index 0000000000..658d3736cc --- /dev/null +++ b/care/facility/migrations/0324_auto_20221014_1535.py @@ -0,0 +1,187 @@ +# Generated by Django 2.2.11 on 2022-10-14 10:05 + +import datetime +from django.db import migrations, models +import django.db.models.deletion +import partial_index +import uuid +import care.facility.models.mixins.permissions.patient +from care.facility.models.patient_base import DISEASE_CHOICES + + +def create_medical_history(apps, schema_editor): + patient_registration = apps.get_model("facility", "PatientRegistration") + patients = patient_registration.objects.all() + + medical_history = apps.get_model("facility", "MedicalHistory") + medical_history_objs = [] + for patient in patients: + medical_history_objs.append( + medical_history( + patient=patient, + ongoing_medication=patient.ongoing_medication, + present_health=patient.present_health, + ) + ) + + medical_history.objects.bulk_create(medical_history_objs) + + +def link_data(apps, schema_editor): + patient_consultation = apps.get_model("facility", "PatientConsultation") + medical_history = apps.get_model("facility", "MedicalHistory") + patient_cons_objs = patient_consultation.objects.all() + + for patient_cons in patient_cons_objs: + patient_cons.last_medical_history = medical_history.objects.get( + id=patient_cons.patient.id + ) + patient_cons.save(update_fields=["last_medical_history"]) + + +def create_disease_history(apps, schema_editor): + disease_history = apps.get_model("facility", "Diseases") + disease_model = apps.get_model("facility", "Disease") + diseases = disease_model.objects.filter(deleted=False) + + disease_objs = [] + for disease in diseases: + disease_objs.append( + disease_history( + patient=disease.patient, + disease=DISEASE_CHOICES[disease.disease], + details=disease.details, + date=datetime.date.today(), + ) + ) + + disease_history.objects.bulk_create(disease_objs) + + +class Migration(migrations.Migration): + + dependencies = [ + ("facility", "0323_merge_20221005_1742"), + ] + + operations = [ + migrations.CreateModel( + name="MedicalHistory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "external_id", + models.UUIDField( + db_index=True, default=uuid.uuid4, unique=True + ), + ), + ( + "created_date", + models.DateTimeField( + auto_now_add=True, db_index=True, null=True + ), + ), + ( + "modified_date", + models.DateTimeField( + auto_now=True, db_index=True, null=True + ), + ), + ("deleted", models.BooleanField(db_index=True, default=False)), + ( + "ongoing_medication", + models.TextField( + blank=True, + default="", + verbose_name="Already pescribed medication if any", + ), + ), + ( + "present_health", + models.TextField( + blank=True, + default="", + verbose_name="Patient's Current Health Details", + ), + ), + ( + "patient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="facility.PatientRegistration", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + models.Model, + care.facility.models.mixins.permissions.patient.PatientRelatedPermissionMixin, + ), + ), + migrations.CreateModel( + name="Diseases", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("disease", models.CharField(max_length=100)), + ("details", models.TextField(blank=True, null=True)), + ("date", models.DateField(blank=True, null=True)), + ("precision", models.IntegerField(default=0)), + ("deleted", models.BooleanField(default=False)), + ( + "medical_history", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="patient_diseases", + to="facility.MedicalHistory", + ), + ), + ], + ), + migrations.AddField( + model_name="patientconsultation", + name="last_medical_history", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="facility.MedicalHistory", + ), + ), + migrations.RunPython( + create_medical_history, migrations.RunPython.noop + ), + migrations.RunPython(link_data, migrations.RunPython.noop), + migrations.RunPython( + create_disease_history, migrations.RunPython.noop + ), + migrations.DeleteModel( + name="Disease", + ), + migrations.AddIndex( + model_name="diseases", + index=partial_index.PartialIndex( + fields=["medical_history", "disease"], + name="facility_di_medical_1af9d2_partial", + unique=True, + where=partial_index.PQ(deleted=False), + ), + ), + ] diff --git a/care/facility/migrations/0325_medicalhistory_consultation.py b/care/facility/migrations/0325_medicalhistory_consultation.py new file mode 100644 index 0000000000..cb7e413668 --- /dev/null +++ b/care/facility/migrations/0325_medicalhistory_consultation.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.11 on 2022-10-18 04:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0324_auto_20221014_1535'), + ] + + operations = [ + migrations.AddField( + model_name='medicalhistory', + name='consultation', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='facility.PatientConsultation'), + ), + ] diff --git a/care/facility/models/json_schema/consultation.py b/care/facility/models/json_schema/consultation.py index 812e252956..91e68402c2 100644 --- a/care/facility/models/json_schema/consultation.py +++ b/care/facility/models/json_schema/consultation.py @@ -17,24 +17,3 @@ } ], } - -VACCINATION = { - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "vaccine": {"type": "string"}, - "doses": {"type": "number", "default": 0}, - "last_vaccinated_date": {"type": "string"}, - }, - "additionalProperties": False, - "required": [ - "vaccine", - "doses", - "last_vaccinated_date", - ], - } - ], -} diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 26e9398004..cd0772a6e0 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -1,7 +1,6 @@ import datetime import enum -from django.contrib.postgres.fields import JSONField as JsonField from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from fernet_fields import EncryptedCharField, EncryptedIntegerField @@ -9,7 +8,6 @@ from simple_history.models import HistoricalRecords from care.facility.models import ( - DISEASE_CHOICES, DiseaseStatusEnum, District, Facility, @@ -20,7 +18,6 @@ Ward, pretty_boolean, ) -from care.facility.models.json_schema.consultation import VACCINATION from care.facility.models.mixins.permissions.facility import ( FacilityRelatedPermissionMixin, ) @@ -47,7 +44,6 @@ ) from care.utils.models.base import BaseManager, BaseModel from care.utils.models.jsonfield import JSONField -from care.utils.models.validators import JSONFieldSchemaValidator class PatientRegistration(PatientBaseModel, PatientPermissionMixin): @@ -187,13 +183,13 @@ class TestTypeEnum(enum.Enum): default="", blank=True, verbose_name="Patient's Current Health Details", - ) + ) # deprecated ongoing_medication = models.TextField( default="", blank=True, verbose_name="Already pescribed medication if any", - ) + ) # deprecated has_SARI = models.BooleanField( default=False, verbose_name="Does the Patient Suffer from SARI" @@ -549,12 +545,12 @@ def save(self, *args, **kwargs) -> None: "countries_travelled": "Countries Patient has Travelled to", "date_of_return": "Return Date from the Last Country if Travelled", "is_migrant_worker": "Is the Patient a Migrant Worker", - "present_health": "Patient's Current Health Details", - "ongoing_medication": "Already pescribed medication if any", "has_SARI": "Does the Patient Suffer from SARI", "date_of_receipt_of_information": "Patient's information received date", "will_donate_blood": "Will Patient Donate Blood?", "fit_for_blood_donation": "Is Patient Fit for Blood Donation?", + # "present_health": "Patient's Current Health Details", + # "ongoing_medication": "Already pescribed medication if any", "date_of_test": "Date of Sample Test", "srf_id": "SRF Test Id", # IDSP Data @@ -726,34 +722,6 @@ class ModeOfContactEnum(enum.IntEnum): objects = BaseManager() -class Disease(models.Model): - patient = models.ForeignKey( - PatientRegistration, - on_delete=models.CASCADE, - related_name="medical_history", - ) - disease = models.IntegerField(choices=DISEASE_CHOICES) - details = models.TextField(blank=True, null=True) - deleted = models.BooleanField(default=False) - - objects = BaseManager() - - class Meta: - indexes = [ - PartialIndex( - fields=["patient", "disease"], - unique=True, - where=PQ(deleted=False), - ) - ] - - def __str__(self): - return self.patient.name + " - " + self.get_disease_display() - - def get_disease_display(self): - return DISEASE_CHOICES[self.disease - 1][1] - - class FacilityPatientStatsHistory(FacilityBaseModel, FacilityRelatedPermissionMixin): facility = models.ForeignKey("Facility", on_delete=models.PROTECT) entry_date = models.DateField() @@ -812,6 +780,12 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): Facility, on_delete=models.CASCADE, related_name="health_details" ) + consultation = models.ForeignKey( + PatientConsultation, + on_delete=models.CASCADE, + null=True, + ) + family_details = models.TextField( default="", blank=True, verbose_name="Patient's Family Details" ) @@ -829,16 +803,6 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Blood Group of Patient", ) - consultation = models.ForeignKey( - PatientConsultation, - on_delete=models.CASCADE, - null=True, - ) - - vaccination_history = JsonField( - default=dict, validators=[JSONFieldSchemaValidator(VACCINATION)] - ) - height = models.FloatField( default=None, null=True, @@ -852,3 +816,82 @@ class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): verbose_name="Patient's Weight in KG", validators=[MinValueValidator(0)], ) + + +class VaccinationHistory(models.Model): + health_details = models.ForeignKey( + PatientHealthDetails, + on_delete=models.CASCADE, + related_name="vaccination_history", + ) + vaccine = models.CharField(max_length=100) + doses = models.IntegerField(default=0) + date = models.DateField(null=True, blank=True) + precision = models.IntegerField(default=0) + + deleted = models.BooleanField(default=False) + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["health_details", "vaccine"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return self.vaccine + " " + self.doses + + +class MedicalHistory(PatientBaseModel, PatientRelatedPermissionMixin): + patient = models.ForeignKey( + PatientRegistration, + on_delete=models.CASCADE, + ) + + consultation = models.ForeignKey( + PatientConsultation, + on_delete=models.CASCADE, + null=True, + ) + + ongoing_medication = models.TextField( + default="", + blank=True, + verbose_name="Already pescribed medication if any", + ) + + present_health = models.TextField( + default="", + blank=True, + verbose_name="Patient's Current Health Details", + ) + + +class Diseases(models.Model): + medical_history = models.ForeignKey( + MedicalHistory, + on_delete=models.CASCADE, + related_name="patient_diseases", + ) + disease = models.CharField(max_length=100) + details = models.TextField(null=True, blank=True) + date = models.DateField(null=True, blank=True) + precision = models.IntegerField(default=0) + + deleted = models.BooleanField(default=False) + objects = BaseManager() + + class Meta: + indexes = [ + PartialIndex( + fields=["medical_history", "disease"], + unique=True, + where=PQ(deleted=False), + ) + ] + + def __str__(self): + return self.patient.name + " " + self.disease diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index b2dae4df96..ec17b7786f 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -143,6 +143,14 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): null=True, default=None, ) + + last_medical_history = models.ForeignKey( + "MedicalHistory", + on_delete=models.SET_NULL, + null=True, + default=None, + ) + # Physical Information height = models.FloatField( diff --git a/care/facility/tasks/patient/discharge_report.py b/care/facility/tasks/patient/discharge_report.py index 0000760c8b..8ca3149532 100644 --- a/care/facility/tasks/patient/discharge_report.py +++ b/care/facility/tasks/patient/discharge_report.py @@ -1,5 +1,4 @@ import datetime -import logging import random import string import time @@ -14,12 +13,12 @@ from care.facility.models import ( DailyRound, + Diseases, + DiseaseStatusEnum, + InvestigationValue, PatientConsultation, PatientRegistration, PatientSample, - Disease, - InvestigationValue, - DiseaseStatusEnum, ) @@ -34,7 +33,7 @@ def generate_discharge_report(patient_id, email): consultations = PatientConsultation.objects.filter(patient=patient).order_by( "-created_date" ) - diseases = Disease.objects.filter(patient=patient) + diseases = Diseases.objects.filter(patient=patient) if consultations.exists(): consultation = consultations.first() samples = PatientSample.objects.filter( diff --git a/care/utils/tests/test_base.py b/care/utils/tests/test_base.py index bc160026b1..276b9d2323 100644 --- a/care/utils/tests/test_base.py +++ b/care/utils/tests/test_base.py @@ -14,7 +14,7 @@ COVID_CATEGORY_CHOICES, DISEASE_CHOICES_MAP, SYMPTOM_CHOICES, - Disease, + Diseases, DiseaseStatusEnum, Facility, LocalBody, @@ -109,7 +109,7 @@ def create_patient(cls, **kwargs): patient = PatientRegistration.objects.create(**patient_data) diseases = [ - Disease.objects.create( + Diseases.objects.create( patient=patient, disease=DISEASE_CHOICES_MAP[mh["disease"]], details=mh["details"], @@ -325,7 +325,10 @@ def get_district_representation(self, district: District): def get_state_representation(self, state: State): if state is None: return {"state": None, "state_object": None} - return {"state": state.id, "state_object": {"id": state.id, "name": state.name}} + return { + "state": state.id, + "state_object": {"id": state.id, "name": state.name}, + } def _convert_to_matchable_types(self, d): def dict_to_matching_type(d: dict):