nci-cidc-api-modules 1.2.48__py3-none-any.whl → 1.2.49__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "1.2.48"
1
+ __version__ = "1.2.49"
@@ -0,0 +1,7 @@
1
+ class ValueLocError(ValueError):
2
+ """A special case of ValueError that allows us to carry along the 'loc' location where a pydantic
3
+ validation error occured."""
4
+
5
+ def __init__(self, *args, loc: str, **kwargs):
6
+ super().__init__(*args, **kwargs)
7
+ self.loc = (loc,) # Make a tuple to match pydantic's loc structure
cidc_api/models/models.py CHANGED
@@ -3492,6 +3492,7 @@ class IngestionJobs(CommonColumns):
3492
3492
  intake_path = Column(String, nullable=True)
3493
3493
  uploader_email = Column(String, nullable=True)
3494
3494
  is_template_downloaded = Column(Boolean, nullable=True)
3495
+ master_aa_version = Column(Integer, nullable=True)
3495
3496
 
3496
3497
  @staticmethod
3497
3498
  @with_default_session
@@ -1,9 +1,10 @@
1
1
  from typing import Self
2
2
 
3
- from pydantic import NonNegativeInt, model_validator
3
+ from pydantic import Field, NonNegativeInt, model_validator
4
4
 
5
5
  from cidc_api.models.pydantic.base import Base
6
6
  from cidc_api.reference.ctcae import is_ctcae_other_term
7
+ from cidc_api.models.errors import ValueLocError
7
8
  from cidc_api.models.types import (
8
9
  CTCAEEventTerm,
9
10
  CTCAEEventCode,
@@ -78,7 +79,10 @@ class AdverseEvent(Base):
78
79
  @model_validator(mode="after")
79
80
  def validate_term_and_code_cr(self) -> Self:
80
81
  if not self.event_term and not self.event_code:
81
- raise ValueError("Please provide event_term or event_code or both")
82
+ raise ValueLocError(
83
+ "Please provide event_term or event_code or both",
84
+ loc="event_term",
85
+ )
82
86
  return self
83
87
 
84
88
  @model_validator(mode="after")
@@ -88,13 +92,28 @@ class AdverseEvent(Base):
88
92
  and is_ctcae_other_term(self.event_term)
89
93
  and not self.event_other_specify
90
94
  ):
