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 @@ -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 @@
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 @@
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 @@
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(

Check warning on line 300 in care/facility/api/serializers/patient_consultation.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/serializers/patient_consultation.py#L300

Added line #L300 was not covered by tests
{
"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"})

Check warning on line 311 in care/facility/api/serializers/patient_consultation.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/serializers/patient_consultation.py#L311

Added line #L311 was not covered by tests

create_diagnosis = validated_data.pop("create_diagnoses")
action = -1
review_interval = -1
Expand Down Expand Up @@ -401,15 +481,18 @@
"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 @@
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
Loading