Skip to content

Commit

Permalink
Add DosageValidator to Prescription model
Browse files Browse the repository at this point in the history
  • Loading branch information
sainak committed Nov 15, 2023
1 parent 7273298 commit da914b9
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Generated by Django 4.2.7 on 2023-11-15 18:13

from django.db import migrations, models

import care.facility.models.prescription


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

operations = [
migrations.AlterField(
model_name="prescription",
name="dosage",
field=models.CharField(
blank=True,
max_length=100,
null=True,
validators=[care.facility.models.prescription.DosageValidator()],
),
),
migrations.AlterField(
model_name="prescription",
name="max_dosage",
field=models.CharField(
blank=True,
max_length=100,
null=True,
validators=[care.facility.models.prescription.DosageValidator()],
),
),
]
46 changes: 44 additions & 2 deletions care/facility/models/prescription.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,44 @@ def generate_choices(enum_class):
return [(tag.name, tag.value) for tag in enum_class]


class DosageValidator:
min_dosage = 0.0001
max_dosage = 5000
allowed_units = {"mg", "g", "ml", "drop(s)", "ampule(s)", "tsp"}

def __call__(self, value: str):
if not value:
return

Check warning on line 47 in care/facility/models/prescription.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/prescription.py#L47

Added line #L47 was not covered by tests
try:
value, unit = value.split(" ", maxsplit=1)
if unit not in self.allowed_units:
raise ValidationError(
f"Unit must be one of {', '.join(self.allowed_units)}"
)

value_num: int | float = float(value)
if value_num.is_integer():
value_num = int(value_num)
elif len(str(value_num).split(".")[1]) > 4:
raise ValidationError(
"Dosage amount must have at most 4 decimal places"
)

if len(value) != len(str(value_num)):
raise ValidationError("Dosage amount must be a valid number")

if self.min_dosage > value_num or value_num > self.max_dosage:
raise ValidationError(
f"Dosage amount must be between {self.min_dosage} and {self.max_dosage}"
)
except ValueError:
raise ValidationError("Invalid dosage")

def deconstruct(self):
path = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
return (path, [], {})

Check warning on line 75 in care/facility/models/prescription.py

View check run for this annotation

Codecov / codecov/patch

care/facility/models/prescription.py#L74-L75

Added lines #L74 - L75 were not covered by tests


class MedibaseMedicineType(enum.Enum):
BRAND = "brand"
GENERIC = "generic"
Expand Down Expand Up @@ -92,7 +130,9 @@ class Prescription(BaseModel):
blank=True,
null=True,
)
dosage = models.CharField(max_length=100, blank=True, null=True)
dosage = models.CharField(
max_length=100, blank=True, null=True, validators=[DosageValidator()]
)

is_prn = models.BooleanField(default=False)

Expand All @@ -107,7 +147,9 @@ class Prescription(BaseModel):

# prn fields
indicator = models.TextField(blank=True, null=True)
max_dosage = models.CharField(max_length=100, blank=True, null=True)
max_dosage = models.CharField(
max_length=100, blank=True, null=True, validators=[DosageValidator()]
)
min_hours_between_doses = models.IntegerField(blank=True, null=True)

notes = models.TextField(default="", blank=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,119 @@
from care.utils.tests.test_utils import TestUtils


class MedicinePrescriptionApiTestCase(TestUtils, APITestCase):
@classmethod
def setUpTestData(cls) -> None:
cls.state = cls.create_state()
cls.district = cls.create_district(cls.state)
cls.local_body = cls.create_local_body(cls.district)
cls.super_user = cls.create_super_user("su", cls.district)
cls.facility = cls.create_facility(cls.super_user, cls.district, cls.local_body)
cls.user = cls.create_user("staff1", cls.district, home_facility=cls.facility)
cls.patient = cls.create_patient(
cls.district, cls.facility, local_body=cls.local_body
)
cls.consultation = cls.create_consultation(cls.patient, cls.facility)
meds = MedibaseMedicine.objects.all().values_list("external_id", flat=True)[:2]
cls.medicine1 = str(meds[0])

def setUp(self) -> None:
super().setUp()

def prescription_data(self, **kwargs):
data = {
"medicine": self.medicine1,
"prescription_type": "REGULAR",
"dosage": "1 mg",
"frequency": "OD",
"is_prn": False,
}
return {**data, **kwargs}

def test_invalid_dosage(self):
data = self.prescription_data(dosage="abc")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(res.json()["dosage"][0], "Invalid dosage")

def test_dosage_out_of_range(self):
data = self.prescription_data(dosage="10000 mg")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
res.json()["dosage"][0],
"Dosage amount must be between 0.0001 and 5000",
)

data = self.prescription_data(dosage="-1 mg")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
res.json()["dosage"][0],
"Dosage amount must be between 0.0001 and 5000",
)

def test_dosage_precision(self):
data = self.prescription_data(dosage="0.300003 mg")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
res.json()["dosage"][0],
"Dosage amount must have at most 4 decimal places",
)

def test_dosage_unit_invalid(self):
data = self.prescription_data(dosage="1 abc")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertTrue(res.json()["dosage"][0].startswith("Unit must be one of"))

def test_dosage_leading_zero(self):
data = self.prescription_data(dosage="01 mg")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
res.json()["dosage"][0], "Dosage amount must be a valid number"
)

def test_dosage_trailing_zero(self):
data = self.prescription_data(dosage="1.0 mg")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(
res.json()["dosage"][0], "Dosage amount must be a valid number"
)

def test_valid_dosage(self):
data = self.prescription_data(dosage="1 mg")
res = self.client.post(
f"/api/v1/consultation/{self.consultation.external_id}/prescriptions/",
data,
)
self.assertEqual(res.status_code, status.HTTP_201_CREATED)


class MedicineAdministrationsApiTestCase(TestUtils, APITestCase):
@classmethod
def setUpTestData(cls) -> None:
Expand Down

0 comments on commit da914b9

Please sign in to comment.