91
- raise ValueError(
92
- 'If severity_grade_system is "CTCAE" and the event_code or event_term are of type "Other, specify", please provide event_other_specify'
95
+ raise ValueLocError(
96
+ 'If severity_grade_system is "CTCAE" and the event_code or event_term are of type '
97
+ '"Other, specify", please provide event_other_specify',
98
+ loc="event_other_specify",
93
99
  )
94
100
  return self
95
101
 
96
102
  @model_validator(mode="after")
97
103
  def validate_system_organ_class_cr(self) -> Self:
98
104
  if self.event_other_specify and not self.system_organ_class:
99
- raise ValueError("If event_other_specify is provided, please provide system_organ_class.")
105
+ raise ValueLocError(
106
+ "If event_other_specify is provided, please provide system_organ_class.",
107
+ loc="system_organ_class",
108
+ )
109
+ return self
110
+
111
+ @model_validator(mode="after")
112
+ def validate_days_to_resolution_of_event_chronology(self) -> Self:
113
+ if self.days_to_resolution_of_event is not None and self.days_to_onset_of_event is not None:
114
+ if self.days_to_resolution_of_event < self.days_to_onset_of_event:
115
+ raise ValueLocError(
116
+ 'Violate "days_to_onset_of_event" <= "days_to_resolution_of_event"',
117
+ loc="days_to_resolution_of_event",
118
+ )
100
119
  return self
@@ -1,7 +1,8 @@
1
- from typing import Self
1
+ from typing import Self, Any
2
2
 
3
- from pydantic import model_validator
3
+ from pydantic import model_validator, field_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import ICD10CMCode, ICD10CMTerm
7
8
 
@@ -30,7 +31,8 @@ class Comorbidity(Base):
30
31
  @model_validator(mode="after")
31
32
  def validate_code_or_term_or_other_cr(self) -> Self:
32
33
  if not self.comorbidity_code and not self.comorbidity_term and not self.comorbidity_other:
33
- raise ValueError(
34
- 'Please provide at least one of "comorbidity_code", "comorbidity_term" or "comorbidity_other".'
34
+ raise ValueLocError(
35
+ 'Please provide at least one of "comorbidity_code", "comorbidity_term" or "comorbidity_other".',
36
+ loc="comorbidity_code",
35
37
  )
36
38
  return self
@@ -2,6 +2,7 @@ from typing import Self, Annotated, List
2
2
 
3
3
  from pydantic import PositiveInt, NonNegativeFloat, PositiveFloat, model_validator, field_validator, BeforeValidator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import (
7
8
  Sex,
@@ -94,13 +95,15 @@ class Demographic(Base):
94
95
  def validate_age_at_enrollment_cr(self) -> Self:
95
96
  if self.age_90_or_over:
96
97
  if self.age_at_enrollment or self.age_at_enrollment_units:
97
- raise ValueError(
98
- 'If "age_90_or_over" is "Yes" then "age_at_enrollment" and "age_at_enrollment_units" must be blank.'
98
+ raise ValueLocError(
99
+ 'If "age_90_or_over" is "Yes" then "age_at_enrollment" and "age_at_enrollment_units" must be blank.',
100
+ loc="age_at_enrollment",
99
101
  )
100
102
  else:
101
103
  if not self.age_at_enrollment or not self.age_at_enrollment_units:
102
- raise ValueError(
103
- 'If "age_90_or_over" is "No" then "age_at_enrollment" and "age_at_enrollment_units" are required.'
104
+ raise ValueLocError(
105
+ 'If "age_90_or_over" is "No" then "age_at_enrollment" and "age_at_enrollment_units" are required.',
106
+ loc="age_at_enrollment_units",
104
107
  )
105
108
  return self
106
109
 
@@ -111,13 +114,17 @@ class Demographic(Base):
111
114
  self.age_at_enrollment if self.age_at_enrollment_units == "Years" else self.age_at_enrollment / 365.25
112
115
  )
113
116
  if age_in_years >= 90:
114
- raise ValueError('"age_at_enrollment" cannot represent a value greater than 90 years of age.')
117
+ raise ValueLocError(
118
+ '"age_at_enrollment" cannot represent a value greater than 90 years of age.',
119
+ loc="age_at_enrollment",
120
+ )
115
121
  return self
116
122
 
117
123
  @model_validator(mode="after")
118
124
  def validate_body_surface_area_units_cr(self) -> Self:
119
125
  if self.body_surface_area and not self.body_surface_area_units:
120
- raise ValueError(
121
- 'If "body_surface_area" is provided then "body_surface_area_units_other" must also be provided.'
126
+ raise ValueLocError(
127
+ 'If "body_surface_area" is provided then "body_surface_area_units_other" must also be provided.',
128
+ loc="body_surface_area_units_other",
122
129
  )
123
130
  return self
@@ -1,6 +1,7 @@
1
1
  from pydantic import NonPositiveInt, model_validator, BeforeValidator
2
2
  from typing import List, Self, Annotated, get_args
3
3
 
4
+ from cidc_api.models.errors import ValueLocError
4
5
  from cidc_api.models.pydantic.base import Base
5
6
  from cidc_api.models.types import (
6
7
  TumorGrade,
@@ -95,8 +96,9 @@ class Disease(Base):
95
96
  @model_validator(mode="after")
96
97
  def validate_code_or_term_or_description_cr(self) -> Self:
97
98
  if not self.morphological_code and not self.morphological_term and not self.cancer_type_description:
98
- raise ValueError(
99
- 'Please provide at least one of "morphological_code", "morphological_term" or "cancer_type_description".'
99
+ raise ValueLocError(
100
+ 'Please provide at least one of "morphological_code", "morphological_term" or "cancer_type_description".',
101
+ loc="morphological_code",
100
102
  )
101
103
  return self
102
104
 
@@ -106,53 +108,67 @@ class Disease(Base):
106
108
  if self.cancer_stage_system == "AJCC" and self.cancer_stage_system_version not in get_args(
107
109
  CancerStageSystemVersionAJCC
108
110
  ):
109
- raise ValueError(msg)
111
+ raise ValueLocError(msg, loc="cancer_stage_system")
110
112
  elif self.cancer_stage_system == "RISS" and self.cancer_stage_system_version not in get_args(
111
113
  CancerStageSystemVersionRISS
112
114
  ):
113
- raise ValueError(msg)
115
+ raise ValueLocError(msg, loc="cancer_stage_system")
114
116
  elif self.cancer_stage_system == "FIGO" and self.cancer_stage_system_version not in get_args(
115
117
  CancerStageSystemVersionFIGO
116
118
  ):
117
- raise ValueError(msg)
119
+ raise ValueLocError(msg, loc="cancer_stage_system")
118
120
  return self
119
121
 
120
122
  @model_validator(mode="after")
121
123
  def validate_cancer_stage_system_version_cr(self) -> Self:
122
124
  if self.cancer_stage_system != "Not Applicable" and not self.cancer_stage_system_version:
123
- raise ValueError(
124
- f'Please provide cancer_stage_system_version when cancer_stage_system is "{self.cancer_stage_system}"'
125
+ raise ValueLocError(
126
+ f'Please provide cancer_stage_system_version when cancer_stage_system is "{self.cancer_stage_system}"',
127
+ loc="cancer_stage_system_version",
125
128
  )
126
129
  return self
127
130
 
128
131
  @model_validator(mode="after")
129
132
  def validate_cancer_stage_cr(self) -> Self:
130
133
  if self.cancer_stage_system != "Not Applicable" and not self.cancer_stage:
131
- raise ValueError(f'Please provide cancer_stage when cancer_stage_system is "{self.cancer_stage_system}"')
134
+ raise ValueLocError(
135
+ f'Please provide cancer_stage when cancer_stage_system is "{self.cancer_stage_system}"',
136
+ loc="cancer_stage",
137
+ )
132
138
  return self
133
139
 
134
140
  @model_validator(mode="after")
135
141
  def validate_t_category_cr(self) -> Self:
136
142
  if self.cancer_stage_system == "AJCC" and not self.t_category:
137
- raise ValueError(f'Please provide t_category when cancer_stage_system is "{self.cancer_stage_system}"')
143
+ raise ValueLocError(
144
+ f'Please provide t_category when cancer_stage_system is "{self.cancer_stage_system}"',
145
+ loc="t_category",
146
+ )
138
147
  return self
139
148
 
140
149
  @model_validator(mode="after")
141
150
  def validate_n_category_cr(self) -> Self:
142
151
  if self.cancer_stage_system == "AJCC" and not self.n_category:
143
- raise ValueError(f'Please provide n_category when cancer_stage_system is "{self.cancer_stage_system}"')
152
+ raise ValueLocError(
153
+ f'Please provide n_category when cancer_stage_system is "{self.cancer_stage_system}"',
154
+ loc="n_category",
155
+ )
144
156
  return self
145
157
 
146
158
  @model_validator(mode="after")
147
159
  def validate_m_category_cr(self) -> Self:
148
160
  if self.cancer_stage_system == "AJCC" and not self.m_category:
149
- raise ValueError(f'Please provide m_category when cancer_stage_system is "{self.cancer_stage_system}"')
161
+ raise ValueLocError(
162
+ f'Please provide m_category when cancer_stage_system is "{self.cancer_stage_system}"',
163
+ loc="m_category",
164
+ )
150
165
  return self
151
166
 
152
167
  @model_validator(mode="after")
153
168
  def validate_extramedullary_organ_cr(self) -> Self:
154
169
  if self.solely_extramedullary_disease in ["No", "Unknown"] and self.extramedullary_organ:
155
- raise ValueError(
156
- "If solely_extramedullary_disease indicates no disease, please leave extramedullary_organ blank."
170
+ raise ValueLocError(
171
+ "If solely_extramedullary_disease indicates no disease, please leave extramedullary_organ blank.",
172
+ loc="extramedullary_organ",
157
173
  )
158
174
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import YNU, ExposureType
7
8
 
@@ -28,5 +29,8 @@ class Exposure(Base):
28
29
  @model_validator(mode="after")
29
30
  def validate_exposure_type_cr(self) -> Self:
30
31
  if self.carcinogen_exposure in ["No", "Unknown"] and self.exposure_type:
31
- raise ValueError("If carcinogen_exposure indicates non exposure, please leave exposure_type blank.")
32
+ raise ValueLocError(
33
+ "If carcinogen_exposure indicates non exposure, please leave exposure_type blank.",
34
+ loc="exposure_type",
35
+ )
32
36
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, PositiveFloat, model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import TobaccoSmokingStatus
7
8
 
@@ -32,5 +33,8 @@ class MedicalHistory(Base):
32
33
  @model_validator(mode="after")
33
34
  def validate_pack_years_smoked_cr(self) -> Self:
34
35
  if self.tobacco_smoking_status in ["Never Smoker", "Unknown", "Not reported"] and self.pack_years_smoked:
35
- raise ValueError("If tobacco_smoking_status indicates non-smoker, please leave pack_years_smoked blank.")
36
+ raise ValueLocError(
37
+ "If tobacco_smoking_status indicates non-smoker, please leave pack_years_smoked blank.",
38
+ loc="pack_years_smoked",
39
+ )
36
40
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonPositiveInt, model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import UberonAnatomicalTerm, ICDO3MorphologicalCode, ICDO3MorphologicalTerm, MalignancyStatus
7
8
 
@@ -43,7 +44,8 @@ class OtherMalignancy(Base):
43
44
  and not self.other_malignancy_morphological_term
44
45
  and not self.other_malignancy_description
45
46
  ):
46
- raise ValueError(
47
- 'Please provide at least one of "morphological_code", "morphological_term" or "malignancy_description".'
47
+ raise ValueLocError(
48
+ 'Please provide at least one of "morphological_code", "morphological_term" or "malignancy_description".',
49
+ loc="morphological_code",
48
50
  )
49
51
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import YNU
7
8
  from cidc_api.models.types import OffStudyReason
@@ -41,11 +42,17 @@ class Participant(Base):
41
42
  @model_validator(mode="after")
42
43
  def off_study_reason_cr(self) -> Self:
43
44
  if self.off_study == "Yes" and not self.off_study_reason:
44
- raise ValueError('If "off_study" is "Yes" then "off_study_reason" is required.')
45
+ raise ValueLocError(
46
+ 'If "off_study" is "Yes" then "off_study_reason" is required.',
47
+ loc="off_study_reason",
48
+ )
45
49
  return self
46
50
 
47
51
  @model_validator(mode="after")
48
52
  def off_study_reason_other_cr(self) -> Self:
49
53
  if self.off_study_reason == "Other" and not self.off_study_reason_other:
50
- raise ValueError('If "off_study_reason" is "Other" then "off_study_reason_other" is required.')
54
+ raise ValueLocError(
55
+ 'If "off_study_reason" is "Other" then "off_study_reason_other" is required.',
56
+ loc="off_study_reason_other",
57
+ )
51
58
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, NonNegativeFloat, model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import (
7
8
  YNU,
@@ -69,11 +70,17 @@ class RadiotherapyDose(Base):
69
70
  @model_validator(mode="after")
70
71
  def validate_changes_delays_description_cr(self) -> Self:
71
72
  if self.dose_changes_delays == "Yes" and not self.changes_delays_description:
72
- raise ValueError('If dose_changes_delays is "Yes", please provide changes_delays_description.')
73
+ raise ValueLocError(
74
+ 'If dose_changes_delays is "Yes", please provide changes_delays_description.',
75
+ loc="changes_delays_description",
76
+ )
73
77
  return self
74
78
 
75
79
  @model_validator(mode="after")
76
80
  def validate_planned_dose_units_cr(self) -> Self:
77
81
  if self.planned_dose and not self.planned_dose_units:
78
- raise ValueError("If planned_dose is provided, please provide planned_dose_units.")
82
+ raise ValueLocError(
83
+ "If planned_dose is provided, please provide planned_dose_units.",
84
+ loc="planned_dose_units",
85
+ )
79
86
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import SurvivalStatus, YNUNA, CauseOfDeath
7
8
 
@@ -44,22 +45,31 @@ class Response(Base):
44
45
  evaluable_for_efficacy: bool
45
46
 
46
47
  # Days from enrollment date to the last time the patient's vital status was verified.
47
- days_to_last_vital_status: NonNegativeInt | None = None # TODO: Needs CR check
48
+ days_to_last_vital_status: NonNegativeInt | None = None
48
49
 
49
50
  @model_validator(mode="after")
50
51
  def validate_cause_of_death_cr(self) -> Self:
51
52
  if self.survival_status == "Dead" and not self.cause_of_death:
52
- raise ValueError('If survival_status is "Dead" then cause_of_death is required.')
53
+ raise ValueLocError(
54
+ 'If survival_status is "Dead" then cause_of_death is required.',
55
+ loc="cause_of_death",
56
+ )
53
57
  return self
54
58
 
55
59
  @model_validator(mode="after")
56
60
  def validate_cause_of_death_cr2(self) -> Self:
57
61
  if self.survival_status == "Alive" and self.cause_of_death:
58
- raise ValueError('If survival_status is "Alive", please leave cause_of_death blank.')
62
+ raise ValueLocError(
63
+ 'If survival_status is "Alive", please leave cause_of_death blank.',
64
+ loc="cause_of_death",
65
+ )
59
66
  return self
60
67
 
61
68
  @model_validator(mode="after")
62
69
  def validate_days_to_death_cr(self) -> Self:
63
70
  if self.survival_status in ["Alive", "Unknown"] and self.days_to_death:
64
- raise ValueError("If survival_status does not indicate death, please leave days_to_death blank.")
71
+ raise ValueLocError(
72
+ "If survival_status does not indicate death, please leave days_to_death blank.",
73
+ loc="days_to_death",
74
+ )
65
75
  return self
@@ -2,7 +2,9 @@ from typing import Self
2
2
 
3
3
  from pydantic import PositiveInt, model_validator, NonNegativeInt
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
7
+ from cidc_api.models.pydantic.stage1.response import Response
6
8
  from cidc_api.models.types import ResponseSystem, ResponseSystemVersion, BestOverallResponse, YNUNA
7
9
 
8
10
 
@@ -28,6 +30,9 @@ class ResponseBySystem(Base):
28
30
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=12220014%20and%20ver_nr=1
29
31
  participant_id: str | None = None
30
32
 
33
+ # The linked parent response for the participant. Used for cross-model validation.
34
+ response: Response | None = None
35
+
31
36
  # A standardized method used to evaluate and categorize the participant’s clinical response to treatment based on predefined criteria.
32
37
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=13381490%20and%20ver_nr=1
33
38
  response_system: ResponseSystem
@@ -69,44 +74,114 @@ class ResponseBySystem(Base):
69
74
  @model_validator(mode="after")
70
75
  def validate_response_duration_cr(self) -> Self:
71
76
  if self.best_overall_response in negative_response_values and self.response_duration:
72
- raise ValueError(
77
+ raise ValueLocError(
73
78
  "If best_overall_response does not indicate a positive response, "
74
- "please leave response_duration blank."
79
+ "please leave response_duration blank.",
80
+ loc="response_duration",
75
81
  )
76
82
  return self
77
83
 
78
84
  @model_validator(mode="after")
79
85
  def validate_days_to_first_response_cr(self) -> Self:
80
86
  if self.best_overall_response in negative_response_values and self.days_to_first_response:
81
- raise ValueError(
87
+ raise ValueLocError(
82
88
  "If best_overall_response does not indicate a positive response, "
83
- "please leave days_to_first_response blank."
89
+ "please leave days_to_first_response blank.",
90
+ loc="days_to_first_response",
84
91
  )
85
92
  return self
86
93
 
87
94
  @model_validator(mode="after")
88
95
  def validate_days_to_best_response_cr(self) -> Self:
89
96
  if self.best_overall_response in negative_response_values and self.days_to_best_response:
90
- raise ValueError(
97
+ raise ValueLocError(
91
98
  "If best_overall_response does not indicate a positive response, \
92
- please leave days_to_best_response blank."
99
+ please leave days_to_best_response blank.",
100
+ loc="days_to_best_response",
93
101
  )
94
102
  return self
95
103
 
96
104
  @model_validator(mode="after")
97
105
  def validate_days_to_disease_progression_cr(self) -> Self:
98
106
  if self.progression in ["No", "Unknown", "Not Applicable"] and self.days_to_disease_progression:
99
- raise ValueError(
107
+ raise ValueLocError(
100
108
  "If progression does not indicate confirmed progression of the disease, \
101
- please leave days_to_disease_progress blank."
109
+ please leave days_to_disease_progress blank.",
110
+ loc="days_to_disease_progress",
102
111
  )
103
112
  return self
104
113
 
105
114
  @model_validator(mode="after")
106
115
  def validate_progression_free_survival_cr(self) -> Self:
107
116
  if self.progression_free_survival_event in ["Unknown", "Not Applicable"] and self.progression_free_survival:
108
- raise ValueError(
117
+ raise ValueLocError(
109
118
  "If progression_free_survival_event is not known, \
110
- please leave progression_free_survival blank."
119
+ please leave progression_free_survival blank.",
120
+ loc="progression_free_survival",
111
121
  )
112
122
  return self
123
+
124
+ @model_validator(mode="after")
125
+ def validate_days_to_best_response_chronology(self) -> Self:
126
+ if self.days_to_best_response is not None and self.days_to_first_response is not None:
127
+ if self.days_to_best_response < self.days_to_first_response:
128
+ raise ValueLocError(
129
+ 'Violate "days_to_best_response" >= days_to_first_response"',
130
+ loc="days_to_best_response",
131
+ )
132
+ return self
133
+
134
+ @model_validator(mode="after")
135
+ def validate_days_to_disease_progression_chronology(self) -> Self:
136
+ if self.days_to_first_response is not None and self.days_to_disease_progression is not None:
137
+ if self.days_to_first_response >= self.days_to_disease_progression:
138
+ raise ValueLocError(
139
+ 'Violate "days_to_first_response" < "days_to_disease_progression"',
140
+ loc="days_to_first_response",
141
+ )
142
+ return self
143
+
144
+ @model_validator(mode="after")
145
+ def validate_days_to_best_response_progression_chronology(self) -> Self:
146
+ if self.days_to_best_response is not None and self.days_to_disease_progression is not None:
147
+ if self.days_to_best_response >= self.days_to_disease_progression:
148
+ raise ValueLocError(
149
+ 'Violate "days_to_best_response" < "days_to_disease_progression"',
150
+ loc="days_to_best_response",
151
+ )
152
+ return self
153
+
154
+ @model_validator(mode="after")
155
+ def validate_days_to_last_vital_status_chronology(self) -> Self:
156
+ if not self.response or not self.response.days_to_last_vital_status:
157
+ return self
158
+ max_value = max(
159
+ self.response.days_to_last_vital_status or 0,
160
+ self.days_to_first_response or 0,
161
+ self.days_to_best_response or 0,
162
+ self.days_to_disease_progression or 0,
163
+ )
164
+ if (self.response.days_to_last_vital_status or 0) != max_value:
165
+ raise ValueLocError(
166
+ '"days_to_last_vital_status" is not the max of all events. Rule: days_to_last_vital_status '
167
+ ">= max(days_to_first_response,days_to_best_response,days_to_disease_progression)",
168
+ loc="days_to_last_vital_status,days_to_first_response,days_to_best_response,days_to_disease_progression",
169
+ )
170
+ return self
171
+
172
+ @model_validator(mode="after")
173
+ def validate_days_to_death_chronology(self) -> Self:
174
+ if not self.response or not self.response.days_to_death:
175
+ return self
176
+ max_value = max(
177
+ self.response.days_to_death or 0,
178
+ self.days_to_first_response or 0,
179
+ self.days_to_best_response or 0,
180
+ self.days_to_disease_progression or 0,
181
+ )
182
+ if (self.response.days_to_death or 0) != max_value:
183
+ raise ValueLocError(
184
+ '"days_to_death" is not the max of all events. Rule: days_to_death'
185
+ ">= max(days_to_first_response,days_to_best_response,days_to_disease_progression)",
186
+ loc="days_to_death,days_to_first_response,days_to_best_response,days_to_disease_progression",
187
+ )
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import SurgicalProcedure, UberonAnatomicalTerm, YNU
7
8
 
@@ -45,5 +46,8 @@ class Surgery(Base):
45
46
  @model_validator(mode="after")
46
47
  def validate_procedure_other_cr(self) -> Self:
47
48
  if self.procedure == "Other, specify" and not self.procedure_other:
48
- raise ValueError('If procedure is "Other, specify", please provide procedure_other.')
49
+ raise ValueLocError(
50
+ 'If procedure is "Other, specify", please provide procedure_other.',
51
+ loc="procedure_other",
52
+ )
49
53
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, NonNegativeFloat, PositiveFloat, model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import YNU, TherapyAgentDoseUnits
7
8
 
@@ -57,11 +58,17 @@ class TherapyAgentDose(Base):
57
58
  @model_validator(mode="after")
58
59
  def validate_changes_delays_description_cr(self) -> Self:
59
60
  if self.dose_changes_delays == "Yes" and not self.changes_delays_description:
60
- raise ValueError('If dose_changes_delays is "Yes", please provide changes_delays_description.')
61
+ raise ValueLocError(
62
+ 'If dose_changes_delays is "Yes", please provide changes_delays_description.',
63
+ loc="changes_delays_description",
64
+ )
61
65
  return self
62
66
 
63
67
  @model_validator(mode="after")
64
68
  def validate_planned_dose_units_cr(self) -> Self:
65
69
  if self.planned_dose and not self.planned_dose_units:
66
- raise ValueError("If planned_dose is provided, please provide planned_dose_units.")
70
+ raise ValueLocError(
71
+ "If planned_dose is provided, please provide planned_dose_units.",
72
+ loc="planned_dose_units",
73
+ )
67
74
  return self
@@ -2,6 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import model_validator
4
4
 
5
+ from cidc_api.models.errors import ValueLocError
5
6
  from cidc_api.models.pydantic.base import Base
6
7
  from cidc_api.models.types import YNU, OffTreatmentReason
7
8
 
@@ -40,11 +41,17 @@ class Treatment(Base):
40
41
  @model_validator(mode="after")
41
42
  def validate_off_treatment_reason_cr(self) -> Self:
42
43
  if self.off_treatment == "Yes" and not self.off_treatment_reason:
43
- raise ValueError('If off_treatment is "Yes", please provide off_treatment_reason.')
44
+ raise ValueLocError(
45
+ 'If off_treatment is "Yes", please provide off_treatment_reason.',
46
+ loc="off_treatment_reason",
47
+ )
44
48
  return self
45
49
 
46
50
  @model_validator(mode="after")
47
51
  def validate_off_treatment_reason_other_cr(self) -> Self:
48
52
  if self.off_treatment_reason == "Other" and not self.off_treatment_reason_other:
49
- raise ValueError('If off_treatment_reason is "Other", please provide off_treatment_reason_other.')
53
+ raise ValueLocError(
54
+ 'If off_treatment_reason is "Other", please provide off_treatment_reason_other.',
55
+ loc="off_treatment_reason_other",
56
+ )
50
57
  return self
@@ -14,6 +14,8 @@ from ..telemetry import trace_
14
14
 
15
15
  logger = get_logger(__name__)
16
16
 
17
+ MASTER_APPENDIX_A_VERSION_PREFIX = "Master Appendix A Version:"
18
+
17
19
 
18
20
  @trace_()
19
21
  def set_current_file(
@@ -479,7 +479,10 @@ async def async_gcs_files_to_pandas_dataframes(bucket_name: str, blob_names: Lis
479
479
 
480
480
  for blob_name, contents in zip(blob_names, all_contents):
481
481
  extension = blob_name.split(".")[-1]
482
- dataframes.append(prepare_dataframe(extension, contents))
482
+ try:
483
+ dataframes.append(prepare_dataframe(extension, contents))
484
+ except pd.errors.EmptyDataError:
485
+ logger.warning(f"The dataframe retrieved from {blob_name} was empty!")
483
486
  return dataframes
484
487
 
485
488
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nci_cidc_api_modules
3
- Version: 1.2.48
3
+ Version: 1.2.49
4
4
  Summary: SQLAlchemy data models and configuration tools used in the NCI CIDC API
5
5
  Home-page: https://github.com/NCI-CIDC/cidc-api-gae
6
6
  License: MIT license
@@ -1,5 +1,5 @@
1
1
  boot.py,sha256=oS_16OgU34iDxyFNpJl5rb5KbfbsCXroUCNLwVPxPNg,416
2
- cidc_api/__init__.py,sha256=Zm4hDkoG9GX2KfPn6Wt1kdPl888Wv-de5OuQhdWNP9E,23
2
+ cidc_api/__init__.py,sha256=qkDVgmmc_W7WSA8UFQzKzULlTX27bnHrBTAlSeTYqYs,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
@@ -8,8 +8,9 @@ cidc_api/config/secrets.py,sha256=jRFj7W43pWuPf9DZQLCKF7WPXf5cUv-BAaS3ASqhV_Q,14
8
8
  cidc_api/config/settings.py,sha256=loern8cejUrOJJAog4z0iW9EQaa0nlN0rFrFOdZ-cLE,4483
9
9
  cidc_api/models/__init__.py,sha256=bl445G8Zic9YbhZ8ZBni07wtBMhLJRMBA-JqjLxx2bw,66
10
10
  cidc_api/models/data.py,sha256=TyVXk5jJO1NN05CLqejLf5BPp6yLSNNxPmFOgXUnA1M,1313
11
+ cidc_api/models/errors.py,sha256=UncRrh-4v0CR5mpLO0erWD1y9ctus3Nk6XOSvrTjfco,338
11
12
  cidc_api/models/migrations.py,sha256=UlS5How3J4ryaRuZT6F5VQtAKikkl0LTv9MgMO_ltiQ,11161
12
- cidc_api/models/models.py,sha256=DTt0eFkFVVxVn23Aqr6TA8CQ7qb2hdqQ0xVJ3RakBBw,153684
13
+ cidc_api/models/models.py,sha256=3O0Oc_MgovrMS1qz-r3zOnPd5_41p7sXc30Qj03QJ6s,153739
13
14
  cidc_api/models/schemas.py,sha256=6IE2dJoEMcMbi0Vr1V3cYKnPKU0hv9vRKBixOZHe88s,2766
14
15
  cidc_api/models/types.py,sha256=WVOTEH61FH5aGVwRbEcfNOce8NLjiEnl1ixtPWav2OA,30058
15
16
  cidc_api/models/db/stage1/__init__.py,sha256=zsxOSoogzL_xZ8B5OwcwLXvAmm6g2GYx1Zf6LLw8dq8,1917
@@ -83,29 +84,29 @@ cidc_api/models/files/facets.py,sha256=vieZ3z_tYB29NqtvNzvKqajVwxM5ZLLZKeyQTq5lh
83
84
  cidc_api/models/pydantic/base.py,sha256=6Lsf4fekIS1E-DwZmgCXlfm3Qq9_23dA_v3iz1w2JoA,1427
84
85
  cidc_api/models/pydantic/stage1/__init__.py,sha256=H-NDjCd8isHTgZ7sqz-MIbzC-PpW1kEtR-x-Dyc8r6Q,1667
85
86
  cidc_api/models/pydantic/stage1/additional_treatment.py,sha256=TFVzjd1NPLSfH9UnL2W3aZhpZSzDsg97j9IK8t882YA,1050
86
- cidc_api/models/pydantic/stage1/adverse_event.py,sha256=zPy02xoRQeyaXBSmqDdQt4BaRTD6OHBjycVHVtBaAow,3880
87
+ cidc_api/models/pydantic/stage1/adverse_event.py,sha256=YzH8YbgxvjrrcJENtbctYCziR_io4fKPtSiHxO0GV9A,4652
87
88
  cidc_api/models/pydantic/stage1/baseline_clinical_assessment.py,sha256=5FYvtOVf_nIK1h3xTs3vQgJe2ivQSVHt_wfrTSDgVxU,1119
88
- cidc_api/models/pydantic/stage1/comorbidity.py,sha256=dk4SJnkaaju-boexf2SW_rNNGXl-Os2LR5lnIxCb-dA,1413
89
+ cidc_api/models/pydantic/stage1/comorbidity.py,sha256=K9MyK4XsSGr-3TjrDH_GJN1rG3uWqkyECbwGGuqkbzc,1528
89
90
  cidc_api/models/pydantic/stage1/consent_group.py,sha256=aP_nd0GKK6K7VH-6IZRSDDX2RxFM_j9B36FIRGwysOU,1216
90
- cidc_api/models/pydantic/stage1/demographic.py,sha256=X0YoFdK7S8JQ9p_Jm3l-R5HgEl9sZnZVFXrECA2i_zw,6007
91
- cidc_api/models/pydantic/stage1/disease.py,sha256=fwK1FSxTm33BzViGTNsb6AahoDc2BTTXMitCGY3KyDk,7650
92
- cidc_api/models/pydantic/stage1/exposure.py,sha256=ERoVQLuNWynw2zugh5bVdUT0NzNbJ6fIUmKhPSCQfqk,1306
91
+ cidc_api/models/pydantic/stage1/demographic.py,sha256=V5Fymdl2ge6eGcnCnP7ekCJuzfR7XeCgwclBKCMeolE,6304
92
+ cidc_api/models/pydantic/stage1/disease.py,sha256=e-BT6Z_QMyDJhtzJcYbMdqABkuVsX4OWw5HGnNuLzZM,8212
93
+ cidc_api/models/pydantic/stage1/exposure.py,sha256=XtnLxXSBrvuvOgrdg2A-x_FclQvT8zJkGeXIB50ZJs0,1426
93
94
  cidc_api/models/pydantic/stage1/gvhd_diagnosis_acute.py,sha256=jVMoWuLfspOZtOpC7rg1EzdvEAg8qEabcTaS6RRobZ8,1395
94
95
  cidc_api/models/pydantic/stage1/gvhd_diagnosis_chronic.py,sha256=SRwkAL07YyIZM4bYCqm01aKqSvj3yRmpsqFKxAZEZoM,1382
95
96
  cidc_api/models/pydantic/stage1/gvhd_organ_acute.py,sha256=VxlNGRJYjm9tW2lyLll8pD0t9JZs18gicnH-Ci_nR2I,771
96
97
  cidc_api/models/pydantic/stage1/gvhd_organ_chronic.py,sha256=Fnp1Qm-ZDDtDLrgOAnEOUnQVNWI9WrTSomiFPStGmjc,810
97
- cidc_api/models/pydantic/stage1/medical_history.py,sha256=IXtEvXrrLhTt6JEsj2MpDiZtLAkmrrsUS4aoe7sxans,1697
98
- cidc_api/models/pydantic/stage1/other_malignancy.py,sha256=V-Jru6uukipocKs7lDbmkGyJsHBxlM3A3NJoTShjJ9I,2188
99
- cidc_api/models/pydantic/stage1/participant.py,sha256=iDQrwYYtJY_XrkdbhsRNZuL44ighvW8JHCGxH-qo8jk,2199
98
+ cidc_api/models/pydantic/stage1/medical_history.py,sha256=5QU01b5-lKIWiz7GoIwZmZ_meFJ70ku7tvd3To1OpIA,1821
99
+ cidc_api/models/pydantic/stage1/other_malignancy.py,sha256=wIiYa3EffLYA4hu8JJORCZwG2GRbRdl2y47VJN4OffI,2283
100
+ cidc_api/models/pydantic/stage1/participant.py,sha256=QQVi0jhqSot1TiopongU_VnJHvk2RLKUpKtR15a_A1M,2402
100
101
  cidc_api/models/pydantic/stage1/prior_treatment.py,sha256=axhyPHwYCoIvcIonjyPJ95HUyaAXqIuOd6hhoDM3LJU,2101
101
- cidc_api/models/pydantic/stage1/radiotherapy_dose.py,sha256=c5q0TxPOrYHX9KTd9he5B01zirKgcCxMnvW_7adzCrI,3327
102
- cidc_api/models/pydantic/stage1/response.py,sha256=rPqfYZhaZRcA1E7dkGOaHe9f2_YIcIg9-KtIPSTh1s8,2886
103
- cidc_api/models/pydantic/stage1/response_by_system.py,sha256=f8ZnLfNXGJLCfFL1X2jxlSUJPGvJKnOCf6PYbJVMXW8,4909
102
+ cidc_api/models/pydantic/stage1/radiotherapy_dose.py,sha256=eGCkqfK2jIu3Lh0G51hXs4oqP9CRAxJrSbApdNjANb8,3536
103
+ cidc_api/models/pydantic/stage1/response.py,sha256=3Cuhu0DBew96e-k0sSN4vvPJ1xSoejXJdjpXw9HPtjo,3126
104
+ cidc_api/models/pydantic/stage1/response_by_system.py,sha256=3Om1R2vJd6C7UDQAIh-suDnwrZGquFf9SpirPJDKFUs,8590
104
105
  cidc_api/models/pydantic/stage1/specimen.py,sha256=scDekZ-RtXOQTTLburhqT3RF4KM34iY9NAV1wYi_HSg,1281
105
106
  cidc_api/models/pydantic/stage1/stem_cell_transplant.py,sha256=XsDVUksqbIprPxHwLlwHGyji4jsIFNMcIk-S0H4rDnQ,1228
106
- cidc_api/models/pydantic/stage1/surgery.py,sha256=53NrET_kNOlXXPP-mH5ecIPrNqDpDQt5s9WfsjPDM-s,2149
107
- cidc_api/models/pydantic/stage1/therapy_agent_dose.py,sha256=Y2L0vAqkfA5eI2qg7iQrqzCORwbkjyVEsthc6f5HKas,3021
108
- cidc_api/models/pydantic/stage1/treatment.py,sha256=1TeBaDJ352IfB10AtJH2iREdex3SZgDRXNUkhT0FZMo,2033
107
+ cidc_api/models/pydantic/stage1/surgery.py,sha256=IPBhgyPHuPrPpy0Qs9j2rHO7ig7J8ixBVNp_1l5TvzU,2271
108
+ cidc_api/models/pydantic/stage1/therapy_agent_dose.py,sha256=ZIsImguwBuAFGogla1rBN6N_FNUzhoC3OkconsJ-33k,3230
109
+ cidc_api/models/pydantic/stage1/treatment.py,sha256=C2xQ_fU0ZxGgBzes7hyHOHvicAzSjyva3Z5F78tor30,2244
109
110
  cidc_api/models/pydantic/stage1/trial.py,sha256=EFsMVMxxHbPeaBb6JG9JpX1qjtoho2t-GZnaI0gsYrY,1859
110
111
  cidc_api/models/pydantic/stage2/__init__.py,sha256=OQRG5wFkNsyAU_0KI1W5PDn8pBDe1EJDcgKZ19shh_g,2331
111
112
  cidc_api/models/pydantic/stage2/additional_treatment.py,sha256=TFVzjd1NPLSfH9UnL2W3aZhpZSzDsg97j9IK8t882YA,1050
@@ -154,13 +155,13 @@ cidc_api/shared/assay_handling.py,sha256=zzWSqQ-ddLVzX5IuHvsaSib2H1lnjXpo9Lbxaoq
154
155
  cidc_api/shared/auth.py,sha256=EIP9AKokLNrI79Fkpv3P7WdzaddJIsuGGICc1W494X0,9110
155
156
  cidc_api/shared/email_layout.html,sha256=pBoTNw3ACHH-ncZFaNvcy5bXMqPwizR78usb0uCYtIc,7670
156
157
  cidc_api/shared/emails.py,sha256=8kNFEaSnKpY-GX_iE59QUhSp3c4_uzy3SpHYt2QjuqI,6121
157
- cidc_api/shared/file_handling.py,sha256=ObsLvBrkwHo6Nv48GvFXDKElNNl4inGpxv--001H_xc,5843
158
- cidc_api/shared/gcloud_client.py,sha256=0B13MRF_CUMbzaKtKVDd9fxErNdNGBpxfkknQ5deu7w,39972
158
+ cidc_api/shared/file_handling.py,sha256=9MZXx5RfXO3A_pXf8Ulb7DQEuyp9j12eO9ad-PcTBXo,5908
159
+ cidc_api/shared/gcloud_client.py,sha256=7q6CWpAZdqSJoAkMpm9Rw7yFwNLIk6D9WZGJeTOwo54,40113
159
160
  cidc_api/shared/jose.py,sha256=-qzGzEDAlokEp9E7WtBtQkXyyfPWTYXlwYpCqVJWmqM,1830
160
161
  cidc_api/shared/rest_utils.py,sha256=RwR30WOUAYCxL7V-i2totEyeriG30GbBDvBcpLXhM9w,6594
161
162
  cidc_api/shared/utils.py,sha256=-gLnzxCR9E6h0plt2xrNisUG5_Y6GhhVwz3DgDIzpvs,367
162
- nci_cidc_api_modules-1.2.48.dist-info/licenses/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
163
- nci_cidc_api_modules-1.2.48.dist-info/METADATA,sha256=nsHP8cxYPyO_ut25JYyLy3TDef7eeuorTsJFFSqMc-w,40301
164
- nci_cidc_api_modules-1.2.48.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
165
- nci_cidc_api_modules-1.2.48.dist-info/top_level.txt,sha256=BSJqF6ura8-bWCKZvarvQEKvidMM05lH0bLQsNOrI0o,14
166
- nci_cidc_api_modules-1.2.48.dist-info/RECORD,,
163
+ nci_cidc_api_modules-1.2.49.dist-info/licenses/LICENSE,sha256=pNYWVTHaYonnmJyplmeAp7tQAjosmDpAWjb34jjv7Xs,1102
164
+ nci_cidc_api_modules-1.2.49.dist-info/METADATA,sha256=vy1hCgQ0v8NmAfY8w71VOrXET5k9u95FmpAUJb-vxHQ,40301
165
+ nci_cidc_api_modules-1.2.49.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
166
+ nci_cidc_api_modules-1.2.49.dist-info/top_level.txt,sha256=BSJqF6ura8-bWCKZvarvQEKvidMM05lH0bLQsNOrI0o,14
167
+ nci_cidc_api_modules-1.2.49.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5