endoreg-db 0.6.3__py3-none-any.whl → 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of endoreg-db might be problematic. Click here for more details.
- endoreg_db/data/__init__.py +14 -0
- endoreg_db/data/disease_classification/chronic_kidney_disease.yaml +2 -2
- endoreg_db/data/disease_classification_choice/chronic_kidney_disease.yaml +6 -6
- endoreg_db/data/examination/examinations/data.yaml +22 -21
- endoreg_db/data/examination/type/data.yaml +12 -0
- endoreg_db/data/examination_indication/endoscopy.yaml +417 -1
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +157 -5
- endoreg_db/data/finding/data.yaml +18 -11
- endoreg_db/data/finding_intervention/endoscopy.yaml +26 -121
- endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +163 -0
- endoreg_db/data/finding_intervention/endoscopy_egd.yaml +128 -0
- endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +32 -0
- endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +9 -0
- endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +36 -0
- endoreg_db/data/information_source/endoscopy_guidelines.yaml +7 -0
- endoreg_db/data/medication_indication/anticoagulation.yaml +4 -4
- endoreg_db/data/pdf_type/data.yaml +9 -16
- endoreg_db/data/requirement/colonoscopy_indications.yaml +56 -0
- endoreg_db/data/requirement/disease_cardiovascular.yaml +79 -0
- endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +38 -0
- endoreg_db/data/requirement/disease_hepatology.yaml +12 -0
- endoreg_db/data/requirement/disease_misc.yaml +12 -0
- endoreg_db/data/requirement/disease_renal.yaml +80 -0
- endoreg_db/data/requirement/event_cardiology.yaml +251 -0
- endoreg_db/data/requirement/lab_value.yaml +120 -0
- endoreg_db/data/requirement_operator/lab_operators.yaml +128 -0
- endoreg_db/data/requirement_operator/model_operators.yaml +90 -0
- endoreg_db/data/requirement_set/endoscopy_bleeding_risk.yaml +12 -0
- endoreg_db/data/requirement_set_type/data.yaml +20 -0
- endoreg_db/data/requirement_type/requirement_types.yaml +83 -0
- endoreg_db/data/risk/bleeding.yaml +26 -0
- endoreg_db/data/risk/thrombosis.yaml +37 -0
- endoreg_db/data/risk_type/data.yaml +27 -0
- endoreg_db/data/unit/time.yaml +36 -1
- endoreg_db/management/commands/load_base_db_data.py +14 -1
- endoreg_db/management/commands/load_center_data.py +46 -21
- endoreg_db/management/commands/load_examination_indication_data.py +49 -27
- endoreg_db/management/commands/load_requirement_data.py +156 -0
- endoreg_db/management/commands/load_risk_data.py +56 -0
- endoreg_db/migrations/0009_requirementoperator_requirementsettype_and_more.py +154 -0
- endoreg_db/models/__init__.py +20 -0
- endoreg_db/models/ai_model/ai_model.py +0 -13
- endoreg_db/models/ai_model/model_meta.py +2 -12
- endoreg_db/models/data_file/base_classes/abstract_frame.py +0 -2
- endoreg_db/models/data_file/base_classes/abstract_pdf.py +0 -9
- endoreg_db/models/data_file/base_classes/abstract_video.py +7 -8
- endoreg_db/models/data_file/base_classes/utils.py +0 -22
- endoreg_db/models/data_file/frame.py +1 -1
- endoreg_db/models/data_file/import_classes/raw_pdf.py +5 -11
- endoreg_db/models/data_file/import_classes/raw_video.py +6 -4
- endoreg_db/models/data_file/video/video.py +3 -3
- endoreg_db/models/disease.py +88 -19
- endoreg_db/models/event.py +108 -21
- endoreg_db/models/examination/examination_indication.py +108 -29
- endoreg_db/models/examination/examination_type.py +20 -6
- endoreg_db/models/information_source.py +37 -1
- endoreg_db/models/laboratory/lab_value.py +83 -32
- endoreg_db/models/requirement/__init__.py +11 -0
- endoreg_db/models/requirement/requirement.py +325 -0
- endoreg_db/models/requirement/requirement_evaluation/__init__.py +134 -0
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +102 -0
- endoreg_db/models/requirement/requirement_operator.py +58 -0
- endoreg_db/models/requirement/requirement_set.py +127 -0
- endoreg_db/models/risk/__init__.py +7 -0
- endoreg_db/models/risk/risk.py +72 -0
- endoreg_db/models/risk/risk_type.py +55 -0
- endoreg_db/serializers/video_segmentation.py +12 -25
- endoreg_db/urls.py +17 -3
- endoreg_db/utils/__init__.py +43 -0
- endoreg_db/utils/dataloader.py +38 -19
- endoreg_db/utils/hashs.py +1 -0
- endoreg_db/utils/paths.py +86 -0
- endoreg_db/views/views.py +107 -0
- {endoreg_db-0.6.3.dist-info → endoreg_db-0.6.4.dist-info}/METADATA +1 -1
- {endoreg_db-0.6.3.dist-info → endoreg_db-0.6.4.dist-info}/RECORD +77 -42
- endoreg_db/management/commands/load_name_data.py +0 -37
- {endoreg_db-0.6.3.dist-info → endoreg_db-0.6.4.dist-info}/WHEEL +0 -0
- {endoreg_db-0.6.3.dist-info → endoreg_db-0.6.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,40 +3,59 @@ import warnings
|
|
|
3
3
|
|
|
4
4
|
LANG = "de"
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class LabValueManager(models.Manager):
|
|
7
8
|
def get_by_natural_key(self, name):
|
|
9
|
+
"""
|
|
10
|
+
Retrieves a model instance by its natural key.
|
|
11
|
+
|
|
12
|
+
This method returns the instance whose unique name matches the provided natural key.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
name: The unique identifier corresponding to the model's "name" field.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
The model instance with a matching name.
|
|
19
|
+
"""
|
|
8
20
|
return self.get(name=name)
|
|
9
21
|
|
|
22
|
+
|
|
10
23
|
class LabValue(models.Model):
|
|
11
24
|
name = models.CharField(max_length=255, unique=True)
|
|
12
25
|
name_de = models.CharField(max_length=255, blank=True, null=True)
|
|
13
26
|
name_en = models.CharField(max_length=255, blank=True, null=True)
|
|
14
27
|
abbreviation = models.CharField(max_length=10, blank=True, null=True)
|
|
15
|
-
default_unit = models.ForeignKey(
|
|
28
|
+
default_unit = models.ForeignKey(
|
|
29
|
+
"Unit", on_delete=models.CASCADE, blank=True, null=True
|
|
30
|
+
)
|
|
16
31
|
numeric_precision = models.IntegerField(default=3)
|
|
17
32
|
default_single_categorical_value_distribution = models.ForeignKey(
|
|
18
|
-
|
|
33
|
+
"SingleCategoricalValueDistribution",
|
|
19
34
|
on_delete=models.CASCADE,
|
|
20
|
-
blank=True,
|
|
21
|
-
|
|
35
|
+
blank=True,
|
|
36
|
+
null=True,
|
|
37
|
+
related_name="default_single_categorical_value_distribution",
|
|
22
38
|
)
|
|
23
39
|
default_numerical_value_distribution = models.ForeignKey(
|
|
24
|
-
|
|
40
|
+
"NumericValueDistribution",
|
|
25
41
|
on_delete=models.CASCADE,
|
|
26
|
-
blank=True,
|
|
27
|
-
|
|
42
|
+
blank=True,
|
|
43
|
+
null=True,
|
|
44
|
+
related_name="default_numerical_value_distribution",
|
|
28
45
|
)
|
|
29
46
|
default_multiple_categorical_value_distribution = models.ForeignKey(
|
|
30
|
-
|
|
47
|
+
"MultipleCategoricalValueDistribution",
|
|
31
48
|
on_delete=models.CASCADE,
|
|
32
|
-
blank=True,
|
|
33
|
-
|
|
49
|
+
blank=True,
|
|
50
|
+
null=True,
|
|
51
|
+
related_name="default_multiple_categorical_value_distribution",
|
|
34
52
|
)
|
|
35
53
|
default_date_value_distribution = models.ForeignKey(
|
|
36
|
-
|
|
54
|
+
"DateValueDistribution",
|
|
37
55
|
on_delete=models.CASCADE,
|
|
38
|
-
blank=True,
|
|
39
|
-
|
|
56
|
+
blank=True,
|
|
57
|
+
null=True,
|
|
58
|
+
related_name="default_date_value_distribution",
|
|
40
59
|
)
|
|
41
60
|
default_normal_range = models.JSONField(blank=True, null=True)
|
|
42
61
|
normal_range_age_dependent = models.BooleanField(default=False)
|
|
@@ -45,12 +64,32 @@ class LabValue(models.Model):
|
|
|
45
64
|
objects = LabValueManager()
|
|
46
65
|
|
|
47
66
|
def natural_key(self):
|
|
67
|
+
"""
|
|
68
|
+
Return a tuple representing the natural key for this instance.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
tuple: A single-element tuple containing the instance's unique name.
|
|
72
|
+
"""
|
|
48
73
|
return (self.name,)
|
|
49
74
|
|
|
50
75
|
def __str__(self):
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
"""
|
|
77
|
+
Return the lab value name as a string.
|
|
78
|
+
|
|
79
|
+
Converts the lab value's `name` attribute into its string representation for
|
|
80
|
+
display purposes.
|
|
81
|
+
"""
|
|
82
|
+
return str(self.name)
|
|
83
|
+
|
|
53
84
|
def get_default_default_distribution(self):
|
|
85
|
+
"""
|
|
86
|
+
Returns the first available default distribution for the lab value.
|
|
87
|
+
|
|
88
|
+
Checks the default distribution fields in the following order:
|
|
89
|
+
default_single_categorical_value_distribution, default_numerical_value_distribution,
|
|
90
|
+
default_multiple_categorical_value_distribution, and default_date_value_distribution.
|
|
91
|
+
If none are set, a warning is issued and None is returned.
|
|
92
|
+
"""
|
|
54
93
|
if self.default_single_categorical_value_distribution:
|
|
55
94
|
return self.default_single_categorical_value_distribution
|
|
56
95
|
elif self.default_numerical_value_distribution:
|
|
@@ -62,9 +101,24 @@ class LabValue(models.Model):
|
|
|
62
101
|
else:
|
|
63
102
|
warnings.warn("No default distribution set for lab value")
|
|
64
103
|
return None
|
|
65
|
-
|
|
66
|
-
def get_normal_range(self, age:int=None, gender=None):
|
|
104
|
+
|
|
105
|
+
def get_normal_range(self, age: int = None, gender=None):
|
|
106
|
+
"""
|
|
107
|
+
Retrieve the normal range for the lab value based on optional age and gender.
|
|
108
|
+
|
|
109
|
+
This method returns the default normal range when the lab value is not age-, gender-, or special-case dependent.
|
|
110
|
+
For gender-dependent ranges, it uses a gender-specific default range and, if no gender is provided, selects one at random with a warning.
|
|
111
|
+
Note that age-dependent and special-case normal ranges are not implemented and will issue warnings when invoked.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
age (int, optional): The age in years used for age-dependent range computation.
|
|
115
|
+
gender (Gender, optional): The gender instance used for gender-dependent range computation.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
dict: A dictionary with 'min' and 'max' keys indicating the lower and upper bounds of the normal range.
|
|
119
|
+
"""
|
|
67
120
|
from endoreg_db.models import Gender
|
|
121
|
+
|
|
68
122
|
assert isinstance(age, int) or age is None
|
|
69
123
|
assert isinstance(gender, Gender) or gender is None
|
|
70
124
|
|
|
@@ -75,37 +129,34 @@ class LabValue(models.Model):
|
|
|
75
129
|
min_value = None
|
|
76
130
|
max_value = None
|
|
77
131
|
|
|
78
|
-
if not age_dependent and
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
min_value = self.default_normal_range.get('min', None)
|
|
82
|
-
max_value = self.default_normal_range.get('max', None)
|
|
132
|
+
if not age_dependent and not gender_dependent and not special_case:
|
|
133
|
+
min_value = self.default_normal_range.get("min", None)
|
|
134
|
+
max_value = self.default_normal_range.get("max", None)
|
|
83
135
|
|
|
84
|
-
if age_dependent:
|
|
136
|
+
if age_dependent:
|
|
85
137
|
# get normal range for age)
|
|
86
138
|
warnings.warn("Age dependent normal range not implemented yet")
|
|
87
139
|
pass
|
|
88
|
-
|
|
140
|
+
|
|
89
141
|
if gender_dependent:
|
|
90
142
|
if not gender:
|
|
91
|
-
warnings.warn(
|
|
143
|
+
warnings.warn(
|
|
144
|
+
"Calling get_normal_range with gender_dependent=True requires gender to be set, choosing by random"
|
|
145
|
+
)
|
|
92
146
|
# set gender to either "male" or "female"
|
|
93
147
|
from random import choice
|
|
148
|
+
|
|
94
149
|
choices = ["male", "female"]
|
|
95
150
|
gender = choice(choices)
|
|
96
151
|
|
|
97
152
|
default_range_dict = self.default_normal_range.get(gender.name, {})
|
|
98
|
-
min_value = default_range_dict.get(
|
|
99
|
-
max_value = default_range_dict.get(
|
|
153
|
+
min_value = default_range_dict.get("min", None)
|
|
154
|
+
max_value = default_range_dict.get("max", None)
|
|
100
155
|
|
|
101
156
|
if special_case:
|
|
102
157
|
# get normal range for special case
|
|
103
158
|
warnings.warn("Special case normal range not implemented yet")
|
|
104
159
|
|
|
105
|
-
normal_range_dict = {
|
|
106
|
-
"min": min_value,
|
|
107
|
-
"max": max_value
|
|
108
|
-
}
|
|
160
|
+
normal_range_dict = {"min": min_value, "max": max_value}
|
|
109
161
|
|
|
110
162
|
return normal_range_dict
|
|
111
|
-
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .requirement import Requirement, RequirementType
|
|
2
|
+
from .requirement_operator import RequirementOperator
|
|
3
|
+
from .requirement_set import RequirementSet, RequirementSetType
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Requirement",
|
|
7
|
+
"RequirementType",
|
|
8
|
+
"RequirementOperator",
|
|
9
|
+
"RequirementSet",
|
|
10
|
+
"RequirementSetType",
|
|
11
|
+
]
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
QuerySet = models.QuerySet
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from endoreg_db.models import (
|
|
10
|
+
RequirementOperator,
|
|
11
|
+
RequirementSet,
|
|
12
|
+
Examination,
|
|
13
|
+
ExaminationIndication,
|
|
14
|
+
LabValue,
|
|
15
|
+
Disease,
|
|
16
|
+
DiseaseClassificationChoice,
|
|
17
|
+
Event,
|
|
18
|
+
Finding,
|
|
19
|
+
FindingMorphologyClassificationChoice,
|
|
20
|
+
FindingLocationClassificationChoice,
|
|
21
|
+
FindingIntervention,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RequirementsModelDict(BaseModel):
|
|
26
|
+
"""
|
|
27
|
+
A class representing a dictionary of models related to a requirement.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
requirement_types (QuerySet[RequirementType]): A queryset of requirement types.
|
|
31
|
+
operators (QuerySet[RequirementOperator]): A queryset of operators.
|
|
32
|
+
requirement_sets (QuerySet[RequirementSet]): A queryset of requirement sets.
|
|
33
|
+
examinations (QuerySet[Examination]): A queryset of examinations.
|
|
34
|
+
examination_indications (QuerySet[ExaminationIndication]): A queryset of examination indications.
|
|
35
|
+
lab_values (QuerySet[LabValue]): A queryset of lab values.
|
|
36
|
+
diseases (QuerySet[Disease]): A queryset of diseases.
|
|
37
|
+
disease_classification_choices (QuerySet[DiseaseClassificationChoice]): A queryset of disease classification choices.
|
|
38
|
+
events (QuerySet[Event]): A queryset of events.
|
|
39
|
+
findings (QuerySet[Finding]): A queryset of findings.
|
|
40
|
+
finding_morphology_classification_choices (QuerySet[FindingMorphologyClassificationChoice]): A queryset of finding morphology classification choices.
|
|
41
|
+
finding_location_classification_choices (QuerySet[FindingLocationClassificationChoice]): A queryset of finding location classification choices.
|
|
42
|
+
finding_interventions (QuerySet[FindingIntervention]): A queryset of finding interventions.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
46
|
+
requirement_types: Optional[QuerySet["RequirementType"]] = None
|
|
47
|
+
operators: Optional[QuerySet["RequirementOperator"]] = None
|
|
48
|
+
requirement_sets: Optional[QuerySet["RequirementSet"]] = None
|
|
49
|
+
examinations: Optional[QuerySet["Examination"]] = None
|
|
50
|
+
examination_indications: Optional[QuerySet["ExaminationIndication"]] = None
|
|
51
|
+
lab_values: Optional[QuerySet["LabValue"]] = None
|
|
52
|
+
diseases: Optional[QuerySet["Disease"]] = None
|
|
53
|
+
disease_classification_choices: Optional[
|
|
54
|
+
QuerySet["DiseaseClassificationChoice"]
|
|
55
|
+
] = None
|
|
56
|
+
events: Optional[QuerySet["Event"]] = None
|
|
57
|
+
findings: Optional[QuerySet["Finding"]] = None
|
|
58
|
+
finding_morphology_classification_choices: Optional[
|
|
59
|
+
QuerySet["FindingMorphologyClassificationChoice"]
|
|
60
|
+
] = None
|
|
61
|
+
finding_location_classification_choices: Optional[
|
|
62
|
+
QuerySet["FindingLocationClassificationChoice"]
|
|
63
|
+
] = None
|
|
64
|
+
finding_interventions: Optional[QuerySet["FindingIntervention"]] = None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RequirementTypeManager(models.Manager):
|
|
68
|
+
def get_by_natural_key(self, name):
|
|
69
|
+
"""
|
|
70
|
+
Retrieve a model instance using its natural key.
|
|
71
|
+
|
|
72
|
+
Queries the database for an instance with a matching name, serving as the natural key.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
name: The natural key identifying the model instance.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The model instance matching the provided natural key.
|
|
79
|
+
"""
|
|
80
|
+
return self.get(name=name)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class RequirementType(models.Model):
|
|
84
|
+
"""
|
|
85
|
+
A class representing a type of requirement.
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
name (str): The name of the requirement type.
|
|
89
|
+
name_de (str): The German name of the requirement type.
|
|
90
|
+
name_en (str): The English name of the requirement type.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
name = models.CharField(max_length=100, unique=True)
|
|
94
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
95
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
96
|
+
description = models.TextField(blank=True, null=True)
|
|
97
|
+
|
|
98
|
+
objects = RequirementTypeManager()
|
|
99
|
+
|
|
100
|
+
def natural_key(self):
|
|
101
|
+
"""
|
|
102
|
+
Return the natural key for the instance as a tuple containing its name.
|
|
103
|
+
|
|
104
|
+
This tuple enables the use of natural key lookups for serialization and deserialization.
|
|
105
|
+
"""
|
|
106
|
+
return (self.name,)
|
|
107
|
+
|
|
108
|
+
def __str__(self):
|
|
109
|
+
"""Return the string representation of the requirement type's name.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
str: The name of the requirement type.
|
|
113
|
+
"""
|
|
114
|
+
return str(self.name)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class RequirementManager(models.Manager):
|
|
118
|
+
def get_by_natural_key(self, name):
|
|
119
|
+
"""
|
|
120
|
+
Retrieve an instance using its natural key.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
name: The natural key used to look up the instance.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The object whose 'name' field matches the given key.
|
|
127
|
+
"""
|
|
128
|
+
return self.get(name=name)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Requirement(models.Model):
|
|
132
|
+
"""
|
|
133
|
+
A class representing a requirement.
|
|
134
|
+
|
|
135
|
+
Attributes:
|
|
136
|
+
name (str): The name of the requirement.
|
|
137
|
+
name_de (str): The German name of the requirement.
|
|
138
|
+
name_en (str): The English name of the requirement.
|
|
139
|
+
description (str): A description of the requirement.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
name = models.CharField(max_length=100, unique=True)
|
|
143
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
144
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
145
|
+
description = models.TextField(blank=True, null=True)
|
|
146
|
+
numeric_value = models.FloatField(
|
|
147
|
+
blank=True,
|
|
148
|
+
null=True,
|
|
149
|
+
help_text="Numeric value for the requirement. If not set, the requirement is not used in calculations.",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
numeric_value_min = models.FloatField(
|
|
153
|
+
blank=True,
|
|
154
|
+
null=True,
|
|
155
|
+
help_text="Minimum numeric value for the requirement. If not set, the requirement is not used in calculations.",
|
|
156
|
+
)
|
|
157
|
+
numeric_value_max = models.FloatField(
|
|
158
|
+
blank=True,
|
|
159
|
+
null=True,
|
|
160
|
+
help_text="Maximum numeric value for the requirement. If not set, the requirement is not used in calculations.",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
string_value = models.CharField(
|
|
164
|
+
max_length=100,
|
|
165
|
+
blank=True,
|
|
166
|
+
null=True,
|
|
167
|
+
help_text="String value for the requirement. If not set, the requirement is not used in calculations.",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
string_values = models.TextField(
|
|
171
|
+
blank=True,
|
|
172
|
+
null=True,
|
|
173
|
+
help_text=" ','-separated list of string values for the requirement.If not set, the requirement is not used in calculations.",
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
objects = RequirementManager()
|
|
177
|
+
|
|
178
|
+
requirement_types = models.ManyToManyField(
|
|
179
|
+
"RequirementType",
|
|
180
|
+
blank=True,
|
|
181
|
+
related_name="linked_requirements",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
operators = models.ManyToManyField(
|
|
185
|
+
"RequirementOperator",
|
|
186
|
+
blank=True,
|
|
187
|
+
related_name="required_in",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
unit = models.ForeignKey(
|
|
191
|
+
"Unit",
|
|
192
|
+
on_delete=models.CASCADE,
|
|
193
|
+
related_name="required_in",
|
|
194
|
+
blank=True,
|
|
195
|
+
null=True,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
examinations = models.ManyToManyField(
|
|
199
|
+
"Examination",
|
|
200
|
+
blank=True,
|
|
201
|
+
related_name="required_in",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
examination_indications = models.ManyToManyField(
|
|
205
|
+
"ExaminationIndication",
|
|
206
|
+
blank=True,
|
|
207
|
+
related_name="required_in",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
diseases = models.ManyToManyField(
|
|
211
|
+
"Disease",
|
|
212
|
+
blank=True,
|
|
213
|
+
related_name="required_in",
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
disease_classification_choices = models.ManyToManyField(
|
|
217
|
+
"DiseaseClassificationChoice",
|
|
218
|
+
blank=True,
|
|
219
|
+
related_name="required_in",
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
events = models.ManyToManyField(
|
|
223
|
+
"Event",
|
|
224
|
+
blank=True,
|
|
225
|
+
related_name="required_in",
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
lab_values = models.ManyToManyField(
|
|
229
|
+
"LabValue",
|
|
230
|
+
blank=True,
|
|
231
|
+
related_name="required_in",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
findings = models.ManyToManyField(
|
|
235
|
+
"Finding",
|
|
236
|
+
blank=True,
|
|
237
|
+
related_name="required_in",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
finding_morphology_classification_choices = models.ManyToManyField(
|
|
241
|
+
"FindingMorphologyClassificationChoice",
|
|
242
|
+
blank=True,
|
|
243
|
+
related_name="required_in",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
finding_location_classification_choices = models.ManyToManyField(
|
|
247
|
+
"FindingLocationClassificationChoice",
|
|
248
|
+
blank=True,
|
|
249
|
+
related_name="required_in",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
finding_interventions = models.ManyToManyField(
|
|
253
|
+
"FindingIntervention",
|
|
254
|
+
blank=True,
|
|
255
|
+
related_name="required_in",
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if TYPE_CHECKING:
|
|
259
|
+
requirement_types: models.QuerySet[RequirementType]
|
|
260
|
+
operators: models.QuerySet[RequirementOperator]
|
|
261
|
+
requirement_sets: models.QuerySet[RequirementSet]
|
|
262
|
+
examinations: models.QuerySet[Examination]
|
|
263
|
+
examination_indications: models.QuerySet[ExaminationIndication]
|
|
264
|
+
lab_values: models.QuerySet[LabValue]
|
|
265
|
+
diseases: models.QuerySet[Disease]
|
|
266
|
+
disease_classification_choices: models.QuerySet[DiseaseClassificationChoice]
|
|
267
|
+
events: models.QuerySet[Event]
|
|
268
|
+
findings: models.QuerySet[Finding]
|
|
269
|
+
finding_morphology_classification_choices: models.QuerySet[
|
|
270
|
+
FindingMorphologyClassificationChoice
|
|
271
|
+
]
|
|
272
|
+
finding_location_classification_choices: models.QuerySet[
|
|
273
|
+
FindingLocationClassificationChoice
|
|
274
|
+
]
|
|
275
|
+
finding_interventions: models.QuerySet[FindingIntervention]
|
|
276
|
+
|
|
277
|
+
def natural_key(self):
|
|
278
|
+
"""
|
|
279
|
+
Return a tuple containing the natural key of the instance.
|
|
280
|
+
|
|
281
|
+
The tuple, comprised solely of the instance's name, serves as an alternative unique identifier for serialization.
|
|
282
|
+
"""
|
|
283
|
+
return (self.name,)
|
|
284
|
+
|
|
285
|
+
def __str__(self):
|
|
286
|
+
"""Return the string representation of the requirement's name.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
str: The name of the requirement.
|
|
290
|
+
"""
|
|
291
|
+
return str(self.name)
|
|
292
|
+
|
|
293
|
+
def get_models_dict(self) -> RequirementsModelDict:
|
|
294
|
+
"""
|
|
295
|
+
Returns a RequirementsModelDict with querysets of all associated models.
|
|
296
|
+
|
|
297
|
+
This method aggregates related models by invoking .all() on each
|
|
298
|
+
many-to-many field of the requirement. The resulting RequirementsModelDict
|
|
299
|
+
includes querysets for requirement types, operators, requirement sets,
|
|
300
|
+
examinations, examination indications, lab values, diseases, disease
|
|
301
|
+
classification choices, events, findings, finding morphology classification
|
|
302
|
+
choices, finding location classification choices, and finding interventions.
|
|
303
|
+
"""
|
|
304
|
+
models_dict = RequirementsModelDict(
|
|
305
|
+
requirement_types=self.requirement_types.all(),
|
|
306
|
+
operators=self.operators.all(),
|
|
307
|
+
requirement_sets=self.requirement_sets.all(),
|
|
308
|
+
examinations=self.examinations.all(),
|
|
309
|
+
examination_indications=self.examination_indications.all(),
|
|
310
|
+
lab_values=self.lab_values.all(),
|
|
311
|
+
diseases=self.diseases.all(),
|
|
312
|
+
disease_classification_choices=self.disease_classification_choices.all(),
|
|
313
|
+
events=self.events.all(),
|
|
314
|
+
findings=self.findings.all(),
|
|
315
|
+
finding_morphology_classification_choices=self.finding_morphology_classification_choices.all(),
|
|
316
|
+
finding_location_classification_choices=self.finding_location_classification_choices.all(),
|
|
317
|
+
finding_interventions=self.finding_interventions.all(),
|
|
318
|
+
)
|
|
319
|
+
return models_dict
|
|
320
|
+
|
|
321
|
+
def evaluate(self, **kwargs):
|
|
322
|
+
#TODO refactor after prototyping
|
|
323
|
+
# from .requirement_evaluation.requirement_type_parser import get_input_data
|
|
324
|
+
|
|
325
|
+
return_value = True
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from endoreg_db.models import (
|
|
3
|
+
Patient,
|
|
4
|
+
PatientExamination,
|
|
5
|
+
Finding,
|
|
6
|
+
FindingIntervention,
|
|
7
|
+
FindingLocationClassification,
|
|
8
|
+
Requirement,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_kwargs_by_req_type_and_operator(requirement, **kwargs):
|
|
13
|
+
"""
|
|
14
|
+
Extract keyword arguments based on the requirement's type and operator.
|
|
15
|
+
|
|
16
|
+
This function inspects the provided requirement to determine which keyword arguments are relevant
|
|
17
|
+
for its evaluation. It constructs and returns a dictionary of arguments suitable for processing the
|
|
18
|
+
requirement with its associated operator.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
requirement: The requirement instance used to identify the applicable parameters.
|
|
22
|
+
**kwargs: Additional keyword arguments that may include values required for evaluation.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A dictionary mapping relevant parameter names to their corresponding values.
|
|
26
|
+
"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def models_match_all(requirement: Requirement, **kwargs):
|
|
31
|
+
"""
|
|
32
|
+
Match all models for a given requirement.
|
|
33
|
+
|
|
34
|
+
Retrieves the models dictionary from the requirement by calling its get_models_dict() method.
|
|
35
|
+
Additional keyword arguments are accepted for future extensions. Note that this function
|
|
36
|
+
currently only extracts the models without performing any matching logic.
|
|
37
|
+
"""
|
|
38
|
+
models_dict = requirement.get_models_dict()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
SUPPORTED_REQUIREMENT_TYPES = ["patient_examination", "patient"]
|
|
42
|
+
SUPPORTED_OPERATORS = {
|
|
43
|
+
"models_match_all": models_match_all,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_operator_function(operator_name):
|
|
48
|
+
"""
|
|
49
|
+
Retrieves the operator function for the given operator name.
|
|
50
|
+
|
|
51
|
+
This function looks up the operator name in the supported operators mapping
|
|
52
|
+
and returns the corresponding operator function used for evaluating requirements.
|
|
53
|
+
If the operator name is not recognized, the behavior is undefined.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
operator_name: The name of the operator to retrieve.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
The operator function associated with the operator name.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_values_from_kwargs(
|
|
65
|
+
requirement: Requirement,
|
|
66
|
+
patient: Optional[Patient] = None,
|
|
67
|
+
patient_examination: Optional[PatientExamination] = None,
|
|
68
|
+
**kwargs,
|
|
69
|
+
) -> dict:
|
|
70
|
+
"""
|
|
71
|
+
Extracts and validates values for requirement evaluation.
|
|
72
|
+
|
|
73
|
+
This function aggregates values from keyword arguments along with
|
|
74
|
+
the 'patient' and 'patient_examination' inputs based on the requirement's types.
|
|
75
|
+
It verifies that all requirement types are supported—currently, only types like
|
|
76
|
+
'patient_examination' and 'patient' are allowed—and ensures that the corresponding
|
|
77
|
+
parameters are provided when required. A ValueError is raised if an unsupported
|
|
78
|
+
requirement type is encountered or if a required parameter is missing.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
requirement (Requirement): The evaluation criterion containing requirement types.
|
|
82
|
+
patient (Optional[Patient]): Patient details, required if the requirement includes 'patient'.
|
|
83
|
+
patient_examination (Optional[PatientExamination]): Examination details, required if the requirement includes 'patient_examination'.
|
|
84
|
+
**kwargs: Additional keyword arguments for the evaluation.
|
|
85
|
+
|
|
86
|
+
Raises:
|
|
87
|
+
ValueError: If a requirement type is unsupported or a necessary parameter is missing.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
dict: A dictionary combining 'patient', 'patient_examination', and additional keyword arguments.
|
|
91
|
+
"""
|
|
92
|
+
requirement_types = [_.name for _ in requirement.requirement_types]
|
|
93
|
+
# operators = [_.name for _ in requirement.operators] # Uncomment when needed
|
|
94
|
+
|
|
95
|
+
for requirement_type in requirement_types:
|
|
96
|
+
if requirement_type not in SUPPORTED_REQUIREMENT_TYPES:
|
|
97
|
+
raise ValueError(f"Unsupported requirement type: {requirement_type}")
|
|
98
|
+
|
|
99
|
+
if "patient_examination" in requirement_types:
|
|
100
|
+
if patient_examination is None:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"patient_examination is required for this requirement type"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
if "patient" in requirement_types:
|
|
106
|
+
if patient is None:
|
|
107
|
+
raise ValueError("patient is required for this requirement type")
|
|
108
|
+
|
|
109
|
+
# Prepare and return the extracted values
|
|
110
|
+
return {"patient": patient, "patient_examination": patient_examination, **kwargs}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def evaluate_requirement(
|
|
114
|
+
requirement: Requirement,
|
|
115
|
+
**kwargs,
|
|
116
|
+
):
|
|
117
|
+
"""
|
|
118
|
+
Evaluates the given requirement against provided parameters.
|
|
119
|
+
|
|
120
|
+
This function determines if the requirement is met based on its type and
|
|
121
|
+
the additional evaluation inputs supplied via keyword arguments. Evaluation
|
|
122
|
+
may depend on context-specific data, such as patient or examination details,
|
|
123
|
+
as required by the requirement.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
requirement (Requirement): The requirement to evaluate.
|
|
127
|
+
**kwargs: Additional parameters that support the evaluation process.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
bool: True if the requirement is met, False otherwise.
|
|
131
|
+
"""
|
|
132
|
+
# Implement the logic to evaluate the requirement based on its type
|
|
133
|
+
# and the provided keyword arguments.
|
|
134
|
+
pass
|