Skip to content

Commit

Permalink
Prescription: Titrated drug dose (#1692)
Browse files Browse the repository at this point in the history
* adds titrated prescription

* fix linting

* optimize prn copy query in migration

* fix reverse migration

* added tests and implemented suggested changes

* fix lint errors

* Add titration dosage information to patient
discharge summary

* Refactor prescription serializer validation

* Merge two facility migrations

* Fix validation for titrated prescriptions in
MedicineAdministrationSerializer

* recreated migrations

* Refactor prescription serializers and models

* fix migrations

* update tests and redo migration

* fix: imports that are incorrectly sorted and/or formatted

* rebase migrations

* Rename dosage field to base_dosage and add dosage_type field in dummy data

* update migrations

* Rebase migrations and adds missing custom migration for setting dosage_type for existing prn prescriptions

---------

Co-authored-by: Ashesh <[email protected]>
Co-authored-by: Aakash Singh <[email protected]>
Co-authored-by: Rithvik Nishad <[email protected]>
Co-authored-by: rithviknishad <[email protected]>
  • Loading branch information
5 people authored Mar 17, 2024
1 parent 93b85ed commit 5545aa4
Show file tree
Hide file tree
Showing 10 changed files with 391 additions and 128 deletions.
31 changes: 19 additions & 12 deletions care/facility/api/serializers/patient_consultation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
Facility,
PatientRegistration,
Prescription,
PrescriptionDosageType,
PrescriptionType,
)
from care.facility.models.asset import AssetLocation
Expand Down Expand Up @@ -151,17 +152,20 @@ class PatientConsultationSerializer(serializers.ModelSerializer):
medico_legal_case = serializers.BooleanField(default=False, required=False)

def get_discharge_prescription(self, consultation):
return Prescription.objects.filter(
consultation=consultation,
prescription_type=PrescriptionType.DISCHARGE.value,
is_prn=False,
).values()
return (
Prescription.objects.filter(
consultation=consultation,
prescription_type=PrescriptionType.DISCHARGE.value,
)
.exclude(dosage_type=PrescriptionDosageType.PRN.value)
.values()
)

def get_discharge_prn_prescription(self, consultation):
return Prescription.objects.filter(
consultation=consultation,
prescription_type=PrescriptionType.DISCHARGE.value,
is_prn=True,
dosage_type=PrescriptionDosageType.PRN.value,
).values()

class Meta:
Expand Down Expand Up @@ -641,17 +645,20 @@ class PatientConsultationDischargeSerializer(serializers.ModelSerializer):
)

def get_discharge_prescription(self, consultation):
return Prescription.objects.filter(
consultation=consultation,
prescription_type=PrescriptionType.DISCHARGE.value,
is_prn=False,
).values()
return (
Prescription.objects.filter(
consultation=consultation,
prescription_type=PrescriptionType.DISCHARGE.value,
)
.exclude(dosage_type=PrescriptionDosageType.PRN.value)
.values()
)

def get_discharge_prn_prescription(self, consultation):
return Prescription.objects.filter(
consultation=consultation,
prescription_type=PrescriptionType.DISCHARGE.value,
is_prn=True,
dosage_type=PrescriptionDosageType.PRN.value,
).values()

class Meta:
Expand Down
121 changes: 76 additions & 45 deletions care/facility/api/serializers/prescription.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from django.utils import timezone
from rest_framework import serializers

from care.facility.models import MedibaseMedicine, MedicineAdministration, Prescription
from care.facility.models import (
MedibaseMedicine,
MedicineAdministration,
Prescription,
PrescriptionDosageType,
)
from care.users.api.serializers.user import UserBaseMinimumSerializer


Expand All @@ -19,23 +24,60 @@ class Meta:
)


class MedicineAdministrationSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source="external_id", read_only=True)

administered_by = UserBaseMinimumSerializer(read_only=True)
archived_by = UserBaseMinimumSerializer(read_only=True)

def validate_administered_date(self, value):
if value > timezone.now():
raise serializers.ValidationError(
"Administered Date cannot be in the future."
)
if self.context["prescription"].created_date > value:
raise serializers.ValidationError(
"Administered Date cannot be before Prescription Date."
)
return value

