nci-cidc-api-modules 1.2.29__py3-none-any.whl → 1.2.45__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.
Files changed (148) hide show
  1. cidc_api/__init__.py +1 -0
  2. cidc_api/config/db.py +21 -1
  3. cidc_api/config/settings.py +1 -0
  4. cidc_api/models/__init__.py +0 -2
  5. cidc_api/models/data.py +17 -4
  6. cidc_api/models/db/stage1/__init__.py +56 -0
  7. cidc_api/models/db/stage1/additional_treatment_orm.py +22 -0
  8. cidc_api/models/db/stage1/adverse_event_orm.py +46 -0
  9. cidc_api/models/db/stage1/base_orm.py +7 -0
  10. cidc_api/models/db/stage1/baseline_clinical_assessment_orm.py +22 -0
  11. cidc_api/models/db/stage1/comorbidity_orm.py +23 -0
  12. cidc_api/models/db/stage1/consent_group_orm.py +32 -0
  13. cidc_api/models/db/stage1/demographic_orm.py +47 -0
  14. cidc_api/models/db/stage1/disease_orm.py +52 -0
  15. cidc_api/models/db/stage1/exposure_orm.py +22 -0
  16. cidc_api/models/db/stage1/gvhd_diagnosis_acute_orm.py +34 -0
  17. cidc_api/models/db/stage1/gvhd_diagnosis_chronic_orm.py +36 -0
  18. cidc_api/models/db/stage1/gvhd_organ_acute_orm.py +21 -0
  19. cidc_api/models/db/stage1/gvhd_organ_chronic_orm.py +21 -0
  20. cidc_api/models/db/stage1/medical_history_orm.py +30 -0
  21. cidc_api/models/db/stage1/other_malignancy_orm.py +29 -0
  22. cidc_api/models/db/stage1/participant_orm.py +77 -0
  23. cidc_api/models/db/stage1/prior_treatment_orm.py +29 -0
  24. cidc_api/models/db/stage1/radiotherapy_dose_orm.py +39 -0
  25. cidc_api/models/db/stage1/response_by_system_orm.py +30 -0
  26. cidc_api/models/db/stage1/response_orm.py +28 -0
  27. cidc_api/models/db/stage1/specimen_orm.py +46 -0
  28. cidc_api/models/db/stage1/stem_cell_transplant_orm.py +25 -0
  29. cidc_api/models/db/stage1/surgery_orm.py +27 -0
  30. cidc_api/models/db/stage1/therapy_agent_dose_orm.py +31 -0
  31. cidc_api/models/db/stage1/treatment_orm.py +38 -0
  32. cidc_api/models/db/stage1/trial_orm.py +35 -0
  33. cidc_api/models/db/stage2/additional_treatment_orm.py +8 -8
  34. cidc_api/models/db/stage2/administrative_person_orm.py +4 -4
  35. cidc_api/models/db/stage2/administrative_role_assignment_orm.py +4 -4
  36. cidc_api/models/db/stage2/adverse_event_orm.py +12 -13
  37. cidc_api/models/db/stage2/arm_orm.py +3 -3
  38. cidc_api/models/db/stage2/base_orm.py +7 -0
  39. cidc_api/models/db/stage2/baseline_clinical_assessment_orm.py +6 -7
  40. cidc_api/models/db/stage2/cohort_orm.py +3 -3
  41. cidc_api/models/db/stage2/comorbidity_orm.py +7 -8
  42. cidc_api/models/db/stage2/consent_group_orm.py +5 -4
  43. cidc_api/models/db/stage2/contact_orm.py +16 -20
  44. cidc_api/models/db/stage2/demographic_orm.py +11 -8
  45. cidc_api/models/db/stage2/disease_orm.py +13 -14
  46. cidc_api/models/db/stage2/exposure_orm.py +5 -4
  47. cidc_api/models/db/stage2/file_orm.py +6 -9
  48. cidc_api/models/db/stage2/gvhd_diagnosis_acute_orm.py +5 -4
  49. cidc_api/models/db/stage2/gvhd_diagnosis_chronic_orm.py +5 -6
  50. cidc_api/models/db/stage2/gvhd_organ_acute_orm.py +4 -3
  51. cidc_api/models/db/stage2/gvhd_organ_chronic_orm.py +4 -3
  52. cidc_api/models/db/stage2/institution_orm.py +7 -7
  53. cidc_api/models/db/stage2/medical_history_orm.py +10 -9
  54. cidc_api/models/db/stage2/other_clinical_endpoint_orm.py +8 -12
  55. cidc_api/models/db/stage2/other_malignancy_orm.py +10 -11
  56. cidc_api/models/db/stage2/participant_orm.py +28 -28
  57. cidc_api/models/db/stage2/prior_treatment_orm.py +15 -14
  58. cidc_api/models/db/stage2/publication_orm.py +9 -11
  59. cidc_api/models/db/stage2/radiotherapy_dose_orm.py +9 -9
  60. cidc_api/models/db/stage2/response_by_system_orm.py +5 -3
  61. cidc_api/models/db/stage2/response_orm.py +6 -5
  62. cidc_api/models/db/stage2/shipment_orm.py +17 -17
  63. cidc_api/models/db/stage2/shipment_specimen_orm.py +4 -4
  64. cidc_api/models/db/stage2/specimen_orm.py +7 -6
  65. cidc_api/models/db/stage2/stem_cell_transplant_orm.py +7 -7
  66. cidc_api/models/db/stage2/surgery_orm.py +7 -7
  67. cidc_api/models/db/stage2/therapy_agent_dose_orm.py +8 -8
  68. cidc_api/models/db/stage2/treatment_orm.py +16 -15
  69. cidc_api/models/db/stage2/trial_orm.py +34 -33
  70. cidc_api/models/files/facets.py +4 -0
  71. cidc_api/models/models.py +154 -9
  72. cidc_api/models/pydantic/{stage2/base.py → base.py} +19 -1
  73. cidc_api/models/pydantic/stage1/__init__.py +56 -0
  74. cidc_api/models/pydantic/stage1/additional_treatment.py +23 -0
  75. cidc_api/models/pydantic/stage1/adverse_event.py +100 -0
  76. cidc_api/models/pydantic/stage1/baseline_clinical_assessment.py +23 -0
  77. cidc_api/models/pydantic/stage1/comorbidity.py +36 -0
  78. cidc_api/models/pydantic/stage1/consent_group.py +30 -0
  79. cidc_api/models/pydantic/stage1/demographic.py +123 -0
  80. cidc_api/models/pydantic/stage1/disease.py +158 -0
  81. cidc_api/models/pydantic/stage1/exposure.py +32 -0
  82. cidc_api/models/pydantic/stage1/gvhd_diagnosis_acute.py +33 -0
  83. cidc_api/models/pydantic/stage1/gvhd_diagnosis_chronic.py +32 -0
  84. cidc_api/models/pydantic/stage1/gvhd_organ_acute.py +22 -0
  85. cidc_api/models/pydantic/stage1/gvhd_organ_chronic.py +23 -0
  86. cidc_api/models/pydantic/stage1/medical_history.py +36 -0
  87. cidc_api/models/pydantic/stage1/other_malignancy.py +49 -0
  88. cidc_api/models/pydantic/stage1/participant.py +51 -0
  89. cidc_api/models/pydantic/stage1/prior_treatment.py +45 -0
  90. cidc_api/models/pydantic/stage1/radiotherapy_dose.py +79 -0
  91. cidc_api/models/pydantic/stage1/response.py +65 -0
  92. cidc_api/models/pydantic/stage1/response_by_system.py +112 -0
  93. cidc_api/models/pydantic/stage1/specimen.py +31 -0
  94. cidc_api/models/pydantic/stage1/stem_cell_transplant.py +35 -0
  95. cidc_api/models/pydantic/stage1/surgery.py +49 -0
  96. cidc_api/models/pydantic/stage1/therapy_agent_dose.py +67 -0
  97. cidc_api/models/pydantic/stage1/treatment.py +50 -0
  98. cidc_api/models/pydantic/stage1/trial.py +45 -0
  99. cidc_api/models/pydantic/stage2/additional_treatment.py +5 -5
  100. cidc_api/models/pydantic/stage2/administrative_person.py +1 -1
  101. cidc_api/models/pydantic/stage2/administrative_role_assignment.py +2 -2
  102. cidc_api/models/pydantic/stage2/adverse_event.py +2 -2
  103. cidc_api/models/pydantic/stage2/arm.py +2 -2
  104. cidc_api/models/pydantic/stage2/baseline_clinical_assessment.py +2 -2
  105. cidc_api/models/pydantic/stage2/cohort.py +1 -1
  106. cidc_api/models/pydantic/stage2/comorbidity.py +1 -1
  107. cidc_api/models/pydantic/stage2/consent_group.py +2 -2
  108. cidc_api/models/pydantic/stage2/contact.py +1 -1
  109. cidc_api/models/pydantic/stage2/demographic.py +27 -18
  110. cidc_api/models/pydantic/stage2/disease.py +33 -19
  111. cidc_api/models/pydantic/stage2/exposure.py +3 -3
  112. cidc_api/models/pydantic/stage2/file.py +2 -2
  113. cidc_api/models/pydantic/stage2/gvhd_diagnosis_acute.py +2 -2
  114. cidc_api/models/pydantic/stage2/gvhd_diagnosis_chronic.py +2 -2
  115. cidc_api/models/pydantic/stage2/gvhd_organ_acute.py +1 -1
  116. cidc_api/models/pydantic/stage2/gvhd_organ_chronic.py +1 -1
  117. cidc_api/models/pydantic/stage2/institution.py +1 -1
  118. cidc_api/models/pydantic/stage2/medical_history.py +2 -2
  119. cidc_api/models/pydantic/stage2/other_clinical_endpoint.py +1 -1
  120. cidc_api/models/pydantic/stage2/other_malignancy.py +12 -8
  121. cidc_api/models/pydantic/stage2/participant.py +10 -6
  122. cidc_api/models/pydantic/stage2/prior_treatment.py +14 -23
  123. cidc_api/models/pydantic/stage2/publication.py +2 -2
  124. cidc_api/models/pydantic/stage2/radiotherapy_dose.py +2 -2
  125. cidc_api/models/pydantic/stage2/response.py +5 -11
  126. cidc_api/models/pydantic/stage2/response_by_system.py +10 -7
  127. cidc_api/models/pydantic/stage2/shipment.py +2 -2
  128. cidc_api/models/pydantic/stage2/shipment_specimen.py +1 -1
  129. cidc_api/models/pydantic/stage2/specimen.py +8 -5
  130. cidc_api/models/pydantic/stage2/stem_cell_transplant.py +2 -2
  131. cidc_api/models/pydantic/stage2/surgery.py +1 -1
  132. cidc_api/models/pydantic/stage2/therapy_agent_dose.py +1 -1
  133. cidc_api/models/pydantic/stage2/treatment.py +2 -2
  134. cidc_api/models/pydantic/stage2/trial.py +19 -15
  135. cidc_api/models/types.py +45 -42
  136. cidc_api/shared/assay_handling.py +68 -0
  137. cidc_api/shared/auth.py +5 -5
  138. cidc_api/shared/file_handling.py +16 -4
  139. cidc_api/shared/gcloud_client.py +78 -16
  140. cidc_api/shared/utils.py +18 -9
  141. cidc_api/telemetry.py +101 -0
  142. {nci_cidc_api_modules-1.2.29.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/METADATA +25 -14
  143. nci_cidc_api_modules-1.2.45.dist-info/RECORD +165 -0
  144. cidc_api/models/db/base_orm.py +0 -25
  145. nci_cidc_api_modules-1.2.29.dist-info/RECORD +0 -109
  146. {nci_cidc_api_modules-1.2.29.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/WHEEL +0 -0
  147. {nci_cidc_api_modules-1.2.29.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/licenses/LICENSE +0 -0
  148. {nci_cidc_api_modules-1.2.29.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
1
  from typing import Self
2
2
 
3
- from pydantic import PositiveInt, model_validator
3
+ from pydantic import PositiveInt, model_validator, NonNegativeInt
4
4
 
5
- from .base import Base
5
+ from cidc_api.models.pydantic.base import Base
6
6
  from cidc_api.models.types import ResponseSystem, ResponseSystemVersion, BestOverallResponse, YNUNA
7
7
 
8
8
 
@@ -26,7 +26,7 @@ class ResponseBySystem(Base):
26
26
 
27
27
  # The unique internal identifier for the associated participant
28
28
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=12220014%20and%20ver_nr=1
29
- participant_id: int | None = None
29
+ participant_id: str | None = None
30
30
 
31
31
  # A standardized method used to evaluate and categorize the participant’s clinical response to treatment based on predefined criteria.
32
32
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=13381490%20and%20ver_nr=1
@@ -41,6 +41,9 @@ class ResponseBySystem(Base):
41
41
  # Days from first response to progression.
42
42
  response_duration: PositiveInt | None = None
43
43
 
44
+ # The number of days from the start of the treatment to the first signs of disease progression.
45
+ duration_of_stable_disease: NonNegativeInt | None = None
46
+
44
47
  # Indicates whether a patient achieved a durable clinical benefit.
45
48
  durable_clinical_benefit: bool | None = None
46
49
 
@@ -67,8 +70,8 @@ class ResponseBySystem(Base):
67
70
  def validate_response_duration_cr(self) -> Self:
68
71
  if self.best_overall_response in negative_response_values and self.response_duration:
69
72
  raise ValueError(
70
- "If best_overall_response does not indicate a positive response, \
71
- please leave response_duration blank."
73
+ "If best_overall_response does not indicate a positive response, "
74
+ "please leave response_duration blank."
72
75
  )
73
76
  return self
74
77
 
@@ -76,8 +79,8 @@ class ResponseBySystem(Base):
76
79
  def validate_days_to_first_response_cr(self) -> Self:
77
80
  if self.best_overall_response in negative_response_values and self.days_to_first_response:
78
81
  raise ValueError(
79
- "If best_overall_response does not indicate a positive response, \
80
- please leave days_to_first_response blank."
82
+ "If best_overall_response does not indicate a positive response, "
83
+ "please leave days_to_first_response blank."
81
84
  )
82
85
  return self
83
86
 
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
 
3
- from .base import Base
3
+ from cidc_api.models.pydantic.base import Base
4
4
  from cidc_api.models.types import AssayPriority, AssayType, Courier, ShipmentCondition, ShipmentQuality
5
5
 
6
6
 
@@ -12,7 +12,7 @@ class Shipment(Base):
12
12
  institution_id: int | None = None
13
13
 
14
14
  # The unique internal identifier for the associated trial.
15
- trial_id: int | None = None
15
+ trial_id: str | None = None
16
16
 
17
17
  # The version number of the trial dataset
18
18
  version: str | None = None
@@ -1,4 +1,4 @@
1
- from .base import Base
1
+ from cidc_api.models.pydantic.base import Base
2
2
 
3
3
 
4
4
  class ShipmentSpecimen(Base):
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
 
3
- from .base import Base
3
+ from cidc_api.models.pydantic.base import Base
4
4
  from cidc_api.models.types import (
5
5
  UberonAnatomicalTerm,
6
6
  ICDO3MorphologicalCode,
@@ -26,7 +26,7 @@ from cidc_api.models.types import (
26
26
 
27
27
 
28
28
  class Specimen(Base):
29
- __data_category__ = "specimen"
29
+ # __data_category__ = "specimen"
30
30
  __cardinality__ = "many"
31
31
 
32
32
  # The unique internal identifier for the specimen record
@@ -34,7 +34,7 @@ class Specimen(Base):
34
34
 
35
35
  # The unique internal identifier for the associated participant
36
36
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=12220014%20and%20ver_nr=1
37
- participant_id: int | None = None
37
+ participant_id: str | None = None
38
38
 
39
39
  # The unique specimen identifier assigned by the CIMAC-CIDC Network.
40
40
  # Formatted as CTTTPPPSS.AA for trial code TTT, participant PPP, sample SS, and aliquot AA.
@@ -66,7 +66,7 @@ class Specimen(Base):
66
66
  # Categorical description of timepoint at which the sample was taken.
67
67
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=5899851%20and%20ver_nr=1
68
68
  # Note: CIDC doesn't conform to this CDE's PVs
69
- collection_event_name: str | None = None
69
+ collection_event_name: str
70
70
 
71
71
  # The type of the specimen
72
72
  specimen_type: SpecimenType | None = None
@@ -208,4 +208,7 @@ class Specimen(Base):
208
208
  date_ingested: datetime | None = None
209
209
 
210
210
  # Days from enrollment date to date specimen was collected.
211
- days_to_specimen_collection: int | None = None
211
+ days_to_specimen_collection: int
212
+
213
+ # The location within the body from which a specimen was originally obtained as captured in the Uberon anatomical term.
214
+ organ_site_of_collection: UberonAnatomicalTerm
@@ -1,5 +1,5 @@
1
1
  from pydantic import NonNegativeInt
2
- from .base import Base
2
+ from cidc_api.models.pydantic.base import Base
3
3
  from cidc_api.models.types import (
4
4
  StemCellDonorType,
5
5
  AllogeneicDonorType,
@@ -26,7 +26,7 @@ class StemCellTransplant(Base):
26
26
  allogeneic_donor_type: AllogeneicDonorType | None = None
27
27
 
28
28
  # Source of the stem cells used for transplant.
29
- stem_cells_source: StemCellSource
29
+ stem_cell_source: StemCellSource
30
30
 
31
31
  # Days from the enrollment date to the date of the stem cell transplant.
32
32
  days_to_transplant: NonNegativeInt
@@ -2,7 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, model_validator
4
4
 
5
- from .base import Base
5
+ from cidc_api.models.pydantic.base import Base
6
6
  from cidc_api.models.types import SurgicalProcedure, UberonAnatomicalTerm, YNU
7
7
 
8
8
 
@@ -2,7 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import NonNegativeInt, NonNegativeFloat, PositiveFloat, model_validator
4
4
 
5
- from .base import Base
5
+ from cidc_api.models.pydantic.base import Base
6
6
  from cidc_api.models.types import YNU, TherapyAgentDoseUnits
7
7
 
8
8
 
@@ -2,7 +2,7 @@ from typing import Self
2
2
 
3
3
  from pydantic import model_validator
4
4
 
5
- from .base import Base
5
+ from cidc_api.models.pydantic.base import Base
6
6
  from cidc_api.models.types import YNU, OffTreatmentReason
7
7
 
8
8
 
@@ -14,7 +14,7 @@ class Treatment(Base):
14
14
  treatment_id: int | None = None
15
15
 
16
16
  # The unique internal identifier for the associated Participant record
17
- participant_id: int | None = None
17
+ participant_id: str | None = None
18
18
 
19
19
  # The unique internal identifier for the associated Arm record
20
20
  arm_id: int | None = None
@@ -1,8 +1,9 @@
1
1
  from datetime import datetime
2
- from typing import List
2
+ from pydantic import BeforeValidator
3
+ from typing import List, Annotated
3
4
 
4
- from .base import Base
5
- from cidc_api.models.types import TrialOrganization, TrialFundingAgency, TrialStatus, AssayType
5
+ from cidc_api.models.pydantic.base import Base
6
+ from cidc_api.models.types import TrialOrganization, TrialFundingAgency, AssayType, AgeGroup, PrimaryPurposeType
6
7
 
7
8
 
8
9
  class Trial(Base):
@@ -11,11 +12,20 @@ class Trial(Base):
11
12
 
12
13
  # The unique identifier for the clinical trial. e.g. "GU16-287","BACCI"
13
14
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=5054234%20and%20ver_nr=1
14
- trial_id: str
15
+ trial_id: str | None = None
15
16
 
16
17
  # The version number of the trial dataset. e.g. "1.0"
17
18
  version: str | None = None
18
19
 
20
+ # A broad textual description of the primary endpoint(s) of the trial.
21
+ primary_endpoint: str | None = None
22
+
23
+ # The identifiable class of the study participant based upon their age.
24
+ age_group: Annotated[List[AgeGroup], BeforeValidator(Base.split_list)]
25
+
26
+ # Clinical and/or molecular characteristics of the cancer(s) in the study population.
27
+ study_population: str | None = None
28
+
19
29
  # ClinicalTrials.gov identifier. e.g. "NCT03731260"
20
30
  # TODO need cde from janice, they will make one
21
31
  nct_id: str | None = None
@@ -45,10 +55,6 @@ class Trial(Base):
45
55
  # e.g. "Duke University - Duke Cancer Institute LAO"
46
56
  grant_or_affiliated_network: TrialFundingAgency | None = None
47
57
 
48
- # What stage the trial is at in its process.
49
- # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=15607838%20and%20ver_nr=1
50
- trial_status: TrialStatus
51
-
52
58
  # The id of the primary organization responsible for storing biospecimens from this study.
53
59
  biobank_institution_id: int | None = None
54
60
 
@@ -63,6 +69,10 @@ class Trial(Base):
63
69
  # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=16333703%20and%20ver_nr=1
64
70
  dates_of_conduct_end: datetime | None = None
65
71
 
72
+ # A classification of the study based upon the primary intent of the study's activities.
73
+ # CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=11160683%20and%20ver_nr=1
74
+ primary_purpose_type: PrimaryPurposeType
75
+
66
76
  # The image of the trial data schema
67
77
  schema_file_id: int | None = None
68
78
 
@@ -75,11 +85,5 @@ class Trial(Base):
75
85
  # The list of assays that CIDC expects to receive for this trial.
76
86
  expected_assays: List[AssayType] = []
77
87
 
78
- # Is the cancer studying a liquid tumor type?
79
- is_liquid_tumor_trial: bool = False
80
-
81
88
  # The dbgap study accession number associated with the trial.
82
- dbgap_study_accession: str | None = None
83
-
84
- # The internal version identifier for this specific trial dataset.
85
- version: str
89
+ dbgap_study_accession: str
cidc_api/models/types.py CHANGED
@@ -20,6 +20,13 @@ from cidc_api.reference.gvhd import is_gvhd_organ
20
20
  # As python Enums are rather cumbersome when we only want a list of permissible values for a string, use Literal instead
21
21
 
22
22
 
23
+ AgeGroup = Literal[
24
+ "Adolescent and Young Adult",
25
+ "Adult",
26
+ "Pediatric",
27
+ ]
28
+
29
+
23
30
  TrialOrganization = Literal[
24
31
  "ECOG-ACRIN",
25
32
  "SWOG",
@@ -53,10 +60,31 @@ TrialFundingAgency = Literal[
53
60
  ]
54
61
 
55
62
 
56
- TrialStatus = Literal[
57
- "Active Study",
58
- "Completed Study",
59
- "Provisional Study",
63
+ PrimaryPurposeType = Literal[
64
+ "Adverse Effect Mitigation Study",
65
+ "Ancillary Study",
66
+ "Basic Science Research ",
67
+ "Correlative Study",
68
+ "Cure Study",
69
+ "Device Feasibility Study",
70
+ "Diagnosis Study",
71
+ "Disease Modifying Treatment Study",
72
+ "Early Detection Study",
73
+ "Education Training Clinical Study",
74
+ "Epidemiology Research ",
75
+ "Genomics Research",
76
+ "Health Services Research",
77
+ "Imaging Research",
78
+ "Interventional Study",
79
+ "Observational Study",
80
+ "Outcomes Research",
81
+ "Prevention Study",
82
+ "Proteomic Research",
83
+ "Rehabilitation Clinical Study ",
84
+ "Screening Study",
85
+ "Supportive Care Study",
86
+ "Transcriptomics Research",
87
+ "Treatment Study",
60
88
  ]
61
89
 
62
90
 
@@ -85,6 +113,7 @@ AssayType = Literal[
85
113
  "snRNA-Seq",
86
114
  "Visium",
87
115
  "Olink HT",
116
+ "TCRseq RNA",
88
117
  ]
89
118
 
90
119
 
@@ -217,24 +246,6 @@ FileFormat = Literal[
217
246
  ]
218
247
 
219
248
 
220
- PrimaryDiagnosisDiseaseGroup = Literal[
221
- "Ovarian epithelial cancer",
222
- "Esophageal cancer, NOS",
223
- "Primary peritoneal carcinoma",
224
- "Gastric cancer, NOS",
225
- "Fallopian tube carcinoma",
226
- "Invasive breast carcinoma",
227
- "Small cell lung cancer",
228
- "Prostate cancer, NOS",
229
- "Non-small cell lung cancer, NOS",
230
- "Myeloma, NOS",
231
- "Melanoma",
232
- "Adenocarcinoma of the gastroesophageal junction",
233
- "Acute myeloid leukemia, NOS",
234
- "Colorectal cancer, NOS",
235
- ]
236
-
237
-
238
249
  TumorGrade = Literal[
239
250
  "G1 Low Grade",
240
251
  "G2 Intermediate Grade",
@@ -255,12 +266,20 @@ CancerStageSystem = Literal[
255
266
  ]
256
267
 
257
268
 
258
- CancerStageSystemVersionAJCC = Literal["8",]
269
+ CancerStageSystemVersionAJCC = Literal["8th Edition",]
259
270
 
260
- CancerStageSystemVersionRISS = Literal["2"]
271
+ CancerStageSystemVersionRISS = Literal["10.1200/JCO.2015.61.2267"]
261
272
 
262
-
263
- CancerStageSystemVersionFIGO = Literal["2021"]
273
+ CancerStageSystemVersionFIGO = Literal[
274
+ "10.1002/ijgo.14923",
275
+ "10.1002/ijgo.13881",
276
+ "10.1002/ijgo.13867",
277
+ "10.1002/ijgo.13865",
278
+ "10.1002/ijgo.13866",
279
+ "10.1002/ijgo.13878",
280
+ "10.1002/ijgo.13877",
281
+ "10.1002/ijgo.12613",
282
+ ]
264
283
 
265
284
  CancerStageSystemVersion = CancerStageSystemVersionAJCC | CancerStageSystemVersionFIGO | CancerStageSystemVersionRISS
266
285
 
@@ -296,7 +315,6 @@ CancerStageAJCC = Literal[
296
315
 
297
316
 
298
317
  CancerStageFIGO = Literal[
299
- "value",
300
318
  "Stage I",
301
319
  "Stage IA",
302
320
  "Stage IA1",
@@ -1027,21 +1045,6 @@ GVHDDiagnosisChronicGlobalSeverity = Literal["Mild", "Moderate", "Severe"]
1027
1045
  GVHDOrganChronicScore = Literal["0", "1", "2", "3"]
1028
1046
 
1029
1047
 
1030
- PriorTreatmentType = Literal[
1031
- "Surgery",
1032
- "Radiotherapy",
1033
- "Immunotherapy",
1034
- "Chemotherapy",
1035
- "Targeted therapy",
1036
- "Other therapy",
1037
- "Radiopharmaceutical",
1038
- "Stem cell transplant",
1039
- "Immunosuppressive therapy/GVHD prophylaxis for transplant",
1040
- "Conditioning therapy",
1041
- "Post-transplant salvage therapy",
1042
- ]
1043
-
1044
-
1045
1048
  ConditioningRegimenType = Literal["Myeloablative", "Reduced-intensity", "Non-myeloablative", "Other"]
1046
1049
 
1047
1050
  StemCellDonorType = Literal["Autologous", "Allogeneic"]
@@ -0,0 +1,68 @@
1
+ from datetime import datetime
2
+ from urllib.parse import quote
3
+
4
+ from werkzeug.exceptions import BadRequest
5
+
6
+ from cidc_api.models import IngestionJobs
7
+ from . import gcloud_client
8
+ from ..shared.auth import get_current_user
9
+
10
+ JOB_TYPE_ASSAY = "assay"
11
+ JOB_TYPE_CLINICAL = "clinical"
12
+ ALLOWED_JOB_TYPES = {JOB_TYPE_CLINICAL, JOB_TYPE_ASSAY}
13
+
14
+
15
+ def resolve_job_type_and_assay_fields(data: dict) -> tuple[str, str | None, str | None]:
16
+ """Decide job_type and gather assay_type/batch_id from request JSON."""
17
+ assay_type = data.get("assay_type")
18
+ # If job_type is assay or assay_type is present, treat this as an assay job.
19
+ job_type = data.get("job_type") or (JOB_TYPE_ASSAY if assay_type else JOB_TYPE_CLINICAL)
20
+
21
+ if job_type not in ALLOWED_JOB_TYPES:
22
+ raise BadRequest("Invalid job_type. Allowed values are 'clinical' or 'assay'.")
23
+
24
+ if job_type == JOB_TYPE_ASSAY and (not assay_type or not isinstance(assay_type, str)):
25
+ raise BadRequest("assay_type must be provided for job_type='assay'.")
26
+
27
+ assay_type = assay_type.strip() if assay_type else None
28
+ batch_id = data.get("batch_id").strip() if isinstance(data.get("batch_id"), str) else None
29
+
30
+ return job_type, assay_type, batch_id
31
+
32
+
33
+ def prepare_assay_job(trial_id: str, assay_type: str, batch_id: str) -> tuple[str, str, str, datetime, int, str]:
34
+ """
35
+ Validate assay job uniqueness and generate submission_id, start_date, version, and the trial’s GCS intake path.
36
+ """
37
+ if not assay_type:
38
+ raise BadRequest("assay_type must be provided for job_type='assay'.")
39
+
40
+ # Enforce uniqueness of (trial_id, assay_type, batch_id) when batch_id is present.
41
+ if batch_id:
42
+ existing_job = IngestionJobs.get_unique_assay_job(trial_id, assay_type, batch_id)
43
+ if existing_job:
44
+ raise BadRequest(
45
+ f"Assay job {existing_job.id} already exists for this exact trial_id/assay_type/batch_id combination."
46
+ )
47
+
48
+ submission_id = IngestionJobs.next_assay_submission_id(trial_id, assay_type)
49
+ job_status = "INITIAL SUBMISSION"
50
+ error_status = "Upload Incomplete" # job starts with 'Incomplete' notifier
51
+ start_date = datetime.now()
52
+ version = 1
53
+
54
+ # Create or retrieve intake bucket corresponding to the trial
55
+ intake_bucket = gcloud_client.create_intake_bucket(get_current_user().email, trial_id=trial_id)
56
+ gcs_path = f"{intake_bucket.name}/{assay_type}/{submission_id}"
57
+
58
+ return submission_id, job_status, error_status, start_date, version, gcs_path
59
+
60
+
61
+ def get_google_links(intake_path: str) -> tuple[str, str]:
62
+ """Build the GCS URI and GCS Console URL corresponding to the intake path."""
63
+ gcs_uri = f"gs://{intake_path}"
64
+ # Encode path to ensure link opens correctly
65
+ encoded_path = quote(intake_path)
66
+ console_url = f"https://console.cloud.google.com/storage/browser/{encoded_path}"
67
+
68
+ return gcs_uri, console_url
cidc_api/shared/auth.py CHANGED
@@ -1,16 +1,14 @@
1
1
  from functools import wraps
2
2
  from typing import List
3
3
 
4
- from packaging import version
5
-
6
4
  from flask import g, request, current_app as app, Flask
5
+ from packaging import version
7
6
  from werkzeug.exceptions import Unauthorized, BadRequest, PreconditionFailed
8
7
 
9
- from ..models import Users, UserSchema
10
-
11
8
  from ..config.logging import get_logger
12
-
9
+ from ..models import Users, UserSchema
13
10
  from ..shared.jose import decode_id_token
11
+ from ..telemetry import trace_
14
12
 
15
13
  logger = get_logger(__name__)
16
14
 
@@ -144,6 +142,7 @@ def get_current_user() -> Users:
144
142
  _user_schema = UserSchema()
145
143
 
146
144
 
145
+ @trace_()
147
146
  def authenticate() -> Users:
148
147
  id_token = _extract_token()
149
148
  token_payload = decode_id_token(id_token)
@@ -172,6 +171,7 @@ def _extract_token() -> str:
172
171
 
173
172
 
174
173
  ### Authorization logic ###
174
+ @trace_()
175
175
  def authorize(user: Users, allowed_roles: List[str], resource: str, method: str) -> bool:
176
176
  """Check if the current user is authorized to act on the current request's resource.
177
177
  Raises Unauthorized
@@ -10,22 +10,33 @@ from ..config.settings import GOOGLE_CLINICAL_DATA_BUCKET
10
10
  from ..models import PreprocessedFiles, TRIAL_APPENDIX_A_CELL_THAT_ENDS_THE_HEADER
11
11
  from ..shared.auth import get_current_user
12
12
  from ..shared.gcloud_client import upload_file_to_gcs, move_gcs_file
13
+ from ..telemetry import trace_
13
14
 
14
15
  logger = get_logger(__name__)
15
16
 
16
17
 
18
+ @trace_()
17
19
  def set_current_file(
18
- file: FileStorage, file_category: str, gcs_folder: str, session: Session, uploader_email: str, job_id: int = None
20
+ file: FileStorage,
21
+ file_category: str,
22
+ gcs_folder: str,
23
+ session: Session,
24
+ uploader_email: str,
25
+ job_id: int = None,
26
+ append_timestamp: bool = None,
19
27
  ) -> PreprocessedFiles:
20
28
  """
21
29
  Archives any existing 'current' files for the given category and job,
22
30
  then uploads the new file as the latest 'current' version.
23
31
  """
24
32
  latest_version = PreprocessedFiles.archive_current_files(file_category, job_id=job_id, session=session)
25
- latest_file = create_file(file, gcs_folder, file_category, session, uploader_email, job_id, latest_version + 1)
33
+ latest_file = create_file(
34
+ file, gcs_folder, file_category, session, uploader_email, job_id, latest_version + 1, append_timestamp
35
+ )
26
36
  return latest_file
27
37
 
28
38
 
39
+ @trace_()
29
40
  def create_file(
30
41
  file: FileStorage,
31
42
  gcs_folder: str,
@@ -34,11 +45,12 @@ def create_file(
34
45
  uploader_email: str,
35
46
  job_id: int = None,
36
47
  version: int = None,
48
+ append_timestamp: bool = None,
37
49
  ) -> PreprocessedFiles:
38
50
  """Upload file to GCS and create corresponding metadata record in the database."""
39
51
  status = "pending" if gcs_folder.endswith("pending/") else "current"
40
- # only need timestamp for current/versioned files
41
- append_timestamp = status == "current"
52
+ # only need timestamp for current/versioned files, if not specified otherwise
53
+ append_timestamp = append_timestamp if append_timestamp is not None else (status == "current")
42
54
  # create file in GCS
43
55
  gcs_file_path = upload_file_to_gcs(file, GOOGLE_CLINICAL_DATA_BUCKET, gcs_folder, append_timestamp=append_timestamp)
44
56
  # create corresponding record in db