Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Symptoms table #2186

Merged
merged 27 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c3b7997
consultation depth backend
hrit2773 May 21, 2024
4cf1aa3
refactor
sainak May 21, 2024
4f35938
fix migrations
sainak May 21, 2024
75cf838
fix test and dummy data
sainak May 21, 2024
0925a27
add is_migrated field
sainak May 21, 2024
0d10da3
add created by to symptoms bulk create
sainak May 21, 2024
b279104
fix discharge summary
sainak May 21, 2024
75913e2
make onset date non nullable
sainak May 21, 2024
23d099d
fixes unknown field excluded
rithviknishad May 21, 2024
1adfb8e
fix tests
sainak May 21, 2024
08b98ed
fix validations
sainak May 21, 2024
424976d
update bulk migration to exclude symptom if already created earlier f…
sainak May 21, 2024
10c94a3
add clinical_impression_status to indicate symptom status
sainak May 21, 2024
535a638
update migrations
sainak May 21, 2024
ddf3979
review suggestions
sainak May 21, 2024
c2f22fc
add trigger for marked as errors
sainak May 21, 2024
3cf9d34
fix validation
sainak May 21, 2024
b189ff4
fix updates
sainak May 21, 2024
25f522c
rename consultation symptom to encounter symptom
sainak May 21, 2024
6b2be52
fix unable to mark as entered in error
rithviknishad May 21, 2024
0041acc
update discharge summary pdf
sainak May 21, 2024
b193a6e
add test cases and minor fixes
sainak May 21, 2024
ce07839
allow create symptoms to be empty
sainak May 21, 2024
5da5830
update migration to ignore asymptomatic symptom
sainak May 21, 2024
26ad20f
Merge branch 'develop' into sainak/feat/symptoms-table
rithviknishad May 22, 2024
a78de17
rebase migrations
rithviknishad May 22, 2024
d63fb4d
Merge branch 'develop' into sainak/feat/symptoms-table
sainak May 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions care/facility/api/serializers/consultation_symptom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from copy import copy

from django.db import transaction
from django.utils.timezone import now
from rest_framework import serializers

from care.facility.events.handler import create_consultation_events
from care.facility.models.consultation_symptom import (
ClinicalImpressionStatus,
ConsultationSymptom,
Symptom,
)
from care.users.api.serializers.user import UserBaseMinimumSerializer


class ConsultationSymptomSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source="external_id", read_only=True)
created_by = UserBaseMinimumSerializer(read_only=True)
updated_by = UserBaseMinimumSerializer(read_only=True)

class Meta:
model = ConsultationSymptom
exclude = (
"consultation",
"external_id",
"deleted",
)
read_only_fields = (
"created_date",
"modified_date",
"is_migrated",
)

def validate_onset_date(self, value):
if value and value > now():
raise serializers.ValidationError("Onset date cannot be in the future")
return value

def validate(self, attrs):
validated_data = super().validate(attrs)
consultation = (
self.instance.consultation
if self.instance
else self.context["consultation"]
)

onset_date = (
self.instance.onset_date
if self.instance
else validated_data.get("onset_date")
)
if cure_date := validated_data.get("cure_date"):
if cure_date < onset_date:
raise serializers.ValidationError(
{"cure_date": "Cure date should be after onset date"}
)

if validated_data.get("symptom") != Symptom.OTHERS and validated_data.get(
"other_symptom"
):
raise serializers.ValidationError(
{
"other_symptom": "Other symptom should be empty when symptom is not OTHERS"
}
)

if validated_data.get("symptom") == Symptom.OTHERS and not validated_data.get(
"other_symptom"
):
raise serializers.ValidationError(
{
"other_symptom": "Other symptom should not be empty when symptom is OTHERS"
}
)

if ConsultationSymptom.objects.filter(
consultation=consultation,
symptom=validated_data.get("symptom"),
other_symptom=validated_data.get("other_symptom") or "",
cure_date__isnull=True,
clinical_impression_status=ClinicalImpressionStatus.IN_PROGRESS,
).exists():
raise serializers.ValidationError(
{"symptom": "An active symptom with the same details already exists"}
)

return validated_data

def create(self, validated_data):
validated_data["consultation"] = self.context["consultation"]
validated_data["created_by"] = self.context["request"].user

with transaction.atomic():
instance: ConsultationSymptom = super().create(validated_data)

create_consultation_events(
instance.consultation_id,
instance,
instance.created_by_id,
instance.created_date,
)

return instance

def update(self, instance, validated_data):
validated_data["updated_by"] = self.context["request"].user

with transaction.atomic():
old_instance = copy(instance)
instance = super().update(instance, validated_data)

create_consultation_events(
instance.consultation_id,
instance,
instance.updated_by_id,
instance.modified_date,
old=old_instance,
)

return instance


