nci-cidc-api-modules 1.2.51__py3-none-any.whl → 1.2.52__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.
- cidc_api/__init__.py +1 -1
- cidc_api/models/pydantic/base.py +63 -2
- cidc_api/models/pydantic/stage1/adverse_event.py +36 -28
- cidc_api/models/pydantic/stage1/comorbidity.py +12 -7
- cidc_api/models/pydantic/stage1/demographic.py +32 -22
- cidc_api/models/pydantic/stage1/disease.py +70 -44
- cidc_api/models/pydantic/stage1/exposure.py +9 -7
- cidc_api/models/pydantic/stage1/medical_history.py +10 -7
- cidc_api/models/pydantic/stage1/other_malignancy.py +14 -10
- cidc_api/models/pydantic/stage1/participant.py +16 -11
- cidc_api/models/pydantic/stage1/radiotherapy_dose.py +17 -11
- cidc_api/models/pydantic/stage1/response.py +24 -15
- cidc_api/models/pydantic/stage1/response_by_system.py +78 -45
- cidc_api/models/pydantic/stage1/surgery.py +10 -6
- cidc_api/models/pydantic/stage1/therapy_agent_dose.py +17 -11
- cidc_api/models/pydantic/stage1/treatment.py +18 -11
- {nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/METADATA +1 -1
- {nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/RECORD +21 -21
- {nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/WHEEL +0 -0
- {nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/licenses/LICENSE +0 -0
- {nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/top_level.txt +0 -0
cidc_api/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.2.
|
|
1
|
+
__version__ = "1.2.52"
|
cidc_api/models/pydantic/base.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import copy
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
+
from typing import Self, ClassVar
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, model_validator, ValidationError
|
|
5
|
+
from pydantic_core import InitErrorDetails
|
|
3
6
|
|
|
4
|
-
import
|
|
7
|
+
from cidc_api.models.errors import ValueLocError
|
|
8
|
+
from functools import wraps
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class Base(BaseModel):
|
|
@@ -11,6 +15,7 @@ class Base(BaseModel):
|
|
|
11
15
|
from_attributes=True,
|
|
12
16
|
extra="allow",
|
|
13
17
|
)
|
|
18
|
+
forced_validators: ClassVar = []
|
|
14
19
|
|
|
15
20
|
# Validates the new state and updates the object if valid
|
|
16
21
|
def update(self, **kwargs):
|
|
@@ -33,6 +38,8 @@ class Base(BaseModel):
|
|
|
33
38
|
self.__dict__.update(original_dict)
|
|
34
39
|
raise
|
|
35
40
|
|
|
41
|
+
# CM that delays validation until all fields are applied.
|
|
42
|
+
|
|
36
43
|
@classmethod
|
|
37
44
|
def split_list(cls, val):
|
|
38
45
|
"""Listify fields that are multi-valued in input data, e.g. 'lung|kidney'"""
|
|
@@ -46,3 +53,57 @@ class Base(BaseModel):
|
|
|
46
53
|
return []
|
|
47
54
|
else:
|
|
48
55
|
raise ValueError("Field value must be string or list")
|
|
56
|
+
|
|
57
|
+
@model_validator(mode="wrap")
|
|
58
|
+
@classmethod
|
|
59
|
+
def check_all_forced_validators(cls, data, handler, info) -> Self:
|
|
60
|
+
"""This base validator ensures all registered forced_validator(s) get called along with
|
|
61
|
+
normal model field validators no matter what the outcome of either is. Normal field_validator
|
|
62
|
+
and model_validator decorators don't guarantee this. We want to collect as many errors as we can.
|
|
63
|
+
|
|
64
|
+
Collects errors from both attempts and raises them in a combined ValidationError."""
|
|
65
|
+
|
|
66
|
+
validation_errors = []
|
|
67
|
+
# When assigning attributes to an already-hydrated model, data is an instance of the model
|
|
68
|
+
# instead of a dict and handler is an AssignmentValidatorCallable instead of a ValidatorCallable(!!)
|
|
69
|
+
extracted_data = data.model_dump() if not isinstance(data, dict) else data
|
|
70
|
+
|
|
71
|
+
for validator in cls.forced_validators:
|
|
72
|
+
try:
|
|
73
|
+
func_name = validator.__name__
|
|
74
|
+
func = getattr(cls, func_name)
|
|
75
|
+
func(extracted_data, info)
|
|
76
|
+
except (ValueError, ValueLocError) as e:
|
|
77
|
+
validation_errors.append(
|
|
78
|
+
InitErrorDetails(
|
|
79
|
+
type="value_error",
|
|
80
|
+
loc=e.loc,
|
|
81
|
+
input=extracted_data,
|
|
82
|
+
ctx={"error": e},
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
try:
|
|
86
|
+
# Instantiate the model to ensure all other normal field validations are called
|
|
87
|
+
retval = handler(data)
|
|
88
|
+
except ValidationError as e:
|
|
89
|
+
validation_errors.extend(e.errors())
|
|
90
|
+
if validation_errors:
|
|
91
|
+
raise ValidationError.from_exception_data(title=cls.__name__, line_errors=validation_errors)
|
|
92
|
+
return retval
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def forced_validator(func):
|
|
96
|
+
"""A method marked with this decorator is added to the class list of forced_validators"""
|
|
97
|
+
func._is_forced_validator = True # Tag the function object with a custom attribute
|
|
98
|
+
return func
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def forced_validators(cls):
|
|
102
|
+
"""A class marked with this decorator accumulates its methods marked with force_validator into a list
|
|
103
|
+
for later invocation."""
|
|
104
|
+
|
|
105
|
+
cls.forced_validators = []
|
|
106
|
+
for obj in cls.__dict__.values():
|
|
107
|
+
if getattr(obj, "_is_forced_validator", False):
|
|
108
|
+
cls.forced_validators.append(obj)
|
|
109
|
+
return cls
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import Field, NonNegativeInt, model_validator
|
|
1
|
+
from pydantic import NonNegativeInt
|
|
4
2
|
|
|
5
3
|
from cidc_api.models.pydantic.base import Base
|
|
6
4
|
from cidc_api.reference.ctcae import is_ctcae_other_term
|
|
5
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
7
6
|
from cidc_api.models.errors import ValueLocError
|
|
8
7
|
from cidc_api.models.types import (
|
|
9
8
|
CTCAEEventTerm,
|
|
@@ -18,6 +17,7 @@ from cidc_api.models.types import (
|
|
|
18
17
|
)
|
|
19
18
|
|
|
20
19
|
|
|
20
|
+
@forced_validators
|
|
21
21
|
class AdverseEvent(Base):
|
|
22
22
|
__data_category__ = "adverse_event"
|
|
23
23
|
__cardinality__ = "many"
|
|
@@ -76,44 +76,52 @@ class AdverseEvent(Base):
|
|
|
76
76
|
# The individual therapy (therapy agent, radiotherapy, surgery, stem cell transplant) in the treatment that is attributed to the adverse event.
|
|
77
77
|
individual_therapy: str | None = None
|
|
78
78
|
|
|
79
|
-
@
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
@forced_validator
|
|
80
|
+
@classmethod
|
|
81
|
+
def validate_term_and_code_cr(cls, data, info) -> None:
|
|
82
|
+
event_code = data.get("event_code", None)
|
|
83
|
+
event_term = data.get("event_term", None)
|
|
84
|
+
|
|
85
|
+
if not event_term and not event_code:
|
|
82
86
|
raise ValueLocError(
|
|
83
87
|
"Please provide event_term or event_code or both",
|
|
84
|
-
loc="event_term",
|
|
88
|
+
loc="event_term,event_code",
|
|
85
89
|
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@
|
|
89
|
-
def validate_event_other_specify_cr(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
):
|
|
90
|
+
|
|
91
|
+
@forced_validator
|
|
92
|
+
@classmethod
|
|
93
|
+
def validate_event_other_specify_cr(cls, data, info) -> None:
|
|
94
|
+
event_other_specify = data.get("event_other_specify", None)
|
|
95
|
+
severity_grade_system = data.get("severity_grade_system", None)
|
|
96
|
+
event_term = data.get("event_term", None)
|
|
97
|
+
|
|
98
|
+
if severity_grade_system == "CTCAE" and is_ctcae_other_term(event_term) and not event_other_specify:
|
|
95
99
|
raise ValueLocError(
|
|
96
100
|
'If severity_grade_system is "CTCAE" and the event_code or event_term are of type '
|
|
97
101
|
'"Other, specify", please provide event_other_specify',
|
|
98
102
|
loc="event_other_specify",
|
|
99
103
|
)
|
|
100
|
-
return self
|
|
101
104
|
|
|
102
|
-
@
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
@forced_validator
|
|
106
|
+
@classmethod
|
|
107
|
+
def validate_system_organ_class_cr(cls, data, info) -> None:
|
|
108
|
+
event_other_specify = data.get("event_other_specify", None)
|
|
109
|
+
system_organ_class = data.get("system_organ_class", None)
|
|
110
|
+
|
|
111
|
+
if event_other_specify and not system_organ_class:
|
|
105
112
|
raise ValueLocError(
|
|
106
|
-
"If event_other_specify is provided, please provide system_organ_class.",
|
|
107
|
-
loc="system_organ_class",
|
|
113
|
+
"If event_other_specify is provided, please provide system_organ_class.", loc="system_organ_class"
|
|
108
114
|
)
|
|
109
|
-
return self
|
|
110
115
|
|
|
111
|
-
@
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
116
|
+
@forced_validator
|
|
117
|
+
@classmethod
|
|
118
|
+
def validate_days_to_resolution_of_event_chronology(cls, data, info) -> None:
|
|
119
|
+
days_to_onset_of_event = data.get("days_to_onset_of_event", None)
|
|
120
|
+
days_to_resolution_of_event = data.get("days_to_resolution_of_event", None)
|
|
121
|
+
|
|
122
|
+
if days_to_resolution_of_event is not None and days_to_onset_of_event is not None:
|
|
123
|
+
if int(days_to_resolution_of_event) < int(days_to_onset_of_event):
|
|
115
124
|
raise ValueLocError(
|
|
116
125
|
'Violate "days_to_onset_of_event" <= "days_to_resolution_of_event"',
|
|
117
126
|
loc="days_to_resolution_of_event",
|
|
118
127
|
)
|
|
119
|
-
return self
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
|
-
from pydantic import
|
|
3
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
4
|
|
|
5
5
|
from cidc_api.models.errors import ValueLocError
|
|
6
6
|
from cidc_api.models.pydantic.base import Base
|
|
7
7
|
from cidc_api.models.types import ICD10CMCode, ICD10CMTerm
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
@forced_validators
|
|
10
11
|
class Comorbidity(Base):
|
|
11
12
|
__data_category__ = "comorbidity"
|
|
12
13
|
__cardinality__ = "many"
|
|
@@ -28,11 +29,15 @@ class Comorbidity(Base):
|
|
|
28
29
|
# A descriptive string that names or briefly describes the comorbidity.
|
|
29
30
|
comorbidity_other: str | None = None
|
|
30
31
|
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
@forced_validator
|
|
33
|
+
@classmethod
|
|
34
|
+
def validate_code_or_term_or_other_cr(cls, data, info) -> None:
|
|
35
|
+
comorbidity_term = data.get("comorbidity_term", None)
|
|
36
|
+
comorbidity_other = data.get("comorbidity_other", None)
|
|
37
|
+
comorbidity_code = data.get("comorbidity_code", None)
|
|
38
|
+
|
|
39
|
+
if not comorbidity_code and not comorbidity_term and not comorbidity_other:
|
|
34
40
|
raise ValueLocError(
|
|
35
41
|
'Please provide at least one of "comorbidity_code", "comorbidity_term" or "comorbidity_other".',
|
|
36
|
-
loc="comorbidity_code",
|
|
42
|
+
loc="comorbidity_code,comorbidity_term,comorbidity_other",
|
|
37
43
|
)
|
|
38
|
-
return self
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Annotated, List
|
|
2
2
|
|
|
3
|
-
from pydantic import PositiveInt, NonNegativeFloat, PositiveFloat,
|
|
3
|
+
from pydantic import PositiveInt, NonNegativeFloat, PositiveFloat, BeforeValidator
|
|
4
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
5
|
|
|
5
6
|
from cidc_api.models.errors import ValueLocError
|
|
6
7
|
from cidc_api.models.pydantic.base import Base
|
|
@@ -17,6 +18,7 @@ from cidc_api.models.types import (
|
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
@forced_validators
|
|
20
22
|
class Demographic(Base):
|
|
21
23
|
__data_category__ = "demographic"
|
|
22
24
|
__cardinality__ = "one"
|
|
@@ -91,40 +93,48 @@ class Demographic(Base):
|
|
|
91
93
|
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2681552%20and%20ver_nr=1
|
|
92
94
|
highest_level_of_education: Education | None = None
|
|
93
95
|
|
|
94
|
-
@
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
@forced_validator
|
|
97
|
+
@classmethod
|
|
98
|
+
def validate_age_at_enrollment_cr(cls, data, info) -> None:
|
|
99
|
+
age_at_enrollment_units = data.get("age_at_enrollment_units", None)
|
|
100
|
+
age_90_or_over = data.get("age_90_or_over", None)
|
|
101
|
+
age_at_enrollment = int(data.get("age_at_enrollment", None) or 0)
|
|
102
|
+
|
|
103
|
+
if age_90_or_over:
|
|
104
|
+
if age_at_enrollment or age_at_enrollment_units:
|
|
98
105
|
raise ValueLocError(
|
|
99
106
|
'If "age_90_or_over" is "Yes" then "age_at_enrollment" and "age_at_enrollment_units" must be blank.',
|
|
100
107
|
loc="age_at_enrollment",
|
|
101
108
|
)
|
|
102
109
|
else:
|
|
103
|
-
if not
|
|
110
|
+
if not age_at_enrollment or not age_at_enrollment_units:
|
|
104
111
|
raise ValueLocError(
|
|
105
112
|
'If "age_90_or_over" is "No" then "age_at_enrollment" and "age_at_enrollment_units" are required.',
|
|
106
|
-
loc="
|
|
113
|
+
loc="age_at_enrollment",
|
|
107
114
|
)
|
|
108
|
-
return self
|
|
109
115
|
|
|
110
|
-
@
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
@forced_validator
|
|
117
|
+
@classmethod
|
|
118
|
+
def validate_age_at_enrollment_value(cls, data, info) -> None:
|
|
119
|
+
age_at_enrollment_units = data.get("age_at_enrollment_units", None)
|
|
120
|
+
age_90_or_over = data.get("age_90_or_over", None)
|
|
121
|
+
age_at_enrollment = int(data.get("age_at_enrollment", None) or 0)
|
|
122
|
+
|
|
123
|
+
if not age_90_or_over:
|
|
124
|
+
age_in_years = age_at_enrollment if age_at_enrollment_units == "Years" else age_at_enrollment / 365.25
|
|
116
125
|
if age_in_years >= 90:
|
|
117
126
|
raise ValueLocError(
|
|
118
127
|
'"age_at_enrollment" cannot represent a value greater than 90 years of age.',
|
|
119
128
|
loc="age_at_enrollment",
|
|
120
129
|
)
|
|
121
|
-
return self
|
|
122
130
|
|
|
123
|
-
@
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
@forced_validator
|
|
132
|
+
@classmethod
|
|
133
|
+
def validate_body_surface_area_units_cr(cls, data, info) -> None:
|
|
134
|
+
body_surface_area = data.get("body_surface_area", None)
|
|
135
|
+
body_surface_area_units = data.get("body_surface_area_units", None)
|
|
136
|
+
|
|
137
|
+
if body_surface_area and not body_surface_area_units:
|
|
126
138
|
raise ValueLocError(
|
|
127
|
-
'If "body_surface_area" is provided then "
|
|
128
|
-
loc="body_surface_area_units_other",
|
|
139
|
+
'If "body_surface_area" is provided then "body_surface_area_units" must also be provided.'
|
|
129
140
|
)
|
|
130
|
-
return self
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
from pydantic import NonPositiveInt,
|
|
2
|
-
from
|
|
1
|
+
from pydantic import NonPositiveInt, BeforeValidator
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
3
|
+
|
|
4
|
+
from typing import List, Self, Annotated, get_args, Any
|
|
3
5
|
|
|
4
6
|
from cidc_api.models.errors import ValueLocError
|
|
5
7
|
from cidc_api.models.pydantic.base import Base
|
|
@@ -21,6 +23,7 @@ from cidc_api.models.types import (
|
|
|
21
23
|
)
|
|
22
24
|
|
|
23
25
|
|
|
26
|
+
@forced_validators
|
|
24
27
|
class Disease(Base):
|
|
25
28
|
__data_category__ = "disease"
|
|
26
29
|
__cardinality__ = "many"
|
|
@@ -93,82 +96,105 @@ class Disease(Base):
|
|
|
93
96
|
|
|
94
97
|
extramedullary_organ: Annotated[List[UberonAnatomicalTerm] | None, BeforeValidator(Base.split_list)] = []
|
|
95
98
|
|
|
96
|
-
@
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
@forced_validator
|
|
100
|
+
@classmethod
|
|
101
|
+
def validate_code_or_term_or_description_cr(cls, data, info) -> None:
|
|
102
|
+
morphological_term = data.get("morphological_term", None)
|
|
103
|
+
cancer_type_description = data.get("cancer_type_description", None)
|
|
104
|
+
morphological_code = data.get("morphological_code", None)
|
|
105
|
+
|
|
106
|
+
if not morphological_code and not morphological_term and not cancer_type_description:
|
|
99
107
|
raise ValueLocError(
|
|
100
108
|
'Please provide at least one of "morphological_code", "morphological_term" or "cancer_type_description".',
|
|
101
109
|
loc="morphological_code",
|
|
102
110
|
)
|
|
103
|
-
return self
|
|
104
111
|
|
|
105
|
-
@
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
@forced_validator
|
|
113
|
+
@classmethod
|
|
114
|
+
def validate_cancer_stage_system_version(cls, data, info) -> None:
|
|
115
|
+
cancer_stage_system_version = data.get("cancer_stage_system_version", None)
|
|
116
|
+
cancer_stage_system = data.get("cancer_stage_system", None)
|
|
117
|
+
|
|
118
|
+
msg = f"{cancer_stage_system_version} is not applicable to {cancer_stage_system}"
|
|
119
|
+
if cancer_stage_system == "AJCC" and cancer_stage_system_version not in get_args(CancerStageSystemVersionAJCC):
|
|
111
120
|
raise ValueLocError(msg, loc="cancer_stage_system")
|
|
112
|
-
elif
|
|
121
|
+
elif cancer_stage_system == "RISS" and cancer_stage_system_version not in get_args(
|
|
113
122
|
CancerStageSystemVersionRISS
|
|
114
123
|
):
|
|
115
124
|
raise ValueLocError(msg, loc="cancer_stage_system")
|
|
116
|
-
elif
|
|
125
|
+
elif cancer_stage_system == "FIGO" and cancer_stage_system_version not in get_args(
|
|
117
126
|
CancerStageSystemVersionFIGO
|
|
118
127
|
):
|
|
119
128
|
raise ValueLocError(msg, loc="cancer_stage_system")
|
|
120
|
-
return self
|
|
121
129
|
|
|
122
|
-
@
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
@forced_validator
|
|
131
|
+
@classmethod
|
|
132
|
+
def validate_cancer_stage_system_version_cr(cls, data, info) -> None:
|
|
133
|
+
cancer_stage_system = data.get("cancer_stage_system", None)
|
|
134
|
+
cancer_stage_system_version = data.get("cancer_stage_system_version", None)
|
|
135
|
+
|
|
136
|
+
if cancer_stage_system != "Not Applicable" and not cancer_stage_system_version:
|
|
125
137
|
raise ValueLocError(
|
|
126
|
-
f'Please provide cancer_stage_system_version when cancer_stage_system is "{
|
|
138
|
+
f'Please provide cancer_stage_system_version when cancer_stage_system is "{cancer_stage_system}"',
|
|
127
139
|
loc="cancer_stage_system_version",
|
|
128
140
|
)
|
|
129
|
-
return self
|
|
130
141
|
|
|
131
|
-
@
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
@forced_validator
|
|
143
|
+
@classmethod
|
|
144
|
+
def validate_cancer_stage_cr(cls, data, info) -> None:
|
|
145
|
+
cancer_stage_system = data.get("cancer_stage_system", None)
|
|
146
|
+
cancer_stage = data.get("cancer_stage", None)
|
|
147
|
+
|
|
148
|
+
if cancer_stage_system != "Not Applicable" and not cancer_stage:
|
|
134
149
|
raise ValueLocError(
|
|
135
|
-
f'Please provide cancer_stage when cancer_stage_system is "{
|
|
150
|
+
f'Please provide cancer_stage when cancer_stage_system is "{cancer_stage_system}"',
|
|
136
151
|
loc="cancer_stage",
|
|
137
152
|
)
|
|
138
|
-
return self
|
|
139
153
|
|
|
140
|
-
@
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
@forced_validator
|
|
155
|
+
@classmethod
|
|
156
|
+
def validate_t_category_cr(cls, data, info) -> None:
|
|
157
|
+
cancer_stage_system = data.get("cancer_stage_system", None)
|
|
158
|
+
t_category = data.get("t_category", None)
|
|
159
|
+
|
|
160
|
+
if cancer_stage_system == "AJCC" and not t_category:
|
|
143
161
|
raise ValueLocError(
|
|
144
|
-
f'Please provide t_category when cancer_stage_system is "{
|
|
162
|
+
f'Please provide t_category when cancer_stage_system is "{cancer_stage_system}"',
|
|
145
163
|
loc="t_category",
|
|
146
164
|
)
|
|
147
|
-
return self
|
|
148
165
|
|
|
149
|
-
@
|
|
150
|
-
|
|
151
|
-
|
|
166
|
+
@forced_validator
|
|
167
|
+
@classmethod
|
|
168
|
+
def validate_n_category_cr(cls, data, info) -> None:
|
|
169
|
+
cancer_stage_system = data.get("cancer_stage_system", None)
|
|
170
|
+
n_category = data.get("n_category", None)
|
|
171
|
+
|
|
172
|
+
if cancer_stage_system == "AJCC" and not n_category:
|
|
152
173
|
raise ValueLocError(
|
|
153
|
-
f'Please provide n_category when cancer_stage_system is "{
|
|
174
|
+
f'Please provide n_category when cancer_stage_system is "{cancer_stage_system}"',
|
|
154
175
|
loc="n_category",
|
|
155
176
|
)
|
|
156
|
-
return self
|
|
157
177
|
|
|
158
|
-
@
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
@forced_validator
|
|
179
|
+
@classmethod
|
|
180
|
+
def validate_m_category_cr(cls, data, info) -> None:
|
|
181
|
+
cancer_stage_system = data.get("cancer_stage_system", None)
|
|
182
|
+
m_category = data.get("m_category", None)
|
|
183
|
+
|
|
184
|
+
if cancer_stage_system == "AJCC" and not m_category:
|
|
161
185
|
raise ValueLocError(
|
|
162
|
-
f'Please provide m_category when cancer_stage_system is "{
|
|
186
|
+
f'Please provide m_category when cancer_stage_system is "{cancer_stage_system}"',
|
|
163
187
|
loc="m_category",
|
|
164
188
|
)
|
|
165
|
-
return self
|
|
166
189
|
|
|
167
|
-
@
|
|
168
|
-
|
|
169
|
-
|
|
190
|
+
@forced_validator
|
|
191
|
+
@classmethod
|
|
192
|
+
def validate_extramedullary_organ_cr(cls, data, info) -> None:
|
|
193
|
+
solely_extramedullary_disease = data.get("solely_extramedullary_disease", None)
|
|
194
|
+
extramedullary_organ = data.get("extramedullary_organ", None)
|
|
195
|
+
|
|
196
|
+
if solely_extramedullary_disease in ["No", "Unknown"] and extramedullary_organ:
|
|
170
197
|
raise ValueLocError(
|
|
171
198
|
"If solely_extramedullary_disease indicates no disease, please leave extramedullary_organ blank.",
|
|
172
199
|
loc="extramedullary_organ",
|
|
173
200
|
)
|
|
174
|
-
return self
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import model_validator
|
|
1
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
2
|
|
|
5
3
|
from cidc_api.models.errors import ValueLocError
|
|
6
4
|
from cidc_api.models.pydantic.base import Base
|
|
7
5
|
from cidc_api.models.types import YNU, ExposureType
|
|
8
6
|
|
|
9
7
|
|
|
8
|
+
@forced_validators
|
|
10
9
|
class Exposure(Base):
|
|
11
10
|
__data_category__ = "exposure"
|
|
12
11
|
__cardinality__ = "many"
|
|
@@ -26,11 +25,14 @@ class Exposure(Base):
|
|
|
26
25
|
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=15753203%20and%20ver_nr=1
|
|
27
26
|
exposure_type: ExposureType | None = None
|
|
28
27
|
|
|
29
|
-
@
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
@forced_validator
|
|
29
|
+
@classmethod
|
|
30
|
+
def validate_exposure_type_cr(cls, data, info) -> None:
|
|
31
|
+
carcinogen_exposure = data.get("carcinogen_exposure", None)
|
|
32
|
+
exposure_type = data.get("exposure_type", None)
|
|
33
|
+
|
|
34
|
+
if carcinogen_exposure in ["No", "Unknown"] and exposure_type:
|
|
32
35
|
raise ValueLocError(
|
|
33
36
|
"If carcinogen_exposure indicates non exposure, please leave exposure_type blank.",
|
|
34
37
|
loc="exposure_type",
|
|
35
38
|
)
|
|
36
|
-
return self
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import NonNegativeInt, PositiveFloat, model_validator
|
|
1
|
+
from pydantic import NonNegativeInt, PositiveFloat
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
3
|
|
|
5
4
|
from cidc_api.models.errors import ValueLocError
|
|
6
5
|
from cidc_api.models.pydantic.base import Base
|
|
7
6
|
from cidc_api.models.types import TobaccoSmokingStatus
|
|
8
7
|
|
|
9
8
|
|
|
9
|
+
@forced_validators
|
|
10
10
|
class MedicalHistory(Base):
|
|
11
11
|
__data_category__ = "medical_history"
|
|
12
12
|
__cardinality__ = "one"
|
|
@@ -30,11 +30,14 @@ class MedicalHistory(Base):
|
|
|
30
30
|
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=16089302%20and%20ver_nr=1
|
|
31
31
|
num_prior_systemic_therapies: NonNegativeInt | None = None
|
|
32
32
|
|
|
33
|
-
@
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
@forced_validator
|
|
34
|
+
@classmethod
|
|
35
|
+
def validate_pack_years_smoked_cr(cls, data, info) -> None:
|
|
36
|
+
tobacco_smoking_status = data.get("tobacco_smoking_status", None)
|
|
37
|
+
pack_years_smoked = data.get("pack_years_smoked", None)
|
|
38
|
+
|
|
39
|
+
if tobacco_smoking_status in ["Never Smoker", "Unknown", "Not reported"] and pack_years_smoked:
|
|
36
40
|
raise ValueLocError(
|
|
37
41
|
"If tobacco_smoking_status indicates non-smoker, please leave pack_years_smoked blank.",
|
|
38
42
|
loc="pack_years_smoked",
|
|
39
43
|
)
|
|
40
|
-
return self
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import NonPositiveInt, model_validator
|
|
1
|
+
from pydantic import NonPositiveInt
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
3
|
|
|
5
4
|
from cidc_api.models.errors import ValueLocError
|
|
6
5
|
from cidc_api.models.pydantic.base import Base
|
|
7
6
|
from cidc_api.models.types import UberonAnatomicalTerm, ICDO3MorphologicalCode, ICDO3MorphologicalTerm, MalignancyStatus
|
|
8
7
|
|
|
9
8
|
|
|
9
|
+
@forced_validators
|
|
10
10
|
class OtherMalignancy(Base):
|
|
11
11
|
__data_category__ = "other_malignancy"
|
|
12
12
|
__cardinality__ = "many"
|
|
@@ -37,15 +37,19 @@ class OtherMalignancy(Base):
|
|
|
37
37
|
# Indicates the participant’s current clinical state regarding the cancer diagnosis.
|
|
38
38
|
other_malignancy_status: MalignancyStatus | None = None
|
|
39
39
|
|
|
40
|
-
@
|
|
41
|
-
|
|
40
|
+
@forced_validator
|
|
41
|
+
@classmethod
|
|
42
|
+
def validate_code_or_term_or_description_cr(cls, data, info) -> None:
|
|
43
|
+
other_malignancy_morphological_term = data.get("other_malignancy_morphological_term", None)
|
|
44
|
+
other_malignancy_description = data.get("other_malignancy_description", None)
|
|
45
|
+
other_malignancy_morphological_code = data.get("other_malignancy_morphological_code", None)
|
|
46
|
+
|
|
42
47
|
if (
|
|
43
|
-
not
|
|
44
|
-
and not
|
|
45
|
-
and not
|
|
48
|
+
not other_malignancy_morphological_code
|
|
49
|
+
and not other_malignancy_morphological_term
|
|
50
|
+
and not other_malignancy_description
|
|
46
51
|
):
|
|
47
52
|
raise ValueLocError(
|
|
48
53
|
'Please provide at least one of "morphological_code", "morphological_term" or "malignancy_description".',
|
|
49
|
-
loc="
|
|
54
|
+
loc="other_malignancy_morphological_code",
|
|
50
55
|
)
|
|
51
|
-
return self
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import model_validator
|
|
1
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
2
|
|
|
5
3
|
from cidc_api.models.errors import ValueLocError
|
|
6
4
|
from cidc_api.models.pydantic.base import Base
|
|
@@ -8,6 +6,7 @@ from cidc_api.models.types import YNU
|
|
|
8
6
|
from cidc_api.models.types import OffStudyReason
|
|
9
7
|
|
|
10
8
|
|
|
9
|
+
@forced_validators
|
|
11
10
|
class Participant(Base):
|
|
12
11
|
__data_category__ = "participant"
|
|
13
12
|
__cardinality__ = "one"
|
|
@@ -39,20 +38,26 @@ class Participant(Base):
|
|
|
39
38
|
# Additional information if "Other" is selected for off_study_reason. e.g. "Transfer to another study"
|
|
40
39
|
off_study_reason_other: str | None = None
|
|
41
40
|
|
|
42
|
-
@
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
@forced_validator
|
|
42
|
+
@classmethod
|
|
43
|
+
def off_study_reason_cr(cls, data, info) -> None:
|
|
44
|
+
off_study = data.get("off_study", None)
|
|
45
|
+
off_study_reason = data.get("off_study_reason", None)
|
|
46
|
+
|
|
47
|
+
if off_study == "Yes" and not off_study_reason:
|
|
45
48
|
raise ValueLocError(
|
|
46
49
|
'If "off_study" is "Yes" then "off_study_reason" is required.',
|
|
47
50
|
loc="off_study_reason",
|
|
48
51
|
)
|
|
49
|
-
return self
|
|
50
52
|
|
|
51
|
-
@
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
@forced_validator
|
|
54
|
+
@classmethod
|
|
55
|
+
def off_study_reason_other_cr(cls, data, info) -> None:
|
|
56
|
+
off_study_reason_other = data.get("off_study_reason_other", None)
|
|
57
|
+
off_study_reason = data.get("off_study_reason", None)
|
|
58
|
+
|
|
59
|
+
if off_study_reason == "Other" and not off_study_reason_other:
|
|
54
60
|
raise ValueLocError(
|
|
55
61
|
'If "off_study_reason" is "Other" then "off_study_reason_other" is required.',
|
|
56
62
|
loc="off_study_reason_other",
|
|
57
63
|
)
|
|
58
|
-
return self
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import NonNegativeInt, NonNegativeFloat, model_validator
|
|
1
|
+
from pydantic import NonNegativeInt, NonNegativeFloat
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
3
|
|
|
5
4
|
from cidc_api.models.errors import ValueLocError
|
|
6
5
|
from cidc_api.models.pydantic.base import Base
|
|
@@ -13,6 +12,7 @@ from cidc_api.models.types import (
|
|
|
13
12
|
)
|
|
14
13
|
|
|
15
14
|
|
|
15
|
+
@forced_validators
|
|
16
16
|
class RadiotherapyDose(Base):
|
|
17
17
|
__data_category__ = "radiotherapy_dose"
|
|
18
18
|
__cardinality__ = "many"
|
|
@@ -67,20 +67,26 @@ class RadiotherapyDose(Base):
|
|
|
67
67
|
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=7063755%20and%20ver_nr=1
|
|
68
68
|
radiation_extent: RadiationExtent
|
|
69
69
|
|
|
70
|
-
@
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
@forced_validator
|
|
71
|
+
@classmethod
|
|
72
|
+
def validate_changes_delays_description_cr(cls, data, info) -> None:
|
|
73
|
+
dose_changes_delays = data.get("dose_changes_delays", None)
|
|
74
|
+
changes_delays_description = data.get("changes_delays_description", None)
|
|
75
|
+
|
|
76
|
+
if dose_changes_delays == "Yes" and not changes_delays_description:
|
|
73
77
|
raise ValueLocError(
|
|
74
78
|
'If dose_changes_delays is "Yes", please provide changes_delays_description.',
|
|
75
79
|
loc="changes_delays_description",
|
|
76
80
|
)
|
|
77
|
-
return self
|
|
78
81
|
|
|
79
|
-
@
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
@forced_validator
|
|
83
|
+
@classmethod
|
|
84
|
+
def validate_planned_dose_units_cr(cls, data, info) -> None:
|
|
85
|
+
planned_dose = data.get("planned_dose", None)
|
|
86
|
+
planned_dose_units = data.get("planned_dose_units", None)
|
|
87
|
+
|
|
88
|
+
if planned_dose and not planned_dose_units:
|
|
82
89
|
raise ValueLocError(
|
|
83
90
|
"If planned_dose is provided, please provide planned_dose_units.",
|
|
84
91
|
loc="planned_dose_units",
|
|
85
92
|
)
|
|
86
|
-
return self
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import NonNegativeInt, model_validator
|
|
1
|
+
from pydantic import NonNegativeInt
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
3
|
|
|
5
4
|
from cidc_api.models.errors import ValueLocError
|
|
6
5
|
from cidc_api.models.pydantic.base import Base
|
|
7
6
|
from cidc_api.models.types import SurvivalStatus, YNUNA, CauseOfDeath
|
|
8
7
|
|
|
9
8
|
|
|
9
|
+
@forced_validators
|
|
10
10
|
class Response(Base):
|
|
11
11
|
__data_category__ = "response"
|
|
12
12
|
__cardinality__ = "one"
|
|
@@ -47,29 +47,38 @@ class Response(Base):
|
|
|
47
47
|
# Days from enrollment date to the last time the patient's vital status was verified.
|
|
48
48
|
days_to_last_vital_status: NonNegativeInt | None = None
|
|
49
49
|
|
|
50
|
-
@
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
@forced_validator
|
|
51
|
+
@classmethod
|
|
52
|
+
def validate_cause_of_death_cr(cls, data, info) -> None:
|
|
53
|
+
survival_status = data.get("survival_status", None)
|
|
54
|
+
cause_of_death = data.get("cause_of_death", None)
|
|
55
|
+
|
|
56
|
+
if survival_status == "Dead" and not cause_of_death:
|
|
53
57
|
raise ValueLocError(
|
|
54
58
|
'If survival_status is "Dead" then cause_of_death is required.',
|
|
55
59
|
loc="cause_of_death",
|
|
56
60
|
)
|
|
57
|
-
return self
|
|
58
61
|
|
|
59
|
-
@
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
@forced_validator
|
|
63
|
+
@classmethod
|
|
64
|
+
def validate_cause_of_death_cr2(cls, data, info) -> None:
|
|
65
|
+
survival_status = data.get("survival_status", None)
|
|
66
|
+
cause_of_death = data.get("cause_of_death", None)
|
|
67
|
+
|
|
68
|
+
if survival_status == "Alive" and cause_of_death:
|
|
62
69
|
raise ValueLocError(
|
|
63
70
|
'If survival_status is "Alive", please leave cause_of_death blank.',
|
|
64
71
|
loc="cause_of_death",
|
|
65
72
|
)
|
|
66
|
-
return self
|
|
67
73
|
|
|
68
|
-
@
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
@forced_validator
|
|
75
|
+
@classmethod
|
|
76
|
+
def validate_days_to_death_cr(cls, data, info) -> None:
|
|
77
|
+
survival_status = data.get("survival_status", None)
|
|
78
|
+
days_to_death = data.get("days_to_death", None)
|
|
79
|
+
|
|
80
|
+
if survival_status in ["Alive", "Unknown"] and days_to_death:
|
|
71
81
|
raise ValueLocError(
|
|
72
82
|
"If survival_status does not indicate death, please leave days_to_death blank.",
|
|
73
83
|
loc="days_to_death",
|
|
74
84
|
)
|
|
75
|
-
return self
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
|
-
from pydantic import PositiveInt,
|
|
3
|
+
from pydantic import PositiveInt, NonNegativeInt, model_validator
|
|
4
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
5
|
|
|
5
6
|
from cidc_api.models.errors import ValueLocError
|
|
6
7
|
from cidc_api.models.pydantic.base import Base
|
|
@@ -19,6 +20,7 @@ negative_response_values = [
|
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
@forced_validators
|
|
22
24
|
class ResponseBySystem(Base):
|
|
23
25
|
__data_category__ = "response_by_system"
|
|
24
26
|
__cardinality__ = "many"
|
|
@@ -71,90 +73,117 @@ class ResponseBySystem(Base):
|
|
|
71
73
|
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=5143957%20and%20ver_nr=1
|
|
72
74
|
progression_free_survival: PositiveInt | None = None
|
|
73
75
|
|
|
74
|
-
@
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
@forced_validator
|
|
77
|
+
@classmethod
|
|
78
|
+
def validate_response_duration_cr(cls, data, info) -> None:
|
|
79
|
+
best_overall_response = data.get("best_overall_response", None)
|
|
80
|
+
response_duration = data.get("response_duration", None)
|
|
81
|
+
|
|
82
|
+
if best_overall_response in negative_response_values and response_duration:
|
|
77
83
|
raise ValueLocError(
|
|
78
84
|
"If best_overall_response does not indicate a positive response, "
|
|
79
85
|
"please leave response_duration blank.",
|
|
80
86
|
loc="response_duration",
|
|
81
87
|
)
|
|
82
|
-
return self
|
|
83
88
|
|
|
84
|
-
@
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
@forced_validator
|
|
90
|
+
@classmethod
|
|
91
|
+
def validate_days_to_first_response_cr(cls, data, info) -> None:
|
|
92
|
+
best_overall_response = data.get("best_overall_response", None)
|
|
93
|
+
days_to_first_response = data.get("days_to_first_response", None)
|
|
94
|
+
|
|
95
|
+
if best_overall_response in negative_response_values and days_to_first_response:
|
|
87
96
|
raise ValueLocError(
|
|
88
97
|
"If best_overall_response does not indicate a positive response, "
|
|
89
98
|
"please leave days_to_first_response blank.",
|
|
90
99
|
loc="days_to_first_response",
|
|
91
100
|
)
|
|
92
|
-
return self
|
|
93
101
|
|
|
94
|
-
@
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
@forced_validator
|
|
103
|
+
@classmethod
|
|
104
|
+
def validate_days_to_best_response_cr(cls, data, info) -> None:
|
|
105
|
+
best_overall_response = data.get("best_overall_response", None)
|
|
106
|
+
days_to_best_response = data.get("days_to_best_response", None)
|
|
107
|
+
|
|
108
|
+
if best_overall_response in negative_response_values and days_to_best_response:
|
|
97
109
|
raise ValueLocError(
|
|
98
|
-
"If best_overall_response does not indicate a positive response,
|
|
99
|
-
please leave days_to_best_response blank.",
|
|
110
|
+
"If best_overall_response does not indicate a positive response, "
|
|
111
|
+
"please leave days_to_best_response blank.",
|
|
100
112
|
loc="days_to_best_response",
|
|
101
113
|
)
|
|
102
|
-
return self
|
|
103
114
|
|
|
104
|
-
@
|
|
105
|
-
|
|
106
|
-
|
|
115
|
+
@forced_validator
|
|
116
|
+
@classmethod
|
|
117
|
+
def validate_days_to_disease_progression_cr(cls, data, info) -> None:
|
|
118
|
+
progression = data.get("progression", None)
|
|
119
|
+
days_to_disease_progression = data.get("days_to_disease_progression", None)
|
|
120
|
+
|
|
121
|
+
if progression in ["No", "Unknown", "Not Applicable"] and days_to_disease_progression:
|
|
107
122
|
raise ValueLocError(
|
|
108
|
-
"If progression does not indicate confirmed progression of the disease,
|
|
109
|
-
please leave
|
|
110
|
-
loc="
|
|
123
|
+
"If progression does not indicate confirmed progression of the disease, "
|
|
124
|
+
"please leave days_to_disease_progression blank.",
|
|
125
|
+
loc="days_to_disease_progression",
|
|
111
126
|
)
|
|
112
|
-
return self
|
|
113
127
|
|
|
114
|
-
@
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
@forced_validator
|
|
129
|
+
@classmethod
|
|
130
|
+
def validate_progression_free_survival_cr(cls, data, info) -> None:
|
|
131
|
+
progression_free_survival_event = data.get("progression_free_survival_event", None)
|
|
132
|
+
progression_free_survival = data.get("progression_free_survival", None)
|
|
133
|
+
|
|
134
|
+
if progression_free_survival_event in ["Unknown", "Not Applicable"] and progression_free_survival:
|
|
117
135
|
raise ValueLocError(
|
|
118
|
-
"If progression_free_survival_event is not known,
|
|
119
|
-
please leave progression_free_survival blank.",
|
|
136
|
+
"If progression_free_survival_event is not known, " "please leave progression_free_survival blank.",
|
|
120
137
|
loc="progression_free_survival",
|
|
121
138
|
)
|
|
122
|
-
return self
|
|
123
139
|
|
|
124
|
-
@
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
140
|
+
@forced_validator
|
|
141
|
+
@classmethod
|
|
142
|
+
def validate_days_to_best_response_chronology(cls, data, info) -> None:
|
|
143
|
+
days_to_first_response = data.get("days_to_first_response", None)
|
|
144
|
+
days_to_best_response = data.get("days_to_best_response", None)
|
|
145
|
+
|
|
146
|
+
if days_to_best_response is not None and days_to_first_response is not None:
|
|
147
|
+
if int(days_to_best_response) < int(days_to_first_response):
|
|
128
148
|
raise ValueLocError(
|
|
129
149
|
'Violate "days_to_best_response" >= days_to_first_response"',
|
|
130
150
|
loc="days_to_best_response",
|
|
131
151
|
)
|
|
132
|
-
return self
|
|
133
152
|
|
|
134
|
-
@
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
@forced_validator
|
|
154
|
+
@classmethod
|
|
155
|
+
def validate_days_to_disease_progression_chronology(cls, data, info) -> None:
|
|
156
|
+
days_to_disease_progression = data.get("days_to_disease_progression", None)
|
|
157
|
+
days_to_first_response = data.get("days_to_first_response", None)
|
|
158
|
+
|
|
159
|
+
if days_to_first_response is not None and days_to_disease_progression is not None:
|
|
160
|
+
if int(days_to_first_response) >= int(days_to_disease_progression):
|
|
138
161
|
raise ValueLocError(
|
|
139
162
|
'Violate "days_to_first_response" < "days_to_disease_progression"',
|
|
140
163
|
loc="days_to_first_response",
|
|
141
164
|
)
|
|
142
|
-
return self
|
|
143
165
|
|
|
144
|
-
@
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
@forced_validator
|
|
167
|
+
@classmethod
|
|
168
|
+
def validate_days_to_best_response_progression_chronology(cls, data, info) -> None:
|
|
169
|
+
days_to_disease_progression = data.get("days_to_disease_progression", None)
|
|
170
|
+
days_to_best_response = data.get("days_to_best_response", None)
|
|
171
|
+
|
|
172
|
+
if days_to_best_response is not None and days_to_disease_progression is not None:
|
|
173
|
+
if int(days_to_best_response) >= int(days_to_disease_progression):
|
|
148
174
|
raise ValueLocError(
|
|
149
175
|
'Violate "days_to_best_response" < "days_to_disease_progression"',
|
|
150
176
|
loc="days_to_best_response",
|
|
151
177
|
)
|
|
152
|
-
return self
|
|
153
178
|
|
|
154
179
|
@model_validator(mode="after")
|
|
155
180
|
def validate_days_to_last_vital_status_chronology(self) -> Self:
|
|
156
|
-
if not self.response
|
|
181
|
+
if not self.response:
|
|
182
|
+
return self
|
|
183
|
+
|
|
184
|
+
if not self.response.days_to_last_vital_status:
|
|
157
185
|
return self
|
|
186
|
+
|
|
158
187
|
max_value = max(
|
|
159
188
|
self.response.days_to_last_vital_status or 0,
|
|
160
189
|
self.days_to_first_response or 0,
|
|
@@ -171,8 +200,11 @@ class ResponseBySystem(Base):
|
|
|
171
200
|
|
|
172
201
|
@model_validator(mode="after")
|
|
173
202
|
def validate_days_to_death_chronology(self) -> Self:
|
|
174
|
-
if not self.response
|
|
203
|
+
if not self.response:
|
|
175
204
|
return self
|
|
205
|
+
if not self.response.days_to_death:
|
|
206
|
+
return self
|
|
207
|
+
|
|
176
208
|
max_value = max(
|
|
177
209
|
self.response.days_to_death or 0,
|
|
178
210
|
self.days_to_first_response or 0,
|
|
@@ -185,3 +217,4 @@ class ResponseBySystem(Base):
|
|
|
185
217
|
">= max(days_to_first_response,days_to_best_response,days_to_disease_progression)",
|
|
186
218
|
loc="days_to_death,days_to_first_response,days_to_best_response,days_to_disease_progression",
|
|
187
219
|
)
|
|
220
|
+
return self
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from
|
|
1
|
+
from pydantic import NonNegativeInt
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
2
3
|
|
|
3
|
-
from pydantic import NonNegativeInt, model_validator
|
|
4
4
|
|
|
5
5
|
from cidc_api.models.errors import ValueLocError
|
|
6
6
|
from cidc_api.models.pydantic.base import Base
|
|
7
7
|
from cidc_api.models.types import SurgicalProcedure, UberonAnatomicalTerm, YNU
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
@forced_validators
|
|
10
11
|
class Surgery(Base):
|
|
11
12
|
__data_category__ = "surgery"
|
|
12
13
|
__cardinality__ = "many"
|
|
@@ -43,11 +44,14 @@ class Surgery(Base):
|
|
|
43
44
|
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=13362284%20and%20ver_nr=1
|
|
44
45
|
extent_of_residual_disease: str | None = None
|
|
45
46
|
|
|
46
|
-
@
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
@forced_validator
|
|
48
|
+
@classmethod
|
|
49
|
+
def validate_procedure_other_cr(cls, data, info) -> None:
|
|
50
|
+
procedure = data.get("procedure", None)
|
|
51
|
+
procedure_other = data.get("procedure_other", None)
|
|
52
|
+
|
|
53
|
+
if procedure == "Other, specify" and not procedure_other:
|
|
49
54
|
raise ValueLocError(
|
|
50
55
|
'If procedure is "Other, specify", please provide procedure_other.',
|
|
51
56
|
loc="procedure_other",
|
|
52
57
|
)
|
|
53
|
-
return self
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import NonNegativeInt, NonNegativeFloat, PositiveFloat, model_validator
|
|
1
|
+
from pydantic import NonNegativeInt, NonNegativeFloat, PositiveFloat
|
|
2
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
3
|
|
|
5
4
|
from cidc_api.models.errors import ValueLocError
|
|
6
5
|
from cidc_api.models.pydantic.base import Base
|
|
7
6
|
from cidc_api.models.types import YNU, TherapyAgentDoseUnits
|
|
8
7
|
|
|
9
8
|
|
|
9
|
+
@forced_validators
|
|
10
10
|
class TherapyAgentDose(Base):
|
|
11
11
|
__data_category__ = "therapy_agent_dose"
|
|
12
12
|
__cardinality__ = "many"
|
|
@@ -55,20 +55,26 @@ class TherapyAgentDose(Base):
|
|
|
55
55
|
# Description of the dose changes, misses, or delays.
|
|
56
56
|
changes_delays_description: str | None = None
|
|
57
57
|
|
|
58
|
-
@
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
@forced_validator
|
|
59
|
+
@classmethod
|
|
60
|
+
def validate_changes_delays_description_cr(cls, data, info) -> None:
|
|
61
|
+
dose_changes_delays = data.get("dose_changes_delays", None)
|
|
62
|
+
changes_delays_description = data.get("changes_delays_description", None)
|
|
63
|
+
|
|
64
|
+
if dose_changes_delays == "Yes" and not changes_delays_description:
|
|
61
65
|
raise ValueLocError(
|
|
62
66
|
'If dose_changes_delays is "Yes", please provide changes_delays_description.',
|
|
63
67
|
loc="changes_delays_description",
|
|
64
68
|
)
|
|
65
|
-
return self
|
|
66
69
|
|
|
67
|
-
@
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
@forced_validator
|
|
71
|
+
@classmethod
|
|
72
|
+
def validate_planned_dose_units_cr(cls, data, info) -> None:
|
|
73
|
+
planned_dose = data.get("planned_dose", None)
|
|
74
|
+
planned_dose_units = data.get("planned_dose_units", None)
|
|
75
|
+
|
|
76
|
+
if planned_dose and not planned_dose_units:
|
|
70
77
|
raise ValueLocError(
|
|
71
78
|
"If planned_dose is provided, please provide planned_dose_units.",
|
|
72
79
|
loc="planned_dose_units",
|
|
73
80
|
)
|
|
74
|
-
return self
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from pydantic import model_validator
|
|
1
|
+
from cidc_api.models.pydantic.base import forced_validator, forced_validators
|
|
4
2
|
|
|
5
3
|
from cidc_api.models.errors import ValueLocError
|
|
6
4
|
from cidc_api.models.pydantic.base import Base
|
|
7
5
|
from cidc_api.models.types import YNU, OffTreatmentReason
|
|
8
6
|
|
|
9
7
|
|
|
8
|
+
@forced_validators
|
|
10
9
|
class Treatment(Base):
|
|
11
10
|
__data_category__ = "treatment"
|
|
12
11
|
__cardinality__ = "many"
|
|
@@ -38,20 +37,28 @@ class Treatment(Base):
|
|
|
38
37
|
# If "Other" is selected for "off_treatment_reason", provide a description of the reason.
|
|
39
38
|
off_treatment_reason_other: str | None = None
|
|
40
39
|
|
|
41
|
-
@
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
@forced_validator
|
|
41
|
+
@classmethod
|
|
42
|
+
def validate_off_treatment_reason_cr(cls, data, info) -> None:
|
|
43
|
+
off_treatment = data.get("off_treatment", None)
|
|
44
|
+
off_treatment_reason = data.get("off_treatment_reason", None)
|
|
45
|
+
|
|
46
|
+
if off_treatment == "Yes" and not off_treatment_reason:
|
|
44
47
|
raise ValueLocError(
|
|
45
48
|
'If off_treatment is "Yes", please provide off_treatment_reason.',
|
|
46
49
|
loc="off_treatment_reason",
|
|
47
50
|
)
|
|
48
|
-
return
|
|
51
|
+
return off_treatment_reason
|
|
52
|
+
|
|
53
|
+
@forced_validator
|
|
54
|
+
@classmethod
|
|
55
|
+
def validate_off_treatment_reason_other_cr(cls, data, info) -> None:
|
|
56
|
+
off_treatment_reason = data.get("off_treatment_reason", None)
|
|
57
|
+
off_treatment_reason_other = data.get("off_treatment_reason_other", None)
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
def validate_off_treatment_reason_other_cr(self) -> Self:
|
|
52
|
-
if self.off_treatment_reason == "Other" and not self.off_treatment_reason_other:
|
|
59
|
+
if off_treatment_reason == "Other" and not off_treatment_reason_other:
|
|
53
60
|
raise ValueLocError(
|
|
54
61
|
'If off_treatment_reason is "Other", please provide off_treatment_reason_other.',
|
|
55
62
|
loc="off_treatment_reason_other",
|
|
56
63
|
)
|
|
57
|
-
return
|
|
64
|
+
return off_treatment_reason_other
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
boot.py,sha256=oS_16OgU34iDxyFNpJl5rb5KbfbsCXroUCNLwVPxPNg,416
|
|
2
|
-
cidc_api/__init__.py,sha256=
|
|
2
|
+
cidc_api/__init__.py,sha256=jf5CtcUq4OTsCQcG2e0HUKeI9KD06OUpOQioBuO1h-Y,23
|
|
3
3
|
cidc_api/telemetry.py,sha256=LuZWkG8CKCn23O41RTNxEOQwMmfpp-fQ6zSInZhVJg8,3333
|
|
4
4
|
cidc_api/config/__init__.py,sha256=5mX8GAPxUKV84iS-aGOoE-4m68LsOCGCDptXNdlgvj0,148
|
|
5
5
|
cidc_api/config/db.py,sha256=mEz8ugjvRNGylCqDYHaaMqaZfDh7sbd76BowmfBvq5c,2428
|
|
@@ -81,32 +81,32 @@ cidc_api/models/db/stage2/trial_orm.py,sha256=7_5kA1Yv80GFQykK8AIlFC0vtrsb2H8xLs
|
|
|
81
81
|
cidc_api/models/files/__init__.py,sha256=8BMTnUSHzUbz0lBeEQY6NvApxDD3GMWMduoVMos2g4Y,213
|
|
82
82
|
cidc_api/models/files/details.py,sha256=sZkGM7iEV4-J6IDQCdiMV6KBDLbPxCOqUMaU3aY9rX8,65153
|
|
83
83
|
cidc_api/models/files/facets.py,sha256=vieZ3z_tYB29NqtvNzvKqajVwxM5ZLLZKeyQTq5lheU,33776
|
|
84
|
-
cidc_api/models/pydantic/base.py,sha256=
|
|
84
|
+
cidc_api/models/pydantic/base.py,sha256=sL_0PwEH2vS8BMT_w-zL7CPeaU-R7ih5afRigX6k2DM,4069
|
|
85
85
|
cidc_api/models/pydantic/stage1/__init__.py,sha256=H-NDjCd8isHTgZ7sqz-MIbzC-PpW1kEtR-x-Dyc8r6Q,1667
|
|
86
86
|
cidc_api/models/pydantic/stage1/additional_treatment.py,sha256=TFVzjd1NPLSfH9UnL2W3aZhpZSzDsg97j9IK8t882YA,1050
|
|
87
|
-
cidc_api/models/pydantic/stage1/adverse_event.py,sha256=
|
|
87
|
+
cidc_api/models/pydantic/stage1/adverse_event.py,sha256=ZW5iVnMmX7NtZcsgDAjzPorWPZDSJSmTXGK1EsU9AaQ,5167
|
|
88
88
|
cidc_api/models/pydantic/stage1/baseline_clinical_assessment.py,sha256=5FYvtOVf_nIK1h3xTs3vQgJe2ivQSVHt_wfrTSDgVxU,1119
|
|
89
|
-
cidc_api/models/pydantic/stage1/comorbidity.py,sha256=
|
|
89
|
+
cidc_api/models/pydantic/stage1/comorbidity.py,sha256=IfxFb0dZAdCY--Ze5kiZ3CrzVKJtuknpcpvrQ3ki-gI,1769
|
|
90
90
|
cidc_api/models/pydantic/stage1/consent_group.py,sha256=aP_nd0GKK6K7VH-6IZRSDDX2RxFM_j9B36FIRGwysOU,1216
|
|
91
|
-
cidc_api/models/pydantic/stage1/demographic.py,sha256=
|
|
92
|
-
cidc_api/models/pydantic/stage1/disease.py,sha256=
|
|
93
|
-
cidc_api/models/pydantic/stage1/exposure.py,sha256=
|
|
91
|
+
cidc_api/models/pydantic/stage1/demographic.py,sha256=S2riNy6njMSamgupFAsbM656TDamQExRzUGKNYaBaOo,6752
|
|
92
|
+
cidc_api/models/pydantic/stage1/disease.py,sha256=G8d2iqsMhmDA3uxjOitxzmlFKIRxyUMp-o-H7eUfZFg,9250
|
|
93
|
+
cidc_api/models/pydantic/stage1/exposure.py,sha256=bd9gUmOQ7ge-xvoxD17pX95MKxfHKD2xNaFEJAMAkn4,1571
|
|
94
94
|
cidc_api/models/pydantic/stage1/gvhd_diagnosis_acute.py,sha256=jVMoWuLfspOZtOpC7rg1EzdvEAg8qEabcTaS6RRobZ8,1395
|
|
95
95
|
cidc_api/models/pydantic/stage1/gvhd_diagnosis_chronic.py,sha256=SRwkAL07YyIZM4bYCqm01aKqSvj3yRmpsqFKxAZEZoM,1382
|
|
96
96
|
cidc_api/models/pydantic/stage1/gvhd_organ_acute.py,sha256=VxlNGRJYjm9tW2lyLll8pD0t9JZs18gicnH-Ci_nR2I,771
|
|
97
97
|
cidc_api/models/pydantic/stage1/gvhd_organ_chronic.py,sha256=Fnp1Qm-ZDDtDLrgOAnEOUnQVNWI9WrTSomiFPStGmjc,810
|
|
98
|
-
cidc_api/models/pydantic/stage1/medical_history.py,sha256=
|
|
99
|
-
cidc_api/models/pydantic/stage1/other_malignancy.py,sha256=
|
|
100
|
-
cidc_api/models/pydantic/stage1/participant.py,sha256=
|
|
98
|
+
cidc_api/models/pydantic/stage1/medical_history.py,sha256=z-cE_ChyWUKTvWjVhKMUAYvap9YfpZn6Z2BiHhBXx6g,2000
|
|
99
|
+
cidc_api/models/pydantic/stage1/other_malignancy.py,sha256=VIzhbkauaDfZdPxhTYpqNtwYBxE_4osE9wyZ67LQJx4,2622
|
|
100
|
+
cidc_api/models/pydantic/stage1/participant.py,sha256=QfRkK2okY7nM84y9QMK12kZLURBGk-HNWWHL4pQZjec,2655
|
|
101
101
|
cidc_api/models/pydantic/stage1/prior_treatment.py,sha256=axhyPHwYCoIvcIonjyPJ95HUyaAXqIuOd6hhoDM3LJU,2101
|
|
102
|
-
cidc_api/models/pydantic/stage1/radiotherapy_dose.py,sha256=
|
|
103
|
-
cidc_api/models/pydantic/stage1/response.py,sha256=
|
|
104
|
-
cidc_api/models/pydantic/stage1/response_by_system.py,sha256=
|
|
102
|
+
cidc_api/models/pydantic/stage1/radiotherapy_dose.py,sha256=gDaL1OQuSuTauml6HafBiVvBn8gcynS3G1fa9KNhEPA,3833
|
|
103
|
+
cidc_api/models/pydantic/stage1/response.py,sha256=TJlKmUz9SypbbDnv6iSmLb4KPEuFUPe9Q8iNSGsLbfI,3491
|
|
104
|
+
cidc_api/models/pydantic/stage1/response_by_system.py,sha256=gjyo_jDyc2xHNNrUwoFqH3Fz51v2eSr3JRHkH0xwVqI,9851
|
|
105
105
|
cidc_api/models/pydantic/stage1/specimen.py,sha256=scDekZ-RtXOQTTLburhqT3RF4KM34iY9NAV1wYi_HSg,1281
|
|
106
106
|
cidc_api/models/pydantic/stage1/stem_cell_transplant.py,sha256=XsDVUksqbIprPxHwLlwHGyji4jsIFNMcIk-S0H4rDnQ,1228
|
|
107
|
-
cidc_api/models/pydantic/stage1/surgery.py,sha256=
|
|
108
|
-
cidc_api/models/pydantic/stage1/therapy_agent_dose.py,sha256=
|
|
109
|
-
cidc_api/models/pydantic/stage1/treatment.py,sha256=
|
|
107
|
+
cidc_api/models/pydantic/stage1/surgery.py,sha256=sHPzYMNusYIQrC6gjWopwxo0j9BNOq_Tcl6HKAMvuAQ,2421
|
|
108
|
+
cidc_api/models/pydantic/stage1/therapy_agent_dose.py,sha256=XX7JM4CdvTuAsbI5rI4wYDcbevP46BetTk_z7N_EL-M,3527
|
|
109
|
+
cidc_api/models/pydantic/stage1/treatment.py,sha256=eEjCcmkLdPmS2kg9dtUm3pt9apo1sRypBbte_xUVAYU,2607
|
|
110
110
|
cidc_api/models/pydantic/stage1/trial.py,sha256=EFsMVMxxHbPeaBb6JG9JpX1qjtoho2t-GZnaI0gsYrY,1859
|
|
111
111
|
cidc_api/models/pydantic/stage2/__init__.py,sha256=OQRG5wFkNsyAU_0KI1W5PDn8pBDe1EJDcgKZ19shh_g,2331
|
|
112
112
|
cidc_api/models/pydantic/stage2/additional_treatment.py,sha256=TFVzjd1NPLSfH9UnL2W3aZhpZSzDsg97j9IK8t882YA,1050
|
|
@@ -160,8 +160,8 @@ cidc_api/shared/gcloud_client.py,sha256=EJ2ZzZ0gwDOZx18PEzXil4F-jpruacShkVIoecw8
|
|
|
160
160
|
cidc_api/shared/jose.py,sha256=-qzGzEDAlokEp9E7WtBtQkXyyfPWTYXlwYpCqVJWmqM,1830
|
|
161
161
|
cidc_api/shared/rest_utils.py,sha256=RwR30WOUAYCxL7V-i2totEyeriG30GbBDvBcpLXhM9w,6594
|
|
162
162
|
cidc_api/shared/utils.py,sha256=-gLnzxCR9E6h0plt2xrNisUG5_Y6GhhVwz3DgDIzpvs,367
|
|
163
|
-
nci_cidc_api_modules-1.2.
|
|
164
|
-
nci_cidc_api_modules-1.2.
|
|
165
|
-
nci_cidc_api_modules-1.2.
|
|
166
|
-
nci_cidc_api_modules-1.2.
|
|
167
|
-
nci_cidc_api_modules-1.2.
|
|
163
|
+
nci_cidc_api_modules-1.2.52.dist-info/licenses/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
|
|
164
|
+
nci_cidc_api_modules-1.2.52.dist-info/METADATA,sha256=LmysKc5tWnhHucOWIA2ZesTGvnWy63VG7a6zw8bRtXQ,40301
|
|
165
|
+
nci_cidc_api_modules-1.2.52.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
166
|
+
nci_cidc_api_modules-1.2.52.dist-info/top_level.txt,sha256=BSJqF6ura8-bWCKZvarvQEKvidMM05lH0bLQsNOrI0o,14
|
|
167
|
+
nci_cidc_api_modules-1.2.52.dist-info/RECORD,,
|
|
File without changes
|
{nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{nci_cidc_api_modules-1.2.51.dist-info → nci_cidc_api_modules-1.2.52.dist-info}/top_level.txt
RENAMED
|
File without changes
|