def validate(self, attrs):
if (
not attrs.get("dosage")
and self.context["prescription"].dosage_type
== PrescriptionDosageType.TITRATED
):
raise serializers.ValidationError(
{"dosage": "Dosage is required for titrated prescriptions."}
)
elif (
self.context["prescription"].dosage_type != PrescriptionDosageType.TITRATED
):
attrs.pop("dosage", None)

return super().validate(attrs)

class Meta:
model = MedicineAdministration
exclude = ("deleted",)
read_only_fields = (
"external_id",
"administered_by",
"archived_by",
"archived_on",
"created_date",
"modified_date",
"prescription",
)


class PrescriptionSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source="external_id", read_only=True)
prescribed_by = UserBaseMinimumSerializer(read_only=True)
last_administered_on = serializers.SerializerMethodField()
last_administration = MedicineAdministrationSerializer(read_only=True)
medicine_object = MedibaseMedicineSerializer(read_only=True, source="medicine")
medicine = serializers.UUIDField(write_only=True)

def get_last_administered_on(self, obj):
last_administration = (
MedicineAdministration.objects.filter(prescription=obj)
.order_by("-administered_date")
.first()
)
if last_administration:
return last_administration.administered_date
return None

class Meta:
model = Prescription
exclude = (
Expand Down Expand Up @@ -75,47 +117,36 @@ def validate(self, attrs):
}
)

if attrs.get("is_prn"):
if not attrs.get("base_dosage"):
raise serializers.ValidationError(
{"base_dosage": "Base dosage is required."}
)

if attrs.get("dosage_type") == PrescriptionDosageType.PRN:
if not attrs.get("indicator"):
raise serializers.ValidationError(
{"indicator": "Indicator should be set for PRN prescriptions."}
)
attrs.pop("frequency", None)
attrs.pop("days", None)
else:
if not attrs.get("frequency"):
raise serializers.ValidationError(
{"frequency": "Frequency should be set for prescriptions."}
)
attrs.pop("indicator", None)
attrs.pop("max_dosage", None)
attrs.pop("min_hours_between_doses", None)

if attrs.get("dosage_type") == PrescriptionDosageType.TITRATED:
if not attrs.get("target_dosage"):
raise serializers.ValidationError(
{
"target_dosage": "Target dosage should be set for titrated prescriptions."
}
)
else:
attrs.pop("target_dosage", None)

return super().validate(attrs)
# TODO: Ensure that this medicine is not already prescribed to the same patient and is currently active.


class MedicineAdministrationSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source="external_id", read_only=True)

administered_by = UserBaseMinimumSerializer(read_only=True)
prescription = PrescriptionSerializer(read_only=True)
archived_by = UserBaseMinimumSerializer(read_only=True)

def validate_administered_date(self, value):
if value > timezone.now():
raise serializers.ValidationError(
"Administered Date cannot be in the future."
)
if self.context["prescription"].created_date > value:
raise serializers.ValidationError(
"Administered Date cannot be before Prescription Date."
)
return value

class Meta:
model = MedicineAdministration
exclude = ("deleted",)
read_only_fields = (
"external_id",
"administered_by",
"archived_by",
"archived_on",
"created_date",
"modified_date",
"prescription",
)
7 changes: 6 additions & 1 deletion care/facility/api/viewsets/prescription.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
from care.facility.models import (
MedicineAdministration,
Prescription,
PrescriptionDosageType,
PrescriptionType,
generate_choices,
)
from care.facility.static_data.medibase import MedibaseMedicine
from care.utils.filters.choicefilter import CareChoiceFilter
from care.utils.filters.multiselect import MultiSelectFilter
from care.utils.queryset.consultation import get_consultation_queryset
from care.utils.static_data.helpers import query_builder, token_escaper

Expand All @@ -34,6 +36,9 @@ def inverse_choices(choices):


inverse_prescription_type = inverse_choices(generate_choices(PrescriptionType))
inverse_prescription_dosage_type = inverse_choices(
generate_choices(PrescriptionDosageType)
)


class MedicineAdminstrationFilter(filters.FilterSet):
Expand Down Expand Up @@ -83,7 +88,7 @@ def archive(self, request, *args, **kwargs):


