from django.db import models
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from ...administration.person.patient import Patient
from ..laboratory import LabValue
from ...other.unit import Unit
from .patient_lab_sample import PatientLabSample
[docs]
class PatientLabValue(models.Model):
"""
A class representing a patient lab value.
Attributes:
patient (Patient): The patient.
lab_value (LabValue): The lab value.
value (float): The value of the lab value.
date (datetime): The date of the lab value.
"""
patient = models.ForeignKey('Patient', on_delete=models.CASCADE,
related_name="lab_values", blank=True, null=True
)
lab_value = models.ForeignKey('LabValue', on_delete=models.CASCADE)
value = models.FloatField(blank=True, null=True)
value_str = models.CharField(max_length=255, blank=True, null=True)
sample = models.ForeignKey(
'PatientLabSample', on_delete=models.CASCADE,
blank=True, null=True,
related_name='values'
)
datetime = models.DateTimeField(# if not set, use now
auto_now_add=True
)
normal_range = models.JSONField(
default = dict
)
unit = models.ForeignKey('Unit', on_delete=models.CASCADE, blank=True, null=True)
if TYPE_CHECKING:
patient: "Patient"
lab_value: "LabValue"
unit: "Unit"
sample: "PatientLabSample"
[docs]
@classmethod
def create_lab_value_by_sample(
cls, sample:"PatientLabSample", lab_value_name:str,
value:Optional[float]=None, value_str:Optional[str]=None,
unit:Optional["Unit"]=None
):
from ..laboratory import LabValue
patient = sample.patient
lab_value = LabValue.objects.get(name=lab_value_name)
pat_lab_val = cls.objects.create(
patient = patient,
lab_value = lab_value,
value = value,
value_str = value_str,
sample = sample,
unit = unit,
)
pat_lab_val.save()
return pat_lab_val
def __str__(self):
formatted_datetime = self.datetime.strftime('%Y-%m-%d %H:%M')
# normal_range = self.get_normal_range()
norm_range_string = f'[{self.normal_range.get("min", "")} - {self.normal_range.get("max", "")}]'
_str = f'{self.lab_value} - {self.value} {self.unit} - {norm_range_string} ({formatted_datetime})'
return _str
[docs]
def get_normal_range(self):
lab_value = self.lab_value
patient = self.patient
age = patient.age()
gender = patient.gender
normal_range_dict = lab_value.get_normal_range(
age,gender
)
return normal_range_dict
[docs]
def set_min_norm_value(self, value, save = True):
self.normal_range["min"] = value
if save:
self.save()
[docs]
def set_max_norm_value(self, value, save = True):
self.normal_range["max"] = value
if save:
self.save()
[docs]
def set_norm_values_from_default(self):
normal_range_dict = self.get_normal_range()
self.set_min_norm_value(normal_range_dict["min"], save = False)
self.set_max_norm_value(normal_range_dict["max"], save = False)
self.save()
[docs]
def set_unit_from_default(self):
self.unit = self.lab_value.default_unit
self.save()
[docs]
def get_value(self):
if self.value:
return self.value
else:
return self.value_str
[docs]
def get_value_field_name(self):
if self.value:
return "value"
else:
return "value_str"
# customize save method so that if a numeric value exists, we round it to the precision of the lab value
[docs]
def save(self, *args, **kwargs):
if self.value:
precision = self.lab_value.numeric_precision
self.value = round(self.value, precision)
super().save(*args, **kwargs)
[docs]
def set_value_by_distribution(self, distribution=None, save = True):
from ...other.gender import Gender
from ...administration.person.patient import Patient
from ..laboratory import LabValue
from ...other.distribution import (
DateValueDistribution,
SingleCategoricalValueDistribution,
NumericValueDistribution,
MultipleCategoricalValueDistribution,
)
import warnings
patient:Patient = self.patient
dob = patient.dob #TODO: age specific norm values
gender:Gender = patient.gender # TODO: gender specific norm values
lab_value:LabValue = self.lab_value
assert self.lab_value, "Lab value must be set to set value by distribution"
self.unit = self.lab_value.default_unit
if not distribution:
distribution = lab_value.get_default_default_distribution()
if not distribution:
warnings.warn(
f"No distribution set for lab value {lab_value}, assuming uniform numeric distribution based on normal values"
)
if not self.normal_range.get("min", None) or not self.normal_range.get("max", None):
self.set_norm_values_from_default()
self.normal_range:dict
_min = self.normal_range.get("min", 0.0001)
_max = self.normal_range.get("max", 100)
_name = "auto-" + self.lab_value.name + "-distribution-default-uniform"
distribution = NumericValueDistribution(
name = _name,
min_descriptor = _min,
max_max_desciptor = _max,
distribution_type = "uniform"
)
value = distribution.generate_value(lab_value=lab_value, patient=patient)
self.value = value
if save:
self.save()
return value
if isinstance(distribution, SingleCategoricalValueDistribution):
value = distribution.generate_value()
self.value_str = value
if save:
self.save()
return value
elif isinstance(distribution, NumericValueDistribution):
value = distribution.generate_value(
lab_value=lab_value,
patient=patient
)
self.value = value
if save:
self.save()
return value
elif isinstance(distribution, MultipleCategoricalValueDistribution):
value = distribution.generate_value()
self.value_str = value
if save:
self.save()
return value
elif isinstance(distribution, DateValueDistribution):
# raise not implemented error
value = distribution.generate_value()
self.value = value
if save:
self.save()