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

Patient Consultation: Route to Facility 🏥, Admission Date & Time 🕚, Rename variable: verified_by to treating_physician #1678

Merged
merged 13 commits into from
Nov 21, 2023
Merged
4 changes: 2 additions & 2 deletions care/abdm/utils/fhir.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,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.daily_round import DailyRoundSerializer
from care.facility.api.serializers.facility import FacilityBasicInfoSerializer
Expand All @@ -18,11 +19,13 @@
Prescription,
PrescriptionType,
)
from care.facility.models.asset import AssetLocation
from care.facility.models.bed import Bed, ConsultationBed
from care.facility.models.notification import Notification
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 @@ -63,6 +66,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 @@ -71,8 +97,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 @@ -222,6 +250,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"})

action = -1
review_interval = -1
if "action" in validated_data:
Expand Down Expand Up @@ -340,15 +420,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 @@ -357,8 +440,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,72 @@
# Generated by Django 4.2.5 on 2023-10-17 05:51

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


class Migration(migrations.Migration):
dependencies = [
("facility", "0392_alter_dailyround_consciousness_level"),
]

operations = [
migrations.RenameField(
model_name="patientconsultation",
old_name="consultation_status",
new_name="deprecated_consultation_status",
),
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="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.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,78 @@
# Generated by Django 4.2.5 on 2023-10-17 05:51

import datetime

from django.db import migrations
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",
"0393_rename_consultation_status_patientconsultation_deprecated_consultation_status_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 populate_route_to_facility(apps, schema_editor):
PatientConsultation = apps.get_model("facility", "PatientConsultation")
qs = PatientConsultation.objects.all()

# Brought Dead/Outpatient -> Outpatient/Emergency Room
qs.filter(deprecated_consultation_status=1).update(route_to_facility=10)
qs.filter(deprecated_consultation_status=5).update(route_to_facility=10)

# Transferred from Ward/ICU -> Internal Transfer within facility
qs.filter(deprecated_consultation_status=2).update(route_to_facility=30)
qs.filter(deprecated_consultation_status=3).update(route_to_facility=30)

# Referred from other hospital -> Referred from another facility
qs.filter(deprecated_consultation_status=4).update(route_to_facility=20)

operations = [
migrations.RunPython(
clean_admission_date, reverse_code=migrations.RunPython.noop
),
migrations.RunPython(
populate_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 @@ -29,8 +29,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 ICDDiseases
Expand Down Expand Up @@ -488,7 +488,7 @@ def save(self, *args, **kwargs) -> None:
"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",
"last_consultation__icd11_diagnoses": "Diagnoses",
Expand Down Expand Up @@ -520,8 +520,8 @@ def format_as_time(time):
"last_consultation__icd11_provisional_diagnoses": (
lambda x: ", ".join([ICDDiseases.by.id[id].label.strip() for id in x])
),
"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 @@ -654,7 +654,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
Loading