class ConsultationPrescriptionFilter(filters.FilterSet):
is_prn = filters.BooleanFilter()
dosage_type = MultiSelectFilter()
prescription_type = CareChoiceFilter(choice_dict=inverse_prescription_type)
discontinued = filters.BooleanFilter()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Generated by Django 4.2.8 on 2024-02-10 10:05

from django.db import migrations, models

import care.utils.models.validators


class Migration(migrations.Migration):
dependencies = [
("facility", "0414_remove_bed_old_name"),
]

def set_prn_prescriptions_dosage_type(apps, schema_editor):
Prescription = apps.get_model("facility", "Prescription")
Prescription.objects.filter(is_prn=True).update(dosage_type="PRN")

def reverse_set_prn_prescriptions_dosage_type(apps, schema_editor):
Prescription = apps.get_model("facility", "Prescription")
Prescription.objects.filter(dosage_type="PRN").update(is_prn=True)

operations = [
migrations.RenameField(
model_name="prescription",
old_name="dosage",
new_name="base_dosage",
),
migrations.AddField(
model_name="prescription",
name="dosage_type",
field=models.CharField(
choices=[
("REGULAR", "REGULAR"),
("TITRATED", "TITRATED"),
("PRN", "PRN"),
],
default="REGULAR",
max_length=100,
),
),
migrations.RunPython(
set_prn_prescriptions_dosage_type, reverse_set_prn_prescriptions_dosage_type
),
migrations.RemoveField(
model_name="prescription",
name="is_prn",
),
migrations.AddField(
model_name="medicineadministration",
name="dosage",
field=models.CharField(
blank=True,
max_length=100,
null=True,
validators=[
care.utils.models.validators.DenominationValidator(
allow_floats=True,
max_amount=5000,
min_amount=0.0001,
precision=4,
units={"mg", "ml", "drop(s)", "ampule(s)", "g", "tsp"},
)
],
),
),
migrations.AddField(
model_name="prescription",
name="instruction_on_titration",
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name="prescription",
name="target_dosage",
field=models.CharField(
blank=True,
max_length=100,
null=True,
validators=[
care.utils.models.validators.DenominationValidator(
allow_floats=True,
max_amount=5000,
min_amount=0.0001,
precision=4,
units={"mg", "ml", "drop(s)", "ampule(s)", "g", "tsp"},
)
],
),
),
]
26 changes: 24 additions & 2 deletions care/facility/models/prescription.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class PrescriptionType(enum.Enum):
REGULAR = "REGULAR"


class PrescriptionDosageType(models.TextChoices):
REGULAR = "REGULAR", "REGULAR"
TITRATED = "TITRATED", "TITRATED"
PRN = "PRN", "PRN"


def generate_choices(enum_class):
return [(tag.name, tag.value) for tag in enum_class]

Expand Down Expand Up @@ -101,11 +107,20 @@ class Prescription(BaseModel, ConsultationRelatedPermissionMixin):
blank=True,
null=True,
)
dosage = models.CharField(
base_dosage = models.CharField(
max_length=100, blank=True, null=True, validators=[dosage_validator]
)
dosage_type = models.CharField(
max_length=100,
choices=PrescriptionDosageType.choices,
default=PrescriptionDosageType.REGULAR.value,
)

is_prn = models.BooleanField(default=False)
# titrated fields
target_dosage = models.CharField(
max_length=100, blank=True, null=True, validators=[dosage_validator]
)
instruction_on_titration = models.TextField(blank=True, null=True)

# non prn fields
frequency = models.CharField(
Expand Down Expand Up @@ -151,6 +166,10 @@ def save(self, *args, **kwargs) -> None:
def medicine_name(self):
return str(self.medicine) if self.medicine else self.medicine_old

@property
def last_administration(self):
return self.administrations.order_by("-administered_date").first()

def has_object_write_permission(self, request):
return ConsultationRelatedPermissionMixin.has_write_permission(request)

Expand All @@ -164,6 +183,9 @@ class MedicineAdministration(BaseModel, ConsultationRelatedPermissionMixin):
on_delete=models.PROTECT,
related_name="administrations",
)
dosage = models.CharField(
max_length=100, blank=True, null=True, validators=[dosage_validator]
)
notes = models.TextField(default="", blank=True)
administered_by = models.ForeignKey(
"users.User",
Expand Down
Loading

0 comments on commit 5545aa4

Please sign in to comment.