from django.db import models
import warnings
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from endoreg_db.models.other.unit import Unit
from ...other.distribution import (
SingleCategoricalValueDistribution,
NumericValueDistribution,
MultipleCategoricalValueDistribution,
DateValueDistribution,
)
LANG = "de"
class LabValueManager(models.Manager):
def get_by_natural_key(self, name):
"""
Retrieves a model instance by its natural key.
This method returns the instance whose unique name matches the provided natural key.
Args:
name: The unique identifier corresponding to the model's "name" field.
Returns:
The model instance with a matching name.
"""
return self.get(name=name)
[docs]
class LabValue(models.Model):
name = models.CharField(max_length=255, unique=True)
name_de = models.CharField(max_length=255, blank=True, null=True)
name_en = models.CharField(max_length=255, blank=True, null=True)
abbreviation = models.CharField(max_length=10, blank=True, null=True)
default_unit = models.ForeignKey(
"Unit", on_delete=models.CASCADE, blank=True, null=True
)
numeric_precision = models.IntegerField(default=3)
default_single_categorical_value_distribution = models.ForeignKey(
"SingleCategoricalValueDistribution",
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="default_single_categorical_value_distribution",
)
default_numerical_value_distribution = models.ForeignKey(
"NumericValueDistribution",
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="default_numerical_value_distribution",
)
default_multiple_categorical_value_distribution = models.ForeignKey(
"MultipleCategoricalValueDistribution",
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="default_multiple_categorical_value_distribution",
)
default_date_value_distribution = models.ForeignKey(
"DateValueDistribution",
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="default_date_value_distribution",
)
default_normal_range = models.JSONField(blank=True, null=True)
normal_range_age_dependent = models.BooleanField(default=False)
normal_range_gender_dependent = models.BooleanField(default=False)
normal_range_special_case = models.BooleanField(default=False)
objects = LabValueManager()
if TYPE_CHECKING:
default_unit: "Unit"
default_single_categorical_value_distribution: "SingleCategoricalValueDistribution"
default_numerical_value_distribution: "NumericValueDistribution"
default_multiple_categorical_value_distribution: "MultipleCategoricalValueDistribution"
default_date_value_distribution: "DateValueDistribution"
[docs]
def natural_key(self):
"""
Return a tuple representing the natural key for this instance.
Returns:
tuple: A single-element tuple containing the instance's unique name.
"""
return (self.name,)
def __str__(self):
"""
Return the lab value name as a string.
Converts the lab value's `name` attribute into its string representation for
display purposes.
"""
return str(self.name)
[docs]
def get_default_default_distribution(self):
"""
Returns the first available default distribution for the lab value.
Checks the default distribution fields in the following order:
default_single_categorical_value_distribution, default_numerical_value_distribution,
default_multiple_categorical_value_distribution, and default_date_value_distribution.
If none are set, a warning is issued and None is returned.
"""
if self.default_single_categorical_value_distribution:
return self.default_single_categorical_value_distribution
elif self.default_numerical_value_distribution:
return self.default_numerical_value_distribution
elif self.default_multiple_categorical_value_distribution:
return self.default_multiple_categorical_value_distribution
elif self.default_date_value_distribution:
return self.default_date_value_distribution
else:
warnings.warn("No default distribution set for lab value")
return None
[docs]
def get_normal_range(self, age: int = None, gender=None):
"""
Retrieve the normal range for the lab value based on optional age and gender.
This method returns the default normal range when the lab value is not age-, gender-, or special-case dependent.
For gender-dependent ranges, it uses a gender-specific default range and, if no gender is provided, selects one at random with a warning.
Note that age-dependent and special-case normal ranges are not implemented and will issue warnings when invoked.
Args:
age (int, optional): The age in years used for age-dependent range computation.
gender (Gender, optional): The gender instance used for gender-dependent range computation.
Returns:
dict: A dictionary with 'min' and 'max' keys indicating the lower and upper bounds of the normal range.
"""
from ...other.gender import Gender
assert isinstance(age, int) or age is None
assert isinstance(gender, Gender) or gender is None
age_dependent = self.normal_range_age_dependent
gender_dependent = self.normal_range_gender_dependent
special_case = self.normal_range_special_case
min_value = None
max_value = None
if not age_dependent and not gender_dependent and not special_case:
min_value = self.default_normal_range.get("min", None)
max_value = self.default_normal_range.get("max", None)
if age_dependent:
# get normal range for age)
warnings.warn("Age dependent normal range not implemented yet")
if gender_dependent:
if not gender:
warnings.warn(
"Calling get_normal_range with gender_dependent=True requires gender to be set, choosing by random"
)
# set gender to either "male" or "female"
from random import choice
choices = ["male", "female"]
gender = choice(choices)
default_range_dict = self.default_normal_range.get(gender.name, {})
min_value = default_range_dict.get("min", None)
max_value = default_range_dict.get("max", None)
if special_case:
# get normal range for special case
warnings.warn("Special case normal range not implemented yet")
normal_range_dict = {"min": min_value, "max": max_value}
return normal_range_dict