nci-cidc-api-modules 1.2.34__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.
- cidc_api/__init__.py +1 -0
- cidc_api/config/db.py +21 -1
- cidc_api/config/settings.py +1 -0
- cidc_api/models/__init__.py +0 -2
- cidc_api/models/data.py +15 -6
- cidc_api/models/db/stage1/__init__.py +56 -0
- cidc_api/models/db/stage1/additional_treatment_orm.py +22 -0
- cidc_api/models/db/stage1/adverse_event_orm.py +46 -0
- cidc_api/models/db/stage1/base_orm.py +7 -0
- cidc_api/models/db/stage1/baseline_clinical_assessment_orm.py +22 -0
- cidc_api/models/db/stage1/comorbidity_orm.py +23 -0
- cidc_api/models/db/stage1/consent_group_orm.py +32 -0
- cidc_api/models/db/stage1/demographic_orm.py +47 -0
- cidc_api/models/db/stage1/disease_orm.py +52 -0
- cidc_api/models/db/stage1/exposure_orm.py +22 -0
- cidc_api/models/db/stage1/gvhd_diagnosis_acute_orm.py +34 -0
- cidc_api/models/db/stage1/gvhd_diagnosis_chronic_orm.py +36 -0
- cidc_api/models/db/stage1/gvhd_organ_acute_orm.py +21 -0
- cidc_api/models/db/stage1/gvhd_organ_chronic_orm.py +21 -0
- cidc_api/models/db/stage1/medical_history_orm.py +30 -0
- cidc_api/models/db/stage1/other_malignancy_orm.py +29 -0
- cidc_api/models/db/stage1/participant_orm.py +77 -0
- cidc_api/models/db/stage1/prior_treatment_orm.py +29 -0
- cidc_api/models/db/stage1/radiotherapy_dose_orm.py +39 -0
- cidc_api/models/db/stage1/response_by_system_orm.py +30 -0
- cidc_api/models/db/stage1/response_orm.py +28 -0
- cidc_api/models/db/stage1/specimen_orm.py +46 -0
- cidc_api/models/db/stage1/stem_cell_transplant_orm.py +25 -0
- cidc_api/models/db/stage1/surgery_orm.py +27 -0
- cidc_api/models/db/stage1/therapy_agent_dose_orm.py +31 -0
- cidc_api/models/db/stage1/treatment_orm.py +38 -0
- cidc_api/models/db/stage1/trial_orm.py +35 -0
- cidc_api/models/db/stage2/additional_treatment_orm.py +6 -7
- cidc_api/models/db/stage2/administrative_person_orm.py +4 -4
- cidc_api/models/db/stage2/administrative_role_assignment_orm.py +4 -4
- cidc_api/models/db/stage2/adverse_event_orm.py +11 -13
- cidc_api/models/db/stage2/arm_orm.py +3 -3
- cidc_api/models/db/stage2/base_orm.py +7 -0
- cidc_api/models/db/stage2/baseline_clinical_assessment_orm.py +5 -7
- cidc_api/models/db/stage2/cohort_orm.py +3 -3
- cidc_api/models/db/stage2/comorbidity_orm.py +6 -8
- cidc_api/models/db/stage2/consent_group_orm.py +4 -4
- cidc_api/models/db/stage2/contact_orm.py +16 -20
- cidc_api/models/db/stage2/demographic_orm.py +3 -3
- cidc_api/models/db/stage2/disease_orm.py +4 -4
- cidc_api/models/db/stage2/exposure_orm.py +3 -3
- cidc_api/models/db/stage2/file_orm.py +6 -9
- cidc_api/models/db/stage2/gvhd_diagnosis_acute_orm.py +4 -4
- cidc_api/models/db/stage2/gvhd_diagnosis_chronic_orm.py +4 -6
- cidc_api/models/db/stage2/gvhd_organ_acute_orm.py +3 -3
- cidc_api/models/db/stage2/gvhd_organ_chronic_orm.py +3 -3
- cidc_api/models/db/stage2/institution_orm.py +7 -7
- cidc_api/models/db/stage2/medical_history_orm.py +9 -9
- cidc_api/models/db/stage2/other_clinical_endpoint_orm.py +8 -12
- cidc_api/models/db/stage2/other_malignancy_orm.py +8 -10
- cidc_api/models/db/stage2/participant_orm.py +23 -24
- cidc_api/models/db/stage2/prior_treatment_orm.py +12 -13
- cidc_api/models/db/stage2/publication_orm.py +9 -11
- cidc_api/models/db/stage2/radiotherapy_dose_orm.py +8 -9
- cidc_api/models/db/stage2/response_by_system_orm.py +3 -3
- cidc_api/models/db/stage2/response_orm.py +3 -3
- cidc_api/models/db/stage2/shipment_orm.py +17 -17
- cidc_api/models/db/stage2/shipment_specimen_orm.py +4 -4
- cidc_api/models/db/stage2/specimen_orm.py +7 -6
- cidc_api/models/db/stage2/stem_cell_transplant_orm.py +6 -7
- cidc_api/models/db/stage2/surgery_orm.py +6 -7
- cidc_api/models/db/stage2/therapy_agent_dose_orm.py +7 -8
- cidc_api/models/db/stage2/treatment_orm.py +15 -15
- cidc_api/models/db/stage2/trial_orm.py +15 -17
- cidc_api/models/files/facets.py +4 -0
- cidc_api/models/models.py +153 -9
- cidc_api/models/pydantic/{stage2/base.py → base.py} +1 -1
- cidc_api/models/pydantic/stage1/__init__.py +56 -0
- cidc_api/models/pydantic/stage1/additional_treatment.py +23 -0
- cidc_api/models/pydantic/stage1/adverse_event.py +100 -0
- cidc_api/models/pydantic/stage1/baseline_clinical_assessment.py +23 -0
- cidc_api/models/pydantic/stage1/comorbidity.py +36 -0
- cidc_api/models/pydantic/stage1/consent_group.py +30 -0
- cidc_api/models/pydantic/stage1/demographic.py +123 -0
- cidc_api/models/pydantic/stage1/disease.py +158 -0
- cidc_api/models/pydantic/stage1/exposure.py +32 -0
- cidc_api/models/pydantic/stage1/gvhd_diagnosis_acute.py +33 -0
- cidc_api/models/pydantic/stage1/gvhd_diagnosis_chronic.py +32 -0
- cidc_api/models/pydantic/stage1/gvhd_organ_acute.py +22 -0
- cidc_api/models/pydantic/stage1/gvhd_organ_chronic.py +23 -0
- cidc_api/models/pydantic/stage1/medical_history.py +36 -0
- cidc_api/models/pydantic/stage1/other_malignancy.py +49 -0
- cidc_api/models/pydantic/stage1/participant.py +51 -0
- cidc_api/models/pydantic/stage1/prior_treatment.py +45 -0
- cidc_api/models/pydantic/stage1/radiotherapy_dose.py +79 -0
- cidc_api/models/pydantic/stage1/response.py +65 -0
- cidc_api/models/pydantic/stage1/response_by_system.py +112 -0
- cidc_api/models/pydantic/stage1/specimen.py +31 -0
- cidc_api/models/pydantic/stage1/stem_cell_transplant.py +35 -0
- cidc_api/models/pydantic/stage1/surgery.py +49 -0
- cidc_api/models/pydantic/stage1/therapy_agent_dose.py +67 -0
- cidc_api/models/pydantic/stage1/treatment.py +50 -0
- cidc_api/models/pydantic/stage1/trial.py +45 -0
- cidc_api/models/pydantic/stage2/additional_treatment.py +2 -4
- cidc_api/models/pydantic/stage2/administrative_person.py +1 -1
- cidc_api/models/pydantic/stage2/administrative_role_assignment.py +2 -2
- cidc_api/models/pydantic/stage2/adverse_event.py +1 -1
- cidc_api/models/pydantic/stage2/arm.py +2 -2
- cidc_api/models/pydantic/stage2/baseline_clinical_assessment.py +1 -1
- cidc_api/models/pydantic/stage2/cohort.py +1 -1
- cidc_api/models/pydantic/stage2/comorbidity.py +1 -1
- cidc_api/models/pydantic/stage2/consent_group.py +2 -2
- cidc_api/models/pydantic/stage2/contact.py +1 -1
- cidc_api/models/pydantic/stage2/demographic.py +1 -1
- cidc_api/models/pydantic/stage2/disease.py +1 -1
- cidc_api/models/pydantic/stage2/exposure.py +1 -1
- cidc_api/models/pydantic/stage2/file.py +2 -2
- cidc_api/models/pydantic/stage2/gvhd_diagnosis_acute.py +1 -1
- cidc_api/models/pydantic/stage2/gvhd_diagnosis_chronic.py +1 -1
- cidc_api/models/pydantic/stage2/gvhd_organ_acute.py +1 -1
- cidc_api/models/pydantic/stage2/gvhd_organ_chronic.py +1 -1
- cidc_api/models/pydantic/stage2/institution.py +1 -1
- cidc_api/models/pydantic/stage2/medical_history.py +1 -1
- cidc_api/models/pydantic/stage2/other_clinical_endpoint.py +1 -1
- cidc_api/models/pydantic/stage2/other_malignancy.py +1 -1
- cidc_api/models/pydantic/stage2/participant.py +6 -3
- cidc_api/models/pydantic/stage2/prior_treatment.py +6 -15
- cidc_api/models/pydantic/stage2/publication.py +2 -2
- cidc_api/models/pydantic/stage2/radiotherapy_dose.py +1 -1
- cidc_api/models/pydantic/stage2/response.py +2 -2
- cidc_api/models/pydantic/stage2/response_by_system.py +1 -1
- cidc_api/models/pydantic/stage2/shipment.py +2 -2
- cidc_api/models/pydantic/stage2/shipment_specimen.py +1 -1
- cidc_api/models/pydantic/stage2/specimen.py +6 -3
- cidc_api/models/pydantic/stage2/stem_cell_transplant.py +2 -2
- cidc_api/models/pydantic/stage2/surgery.py +1 -1
- cidc_api/models/pydantic/stage2/therapy_agent_dose.py +1 -1
- cidc_api/models/pydantic/stage2/treatment.py +1 -1
- cidc_api/models/pydantic/stage2/trial.py +8 -10
- cidc_api/models/types.py +30 -16
- cidc_api/shared/assay_handling.py +68 -0
- cidc_api/shared/auth.py +5 -5
- cidc_api/shared/file_handling.py +16 -4
- cidc_api/shared/gcloud_client.py +78 -16
- cidc_api/shared/utils.py +18 -9
- cidc_api/telemetry.py +101 -0
- {nci_cidc_api_modules-1.2.34.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/METADATA +21 -12
- nci_cidc_api_modules-1.2.45.dist-info/RECORD +165 -0
- cidc_api/models/db/base_orm.py +0 -25
- nci_cidc_api_modules-1.2.34.dist-info/RECORD +0 -109
- {nci_cidc_api_modules-1.2.34.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/WHEEL +0 -0
- {nci_cidc_api_modules-1.2.34.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/licenses/LICENSE +0 -0
- {nci_cidc_api_modules-1.2.34.dist-info → nci_cidc_api_modules-1.2.45.dist-info}/top_level.txt +0 -0
cidc_api/models/models.py
CHANGED
|
@@ -28,6 +28,7 @@ __all__ = [
|
|
|
28
28
|
"JobFileCategories",
|
|
29
29
|
"CategoryDataElements",
|
|
30
30
|
"ValidationConfigs",
|
|
31
|
+
"MASTER_APPENDIX_A",
|
|
31
32
|
"TRIAL_APPENDIX_A",
|
|
32
33
|
"TRIAL_APPENDIX_A_CELL_THAT_ENDS_THE_HEADER",
|
|
33
34
|
"REQUEST_LETTER",
|
|
@@ -35,7 +36,8 @@ __all__ = [
|
|
|
35
36
|
"ADMIN_FILE_CATEGORIES",
|
|
36
37
|
"FINAL_JOB_STATUS",
|
|
37
38
|
"INGESTION_JOB_STATUSES",
|
|
38
|
-
"
|
|
39
|
+
"ASSAY_JOB_COLORS",
|
|
40
|
+
"CLINICAL_JOB_COLORS",
|
|
39
41
|
]
|
|
40
42
|
|
|
41
43
|
import hashlib
|
|
@@ -3261,6 +3263,7 @@ def upload_manifest_json(
|
|
|
3261
3263
|
return manifest_upload.id
|
|
3262
3264
|
|
|
3263
3265
|
|
|
3266
|
+
MASTER_APPENDIX_A = "master_appendix_a"
|
|
3264
3267
|
TRIAL_APPENDIX_A = "trial_appendix_a"
|
|
3265
3268
|
REQUEST_LETTER = "request_letter"
|
|
3266
3269
|
DETAILED_VALIDATION = "detailed_validation"
|
|
@@ -3360,6 +3363,16 @@ class PreprocessedFiles(CommonColumns):
|
|
|
3360
3363
|
query = cls.add_job_filter(query, job_id)
|
|
3361
3364
|
return query.all()
|
|
3362
3365
|
|
|
3366
|
+
@classmethod
|
|
3367
|
+
@with_default_session
|
|
3368
|
+
def get_latest_current_file(
|
|
3369
|
+
cls, file_category: str, job_id: int = None, session: Session = None
|
|
3370
|
+
) -> Optional["PreprocessedFiles"]:
|
|
3371
|
+
"""Return the latest 'current' file for the given category and job_id. Returns None if no file exists."""
|
|
3372
|
+
query = session.query(cls).filter_by(file_category=file_category, status="current")
|
|
3373
|
+
query = cls.add_job_filter(query, job_id)
|
|
3374
|
+
return query.order_by(cls.version.desc()).first()
|
|
3375
|
+
|
|
3363
3376
|
@classmethod
|
|
3364
3377
|
@with_default_session
|
|
3365
3378
|
def get_file_by_category_and_version(
|
|
@@ -3436,7 +3449,7 @@ INGESTION_JOB_STATUSES = [
|
|
|
3436
3449
|
]
|
|
3437
3450
|
|
|
3438
3451
|
# Business decision to pass hex codes from the backend though that should be done by the front end...
|
|
3439
|
-
|
|
3452
|
+
CLINICAL_JOB_COLORS = {
|
|
3440
3453
|
"DRAFT": "",
|
|
3441
3454
|
"INITIAL SUBMISSION": "#ACCAD7",
|
|
3442
3455
|
"VALIDATION REVIEW": "#DABE90",
|
|
@@ -3444,6 +3457,13 @@ INGESTION_JOB_COLORS = {
|
|
|
3444
3457
|
"INGESTION": "#8FCEC7",
|
|
3445
3458
|
"PUBLISHED": "#90D9E6",
|
|
3446
3459
|
}
|
|
3460
|
+
ASSAY_JOB_COLORS = {
|
|
3461
|
+
"INITIAL SUBMISSION": "#43807E",
|
|
3462
|
+
"VALIDATION REVIEW": "#906F3F",
|
|
3463
|
+
"REVISION SUBMISSION": "#95358A",
|
|
3464
|
+
"INGESTION": "#542C88",
|
|
3465
|
+
"PUBLISHED": "#1C81A0",
|
|
3466
|
+
}
|
|
3447
3467
|
# TODO If have "CANCELLED" concept or other final status, add here
|
|
3448
3468
|
FINAL_JOB_STATUS = ["PUBLISHED"]
|
|
3449
3469
|
TRIAL_APPENDIX_A_CELL_THAT_ENDS_THE_HEADER = "Data Category"
|
|
@@ -3465,11 +3485,45 @@ class IngestionJobs(CommonColumns):
|
|
|
3465
3485
|
pending = Column(Boolean, nullable=False, default=False)
|
|
3466
3486
|
start_date = Column(DateTime, nullable=True)
|
|
3467
3487
|
error_status = Column(String, nullable=True)
|
|
3488
|
+
job_type = Column(String, nullable=False, default="clinical")
|
|
3489
|
+
assay_type = Column(String, nullable=True)
|
|
3490
|
+
batch_id = Column(String, nullable=True)
|
|
3491
|
+
submission_id = Column(String, nullable=True)
|
|
3492
|
+
intake_path = Column(String, nullable=True)
|
|
3493
|
+
uploader_email = Column(String, nullable=True)
|
|
3494
|
+
is_template_downloaded = Column(Boolean, nullable=True)
|
|
3468
3495
|
|
|
3469
3496
|
@staticmethod
|
|
3470
3497
|
@with_default_session
|
|
3471
|
-
def create(
|
|
3472
|
-
|
|
3498
|
+
def create(
|
|
3499
|
+
trial_id: str,
|
|
3500
|
+
status: str,
|
|
3501
|
+
version: int,
|
|
3502
|
+
error_status: str = None,
|
|
3503
|
+
pending: Boolean = False,
|
|
3504
|
+
job_type: str = "clinical",
|
|
3505
|
+
assay_type: str = None,
|
|
3506
|
+
batch_id: str = None,
|
|
3507
|
+
submission_id: str = None,
|
|
3508
|
+
intake_path: str = None,
|
|
3509
|
+
start_date: datetime = None,
|
|
3510
|
+
uploader_email: str = None,
|
|
3511
|
+
session: Session = None,
|
|
3512
|
+
):
|
|
3513
|
+
new_job = IngestionJobs(
|
|
3514
|
+
trial_id=trial_id,
|
|
3515
|
+
status=status,
|
|
3516
|
+
error_status=error_status,
|
|
3517
|
+
version=version,
|
|
3518
|
+
pending=pending,
|
|
3519
|
+
job_type=job_type,
|
|
3520
|
+
assay_type=assay_type,
|
|
3521
|
+
batch_id=batch_id,
|
|
3522
|
+
submission_id=submission_id,
|
|
3523
|
+
intake_path=intake_path,
|
|
3524
|
+
start_date=start_date,
|
|
3525
|
+
uploader_email=uploader_email,
|
|
3526
|
+
)
|
|
3473
3527
|
new_job.insert(session=session)
|
|
3474
3528
|
return new_job
|
|
3475
3529
|
|
|
@@ -3494,29 +3548,43 @@ class IngestionJobs(CommonColumns):
|
|
|
3494
3548
|
|
|
3495
3549
|
@classmethod
|
|
3496
3550
|
@with_default_session
|
|
3497
|
-
def get_jobs_by_trial(
|
|
3498
|
-
|
|
3551
|
+
def get_jobs_by_trial(
|
|
3552
|
+
cls, trial_id: str, job_type: str = "clinical", session: Session = None
|
|
3553
|
+
) -> list["IngestionJobs"]:
|
|
3554
|
+
return (
|
|
3555
|
+
session.query(cls)
|
|
3556
|
+
.filter(cls.trial_id == trial_id, cls.job_type == job_type)
|
|
3557
|
+
.order_by(cls.version.desc())
|
|
3558
|
+
.all()
|
|
3559
|
+
)
|
|
3499
3560
|
|
|
3500
3561
|
@classmethod
|
|
3501
3562
|
@with_default_session
|
|
3502
|
-
def get_open_job_by_trial(
|
|
3563
|
+
def get_open_job_by_trial(
|
|
3564
|
+
cls, trial_id: str, job_type: str = "clinical", session: Session = None
|
|
3565
|
+
) -> Optional["IngestionJobs"]:
|
|
3503
3566
|
"""Return the open job for a given trial if it exists."""
|
|
3504
3567
|
return (
|
|
3505
3568
|
session.query(cls)
|
|
3506
3569
|
.filter(
|
|
3507
3570
|
cls.trial_id == trial_id,
|
|
3571
|
+
cls.job_type == job_type,
|
|
3508
3572
|
cls.status.notin_(FINAL_JOB_STATUS),
|
|
3509
3573
|
)
|
|
3510
3574
|
.order_by(cls._created.desc())
|
|
3511
3575
|
.first()
|
|
3512
3576
|
)
|
|
3513
3577
|
|
|
3578
|
+
@classmethod
|
|
3579
|
+
def get_jobs_for_user(cls, user: Users, job_type: str = None) -> list["IngestionJobs"]:
|
|
3580
|
+
return cls.get_assay_jobs_for_user(user) if job_type == "assay" else cls.get_clinical_jobs_for_user(user)
|
|
3581
|
+
|
|
3514
3582
|
@classmethod
|
|
3515
3583
|
@with_default_session
|
|
3516
|
-
def
|
|
3584
|
+
def get_clinical_jobs_for_user(cls, user: Users, session: Session = None) -> list["IngestionJobs"]:
|
|
3517
3585
|
if user.role not in [CIDCRole.ADMIN.value, CIDCRole.CLINICAL_TRIAL_USER.value]:
|
|
3518
3586
|
return []
|
|
3519
|
-
job_query = session.query(cls).filter(cls.status.notin_(["DRAFT"]))
|
|
3587
|
+
job_query = session.query(cls).filter(cls.status.notin_(["DRAFT"]), cls.job_type == "clinical")
|
|
3520
3588
|
if (
|
|
3521
3589
|
user.role != CIDCRole.ADMIN.value
|
|
3522
3590
|
and not session.query(Permissions)
|
|
@@ -3539,6 +3607,81 @@ class IngestionJobs(CommonColumns):
|
|
|
3539
3607
|
job_query = job_query.filter(cls.trial_id.in_(map(lambda x: x.trial_id, authorized_trials)))
|
|
3540
3608
|
return job_query.order_by(cls._created.desc()).all()
|
|
3541
3609
|
|
|
3610
|
+
@classmethod
|
|
3611
|
+
@with_default_session
|
|
3612
|
+
def get_assay_jobs_for_user(cls, user: Users, session: Session = None) -> list["IngestionJobs"]:
|
|
3613
|
+
# TODO allow more than just Admin role and get authorized trials based on permissions
|
|
3614
|
+
if user.role not in [CIDCRole.ADMIN.value]:
|
|
3615
|
+
return []
|
|
3616
|
+
return session.query(cls).filter(cls.job_type == "assay").order_by(cls._created.desc()).all()
|
|
3617
|
+
|
|
3618
|
+
@classmethod
|
|
3619
|
+
@with_default_session
|
|
3620
|
+
def get_unique_assay_job(
|
|
3621
|
+
cls,
|
|
3622
|
+
trial_id: str,
|
|
3623
|
+
assay_type: str,
|
|
3624
|
+
batch_id: str,
|
|
3625
|
+
session: Session = None,
|
|
3626
|
+
) -> Optional["IngestionJobs"]:
|
|
3627
|
+
"""Look for unique assay job with matching trial_id/assay_type/batch_id combination."""
|
|
3628
|
+
return (
|
|
3629
|
+
session.query(cls)
|
|
3630
|
+
.filter(
|
|
3631
|
+
cls.job_type == "assay",
|
|
3632
|
+
cls.trial_id == trial_id,
|
|
3633
|
+
cls.assay_type == assay_type,
|
|
3634
|
+
cls.batch_id == batch_id,
|
|
3635
|
+
)
|
|
3636
|
+
.first()
|
|
3637
|
+
)
|
|
3638
|
+
|
|
3639
|
+
@classmethod
|
|
3640
|
+
@with_default_session
|
|
3641
|
+
def next_assay_submission_id(cls, trial_id: str, assay_type: str, session: Session = None) -> str:
|
|
3642
|
+
"""
|
|
3643
|
+
Generate the next CIDC Submission ID for an assay job.
|
|
3644
|
+
|
|
3645
|
+
Format:
|
|
3646
|
+
<trial_id>-<assay_type>-<yyyymmdd> (first submission of the day)
|
|
3647
|
+
<trial_id>-<assay_type>-<yyyymmdd>-<#> (subsequent submissions on same day)
|
|
3648
|
+
|
|
3649
|
+
Uses only the most recent matching submission_id to determine the next suffix.
|
|
3650
|
+
"""
|
|
3651
|
+
today_str = datetime.now().strftime("%Y%m%d")
|
|
3652
|
+
base_submission_id = f"{trial_id}-{assay_type}-{today_str}"
|
|
3653
|
+
|
|
3654
|
+
# Get the most recent submission_id matching this prefix
|
|
3655
|
+
latest = (
|
|
3656
|
+
session.query(cls.submission_id)
|
|
3657
|
+
.filter(
|
|
3658
|
+
cls.trial_id == trial_id,
|
|
3659
|
+
cls.assay_type == assay_type,
|
|
3660
|
+
cls.submission_id.like(f"{base_submission_id}%"),
|
|
3661
|
+
)
|
|
3662
|
+
.order_by(cls._created.desc())
|
|
3663
|
+
.first()
|
|
3664
|
+
)
|
|
3665
|
+
|
|
3666
|
+
# No existing submission for this prefix -> start at 1
|
|
3667
|
+
if not latest or not latest[0]:
|
|
3668
|
+
return base_submission_id
|
|
3669
|
+
|
|
3670
|
+
last_id = latest[0]
|
|
3671
|
+
# Case 1: the latest is exactly the prefix (i.e., first submission today)
|
|
3672
|
+
if last_id == base_submission_id:
|
|
3673
|
+
return f"{base_submission_id}-2"
|
|
3674
|
+
|
|
3675
|
+
# Case 2: latest already has a suffix
|
|
3676
|
+
try:
|
|
3677
|
+
_, last_suffix = last_id.rsplit("-", 1)
|
|
3678
|
+
n = int(last_suffix)
|
|
3679
|
+
return f"{base_submission_id}-{n + 1}"
|
|
3680
|
+
except Exception as e:
|
|
3681
|
+
# If malformed, restart numbering for safety
|
|
3682
|
+
logger.error("Unexpected error parsing Submission ID in next_assay_submission_id: %s", e)
|
|
3683
|
+
return f"{base_submission_id}-2"
|
|
3684
|
+
|
|
3542
3685
|
|
|
3543
3686
|
class JobFileCategories(CommonColumns):
|
|
3544
3687
|
__tablename__ = "job_file_categories"
|
|
@@ -3613,6 +3756,7 @@ class CategoryDataElements(CommonColumns):
|
|
|
3613
3756
|
name = Column(String, nullable=False)
|
|
3614
3757
|
is_custom = Column(Boolean, nullable=False, default=False, server_default="false")
|
|
3615
3758
|
element_type = Column(String, nullable=False)
|
|
3759
|
+
data_type = Column(String, nullable=True)
|
|
3616
3760
|
cardinality = Column(String, nullable=True)
|
|
3617
3761
|
|
|
3618
3762
|
@classmethod
|
|
@@ -9,7 +9,7 @@ class Base(BaseModel):
|
|
|
9
9
|
model_config = ConfigDict(
|
|
10
10
|
validate_assignment=True,
|
|
11
11
|
from_attributes=True,
|
|
12
|
-
|
|
12
|
+
extra="allow",
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
# Validates the new state and updates the object if valid
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from .additional_treatment import AdditionalTreatment
|
|
2
|
+
from .adverse_event import AdverseEvent
|
|
3
|
+
from .baseline_clinical_assessment import BaselineClinicalAssessment
|
|
4
|
+
from .comorbidity import Comorbidity
|
|
5
|
+
from .consent_group import ConsentGroup
|
|
6
|
+
from .demographic import Demographic
|
|
7
|
+
from .disease import Disease
|
|
8
|
+
from .exposure import Exposure
|
|
9
|
+
from .gvhd_diagnosis_acute import GVHDDiagnosisAcute
|
|
10
|
+
from .gvhd_diagnosis_chronic import GVHDDiagnosisChronic
|
|
11
|
+
from .gvhd_organ_acute import GVHDOrganAcute
|
|
12
|
+
from .gvhd_organ_chronic import GVHDOrganChronic
|
|
13
|
+
from .medical_history import MedicalHistory
|
|
14
|
+
from .other_malignancy import OtherMalignancy
|
|
15
|
+
from .participant import Participant
|
|
16
|
+
from .prior_treatment import PriorTreatment
|
|
17
|
+
from .radiotherapy_dose import RadiotherapyDose
|
|
18
|
+
from .response import Response
|
|
19
|
+
from .response_by_system import ResponseBySystem
|
|
20
|
+
from .specimen import Specimen
|
|
21
|
+
from .stem_cell_transplant import StemCellTransplant
|
|
22
|
+
from .surgery import Surgery
|
|
23
|
+
from .therapy_agent_dose import TherapyAgentDose
|
|
24
|
+
from .treatment import Treatment
|
|
25
|
+
from .trial import Trial
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AdditionalTreatment",
|
|
30
|
+
"AdverseEvent",
|
|
31
|
+
"BaselineClinicalAssessment",
|
|
32
|
+
"Comorbidity",
|
|
33
|
+
"ConsentGroup",
|
|
34
|
+
"Demographic",
|
|
35
|
+
"Disease",
|
|
36
|
+
"Exposure",
|
|
37
|
+
"GVHDDiagnosisAcute",
|
|
38
|
+
"GVHDOrganAcute",
|
|
39
|
+
"GVHDDiagnosisChronic",
|
|
40
|
+
"GVHDOrganChronic",
|
|
41
|
+
"MedicalHistory",
|
|
42
|
+
"OtherMalignancy",
|
|
43
|
+
"Participant",
|
|
44
|
+
"PriorTreatment",
|
|
45
|
+
"RadiotherapyDose",
|
|
46
|
+
"Response",
|
|
47
|
+
"ResponseBySystem",
|
|
48
|
+
"Specimen",
|
|
49
|
+
"StemCellTransplant",
|
|
50
|
+
"Surgery",
|
|
51
|
+
"TherapyAgentDose",
|
|
52
|
+
"Treatment",
|
|
53
|
+
"Trial",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
all_models = [globals()[cls_name] for cls_name in __all__]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from pydantic import NonNegativeInt
|
|
2
|
+
from cidc_api.models.pydantic.base import Base
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AdditionalTreatment(Base):
|
|
6
|
+
__data_category__ = "additional_treatment"
|
|
7
|
+
__cardinality__ = "many"
|
|
8
|
+
|
|
9
|
+
# The unique internal identifier for the AdditionalTreatment record
|
|
10
|
+
additional_treatment_id: int | None = None
|
|
11
|
+
|
|
12
|
+
# The unique internal identifier for the associated Participant record
|
|
13
|
+
participant_id: str | None = None
|
|
14
|
+
|
|
15
|
+
# Number of days from the enrollment date to the first recorded administration or occurrence of the treatment modality.
|
|
16
|
+
additional_treatment_days_to_start: NonNegativeInt | None = None
|
|
17
|
+
|
|
18
|
+
# Number of days from the enrollment date to the last recorded administration or occurrence of the treatment modality.
|
|
19
|
+
additional_treatment_days_to_end: NonNegativeInt | None = None
|
|
20
|
+
|
|
21
|
+
# Description of the prior treatment such as its full generic name if it is a type of therapy agent, radiotherapy procedure
|
|
22
|
+
# name and location, or surgical procedure name and location.
|
|
23
|
+
additional_treatment_description: str
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from pydantic import NonNegativeInt, model_validator
|
|
4
|
+
|
|
5
|
+
from cidc_api.models.pydantic.base import Base
|
|
6
|
+
from cidc_api.reference.ctcae import is_ctcae_other_term
|
|
7
|
+
from cidc_api.models.types import (
|
|
8
|
+
CTCAEEventTerm,
|
|
9
|
+
CTCAEEventCode,
|
|
10
|
+
SeverityGradeSystem,
|
|
11
|
+
SeverityGradeSystemVersion,
|
|
12
|
+
SeverityGrade,
|
|
13
|
+
SystemOrganClass,
|
|
14
|
+
AttributionCause,
|
|
15
|
+
AttributionLikelihood,
|
|
16
|
+
YNU,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AdverseEvent(Base):
|
|
21
|
+
__data_category__ = "adverse_event"
|
|
22
|
+
__cardinality__ = "many"
|
|
23
|
+
|
|
24
|
+
# The unique internal identifier of the adverse event
|
|
25
|
+
adverse_event_id: int | None = None
|
|
26
|
+
|
|
27
|
+
# The unique internal identifier of the associated participant
|
|
28
|
+
participant_id: str | None = None
|
|
29
|
+
|
|
30
|
+
# The unique internal identifier of the attributed treatment, if any
|
|
31
|
+
treatment_id: int | None = None
|
|
32
|
+
|
|
33
|
+
# Text that represents the Common Terminology Criteria for Adverse Events low level term name for an adverse event.
|
|
34
|
+
event_term: CTCAEEventTerm | None = None
|
|
35
|
+
|
|
36
|
+
# A MedDRA code mapped to a CTCAE low level name for an adverse event.
|
|
37
|
+
event_code: CTCAEEventCode | None = None
|
|
38
|
+
|
|
39
|
+
# System used to define and report adverse event severity grade.
|
|
40
|
+
severity_grade_system: SeverityGradeSystem
|
|
41
|
+
|
|
42
|
+
# The version of the adverse event grading system.
|
|
43
|
+
severity_grade_system_version: SeverityGradeSystemVersion
|
|
44
|
+
|
|
45
|
+
# Numerical grade indicating the severity of an adverse event.
|
|
46
|
+
severity_grade: SeverityGrade
|
|
47
|
+
|
|
48
|
+
# A brief description that sufficiently details the event.
|
|
49
|
+
event_other_specify: str | None = None
|
|
50
|
+
|
|
51
|
+
# The highest level of the MedDRA hierarchy, distinguished by anatomical or physiological system, etiology (disease origin) or purpose.
|
|
52
|
+
system_organ_class: SystemOrganClass | None = None
|
|
53
|
+
|
|
54
|
+
# Indicator to identify whether a participant exited the study prematurely due to the adverse event being described.
|
|
55
|
+
discontinuation_due_to_event: bool
|
|
56
|
+
|
|
57
|
+
# Days from enrollment date to date of onset of the adverse event.
|
|
58
|
+
days_to_onset_of_event: NonNegativeInt
|
|
59
|
+
|
|
60
|
+
# Days from enrollment date to date of resolution of the adverse event.
|
|
61
|
+
days_to_resolution_of_event: NonNegativeInt | None = None
|
|
62
|
+
|
|
63
|
+
# Indicates whether the adverse event was a serious adverse event (SAE).
|
|
64
|
+
serious_adverse_event: YNU
|
|
65
|
+
|
|
66
|
+
# Indicates whether the adverse event was a dose-limiting toxicity (DLT).
|
|
67
|
+
dose_limiting_toxicity: YNU
|
|
68
|
+
|
|
69
|
+
# Indicates if the adverse was attributable to the protocol as a whole or to an individual treatment.
|
|
70
|
+
attribution_cause: AttributionCause
|
|
71
|
+
|
|
72
|
+
# The code that indicates whether the adverse event is related to the treatment/intervention.
|
|
73
|
+
attribution_likelihood: AttributionLikelihood
|
|
74
|
+
|
|
75
|
+
# The individual therapy (therapy agent, radiotherapy, surgery, stem cell transplant) in the treatment that is attributed to the adverse event.
|
|
76
|
+
individual_therapy: str | None = None
|
|
77
|
+
|
|
78
|
+
@model_validator(mode="after")
|
|
79
|
+
def validate_term_and_code_cr(self) -> Self:
|
|
80
|
+
if not self.event_term and not self.event_code:
|
|
81
|
+
raise ValueError("Please provide event_term or event_code or both")
|
|
82
|
+
return self
|
|
83
|
+
|
|
84
|
+
@model_validator(mode="after")
|
|
85
|
+
def validate_event_other_specify_cr(self) -> Self:
|
|
86
|
+
if (
|
|
87
|
+
self.severity_grade_system == "CTCAE"
|
|
88
|
+
and is_ctcae_other_term(self.event_term)
|
|
89
|
+
and not self.event_other_specify
|
|
90
|
+
):
|
|
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'
|
|
93
|
+
)
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
@model_validator(mode="after")
|
|
97
|
+
def validate_system_organ_class_cr(self) -> Self:
|
|
98
|
+
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.")
|
|
100
|
+
return self
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from cidc_api.models.pydantic.base import Base
|
|
2
|
+
from cidc_api.models.types import ECOGScore, KarnofskyScore
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaselineClinicalAssessment(Base):
|
|
6
|
+
__data_category__ = "baseline_clinical_assessment"
|
|
7
|
+
__cardinality__ = "one"
|
|
8
|
+
|
|
9
|
+
# A unique internal identifier for the baseline clinical assessment
|
|
10
|
+
baseline_clinical_assessment_id: int | None = None
|
|
11
|
+
|
|
12
|
+
# The unique identifier for the associated participant
|
|
13
|
+
participant_id: str | None = None
|
|
14
|
+
|
|
15
|
+
# The numerical score that represents the functional capabilities of a participant at the
|
|
16
|
+
# enrollment date using the Eastern Cooperative Oncology Group Performance Status assessment.
|
|
17
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=88%20and%20ver_nr=5.1
|
|
18
|
+
ecog_score: ECOGScore | None = None
|
|
19
|
+
|
|
20
|
+
# Score from the Karnofsky Performance status scale, representing the functional capabilities of a participant
|
|
21
|
+
# at the enrollment date.
|
|
22
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2003853%20and%20ver_nr=4.2
|
|
23
|
+
karnofsky_score: KarnofskyScore | None = None
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Self
|
|
2
|
+
|
|
3
|
+
from pydantic import model_validator
|
|
4
|
+
|
|
5
|
+
from cidc_api.models.pydantic.base import Base
|
|
6
|
+
from cidc_api.models.types import ICD10CMCode, ICD10CMTerm
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Comorbidity(Base):
|
|
10
|
+
__data_category__ = "comorbidity"
|
|
11
|
+
__cardinality__ = "many"
|
|
12
|
+
|
|
13
|
+
# The unique internal identifier for the comorbidity record
|
|
14
|
+
comorbidity_id: int | None = None
|
|
15
|
+
|
|
16
|
+
# The unique internal identifier for the associated MedicalHistory record
|
|
17
|
+
medical_history_id: int | None = None
|
|
18
|
+
|
|
19
|
+
# The diagnosis, in humans, as captured in the tenth version of the
|
|
20
|
+
# International Classification of Disease (ICD-10-CM, the disease code subset of ICD-10).
|
|
21
|
+
comorbidity_code: ICD10CMCode | None = None
|
|
22
|
+
|
|
23
|
+
# The words from the tenth version of the International Classification of Disease (ICD-10-CM,
|
|
24
|
+
# the disease subset of ICD-10) used to identify the diagnosis in humans.
|
|
25
|
+
comorbidity_term: ICD10CMTerm | None = None
|
|
26
|
+
|
|
27
|
+
# A descriptive string that names or briefly describes the comorbidity.
|
|
28
|
+
comorbidity_other: str | None = None
|
|
29
|
+
|
|
30
|
+
@model_validator(mode="after")
|
|
31
|
+
def validate_code_or_term_or_other_cr(self) -> Self:
|
|
32
|
+
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".'
|
|
35
|
+
)
|
|
36
|
+
return self
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pydantic import NonNegativeInt
|
|
2
|
+
|
|
3
|
+
from cidc_api.models.pydantic.base import Base
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConsentGroup(Base):
|
|
7
|
+
__data_category__ = "consent_group"
|
|
8
|
+
__cardinality__ = "one"
|
|
9
|
+
|
|
10
|
+
# The unique internal identifier for the consent group record
|
|
11
|
+
consent_group_id: int | None = None
|
|
12
|
+
|
|
13
|
+
# The unique internal identifier for the associated Trial record
|
|
14
|
+
trial_id: str | None = None
|
|
15
|
+
|
|
16
|
+
# The version number of the trial dataset
|
|
17
|
+
version: str | None = None
|
|
18
|
+
|
|
19
|
+
# An abbreviated name for the consent group
|
|
20
|
+
consent_group_short_name: str
|
|
21
|
+
|
|
22
|
+
# The words or acronym which describe a set of study participants
|
|
23
|
+
# who have signed the same consent agreement and that will be included in the dbGaP repository.
|
|
24
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=14534329%20and%20ver_nr=1.00
|
|
25
|
+
consent_group_name: str
|
|
26
|
+
|
|
27
|
+
# A numeral or string of numerals used to identify the set of study participants who have signed the same consent
|
|
28
|
+
# agreement and that will be included in the dbGaP repository.
|
|
29
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=14534330%20and%20ver_nr=1.00
|
|
30
|
+
consent_group_number: NonNegativeInt
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from typing import Self, Annotated, List
|
|
2
|
+
|
|
3
|
+
from pydantic import PositiveInt, NonNegativeFloat, PositiveFloat, model_validator, field_validator, BeforeValidator
|
|
4
|
+
|
|
5
|
+
from cidc_api.models.pydantic.base import Base
|
|
6
|
+
from cidc_api.models.types import (
|
|
7
|
+
Sex,
|
|
8
|
+
Race,
|
|
9
|
+
Ethnicity,
|
|
10
|
+
HeightUnits,
|
|
11
|
+
WeightUnits,
|
|
12
|
+
BodySurfaceAreaUnits,
|
|
13
|
+
Occupation,
|
|
14
|
+
Education,
|
|
15
|
+
AgeAtEnrollmentUnits,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Demographic(Base):
|
|
20
|
+
__data_category__ = "demographic"
|
|
21
|
+
__cardinality__ = "one"
|
|
22
|
+
|
|
23
|
+
# The unique internal identifier for this demographic record
|
|
24
|
+
demographic_id: int | None = None
|
|
25
|
+
|
|
26
|
+
# The unique internal identifier for the associated participant
|
|
27
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=12220014%20and%20ver_nr=1
|
|
28
|
+
participant_id: str | None = None
|
|
29
|
+
|
|
30
|
+
# The age of the subject when the subject enrolled in the study.
|
|
31
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=15742605%20and%20ver_nr=1
|
|
32
|
+
age_at_enrollment: PositiveInt | None = None
|
|
33
|
+
|
|
34
|
+
# Unit of measurement for the age of the participant. e.g. "Years"
|
|
35
|
+
age_at_enrollment_units: AgeAtEnrollmentUnits | None = None
|
|
36
|
+
|
|
37
|
+
# Indicates whether the participant is 90 years old or over. (for PHI purposes)
|
|
38
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=15354920%20and%20ver_nr=1
|
|
39
|
+
age_90_or_over: bool
|
|
40
|
+
|
|
41
|
+
# A textual description of a person's sex at birth. e.g. "Male"
|
|
42
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=7572817%20and%20ver_nr=3
|
|
43
|
+
sex: Sex
|
|
44
|
+
|
|
45
|
+
# The race of the participant. e.g. "White"
|
|
46
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2192199%20and%20ver_nr=1
|
|
47
|
+
race: Annotated[List[Race], BeforeValidator(Base.split_list)]
|
|
48
|
+
|
|
49
|
+
# The ethnicity of the participant. e.g. "Hispanic or Latino"
|
|
50
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2192217%20and%20ver_nr=2
|
|
51
|
+
ethnicity: Ethnicity
|
|
52
|
+
|
|
53
|
+
# The number that describes the vertical distance of the participant at enrollment date.
|
|
54
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2179643%20and%20ver_nr=4
|
|
55
|
+
height: PositiveFloat
|
|
56
|
+
|
|
57
|
+
# Unit of measurement for the height of the participant at the enrollment date. e.g. "in"
|
|
58
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2538920%20and%20ver_nr=1
|
|
59
|
+
height_units: HeightUnits
|
|
60
|
+
|
|
61
|
+
# The mass of the participant's entire body at the enrollment date.
|
|
62
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2179689%20and%20ver_nr=4
|
|
63
|
+
weight: PositiveFloat
|
|
64
|
+
|
|
65
|
+
# Unit of measurement for the weight of the participant at the enrollment date. e.g. "kg"
|
|
66
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2630200%20and%20ver_nr=1
|
|
67
|
+
weight_units: WeightUnits
|
|
68
|
+
|
|
69
|
+
# The body mass index of the participant at the enrollment date.
|
|
70
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2006410%20and%20ver_nr=3
|
|
71
|
+
body_mass_index: PositiveFloat | None = None
|
|
72
|
+
|
|
73
|
+
# A decimal number that represents the measure of the 2-dimensional extent of the body surface (i.e., the skin) of the participant at the enrollment date.
|
|
74
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=6606197%20and%20ver_nr=1
|
|
75
|
+
body_surface_area: PositiveFloat | None = None
|
|
76
|
+
|
|
77
|
+
# Unit of measurement for body surface area of the participant at the enrollment date. e.g. "m2"
|
|
78
|
+
# https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=15114329%20and%20ver_nr=1
|
|
79
|
+
body_surface_area_units: BodySurfaceAreaUnits | None = None
|
|
80
|
+
|
|
81
|
+
# The occupation/job category of the participant. e.g. "Manager"
|
|
82
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=6617540%20and%20ver_nr=1
|
|
83
|
+
occupation: Occupation | None = None
|
|
84
|
+
|
|
85
|
+
# The amount of earnings in USD made by the participant's family in a year.
|
|
86
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=14834966%20and%20ver_nr=1
|
|
87
|
+
income: NonNegativeFloat | None = None
|
|
88
|
+
|
|
89
|
+
# The highest level of education attained by the participant. e.g. "Bachelor's Degree"
|
|
90
|
+
# CDE: https://cadsr.cancer.gov/onedata/dmdirect/NIH/NCI/CO/CDEDD?filter=CDEDD.ITEM_ID=2681552%20and%20ver_nr=1
|
|
91
|
+
highest_level_of_education: Education | None = None
|
|
92
|
+
|
|
93
|
+
@model_validator(mode="after")
|
|
94
|
+
def validate_age_at_enrollment_cr(self) -> Self:
|
|
95
|
+
if self.age_90_or_over:
|
|
96
|
+
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.'
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
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
|
+
)
|
|
105
|
+
return self
|
|
106
|
+
|
|
107
|
+
@model_validator(mode="after")
|
|
108
|
+
def validate_age_at_enrollment_value(self) -> Self:
|
|
109
|
+
if not self.age_90_or_over:
|
|
110
|
+
age_in_years = (
|
|
111
|
+
self.age_at_enrollment if self.age_at_enrollment_units == "Years" else self.age_at_enrollment / 365.25
|
|
112
|
+
)
|
|
113
|
+
if age_in_years >= 90:
|
|
114
|
+
raise ValueError('"age_at_enrollment" cannot represent a value greater than 90 years of age.')
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
@model_validator(mode="after")
|
|
118
|
+
def validate_body_surface_area_units_cr(self) -> Self:
|
|
119
|
+
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.'
|
|
122
|
+
)
|
|
123
|
+
return self
|