class ConsultationCreateSymptomSerializer(serializers.ModelSerializer):
class Meta:
model = ConsultationSymptom
fields = ("symptom", "other_symptom", "onset_date")
5 changes: 1 addition & 4 deletions care/facility/api/serializers/daily_round.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from care.facility.models.bed import Bed
from care.facility.models.daily_round import DailyRound
from care.facility.models.notification import Notification
from care.facility.models.patient_base import SYMPTOM_CHOICES, SuggestionChoices
from care.facility.models.patient_base import SuggestionChoices
from care.facility.models.patient_consultation import PatientConsultation
from care.users.api.serializers.user import UserBaseMinimumSerializer
from care.utils.notification_handler import NotificationGenerator
Expand All @@ -24,9 +24,6 @@

class DailyRoundSerializer(serializers.ModelSerializer):
id = serializers.CharField(source="external_id", read_only=True)
additional_symptoms = serializers.MultipleChoiceField(
choices=SYMPTOM_CHOICES, required=False
)
deprecated_covid_category = ChoiceField(
choices=COVID_CATEGORY_CHOICES, required=False
) # Deprecated
Expand Down
78 changes: 74 additions & 4 deletions care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
ConsultationCreateDiagnosisSerializer,
ConsultationDiagnosisSerializer,
)
from care.facility.api.serializers.consultation_symptom import (
ConsultationCreateSymptomSerializer,
ConsultationSymptomSerializer,
)
from care.facility.api.serializers.daily_round import DailyRoundSerializer
from care.facility.api.serializers.facility import FacilityBasicInfoSerializer
from care.facility.events.handler import create_consultation_events
Expand All @@ -32,13 +36,17 @@
)
from care.facility.models.asset import AssetLocation
from care.facility.models.bed import Bed, ConsultationBed
from care.facility.models.consultation_symptom import (
ClinicalImpressionStatus,
ConsultationSymptom,
Symptom,
)
from care.facility.models.icd11_diagnosis import (
ConditionVerificationStatus,
ConsultationDiagnosis,
)
from care.facility.models.notification import Notification
from care.facility.models.patient_base import (
SYMPTOM_CHOICES,
NewDischargeReasonEnum,
RouteToFacility,
SuggestionChoices,
Expand Down Expand Up @@ -66,7 +74,6 @@ class PatientConsultationSerializer(serializers.ModelSerializer):
source="suggestion",
)

symptoms = serializers.MultipleChoiceField(choices=SYMPTOM_CHOICES)
deprecated_covid_category = ChoiceField(
choices=COVID_CATEGORY_CHOICES, required=False
)
Expand Down Expand Up @@ -151,7 +158,13 @@ class PatientConsultationSerializer(serializers.ModelSerializer):
help_text="Bulk create diagnoses for the consultation upon creation",
)
diagnoses = ConsultationDiagnosisSerializer(many=True, read_only=True)

create_symptoms = ConsultationCreateSymptomSerializer(
many=True,
write_only=True,
required=False,
help_text="Bulk create symptoms for the consultation upon creation",
)
symptoms = ConsultationSymptomSerializer(many=True, read_only=True)
medico_legal_case = serializers.BooleanField(default=False, required=False)

def get_discharge_prescription(self, consultation):
Expand Down Expand Up @@ -332,6 +345,7 @@ def create(self, validated_data):
raise ValidationError({"route_to_facility": "This field is required"})

create_diagnosis = validated_data.pop("create_diagnoses")
create_symptoms = validated_data.pop("create_symptoms")
action = -1
review_interval = -1
if "action" in validated_data:
Expand Down Expand Up @@ -407,6 +421,19 @@ def create(self, validated_data):
]
)

symptoms = ConsultationSymptom.objects.bulk_create(
ConsultationSymptom(
consultation=consultation,
symptom=obj.get("symptom"),
onset_date=obj.get("onset_date"),
cure_date=obj.get("cure_date"),
clinical_impression_status=obj.get("clinical_impression_status"),
other_symptom=obj.get("other_symptom") or "",
created_by=self.context["request"].user,
)
for obj in create_symptoms
)

