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

Prescription: Titrated drug dose #1692

Merged
merged 31 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8ac9459
adds titrated prescription
GokulramGHV Oct 30, 2023
6ba692d
fix linting
GokulramGHV Oct 30, 2023
ad069bc
optimize prn copy query in migration
GokulramGHV Oct 30, 2023
608ffaa
fix reverse migration
GokulramGHV Oct 30, 2023
eb1fd59
added tests and implemented suggested changes
GokulramGHV Nov 5, 2023
22a8baa
Merge branch 'master' into fix-issue-6432
GokulramGHV Nov 5, 2023
16e8115
fix lint errors
GokulramGHV Nov 6, 2023
6071f17
Add titration dosage information to patient
GokulramGHV Nov 8, 2023
1c9977a
Refactor prescription serializer validation
GokulramGHV Nov 12, 2023
47b1661
Merge branch 'master' into fix-issue-6432
GokulramGHV Nov 13, 2023
158fd0a
Merge two facility migrations
GokulramGHV Nov 13, 2023
f688f48
Fix validation for titrated prescriptions in
GokulramGHV Nov 16, 2023
76e9270
recreated migrations
GokulramGHV Nov 16, 2023
a39e484
Merge branch 'master' into fix-issue-6432
GokulramGHV Nov 16, 2023
f4c7a84
Refactor prescription serializers and models
GokulramGHV Dec 7, 2023
9e52b82
Merge branch 'master' into fix-issue-6432
GokulramGHV Dec 9, 2023
1ea6cfb
fix migrations
GokulramGHV Dec 9, 2023
cdc65b6
Merge branch 'master' into fix-issue-6432
GokulramGHV Dec 14, 2023
b045add
update tests and redo migration
GokulramGHV Dec 14, 2023
cd2749c
Merge branch 'master' into fix-issue-6432
GokulramGHV Dec 20, 2023
37d2bc2
fix: imports that are incorrectly sorted and/or formatted
GokulramGHV Dec 20, 2023
03d53e2
Merge branch 'master' into fix-issue-6432
GokulramGHV Dec 24, 2023
ee29de2
rebase migrations
GokulramGHV Dec 24, 2023
9a48f8e
Merge branch 'master' into fix-issue-6432
Ashesh3 Dec 26, 2023
e980da4
Rename dosage field to base_dosage and add dosage_type field in dummy…
GokulramGHV Dec 26, 2023
faf7ce7
Merge branch 'master' into fix-issue-6432
sainak Jan 22, 2024
687efd7
Merge remote-tracking branch 'origin/master' into fix-issue-6432
sainak Feb 10, 2024
f5d36ae
update migrations
sainak Feb 10, 2024
42aeb66
Merge branch 'master' into fix-issue-6432
rithviknishad Feb 20, 2024
533ec90
Rebase migrations and adds missing custom migration for setting dosag…
rithviknishad Feb 26, 2024
d7f6824
Merge branch 'master' into fix-issue-6432
sainak Mar 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@ -609,17 +613,20 @@
)

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L616 was not covered by tests
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
rithviknishad marked this conversation as resolved.
Show resolved Hide resolved
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 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

Check warning on line 42 in care/facility/api/serializers/prescription.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/serializers/prescription.py#L42

Added line #L42 was not covered by tests

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 @@
}
)

if attrs.get("is_prn"):
if not attrs.get("base_dosage"):
raise serializers.ValidationError(

Check warning on line 121 in care/facility/api/serializers/prescription.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/serializers/prescription.py#L121

Added line #L121 was not covered by tests
{"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(

Check warning on line 143 in care/facility/api/serializers/prescription.py

View check run for this annotation

Codecov / codecov/patch

care/facility/api/serializers/prescription.py#L143

Added line #L143 was not covered by tests
{
"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 @@ -16,11 +16,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 @@ -33,6 +35,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 @@ -82,7 +87,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 @@ -39,6 +39,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 @@ -98,11 +104,20 @@ class Prescription(BaseModel):
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 @@ -148,6 +163,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 __str__(self):
return self.medicine + " - " + self.consultation.patient.name

Expand All @@ -158,6 +177,9 @@ class MedicineAdministration(BaseModel):
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
Loading