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 5a0bdbd7c5..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, @@ -25,11 +25,7 @@ PatientSearch, ) from care.facility.models.notification import Notification -from care.facility.models.patient_base import ( - BLOOD_GROUP_CHOICES, - DISEASE_STATUS_CHOICES, - DiseaseStatusEnum, -) +from care.facility.models.patient_base import DISEASE_STATUS_CHOICES, DiseaseStatusEnum from care.facility.models.patient_consultation import PatientConsultation from care.facility.models.patient_external_test import PatientExternalTest from care.facility.models.patient_tele_consultation import PatientTeleConsultation @@ -71,9 +67,9 @@ class PatientListSerializer(serializers.ModelSerializer): last_consultation = PatientConsultationSerializer(read_only=True) - blood_group = ChoiceField(choices=BLOOD_GROUP_CHOICES, required=True) disease_status = ChoiceField( - choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + choices=DISEASE_STATUS_CHOICES, + default=DiseaseStatusEnum.SUSPECTED.value, ) source = ChoiceField(choices=PatientRegistration.SourceChoices) @@ -89,7 +85,6 @@ class Meta: "year_of_birth", "meta_info", "countries_travelled_old", - "allergies", "external_id", ) read_only = TIMESTAMP_FIELDS @@ -132,7 +127,9 @@ class Meta: phone_number = PhoneNumberIsPossibleField() - facility = ExternalIdSerializerField(queryset=Facility.objects.all(), required=False) + facility = ExternalIdSerializerField( + queryset=Facility.objects.all(), required=False + ) medical_history = serializers.ListSerializer( child=MedicalHistorySerializer(), required=False ) @@ -151,7 +148,8 @@ class Meta: default=PatientRegistration.SourceEnum.CARE.value, ) disease_status = ChoiceField( - choices=DISEASE_STATUS_CHOICES, default=DiseaseStatusEnum.SUSPECTED.value + choices=DISEASE_STATUS_CHOICES, + default=DiseaseStatusEnum.SUSPECTED.value, ) meta_info = PatientMetaInfoSerializer(required=False, allow_null=True) @@ -167,9 +165,6 @@ class Meta: last_edited = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) - vaccine_name = serializers.ChoiceField( - choices=PatientRegistration.vaccineChoices, required=False, allow_null=True - ) assigned_to_object = UserBaseMinimumSerializer(source="assigned_to", read_only=True) @@ -187,7 +182,11 @@ class Meta: "external_id", ) include = ("contacted_patients",) - read_only = TIMESTAMP_FIELDS + ("last_edited", "created_by", "is_active") + read_only = TIMESTAMP_FIELDS + ( + "last_edited", + "created_by", + "is_active", + ) # def get_last_consultation(self, obj): # last_consultation = PatientConsultation.objects.filter(patient=obj).last() @@ -218,12 +217,6 @@ def validate(self, attrs): {"non_field_errors": ["Either age or date_of_birth should be passed"]} ) - if validated.get("is_vaccinated"): - if validated.get("number_of_doses") == 0: - raise serializers.ValidationError("Number of doses cannot be 0") - if validated.get("vaccine_name") is None: - raise serializers.ValidationError("Vaccine name cannot be null") - return validated def check_external_entry(self, srf_id): @@ -266,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) @@ -312,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(): @@ -386,11 +379,11 @@ class PatientSearchSerializer(serializers.ModelSerializer): class Meta: model = PatientSearch exclude = ( - "date_of_birth", - "year_of_birth", - "external_id", - "id", - ) + TIMESTAMP_FIELDS + "date_of_birth", + "year_of_birth", + "external_id", + "id", + ) + TIMESTAMP_FIELDS class PatientTransferSerializer(serializers.ModelSerializer): diff --git a/care/facility/api/serializers/patient_consultation.py b/care/facility/api/serializers/patient_consultation.py index 2841097385..87bba3baa3 100644 --- a/care/facility/api/serializers/patient_consultation.py +++ b/care/facility/api/serializers/patient_consultation.py @@ -8,6 +8,10 @@ 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, +) from care.facility.models import ( CATEGORY_CHOICES, COVID_CATEGORY_CHOICES, @@ -16,6 +20,7 @@ ) from care.facility.models.bed import Bed, ConsultationBed from care.facility.models.notification import Notification +from care.facility.models.patient import MedicalHistory, PatientHealthDetails from care.facility.models.patient_base import ( DISCHARGE_REASON_CHOICES, SYMPTOM_CHOICES, @@ -69,7 +74,9 @@ class PatientConsultationSerializer(serializers.ModelSerializer): discharge_notes = serializers.CharField(read_only=True) action = ChoiceField( - choices=PatientRegistration.ActionChoices, write_only=True, required=False + choices=PatientRegistration.ActionChoices, + write_only=True, + required=False, ) review_time = serializers.IntegerField(default=-1, write_only=True, required=False) @@ -77,6 +84,29 @@ class PatientConsultationSerializer(serializers.ModelSerializer): last_edited_by = UserBaseMinimumSerializer(read_only=True) created_by = UserBaseMinimumSerializer(read_only=True) last_daily_round = DailyRoundSerializer(read_only=True) + last_health_details = ExternalIdSerializerField( + queryset=PatientHealthDetails.objects.all(), + required=False, + ) + + health_details_object = PatientHealthDetailsSerializer( + source="last_health_details", + read_only=True, + ) + + 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) @@ -104,7 +134,9 @@ def get_icd11_diagnoses_object(self, consultation): 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(consultation.icd11_provisional_diagnoses) + return self.get_icd11_diagnoses_objects_by_ids( + consultation.icd11_provisional_diagnoses + ) class Meta: model = PatientConsultation @@ -115,7 +147,10 @@ class Meta: "created_by", "kasp_enabled_date", ) - exclude = ("deleted", "external_id") + exclude = ( + "deleted", + "external_id", + ) def validate_bed_number(self, bed_number): try: @@ -126,7 +161,6 @@ def validate_bed_number(self, bed_number): return bed_number def update(self, instance, validated_data): - instance.last_edited_by = self.context["request"].user if instance.discharge_date: @@ -162,9 +196,52 @@ def update(self, instance, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) _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) + try: + last_health_details_object = PatientHealthDetails.objects.get( + consultation=consultation + ) + + health_details_serializer = PatientHealthDetailsSerializer( + last_health_details_object, + data={ + "patient": consultation.patient.external_id.hex, + "facility": consultation.facility.external_id.hex, + "consultation": consultation.external_id.hex, + **health_details_data, + }, + context={ + "request": self.context["request"], + }, + ) + 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"]: NotificationGenerator( @@ -230,7 +307,9 @@ def create(self, validated_data): validated_data["kasp_enabled_date"] = localtime(now()) bed = validated_data.pop("bed", 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 @@ -239,9 +318,63 @@ def create(self, validated_data): consultation.last_edited_by = self.context["request"].user consultation.save() + try: + health_details_serializer = PatientHealthDetailsSerializer( + data={ + "patient": consultation.patient.external_id.hex, + "facility": consultation.facility.external_id.hex, + "consultation": consultation.external_id.hex, + **health_details_data, + }, + context={ + "request": self.context["request"], + }, + ) + 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: + raise ValidationError( + { + "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.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( - bed=bed, consultation=consultation, start_date=consultation.created_date + bed=bed, + consultation=consultation, + start_date=consultation.created_date, ) consultation_bed.save() consultation.current_bed = consultation_bed @@ -332,27 +465,27 @@ def validate(self, attrs): for diagnosis in validated["icd11_diagnoses"]: try: ICDDiseases.by.id[diagnosis] - except BaseException: + except BaseException as error: raise ValidationError( { "icd11_diagnoses": [ f"{diagnosis} is not a valid ICD 11 Diagnosis ID" ] } - ) + ) from error if "icd11_provisional_diagnoses" in validated: for diagnosis in validated["icd11_provisional_diagnoses"]: try: ICDDiseases.by.id[diagnosis] - except BaseException: + except BaseException as error: raise ValidationError( { "icd11_provisional_diagnoses": [ f"{diagnosis} is not a valid ICD 11 Diagnosis ID" ] } - ) + ) from error return validated diff --git a/care/facility/api/serializers/patient_health_details.py b/care/facility/api/serializers/patient_health_details.py new file mode 100644 index 0000000000..c5beaaf8ea --- /dev/null +++ b/care/facility/api/serializers/patient_health_details.py @@ -0,0 +1,105 @@ +from django.db import transaction +from django.forms import ChoiceField +from rest_framework import serializers + +from care.facility.models import ( + PatientConsultation, + PatientHealthDetails, + PatientRegistration, +) +from care.facility.models.facility import Facility +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) + + patient = ExternalIdSerializerField( + queryset=PatientRegistration.objects.all(), required=False + ) + facility = ExternalIdSerializerField( + queryset=Facility.objects.all(), required=False + ) + consultation = ExternalIdSerializerField( + queryset=PatientConsultation.objects.all(), required=False + ) + 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): + 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) + vaccines = [] + for vaccine in vaccination_history: + vaccines.append( + VaccinationHistory( + health_details=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): + 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/api/viewsets/patient.py b/care/facility/api/viewsets/patient.py index a897eec81e..b801e2134b 100644 --- a/care/facility/api/viewsets/patient.py +++ b/care/facility/api/viewsets/patient.py @@ -9,13 +9,21 @@ from django.utils.timezone import localtime, now from django_filters import rest_framework as filters from djqscsv import render_to_csv_response -from dry_rest_permissions.generics import DRYPermissionFiltersBase, DRYPermissions +from dry_rest_permissions.generics import ( + DRYPermissionFiltersBase, + DRYPermissions, +) from rest_framework import filters as rest_framework_filters from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action from rest_framework.exceptions import ValidationError from rest_framework.generics import get_object_or_404 -from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin +from rest_framework.mixins import ( + CreateModelMixin, + ListModelMixin, + RetrieveModelMixin, + UpdateModelMixin, +) from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -29,6 +37,9 @@ PatientSearchSerializer, PatientTransferSerializer, ) +from care.facility.api.serializers.patient_health_details import ( + PatientHealthDetailsSerializer, +) from care.facility.api.serializers.patient_icmr import PatientICMRSerializer from care.facility.api.viewsets import UserAccessMixin from care.facility.api.viewsets.mixins.history import HistoryMixin @@ -44,11 +55,14 @@ PatientRegistration, PatientSearch, ShiftingRequest, + PatientHealthDetails, ) from care.facility.models.base import covert_choice_dict from care.facility.models.bed import AssetBed, ConsultationBed from care.facility.models.patient_base import DISEASE_STATUS_DICT -from care.facility.tasks.patient.discharge_report import generate_discharge_report +from care.facility.tasks.patient.discharge_report import ( + generate_discharge_report, +) from care.users.models import User from care.utils.cache.cache_allowed_facilities import get_accessible_facilities from care.utils.filters import CareChoiceFilter, MultiSelectFilter @@ -68,10 +82,13 @@ class PatientFilterSet(filters.FilterSet): disease_status = CareChoiceFilter(choice_dict=DISEASE_STATUS_DICT) facility = filters.UUIDFilter(field_name="facility__external_id") facility_type = CareChoiceFilter( - field_name="facility__facility_type", choice_dict=REVERSE_FACILITY_TYPES + field_name="facility__facility_type", + choice_dict=REVERSE_FACILITY_TYPES, ) phone_number = filters.CharFilter(field_name="phone_number") - emergency_phone_number = filters.CharFilter(field_name="emergency_phone_number") + emergency_phone_number = filters.CharFilter( + field_name="emergency_phone_number" + ) allow_transfer = filters.BooleanFilter(field_name="allow_transfer") name = filters.CharFilter(field_name="name", lookup_expr="icontains") ip_no = filters.CharFilter( @@ -91,14 +108,14 @@ class PatientFilterSet(filters.FilterSet): 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") + 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") is_active = filters.BooleanFilter(field_name="is_active") # Location Based Filtering @@ -111,7 +128,9 @@ class PatientFilterSet(filters.FilterSet): 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") + state_name = filters.CharFilter( + field_name="state__name", lookup_expr="icontains" + ) # Consultation Fields is_kasp = filters.BooleanFilter(field_name="last_consultation__is_kasp") last_consultation_kasp_enabled_date = filters.DateFromToRangeFilter( @@ -142,11 +161,12 @@ class PatientFilterSet(filters.FilterSet): # 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") # Permission Filters assigned_to = filters.NumberFilter(field_name="assigned_to") # Other Filters - has_bed = filters.BooleanFilter(field_name="has_bed", method="filter_bed_not_null") + has_bed = filters.BooleanFilter( + field_name="has_bed", method="filter_bed_not_null" + ) def filter_bed_not_null(self, queryset, name, value): return queryset.filter( @@ -163,14 +183,21 @@ def filter_queryset(self, request, queryset, view): return queryset.filter( last_consultation__last_daily_round__bed_id__in=AssetBed.objects.filter( asset=request.user.asset - ).values("id"), + ).values( + "id" + ), last_consultation__last_daily_round__bed__isnull=False, ) if not request.user.is_superuser: if request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: queryset = queryset.filter(facility__state=request.user.state) - elif request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(facility__district=request.user.district) + elif ( + request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + ): + queryset = queryset.filter( + facility__district=request.user.district + ) elif view.action != "transfer": allowed_facilities = get_accessible_facilities(request.user) q_filters = Q(facility__id__in=allowed_facilities) @@ -250,7 +277,6 @@ class PatientViewSet( "modified_date", "date_declared_positive", "date_of_result", - "last_vaccinated_date", "last_consultation_admission_date", "last_consultation_discharge_date", "last_consultation_symptoms_onset_date", @@ -349,7 +375,9 @@ def discharge_patient(self, request, *args, **kwargs): patient.allow_transfer = not discharged patient.save(update_fields=["allow_transfer", "is_active"]) last_consultation = ( - PatientConsultation.objects.filter(patient=patient).order_by("-id").first() + PatientConsultation.objects.filter(patient=patient) + .order_by("-id") + .first() ) current_time = localtime(now()) if last_consultation: @@ -385,12 +413,16 @@ def discharge_summary(self, request, *args, **kwargs): @action(detail=True, methods=["POST"]) def transfer(self, request, *args, **kwargs): patient = PatientRegistration.objects.get( - id=PatientSearch.objects.get(external_id=kwargs["external_id"]).patient_id + id=PatientSearch.objects.get( + external_id=kwargs["external_id"] + ).patient_id ) if patient.allow_transfer is False: return Response( - {"Patient": "Cannot Transfer Patient , Source Facility Does Not Allow"}, + { + "Patient": "Cannot Transfer Patient , Source Facility Does Not Allow" + }, status=status.HTTP_406_NOT_ACCEPTABLE, ) patient.is_active = True @@ -400,7 +432,9 @@ def transfer(self, request, *args, **kwargs): serializer.save() patient = PatientRegistration.objects.get( - id=PatientSearch.objects.get(external_id=kwargs["external_id"]).patient_id + id=PatientSearch.objects.get( + external_id=kwargs["external_id"] + ).patient_id ) response_serializer = self.get_serializer_class()(patient) # Update all Active Shifting Request to Rejected @@ -414,7 +448,9 @@ def transfer(self, request, *args, **kwargs): + f"\n The shifting request was auto rejected by the system as the patient was moved to {patient.facility.name}" ) shifting_request.save(update_fields=["status", "comments"]) - return Response(data=response_serializer.data, status=status.HTTP_200_OK) + return Response( + data=response_serializer.data, status=status.HTTP_200_OK + ) class FacilityPatientStatsHistoryFilterSet(filters.FilterSet): @@ -442,9 +478,14 @@ def get_queryset(self): ) if user.is_superuser: return queryset - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + elif ( + self.request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + ): return queryset.filter(facility__district=user.district) - elif self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: + elif ( + self.request.user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"] + ): return queryset.filter(facility__state=user.state) return queryset.filter(facility__users__id__exact=user.id) @@ -497,7 +538,10 @@ def get_queryset(self): data=self.request.query_params, partial=True ) serializer.is_valid(raise_exception=True) - if self.request.user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: + if ( + self.request.user.user_type + >= User.TYPE_VALUE_MAP["DistrictLabAdmin"] + ): search_keys = [ "date_of_birth", "year_of_birth", @@ -506,7 +550,12 @@ def get_queryset(self): "age", ] else: - search_keys = ["date_of_birth", "year_of_birth", "phone_number", "age"] + search_keys = [ + "date_of_birth", + "year_of_birth", + "phone_number", + "age", + ] search_fields = { key: serializer.validated_data[key] for key in search_keys @@ -536,7 +585,9 @@ def get_queryset(self): if name: queryset = ( - queryset.annotate(similarity=TrigramSimilarity("name", name)) + queryset.annotate( + similarity=TrigramSimilarity("name", name) + ) .filter(similarity__gt=0.2) .order_by("-similarity") ) @@ -587,7 +638,9 @@ def get_queryset(self): if user.user_type >= User.TYPE_VALUE_MAP["StateLabAdmin"]: queryset = queryset.filter(patient__facility__state=user.state) elif user.user_type >= User.TYPE_VALUE_MAP["DistrictLabAdmin"]: - queryset = queryset.filter(patient__facility__district=user.district) + queryset = queryset.filter( + patient__facility__district=user.district + ) else: allowed_facilities = get_accessible_facilities(user) q_filters = Q(patient__facility__id__in=allowed_facilities) @@ -607,5 +660,22 @@ def perform_create(self, serializer): {"patient": "Only active patients data can be updated"} ) return serializer.save( - facility=patient.facility, patient=patient, created_by=self.request.user + facility=patient.facility, + patient=patient, + created_by=self.request.user, ) + + +class PatientHealthDetailsViewSet( + RetrieveModelMixin, + CreateModelMixin, + ListModelMixin, + UpdateModelMixin, + GenericViewSet, +): + queryset = PatientHealthDetails.objects.all().select_related( + "facility", "patient" + ) + serializer_class = PatientHealthDetailsSerializer + permission_classes = (IsAuthenticated, DRYPermissions) + lookup_field = "external_id" diff --git a/care/facility/api/viewsets/patient_otp.py b/care/facility/api/viewsets/patient_otp.py index 6bd4b49280..c16936eee5 100644 --- a/care/facility/api/viewsets/patient_otp.py +++ b/care/facility/api/viewsets/patient_otp.py @@ -1,4 +1,3 @@ -from rest_framework import serializers, status from rest_framework.decorators import action from rest_framework import mixins from rest_framework.permissions import AllowAny @@ -6,7 +5,9 @@ from rest_framework.viewsets import GenericViewSet from rest_framework.exceptions import ValidationError from django.conf import settings -from care.facility.api.serializers.patient_otp import PatientMobileOTPSerializer +from care.facility.api.serializers.patient_otp import ( + PatientMobileOTPSerializer, +) from care.facility.models.patient import PatientMobileOTP from care.users.models import phone_number_regex @@ -15,7 +16,8 @@ class PatientMobileOTPViewSet( - mixins.CreateModelMixin, GenericViewSet, + mixins.CreateModelMixin, + GenericViewSet, ): permission_classes = (AllowAny,) serializer_class = PatientMobileOTPSerializer @@ -30,11 +32,15 @@ def login(self, request): try: phone_number_regex(phone_number) except: - raise ValidationError({"phone_number": "Invalid phone number format"}) + raise ValidationError( + {"phone_number": "Invalid phone number format"} + ) if len(otp) != settings.OTP_LENGTH: raise ValidationError({"otp": "Invalid OTP"}) - otp_object = PatientMobileOTP.objects.filter(phone_number=phone_number, otp=otp, is_used=False).first() + otp_object = PatientMobileOTP.objects.filter( + phone_number=phone_number, otp=otp, is_used=False + ).first() if not otp_object: raise ValidationError({"otp": "Invalid OTP"}) @@ -47,4 +53,3 @@ def login(self, request): token["phone_number"] = phone_number return Response({"access": str(token)}) - diff --git a/care/facility/migrations/0313_auto_20220901_2213.py b/care/facility/migrations/0313_auto_20220901_2213.py index 5997316184..569d170cb7 100644 --- a/care/facility/migrations/0313_auto_20220901_2213.py +++ b/care/facility/migrations/0313_auto_20220901_2213.py @@ -6,17 +6,25 @@ class Migration(migrations.Migration): dependencies = [ - ('facility', '0312_patientconsultation_investigation'), + ("facility", "0312_patientconsultation_investigation"), ] operations = [ migrations.AlterField( - model_name='facility', - name='features', - field=multiselectfield.db.fields.MultiSelectField(blank=True, - choices=[(1, 'CT Scan Facility'), (2, 'Maternity Care'), - (3, 'X-Ray facility'), (4, 'Neonatal care'), - (5, 'Operation theater'), (6, 'Blood Bank')], - max_length=11, null=True), + model_name="facility", + name="features", + field=multiselectfield.db.fields.MultiSelectField( + blank=True, + choices=[ + (1, "CT Scan Facility"), + (2, "Maternity Care"), + (3, "X-Ray facility"), + (4, "Neonatal care"), + (5, "Operation theater"), + (6, "Blood Bank"), + ], + max_length=11, + null=True, + ), ), ] diff --git a/care/facility/migrations/0322_patienthealthdetails.py b/care/facility/migrations/0322_patienthealthdetails.py new file mode 100644 index 0000000000..afaf8f7fc2 --- /dev/null +++ b/care/facility/migrations/0322_patienthealthdetails.py @@ -0,0 +1,276 @@ +import uuid +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 care.utils.models.validators + + +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() + + health_details_objs = [] + for patient in patients: + has_allergy = False + allergies = "" + blood_group = None + consultation = None + facility = patient.facility + weight = 0.0 + height = 0.0 + + if patient.allergies: + has_allergy = True + allergies = patient.allergies + + if patient.blood_group is not None: + blood_group = patient.blood_group + + if patient.last_consultation is not None: + facility = patient.last_consultation.facility + consultation = patient.last_consultation + + if patient.last_consultation.weight is not None: + weight = patient.last_consultation.weight + if patient.last_consultation.height is not None: + height = patient.last_consultation.height + + health_details_obj = health_details( + patient=patient, + facility=facility, + weight=weight, + height=height, + consultation=consultation, + has_allergy=has_allergy, + allergies=allergies, + blood_group=blood_group, + ) + + health_details_objs.append(health_details_obj) + + health_details.objects.bulk_create(health_details_objs) + + +def link_data(apps, schema_editor): + patient_consultation = apps.get_model("facility", "PatientConsultation") + health_details = apps.get_model("facility", "PatientHealthDetails") + patient_cons_objs = patient_consultation.objects.all() + + for patient_cons in patient_cons_objs: + patient_cons.last_health_details = health_details.objects.get( + id=patient_cons.patient.id + ) + 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 = [ + ("facility", "0321_merge_20220921_2255"), + ] + + operations = [ + migrations.CreateModel( + name="PatientHealthDetails", + 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)), + ( + "family_details", + models.TextField( + blank=True, + default="", + verbose_name="Patient's Family Details", + ), + ), + ( + "facility", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="health_details", + to="facility.Facility", + ), + ), + ( + "patient", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="health_details", + to="facility.PatientRegistration", + ), + ), + ( + "has_allergy", + models.BooleanField(default=False), + ), + ( + "allergies", + models.TextField( + blank=True, + default="", + verbose_name="Patient's Known Allergies", + ), + ), + ( + "blood_group", + models.CharField( + choices=[ + ("A+", "A+"), + ("A-", "A-"), + ("B+", "B+"), + ("B-", "B-"), + ("AB+", "AB+"), + ("AB-", "AB-"), + ("O+", "O+"), + ("O-", "O-"), + ("UNK", "UNKNOWN"), + ], + max_length=4, + null=True, + verbose_name="Blood Group of Patient", + ), + ), + ( + "consultation", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="facility.PatientConsultation", + ), + ), + ( + "weight", + models.FloatField( + default=None, + null=True, + validators=[ + django.core.validators.MinValueValidator(0) + ], + verbose_name="Patient's Weight in KG", + ), + ), + ( + "height", + models.FloatField( + default=None, + null=True, + validators=[ + django.core.validators.MinValueValidator(0) + ], + verbose_name="Patient's Height in CM", + ), + ), + ], + options={ + "abstract": False, + }, + bases=( + models.Model, + 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", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="facility.PatientHealthDetails", + ), + ), + 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/0323_merge_20221005_1742.py b/care/facility/migrations/0323_merge_20221005_1742.py new file mode 100644 index 0000000000..b55835ddb6 --- /dev/null +++ b/care/facility/migrations/0323_merge_20221005_1742.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-10-05 12:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0322_fileupload_is_archived'), + ('facility', '0322_patienthealthdetails'), + ] + + operations = [ + ] 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/migrations/0326_merge_20221019_1159.py b/care/facility/migrations/0326_merge_20221019_1159.py new file mode 100644 index 0000000000..13b55f380e --- /dev/null +++ b/care/facility/migrations/0326_merge_20221019_1159.py @@ -0,0 +1,14 @@ +# Generated by Django 2.2.11 on 2022-10-19 06:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('facility', '0323_fileupload_archive_reason'), + ('facility', '0325_medicalhistory_consultation'), + ] + + operations = [ + ] diff --git a/care/facility/models/json_schema/consultation.py b/care/facility/models/json_schema/consultation.py index 95caa25db4..91e68402c2 100644 --- a/care/facility/models/json_schema/consultation.py +++ b/care/facility/models/json_schema/consultation.py @@ -1,7 +1,7 @@ from care.facility.models.json_schema.common import DATETIME_REGEX LINES_CATHETERS = { - "$schema": f"http://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": [ { diff --git a/care/facility/models/notification.py b/care/facility/models/notification.py index d5a40db4f8..a43b1a4cb8 100644 --- a/care/facility/models/notification.py +++ b/care/facility/models/notification.py @@ -1,10 +1,10 @@ import enum +from django.contrib.postgres.fields import JSONField from django.db import models from care.facility.models import FacilityBaseModel from care.users.models import User -from django.contrib.postgres.fields import JSONField class Notification(FacilityBaseModel): @@ -41,12 +41,24 @@ class Event(enum.Enum): EventChoices = [(e.value, e.name) for e in Event] intended_for = models.ForeignKey( - User, on_delete=models.SET_NULL, null=True, related_name="notification_intended_for", + User, + on_delete=models.SET_NULL, + null=True, + related_name="notification_intended_for", + ) + medium_sent = models.IntegerField( + choices=MediumChoices, default=Medium.SYSTEM.value + ) + caused_by = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + related_name="notification_caused_by", ) - medium_sent = models.IntegerField(choices=MediumChoices, default=Medium.SYSTEM.value) - caused_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, related_name="notification_caused_by",) read_at = models.DateTimeField(null=True, blank=True) - event_type = models.IntegerField(choices=EventTypeChoices, default=EventType.SYSTEM_GENERATED.value) + event_type = models.IntegerField( + choices=EventTypeChoices, default=EventType.SYSTEM_GENERATED.value + ) event = models.IntegerField(choices=EventChoices, default=Event.MESSAGE.value) message = models.TextField(max_length=2000, null=True, default=None) caused_objects = JSONField(null=True, blank=True, default=dict) diff --git a/care/facility/models/patient.py b/care/facility/models/patient.py index 618cc5929e..cd0772a6e0 100644 --- a/care/facility/models/patient.py +++ b/care/facility/models/patient.py @@ -8,7 +8,6 @@ from simple_history.models import HistoricalRecords from care.facility.models import ( - DISEASE_CHOICES, DiseaseStatusEnum, District, Facility, @@ -34,6 +33,7 @@ REVERSE_CATEGORY_CHOICES, REVERSE_COVID_CATEGORY_CHOICES, REVERSE_DISEASE_STATUS_CHOICES, + VACCINE_CHOICES, ) from care.facility.models.patient_consultation import PatientConsultation from care.users.models import ( @@ -64,17 +64,6 @@ class SourceEnum(enum.Enum): SourceChoices = [(e.value, e.name) for e in SourceEnum] - class vaccineEnum(enum.Enum): - COVISHIELD = "CoviShield" - COVAXIN = "Covaxin" - SPUTNIK = "Sputnik" - MODERNA = "Moderna" - PFIZER = "Pfizer" - JANSSEN = "Janssen" - SINOVAC = "Sinovac" - - vaccineChoices = [(e.value, e.name) for e in vaccineEnum] - class ActionEnum(enum.Enum): PENDING = 10 SPECIALIST_REQUIRED = 30 @@ -137,7 +126,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") @@ -151,7 +142,7 @@ class TestTypeEnum(enum.Enum): blank=False, max_length=4, verbose_name="Blood Group of Patient", - ) + ) # deprecated contact_with_confirmed_carrier = models.BooleanField( default=False, verbose_name="Confirmed Contact with a Covid19 Carrier" @@ -172,7 +163,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, @@ -181,15 +174,23 @@ class TestTypeEnum(enum.Enum): ) allergies = models.TextField( - default="", blank=True, verbose_name="Patient's Known Allergies" - ) + default="", + blank=True, + verbose_name="Patient's Known Allergies", + ) # deprecated present_health = models.TextField( - default="", blank=True, verbose_name="Patient's Current Health Details" - ) + 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" - ) + 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" ) @@ -249,7 +250,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 +264,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,29 +335,45 @@ 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, null=False, blank=False, validators=[MinValueValidator(0), MaxValueValidator(3)], - ) + ) # deprecated + vaccine_name = models.CharField( - choices=vaccineChoices, default=None, null=True, blank=False, max_length=15 - ) + choices=VACCINE_CHOICES, + default=None, + null=True, + blank=False, + max_length=15, + ) # deprecated covin_id = models.CharField( max_length=15, @@ -360,9 +382,12 @@ class TestTypeEnum(enum.Enum): blank=True, verbose_name="COVID-19 Vaccination ID", ) + last_vaccinated_date = models.DateTimeField( - null=True, blank=True, verbose_name="Date Last Vaccinated" - ) + null=True, + blank=True, + verbose_name="Date Last Vaccinated", + ) # deprecated # Extras cluster_name = models.CharField( @@ -511,7 +536,7 @@ def save(self, *args, **kwargs) -> None: # remarks "number_of_aged_dependents": "Number of people aged above 60 living with the patient", "number_of_chronic_diseased_dependents": "Number of people who have chronic diseases living with the patient", - "blood_group": "Blood Group", + "last_consultation__last_health_details__blood_group": "Blood Group", "is_medical_worker": "Is the Patient a Medical Worker", "contact_with_confirmed_carrier": "Confirmed Contact with a Covid19 Carrier", "contact_with_suspected_carrier": "Suspected Contact with a Covid19 Carrier", @@ -520,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 @@ -542,9 +567,6 @@ def save(self, *args, **kwargs) -> None: "date_declared_positive": "Date Patient is Declared Positive", # Vaccination Data "is_vaccinated": "Is Patient Vaccinated", - "number_of_doses": "Number of Vaccine Doses Recieved", - "vaccine_name": "Vaccine Name", - "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", @@ -558,7 +580,9 @@ def save(self, *args, **kwargs) -> None: CSV_MAKE_PRETTY = { "gender": (lambda x: REVERSE_GENDER_CHOICES[x]), - "blood_group": (lambda x: REVERSE_BLOOD_GROUP_CHOICES[x]), + "last_consultation__last_health_details__blood_group": ( + lambda x: REVERSE_BLOOD_GROUP_CHOICES[x] + ), "disease_status": (lambda x: REVERSE_DISEASE_STATUS_CHOICES[x]), "is_medical_worker": pretty_boolean, "will_donate_blood": pretty_boolean, @@ -673,7 +697,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, @@ -696,30 +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() @@ -766,3 +768,130 @@ class PatientNotes(FacilityBaseModel, PatientRelatedPermissionMixin): null=True, ) note = models.TextField(default="", blank=True) + + +class PatientHealthDetails(PatientBaseModel, PatientRelatedPermissionMixin): + patient = models.ForeignKey( + PatientRegistration, + on_delete=models.CASCADE, + related_name="health_details", + ) + facility = models.ForeignKey( + 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" + ) + + has_allergy = models.BooleanField(default=False) + allergies = models.TextField( + default="", blank=True, verbose_name="Patient's Known Allergies" + ) + + blood_group = models.CharField( + choices=BLOOD_GROUP_CHOICES, + null=True, + blank=False, + max_length=4, + verbose_name="Blood Group of Patient", + ) + + height = models.FloatField( + default=None, + null=True, + verbose_name="Patient's Height in CM", + validators=[MinValueValidator(0)], + ) + + weight = models.FloatField( + default=None, + null=True, + 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_base.py b/care/facility/models/patient_base.py index d26a83a961..60fda78a2c 100644 --- a/care/facility/models/patient_base.py +++ b/care/facility/models/patient_base.py @@ -113,7 +113,22 @@ class BedType(enum.Enum): REVERSE_BLOOD_GROUP_CHOICES = reverse_choices(BLOOD_GROUP_CHOICES) REVERSE_DISEASE_STATUS_CHOICES = reverse_choices(DISEASE_STATUS_CHOICES) -REVERSE_COVID_CATEGORY_CHOICES = reverse_choices(COVID_CATEGORY_CHOICES) # Deprecated +REVERSE_COVID_CATEGORY_CHOICES = reverse_choices( + COVID_CATEGORY_CHOICES +) # Deprecated REVERSE_CATEGORY_CHOICES = reverse_choices(CATEGORY_CHOICES) # REVERSE_ADMIT_CHOICES = reverse_choices(ADMIT_CHOICES) REVERSE_BED_TYPE_CHOICES = reverse_choices(BedTypeChoices) + + +class VaccineEnum(enum.Enum): + COVISHIELD = "CoviShield" + COVAXIN = "Covaxin" + SPUTNIK = "Sputnik" + MODERNA = "Moderna" + PFIZER = "Pfizer" + JANSSEN = "Janssen" + SINOVAC = "Sinovac" + + +VACCINE_CHOICES = [(e.value, e.name) for e in VaccineEnum] diff --git a/care/facility/models/patient_consultation.py b/care/facility/models/patient_consultation.py index d6075d6e8d..ec17b7786f 100644 --- a/care/facility/models/patient_consultation.py +++ b/care/facility/models/patient_consultation.py @@ -33,7 +33,9 @@ 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) @@ -101,7 +103,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) @@ -111,11 +116,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( @@ -126,6 +137,20 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): default=None, ) + last_health_details = models.ForeignKey( + "PatientHealthDetails", + on_delete=models.SET_NULL, + null=True, + default=None, + ) + + last_medical_history = models.ForeignKey( + "MedicalHistory", + on_delete=models.SET_NULL, + null=True, + default=None, + ) + # Physical Information height = models.FloatField( @@ -133,13 +158,15 @@ class PatientConsultation(PatientBaseModel, PatientRelatedPermissionMixin): null=True, verbose_name="Patient's Height in CM", validators=[MinValueValidator(0)], - ) + ) # deprecated + weight = models.FloatField( default=None, null=True, verbose_name="Patient's Weight in KG", validators=[MinValueValidator(0)], - ) + ) # deprecated + HBA1C = models.FloatField( default=None, null=True, 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/notification_handler.py b/care/utils/notification_handler.py index 8689bb5dcf..0a8f593b68 100644 --- a/care/utils/notification_handler.py +++ b/care/utils/notification_handler.py @@ -10,7 +10,10 @@ from care.facility.models.notification import Notification from care.facility.models.patient import PatientRegistration from care.facility.models.patient_consultation import PatientConsultation -from care.facility.models.patient_investigation import InvestigationSession, InvestigationValue +from care.facility.models.patient_investigation import ( + InvestigationSession, + InvestigationValue, +) from care.facility.models.shifting import ShiftingRequest from care.users.models import User from care.utils.sms.sendSMS import sendSMS @@ -61,10 +64,14 @@ def __init__( if not isinstance(event, Notification.Event): raise NotificationCreationException("Event Invalid") if not isinstance(caused_by, User): - raise NotificationCreationException("edited_by must be an instance of a user") + raise NotificationCreationException( + "edited_by must be an instance of a user" + ) if facility: if not isinstance(facility, Facility): - raise NotificationCreationException("facility must be an instance of Facility") + raise NotificationCreationException( + "facility must be an instance of Facility" + ) mediums = [] if notification_mediums: for medium in notification_mediums: @@ -115,16 +122,19 @@ def serialize_extra_data(self, extra_data): if not extra_data: return None for key in extra_data: - extra_data[key] = {"model_name": extra_data[key].__class__.__name__, "model_id": extra_data[key].id} + extra_data[key] = { + "model_name": extra_data[key].__class__.__name__, + "model_id": extra_data[key].id, + } return extra_data def deserialize_extra_data(self, extra_data): if not extra_data: return None for key in extra_data: - extra_data[key] = apps.get_model("facility.{}".format(extra_data[key]["model_name"])).objects.get( - id=extra_data[key]["model_id"] - ) + extra_data[key] = apps.get_model( + "facility.{}".format(extra_data[key]["model_name"]) + ).objects.get(id=extra_data[key]["model_id"]) return extra_data def generate_extra_users(self): @@ -134,7 +144,9 @@ def generate_extra_users(self): if isinstance(self.caused_object, PatientRegistration): if self.caused_object.last_consultation: if self.caused_object.last_consultation.assigned_to: - self.extra_users.append(self.caused_object.last_consultation.assigned_to.id) + self.extra_users.append( + self.caused_object.last_consultation.assigned_to.id + ) if isinstance(self.caused_object, InvestigationSession): if self.extra_data["consultation"].assigned_to: self.extra_users.append(self.extra_data["consultation"].assigned_to.id) @@ -163,20 +175,26 @@ def generate_system_message(self): elif isinstance(self.caused_object, PatientConsultation): if self.event == Notification.Event.PATIENT_CONSULTATION_CREATED.value: message = "Consultation for Patient {} was created by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) elif self.event == Notification.Event.PATIENT_CONSULTATION_UPDATED.value: message = "Consultation for Patient {} was updated by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) if self.event == Notification.Event.PATIENT_CONSULTATION_DELETED.value: message = "Consultation for Patient {} was deleted by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name() + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) elif isinstance(self.caused_object, InvestigationSession): if self.event == Notification.Event.INVESTIGATION_SESSION_CREATED.value: - message = "Investigation Session for Patient {} was created by {}".format( - self.extra_data["consultation"].patient.name, self.caused_by.get_full_name() + message = ( + "Investigation Session for Patient {} was created by {}".format( + self.extra_data["consultation"].patient.name, + self.caused_by.get_full_name(), + ) ) elif isinstance(self.caused_object, InvestigationValue): if self.event == Notification.Event.INVESTIGATION_UPDATED.value: @@ -186,13 +204,19 @@ def generate_system_message(self): self.caused_by.get_full_name(), ) elif isinstance(self.caused_object, DailyRound): - if self.event == Notification.Event.PATIENT_CONSULTATION_UPDATE_CREATED.value: + if ( + self.event + == Notification.Event.PATIENT_CONSULTATION_UPDATE_CREATED.value + ): message = "Consultation for Patient {} at facility {} was created by {}".format( self.caused_object.consultation.patient.name, self.caused_object.consultation.facility.name, self.caused_by.get_full_name(), ) - elif self.event == Notification.Event.PATIENT_CONSULTATION_UPDATE_UPDATED.value: + elif ( + self.event + == Notification.Event.PATIENT_CONSULTATION_UPDATE_UPDATED.value + ): message = "Consultation for Patient {} at facility {} was updated by {}".format( self.caused_object.consultation.patient.name, self.caused_object.consultation.facility.name, @@ -201,7 +225,8 @@ def generate_system_message(self): elif isinstance(self.caused_object, ShiftingRequest): if self.event == Notification.Event.SHIFTING_UPDATED.value: message = "Shifting for Patient {} was updated by {}".format( - self.caused_object.patient.name, self.caused_by.get_full_name(), + self.caused_object.patient.name, + self.caused_by.get_full_name(), ) return message @@ -246,31 +271,55 @@ def generate_cause_objects(self): if isinstance(self.caused_object, PatientRegistration): self.caused_objects["patient"] = str(self.caused_object.external_id) if self.caused_object.facility: - self.caused_objects["facility"] = str(self.caused_object.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.facility.external_id + ) if isinstance(self.caused_object, PatientConsultation): self.caused_objects["consultation"] = str(self.caused_object.external_id) self.caused_objects["patient"] = str(self.caused_object.patient.external_id) if self.caused_object.patient.facility: - self.caused_objects["facility"] = str(self.caused_object.patient.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.patient.facility.external_id + ) if isinstance(self.caused_object, InvestigationSession): - self.caused_objects["consultation"] = str(self.extra_data["consultation"].external_id) - self.caused_objects["patient"] = str(self.extra_data["consultation"].patient.external_id) + self.caused_objects["consultation"] = str( + self.extra_data["consultation"].external_id + ) + self.caused_objects["patient"] = str( + self.extra_data["consultation"].patient.external_id + ) if self.extra_data["consultation"].patient.facility: - self.caused_objects["facility"] = str(self.extra_data["consultation"].patient.facility.external_id) + self.caused_objects["facility"] = str( + self.extra_data["consultation"].patient.facility.external_id + ) self.caused_objects["session"] = str(self.caused_object.external_id) if isinstance(self.caused_object, InvestigationValue): - self.caused_objects["consultation"] = str(self.caused_object.consultation.external_id) - self.caused_objects["patient"] = str(self.caused_object.consultation.patient.external_id) + self.caused_objects["consultation"] = str( + self.caused_object.consultation.external_id + ) + self.caused_objects["patient"] = str( + self.caused_object.consultation.patient.external_id + ) if self.caused_object.consultation.patient.facility: - self.caused_objects["facility"] = str(self.caused_object.consultation.patient.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.consultation.patient.facility.external_id + ) self.caused_objects["session"] = str(self.caused_object.session.external_id) - self.caused_objects["investigation"] = str(self.caused_object.investigation.external_id) + self.caused_objects["investigation"] = str( + self.caused_object.investigation.external_id + ) if isinstance(self.caused_object, DailyRound): - self.caused_objects["consultation"] = str(self.caused_object.consultation.external_id) - self.caused_objects["patient"] = str(self.caused_object.consultation.patient.external_id) + self.caused_objects["consultation"] = str( + self.caused_object.consultation.external_id + ) + self.caused_objects["patient"] = str( + self.caused_object.consultation.patient.external_id + ) self.caused_objects["daily_round"] = str(self.caused_object.id) if self.caused_object.consultation.patient.facility: - self.caused_objects["facility"] = str(self.caused_object.consultation.facility.external_id) + self.caused_objects["facility"] = str( + self.caused_object.consultation.facility.external_id + ) if isinstance(self.caused_object, ShiftingRequest): self.caused_objects["shifting"] = str(self.caused_object.external_id) @@ -313,18 +362,26 @@ def send_webpush_user(self, user, message): webpush( subscription_info={ "endpoint": user.pf_endpoint, - "keys": {"p256dh": user.pf_p256dh, "auth": user.pf_auth}, + "keys": { + "p256dh": user.pf_p256dh, + "auth": user.pf_auth, + }, }, data=message, vapid_private_key=settings.VAPID_PRIVATE_KEY, - vapid_claims={"sub": "mailto:info@coronasafe.network",}, + vapid_claims={ + "sub": "mailto:info@coronasafe.network", + }, ) except WebPushException as ex: print("Web Push Failed with Exception: {}", repr(ex)) if ex.response and ex.response.json(): extra = ex.response.json() print( - "Remote service replied with a {}:{}, {}", extra.code, extra.errno, extra.message, + "Remote service replied with a {}:{}, {}", + extra.code, + extra.errno, + extra.message, ) except Exception as e: print("Error When Doing WebPush", e) @@ -333,8 +390,15 @@ def generate(self): if not self.worker_initiated: return for medium in self.notification_mediums: - if medium == Notification.Medium.SMS.value and settings.SEND_SMS_NOTIFICATION: - sendSMS(self.generate_sms_phone_numbers(), self.generate_sms_message(), many=True) + if ( + medium == Notification.Medium.SMS.value + and settings.SEND_SMS_NOTIFICATION + ): + sendSMS( + self.generate_sms_phone_numbers(), + self.generate_sms_message(), + many=True, + ) elif medium == Notification.Medium.SYSTEM.value: if not self.message: self.message = self.generate_system_message() @@ -344,9 +408,18 @@ def generate(self): ) if not self.defer_notifications: self.send_webpush_user( - user, json.dumps({"external_id": str(notification_obj.external_id), "title": self.message}) + user, + json.dumps( + { + "external_id": str(notification_obj.external_id), + "title": self.message, + } + ), ) - elif medium == Notification.Medium.WHATSAPP.value and settings.ENABLE_WHATSAPP: + elif ( + medium == Notification.Medium.WHATSAPP.value + and settings.ENABLE_WHATSAPP + ): for user in self.generate_whatsapp_users(): number = user.alt_phone_number message = self.generate_whatsapp_message() @@ -354,4 +427,3 @@ def generate(self): user, message, Notification.Medium.WHATSAPP.value ) sendWhatsappMessage(number, message, notification_obj.external_id) - 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): diff --git a/config/api_router.py b/config/api_router.py index dd949a3a1d..fcd65e4859 100644 --- a/config/api_router.py +++ b/config/api_router.py @@ -34,6 +34,7 @@ from care.facility.api.viewsets.notification import NotificationViewSet from care.facility.api.viewsets.patient import ( FacilityPatientStatsHistoryViewSet, + PatientHealthDetailsViewSet, PatientNotesViewSet, PatientSearchViewSet, PatientViewSet, @@ -110,6 +111,7 @@ router.register("patient", PatientViewSet) router.register("consultation", PatientConsultationViewSet) +router.register("health_details", PatientHealthDetailsViewSet) router.register("external_result", PatientExternalTestViewSet)