Skip to content

Commit

Permalink
Patient Consultation: Route to Facility 🏥, Admission Date & Time 🕚, R…
Browse files Browse the repository at this point in the history
…ename variable: `verified_by` to `treating_physician` (#1678)

* refactor patient_consultation

* rebase migrations

* reorder fields

* fix existing tests

* add tests

* rebase migrations

* Rename and Alter field route to facility instead.
Rebase migrations.

* update tests

* fix tests, season 2

* Apply suggestions based on Code Review
  • Loading branch information
rithviknishad authored Nov 21, 2023
1 parent fefa1a9 commit e9cd2e6
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 53 deletions.
4 changes: 2 additions & 2 deletions care/abdm/utils/fhir.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ def _practioner(self):
id = str(uuid())
name = (
(
self.consultation.verified_by
and f"{self.consultation.verified_by.first_name} {self.consultation.verified_by.last_name}"
self.consultation.treating_physician
and f"{self.consultation.treating_physician.first_name} {self.consultation.treating_physician.last_name}"
)
or self.consultation.deprecated_verified_by
or f"{self.consultation.created_by.first_name} {self.consultation.created_by.last_name}"
Expand Down
97 changes: 90 additions & 7 deletions care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from care.abdm.utils.api_call import AbdmGateway
from care.facility.api.serializers import TIMESTAMP_FIELDS
from care.facility.api.serializers.asset import AssetLocationSerializer
from care.facility.api.serializers.bed import ConsultationBedSerializer
from care.facility.api.serializers.consultation_diagnosis import (
ConsultationCreateDiagnosisSerializer,
Expand All @@ -22,6 +23,7 @@
Prescription,
PrescriptionType,
)
from care.facility.models.asset import AssetLocation
from care.facility.models.bed import Bed, ConsultationBed
from care.facility.models.icd11_diagnosis import (
ConditionVerificationStatus,
Expand All @@ -31,6 +33,7 @@
from care.facility.models.patient_base import (
DISCHARGE_REASON_CHOICES,
SYMPTOM_CHOICES,
RouteToFacility,
SuggestionChoices,
)
from care.facility.models.patient_consultation import PatientConsultation
Expand Down Expand Up @@ -70,6 +73,29 @@ class PatientConsultationSerializer(serializers.ModelSerializer):
referred_to_external = serializers.CharField(
required=False, allow_null=True, allow_blank=True
)

referred_from_facility_object = FacilityBasicInfoSerializer(
source="referred_from_facility", read_only=True
)
referred_from_facility = ExternalIdSerializerField(
queryset=Facility.objects.all(),
required=False,
)
referred_from_facility_external = serializers.CharField(
required=False, allow_null=True, allow_blank=True
)
referred_by_external = serializers.CharField(
required=False, allow_null=True, allow_blank=True
)

transferred_from_location_object = AssetLocationSerializer(
source="transferred_from_location", read_only=True
)
transferred_from_location = ExternalIdSerializerField(
queryset=AssetLocation.objects.all(),
required=False,
)

patient = ExternalIdSerializerField(queryset=PatientRegistration.objects.all())
facility = ExternalIdSerializerField(read_only=True)

Expand All @@ -78,8 +104,10 @@ class PatientConsultationSerializer(serializers.ModelSerializer):
queryset=User.objects.all(), required=False, allow_null=True
)

verified_by_object = UserBaseMinimumSerializer(source="verified_by", read_only=True)
verified_by = serializers.PrimaryKeyRelatedField(
treating_physician_object = UserBaseMinimumSerializer(
source="treating_physician", read_only=True
)
treating_physician = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), required=False, allow_null=True
)

Expand Down Expand Up @@ -230,6 +258,58 @@ def update(self, instance, validated_data):
return consultation

def create(self, validated_data):
if route_to_facility := validated_data.get("route_to_facility"):
if route_to_facility == RouteToFacility.OUTPATIENT:
validated_data["icu_admission_date"] = None
validated_data["transferred_from_location"] = None
validated_data["referred_from_facility"] = None
validated_data["referred_from_facility_external"] = ""
validated_data["referred_by_external"] = ""

if route_to_facility == RouteToFacility.INTRA_FACILITY_TRANSFER:
validated_data["referred_from_facility"] = None
validated_data["referred_from_facility_external"] = ""
validated_data["referred_by_external"] = ""

if not validated_data.get("transferred_from_location"):
raise ValidationError(
{
"transferred_from_location": [
"This field is required as the patient has been transferred from another location."
]
}
)

if route_to_facility == RouteToFacility.INTER_FACILITY_TRANSFER:
validated_data["transferred_from_location"] = None

if not validated_data.get(
"referred_from_facility"
) and not validated_data.get("referred_from_facility_external"):
raise ValidationError(
{
"referred_from_facility": [
"This field is required as the patient has been referred from another facility."
]
}
)

if validated_data.get("referred_from_facility") and validated_data.get(
"referred_from_facility_external"
):
raise ValidationError(
{
"referred_from_facility": [
"Only one of referred_from_facility and referred_from_facility_external can be set"
],
"referred_from_facility_external": [
"Only one of referred_from_facility and referred_from_facility_external can be set"
],
}
)
else:
raise ValidationError({"route_to_facility": "This field is required"})

create_diagnosis = validated_data.pop("create_diagnoses")
action = -1
review_interval = -1
Expand Down Expand Up @@ -401,15 +481,18 @@ def validate(self, attrs):
"suggestion" in validated
and validated["suggestion"] != SuggestionChoices.DD
):
if "verified_by" not in validated:
if "treating_physician" not in validated:
raise ValidationError(
{
"verified_by": [
"treating_physician": [
"This field is required as the suggestion is not 'Declared Death'"
]
}
)
if not validated["verified_by"].user_type == User.TYPE_VALUE_MAP["Doctor"]:
if (
not validated["treating_physician"].user_type
== User.TYPE_VALUE_MAP["Doctor"]
):
raise ValidationError("Only Doctors can verify a Consultation")

facility = (
Expand All @@ -418,8 +501,8 @@ def validate(self, attrs):
or validated["patient"].facility
)
if (
validated["verified_by"].home_facility
and validated["verified_by"].home_facility != facility
validated["treating_physician"].home_facility
and validated["treating_physician"].home_facility != facility
):
raise ValidationError(
"Home Facility of the Doctor must be the same as the Consultation Facility"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generated by Django 4.2.5 on 2023-11-14 06:22

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
(
"facility",
"0393_rename_diagnosis_patientconsultation_deprecated_diagnosis_and_more",
),
]

operations = [
migrations.RenameField(
model_name="patientconsultation",
old_name="consultation_status",
new_name="route_to_facility",
),
migrations.RenameField(
model_name="patientconsultation",
old_name="verified_by",
new_name="treating_physician",
),
migrations.AddField(
model_name="patientconsultation",
name="icu_admission_date",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name="patientconsultation",
name="referred_by_external",
field=models.TextField(blank=True, default="", null=True),
),
migrations.AddField(
model_name="patientconsultation",
name="referred_from_facility",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="facility.facility",
),
),
migrations.AddField(
model_name="patientconsultation",
name="referred_from_facility_external",
field=models.TextField(blank=True, default="", null=True),
),
migrations.AddField(
model_name="patientconsultation",
name="transferred_from_location",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
to="facility.assetlocation",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Generated by Django 4.2.5 on 2023-11-14 06:23

import datetime

from django.db import migrations, models
from django.db.models import DurationField, ExpressionWrapper, F
from django.db.models.functions import TruncDay
from django.utils import timezone


class Migration(migrations.Migration):
dependencies = [
(
"facility",
"0394_rename_consultation_status_patientconsultation_route_to_facility_and_more",
),
]

def clean_admission_date(apps, schema_editor):
"""
Clean admission_date field to be 00:00:00 IST
For example:
`2023-10-06 06:00:00 +05:30 IST` (`2023-10-06 00:30:00 +00:00 UTC`) would be updated to
`2023-10-06 00:00:00 +05:30 IST` (`2023-10-05 18:30:00 +00:00 UTC`)
Equivalent to the following SQL:
```sql
UPDATE facility_patientconsultation
SET admission_date =
timezone('IST', admission_date) AT TIME ZONE 'UTC' +
(date_trunc('day', timezone('IST', admission_date)) - timezone('IST', admission_date)) +
(interval '-5 hours -30 minutes')
WHERE admission_date IS NOT NULL;
```
"""

current_timezone = timezone.get_current_timezone()
tz_offset = timezone.timedelta(
minutes=current_timezone.utcoffset(datetime.datetime.utcnow()).seconds / 60
)

PatientConsultation = apps.get_model("facility", "PatientConsultation")
PatientConsultation.objects.filter(admission_date__isnull=False).update(
admission_date=ExpressionWrapper(
# Convert the admission_date to UTC by subtracting the current offset
F("admission_date") - tz_offset +
# Get the day part of the admission_date and subtract the actual admission_date from it
(TruncDay(F("admission_date")) - F("admission_date")),
output_field=DurationField(),
)
)

def migrate_route_to_facility(apps, schema_editor):
PatientConsultation = apps.get_model("facility", "PatientConsultation")
qs = PatientConsultation.objects.all()

# Unknown -> None
qs.filter(route_to_facility=0).update(route_to_facility=None)
# Brought Dead/Outpatient -> Outpatient/Emergency Room
qs.filter(models.Q(route_to_facility=1) | models.Q(route_to_facility=5)).update(
route_to_facility=10
)
# Transferred from Ward/ICU -> Internal Transfer within facility
qs.filter(models.Q(route_to_facility=2) | models.Q(route_to_facility=3)).update(
route_to_facility=30
)
# Referred from other hospital -> Referred from another facility
qs.filter(route_to_facility=4).update(route_to_facility=20)

operations = [
migrations.RunPython(
clean_admission_date, reverse_code=migrations.RunPython.noop
),
migrations.AlterField(
model_name="patientconsultation",
name="route_to_facility",
field=models.SmallIntegerField(
blank=True,
choices=[
(None, "(Unknown)"),
(10, "Outpatient/Emergency Room"),
(20, "Referred from another facility"),
(30, "Internal Transfer within the facility"),
],
null=True,
),
),
migrations.RunPython(
migrate_route_to_facility, reverse_code=migrations.RunPython.noop
),
]
10 changes: 5 additions & 5 deletions care/facility/models/patient.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
BLOOD_GROUP_CHOICES,
DISEASE_STATUS_CHOICES,
REVERSE_CATEGORY_CHOICES,
REVERSE_CONSULTATION_STATUS_CHOICES,
REVERSE_DISCHARGE_REASON_CHOICES,
REVERSE_ROUTE_TO_FACILITY_CHOICES,
)
from care.facility.models.patient_consultation import PatientConsultation
from care.facility.static_data.icd11 import get_icd11_diagnoses_objects_by_ids
Expand Down Expand Up @@ -515,7 +515,7 @@ def annotate_diagnosis_ids(*args, **kwargs):
"created_date": "Date of Registration",
"created_date__time": "Time of Registration",
# Last Consultation Details
"last_consultation__consultation_status": "Status during consultation",
"last_consultation__route_to_facility": "Route to Facility",
"last_consultation__created_date": "Date of first consultation",
"last_consultation__created_date__time": "Time of first consultation",
# Diagnosis Details
Expand Down Expand Up @@ -555,8 +555,8 @@ def format_diagnoses(diagnosis_ids):
"provisional_diagnoses": format_diagnoses,
"differential_diagnoses": format_diagnoses,
"confirmed_diagnoses": format_diagnoses,
"last_consultation__consultation_status": (
lambda x: REVERSE_CONSULTATION_STATUS_CHOICES.get(x, "-").replace("_", " ")
"last_consultation__route_to_facility": (
lambda x: REVERSE_ROUTE_TO_FACILITY_CHOICES.get(x, "-")
),
"last_consultation__category": lambda x: REVERSE_CATEGORY_CHOICES.get(x, "-"),
"last_consultation__discharge_reason": (
Expand Down Expand Up @@ -689,7 +689,7 @@ class FacilityPatientStatsHistory(FacilityBaseModel, FacilityRelatedPermissionMi
"facilitypatientstatshistory__num_patients_visited": "Vistited Patients",
"facilitypatientstatshistory__num_patients_home_quarantine": "Home Quarantined Patients",
"facilitypatientstatshistory__num_patients_isolation": "Patients Isolated",
"facilitypatientstatshistory__num_patient_referred": "Patients Reffered",
"facilitypatientstatshistory__num_patient_referred": "Patients Referred",
"facilitypatientstatshistory__num_patient_confirmed_positive": "Patients Confirmed Positive",
}

Expand Down
Loading

0 comments on commit e9cd2e6

Please sign in to comment.