if bed and consultation.suggestion == SuggestionChoices.A:
consultation_bed = ConsultationBed(
bed=bed,
Expand Down Expand Up @@ -444,7 +471,7 @@ def create(self, validated_data):

create_consultation_events(
consultation.id,
(consultation, *diagnosis),
(consultation, *diagnosis, *symptoms),
consultation.created_by.id,
consultation.created_date,
)
Expand Down Expand Up @@ -502,6 +529,46 @@ def validate_create_diagnoses(self, value):

return value

def validate_create_symptoms(self, value):
if self.instance:
raise ValidationError("Bulk create symptoms is not allowed on update")

if len(value) == 0:
raise ValidationError("Atleast one symptom is required")

counter: set[int | str] = set()
for obj in value:
item: int | str = obj["symptom"]
if obj["symptom"] == Symptom.OTHERS:
item: str = obj["other_symptom"].strip().lower()
if item in counter:
# Reject if duplicate symptoms are provided
raise ValidationError("Duplicate symptoms are not allowed")
counter.add(item)

current_time = now()
for obj in value:
if not obj.get("onset_date"):
raise ValidationError(
{"onset_date": "This field is required for all symptoms"}
)

if obj["onset_date"] > current_time:
raise ValidationError(
{"onset_date": "Onset date cannot be in the future"}
)

if cure_date := obj.get("cure_date"):
if cure_date < obj["onset_date"]:
raise ValidationError(
{"cure_date": "Cure date should be after onset date"}
)
obj["clinical_impression_status"] = ClinicalImpressionStatus.COMPLETED
else:
obj["clinical_impression_status"] = ClinicalImpressionStatus.IN_PROGRESS

return value

def validate_encounter_date(self, value):
if value < MIN_ENCOUNTER_DATE:
raise ValidationError(
Expand Down Expand Up @@ -623,6 +690,9 @@ def validate(self, attrs):
if not self.instance and "create_diagnoses" not in validated:
raise ValidationError({"create_diagnoses": ["This field is required."]})

if not self.instance and "create_symptoms" not in validated:
raise ValidationError({"create_symptoms": ["This field is required."]})

return validated


Expand Down
4 changes: 2 additions & 2 deletions care/facility/api/serializers/patient_icmr.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers

from care.facility.models import DISEASE_CHOICES, SAMPLE_TYPE_CHOICES, SYMPTOM_CHOICES
from care.facility.models import DISEASE_CHOICES, SAMPLE_TYPE_CHOICES
from care.facility.models.patient_icmr import (
PatientConsultationICMR,
PatientIcmr,
Expand Down Expand Up @@ -124,7 +124,7 @@ class Meta:

class ICMRMedicalConditionSerializer(serializers.ModelSerializer):
date_of_onset_of_symptoms = serializers.DateField()
symptoms = serializers.ListSerializer(child=ChoiceField(choices=SYMPTOM_CHOICES))
symptoms = serializers.ListSerializer(child=serializers.CharField())
hospitalization_date = serializers.DateField()
hospital_phone_number = serializers.CharField(
source="consultation.facility.phone_number"
Expand Down
59 changes: 59 additions & 0 deletions care/facility/api/viewsets/consultation_symptoms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from django.shortcuts import get_object_or_404
from django_filters import rest_framework as filters
from dry_rest_permissions.generics import DRYPermissions
from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet

from care.facility.api.serializers.consultation_symptom import (
ConsultationSymptomSerializer,
)
from care.facility.models.consultation_symptom import (
ClinicalImpressionStatus,
ConsultationSymptom,
)
from care.utils.queryset.consultation import get_consultation_queryset


class ConsultationSymptomFilter(filters.FilterSet):
is_cured = filters.BooleanFilter(method="filter_is_cured")

def filter_is_cured(self, queryset, name, value):
if value:
return queryset.filter(cure_date__isnull=False)
return queryset.filter(cure_date__isnull=True)


class ConsultationSymptomViewSet(ModelViewSet):
serializer_class = ConsultationSymptomSerializer
permission_classes = (IsAuthenticated, DRYPermissions)
queryset = ConsultationSymptom.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ConsultationSymptomFilter
lookup_field = "external_id"

def get_consultation_obj(self):
return get_object_or_404(
get_consultation_queryset(self.request.user).filter(
external_id=self.kwargs["consultation_external_id"]
)
)

def get_queryset(self):
consultation = self.get_consultation_obj()
return self.queryset.filter(consultation_id=consultation.id)

def get_serializer_context(self):
context = super().get_serializer_context()
context["consultation"] = self.get_consultation_obj()
return context

def perform_destroy(self, instance):
serializer = self.get_serializer(
instance,
data={
"clinical_impression_status": ClinicalImpressionStatus.ENTERED_IN_ERROR
},
partial=True,
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
4 changes: 0 additions & 4 deletions care/facility/api/viewsets/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,6 @@ def filter_by_category(self, queryset, name, value):
last_consultation_discharge_date = filters.DateFromToRangeFilter(
field_name="last_consultation__discharge_date"
)
last_consultation_symptoms_onset_date = filters.DateFromToRangeFilter(
field_name="last_consultation__symptoms_onset_date"
)
last_consultation_admitted_bed_type_list = MultiSelectFilter(
method="filter_by_bed_type",
)
Expand Down Expand Up @@ -449,7 +446,6 @@ class PatientViewSet(
"last_vaccinated_date",
"last_consultation_encounter_date",
"last_consultation_discharge_date",
"last_consultation_symptoms_onset_date",
]
CSV_EXPORT_LIMIT = 7

Expand Down
Loading
Loading