endoreg-db 0.6.2__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/distribution/numeric/data.yaml +1 -1
- 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/mermaid/Overall_flow_patient_finding_intervention.md +10 -0
- endoreg_db/mermaid/anonymized_image_annotation.md +20 -0
- endoreg_db/mermaid/binary_classification_annotation.md +50 -0
- endoreg_db/mermaid/classification.md +8 -0
- endoreg_db/mermaid/examination.md +8 -0
- endoreg_db/mermaid/findings.md +7 -0
- endoreg_db/mermaid/image_classification.md +28 -0
- endoreg_db/mermaid/interventions.md +8 -0
- endoreg_db/mermaid/morphology.md +8 -0
- endoreg_db/mermaid/patient_creation.md +14 -0
- endoreg_db/mermaid/video_segmentation_annotation.md +17 -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/raw_pdf_anony_text_validation.py +137 -0
- endoreg_db/serializers/raw_pdf_meta_validation.py +223 -0
- endoreg_db/serializers/raw_video_meta_validation.py +163 -1
- endoreg_db/serializers/video_segmentation.py +208 -126
- endoreg_db/urls.py +127 -14
- 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/raw_pdf_anony_text_validation_views.py +95 -0
- endoreg_db/views/raw_pdf_meta_validation_views.py +111 -0
- endoreg_db/views/raw_video_meta_validation_views.py +128 -18
- endoreg_db/views/video_segmentation_views.py +28 -11
- endoreg_db/views/views.py +107 -0
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/METADATA +1 -1
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/RECORD +96 -46
- endoreg_db/management/commands/load_name_data.py +0 -37
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/WHEEL +0 -0
- {endoreg_db-0.6.2.dist-info → endoreg_db-0.6.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Currently those strings MUST match the ones
|
|
2
|
+
# in the requirement_type data definitions
|
|
3
|
+
from collections import namedtuple
|
|
4
|
+
from endoreg_db.models import (
|
|
5
|
+
Requirement,
|
|
6
|
+
RequirementType,
|
|
7
|
+
PatientExamination,
|
|
8
|
+
PatientFindingIntervention,
|
|
9
|
+
Examination,
|
|
10
|
+
ExaminationIndication,
|
|
11
|
+
Disease,
|
|
12
|
+
DiseaseClassificationChoice,
|
|
13
|
+
PatientEvent,
|
|
14
|
+
PatientFinding,
|
|
15
|
+
PatientFindingMorphology,
|
|
16
|
+
)
|
|
17
|
+
from typing import List
|
|
18
|
+
from icecream import ic
|
|
19
|
+
# Requirement has RequirementTypes
|
|
20
|
+
# Requirement has RequirementOperators
|
|
21
|
+
# For each Operator/Type pair, there must be a custom function
|
|
22
|
+
# for evaluation
|
|
23
|
+
|
|
24
|
+
OperatorTypeTuple = namedtuple("OperatorTypeTuple", ["operator", "requirement_type"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
data_model_dict = {
|
|
28
|
+
"patient_examination": PatientExamination,
|
|
29
|
+
"finding_intervention": PatientFindingIntervention,
|
|
30
|
+
"finding_interventions": List[PatientFindingIntervention],
|
|
31
|
+
"examination": Examination,
|
|
32
|
+
"examination_indication": ExaminationIndication,
|
|
33
|
+
"disease": Disease,
|
|
34
|
+
"disease_classification_choice": DiseaseClassificationChoice,
|
|
35
|
+
"event": PatientEvent,
|
|
36
|
+
"finding": PatientFinding,
|
|
37
|
+
"finding_morphology": PatientFindingMorphology,
|
|
38
|
+
# Add more mappings as needed
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def evaluate_operator_type_tuple(
|
|
43
|
+
operator_type_tuple: OperatorTypeTuple, data: object, **kwargs
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Evaluates the requirement type and operator tuple against the provided data.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
operator_type_tuple (OperatorTypeTuple): Tuple containing requirement type and operator.
|
|
50
|
+
data (object): The data to evaluate.
|
|
51
|
+
**kwargs: Additional keyword arguments.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
bool: True if the evaluation is successful, False otherwise.
|
|
55
|
+
"""
|
|
56
|
+
# data = kwargs.get(name, None)
|
|
57
|
+
|
|
58
|
+
# # make sure data type matches the model for the req_type name
|
|
59
|
+
# if data is None:
|
|
60
|
+
# raise ValueError(f"No data found for requirement type: {name}")
|
|
61
|
+
# if name not in data_model_dict:
|
|
62
|
+
# raise ValueError(f"Unknown requirement type: {name}")
|
|
63
|
+
|
|
64
|
+
# model = data_model_dict[name]
|
|
65
|
+
# if not isinstance(data, model):
|
|
66
|
+
# raise TypeError(
|
|
67
|
+
# f"Data type mismatch for {name}: expected {model}, got {type(data)}"
|
|
68
|
+
# )
|
|
69
|
+
|
|
70
|
+
requirement_type = operator_type_tuple.requirement_type
|
|
71
|
+
operator = operator_type_tuple.operator
|
|
72
|
+
|
|
73
|
+
# Call the appropriate function based on the requirement type and operator
|
|
74
|
+
# This is a placeholder; actual implementation will depend on your logic
|
|
75
|
+
return True # Replace with actual evaluation logic
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def evaluate_requirement(requirement: Requirement, **kwargs):
|
|
79
|
+
requirement_types = requirement.requirement_types.all()
|
|
80
|
+
requirement_operators = requirement.operators.all()
|
|
81
|
+
operator_return_values = {}
|
|
82
|
+
operator_evaluation_results = {}
|
|
83
|
+
|
|
84
|
+
for requirement_operator in requirement_operators:
|
|
85
|
+
# Create tuples
|
|
86
|
+
requirement_type_operator_tuples = [
|
|
87
|
+
OperatorTypeTuple(requirement_operator, rt) for ro in requirement_types
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
eval_result = [
|
|
91
|
+
evaluate_operator_type_tuple(t, **kwargs)
|
|
92
|
+
for t in requirement_type_operator_tuples
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
operator_evaluation_results[requirement_operator.name] = eval_result
|
|
96
|
+
|
|
97
|
+
# TODO FIX
|
|
98
|
+
operator_return_values[requirement_operator.name] = (
|
|
99
|
+
requirement_operator.evaluate_return_values()
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return operator_evaluation_results, operator_return_values
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RequirementOperatorManager(models.Manager):
|
|
6
|
+
def get_by_natural_key(self, name):
|
|
7
|
+
"""
|
|
8
|
+
Retrieve a RequirementOperator instance by its natural key.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
name (str): The unique name representing the natural key.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
RequirementOperator: The model instance matching the provided name.
|
|
15
|
+
"""
|
|
16
|
+
return self.get(name=name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RequirementOperator(models.Model):
|
|
20
|
+
"""
|
|
21
|
+
A class representing a requirement operator.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name (str): The name of the requirement operator.
|
|
25
|
+
name_de (str): The German name of the requirement operator.
|
|
26
|
+
name_en (str): The English name of the requirement operator.
|
|
27
|
+
description (str): A description of the requirement operator.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name = models.CharField(max_length=100, unique=True)
|
|
31
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
32
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
33
|
+
description = models.TextField(blank=True, null=True)
|
|
34
|
+
|
|
35
|
+
objects = RequirementOperatorManager()
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from endoreg_db.models.requirement.requirement import Requirement
|
|
39
|
+
|
|
40
|
+
requirements: models.QuerySet[Requirement]
|
|
41
|
+
|
|
42
|
+
def natural_key(self):
|
|
43
|
+
"""
|
|
44
|
+
Return the natural key for the requirement operator.
|
|
45
|
+
|
|
46
|
+
This method returns a tuple containing the operator's name, which serves as its
|
|
47
|
+
natural key for serialization and unique identification within the system.
|
|
48
|
+
"""
|
|
49
|
+
return (self.name,)
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
"""
|
|
53
|
+
Return the string representation of the requirement operator.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str: The name attribute of the operator.
|
|
57
|
+
"""
|
|
58
|
+
return str(self.name)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RequirementSetTypeManager(models.Manager):
|
|
6
|
+
"""
|
|
7
|
+
Manager for RequirementSetType with custom query methods.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def get_by_natural_key(self, name: str) -> "RequirementSetType":
|
|
11
|
+
"""
|
|
12
|
+
Retrieves a RequirementSetType instance using its natural key.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
name (str): The unique name that serves as the natural key.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
RequirementSetType: The matching RequirementSetType instance.
|
|
19
|
+
"""
|
|
20
|
+
return self.get(name=name)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RequirementSetType(models.Model):
|
|
24
|
+
"""
|
|
25
|
+
A class representing a type of requirement set.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
name (str): The name of the requirement set type.
|
|
29
|
+
name_de (str): The German name of the requirement set type.
|
|
30
|
+
name_en (str): The English name of the requirement set type.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name = models.CharField(max_length=100, unique=True)
|
|
34
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
35
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
36
|
+
description = models.TextField(blank=True, null=True)
|
|
37
|
+
|
|
38
|
+
objects = RequirementSetTypeManager()
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from endoreg_db.models.requirement import RequirementType
|
|
42
|
+
|
|
43
|
+
requirement_types: models.QuerySet[RequirementType]
|
|
44
|
+
|
|
45
|
+
def natural_key(self):
|
|
46
|
+
"""
|
|
47
|
+
Return the natural key tuple for the instance.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
tuple: A one-element tuple containing the instance's name, used as its natural key.
|
|
51
|
+
"""
|
|
52
|
+
return (self.name,)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class RequirementSetManager(models.Manager):
|
|
56
|
+
def get_by_natural_key(self, name):
|
|
57
|
+
"""
|
|
58
|
+
Retrieves a model instance by its natural key.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
name: The natural key value, typically corresponding to the model's unique name.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
The model instance whose name matches the provided natural key.
|
|
65
|
+
"""
|
|
66
|
+
return self.get(name=name)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class RequirementSet(models.Model):
|
|
70
|
+
"""
|
|
71
|
+
A class representing a set of requirements.
|
|
72
|
+
|
|
73
|
+
Attributes:
|
|
74
|
+
name (str): The name of the requirement set.
|
|
75
|
+
name_de (str): The German name of the requirement set.
|
|
76
|
+
name_en (str): The English name of the requirement set.
|
|
77
|
+
description (str): A description of the requirement set.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
name = models.CharField(max_length=100, unique=True)
|
|
81
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
82
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
83
|
+
description = models.TextField(blank=True, null=True)
|
|
84
|
+
requirements = models.ManyToManyField(
|
|
85
|
+
"Requirement",
|
|
86
|
+
blank=True,
|
|
87
|
+
related_name="requirement_sets",
|
|
88
|
+
)
|
|
89
|
+
links_to_sets = models.ManyToManyField(
|
|
90
|
+
"RequirementSet",
|
|
91
|
+
blank=True,
|
|
92
|
+
related_name="links_from_sets",
|
|
93
|
+
)
|
|
94
|
+
requirement_set_type = models.ForeignKey(
|
|
95
|
+
"RequirementSetType",
|
|
96
|
+
on_delete=models.CASCADE,
|
|
97
|
+
related_name="requirement_sets",
|
|
98
|
+
blank=True,
|
|
99
|
+
null=True,
|
|
100
|
+
)
|
|
101
|
+
information_sources = models.ManyToManyField(
|
|
102
|
+
"InformationSource",
|
|
103
|
+
related_name="requirement_sets",
|
|
104
|
+
blank=True,
|
|
105
|
+
)
|
|
106
|
+
objects = RequirementSetManager()
|
|
107
|
+
|
|
108
|
+
if TYPE_CHECKING:
|
|
109
|
+
from endoreg_db.models import Requirement, InformationSource
|
|
110
|
+
|
|
111
|
+
requirements: models.QuerySet[Requirement]
|
|
112
|
+
information_source: InformationSource
|
|
113
|
+
requirement_set_type: RequirementSetType
|
|
114
|
+
linked_sets: models.QuerySet["RequirementSet"]
|
|
115
|
+
|
|
116
|
+
def natural_key(self):
|
|
117
|
+
"""Return the natural key as a tuple containing the instance's name."""
|
|
118
|
+
return (self.name,)
|
|
119
|
+
|
|
120
|
+
def __str__(self):
|
|
121
|
+
"""
|
|
122
|
+
Return the string representation of the requirement set.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
str: The name of the requirement set.
|
|
126
|
+
"""
|
|
127
|
+
return str(self.name)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from typing import List, TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RiskManager(models.Manager):
|
|
6
|
+
def get_by_natural_key(self, name):
|
|
7
|
+
"""
|
|
8
|
+
Retrieve a risk instance using its natural key.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
name: The unique name identifying the risk instance.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
The risk instance with the matching name.
|
|
15
|
+
"""
|
|
16
|
+
return self.get(name=name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Risk(models.Model):
|
|
20
|
+
"""
|
|
21
|
+
A class representing a risk.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name (str): The name of the risk.
|
|
25
|
+
name_de (str): The German name of the risk.
|
|
26
|
+
name_en (str): The English name of the risk.
|
|
27
|
+
description (str): A description of the risk.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name = models.CharField(max_length=100, unique=True)
|
|
31
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
32
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
33
|
+
description = models.TextField(blank=True, null=True)
|
|
34
|
+
|
|
35
|
+
risk_value = models.FloatField(
|
|
36
|
+
blank=True,
|
|
37
|
+
null=True,
|
|
38
|
+
help_text="Risk value for the risk. If not set, the risk is not used in calculations.",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
risk_type = models.ForeignKey(
|
|
42
|
+
"RiskType",
|
|
43
|
+
on_delete=models.CASCADE,
|
|
44
|
+
related_name="risks",
|
|
45
|
+
blank=True,
|
|
46
|
+
null=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
objects = RiskManager()
|
|
50
|
+
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
from endoreg_db.models.risk.risk_type import RiskType
|
|
53
|
+
|
|
54
|
+
risk_types: RiskType
|
|
55
|
+
|
|
56
|
+
def natural_key(self):
|
|
57
|
+
"""
|
|
58
|
+
Return a tuple containing the natural key of the risk instance.
|
|
59
|
+
|
|
60
|
+
The tuple consists of the unique 'name' attribute, which enables natural key lookups
|
|
61
|
+
and serialization within Django.
|
|
62
|
+
"""
|
|
63
|
+
return (self.name,)
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
"""
|
|
67
|
+
Return the string representation of the risk.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
str: The risk's name.
|
|
71
|
+
"""
|
|
72
|
+
return str(self.name)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RiskTypeManager(models.Manager):
|
|
6
|
+
def get_by_natural_key(self, name):
|
|
7
|
+
"""
|
|
8
|
+
Retrieves a RiskType instance using its natural key.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
name (str): The unique name identifying the RiskType instance.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
RiskType: The matching instance with the provided name.
|
|
15
|
+
"""
|
|
16
|
+
return self.get(name=name)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RiskType(models.Model):
|
|
20
|
+
"""
|
|
21
|
+
A class representing a risk type.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
name (str): The name of the risk type.
|
|
25
|
+
name_de (str): The German name of the risk type.
|
|
26
|
+
name_en (str): The English name of the risk type.
|
|
27
|
+
description (str): A description of the risk type.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
name = models.CharField(max_length=100, unique=True)
|
|
31
|
+
name_de = models.CharField(max_length=100, blank=True, null=True)
|
|
32
|
+
name_en = models.CharField(max_length=100, blank=True, null=True)
|
|
33
|
+
description = models.TextField(blank=True, null=True)
|
|
34
|
+
|
|
35
|
+
objects = RiskTypeManager()
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from endoreg_db.models.risk.risk import Risk
|
|
39
|
+
|
|
40
|
+
risks: models.QuerySet[Risk]
|
|
41
|
+
|
|
42
|
+
def natural_key(self):
|
|
43
|
+
"""
|
|
44
|
+
Return the natural key for this risk type.
|
|
45
|
+
|
|
46
|
+
This method returns a tuple containing only the risk type's unique name, which is used
|
|
47
|
+
to identify the instance naturally.
|
|
48
|
+
"""
|
|
49
|
+
return (self.name,)
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
"""
|
|
53
|
+
Return the risk type's name as its string representation.
|
|
54
|
+
"""
|
|
55
|
+
return str(self.name)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from rest_framework import serializers
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from ..models import RawPdfFile
|
|
5
|
+
|
|
6
|
+
class RawPdfAnonyTextSerializer(serializers.ModelSerializer):
|
|
7
|
+
"""
|
|
8
|
+
Serializer to fetch PDF metadata along with `anonymized_text` from `RawPdfFile`.
|
|
9
|
+
Ensures Vue.js can process JSON efficiently.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
pdf_url = serializers.SerializerMethodField()
|
|
13
|
+
full_pdf_path = serializers.SerializerMethodField()
|
|
14
|
+
file = serializers.SerializerMethodField()
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
model = RawPdfFile
|
|
18
|
+
fields = ['id', 'file', 'pdf_url', 'full_pdf_path',
|
|
19
|
+
'sensitive_meta_id', 'anonymized_text']
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def get_next_pdf(last_id=None):
|
|
23
|
+
"""
|
|
24
|
+
Retrieves the first available PDF if `last_id` is NOT provided.
|
|
25
|
+
Otherwise, fetches the next available PDF where `id > last_id`.
|
|
26
|
+
"""
|
|
27
|
+
query_filter = {} if last_id is None else {"id__gt": int(last_id)}
|
|
28
|
+
pdf_entry = RawPdfFile.objects.filter(**query_filter).order_by('id').first()
|
|
29
|
+
return pdf_entry
|
|
30
|
+
|
|
31
|
+
def get_pdf_url(self, obj):
|
|
32
|
+
"""
|
|
33
|
+
Generates the full URL where Vue.js can fetch and display the PDF.
|
|
34
|
+
"""
|
|
35
|
+
request = self.context.get('request')
|
|
36
|
+
return request.build_absolute_uri(f"/api/pdf/anony_text/?id={obj.id}") if request and obj.file else None
|
|
37
|
+
|
|
38
|
+
def get_file(self, obj):
|
|
39
|
+
"""
|
|
40
|
+
Returns the relative file path stored in the database.
|
|
41
|
+
"""
|
|
42
|
+
return str(obj.file.name).strip() if obj.file else None
|
|
43
|
+
|
|
44
|
+
def get_full_pdf_path(self, obj):
|
|
45
|
+
"""
|
|
46
|
+
Constructs the full absolute file path using `settings.MEDIA_ROOT`.
|
|
47
|
+
"""
|
|
48
|
+
if not obj.file:
|
|
49
|
+
return None
|
|
50
|
+
pdf_relative_path = str(obj.file.name)
|
|
51
|
+
full_path = Path(settings.MEDIA_ROOT) / pdf_relative_path
|
|
52
|
+
return str(full_path) if full_path.exists() else None
|
|
53
|
+
|
|
54
|
+
def validate_anonymized_text(self, value):
|
|
55
|
+
"""
|
|
56
|
+
Ensures the anonymized_text is not empty or too long.
|
|
57
|
+
"""
|
|
58
|
+
if not value.strip():
|
|
59
|
+
raise serializers.ValidationError("Anonymized text cannot be empty.")
|
|
60
|
+
if len(value) > 5000: # Arbitrary limit to prevent excessively long text
|
|
61
|
+
raise serializers.ValidationError("Anonymized text exceeds the maximum length of 5000 characters.")
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
def update(self, instance, validated_data):
|
|
65
|
+
"""
|
|
66
|
+
Updates only `anonymized_text` without modifying other fields.
|
|
67
|
+
"""
|
|
68
|
+
instance.anonymized_text = validated_data.get('anonymized_text', instance.anonymized_text)
|
|
69
|
+
instance.save()
|
|
70
|
+
return instance
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
await import('https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js');
|
|
76
|
+
|
|
77
|
+
const fetchPdfWithAnonymizedText = async (lastId = null) => {
|
|
78
|
+
const url = lastId
|
|
79
|
+
? `http://localhost:8000/api/pdf/anony_text/?last_id=${lastId}`
|
|
80
|
+
: "http://localhost:8000/api/pdf/anony_text/";
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const response = await axios.get(url, { headers: { "Accept": "application/json" } });
|
|
84
|
+
console.log("PDF Data:", response.data);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error("Error fetching PDF:", error.response?.data || error);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
fetchPdfWithAnonymizedText();
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
await import('https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js');
|
|
96
|
+
|
|
97
|
+
const updateAnonymizedText = async (pdfId, newText) => {
|
|
98
|
+
try {
|
|
99
|
+
const response = await axios.patch("http://localhost:8000/api/pdf/update_anony_text/", {
|
|
100
|
+
id: pdfId,
|
|
101
|
+
anonymized_text: newText
|
|
102
|
+
}, { headers: { "Content-Type": "application/json" } });
|
|
103
|
+
|
|
104
|
+
console.log("Update Success:", response.data);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error("Update Error:", error.response?.data || error);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
updateAnonymizedText(1, "Updated anonymized text.");
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
await import('https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js');
|
|
116
|
+
|
|
117
|
+
const updateAnonymizedText = async () => {
|
|
118
|
+
const updatedData = {
|
|
119
|
+
id: 1,
|
|
120
|
+
anonymized_text: "This is the updated anonymized text."
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const response = await axios.patch("http://localhost:8000/api/pdf/update_anony_text/", updatedData, {
|
|
125
|
+
headers: { "Content-Type": "application/json" }
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
console.log("Update Success:", response.data);
|
|
129
|
+
alert("Anonymized text updated successfully!");
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Update Error:", error.response?.data || error);
|
|
132
|
+
alert("Failed to update anonymized text.");
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
updateAnonymizedText();
|
|
137
|
+
"""
|