From c9789254d8e35d5af8d2415c623d355cd1fa755c Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 13:53:53 +0530 Subject: [PATCH 1/9] add fields_to_store parameter event handler --- care/facility/events/handler.py | 57 +++++++++++++++++---------------- care/utils/event_utils.py | 14 +++++++- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/care/facility/events/handler.py b/care/facility/events/handler.py index 4fac4efe03..fe2ff1a85c 100644 --- a/care/facility/events/handler.py +++ b/care/facility/events/handler.py @@ -1,56 +1,49 @@ from datetime import datetime -from celery import shared_task -from django.core import serializers -from django.db import models, transaction +from django.db import transaction from django.db.models import Model from django.db.models.query import QuerySet from django.utils.timezone import now from care.facility.models.events import ChangeType, EventType, PatientConsultationEvent -from care.utils.event_utils import get_changed_fields +from care.utils.event_utils import get_changed_fields, serialize_field -def transform(object_instance: Model, old_instance: Model): - fields = [] +def transform( + object_instance: Model, + old_instance: Model, + fields_to_store: set | None = None, +) -> dict: + fields = set() if old_instance: changed_fields = get_changed_fields(old_instance, object_instance) - fields = [ + fields = { field for field in object_instance._meta.fields if field.name in changed_fields - ] + } else: - fields = object_instance._meta.fields + fields = set(object_instance._meta.fields) - data = {} - for field in fields: - value = getattr(object_instance, field.name) - if isinstance(value, models.Model): - data[field.name] = serializers.serialize("python", [value])[0]["fields"] - elif issubclass(field.__class__, models.Field) and field.choices: - # serialize choice fields with display value - data[field.name] = getattr( - object_instance, f"get_{field.name}_display", lambda: value - )() - else: - data[field.name] = value - return data + if fields_to_store: + fields &= fields_to_store + + return {field.name: serialize_field(object_instance, field) for field in fields} -@shared_task def create_consultation_event_entry( consultation_id: int, object_instance: Model, caused_by: int, created_date: datetime, old_instance: Model = None, + fields_to_store: set | None = None, ): change_type = ChangeType.UPDATED if old_instance else ChangeType.CREATED - data = transform(object_instance, old_instance) + data = transform(object_instance, old_instance, fields_to_store) - fields_to_store = set(data.keys()) + fields_to_store = fields_to_store or set(data.keys()) batch = [] groups = EventType.objects.filter( @@ -103,6 +96,7 @@ def create_consultation_events( caused_by: int, created_date: datetime = None, old: Model | None = None, + fields_to_store: list | set | None = None, ): if created_date is None: created_date = now() @@ -115,9 +109,18 @@ def create_consultation_events( ) for obj in objects: create_consultation_event_entry( - consultation_id, obj, caused_by, created_date + consultation_id, + obj, + caused_by, + created_date, + fields_to_store=set(fields_to_store), ) else: create_consultation_event_entry( - consultation_id, objects, caused_by, created_date, old + consultation_id, + objects, + caused_by, + created_date, + old, + fields_to_store=set(fields_to_store), ) diff --git a/care/utils/event_utils.py b/care/utils/event_utils.py index 27ae5e715d..890139a363 100644 --- a/care/utils/event_utils.py +++ b/care/utils/event_utils.py @@ -2,7 +2,8 @@ from json import JSONEncoder from logging import getLogger -from django.db.models import Model +from django.core.serializers import serialize +from django.db.models import Field, Model from multiselectfield.db.fields import MSFList, MultiSelectField logger = getLogger(__name__) @@ -27,6 +28,17 @@ def get_changed_fields(old: Model, new: Model) -> set[str]: return changed_fields +def serialize_field(object: Model, field: Field): + value = getattr(object, field.name) + if isinstance(value, Model): + # serialize the fields of the related model + return serialize("python", [value])[0]["fields"] + if issubclass(field.__class__, Field) and field.choices: + # serialize choice fields with display value + return getattr(object, f"get_{field.name}_display", lambda: value)() + return value + + def model_diff(old, new): diff = {} for field in new._meta.fields: From 1856d926d6fb304083e67fe70d9f52cd076d9001 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 14:01:30 +0530 Subject: [PATCH 2/9] add event trigger for daily round patch requests --- care/facility/api/serializers/daily_round.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 148cbdf94d..96ee028c51 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -139,6 +139,14 @@ def update(self, instance, validated_data): facility=instance.consultation.patient.facility, ).generate() + create_consultation_events( + instance.consultation_id, + instance, + instance.created_by_id, + instance.created_date, + fields_to_store=set(validated_data.keys()), + ) + return super().update(instance, validated_data) def update_last_daily_round(self, daily_round_obj): From 4a3d4a7fbe0c7b980bfe9ed41402f18edd3ee888 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 14:11:10 +0530 Subject: [PATCH 3/9] fix --- care/facility/events/handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/care/facility/events/handler.py b/care/facility/events/handler.py index fe2ff1a85c..7fe9d8d008 100644 --- a/care/facility/events/handler.py +++ b/care/facility/events/handler.py @@ -113,7 +113,7 @@ def create_consultation_events( obj, caused_by, created_date, - fields_to_store=set(fields_to_store), + fields_to_store=set(fields_to_store) if fields_to_store else None, ) else: create_consultation_event_entry( @@ -122,5 +122,5 @@ def create_consultation_events( caused_by, created_date, old, - fields_to_store=set(fields_to_store), + fields_to_store=set(fields_to_store) if fields_to_store else None, ) From 268e5cf591ef76c99ff7ef9b56bfc5bd46754cf1 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 17:08:19 +0530 Subject: [PATCH 4/9] update event groups for daily rounds --- .../management/commands/load_event_types.py | 153 +++++++++--------- 1 file changed, 75 insertions(+), 78 deletions(-) diff --git a/care/facility/management/commands/load_event_types.py b/care/facility/management/commands/load_event_types.py index 5d3e034295..d70cd39375 100644 --- a/care/facility/management/commands/load_event_types.py +++ b/care/facility/management/commands/load_event_types.py @@ -94,29 +94,33 @@ class Command(BaseCommand): "model": "DailyRound", "children": ( { - "name": "HEALTH", + "name": "DAILY_ROUND_DETAILS", + "fields": ( + "taken_at", + "round_type", + "other_details", + "action", + "review_after", + ), "children": ( { "name": "ROUND_SYMPTOMS", # todo resolve clash with consultation symptoms - "fields": ("additional_symptoms", "other_symptoms"), + "fields": ("additional_symptoms"), }, { "name": "PHYSICAL_EXAMINATION", "fields": ("physical_examination_info",), }, - {"name": "PATIENT_CATEGORY", "fields": ("patient_category",)}, + { + "name": "PATIENT_CATEGORY", + "fields": ("patient_category",), + }, ), }, { "name": "VITALS", "children": ( - { - "name": "TEMPERATURE", - "fields": ( - "temperature", - "temperature_measured_at", # todo remove field - ), - }, + {"name": "TEMPERATURE", "fields": ("temperature",)}, {"name": "SPO2", "fields": ("spo2",)}, {"name": "PULSE", "fields": ("pulse",)}, {"name": "BLOOD_PRESSURE", "fields": ("bp",)}, @@ -124,58 +128,6 @@ class Command(BaseCommand): {"name": "RHYTHM", "fields": ("rhythm", "rhythm_details")}, ), }, - { - "name": "RESPIRATORY", - "children": ( - { - "name": "BILATERAL_AIR_ENTRY", - "fields": ("bilateral_air_entry",), - }, - ), - }, - { - "name": "INTAKE_OUTPUT", - "children": ( - {"name": "INFUSIONS", "fields": ("infusions",)}, - {"name": "IV_FLUIDS", "fields": ("iv_fluids",)}, - {"name": "FEEDS", "fields": ("feeds",)}, - { - "name": "TOTAL_INTAKE", - "fields": ("total_intake_calculated",), - }, - {"name": "OUTPUT", "fields": ("output",)}, - { - "name": "TOTAL_OUTPUT", - "fields": ("total_output_calculated",), - }, - ), - }, - { - "name": "VENTILATOR_MODES", - "fields": ( - "ventilator_interface", - "ventilator_mode", - "ventilator_peep", - "ventilator_pip", - "ventilator_mean_airway_pressure", - "ventilator_resp_rate", - "ventilator_pressure_support", - "ventilator_tidal_volume", - "ventilator_oxygen_modality", - "ventilator_oxygen_modality_oxygen_rate", - "ventilator_oxygen_modality_flow_rate", - "ventilator_fi02", - "ventilator_spo2", - ), - }, - { - "name": "DIALYSIS", - "fields": ( - "pressure_sore", - "dialysis_fluid_balance", - "dialysis_net_balance", - ), - }, { "name": "NEUROLOGICAL", "fields": ( @@ -191,40 +143,84 @@ class Command(BaseCommand): "glasgow_verbal_response", "glasgow_motor_response", "glasgow_total_calculated", - "limb_response_upper_extremity_right", "limb_response_upper_extremity_left", + "limb_response_upper_extremity_right", "limb_response_lower_extremity_left", "limb_response_lower_extremity_right", "consciousness_level", "consciousness_level_detail", + "in_prone_position", ), }, { - "name": "BLOOD_GLUCOSE", - "fields": ("blood_sugar_level",), + "name": "RESPIRATORY_SUPPORT", + "fields": ( + "bilateral_air_entry", + "etco2", + "ventilator_fi02", + "ventilator_interface", + "ventilator_mean_airway_pressure", + "ventilator_mode", + "ventilator_oxygen_modality", + "ventilator_oxygen_modality_flow_rate", + "ventilator_oxygen_modality_oxygen_rate", + "ventilator_peep", + "ventilator_pip", + "ventilator_pressure_support", + "ventilator_resp_rate", + "ventilator_spo2", + "ventilator_tidal_volume", + ), }, { - "name": "DAILY_ROUND_DETAILS", + "name": "ARTERIAL_BLOOD_GAS_ANALYSIS", "fields": ( - "other_details", - "medication_given", - "in_prone_position", - "etco2", - "pain", - "pain_scale_enhanced", - "ph", - "pco2", - "po2", - "hco3", "base_excess", + "hco3", "lactate", - "sodium", + "pco2", + "ph", + "po2", "potassium", + "sodium", + ), + }, + { + "name": "BLOOD_GLUCOSE", + "fields": ( + "blood_sugar_level", "insulin_intake_dose", "insulin_intake_frequency", - "nursing", ), }, + { + "name": "IO_BALANCE", + "children": ( + {"name": "INFUSIONS", "fields": ("infusions",)}, + {"name": "IV_FLUIDS", "fields": ("iv_fluids",)}, + {"name": "FEEDS", "fields": ("feeds",)}, + {"name": "OUTPUT", "fields": ("output",)}, + { + "name": "TOTAL_INTAKE", + "fields": ("total_intake_calculated",), + }, + { + "name": "TOTAL_OUTPUT", + "fields": ("total_output_calculated",), + }, + ), + }, + { + "name": "DIALYSIS", + "fields": ( + "dialysis_fluid_balance", + "dialysis_net_balance", + ), + "children": ( + {"name": "PRESSURE_SORE", "fields": ("pressure_sore",)}, + ), + }, + {"name": "NURSING", "fields": ("nursing",)}, ), }, { @@ -246,6 +242,7 @@ def create_objects( model = event_type.get("model", model) obj, _ = EventType.objects.update_or_create( name=event_type["name"], + fields=event_type.get("fields", []), defaults={ "parent": parent, "model": model, From adc2188d3afbd244af621e6a328c210588ab28eb Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 17:22:47 +0530 Subject: [PATCH 5/9] fix --- care/facility/management/commands/load_event_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/care/facility/management/commands/load_event_types.py b/care/facility/management/commands/load_event_types.py index d70cd39375..07783dac80 100644 --- a/care/facility/management/commands/load_event_types.py +++ b/care/facility/management/commands/load_event_types.py @@ -105,7 +105,7 @@ class Command(BaseCommand): "children": ( { "name": "ROUND_SYMPTOMS", # todo resolve clash with consultation symptoms - "fields": ("additional_symptoms"), + "fields": ("additional_symptoms",), }, { "name": "PHYSICAL_EXAMINATION", From bbf86153edf79172c6d2d8426103750898668477 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 17:34:58 +0530 Subject: [PATCH 6/9] mark removed event groups as inactive --- care/facility/api/viewsets/events.py | 2 +- care/facility/events/handler.py | 2 +- .../facility/management/commands/load_event_types.py | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/care/facility/api/viewsets/events.py b/care/facility/api/viewsets/events.py index b99ad48565..4e0a31a5fe 100644 --- a/care/facility/api/viewsets/events.py +++ b/care/facility/api/viewsets/events.py @@ -20,7 +20,7 @@ class EventTypeViewSet(ReadOnlyModelViewSet): serializer_class = EventTypeSerializer - queryset = EventType.objects.all() + queryset = EventType.objects.filter(is_active=True) permission_classes = (IsAuthenticated,) def get_serializer_class(self) -> type[BaseSerializer]: diff --git a/care/facility/events/handler.py b/care/facility/events/handler.py index 7fe9d8d008..cf4b58536b 100644 --- a/care/facility/events/handler.py +++ b/care/facility/events/handler.py @@ -47,7 +47,7 @@ def create_consultation_event_entry( batch = [] groups = EventType.objects.filter( - model=object_instance.__class__.__name__, fields__len__gt=0 + model=object_instance.__class__.__name__, fields__len__gt=0, is_active=True ).values_list("id", "fields") for group_id, group_fields in groups: if set(group_fields) & fields_to_store: diff --git a/care/facility/management/commands/load_event_types.py b/care/facility/management/commands/load_event_types.py index 07783dac80..d742c2283e 100644 --- a/care/facility/management/commands/load_event_types.py +++ b/care/facility/management/commands/load_event_types.py @@ -235,6 +235,12 @@ class Command(BaseCommand): }, ) + inactive_event_types: Tuple[str, ...] = ( + "RESPIRATORY", + "INTAKE_OUTPUT", + "VENTILATOR_MODES", + ) + def create_objects( self, types: Tuple[EventType, ...], model: str = None, parent: EventType = None ): @@ -242,11 +248,11 @@ def create_objects( model = event_type.get("model", model) obj, _ = EventType.objects.update_or_create( name=event_type["name"], - fields=event_type.get("fields", []), defaults={ "parent": parent, "model": model, "fields": event_type.get("fields", []), + "is_active": True, }, ) if children := event_type.get("children"): @@ -255,6 +261,10 @@ def create_objects( def handle(self, *args, **options): self.stdout.write("Loading Event Types... ", ending="") + EventType.objects.filter(name__in=self.inactive_event_types).update( + is_active=False + ) + self.create_objects(self.consultation_event_types) self.stdout.write(self.style.SUCCESS("OK")) From d791b6dff74439a5f19f21a57aa718b09f91e030 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 20:13:36 +0530 Subject: [PATCH 7/9] fix events not getting create when fields_to_store is present --- care/facility/events/handler.py | 17 ++++++++--------- care/utils/event_utils.py | 5 ++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/care/facility/events/handler.py b/care/facility/events/handler.py index cf4b58536b..66525603c0 100644 --- a/care/facility/events/handler.py +++ b/care/facility/events/handler.py @@ -1,7 +1,7 @@ from datetime import datetime from django.db import transaction -from django.db.models import Model +from django.db.models import Field, Model from django.db.models.query import QuerySet from django.utils.timezone import now @@ -12,9 +12,9 @@ def transform( object_instance: Model, old_instance: Model, - fields_to_store: set | None = None, -) -> dict: - fields = set() + fields_to_store: set[str] | None = None, +) -> dict[str, any]: + fields: set[Field] = set() if old_instance: changed_fields = get_changed_fields(old_instance, object_instance) fields = { @@ -26,7 +26,7 @@ def transform( fields = set(object_instance._meta.fields) if fields_to_store: - fields &= fields_to_store + fields = {field for field in fields if field.name in fields_to_store} return {field.name: serialize_field(object_instance, field) for field in fields} @@ -36,13 +36,12 @@ def create_consultation_event_entry( object_instance: Model, caused_by: int, created_date: datetime, - old_instance: Model = None, - fields_to_store: set | None = None, + old_instance: Model | None = None, + fields_to_store: set[str] | None = None, ): change_type = ChangeType.UPDATED if old_instance else ChangeType.CREATED data = transform(object_instance, old_instance, fields_to_store) - fields_to_store = fields_to_store or set(data.keys()) batch = [] @@ -96,7 +95,7 @@ def create_consultation_events( caused_by: int, created_date: datetime = None, old: Model | None = None, - fields_to_store: list | set | None = None, + fields_to_store: list[str] | set[str] | None = None, ): if created_date is None: created_date = now() diff --git a/care/utils/event_utils.py b/care/utils/event_utils.py index 890139a363..6105d07e26 100644 --- a/care/utils/event_utils.py +++ b/care/utils/event_utils.py @@ -14,7 +14,7 @@ def is_null(data): def get_changed_fields(old: Model, new: Model) -> set[str]: - changed_fields = set() + changed_fields: set[str] = set() for field in new._meta.fields: field_name = field.name if isinstance(field, MultiSelectField): @@ -22,8 +22,7 @@ def get_changed_fields(old: Model, new: Model) -> set[str]: new_val = set(map(str, getattr(new, field_name, []))) if old_val != new_val: changed_fields.add(field_name) - continue - if getattr(old, field_name, None) != getattr(new, field_name, None): + elif getattr(old, field_name, None) != getattr(new, field_name, None): changed_fields.add(field_name) return changed_fields From 172a35b4354ed0e39dd08a4c1dbd27e099c9ad76 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 15 May 2024 21:59:46 +0530 Subject: [PATCH 8/9] fix --- care/facility/api/serializers/daily_round.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/care/facility/api/serializers/daily_round.py b/care/facility/api/serializers/daily_round.py index 96ee028c51..0ec6bda9af 100644 --- a/care/facility/api/serializers/daily_round.py +++ b/care/facility/api/serializers/daily_round.py @@ -139,6 +139,8 @@ def update(self, instance, validated_data): facility=instance.consultation.patient.facility, ).generate() + instance = super().update(instance, validated_data) + create_consultation_events( instance.consultation_id, instance, @@ -147,7 +149,7 @@ def update(self, instance, validated_data): fields_to_store=set(validated_data.keys()), ) - return super().update(instance, validated_data) + return instance def update_last_daily_round(self, daily_round_obj): consultation = daily_round_obj.consultation From afd97a4fa3d03ebcba1169b63523cdf946dc04cb Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 16 May 2024 16:46:58 +0530 Subject: [PATCH 9/9] adds missing pain scale event --- care/facility/management/commands/load_event_types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/care/facility/management/commands/load_event_types.py b/care/facility/management/commands/load_event_types.py index d742c2283e..06a82709ca 100644 --- a/care/facility/management/commands/load_event_types.py +++ b/care/facility/management/commands/load_event_types.py @@ -126,6 +126,7 @@ class Command(BaseCommand): {"name": "BLOOD_PRESSURE", "fields": ("bp",)}, {"name": "RESPIRATORY_RATE", "fields": ("resp",)}, {"name": "RHYTHM", "fields": ("rhythm", "rhythm_details")}, + {"name": "PAIN_SCALE", "fields": ("pain_scale_enhanced",)}, ), }, {