From 2f8477b5bf05298966a9f8cd6a45884d58cf661f Mon Sep 17 00:00:00 2001 From: Alexis Lucattini Date: Fri, 4 Oct 2024 14:32:36 +1000 Subject: [PATCH] Friday changes: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PierianDx * Added pieriandx pipeline manager (monitors runs and sends events once the reports are generated) * Added nails for stacky ## Workflow Type Renames * wgtsQc to wgts-qc * tumor_normal to tumor-normal ## ICAv2 Resilience * Allow a ‘rerun’ of a workflow execution if the analysis id doesn’t exist (but the portal id is in the database). * Reduce concurrency in cttsov2 copy jobs to 5 * Reduce concurrency in bsshfastqcopy jobs to 5 ## Metadata * Refactored metadata tools to match new metadata manager structure in dev * Made metadata mapper lambda construct that allows user to easily collect an orcabus id from an internal id (library id / subject id / project id etc) and vice versa. ## Showers Don’t produce subject based showers. ## Stacky Refactored stacky services to not register subjects / instruments, libraries only, use a scan to find other libraries that have the same subject / on same instrument instead. Reduces number of components within the stack. --- .gitattributes | 1 + config/config.ts | 7 + config/constants.ts | 182 +- config/stacks/pierianDxPipelineManager.ts | 71 + config/stacks/stackyMcStackFace.ts | 26 +- .../index.ts | 2 +- ...workflow_and_raise_internal_event.asl.json | 26 +- ...t_to_ready_step_function_template.asl.json | 202 - .../icav2-copy-files-batch/index.ts | 6 +- .../copy_files_batch_sfn.asl.json | 3 +- .../check_or_launch_job_lambda.py | 6 +- .../get_metadata_objects_from_samplesheet.py | 1177 ++-- .../components/python-lambda-layer/index.ts | 5 +- .../python-lambda-metadata-mapper/index.ts | 94 + .../map_metadata_py/map_metadata.py | 446 ++ .../src/metadata_tools/__init__.py | 67 +- .../src/metadata_tools/utils/aws_helpers.py | 2 - .../metadata_tools/utils/contact_helpers.py | 50 + .../src/metadata_tools/utils/globals.py | 8 + .../utils/individual_helpers.py | 60 + .../metadata_tools/utils/library_helpers.py | 50 +- .../metadata_tools/utils/project_helpers.py | 50 + .../metadata_tools/utils/sample_helpers.py | 70 + .../metadata_tools/utils/specimen_helpers.py | 69 - .../metadata_tools/utils/subject_helpers.py | 62 +- .../Readme.md | 0 ...techange_input_maker_step_function_sfn.png | Bin .../index.ts | 74 +- ...fill_placeholders_in_event_payload_data.py | 0 .../requirements.txt | 0 ..._parameters_from_ssm_sfn_template.asl.json | 6 +- ...rate_ready_step_function_template.asl.json | 80 + .../index.ts | 9 - ...trunstatechange_preamble_template.asl.json | 19 +- .../deploy/index.ts | 26 + .../stacky-mcstackface-dynamodb/index.ts | 11 + .../stateful/statefulStackCollectionClass.ts | 17 + .../bssh-icav2-fastq-copy-manager/Readme.md | 2 +- .../constructs/cttsov2-icav2-manager/index.ts | 57 +- .../cttso-v2-pipeline-manager/deploy/stack.ts | 132 +- .../check_num_running_sfns.py | 88 + .../get_random_number_py/get_random_number.py | 33 + .../set_cttso_v2_nf_inputs.asl.json | 74 +- .../pieriandx-pipeline-manager/Readme.md | 247 + .../deploy/constructs/lambda_layer.ts | 40 + ...andx_launch_case_creation_step_function.ts | 67 + ...h_informaticsjob_creation_step_function.ts | 58 + ...nch_sequencerrun_creation_step_function.ts | 70 + .../pieriandx_launch_step_function.ts | 161 + .../pieriandx_monitor_runs_step_function.ts | 116 + .../deploy/index.ts | 441 ++ .../images/case_overview.png | Bin 0 -> 58423 bytes .../images/informaticsjob_launch_overview.png | Bin 0 -> 69252 bytes .../images/launch_overview.png | Bin 0 -> 129007 bytes .../images/sequencerrun_overview.png | Bin 0 -> 96617 bytes .../lambdas/generate_case_py/generate_case.py | 225 + .../generate_informaticsjob.py | 90 + .../generate_output_data_payload.py | 183 + .../generate_pieriandx_objects.py | 623 ++ .../generate_samplesheet.py | 115 + .../generate_sequencerrun_case.py | 90 + .../get_informaticsjob_and_report_status.py | 301 + .../upload_pieriandx_sample_data_to_s3.py | 109 + .../layers/pyproject.toml | 39 + .../src/pieriandx_pipeline_tools/__init__.py | 0 .../pieriandx_classes/__init__.py | 90 + .../pieriandx_classes/case.py | 17 + .../pieriandx_classes/dag.py | 10 + .../pieriandx_classes/data_file.py | 68 + .../pieriandx_classes/disease.py | 10 + .../pieriandx_classes/informaticsjob.py | 8 + .../pieriandx_classes/medical_facility.py | 10 + .../medical_record_number.py | 10 + .../pieriandx_classes/physician.py | 10 + .../pieriandx_classes/sequencerrun.py | 8 + .../pieriandx_classes/specimen.py | 20 + .../specimen_sequencer_info.py | 8 + .../pieriandx_enums/__init__.py | 0 .../pieriandx_enums/ethnicity.py | 10 + .../pieriandx_enums/gender.py | 13 + .../pieriandx_enums/race.py | 14 + .../pieriandx_enums/sample_type.py | 10 + .../pieriandx_enums/sequencing_type.py | 8 + .../pieriandx_enums/specimen_type.py | 26 + .../pieriandx_lookup/__init__.py | 0 .../pieriandx_lookup/get_disease_label.py | 53 + .../pieriandx_lookup/get_specimen_label.py | 58 + .../snomed_ct_disease_tree.json.gz | 3 + .../snomed_ct_specimen_type.json.gz | 3 + .../pieriandx_models/__init__.py | 0 .../pieriandx_models/case_creation.py | 79 + .../pieriandx_models/dag.py | 19 + .../pieriandx_models/disease.py | 28 + .../informaticsjob_creation.py | 28 + .../pieriandx_models/medical_facility.py | 20 + .../pieriandx_models/medical_record_number.py | 14 + .../pieriandx_models/physician.py | 18 + .../pieriandx_models/sequencerrun_creation.py | 23 + .../pieriandx_models/specimen.py | 94 + .../specimen_sequencer_info.py | 33 + .../utils/compression_helpers.py | 60 + .../utils/lambda_helpers.py | 27 + .../utils/pieriandx_helpers.py | 57 + .../utils/s3_helpers.py | 35 + .../utils/samplesheet_helpers.py | 43 + .../utils/secretsmanager_helpers.py | 70 + .../launch_pieriandx.asl.json | 451 ++ .../launch_pieriandx_case_creation.asl.json | 114 + ...pieriandx_informaticsjob_creation.asl.json | 119 + ...h_pieriandx_sequencerrun_creation.asl.json | 280 + .../monitor_runs.asl.json | 277 + .../part_1/samplesheet-event-shower/index.ts | 6 +- .../generate_event_data_objects.py | 5775 +++++++++-------- .../requirements.txt | 2 + ...lesheet_event_shower_sfn_template.asl.json | 236 +- .../fastq-list-rows-event-shower/index.ts | 8 +- .../generate_event_data_objects.py | 1998 +++--- ...ist_row_event_shower_sfn_template.asl.json | 84 +- .../glue-constructs/elmer/index.ts | 39 +- .../index.ts | 83 +- ...to_bssh_fastq_copy_draft_template.asl.json | 51 +- .../index.ts | 99 - .../glue-constructs/gorilla/index.ts | 27 +- .../index.ts | 66 +- ...terop_qc_draft_event_sfn_template.asl.json | 55 +- .../index.ts | 104 - .../glue-constructs/index.ts | 131 +- .../glue-constructs/jb-weld/index.ts | 109 +- .../index.ts | 89 - ...v2_instrument_run_db_sfn_template.asl.json | 31 - .../initialise-cttsov2-library-dbs/index.ts | 0 ...e_cttsov2_library_db_sfn_template.asl.json | 56 +- .../populate-fastq-list-row-dbs/index.ts | 0 ...pdate_fastq_list_row_sfn_template.asl.json | 0 .../Readme.md | 0 .../index.ts | 81 +- .../build_cttso_v2_samplesheet.py | 0 ...cttsov2_draft_events_sfn_template.asl.json | 108 +- .../part_5/cttsov2-draft-to-ready/index.ts | 110 - .../glue-constructs/kwik/index.ts | 84 +- .../index.ts | 88 - ...ts_instrument_run_db_sfn_template.asl.json | 31 - .../initialise-wgts-library-dbs/index.ts | 0 ...lise_wgts_library_db_sfn_template.asl.json | 41 +- .../populate-fastq-list-row-dbs/index.ts | 0 ...pdate_fastq_list_row_sfn_template.asl.json | 3 +- .../Readme.md | 0 .../index.ts | 96 +- .../generate_event_data.py | 0 ...ete_to_wgts_qc_draft_sfn_template.asl.json | 108 +- .../index.ts | 2 +- ...ect_qc_metrics_from_alignment_directory.py | 0 .../requirements.txt | 0 .../generate_event_data_objects.py | 6 +- .../wgts_qc_complete_sfn_template.asl.json | 8 +- .../library-qc-complete-event/index.ts | 0 .../sum_coverages_for_rgids.py | 0 ...ibrary_qc_complete_event_template.asl.json | 8 +- .../part_5/wgts-qc-draft-to-ready/index.ts | 106 - .../glue-constructs/loctite/index.ts | 139 +- .../initialise-tn-library-dbs/index.ts | 0 ...ialise_tn_library_db_sfn_template.asl.json | 40 +- .../part_1/initialise-tn-subject-dbs/index.ts | 81 - ...ialise_tn_subject_db_sfn_template.asl.json | 57 - .../update-fastq-list-rows-dbs/index.ts | 0 ...d_fastq_list_rows_db_sfn_template.asl.json | 0 .../index.ts | 133 +- .../find_complement_library_pair.py | 108 + .../generate_draft_event_payload.py | 2 + ...complete_to_tn_draft_sfn_template.asl.json | 129 +- .../index.ts | 81 - ...ow_qc_complete_to_db_sfn_template.asl.json | 61 - .../find_complement_library_pair.py | 58 - .../loctite/part_6/tn-draft-to-ready/index.ts | 105 - .../glue-constructs/mod-podge/index.ts | 90 +- ...alise_wts_library_db_sfn_template.asl.json | 21 +- .../library-qc-complete-to-wts}/index.ts | 96 +- .../generate_draft_event_payload.py | 0 ...omplete_to_wts_draft_sfn_template.asl.json | 49 +- .../index.ts | 81 - ...ow_qc_complete_to_db_sfn_template.asl.json | 61 - .../part_5/wts-draft-to-ready/index.ts | 105 - .../glue-constructs/nails/index.ts | 88 + .../part_1/initialise-library-db/index.ts | 112 + ...ore_cttsov2_metadata_sfn_template.asl.json | 48 + .../index.ts | 267 + .../generate_portal_run_id.py | 27 + .../get_data_from_redcap.py | 339 + .../get_data_from_redcap_py/requirements.txt | 2 + .../get_deidentified_case_metadata.py | 159 + .../requirements.txt | 1 + .../get_identified_case_metadata.py | 163 + .../requirements.txt | 1 + .../get_pieriandx_data_files.py | 181 + .../requirements.txt | 1 + .../get_project_info_py/get_project_info.py | 199 + ...ieriandx_ready_event_sfn_template.asl.json | 390 ++ .../glue-constructs/pva/index.ts | 100 +- .../initialise-umccrise-library-dbs/index.ts | 21 +- ..._umccrise_library_db_sfn_template.asl.json | 98 + .../initialise-umccrise-subject-dbs/index.ts | 81 - ..._umccrise_subject_db_sfn_template.asl.json | 57 - ..._umccrise_library_db_sfn_template.asl.json | 190 - .../tn-complete-to-umccrise-draft/index.ts | 107 +- .../generate_draft_event_payload.py | 4 +- ...te_to_umccrise_draft_sfn_template.asl.json | 62 +- .../update-fastq-list-rows-dbs/index.ts | 87 - ...d_fastq_list_rows_db_sfn_template.asl.json | 61 - .../part_5/umccrise-draft-to-ready/index.ts | 105 - .../glue-constructs/roket/index.ts | 68 +- .../initialise-rnasum-library-dbs/index.ts | 127 + .../initialise_library_db_template.asl.json | 98 + .../initialise-rnasum-subject-dbs/index.ts | 81 - ...se_rnasum_subject_db_sfn_template.asl.json | 57 - .../index.ts | 100 +- .../generate_workflow_inputs.py | 4 +- ..._and_wts_complete_to_rnasum_draft.asl.json | 162 +- .../part_3/rnasum-draft-to-ready/index.ts | 105 - .../statelessStackCollectionClass.ts | 15 + 219 files changed, 15709 insertions(+), 8186 deletions(-) create mode 100644 config/stacks/pierianDxPipelineManager.ts delete mode 100644 lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/workflowrunstatechange_draft_to_ready_step_function_template.asl.json create mode 100644 lib/workload/components/python-lambda-metadata-mapper/index.ts create mode 100644 lib/workload/components/python-lambda-metadata-mapper/map_metadata_py/map_metadata.py create mode 100644 lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/contact_helpers.py create mode 100644 lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/individual_helpers.py create mode 100644 lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/project_helpers.py create mode 100644 lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/sample_helpers.py delete mode 100644 lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/specimen_helpers.py rename lib/workload/components/{event-workflowdraftrunstatechange-to-workflowrunstatechange-ready => sfn-generate-workflowrunstatechange-ready-event}/Readme.md (100%) rename lib/workload/components/{event-workflowdraftrunstatechange-to-workflowrunstatechange-ready => sfn-generate-workflowrunstatechange-ready-event}/images/workflowrunstatechange_input_maker_step_function_sfn.png (100%) rename lib/workload/components/{event-workflowdraftrunstatechange-to-workflowrunstatechange-ready => sfn-generate-workflowrunstatechange-ready-event}/index.ts (69%) rename lib/workload/components/{event-workflowdraftrunstatechange-to-workflowrunstatechange-ready => sfn-generate-workflowrunstatechange-ready-event}/lambdas/fill_placeholders_in_event_payload_data_py/fill_placeholders_in_event_payload_data.py (100%) rename lib/workload/components/{event-workflowdraftrunstatechange-to-workflowrunstatechange-ready => sfn-generate-workflowrunstatechange-ready-event}/lambdas/fill_placeholders_in_event_payload_data_py/requirements.txt (100%) rename lib/workload/components/{event-workflowdraftrunstatechange-to-workflowrunstatechange-ready => sfn-generate-workflowrunstatechange-ready-event}/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json (98%) create mode 100644 lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/workflowrunstatechange_generate_ready_step_function_template.asl.json create mode 100644 lib/workload/stateful/stacks/pieriandx-pipeline-dynamo-db/deploy/index.ts create mode 100644 lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/check_num_running_sfns_py/check_num_running_sfns.py create mode 100644 lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/get_random_number_py/get_random_number.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/Readme.md create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/lambda_layer.ts create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_case_creation_step_function.ts create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_informaticsjob_creation_step_function.ts create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_sequencerrun_creation_step_function.ts create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_step_function.ts create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_monitor_runs_step_function.ts create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/index.ts create mode 100755 lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/case_overview.png create mode 100755 lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/informaticsjob_launch_overview.png create mode 100755 lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/launch_overview.png create mode 100755 lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/sequencerrun_overview.png create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_case_py/generate_case.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_informaticsjob_py/generate_informaticsjob.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_output_data_payload_py/generate_output_data_payload.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_pieriandx_objects_py/generate_pieriandx_objects.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_samplesheet_py/generate_samplesheet.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_sequencerrun_case_py/generate_sequencerrun_case.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/get_informaticsjob_and_report_status_py/get_informaticsjob_and_report_status.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/upload_pieriandx_sample_data_to_s3_py/upload_pieriandx_sample_data_to_s3.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/pyproject.toml create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/__init__.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/__init__.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/case.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/dag.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/data_file.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/disease.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/informaticsjob.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_facility.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_record_number.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/physician.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/sequencerrun.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen_sequencer_info.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/__init__.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/ethnicity.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/gender.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/race.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sample_type.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sequencing_type.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/specimen_type.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/__init__.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_disease_label.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_specimen_label.py create mode 100755 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_disease_tree.json.gz create mode 100755 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_specimen_type.json.gz create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/__init__.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/case_creation.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/dag.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/disease.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/informaticsjob_creation.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_facility.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_record_number.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/physician.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/sequencerrun_creation.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen_sequencer_info.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/compression_helpers.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/lambda_helpers.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/pieriandx_helpers.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/s3_helpers.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/samplesheet_helpers.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/secretsmanager_helpers.py create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx.asl.json create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_case_creation.asl.json create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_informaticsjob_creation.asl.json create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_sequencerrun_creation.asl.json create mode 100644 lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/monitor_runs.asl.json create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/requirements.txt rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/{bclconvert-succeeded-to-bssh-fastq-copy-draft => bclconvert-succeeded-to-bssh-fastq-copy}/index.ts (69%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/{bclconvert-succeeded-to-bssh-fastq-copy-draft => bclconvert-succeeded-to-bssh-fastq-copy}/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json (78%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_2/bssh-fastq-copy-manager-draft-to-ready/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_2/bclconvert-interop-qc-input-maker/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/step_functions_templates/initialise_cttsov2_instrument_run_db_sfn_template.asl.json rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_2 => part_1}/initialise-cttsov2-library-dbs/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_2 => part_1}/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json (73%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_3 => part_2}/populate-fastq-list-row-dbs/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_3 => part_2}/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft => part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready}/Readme.md (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft => part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready}/index.ts (73%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft => part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready}/lambdas/build_cttsov2_samplesheet_py/build_cttso_v2_samplesheet.py (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/{part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft => part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready}/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json (76%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_5/cttsov2-draft-to-ready/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/step_functions_templates/initialise_wgts_instrument_run_db_sfn_template.asl.json rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_2 => part_1}/initialise-wgts-library-dbs/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_2 => part_1}/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json (78%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_3 => part_2}/populate-fastq-list-row-dbs/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_3 => part_2}/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json (96%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft => part_3/fastq-list-rows-shower-complete-to-wgts-qc}/Readme.md (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft => part_3/fastq-list-rows-shower-complete-to-wgts-qc}/index.ts (72%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft => part_3/fastq-list-rows-shower-complete-to-wgts-qc}/lambdas/generate_event_data_py/generate_event_data.py (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft => part_3/fastq-list-rows-shower-complete-to-wgts-qc}/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json (77%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_6 => part_4}/push-fastq-list-row-qc-complete-event/index.ts (99%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_6 => part_4}/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/collect_qc_metrics_from_alignment_directory.py (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_6 => part_4}/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/requirements.txt (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_6 => part_4}/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py (90%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_6 => part_4}/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json (95%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_7 => part_5}/library-qc-complete-event/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_7 => part_5}/library-qc-complete-event/lambdas/sum_coverages_for_rgids_py/sum_coverages_for_rgids.py (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/{part_7 => part_5}/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json (96%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/wgts-qc-draft-to-ready/index.ts rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_2 => part_1}/initialise-tn-library-dbs/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_2 => part_1}/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json (83%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/step_functions_templates/initialise_tn_subject_db_sfn_template.asl.json rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_3 => part_2}/update-fastq-list-rows-dbs/index.ts (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_3 => part_2}/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_5/library-qc-complete-db-to-tn-draft => part_3/library-qc-complete-db-to-tn-ready}/index.ts (53%) create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/find_complement_library_pair_py/find_complement_library_pair.py rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_5/library-qc-complete-db-to-tn-draft => part_3/library-qc-complete-db-to-tn-ready}/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py (95%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/{part_5/library-qc-complete-db-to-tn-draft => part_3/library-qc-complete-db-to-tn-ready}/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json (80%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/find_complement_library_pair_py/find_complement_library_pair.py delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_6/tn-draft-to-ready/index.ts rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/{part_4/library-qc-complete-to-wts-draft => part_3/library-qc-complete-to-wts}/index.ts (61%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/{part_4/library-qc-complete-to-wts-draft => part_3/library-qc-complete-to-wts}/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py (100%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/{part_4/library-qc-complete-to-wts-draft => part_3/library-qc-complete-to-wts}/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json (79%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_5/wts-draft-to-ready/index.ts create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/index.ts create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/index.ts create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/step_functions_templates/store_cttsov2_metadata_sfn_template.asl.json create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/index.ts create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/generate_portal_run_id_py/generate_portal_run_id.py create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/get_data_from_redcap.py create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/requirements.txt create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/get_deidentified_case_metadata.py create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/requirements.txt create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/get_identified_case_metadata.py create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/requirements.txt create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/get_pieriandx_data_files.py create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/requirements.txt create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_project_info_py/get_project_info.py create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/step_functions_templates/cttso_v2_outputs_to_pieriandx_ready_event_sfn_template.asl.json rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/{part_2 => part_1}/initialise-umccrise-library-dbs/index.ts (82%) create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/step_functions_templates/initialise_umccrise_subject_db_sfn_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/{part_4 => part_2}/tn-complete-to-umccrise-draft/index.ts (58%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/{part_4 => part_2}/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py (93%) rename lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/{part_4 => part_2}/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json (66%) delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_5/umccrise-draft-to-ready/index.ts create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/index.ts create mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/step_functions_templates/initialise_library_db_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/index.ts delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/step_functions_templates/initialise_rnasum_subject_db_sfn_template.asl.json delete mode 100644 lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_3/rnasum-draft-to-ready/index.ts diff --git a/.gitattributes b/.gitattributes index 937c0eb37..baa1a1850 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ /.yarn/releases/** binary /.yarn/plugins/** binary +*.json.gz filter=lfs diff=lfs merge=lfs -text diff --git a/config/config.ts b/config/config.ts index f7dd0a45a..fadbf0299 100644 --- a/config/config.ts +++ b/config/config.ts @@ -48,6 +48,11 @@ import { getRnasumIcav2PipelineTableStackProps, } from './stacks/rnasumPipelineManager'; import { getFmAnnotatorProps } from './stacks/fmAnnotator'; +import { + getPierianDxPipelineManagerStackProps, + getPierianDxPipelineTableStackProps, +} from './stacks/pierianDxPipelineManager'; +import { PieriandxPipelineManagerStack } from '../lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy'; interface EnvironmentConfig { name: string; @@ -83,6 +88,7 @@ export const getEnvironmentConfig = (stage: AppStage): EnvironmentConfig | null rnasumIcav2PipelineTableStackProps: getRnasumIcav2PipelineTableStackProps(), BclConvertTableStackProps: getBclConvertManagerTableStackProps(stage), stackyStatefulTablesStackProps: getStatefulGlueStackProps(), + pierianDxPipelineTableStackProps: getPierianDxPipelineTableStackProps(), }, statelessConfig: { metadataManagerStackProps: getMetadataManagerStackProps(stage), @@ -98,6 +104,7 @@ export const getEnvironmentConfig = (stage: AppStage): EnvironmentConfig | null wtsIcav2PipelineManagerStackProps: getWtsIcav2PipelineManagerStackProps(stage), umccriseIcav2PipelineManagerStackProps: getUmccriseIcav2PipelineManagerStackProps(stage), rnasumIcav2PipelineManagerStackProps: getRnasumIcav2PipelineManagerStackProps(stage), + pieriandxPipelineManagerStackProps: getPierianDxPipelineManagerStackProps(stage), eventSchemaStackProps: getEventSchemaStackProps(), dataSchemaStackProps: getDataSchemaStackProps(), bclConvertManagerStackProps: getBclConvertManagerStackProps(stage), diff --git a/config/constants.ts b/config/constants.ts index cc98f88e7..eb8d47cb5 100644 --- a/config/constants.ts +++ b/config/constants.ts @@ -291,7 +291,7 @@ External resources required by the wgtsqc Stack // Deployed under dev/stg/prod export const wgtsQcIcav2PipelineIdSSMParameterPath = '/icav2/umccr-prod/wgts_qc_4.2.4_pipeline_id'; // 03689516-b7f8-4dca-bba9-8405b85fae45 -export const wgtsQcIcav2PipelineWorkflowType = 'wgtsQc'; +export const wgtsQcIcav2PipelineWorkflowType = 'wgts-qc'; export const wgtsQcIcav2PipelineWorkflowTypeVersion = '4.2.4'; export const wgtsQcIcav2ServiceVersion = '2024.07.01'; @@ -324,7 +324,7 @@ TN Stateless stack // Deployed under dev/stg/prod export const tnIcav2PipelineIdSSMParameterPath = '/icav2/umccr-prod/tumor_normal_4.2.4_pipeline_id'; // 0f5575bc-6cf8-4a90-a80e-05088aae8ed7 -export const tnIcav2PipelineWorkflowType = 'tumor_normal'; +export const tnIcav2PipelineWorkflowType = 'tumor-normal'; export const tnIcav2PipelineWorkflowTypeVersion = '4.2.4'; export const tnIcav2ServiceVersion = '2024.07.01'; export const tnIcav2ReadyEventSource = 'orcabus.workflowmanager'; @@ -500,6 +500,183 @@ export const rnasumIcav2EventDetailType = 'WorkflowRunStateChange'; export const rnasumStateMachinePrefix = 'rnasumSfn'; export const rnasumDefaultDatasetVersion = 'PANCAN'; +/* +Ora Compression Stateless Stack +*/ + +// Deployed in dev +// export const oraCompressionTarSSMParameterPath = '/icav2/umccr-prod/ora_compression_tar_uri'; // icav2://reference-data/dragen-ora/v2/ora_reference_v2.tar.gz + +/* +PierianDx Stateful and Stateless stacks +*/ +export const pieriandxPrefix = 'pieriandx'; +export const pieriandxTriggerLaunchSource = 'orcabus.workflowmanager'; +export const pieriandxWorkflowName = 'pieriandx'; +export const pieriandxWorkflowVersion = '2.1'; +export const pieriandxDetailType = 'WorkflowRunStateChange'; +export const pieriandxEventSource = 'orcabus.pieriandx'; +export const pieriandxPayloadVersion = '2024.10.01'; +export const pieriandxDynamodbTable = 'PierianDxPipelineDynamoDbTable'; + +/* +[ + { + "panelName": "main", + "panelId": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne" // pragma: allowlist secret + }, + { + "panelName": "subpanel", + "panelId": "tso500_DRAGEN_ctDNA_v2_1_subpanel_Universityofmelbourne" // pragma: allowlist secret + } +] + +*/ +export const pieriandxDefaultPanelName = 'main'; +export const pieriandxPanelMapSsmParameterPath = '/umccr/orcabus/stateful/pieriandx/panel_map'; + +/* +[ + { + "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", + "dagDescription": "tso500_ctdna_workflow" + } +] +*/ +export const pieriandxDefaultDagName = 'cromwell_tso500_ctdna_workflow_1.0.4'; +export const pieriandxDagSsmParameterPath = '/umccr/orcabus/stateful/pieriandx/dag_map'; + +/* +"s3://pdx-cgwxfer-test/melbournetest" // development +"s3://pdx-cgwxfer-test/melbournetest" // staging +"s3://pdx-cgwxfer/melbourne" // production +*/ +export const pieriandxS3SequencerRunRootSsmParameterPath = + '/umccr/orcabus/pieriandx/s3_sequencer_run_root'; + +/* +"services@umccr.org" // development +"services@umccr.org" // staging +"services@umccr.org" // production +*/ +export const pieriandxUserEmailSsmParameterPath = '/umccr/orcabus/pieriandx/user_email'; + +/* +"melbournetest" // development +"melbournetest" // staging +"melbourne" // production +*/ +export const pieriandxInstitutionSsmParameterPath = '/umccr/orcabus/pieriandx/institution'; + +/* +"https://app.uat.pieriandx.com/cgw-api/v2.0.0" // development +"https://app.uat.pieriandx.com/cgw-api/v2.0.0" // staging +"https://app.pieriandx.com/cgw-api/v2.0.0" // production +*/ +export const pieriandxBaseUrlSsmParameterPath = '/umccr/orcabus/pieriandx/base_url'; + +// Constant for all environments +export const pieriandxAuthTokeSsmParameterPath = 'collectPierianDxAccessToken'; + +// Secret name for PierianDx S3 credentials (test bucket for dev and staging, prod bucket for prod) +export const pieriandxS3CredentialsSecretsManagerId = 'PierianDx/S3Credentials'; // pragma: allowlist secret + +/* +[ + { + "project_id": "PO", + "panel": "subpanel", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "COUMN", + "panel": "subpanel", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "CUP", + "panel": "main", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code": 285645000 + }, + { + "project_id": "PPGL", + "panel": "main", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "MESO", + "panel": "subpanel", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "OCEANiC", + "panel": "subpanel", + "sample_type": "patientcare", + "is_identified": "deidentified", + "default_snomed_disease_code": null + }, + { + "project_id": "SOLACE2", + "panel": "main", + "sample_type": "patientcare", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + }, + { + "project_id": "IMPARP", + "panel": "main", + "sample_type": "patientcare", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + }, + { + "project_id": "Control", + "panel": "main", + "sample_type": "validation", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + }, + { + "project_id": "QAP", + "panel": "subpanel", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "iPredict2", + "panel": "subpanel", + "sample_type": "patientcare", + "is_identified": "identified", + "default_snomed_disease_code":null + }, + { + "project_id": "*", + "panel": "main", + "sample_type": "patientcare", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + } +] +*/ +export const pieriandxProjectInfoSsmParameterPath = '/umccr/orcabus/pieriandx/project_info'; + +export const redcapLambdaFunctionName: Record = { + [AppStage.BETA]: 'redcap-apis-dev-lambda-function', + [AppStage.GAMMA]: 'redcap-apis-stg-lambda-function', + [AppStage.PROD]: 'redcap-apis-prod-lambda-function', +}; + // Mock Stack export const mockEventBusName = eventBusName; export const mockInstrumentRunTableName = 'stacky-instrument-run-table'; @@ -510,6 +687,7 @@ export const mockTnGlueTableName = 'stacky-tn-glue-table'; export const mockWtsGlueTableName = 'stacky-wts-glue-table'; export const mockUmccriseGlueTableName = 'stacky-umccrise-glue-table'; export const mockRnasumGlueTableName = 'stacky-rnasum-glue-table'; +export const mockPierianDxGlueTableName = 'stacky-pieriandx-glue-table'; export const mockWorkflowManagerTableName = 'stacky-workflow-manager-table'; // { diff --git a/config/stacks/pierianDxPipelineManager.ts b/config/stacks/pierianDxPipelineManager.ts new file mode 100644 index 000000000..b1f2b31a7 --- /dev/null +++ b/config/stacks/pierianDxPipelineManager.ts @@ -0,0 +1,71 @@ +import { + AppStage, + eventBusName, + icav2AccessTokenSecretName, + pieriandxAuthTokeSsmParameterPath, + pieriandxBaseUrlSsmParameterPath, + pieriandxDagSsmParameterPath, + pieriandxDefaultDagName, + pieriandxDefaultPanelName, + pieriandxDetailType, + pieriandxDynamodbTable, + pieriandxEventSource, + pieriandxInstitutionSsmParameterPath, + pieriandxPanelMapSsmParameterPath, + pieriandxPayloadVersion, + pieriandxPrefix, + pieriandxS3CredentialsSecretsManagerId, + pieriandxS3SequencerRunRootSsmParameterPath, + pieriandxTriggerLaunchSource, + pieriandxUserEmailSsmParameterPath, + pieriandxWorkflowName, + pieriandxWorkflowVersion, +} from '../constants'; +import { PierianDxPipelineTableConfig } from '../../lib/workload/stateful/stacks/pieriandx-pipeline-dynamo-db/deploy'; +import { PierianDxPipelineManagerConfig } from '../../lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy'; + +// Stateful +export const getPierianDxPipelineTableStackProps = (): PierianDxPipelineTableConfig => { + return { + dynamodbTableName: pieriandxDynamodbTable, + }; +}; + +// Stateless +export const getPierianDxPipelineManagerStackProps = ( + stage: AppStage +): PierianDxPipelineManagerConfig => { + return { + /* DynamoDB Table */ + dynamodbTableName: pieriandxDynamodbTable, + /* Workflow knowledge */ + workflowName: pieriandxWorkflowName, + workflowVersion: pieriandxWorkflowVersion, + /* Default values */ + defaultDagVersion: pieriandxDefaultDagName, + defaultPanelName: pieriandxDefaultPanelName, + /* Secrets */ + /* ICAv2 Pipeline analysis essentials */ + icav2AccessTokenSecretId: icav2AccessTokenSecretName[stage], // "/icav2/umccr-prod/service-production-jwt-token-secret-arn" + pieriandxS3AccessTokenSecretId: pieriandxS3CredentialsSecretsManagerId, // "/pieriandx/s3AccessCredentials" + /* SSM Parameters */ + dagSsmParameterPath: pieriandxDagSsmParameterPath, + panelNameSsmParameterPath: pieriandxPanelMapSsmParameterPath, + s3SequencerRunRootSsmParameterPath: pieriandxS3SequencerRunRootSsmParameterPath, + /* + Pieriandx specific parameters + */ + pieriandxUserEmailSsmParameterPath: pieriandxUserEmailSsmParameterPath, + pieriandxInstitutionSsmParameterPath: pieriandxInstitutionSsmParameterPath, + pieriandxBaseUrlSsmParameterPath: pieriandxBaseUrlSsmParameterPath, + pieriandxAuthTokenCollectionLambdaFunctionName: pieriandxAuthTokeSsmParameterPath, + /* Event info */ + eventDetailType: pieriandxDetailType, + eventBusName: eventBusName, + eventSource: pieriandxEventSource, + payloadVersion: pieriandxPayloadVersion, + triggerLaunchSource: pieriandxTriggerLaunchSource, + /* Custom */ + prefix: pieriandxPrefix, + }; +}; diff --git a/config/stacks/stackyMcStackFace.ts b/config/stacks/stackyMcStackFace.ts index 93589e33f..2ea67a19d 100644 --- a/config/stacks/stackyMcStackFace.ts +++ b/config/stacks/stackyMcStackFace.ts @@ -16,20 +16,18 @@ import { mockWtsGlueTableName, mockUmccriseGlueTableName, mockRnasumGlueTableName, + mockPierianDxGlueTableName, + pieriandxProjectInfoSsmParameterPath, + redcapLambdaFunctionName, } from '../constants'; import { GlueStackConfig } from '../../lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs'; import { StackyStatefulTablesConfig } from '../../lib/workload/stateful/stacks/stacky-mcstackface-dynamodb'; export const getGlueStackProps = (stage: AppStage): GlueStackConfig => { return { - /* SSM Parameters */ - bsshOutputFastqCopyUriSsmParameterName: mockPrimaryOutputUriSsmParameterName, - analysisCacheUriSsmParameterName: mockAnalysisCacheUriSsmParameterName, - analysisOutputUriSsmParameterName: mockAnalysisOutputUriSsmParameterName, - icav2ProjectIdSsmParameterName: mockIcav2ProjectIdSsmParameterName, - analysisLogsUriSsmParameterName: mockAnalysisLogsUriSsmParameterName, /* Events */ eventBusName: mockEventBusName, + /* Tables */ inputMakerTableName: mockInputMakerTableName, instrumentRunTableName: mockInstrumentRunTableName, @@ -40,8 +38,23 @@ export const getGlueStackProps = (stage: AppStage): GlueStackConfig => { wtsGlueTableName: mockWtsGlueTableName, umccriseGlueTableName: mockUmccriseGlueTableName, rnasumGlueTableName: mockRnasumGlueTableName, + pieriandxGlueTableName: mockPierianDxGlueTableName, + + /* SSM Parameters */ + analysisCacheUriSsmParameterName: mockAnalysisCacheUriSsmParameterName, + analysisOutputUriSsmParameterName: mockAnalysisOutputUriSsmParameterName, + icav2ProjectIdSsmParameterName: mockIcav2ProjectIdSsmParameterName, + analysisLogsUriSsmParameterName: mockAnalysisLogsUriSsmParameterName, + /* Secrets */ icav2AccessTokenSecretName: icav2AccessTokenSecretName[stage], + + /* BSSH SSM Parameters */ + bsshOutputFastqCopyUriSsmParameterName: mockPrimaryOutputUriSsmParameterName, + + /* PierianDx SSM Parameters */ + pieriandxProjectInfoSsmParameterPath: pieriandxProjectInfoSsmParameterPath, + redcapLambdaFunctionName: redcapLambdaFunctionName[stage], }; }; @@ -56,5 +69,6 @@ export const getStatefulGlueStackProps = (): StackyStatefulTablesConfig => { dynamodbWtsGlueTableName: mockWtsGlueTableName, dynamodbUmccriseGlueTableName: mockUmccriseGlueTableName, dynamodbRnasumGlueTableName: mockRnasumGlueTableName, + dynamodbPieriandxGlueTableName: mockPierianDxGlueTableName, }; }; diff --git a/lib/workload/components/dynamodb-icav2-handle-event-change-sfn/index.ts b/lib/workload/components/dynamodb-icav2-handle-event-change-sfn/index.ts index e5f3b1a06..5efb19d8b 100644 --- a/lib/workload/components/dynamodb-icav2-handle-event-change-sfn/index.ts +++ b/lib/workload/components/dynamodb-icav2-handle-event-change-sfn/index.ts @@ -127,7 +127,7 @@ export class Icav2AnalysisEventHandlerConstruct extends Construct { Convert a workflow name to lowercase and remove any spacing This has to be in align with Python impl: - lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/lambdas/generate_workflow_run_name_py/generate_workflow_run_name.py + lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/lambdas/generate_workflow_run_name_py/generate_workflow_run_name.py */ let _name = name.toLowerCase().replace(new RegExp(' ', 'g'), ''); _name = _name.replace(new RegExp('\\.', 'g'), '-'); diff --git a/lib/workload/components/dynamodb-icav2-ready-event-handler-sfn/step_functions_templates/icav2_launch_workflow_and_raise_internal_event.asl.json b/lib/workload/components/dynamodb-icav2-ready-event-handler-sfn/step_functions_templates/icav2_launch_workflow_and_raise_internal_event.asl.json index b523599dc..e5045c7da 100644 --- a/lib/workload/components/dynamodb-icav2-ready-event-handler-sfn/step_functions_templates/icav2_launch_workflow_and_raise_internal_event.asl.json +++ b/lib/workload/components/dynamodb-icav2-ready-event-handler-sfn/step_functions_templates/icav2_launch_workflow_and_raise_internal_event.asl.json @@ -24,20 +24,34 @@ } } }, - "Next": "Is Portal Run ID in Database", + "Next": "Is Portal Run ID + Analysis ID in Database", "ResultPath": "$.portal_run_id_in_db_step", "ResultSelector": { "db_response.$": "$" } }, - "Is Portal Run ID in Database": { + "Is Portal Run ID + Analysis ID in Database": { "Type": "Choice", "Choices": [ { - "Not": { - "Variable": "$.portal_run_id_in_db_step.db_response.Item", - "IsPresent": true - }, + "Or": [ + { + "Not": { + "Variable": "$.portal_run_id_in_db_step.db_response.Item", + "IsPresent": true + } + }, + { + "Not": { + "Variable": "$.portal_run_id_in_db_step.db_response.Item.analysis_id", + "IsPresent": true + } + }, + { + "Variable": "$.portal_run_id_in_db_step.db_response.Item.analysis_id.S", + "StringEquals": "" + } + ], "Next": "Add Technical Tags" } ], diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/workflowrunstatechange_draft_to_ready_step_function_template.asl.json b/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/workflowrunstatechange_draft_to_ready_step_function_template.asl.json deleted file mode 100644 index f658c798d..000000000 --- a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/workflowrunstatechange_draft_to_ready_step_function_template.asl.json +++ /dev/null @@ -1,202 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "move inputs", - "States": { - "move inputs": { - "Type": "Pass", - "Next": "Get portal run id from payload", - "Parameters": { - "input_event_detail.$": "$" - } - }, - "Get portal run id from payload": { - "Type": "Pass", - "Next": "Get portal run Id in database (local)", - "Parameters": { - "portal_run_id.$": "$.input_event_detail.portalRunId" - }, - "ResultPath": "$.get_portal_run_id_step" - }, - "Get portal run Id in database (local)": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.get_portal_run_id_step.portal_run_id", - "id_type": "${__workflow_type_partition_name__}" - } - }, - "ResultPath": "$.get_portal_run_id_in_db_step", - "Next": "is portal run id in database" - }, - "is portal run id in database": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_portal_run_id_in_db_step.Item", - "IsPresent": true, - "Next": "Get Item from DataBase (local)" - } - ], - "Default": "Get Workflow Run Engine Parameters" - }, - "Get Workflow Run Engine Parameters": { - "Type": "Task", - "Resource": "arn:aws:states:::states:startExecution.sync:2", - "Parameters": { - "StateMachineArn": "${__engine_parameters_maker_state_machine_arn__}", - "Input": { - "portal_run_id.$": "$.input_event_detail.portalRunId", - "workflow_name.$": "$.input_event_detail.workflowName", - "workflow_version.$": "$.input_event_detail.workflowVersion", - "event_data_inputs.$": "$.input_event_detail.payload.data.inputs", - "ssm_parameters_list": [ - { - "engine_parameter_key": "outputUri", - "ssm_name": "${__output_uri_ssm_parameter_name__}" - }, - { - "engine_parameter_key": "logsUri", - "ssm_name": "${__logs_uri_ssm_parameter_name__}" - }, - { - "engine_parameter_key": "cacheUri", - "ssm_name": "${__cache_uri_ssm_parameter_name__}" - }, - { - "engine_parameter_key": "projectId", - "ssm_name": "${__project_id_ssm_parameter_name__}" - } - ] - } - }, - "Next": "Update workflow table database", - "ResultPath": "$.set_workflow_run_engine_parameters", - "ResultSelector": { - "engine_parameters.$": "$.Output.engine_parameters" - } - }, - "Update workflow table database": { - "Type": "Parallel", - "Branches": [ - { - "StartAt": "Initialise Event Data Item with Inputs Engine Parameters", - "States": { - "Initialise Event Data Item with Inputs Engine Parameters": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.get_portal_run_id_step.portal_run_id", - "id_type": "${__workflow_type_partition_name__}" - }, - "UpdateExpression": "SET event_data_inputs = :event_data_inputs, workflow_run_name = :workflow_run_name, portal_run_id = :portal_run_id, event_data_engine_parameters = :event_data_engine_parameters, event_data_tags = :event_data_tags", - "ExpressionAttributeValues": { - ":portal_run_id": { - "S.$": "$.input_event_detail.portalRunId" - }, - ":workflow_run_name": { - "S.$": "$.input_event_detail.workflowRunName" - }, - ":event_data_inputs": { - "S.$": "States.JsonToString($.input_event_detail.payload.data.inputs)" - }, - ":event_data_engine_parameters": { - "S.$": "States.JsonToString($.set_workflow_run_engine_parameters.engine_parameters)" - }, - ":event_data_tags": { - "S.$": "States.JsonToString($.input_event_detail.payload.data.tags)" - } - } - }, - "ResultPath": null, - "End": true - } - } - }, - { - "StartAt": "Update portal_run table", - "States": { - "Update portal_run table": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.input_event_detail.portalRunId", - "id_type": "${__portal_run_partition_name__}" - }, - "UpdateExpression": "SET analysis_status = :analysis_status", - "ExpressionAttributeValues": { - ":analysis_status": { - "S": "READY" - } - } - }, - "ResultPath": null, - "End": true - } - } - } - ], - "ResultPath": null, - "Next": "Wait 1 Second (post event detail output update)" - }, - "Wait 1 Second (post event detail output update)": { - "Type": "Wait", - "Seconds": 1, - "Next": "Get Item from DataBase (local)" - }, - "Get Item from DataBase (local)": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.get_portal_run_id_step.portal_run_id", - "id_type": "${__workflow_type_partition_name__}" - } - }, - "Next": "EventBridge PutEvents", - "ResultSelector": { - "inputs.$": "States.StringToJson($.Item.event_data_inputs.S)", - "engine_parameters.$": "States.StringToJson($.Item.event_data_engine_parameters.S)", - "tags.$": "States.StringToJson($.Item.event_data_tags.S)" - }, - "ResultPath": "$.get_event_data_output_step" - }, - "EventBridge PutEvents": { - "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", - "Parameters": { - "Entries": [ - { - "Source": "${__event_output_source__}", - "EventBusName": "${__event_bus_name__}", - "DetailType": "${__detail_type__}", - "Detail": { - "portalRunId.$": "$.input_event_detail.portalRunId", - "timestamp.$": "$$.State.EnteredTime", - "status": "READY", - "workflowName.$": "$.input_event_detail.workflowName", - "workflowVersion.$": "$.input_event_detail.workflowVersion", - "workflowRunName.$": "$.input_event_detail.workflowRunName", - "linkedLibraries.$": "$.input_event_detail.linkedLibraries", - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs.$": "$.get_event_data_output_step.inputs", - "engineParameters.$": "$.get_event_data_output_step.engine_parameters", - "tags.$": "$.input_event_detail.payload.data.tags" - } - } - } - } - ] - }, - "End": true - } - } -} diff --git a/lib/workload/components/icav2-copy-files-batch/index.ts b/lib/workload/components/icav2-copy-files-batch/index.ts index 72eb64431..03fb9b966 100644 --- a/lib/workload/components/icav2-copy-files-batch/index.ts +++ b/lib/workload/components/icav2-copy-files-batch/index.ts @@ -24,7 +24,7 @@ export class ICAv2CopyBatchUtilityConstruct extends Construct { super(scope, id); // Manifest inverter lambda - const manifest_inverter_lambda = new PythonFunction(this, 'manifest_inverter_lambda', { + const manifestInverterLambda = new PythonFunction(this, 'manifest_inverter_lambda', { entry: path.join(__dirname, 'manifest_handler_lambda_py'), runtime: lambda.Runtime.PYTHON_3_12, architecture: lambda.Architecture.ARM_64, @@ -48,13 +48,13 @@ export class ICAv2CopyBatchUtilityConstruct extends Construct { ), // Definition Substitutions definitionSubstitutions: { - __manifest_inverter_lambda_arn__: manifest_inverter_lambda.currentVersion.functionArn, + __manifest_inverter_lambda_arn__: manifestInverterLambda.currentVersion.functionArn, __copy_single_job_state_machine_arn__: this.icav2CopyFilesSfnObj.stateMachineArn, }, }); // Add execution permissions to stateMachine role - manifest_inverter_lambda.currentVersion.grantInvoke(this.icav2CopyFilesBatchSfnObj.role); + manifestInverterLambda.currentVersion.grantInvoke(this.icav2CopyFilesBatchSfnObj.role); // Because we run a nested state machine, we need to add the permissions to the state machine role // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr diff --git a/lib/workload/components/icav2-copy-files-batch/step_functions_templates/copy_files_batch_sfn.asl.json b/lib/workload/components/icav2-copy-files-batch/step_functions_templates/copy_files_batch_sfn.asl.json index b10ecdc50..06a597be4 100644 --- a/lib/workload/components/icav2-copy-files-batch/step_functions_templates/copy_files_batch_sfn.asl.json +++ b/lib/workload/components/icav2-copy-files-batch/step_functions_templates/copy_files_batch_sfn.asl.json @@ -39,6 +39,8 @@ }, "Process Manifest": { "Type": "Map", + "ItemsPath": "$.manifest_inverted_step.manifest_inverted", + "MaxConcurrency": 5, "Iterator": { "StartAt": "Copy Single Job State Machine", "States": { @@ -63,7 +65,6 @@ } } }, - "ItemsPath": "$.manifest_inverted_step.manifest_inverted", "ResultPath": "$.job_list_with_attempt_counter", "Next": "Succeed" }, diff --git a/lib/workload/components/icav2-copy-files/check_or_launch_job_lambda_py/check_or_launch_job_lambda.py b/lib/workload/components/icav2-copy-files/check_or_launch_job_lambda_py/check_or_launch_job_lambda.py index 11fcc71a5..bedaad302 100644 --- a/lib/workload/components/icav2-copy-files/check_or_launch_job_lambda_py/check_or_launch_job_lambda.py +++ b/lib/workload/components/icav2-copy-files/check_or_launch_job_lambda_py/check_or_launch_job_lambda.py @@ -193,9 +193,11 @@ def handler(event, context): "dest_uri": dest_uri, "source_uris": source_uris, "job_id": job_id, - "failed_job_list": failed_job_list, # Empty list or list of failed jobs + # Empty list or list of failed jobs + "failed_job_list": failed_job_list, "job_status": "RUNNING", - "wait_time_seconds": wait_time_seconds + DEFAULT_WAIT_TIME_SECONDS_EXT # Wait a bit longer (an extra 10 seconds) + # Wait a bit longer (an extra 10 seconds) + "wait_time_seconds": wait_time_seconds + DEFAULT_WAIT_TIME_SECONDS_EXT } # Handle a failed job diff --git a/lib/workload/components/python-lambda-get-metadata-objects-from-samplesheet/get_metadata_objects_from_samplesheet_py/get_metadata_objects_from_samplesheet.py b/lib/workload/components/python-lambda-get-metadata-objects-from-samplesheet/get_metadata_objects_from_samplesheet_py/get_metadata_objects_from_samplesheet.py index df94480ed..11d20f6a7 100644 --- a/lib/workload/components/python-lambda-get-metadata-objects-from-samplesheet/get_metadata_objects_from_samplesheet_py/get_metadata_objects_from_samplesheet.py +++ b/lib/workload/components/python-lambda-get-metadata-objects-from-samplesheet/get_metadata_objects_from_samplesheet_py/get_metadata_objects_from_samplesheet.py @@ -8,10 +8,8 @@ import logging from typing import List, Dict -import pandas as pd - # Layer imports -from metadata_tools import get_all_libraries, get_all_specimens, get_all_subjects +from metadata_tools import get_all_libraries # Logger logger = logging.getLogger() @@ -34,93 +32,6 @@ def get_library_objs(library_id_list: List[str]) -> List[Dict]: ) -def get_specimen_objs(specimen_id_list: List[int]) -> List[Dict]: - """ - Get all specimen objects by doing a bulk download + filter rather than query 1-1 - :param specimen_id_list: - :return: - """ - specimen_df = pd.DataFrame( - filter( - lambda specimen_obj_iter: specimen_obj_iter['orcabusId'] in specimen_id_list, - get_all_specimens() - ) - ) - - return ( - specimen_df.drop_duplicates(). - sort_values(by='orcabusId'). - to_dict(orient='records') - ) - - -def get_specimen_objs_by_library_obj_list(library_obj_list: List[Dict]) -> List[Dict]: - """ - Get specimen objects by library object list - :param library_obj_list: - :return: - """ - specimen_id_list = list( - map( - lambda library_obj_iter: library_obj_iter['specimen'], - library_obj_list - ) - ) - - # Get the specimens as a dataframe - specimens_df = pd.DataFrame(get_specimen_objs(specimen_id_list)) - - return specimens_df.to_dict(orient='records') - - -def get_subject_objs_by_specimen_obj_list(specimen_obj_list: List[Dict]) -> List[Dict]: - """ - Get all subjects by a specimen object list - :param specimen_obj_list: - :return: - """ - # Convert to dataframe to we can coerce columns subjects -> subject - specimens_df = pd.DataFrame(specimen_obj_list) - - # Since we're merging onto the subject df, we want to rename id to - # specimen (since the specimen id column is also called specimen in the library dataframe - # We will also only keep specimen, and subject columns since these are the key linker between the - # subject and library databases - specimens_df.rename( - columns={ - "orcabusId": "specimen" - }, - inplace=True - ) - specimens_df = specimens_df[["specimen", "subject"]] - - # Get all subjects - all_subjects_list_dict = get_all_subjects() - all_subjects_df = pd.DataFrame(all_subjects_list_dict) - - # Merge specimens and subjects df - filtered_subjects_df = pd.merge( - all_subjects_df, - specimens_df, - left_on="orcabusId", - right_on="subject", - how='inner' - ) - - subject_id_list = filtered_subjects_df["subjectId"].tolist() - - return ( - pd.DataFrame( - filter( - lambda subject_iter: subject_iter['subjectId'] in subject_id_list, - all_subjects_list_dict - ) - ).drop_duplicates(). - sort_values(by='orcabusId'). - to_dict(orient='records') - ) - - def handler(event, context): """ Given a samplesheet dictionary, collect the sample_id attributes as library ids. @@ -131,11 +42,13 @@ def handler(event, context): :return: """ + # Get the samplesheet dictionary if "samplesheet" not in event.keys(): logger.error("Could not get samplesheet") raise KeyError samplesheet_dict = event["samplesheet"] + # Get the bclconvert_data from the samplesheet if "bclconvert_data" not in samplesheet_dict.keys(): logger.error("Could not get bclconvert_data from samplesheet") raise KeyError @@ -146,7 +59,7 @@ def handler(event, context): set( list( map( - lambda bclconvert_data_row_iter: bclconvert_data_row_iter.get("sample_id"), + lambda bclconvert_data_row_iter_: bclconvert_data_row_iter_.get("sample_id"), bclconvert_data ) ) @@ -156,22 +69,19 @@ def handler(event, context): # Get library objects library_obj_list = get_library_objs(library_id_list) - # Get specimen objects - specimen_obj_list = get_specimen_objs_by_library_obj_list(library_obj_list) - - # Get subject objects - subject_obj_list = get_subject_objs_by_specimen_obj_list(specimen_obj_list) - # Get all libraries from the database return { - "library_obj_list": library_obj_list, - "specimen_obj_list": specimen_obj_list, - "subject_obj_list": subject_obj_list + "library_obj_list": library_obj_list } # if __name__ == "__main__": # import json +# from os import environ +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' # print( # json.dumps( # handler( @@ -718,611 +628,816 @@ def handler(event, context): # # { # # "library_obj_list": [ # # { -# # "orcabusId": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX", +# # "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EBXK08WDWB97BSCX1C9", +# # "projectId": "PO", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4MPHSX7MRCTTFWJBYTT7", +# # "sampleId": "MDX210402", +# # "externalSampleId": "ZUHR111121", +# # "source": "plasma-serum" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4MNXJSDRR406DAXFZP2N", +# # "subjectId": "PM3045106" +# # }, # # "libraryId": "L2400102", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "borderline", # # "type": "WGS", # # "assay": "ctTSO", -# # "coverage": 50.0, -# # "projectOwner": "VCCC", -# # "projectName": "PO", -# # "specimen": "spc.01J5S9C4V269YTNA17TTP6NF76" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBG0NF8QBNVKM6ESCD60", +# # "coverage": 50.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4XMDW0FV1YMWHSZZQ4TX", +# # "sampleId": "PTC_SCMM1pc2", +# # "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "subjectId": "CMM1pc-10646259ilm" +# # }, # # "libraryId": "L2400159", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBFDVZX7ZT3Y6TH28SY4" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBHP6NSB42RVFAP9PGJP", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4XQ071BF3WZN111SNJ2B", +# # "sampleId": "PTC_SCMM1pc3", +# # "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "subjectId": "CMM1pc-10646259ilm" +# # }, # # "libraryId": "L2400160", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBH4V5B56CEJ5Q1XQKQ9" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBKCATYSFY40BRX6WJWX", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4XRG9NB38N03688M2CCB", +# # "sampleId": "PTC_SCMM1pc4", +# # "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "subjectId": "CMM1pc-10646259ilm" +# # }, # # "libraryId": "L2400161", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBJTBJB72KJ74VSCHKJF" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4XWXFANT7P0T3AFXA85G", +# # "sampleId": "PTC_SCMM01pc20", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 20ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, # # "libraryId": "L2400162", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBMKTX5KN1XMPN479R2M" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4XYTSVQVRSBA9M26NSZY", +# # "sampleId": "PTC_SCMM01pc15", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 15ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, # # "libraryId": "L2400163", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBPSPN6S3TQCVJZF0XFE" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBS64DNTHK6CE850CCNZ", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4Y0V0ZBKAE91TDSY0BBB", +# # "sampleId": "PTC_SCMM01pc10", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 10ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, # # "libraryId": "L2400164", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBRM3Y6PPF6E5NWZA7HG" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBTZRYQNTGAHPC2T601D", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4Y37JTPEEJSED9BXH8N2", +# # "sampleId": "PTC_SCMM01pc5", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 5ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, # # "libraryId": "L2400165", # # "phenotype": "tumor", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 38.6, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBTACFBNJKE8C523B0A7" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBX10204CK7EKGTH9TMB", +# # "coverage": 38.6 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4Y4XK1WX4WCPD6XY8KNM", +# # "sampleId": "NTC_v2ctTSO240207", +# # "externalSampleId": "negative control", +# # "source": "water" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "subjectId": "negative control" +# # }, # # "libraryId": "L2400166", # # "phenotype": "negative-control", # # "workflow": "manual", # # "quality": "good", # # "type": "ctDNA", # # "assay": "ctTSOv2", -# # "coverage": 0.1, -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "specimen": "spc.01J5S9CBWCGKQMG5S3ZSWA2ATE" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7", +# # "coverage": 0.1 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZDAFRK3K3PY33F8XS0W", +# # "sampleId": "PRJ240169", +# # "externalSampleId": "AUS-006-DRW_C1D1PRE", +# # "source": "blood" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "subjectId": "AUS-006-DRW" +# # }, # # "libraryId": "L2400191", # # "phenotype": "normal", # # "workflow": "research", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 40.0, -# # "projectOwner": "TJohn", -# # "projectName": "CAVATAK", -# # "specimen": "spc.01J5S9CDEH0ATXYAK52KW807R4" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S", +# # "coverage": 40.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZMETZP255WMFC8TSCYT", +# # "sampleId": "PRJ240180", +# # "externalSampleId": "AUS-006-DRW_Day0", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "subjectId": "AUS-006-DRW" +# # }, # # "libraryId": "L2400195", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 80.0, -# # "projectOwner": "TJohn", -# # "projectName": "CAVATAK", -# # "specimen": "spc.01J5S9CDQ0V9T98EGRPQJAP11S" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8", +# # "coverage": 80.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZNT47EM37QKMT12JPPJ", +# # "sampleId": "PRJ240181", +# # "externalSampleId": "AUS-006-DRW_Day33", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "subjectId": "AUS-006-DRW" +# # }, # # "libraryId": "L2400196", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 80.0, -# # "projectOwner": "TJohn", -# # "projectName": "CAVATAK", -# # "specimen": "spc.01J5S9CDRZMMR9S784BYSMVWCT" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ", +# # "coverage": 80.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZQ76H8P0Q7S618F3BMA", +# # "sampleId": "PRJ240182", +# # "externalSampleId": "AUS-007-JMA_Day0", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# # "subjectId": "AUS-007-JMA" +# # }, # # "libraryId": "L2400197", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 80.0, -# # "projectOwner": "TJohn", -# # "projectName": "CAVATAK", -# # "specimen": "spc.01J5S9CDTSHGYMMJHE3SXEB2JG" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q", +# # "coverage": 80.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZVAR9NQM55Z2TXCDY9V", +# # "sampleId": "PRJ240183", +# # "externalSampleId": "AUS-007-JMA_Day15", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# # "subjectId": "AUS-007-JMA" +# # }, # # "libraryId": "L2400198", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 80.0, -# # "projectOwner": "TJohn", -# # "projectName": "CAVATAK", -# # "specimen": "spc.01J5S9CDWHAYG4RRG75GYZEK25" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N", +# # "coverage": 80.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES51T84KVVVSEPYQFGW0EV", +# # "sampleId": "PRJ240199", +# # "externalSampleId": "DNA188239", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "subjectId": "SN_PMC-141" +# # }, # # "libraryId": "L2400231", # # "phenotype": "tumor", # # "workflow": "clinical", # # "quality": "poor", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 100.0, -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "specimen": "spc.01J5S9CFWAQTGK4MZB3HM5NVBC" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9", +# # "coverage": 100.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES527QKB5Y5RVZWZ8HQX0H", +# # "sampleId": "PRJ240643", +# # "externalSampleId": "DNA188378", +# # "source": "blood" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "subjectId": "SN_PMC-141" +# # }, # # "libraryId": "L2400238", # # "phenotype": "normal", # # "workflow": "clinical", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 40.0, -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "specimen": "spc.01J5S9CGBQCSQCS7XR3T89A82F" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA", +# # "coverage": 40.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES52A5QX0GQ6RB78Z8DGYQ", +# # "sampleId": "PRJ240646", +# # "externalSampleId": "DNA189922", +# # "source": "blood" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "subjectId": "SN_PMC-145" +# # }, # # "libraryId": "L2400239", # # "phenotype": "normal", # # "workflow": "clinical", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 40.0, -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "specimen": "spc.01J5S9CGE05BJCJ20M2KP4QWWB" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB", +# # "coverage": 40.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES52BM8BVS3PX47E6FM7D5", +# # "sampleId": "PRJ240647", +# # "externalSampleId": "DNA189848", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "subjectId": "SN_PMC-145" +# # }, # # "libraryId": "L2400240", # # "phenotype": "tumor", # # "workflow": "clinical", # # "quality": "poor", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 100.0, -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "specimen": "spc.01J5S9CGFQM3BQKADX8TWQ4ZH5" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD", +# # "coverage": 100.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES52D076FQM5K8128AQ593", +# # "sampleId": "NTC_TSqN240226", +# # "externalSampleId": "NTC_TSqN240226", +# # "source": "water" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "subjectId": "negative control" +# # }, # # "libraryId": "L2400241", # # "phenotype": "negative-control", # # "workflow": "control", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 0.1, -# # "projectOwner": "UMCCR", -# # "projectName": "Control", -# # "specimen": "spc.01J5S9CGHK1G7YXD7C4FCXXS52" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CGKWDN7STKZKQM3KH9XR", +# # "coverage": 0.1 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES52EEX67YRYAJS3F5GMJ5", +# # "sampleId": "PTC_TSqN240226", +# # "externalSampleId": "NA24385-3", +# # "source": "cell-line" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DRJ31Z2H1GJQZGVDXZR", +# # "subjectId": "NA24385" +# # }, # # "libraryId": "L2400242", # # "phenotype": "normal", # # "workflow": "control", # # "quality": "good", # # "type": "WGS", # # "assay": "TsqNano", -# # "coverage": 15.0, -# # "projectOwner": "UMCCR", -# # "projectName": "Control", -# # "specimen": "spc.01J5S9CGKAT4GHZ1VJFHVV15AD" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE", +# # "coverage": 15.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES52XE661E8V8XTWD02QCK", +# # "sampleId": "PTC_NebRNA240226", +# # "externalSampleId": "Colo829", +# # "source": "cell-line" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4GNVGZSJVTHGVKS9VW7F", +# # "subjectId": "Colo829" +# # }, # # "libraryId": "L2400249", # # "phenotype": "tumor", # # "workflow": "control", # # "quality": "good", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 1.0, -# # "projectOwner": "UMCCR", -# # "projectName": "Control", -# # "specimen": "spc.01J5S9CH267XEJP5GMZK31MJWS" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CH4CYPA4SP05H8KRX4W9", +# # "coverage": 1.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES4FP9WTFBDNGKVG3D9BD4", +# # "sampleId": "PRJ240003", +# # "externalSampleId": "3-23BCRL057T", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4FNJ2FCAK0RJST0428X0", +# # "subjectId": "23BCRL057T" +# # }, # # "libraryId": "L2400250", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Whittle", -# # "projectName": "BPOP-retro", -# # "specimen": "spc.01J5S9C0QC2TBZD7XA26D7WGTW" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CH65E4EE5QJEJ1C60GGG", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES530355YNZ3VHQQQ204PF", +# # "sampleId": "PRJ240561", +# # "externalSampleId": "4-218-004_Bx", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# # "subjectId": "218-004" +# # }, # # "libraryId": "L2400251", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Whittle", -# # "projectName": "BPOP-retro", -# # "specimen": "spc.01J5S9CH5KXY3VMB9M9J2RCR7B" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES531H420JM9MG5R4AE1AZ", +# # "sampleId": "PRJ240562", +# # "externalSampleId": "5-218-004_04", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# # "subjectId": "218-004" +# # }, # # "libraryId": "L2400252", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Whittle", -# # "projectName": "BPOP-retro", -# # "specimen": "spc.01J5S9CH774HEZFEVWWP2XADK1" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES532ZBHWY3DWY0DWQ223R", +# # "sampleId": "PRJ240566", +# # "externalSampleId": "9-218-007_Bx", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# # "subjectId": "218-007" +# # }, # # "libraryId": "L2400253", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "good", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Whittle", -# # "projectName": "BPOP-retro", -# # "specimen": "spc.01J5S9CH98MQ2B1G2BQFEY0XZH" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES534BX7B89X5EKSCFRDDZ", +# # "sampleId": "PRJ240567", +# # "externalSampleId": "10-218-007_04", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# # "subjectId": "218-007" +# # }, # # "libraryId": "L2400254", # # "phenotype": "tumor", # # "workflow": "research", # # "quality": "borderline", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Whittle", -# # "projectName": "BPOP-retro", -# # "specimen": "spc.01J5S9CHAX3XKJE5XE4VQWYN5H" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CHE4ERQ4H209DH397W8A", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES535VGG93023KWAFMWGH4", +# # "sampleId": "PRJ240200", +# # "externalSampleId": "RNA036747", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "subjectId": "SN_PMC-141" +# # }, # # "libraryId": "L2400255", # # "phenotype": "tumor", # # "workflow": "clinical", # # "quality": "very-poor", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "specimen": "spc.01J5S9CHDGRNK70B043K887RP2" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES5379C40K08YG3JDMZJN7", +# # "sampleId": "PRJ240648", +# # "externalSampleId": "RNA037080", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "subjectId": "SN_PMC-145" +# # }, # # "libraryId": "L2400256", # # "phenotype": "tumor", # # "workflow": "clinical", # # "quality": "very-poor", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 6.0, -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "specimen": "spc.01J5S9CHFAPXYKK49FAGVF5CQF" -# # }, -# # { -# # "orcabusId": "lib.01J5S9CHHNGFJN73NPRQMSYGN9", +# # "coverage": 6.0 +# # }, +# # { +# # "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP", +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "sample": { +# # "orcabusId": "smp.01J8ES538PFF6MQQ35PTC00JAY", +# # "sampleId": "NTC_NebRNA240226", +# # "externalSampleId": "NTC_NebRNA240226", +# # "source": "water" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "subjectId": "negative control" +# # }, # # "libraryId": "L2400257", # # "phenotype": "negative-control", # # "workflow": "control", # # "quality": "good", # # "type": "WTS", # # "assay": "NebRNA", -# # "coverage": 0.1, -# # "projectOwner": "UMCCR", -# # "projectName": "Control", -# # "specimen": "spc.01J5S9CHH24VFM443RD8Q8X4B3" -# # } -# # ], -# # "specimen_obj_list": [ -# # { -# # "orcabusId": "spc.01J5S9C0QC2TBZD7XA26D7WGTW", -# # "specimenId": "PRJ240003", -# # "source": "tissue", -# # "subject": "sbj.01J5S9C0PVB4QNVGK4Q1WSYEGV" -# # }, -# # { -# # "orcabusId": "spc.01J5S9C4V269YTNA17TTP6NF76", -# # "specimenId": "MDX210402", -# # "source": "plasma-serum", -# # "subject": "sbj.01J5S9C4TE1GCWA1QGNCWHB1Y9" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBFDVZX7ZT3Y6TH28SY4", -# # "specimenId": "PTC_SCMM1pc2", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBH4V5B56CEJ5Q1XQKQ9", -# # "specimenId": "PTC_SCMM1pc3", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBJTBJB72KJ74VSCHKJF", -# # "specimenId": "PTC_SCMM1pc4", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBMKTX5KN1XMPN479R2M", -# # "specimenId": "PTC_SCMM01pc20", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBPSPN6S3TQCVJZF0XFE", -# # "specimenId": "PTC_SCMM01pc15", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBRM3Y6PPF6E5NWZA7HG", -# # "specimenId": "PTC_SCMM01pc10", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBTACFBNJKE8C523B0A7", -# # "specimenId": "PTC_SCMM01pc5", -# # "source": "cfDNA", -# # "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CBWCGKQMG5S3ZSWA2ATE", -# # "specimenId": "NTC_v2ctTSO240207", -# # "source": "water", -# # "subject": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CDEH0ATXYAK52KW807R4", -# # "specimenId": "PRJ240169", -# # "source": "blood", -# # "subject": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CDQ0V9T98EGRPQJAP11S", -# # "specimenId": "PRJ240180", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CDRZMMR9S784BYSMVWCT", -# # "specimenId": "PRJ240181", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CDTSHGYMMJHE3SXEB2JG", -# # "specimenId": "PRJ240182", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CDG7B0KA8YEDK876VVDP" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CDWHAYG4RRG75GYZEK25", -# # "specimenId": "PRJ240183", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CDG7B0KA8YEDK876VVDP" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CFWAQTGK4MZB3HM5NVBC", -# # "specimenId": "PRJ240199", -# # "source": "FFPE", -# # "subject": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CGBQCSQCS7XR3T89A82F", -# # "specimenId": "PRJ240643", -# # "source": "blood", -# # "subject": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CGE05BJCJ20M2KP4QWWB", -# # "specimenId": "PRJ240646", -# # "source": "blood", -# # "subject": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CGFQM3BQKADX8TWQ4ZH5", -# # "specimenId": "PRJ240647", -# # "source": "FFPE", -# # "subject": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CGHK1G7YXD7C4FCXXS52", -# # "specimenId": "NTC_TSqN240226", -# # "source": "water", -# # "subject": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CGKAT4GHZ1VJFHVV15AD", -# # "specimenId": "PTC_TSqN240226", -# # "source": "cell-line", -# # "subject": "sbj.01J5S9BYVWZDS8AW7A94CDQBXK" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CH267XEJP5GMZK31MJWS", -# # "specimenId": "PTC_NebRNA240226", -# # "source": "cell-line", -# # "subject": "sbj.01J5S9C1S3XV8PNB78XYJ1EQM1" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CH5KXY3VMB9M9J2RCR7B", -# # "specimenId": "PRJ240561", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CH774HEZFEVWWP2XADK1", -# # "specimenId": "PRJ240562", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CH98MQ2B1G2BQFEY0XZH", -# # "specimenId": "PRJ240566", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CG5GEWYBK0065C49HT23" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CHAX3XKJE5XE4VQWYN5H", -# # "specimenId": "PRJ240567", -# # "source": "tissue", -# # "subject": "sbj.01J5S9CG5GEWYBK0065C49HT23" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CHDGRNK70B043K887RP2", -# # "specimenId": "PRJ240200", -# # "source": "FFPE", -# # "subject": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CHFAPXYKK49FAGVF5CQF", -# # "specimenId": "PRJ240648", -# # "source": "FFPE", -# # "subject": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3" -# # }, -# # { -# # "orcabusId": "spc.01J5S9CHH24VFM443RD8Q8X4B3", -# # "specimenId": "NTC_NebRNA240226", -# # "source": "water", -# # "subject": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6" -# # } -# # ], -# # "subject_obj_list": [ -# # { -# # "orcabusId": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6", -# # "subjectId": "SBJ00006" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9BYVWZDS8AW7A94CDQBXK", -# # "subjectId": "SBJ00005" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9C0PVB4QNVGK4Q1WSYEGV", -# # "subjectId": "SBJ04488" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9C1S3XV8PNB78XYJ1EQM1", -# # "subjectId": "SBJ00029" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9C4TE1GCWA1QGNCWHB1Y9", -# # "subjectId": "SBJ01143" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB", -# # "subjectId": "SBJ04407" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", -# # "subjectId": "SBJ04648" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS", -# # "subjectId": "SBJ04653" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CDG7B0KA8YEDK876VVDP", -# # "subjectId": "SBJ04654" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5", -# # "subjectId": "SBJ04659" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN", -# # "subjectId": "SBJ04660" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CG5GEWYBK0065C49HT23", -# # "subjectId": "SBJ04661" -# # }, -# # { -# # "orcabusId": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3", -# # "subjectId": "SBJ04662" +# # "coverage": 0.1 # # } # # ] # # } diff --git a/lib/workload/components/python-lambda-layer/index.ts b/lib/workload/components/python-lambda-layer/index.ts index 55c4f271d..b8d952b2f 100644 --- a/lib/workload/components/python-lambda-layer/index.ts +++ b/lib/workload/components/python-lambda-layer/index.ts @@ -29,7 +29,10 @@ export class PythonLambdaLayerConstruct extends Construct { return []; }, afterBundling(inputDir: string, outputDir: string): string[] { - return [`python -m pip install ${inputDir} -t ${outputDir}`]; + return [ + `python -m pip install ${inputDir} -t ${outputDir}`, + `find ${outputDir} -name 'pandas' -exec rm -rf {}/tests/ \\;`, + ]; }, }, }, diff --git a/lib/workload/components/python-lambda-metadata-mapper/index.ts b/lib/workload/components/python-lambda-metadata-mapper/index.ts new file mode 100644 index 000000000..369224988 --- /dev/null +++ b/lib/workload/components/python-lambda-metadata-mapper/index.ts @@ -0,0 +1,94 @@ +/* +Quick and dirty way to map orcabus ids to complementary ids for each +of the databases from the metadata manager + +Comes with the bells and whistles of metadata tools layer and +permissions to use the orcabus token. + +User will need to use the 'addEnvironment' method on the returned lambda object in order +to specify what command will be run + +User will need + +ENV: +CONTEXT: One of the following: + - library + - subject + - individual + - sample + - project + - contact + +FROM_ORCABUS or FROM_ID +RETURN_STR or RETURN_OBJ + +Look at ./map_metadata_py/map_metadata.py for examples of outputs + +*/ + +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; +import path from 'path'; +import { MetadataToolsPythonLambdaLayer } from '../python-metadata-tools-layer'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Duration } from 'aws-cdk-lib'; + +interface MapMetadataLambdaObj { + functionNamePrefix: string; +} + +export class GetMetadataLambdaConstruct extends Construct { + public readonly lambdaObj: PythonFunction; + + // Globals + private readonly hostnameSsmParameterPath = '/hosted_zone/umccr/name'; + private readonly orcabusTokenSecretId = 'orcabus/token-service-jwt'; // pragma: allowlist secret + + constructor(scope: Construct, id: string, props: MapMetadataLambdaObj) { + super(scope, id); + + // Get the metadata layer object + const metadataLayerObj = new MetadataToolsPythonLambdaLayer(this, 'metadata-tools-layer', { + layerPrefix: 'get-library-objects', + }); + + /* + Collect the required secret and ssm parameters for getting metadata + */ + const hostnameSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'hostname_ssm_parameter', + this.hostnameSsmParameterPath + ); + const orcabusTokenSecretObj = secretsmanager.Secret.fromSecretNameV2( + this, + 'orcabus_token_secret', + this.orcabusTokenSecretId + ); + + // Get library objects + this.lambdaObj = new PythonFunction(this, 'map_metadata_py', { + functionName: `${props.functionNamePrefix}-map-metadata-py`, + entry: path.join(__dirname, 'map_metadata_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'map_metadata.py', + handler: 'handler', + memorySize: 1024, + layers: [metadataLayerObj.lambdaLayerVersionObj], + environment: { + HOSTNAME_SSM_PARAMETER: hostnameSsmParameterObj.parameterName, + ORCABUS_TOKEN_SECRET_ID: orcabusTokenSecretObj.secretName, + }, + timeout: Duration.seconds(60), + }); + + // Allow the lambda to read the secret + orcabusTokenSecretObj.grantRead(this.lambdaObj.currentVersion); + + // Allow the lambda to read the ssm parameter + hostnameSsmParameterObj.grantRead(this.lambdaObj.currentVersion); + } +} diff --git a/lib/workload/components/python-lambda-metadata-mapper/map_metadata_py/map_metadata.py b/lib/workload/components/python-lambda-metadata-mapper/map_metadata_py/map_metadata.py new file mode 100644 index 000000000..ca6a493ab --- /dev/null +++ b/lib/workload/components/python-lambda-metadata-mapper/map_metadata_py/map_metadata.py @@ -0,0 +1,446 @@ +#!/usr/bin/env python3 + + +# !/usr/bin/env python3 + +""" +This script is used to get the subject orcabus id from the subject id. +""" + +# Standard imports +from os import environ + +# Metadata imports +from metadata_tools import ( + # Orcabus helpers + get_orcabus_token, + # Subject helpers + get_subject_from_subject_orcabus_id, get_subject_from_subject_id, + # Sample helpers + get_sample_from_sample_orcabus_id, get_sample_from_sample_id, + # Library helpers + get_library_from_library_orcabus_id, get_library_from_library_id, + # Project helpers + get_project_from_project_orcabus_id, get_project_from_project_id, + # Individual helpers + get_individual_from_individual_orcabus_id, get_individual_from_individual_id, + # Contact helpers + get_contact_from_contact_orcabus_id, get_contact_from_contact_id +) + +# Globals +ALLOWED_CONTEXTS = ['individual', 'subject', 'sample', 'library', 'project', 'contact'] + + +def handler(event, context): + """ + Lambda handler. + + # Use the environment variables to customize the behavior of the function + # Based on the use case + ENV VAR VALUE is required + ENV VAR FROM_ORCABUS or FROM_ID is required + ENV VAR CONTEXT is required, one of 'subject', 'sample', 'library', 'project' + ENV VAR RETURN_STR or RETURN_OBJ is required + + :param event: + :param context: + :return: + """ + + # Get the orcabus token + environ['ORCABUS_TOKEN'] = get_orcabus_token() + + # Get value from the event object + value = event['value'] + + # Check if FROM_ORCABUS or FROM_ID is set + # But make sure not both are set + # And that at least one is set + if ('FROM_ORCABUS' in environ and 'FROM_ID' in environ) or ( + 'FROM_ORCABUS' not in environ and 'FROM_ID' not in environ): + raise ValueError("Either FROM_ORCABUS or FROM_ID must be set, but not both.") + is_from_orcabus = 'FROM_ORCABUS' in environ + + # Get the context + # And ensure that context is one of + # 'subject', 'sample', 'library', 'project' + if 'CONTEXT' not in environ: + raise ValueError("CONTEXT must be set.") + context = environ['CONTEXT'] + if context not in ALLOWED_CONTEXTS: + raise ValueError(f"CONTEXT must be one of {' '.join(ALLOWED_CONTEXTS)}") + + # Get the return type + # And ensure that it is one of 'id', 'object' and that not both are set + if 'RETURN_STR' not in environ and 'RETURN_OBJ' not in environ: + raise ValueError("RETURN_STR or RETURN_OBJ must be set.") + if 'RETURN_STR' in environ and 'RETURN_OBJ' in environ: + raise ValueError("RETURN_STR and RETURN_OBJ cannot be set at the same time.") + is_return_obj = 'RETURN_OBJ' in environ + + # Get the object based on the context + # For each context we then check if we need to return the object or the id + # And by 'id' we mean the orcabus id or the business unique identifier + + # If the context is 'individual' + if context == 'individual': + # Get the individual object + if is_from_orcabus: + individual_obj = get_individual_from_individual_orcabus_id(value) + else: + individual_obj = get_individual_from_individual_id(value) + + # Return the individual object if RETURN_OBJ is set + # Otherwise, return the orcabus id + if is_return_obj: + return individual_obj + + if is_from_orcabus: + return { + "individual_id": individual_obj['individualId'] + } + else: + return { + "orcabus_id": individual_obj['orcabusId'] + } + + # If the context is 'subject' + if context == 'subject': + # Get the subject object + if is_from_orcabus: + subject_obj = get_subject_from_subject_orcabus_id(value) + else: + subject_obj = get_subject_from_subject_id(value) + + # Return the subject object if RETURN_OBJ is set + # Otherwise, return the orcabus id + if is_return_obj: + return subject_obj + + if is_from_orcabus: + return { + "subject_id": subject_obj['subjectId'] + } + else: + return { + "orcabus_id": subject_obj['orcabusId'] + } + + # If the context is 'sample' + if context == 'sample': + + # Get the sample object + if is_from_orcabus: + sample_obj = get_sample_from_sample_orcabus_id(value) + else: + sample_obj = get_sample_from_sample_id(value) + + # Return the sample object if RETURN_OBJ is set + # Otherwise, return the orcabus id + if is_return_obj: + return sample_obj + + if is_from_orcabus: + return { + "sample_id": sample_obj['sampleId'] + } + else: + return { + "orcabus_id": sample_obj['orcabusId'] + } + + # If the context is 'library' + if context == 'library': + if is_from_orcabus: + library_obj = get_library_from_library_orcabus_id(value) + else: + library_obj = get_library_from_library_id(value) + + if is_return_obj: + return library_obj + + if is_from_orcabus: + return { + "library_id": library_obj['libraryId'] + } + else: + return { + "orcabus_id": library_obj['orcabusId'] + } + + # If the context is 'project' + if context == 'project': + if is_from_orcabus: + project_obj = get_project_from_project_orcabus_id(value) + else: + project_obj = get_project_from_project_id(value) + + if is_return_obj: + return project_obj + + if is_from_orcabus: + return { + "project_id": project_obj['projectId'] + } + else: + return { + "orcabus_id": project_obj['orcabusId'] + } + + # If the context is 'project' + if context == 'contact': + if is_from_orcabus: + contact_obj = get_contact_from_contact_orcabus_id(value) + else: + contact_obj = get_contact_from_contact_id(value) + + if is_return_obj: + return contact_obj + + if is_from_orcabus: + return { + "contact_id": contact_obj['contactId'] + } + else: + return { + "orcabus_id": contact_obj['orcabusId'] + } + + +# if __name__ == "__main__": +# import json +# +# # Set the aws variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# # Set the context variables +# environ['CONTEXT'] = 'individual' +# environ['FROM_ORCABUS'] = '' +# environ['RETURN_STR'] = '' +# +# # print( +# # json.dumps( +# # handler( +# # { +# # "value": "idv.01J8EV7AVRB43911YD4WKZNCHK" +# # }, +# # None +# # ), +# # indent=4 +# # ) +# # ) +# # +# # # { +# # # "individual_id": "SBJ05695" +# # # } + +# if __name__ == "__main__": +# # Import the json module +# import json +# +# # Set the environment variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# # Set the context variables +# environ['CONTEXT'] = 'subject' +# environ['FROM_ID'] = '' +# environ['RETURN_OBJ'] = '' +# +# print( +# json.dumps( +# handler( +# { +# "value": "VENTURE-24004301" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "orcabusId": "sbj.01J8ES921MV8VCY71SJPAS7D24", +# # "individualSet": [ +# # { +# # "orcabusId": "idv.01J8ES90SEHD03JY5R0DR8K44H", +# # "individualId": null, +# # "source": "lab" +# # }, +# # { +# # "orcabusId": "idv.01J8EV7ARC2ZF9X0FA243CSXA5", +# # "individualId": "SBJ05693", +# # "source": "lab" +# # } +# # ], +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES922VZT5S55T9HXPRZXC7", +# # "libraryId": "L2401462", +# # "phenotype": "normal", +# # "workflow": "qc", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 30.0, +# # "sample": "smp.01J8ES922C8KDSG3TWT8AEFCPZ", +# # "subject": "sbj.01J8ES921MV8VCY71SJPAS7D24" +# # } +# # ], +# # "subjectId": "VENTURE-24004301" +# # } + + +# if __name__ == "__main__": +# # Import the json module +# import json +# +# # Set the environment variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# # Set the context variables +# environ['CONTEXT'] = 'sample' +# environ['FROM_ID'] = '' +# environ['RETURN_OBJ'] = '' +# +# +# print( +# json.dumps( +# handler( +# { +# "value": "PTC_TSqN240923" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "orcabusId": "smp.01J8ES92CBMCVE09FJSPADGYG1", +# # "sampleId": "PTC_TSqN240923", +# # "externalSampleId": "NA24385", +# # "source": "cell-line" +# # } + + +# if __name__ == "__main__": +# # Set the environment variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# print( +# handler( +# { +# "subject_id": "VENTURE-24004301" +# }, +# None +# ) +# ) + + +# if __name__ == "__main__": +# # Import the json module +# import json +# +# # Set the environment variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# # Set the context variables +# environ['CONTEXT'] = 'library' +# environ['FROM_ORCABUS'] = '' +# environ['RETURN_STR'] = '' +# +# print( +# json.dumps( +# handler( +# { +# "value": "lib.01J8ES92FTH314XSPZDWBA91E2" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "library_id": "L2401469" +# # } + + +# if __name__ == "__main__": +# # Import the json module +# import json +# +# # Set the environment variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# # Set the context variables +# environ['CONTEXT'] = 'project' +# environ['FROM_ORCABUS'] = '' +# environ['RETURN_STR'] = '' +# +# print( +# json.dumps( +# handler( +# { +# "value": "prj.01J8ES70B12PTV40XAYF0GPW3C" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "project_id": "SEQC" +# # } + + +# if __name__ == "__main__": +# # Import the json module +# import json +# +# # Set the environment variables +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['HOSTNAME_SSM_PARAMETER'] = '/hosted_zone/umccr/name' +# environ['ORCABUS_TOKEN_SECRET_ID'] = 'orcabus/token-service-jwt' +# +# # Set the context variables +# environ['CONTEXT'] = 'contact' +# environ['FROM_ORCABUS'] = '' +# environ['RETURN_STR'] = '' +# +# print( +# json.dumps( +# handler( +# { +# "value": "ctc.01J8ES70ANRQQ4K71HQ9PZAEM6" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "contact_id": "Hofmann" +# # } diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/__init__.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/__init__.py index a8fd46d9b..e5d62ab47 100644 --- a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/__init__.py +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/__init__.py @@ -1,8 +1,12 @@ #!/usr/bin/env python +# Utils +from .utils.aws_helpers import get_orcabus_token + # Library Helpers from .utils.library_helpers import ( get_library_from_library_id, + get_library_from_library_orcabus_id, get_subject_from_library_id, get_library_type, get_library_assay_type, @@ -11,39 +15,78 @@ get_all_libraries ) -# Specimen Helpers -from .utils.specimen_helpers import ( - get_specimen_from_specimen_id, - list_libraries_in_specimen, - get_all_specimens +# Sample Helpers +from .utils.sample_helpers import ( + get_sample_from_sample_id, + get_sample_from_sample_orcabus_id, + list_libraries_in_sample, + get_all_samples ) # Subject Helpers from .utils.subject_helpers import ( get_subject_from_subject_id, - list_specimens_in_subject, + get_subject_from_subject_orcabus_id, + list_samples_in_subject, list_libraries_in_subject, get_all_subjects ) +# Project Helpers +from .utils.project_helpers import ( + get_all_projects, + get_project_from_project_id, + get_project_from_project_orcabus_id, +) + +# Individual Helpers +from .utils.individual_helpers import ( + get_individual_from_individual_id, + get_individual_from_individual_orcabus_id, + get_all_individuals +) + +# Contact helpers +from .utils.contact_helpers import ( + get_contact_from_contact_id, + get_contact_from_contact_orcabus_id, + get_all_contacts +) # Set _all__ __all__ = [ + # Utils + 'get_orcabus_token', # Library Funcs 'get_library_from_library_id', + 'get_library_from_library_orcabus_id', 'get_subject_from_library_id', 'get_library_type', 'get_library_assay_type', 'get_library_phenotype', 'get_library_workflow', 'get_all_libraries', - # Specimen Funcs - 'get_specimen_from_specimen_id', - 'list_libraries_in_specimen', - 'get_all_specimens', + # Sample Funcs + 'get_sample_from_sample_id', + 'get_sample_from_sample_orcabus_id', + 'list_libraries_in_sample', + 'list_samples_in_subject', + 'get_all_samples', # Subject Funcs 'get_subject_from_subject_id', - 'list_specimens_in_subject', + 'get_subject_from_subject_orcabus_id', 'list_libraries_in_subject', - 'get_all_subjects' + 'get_all_subjects', + # Project Funcs + 'get_all_projects', + 'get_project_from_project_id', + 'get_project_from_project_orcabus_id', + # Individual Funcs + 'get_individual_from_individual_id', + 'get_individual_from_individual_orcabus_id', + 'get_all_individuals', + # Contact Funcs + 'get_contact_from_contact_id', + 'get_contact_from_contact_orcabus_id', + 'get_all_contacts', ] diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/aws_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/aws_helpers.py index 061510bcf..0a5bf2f44 100644 --- a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/aws_helpers.py +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/aws_helpers.py @@ -6,8 +6,6 @@ import json from os import environ -# Locals - # Type hinting if typing.TYPE_CHECKING: from mypy_boto3_secretsmanager import SecretsManagerClient diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/contact_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/contact_helpers.py new file mode 100644 index 000000000..d93ccc18a --- /dev/null +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/contact_helpers.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +""" +Helpers for using the contact API endpoint +""" + +# Standard imports +from typing import List, Dict +from .globals import CONTACT_ENDPOINT + +# Local imports +from .requests_helpers import get_request_response_results + + +def get_contact_from_contact_id(contact_id: str) -> Dict: + """ + Get subject from the subject id + :param contact_id: + :return: + """ + # We have an internal id, convert to int + params = { + "contact_id": contact_id + } + + # Get subject + return get_request_response_results(CONTACT_ENDPOINT, params)[0] + + +def get_contact_from_contact_orcabus_id(contact_orcabus_id: str) -> Dict: + """ + Get contact from the contact id + :param contact_orcabus_id: + :return: + """ + params = { + "orcabus_id": contact_orcabus_id.split(".")[1] + } + + # Get contact + return get_request_response_results(CONTACT_ENDPOINT, params)[0] + + +def get_all_contacts() -> List[Dict]: + """ + Get all subjects + :return: + """ + + return get_request_response_results(CONTACT_ENDPOINT) diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/globals.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/globals.py index 6ce6e8a40..a0ad2902f 100644 --- a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/globals.py +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/globals.py @@ -4,3 +4,11 @@ # AWS PARAMETERS METADATA_SUBDOMAIN_NAME = "metadata" + +# API ENDPOINTS +LIBRARY_ENDPOINT = "api/v1/library" +SAMPLE_ENDPOINT = "api/v1/sample" +SUBJECT_ENDPOINT = "api/v1/subject" +PROJECT_ENDPOINT = "api/v1/project" +INDIVIDUAL_ENDPOINT = "api/v1/individual" +CONTACT_ENDPOINT = "api/v1/contact" \ No newline at end of file diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/individual_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/individual_helpers.py new file mode 100644 index 000000000..551e1d519 --- /dev/null +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/individual_helpers.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +""" +This module contains helper functions for the individual class. +""" + +#!/usr/bin/env python3 + + +# !/usr/bin/env python3 + + +""" +Helper functions for a subject +""" + +# Standard imports +from typing import Dict + +# Local imports +from .globals import INDIVIDUAL_ENDPOINT +from .requests_helpers import get_request_response_results + + +def get_individual_from_individual_id(individual_id: str) -> Dict: + """ + Get individual from the individual id + :param individual_id: + :return: + """ + # We have an internal id + params = { + "individual_id": individual_id + } + + # Get individual + return get_request_response_results(INDIVIDUAL_ENDPOINT, params)[0] + + +def get_individual_from_individual_orcabus_id(individual_orcabus_id: str) -> Dict: + """ + Get individual from the individual id + :param individual_orcabus_id: + :return: + """ + # We have an internal id + params = { + "orcabus_id": individual_orcabus_id + } + + # Get individual + return get_request_response_results(INDIVIDUAL_ENDPOINT, params)[0] + + +def get_all_individuals(): + """ + Get all samples from the sample database + :return: + """ + return get_request_response_results(INDIVIDUAL_ENDPOINT) diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/library_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/library_helpers.py index bf5d0ff49..675ae6ca1 100644 --- a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/library_helpers.py +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/library_helpers.py @@ -1,6 +1,7 @@ #!/usr/bin/env python from typing import Union, Dict, List +from .globals import LIBRARY_ENDPOINT from .requests_helpers import get_request_response_results @@ -10,20 +11,31 @@ def get_library_from_library_id(library_id: Union[int | str]) -> Dict: :param library_id: :return: """ - endpoint = "api/v1/library" + # Get library id + # We have an internal id, convert to int + params = { + "library_id": library_id + } + + # Get library + return get_request_response_results(LIBRARY_ENDPOINT, params)[0] + +def get_library_from_library_orcabus_id(library_orcabus_id: Union[int | str]) -> Dict: + """ + Get library from the library id + :param library_orcabus_id: + :return: + """ # Get library id - if isinstance(library_id, str): - # We have an internal id, convert to int - params = { - "library_id": library_id - } - else: - endpoint = f"{endpoint}/{library_id}" - params = {} + # We have an internal id, convert to int + params = { + "orcabus_id": library_orcabus_id + } # Get library - return get_request_response_results(endpoint, params)[0] + return get_request_response_results(LIBRARY_ENDPOINT, params)[0] + def get_subject_from_library_id(library_id: Union[int | str]) -> Dict: @@ -32,20 +44,10 @@ def get_subject_from_library_id(library_id: Union[int | str]) -> Dict: :param library_id: :return: """ - from .specimen_helpers import get_specimen_from_specimen_id from .subject_helpers import get_subject_from_subject_id - # Get the specimen linked to this library id - specimen_id = get_library_from_library_id(library_id)["specimen"] - - specimen_obj = get_specimen_from_specimen_id(specimen_id) - - if "subjects" in specimen_obj.keys() and len(specimen_obj["subjects"]) > 0: - subject_id = specimen_obj["subjects"][0] - elif "subject" in specimen_obj.keys(): - subject_id = specimen_obj["subject"] - else: - raise KeyError(f"Subject not found for library id: {library_id}") + # Get the subject linked to this library id + subject_id = get_library_from_library_id(library_id)["subject"]['subjectId'] return get_subject_from_subject_id(subject_id) @@ -121,6 +123,4 @@ def get_all_libraries() -> List[Dict]: Collect all libraries from the database :return: """ - endpoint = "api/v1/library" - - return get_request_response_results(endpoint) + return get_request_response_results(LIBRARY_ENDPOINT) diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/project_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/project_helpers.py new file mode 100644 index 000000000..723f29235 --- /dev/null +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/project_helpers.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +""" +Helpers for using the project API endpoint +""" + +# Standard imports +from typing import List, Dict +from .globals import PROJECT_ENDPOINT + +# Local imports +from .requests_helpers import get_request_response_results + + +def get_project_from_project_id(project_id: str) -> Dict: + """ + Get subject from the subject id + :param project_id: + :return: + """ + # We have an internal id, convert to int + params = { + "project_id": project_id + } + + # Get subject + return get_request_response_results(PROJECT_ENDPOINT, params)[0] + + +def get_project_from_project_orcabus_id(project_orcabus_id: str) -> Dict: + """ + Get project from the project id + :param project_orcabus_id: + :return: + """ + params = { + "orcabus_id": project_orcabus_id.split(".")[1] + } + + # Get project + return get_request_response_results(PROJECT_ENDPOINT, params)[0] + + +def get_all_projects() -> List[Dict]: + """ + Get all subjects + :return: + """ + + return get_request_response_results(PROJECT_ENDPOINT) diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/sample_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/sample_helpers.py new file mode 100644 index 000000000..1fa84bcb0 --- /dev/null +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/sample_helpers.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 + + +# !/usr/bin/env python3 + + +""" +Helper functions for a subject +""" + +# Standard imports +from typing import List, Union, Dict + +# Local imports +from .globals import SAMPLE_ENDPOINT, LIBRARY_ENDPOINT +from .requests_helpers import get_request_response_results + + +def get_sample_from_sample_id(sample_id: str) -> Dict: + """ + Get sample from the sample id + :param sample_id: + :return: + """ + # We have an internal id + params = { + "sample_id": sample_id + } + + # Get sample + return get_request_response_results(SAMPLE_ENDPOINT, params)[0] + + +def get_sample_from_sample_orcabus_id(sample_orcabus_id: str) -> Dict: + """ + Get sample from the sample id + :param sample_orcabus_id: + :return: + """ + # We have an internal id + params = { + "orcabus_id": sample_orcabus_id + } + + # Get sample + return get_request_response_results(SAMPLE_ENDPOINT, params)[0] + + + +def list_libraries_in_sample(sample_id: Union[str, int]) -> List[Dict]: + """ + Given a sample_id, list the samples in the subject + :param sample_id: + :return: + """ + # Get the subject + return list( + filter( + lambda library_iter: library_iter.get("sample").get("sampleId") == sample_id, + get_request_response_results(LIBRARY_ENDPOINT) + ) + ) + + +def get_all_samples(): + """ + Get all samples from the sample database + :return: + """ + return get_request_response_results(SAMPLE_ENDPOINT) diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/specimen_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/specimen_helpers.py deleted file mode 100644 index cc165556a..000000000 --- a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/specimen_helpers.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 - - -# !/usr/bin/env python3 - - -""" -Helper functions for a subject -""" - -# Standard imports -from typing import List, Union, Dict - -# Local imports -from .requests_helpers import get_request_response_results - - -def get_specimen_from_specimen_id(specimen_id: Union[str, int]) -> Dict: - """ - Get specimen from the specimen id - :param specimen_id: - :return: - """ - endpoint = "specimen" - - # Get specimen id - if isinstance(specimen_id, str): - # We have an internal id, convert to int - params = { - "specimen_id": specimen_id - } - else: - endpoint = f"{endpoint}/{specimen_id}" - params = {} - - # Get specimen - return get_request_response_results(endpoint, params)[0] - - -def list_libraries_in_specimen(specimen_id: Union[str, int]) -> List[Dict]: - """ - Given a specimen_id id, list the specimens in the subject - :param specimen_id: - :return: - """ - - # If subject id is a string, we have the internal id (SBJ...) - specimen = get_specimen_from_specimen_id(specimen_id) - - endpoint = f"api/v1/library" - - # Get the subject - return list( - filter( - lambda library_iter: library_iter.get("specimen") == specimen.get("id"), - get_request_response_results(endpoint) - ) - ) - - -def get_all_specimens(): - """ - Get all specimens from the specimen database - :return: - """ - endpoint = "api/v1/specimen" - - return get_request_response_results(endpoint) - diff --git a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/subject_helpers.py b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/subject_helpers.py index e2bfa6514..f7e8a99ce 100644 --- a/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/subject_helpers.py +++ b/lib/workload/components/python-metadata-tools-layer/metadata_tools_layer/src/metadata_tools/utils/subject_helpers.py @@ -8,39 +8,50 @@ # Standard imports from typing import List, Union, Dict +from .globals import SUBJECT_ENDPOINT # Local imports from .requests_helpers import get_request_response_results -def get_subject_from_subject_id(subject_id: Union[str, int]) -> Dict: +def get_subject_from_subject_id(subject_id: str) -> Dict: """ Get subject from the subject id :param subject_id: :return: """ - endpoint = "api/v1/subject" + # We have an internal id, convert to int + params = { + "subject_id": subject_id + } + # Get subject + return get_request_response_results(SUBJECT_ENDPOINT, params)[0] + + + +def get_subject_from_subject_orcabus_id(subject_orcabus_id: str) -> Dict: + """ + Get subject from the subject id + :param subject_orcabus_id: + :return: + """ # Get subject id - if isinstance(subject_id, str): - # We have an internal id, convert to int - params = { - "subject_id": subject_id - } - else: - endpoint = f"{endpoint}/{subject_id}" - params = {} + # We have an internal id, convert to int + params = { + "orcabus_id": subject_orcabus_id + } # Get subject - return get_request_response_results(endpoint, params)[0] + return get_request_response_results(SUBJECT_ENDPOINT, params)[0] -def list_specimens_in_subject(subject_id: Union[str, int]) -> List[Dict]: +def list_samples_in_subject(subject_id: Union[str, int]) -> List[Dict]: """ - Given a subject id, list the specimens in the subject + Given a subject id, list the samples in the subject :param subject_id: :return: """ - from metadata_tools import get_all_specimens + from metadata_tools import get_all_samples # Get ID For Subject subject = get_subject_from_subject_id(subject_id) @@ -48,10 +59,12 @@ def list_specimens_in_subject(subject_id: Union[str, int]) -> List[Dict]: # Get the subject return list( filter( - lambda specimen_iter: - subject.get("id") == specimen_iter.get("subjects")[0] if "subjects" in specimen_iter.keys() - else specimen_iter.get("subject") == subject.get("id"), - get_all_specimens() + lambda sample_iter: + subject.get("id") == sample_iter.get("subjects")[0] + if "subjects" in sample_iter.keys() + else + sample_iter.get("subject") == subject.get("id"), + get_all_samples() ) ) @@ -62,14 +75,14 @@ def list_libraries_in_subject(subject_id: str) -> List[Dict]: :param subject_id: :return: """ - from .specimen_helpers import list_libraries_in_specimen + from .sample_helpers import list_libraries_in_sample library_list = [] - specimens_list = list_specimens_in_subject(subject_id) + samples_list = list_samples_in_subject(subject_id) - for specimen_iter in specimens_list: - library_list.extend(list_libraries_in_specimen(specimen_iter.get("id"))) + for sample_iter in samples_list: + library_list.extend(list_libraries_in_sample(sample_iter.get("id"))) return library_list @@ -79,7 +92,4 @@ def get_all_subjects() -> List[Dict]: Get all subjects :return: """ - - endpoint = "api/v1/subject" - - return get_request_response_results(endpoint) + return get_request_response_results(SUBJECT_ENDPOINT) diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/Readme.md b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/Readme.md similarity index 100% rename from lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/Readme.md rename to lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/Readme.md diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/images/workflowrunstatechange_input_maker_step_function_sfn.png b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/images/workflowrunstatechange_input_maker_step_function_sfn.png similarity index 100% rename from lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/images/workflowrunstatechange_input_maker_step_function_sfn.png rename to lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/images/workflowrunstatechange_input_maker_step_function_sfn.png diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/index.ts b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/index.ts similarity index 69% rename from lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/index.ts rename to lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/index.ts index d14033b58..99d09be7a 100644 --- a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/index.ts +++ b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/index.ts @@ -5,15 +5,12 @@ Collect the engine parameters required for the workflow run state change to be s import { Construct } from 'constructs'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import path from 'path'; import * as cdk from 'aws-cdk-lib'; -import { PythonLambdaUuidConstruct } from '../python-lambda-uuid-generator-function'; import { PythonLambdaFlattenListOfObjectsConstruct } from '../python-lambda-flatten-list-of-objects'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import { Duration } from 'aws-cdk-lib'; @@ -22,15 +19,8 @@ export interface WorkflowRunStateChangeInternalInputMakerProps { /* Object name prefixes */ stateMachinePrefix: string; lambdaPrefix: string; - rulePrefix: string; - /* Table configs */ - tableObj: dynamodb.ITableV2; - tablePartitionName: string; /* Event trigger configs */ eventBusObj: events.IEventBus; - triggerSource: string; - triggerStatus: string; - triggerDetailType?: string; outputSource: string; /* Workflow metadata constants */ workflowName: string; @@ -45,11 +35,10 @@ export interface WorkflowRunStateChangeInternalInputMakerProps { icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct extends Construct { +export class GenerateWorkflowRunStateChangeReadyConstruct extends Construct { public readonly stepFunctionObj: sfn.StateMachine; - public readonly defaultTriggerDetailType = 'WorkflowDraftRunStateChange'; public readonly outputDetailType = 'WorkflowRunStateChange'; - private readonly portalRunPartitionName = 'portal_run'; + private readonly readyStatus = 'READY'; constructor(scope: Construct, id: string, props: WorkflowRunStateChangeInternalInputMakerProps) { super(scope, id); @@ -63,7 +52,7 @@ export class WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct e this, 'fill_placeholders_in_event_payload_data_lambda', { - functionName: `${props.lambdaPrefix}-fill-placeholders-in-event-payload-data`, + functionName: `${props.lambdaPrefix}-fill-engine-parameters`, entry: path.join(__dirname, 'lambdas', 'fill_placeholders_in_event_payload_data_py'), index: 'fill_placeholders_in_event_payload_data.py', runtime: lambda.Runtime.PYTHON_3_12, @@ -133,33 +122,24 @@ export class WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct e /* Part 2 - Build the AWS State Machine */ - /* Build the uuid generator lambda */ - const uuidGeneratorLambda = new PythonLambdaUuidConstruct(this, 'uuid_generator_lambda'); - - // FIXME - sfn should check that the data object in the input - // FIXME matches the dataobject in the database first before raising the event this.stepFunctionObj = new sfn.StateMachine(this, 'StateMachine', { - stateMachineName: `${props.stateMachinePrefix}-draft-to-ready-sfn`, + stateMachineName: `${props.stateMachinePrefix}-ready-sfn`, definitionBody: sfn.DefinitionBody.fromFile( path.join( __dirname, 'step_functions_templates', - 'workflowrunstatechange_draft_to_ready_step_function_template.asl.json' + 'workflowrunstatechange_generate_ready_step_function_template.asl.json' ) ), definitionSubstitutions: { - /* Lambdas */ - __generate_uuid_lambda_function_arn__: - uuidGeneratorLambda.lambdaObj.currentVersion.functionArn, - /* Table configurations */ - __table_name__: props.tableObj.tableName, - __workflow_type_partition_name__: props.tablePartitionName, - __portal_run_partition_name__: this.portalRunPartitionName, /* Event configurations */ __event_output_source__: props.outputSource, __detail_type__: this.outputDetailType, __event_bus_name__: props.eventBusObj.eventBusName, + __ready_status__: this.readyStatus, /* Workflow name */ + __workflow_name__: props.workflowName, + __workflow_version__: props.workflowVersion, __payload_version__: props.payloadVersion, /* Nested statemachine */ __engine_parameters_maker_state_machine_arn__: @@ -173,15 +153,8 @@ export class WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct e }); /* - Part 3 - Connect permissions + Part 3 - Connect permissions between state-machines */ - /* Allow step functions to invoke the lambda */ - [uuidGeneratorLambda.lambdaObj].forEach((lambdaObj) => { - lambdaObj.currentVersion.grantInvoke(this.stepFunctionObj.role); - }); - - /* Allow step function to write to table */ - props.tableObj.grantReadWriteData(this.stepFunctionObj.role); /* Allow step function to call nested state machine */ // Because we run a nested state machine, we need to add the permissions to the state machine role @@ -194,34 +167,9 @@ export class WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct e actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], }) ); - engineParameterGeneratorStateMachineSfn.grantStartExecution( - this.stepFunctionObj.role - ); + engineParameterGeneratorStateMachineSfn.grantStartExecution(this.stepFunctionObj); /* Allow step function to send events */ - props.eventBusObj.grantPutEventsTo(this.stepFunctionObj.role); - - /* - Part 4 - Set up a rule to trigger the state machine - */ - const rule = new events.Rule(this, 'workflowrunstatechangeparser_event_rule', { - ruleName: `${props.rulePrefix}-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [props.triggerSource], - detailType: [props.triggerDetailType || this.defaultTriggerDetailType], - detail: { - status: [{ 'equals-ignore-case': props.triggerStatus }], - workflowName: [{ 'equals-ignore-case': props.workflowName }], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(this.stepFunctionObj, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); + props.eventBusObj.grantPutEventsTo(this.stepFunctionObj); } } diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/lambdas/fill_placeholders_in_event_payload_data_py/fill_placeholders_in_event_payload_data.py b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/lambdas/fill_placeholders_in_event_payload_data_py/fill_placeholders_in_event_payload_data.py similarity index 100% rename from lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/lambdas/fill_placeholders_in_event_payload_data_py/fill_placeholders_in_event_payload_data.py rename to lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/lambdas/fill_placeholders_in_event_payload_data_py/fill_placeholders_in_event_payload_data.py diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/lambdas/fill_placeholders_in_event_payload_data_py/requirements.txt b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/lambdas/fill_placeholders_in_event_payload_data_py/requirements.txt similarity index 100% rename from lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/lambdas/fill_placeholders_in_event_payload_data_py/requirements.txt rename to lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/lambdas/fill_placeholders_in_event_payload_data_py/requirements.txt diff --git a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json similarity index 98% rename from lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json rename to lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json index 4afdfa3ad..5dd9da428 100644 --- a/lib/workload/components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json +++ b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/generate_analysis_engine_parameters_from_ssm_sfn_template.asl.json @@ -36,10 +36,10 @@ }, "Set Map Output Blank": { "Type": "Pass", - "End": true, - "Result": { + "Parameters": { "output.$": "States.StringToJson(States.Format('\\{\"{}\":\"{}\"\\}', $.engine_parameter_key, ''))" - } + }, + "End": true }, "Get EngineParameter URI": { "Type": "Task", diff --git a/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/workflowrunstatechange_generate_ready_step_function_template.asl.json b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/workflowrunstatechange_generate_ready_step_function_template.asl.json new file mode 100644 index 000000000..42e1e742d --- /dev/null +++ b/lib/workload/components/sfn-generate-workflowrunstatechange-ready-event/step_functions_templates/workflowrunstatechange_generate_ready_step_function_template.asl.json @@ -0,0 +1,80 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "move inputs", + "States": { + "move inputs": { + "Type": "Pass", + "Next": "Get Workflow Run Engine Parameters", + "Parameters": { + "input_event_detail_draft.$": "$.StatePayload" + } + }, + "Get Workflow Run Engine Parameters": { + "Type": "Task", + "Resource": "arn:aws:states:::states:startExecution.sync:2", + "Parameters": { + "StateMachineArn": "${__engine_parameters_maker_state_machine_arn__}", + "Input": { + "portal_run_id.$": "$.input_event_detail_draft.portal_run_id", + "workflow_name": "${__workflow_name__}", + "workflow_version": "${__workflow_version__}", + "event_data_inputs.$": "$.input_event_detail_draft.data_inputs", + "ssm_parameters_list": [ + { + "engine_parameter_key": "outputUri", + "ssm_name": "${__output_uri_ssm_parameter_name__}" + }, + { + "engine_parameter_key": "logsUri", + "ssm_name": "${__logs_uri_ssm_parameter_name__}" + }, + { + "engine_parameter_key": "cacheUri", + "ssm_name": "${__cache_uri_ssm_parameter_name__}" + }, + { + "engine_parameter_key": "projectId", + "ssm_name": "${__project_id_ssm_parameter_name__}" + } + ] + } + }, + "Next": "EventBridge PutEvents", + "ResultPath": "$.set_workflow_run_engine_parameters", + "ResultSelector": { + "engine_parameters.$": "$.Output.engine_parameters" + } + }, + "EventBridge PutEvents": { + "Type": "Task", + "Resource": "arn:aws:states:::events:putEvents", + "Parameters": { + "Entries": [ + { + "Source": "${__event_output_source__}", + "EventBusName": "${__event_bus_name__}", + "DetailType": "${__detail_type__}", + "Detail": { + "portalRunId.$": "$.input_event_detail_draft.portal_run_id", + "timestamp.$": "$$.State.EnteredTime", + "status": "${__ready_status__}", + "workflowName": "${__workflow_name__}", + "workflowVersion": "${__workflow_version__}", + "workflowRunName.$": "$.input_event_detail_draft.workflow_run_name", + "linkedLibraries.$": "$.input_event_detail_draft.linked_libraries", + "payload": { + "version": "${__payload_version__}", + "data": { + "inputs.$": "$.input_event_detail_draft.data_inputs", + "engineParameters.$": "$.set_workflow_run_engine_parameters.engine_parameters", + "tags.$": "$.input_event_detail_draft.data_tags" + } + } + } + } + ] + }, + "End": true + } + } +} diff --git a/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/index.ts b/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/index.ts index 65cfdd601..e248bf636 100644 --- a/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/index.ts +++ b/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/index.ts @@ -17,9 +17,6 @@ import { Duration } from 'aws-cdk-lib'; export interface WorkflowRunStateChangeInternalInputMakerProps { /* Object name prefixes */ stateMachinePrefix: string; - /* Table configs */ - tableObj: dynamodb.ITableV2; - portalRunTablePartitionName: string; /* Workflow metadata constants */ workflowName: string; workflowVersion: string; @@ -79,9 +76,6 @@ export class WorkflowDraftRunStateChangeCommonPreambleConstruct extends Construc portalRunIdLambda.currentVersion.functionArn, __generate_workflow_run_name_lambda_function_arn__: workflowRunNameLambda.currentVersion.functionArn, - /* Table configurations */ - __table_name__: props.tableObj.tableName, - __portal_run_partition_name__: props.portalRunTablePartitionName, /* Workflow name */ __workflow_name__: props.workflowName, __workflow_version__: props.workflowVersion, @@ -95,8 +89,5 @@ export class WorkflowDraftRunStateChangeCommonPreambleConstruct extends Construc [workflowRunNameLambda, portalRunIdLambda].forEach((lambdaObj) => { lambdaObj.currentVersion.grantInvoke(this.stepFunctionObj.role); }); - - /* Allow step function to write to table */ - props.tableObj.grantReadWriteData(this.stepFunctionObj.role); } } diff --git a/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/step_functions_templates/workflowdraftrunstatechange_preamble_template.asl.json b/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/step_functions_templates/workflowdraftrunstatechange_preamble_template.asl.json index a0c6aad64..4fce64ac3 100644 --- a/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/step_functions_templates/workflowdraftrunstatechange_preamble_template.asl.json +++ b/lib/workload/components/sfn-workflowdraftrunstatechange-common-preamble/step_functions_templates/workflowdraftrunstatechange_preamble_template.asl.json @@ -59,29 +59,12 @@ "BackoffRate": 2 } ], - "Next": "Save Portal Run ID To Input Maker DB", + "Next": "Wait 1 Second", "ResultPath": "$.generate_workflow_run_name_step", "ResultSelector": { "workflow_run_name.$": "$.Payload.workflow_run_name" } }, - "Save Portal Run ID To Input Maker DB": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id.$": "$.generate_portal_run_id_step.portal_run_id", - "id_type": "${__portal_run_partition_name__}", - "workflow_name": "${__workflow_name__}", - "workflow_version": "${__workflow_version__}", - "workflow_run_name.$": "$.generate_workflow_run_name_step.workflow_run_name", - "status": "DRAFT" - } - }, - "Next": "Wait 1 Second", - "ResultPath": null - }, "Wait 1 Second": { "Type": "Wait", "Seconds": 1, diff --git a/lib/workload/stateful/stacks/pieriandx-pipeline-dynamo-db/deploy/index.ts b/lib/workload/stateful/stacks/pieriandx-pipeline-dynamo-db/deploy/index.ts new file mode 100644 index 000000000..7a4513817 --- /dev/null +++ b/lib/workload/stateful/stacks/pieriandx-pipeline-dynamo-db/deploy/index.ts @@ -0,0 +1,26 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { DynamodbPartitionedPipelineConstruct } from '../../../../components/dynamodb-partitioned-table'; + +export interface PierianDxPipelineTableConfig { + dynamodbTableName: string; +} + +export type PierianDxPipelineTableStackProps = PierianDxPipelineTableConfig & cdk.StackProps; + +export class PierianDxPipelineTable extends cdk.Stack { + constructor(scope: Construct, id: string, props: PierianDxPipelineTableStackProps) { + super(scope, id, props); + + /* + Initialise dynamodb table, where portal_run_id is the primary sort key + */ + const dynamodb_table = new DynamodbPartitionedPipelineConstruct( + this, + 'pieriandx_pipeline_table', + { + tableName: props.dynamodbTableName, + } + ); + } +} diff --git a/lib/workload/stateful/stacks/stacky-mcstackface-dynamodb/index.ts b/lib/workload/stateful/stacks/stacky-mcstackface-dynamodb/index.ts index 23f2630ff..3cba709e1 100644 --- a/lib/workload/stateful/stacks/stacky-mcstackface-dynamodb/index.ts +++ b/lib/workload/stateful/stacks/stacky-mcstackface-dynamodb/index.ts @@ -3,6 +3,7 @@ import { Construct } from 'constructs'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import { DynamodbPartitionedPipelineConstruct } from '../../../components/dynamodb-partitioned-table'; import * as cdk from 'aws-cdk-lib'; +import { mockPierianDxGlueTableName } from '../../../../../config/constants'; export interface StackyStatefulTablesConfig { dynamodbInstrumentRunManagerTableName: string; @@ -14,6 +15,7 @@ export interface StackyStatefulTablesConfig { dynamodbWtsGlueTableName: string; dynamodbUmccriseGlueTableName: string; dynamodbRnasumGlueTableName: string; + dynamodbPieriandxGlueTableName: string; removalPolicy?: RemovalPolicy; } @@ -29,6 +31,7 @@ export class StackyStatefulTablesStack extends Stack { public readonly wtsGlueTable: dynamodb.ITableV2; public readonly umccriseGlueTable: dynamodb.ITableV2; public readonly rnasumGlueTable: dynamodb.ITableV2; + public readonly pieriandxGlueTable: dynamodb.ITableV2; constructor(scope: Construct, id: string, props: StackProps & StackyStatefulTablesStackProps) { super(scope, id, props); @@ -115,5 +118,13 @@ export class StackyStatefulTablesStack extends Stack { tableName: props.dynamodbRnasumGlueTableName, removalPolicy: props.removalPolicy, }).tableObj; + + /* + Initialise dynamodb table for the pieriandx glue service + */ + this.pieriandxGlueTable = new DynamodbPartitionedPipelineConstruct(this, 'pieriandxGlueTable', { + tableName: props.dynamodbPieriandxGlueTableName, + removalPolicy: props.removalPolicy, + }).tableObj; } } diff --git a/lib/workload/stateful/statefulStackCollectionClass.ts b/lib/workload/stateful/statefulStackCollectionClass.ts index 86d6648f9..41f3db05d 100644 --- a/lib/workload/stateful/statefulStackCollectionClass.ts +++ b/lib/workload/stateful/statefulStackCollectionClass.ts @@ -45,6 +45,11 @@ import { RnasumIcav2PipelineTable, RnasumIcav2PipelineTableStackProps, } from './stacks/rnasum-pipeline-dynamo-db/deploy/stack'; +import { + PierianDxPipelineTable, + PierianDxPipelineTableStackProps, +} from './stacks/pieriandx-pipeline-dynamo-db/deploy'; +import { getPierianDxPipelineTableStackProps } from '../../../config/stacks/pierianDxPipelineManager'; export interface StatefulStackCollectionProps { dataBucketStackProps: DataBucketStackProps; @@ -61,6 +66,7 @@ export interface StatefulStackCollectionProps { rnasumIcav2PipelineTableStackProps: RnasumIcav2PipelineTableStackProps; BclConvertTableStackProps: BclConvertTableStackProps; stackyStatefulTablesStackProps: StackyStatefulTablesStackProps; + pierianDxPipelineTableStackProps: PierianDxPipelineTableStackProps; } export class StatefulStackCollection { @@ -80,6 +86,7 @@ export class StatefulStackCollection { readonly rnasumIcav2PipelineTableStack: Stack; readonly BclConvertTableStack: Stack; readonly stackyStatefulTablesStack: Stack; + readonly pierianDxPipelineTableStack: Stack; constructor( scope: Construct, @@ -178,6 +185,7 @@ export class StatefulStackCollection { ...this.createTemplateProps(env, 'BclConvertTableStack'), ...statefulConfiguration.BclConvertTableStackProps, }); + this.stackyStatefulTablesStack = new StackyStatefulTablesStack( scope, 'StackyStatefulTablesStack', @@ -186,6 +194,15 @@ export class StatefulStackCollection { ...statefulConfiguration.stackyStatefulTablesStackProps, } ); + + this.pierianDxPipelineTableStack = new PierianDxPipelineTable( + scope, + 'PierianDxPipelineTableStack', + { + ...this.createTemplateProps(env, 'PierianDxPipelineTableStack'), + ...statefulConfiguration.pierianDxPipelineTableStackProps, + } + ); } /** diff --git a/lib/workload/stateless/stacks/bssh-icav2-fastq-copy-manager/Readme.md b/lib/workload/stateless/stacks/bssh-icav2-fastq-copy-manager/Readme.md index 84f07b165..45f438ec3 100644 --- a/lib/workload/stateless/stacks/bssh-icav2-fastq-copy-manager/Readme.md +++ b/lib/workload/stateless/stacks/bssh-icav2-fastq-copy-manager/Readme.md @@ -588,7 +588,7 @@ To decode these see the headers below ## Lambdas in this directory -All lambdas run on python 3.11 or higher. +All lambdas run on python 3.12 or higher. ### Process BCLConvert Output diff --git a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/constructs/cttsov2-icav2-manager/index.ts b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/constructs/cttsov2-icav2-manager/index.ts index 7a73e9b8f..55d704d0c 100644 --- a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/constructs/cttsov2-icav2-manager/index.ts +++ b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/constructs/cttsov2-icav2-manager/index.ts @@ -36,8 +36,10 @@ interface Cttsov2Icav2PipelineManagerConstructProps { /* Extras */ // Lambdas // SFN Input Lambdas - uploadSamplesheetToCacheDirLambdaObj: PythonFunction; // __dirname + '/../../../lambdas/upload_samplesheet_to_cache_dir_py' - generateCopyManifestDictLambdaObj: PythonFunction; // __dirname + '/../../../lambdas/generate_copy_manifest_dict_py' + uploadSamplesheetToCacheDirLambdaObj: PythonFunction; + generateCopyManifestDictLambdaObj: PythonFunction; + checkNumRunningSfnsLambdaObj: PythonFunction; + getRandomNumberLambdaObj: PythonFunction; // SFN Output lambdas deleteCacheUriLambdaObj: PythonFunction; setOutputJsonLambdaObj: PythonFunction; @@ -60,7 +62,7 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { ); // Specify the statemachine and replace the arn placeholders with the lambda arns defined above - const configure_inputs_sfn = new sfn.StateMachine( + const configureInputsSfn = new sfn.StateMachine( this, 'cttso_v2_launch_step_functions_state_machine', { @@ -74,6 +76,10 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { props.generateCopyManifestDictLambdaObj.currentVersion.functionArn, __upload_samplesheet_to_cache_dir__: props.uploadSamplesheetToCacheDirLambdaObj.currentVersion.functionArn, + __get_variable_number_of_seconds_lambda_function_arn__: + props.getRandomNumberLambdaObj.currentVersion.functionArn, + __check_number_of_copy_jobs_running_lambda_function_arn__: + props.checkNumRunningSfnsLambdaObj.currentVersion.functionArn, /* Subfunction state machines */ __copy_icav2_files_state_machine_arn__: props.icav2CopyFilesStateMachineObj.stateMachineArn, @@ -84,18 +90,21 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { ); // Grant lambda invoke permissions to the state machine - [props.generateCopyManifestDictLambdaObj, props.uploadSamplesheetToCacheDirLambdaObj].forEach( - (lambda_obj) => { - lambda_obj.currentVersion.grantInvoke(configure_inputs_sfn.role); - } - ); + [ + props.generateCopyManifestDictLambdaObj, + props.uploadSamplesheetToCacheDirLambdaObj, + props.getRandomNumberLambdaObj, + props.checkNumRunningSfnsLambdaObj, + ].forEach((lambda_obj) => { + lambda_obj.currentVersion.grantInvoke(configureInputsSfn); + }); // Allow state machine to read/write to dynamodb table - props.dynamodbTableObj.grantReadWriteData(configure_inputs_sfn.role); + props.dynamodbTableObj.grantReadWriteData(configureInputsSfn); // Because we run a nested state machine, we need to add the permissions to the state machine role // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr - configure_inputs_sfn.addToRolePolicy( + configureInputsSfn.addToRolePolicy( new iam.PolicyStatement({ resources: [ `arn:aws:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule`, @@ -105,14 +114,24 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { ); // Add state machine execution permissions to stateMachine role - props.icav2CopyFilesStateMachineObj.grantStartExecution(configure_inputs_sfn.role); + props.icav2CopyFilesStateMachineObj.grantStartExecution(configureInputsSfn); + + // Update checkNumRunningSfnsLambdaObj env var to include the state machine arn of + // the icav2 copy files sfn + props.checkNumRunningSfnsLambdaObj.addEnvironment( + 'SFN_ARN', + props.icav2CopyFilesStateMachineObj.stateMachineArn + ); + + // Allow the check num running sfns lambda to list the number of running icav2 copy file sfns running + props.icav2CopyFilesStateMachineObj.grantRead(props.checkNumRunningSfnsLambdaObj); /* Part 2: Configure the lambdas and outputs step function Quite a bit more complicated than regular ICAv2 workflow setup since we need to 1. Generate the outputs json from a nextflow pipeline (which doesn't have a json outputs endpoint) 2. Delete the cache fastqs we generated in the configure inputs json step function - */ + */ // Add icav2 secrets permissions to lambdas [ @@ -122,10 +141,10 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { props.compressVcfLambdaObj, props.checkSuccessSampleLambdaObj, ].forEach((lambda_obj) => { - props.icav2AccessTokenSecretObj.grantRead(lambda_obj.currentVersion.role); + props.icav2AccessTokenSecretObj.grantRead(lambda_obj.currentVersion); }); - const configure_outputs_sfn = new sfn.StateMachine(this, 'sfn_configure_outputs_json', { + const configureOutputsSfn = new sfn.StateMachine(this, 'sfn_configure_outputs_json', { stateMachineName: `${props.stateMachinePrefix}-configure-outputs-json`, // defintiontemplate definitionBody: DefinitionBody.fromFile(props.generateOutputJsonSfnTemplatePath), @@ -147,7 +166,7 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { }); // Allow the state machine to read/write to dynamodb table - props.dynamodbTableObj.grantReadWriteData(configure_outputs_sfn.role); + props.dynamodbTableObj.grantReadWriteData(configureOutputsSfn); // Allow the state machine to invoke the lambdas [ @@ -157,7 +176,7 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { props.compressVcfLambdaObj, props.checkSuccessSampleLambdaObj, ].forEach((lambda_obj) => { - lambda_obj.currentVersion.grantInvoke(configure_outputs_sfn.role); + lambda_obj.currentVersion.grantInvoke(configureOutputsSfn); }); /* Add ICAv2 WfmworkflowRunStateChange wrapper around launch state machine */ @@ -179,7 +198,7 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { internalEventSource: props.internalEventSource, // What we push back to the orcabus /* State machines to run (underneath) */ /* The inputs generation statemachine */ - generateInputsJsonSfn: configure_inputs_sfn, + generateInputsJsonSfn: configureInputsSfn, /* Internal workflowRunStateChange event details */ workflowName: props.workflowType, workflowVersion: props.workflowVersion, @@ -188,7 +207,7 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { // Create statemachine for handling any state changes of the pipeline // Generate state machine for handling the external ICAv2 event - const handle_external_icav2_event_sfn = new Icav2AnalysisEventHandlerConstruct( + const handleExternalIcav2EventSfn = new Icav2AnalysisEventHandlerConstruct( this, 'handle_interop_qc_ready_event', { @@ -198,7 +217,7 @@ export class Cttsov2Icav2PipelineManagerConstruct extends Construct { eventBusName: props.eventBusObj.eventBusName, icaEventPipeName: props.icaEventPipeName, internalEventSource: props.internalEventSource, - generateOutputsJsonSfn: configure_outputs_sfn, + generateOutputsJsonSfn: configureOutputsSfn, workflowName: props.workflowType, workflowVersion: props.workflowVersion, serviceVersion: props.serviceVersion, diff --git a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/stack.ts b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/stack.ts index a12c9defb..da35eabdb 100644 --- a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/stack.ts +++ b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/deploy/stack.ts @@ -22,8 +22,8 @@ export interface Cttsov2Icav2PipelineManagerConfig { eventBusName: string; icaEventPipeName: string; /* - Event handling - */ + Event handling + */ workflowType: string; workflowVersion: string; serviceVersion: string; @@ -31,8 +31,8 @@ export interface Cttsov2Icav2PipelineManagerConfig { internalEventSource: string; detailType: string; /* - Names for statemachines - */ + Names for statemachines + */ stateMachinePrefix: string; } @@ -44,31 +44,31 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { super(scope, id, props); // Get dynamodb table for construct - const dynamodb_table_obj = dynamodb.TableV2.fromTableName( + const dynamodbTableObj = dynamodb.TableV2.fromTableName( this, 'dynamodb_table', props.dynamodbTableName ); // Get ICAv2 Access token secret object for construct - const icav2_access_token_secret_obj = secretsManager.Secret.fromSecretNameV2( + const icav2AccessTokenSecretObj = secretsManager.Secret.fromSecretNameV2( this, 'icav2_secrets_object', props.icav2TokenSecretId ); // Get the copy batch state machine name - const icav2_copy_files_state_machine_obj = new ICAv2CopyFilesConstruct( + const icav2CopyFilesStateMachineObj = new ICAv2CopyFilesConstruct( this, 'icav2_copy_files_state_machine_obj', { - icav2JwtSecretParameterObj: icav2_access_token_secret_obj, + icav2JwtSecretParameterObj: icav2AccessTokenSecretObj, stateMachineName: `${props.stateMachinePrefix}-icav2-copy-files-sfn`, } ); // Set ssm parameter object list - const pipeline_id_ssm_obj_list = ssm.StringParameter.fromStringParameterName( + const pipelineIdSsmObjList = ssm.StringParameter.fromStringParameterName( this, props.pipelineIdSsmPath, props.pipelineIdSsmPath @@ -79,16 +79,18 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { /* Build lambdas - */ + */ + /* Part 1: Set up the lambdas needed for the input json generation state machine Quite a bit more complicated than regular ICAv2 workflow setup since we need to 1. Convert the samplesheet from json into csv format 2. Upload the samplesheet to icav2 3. Copy fastqs into a particular directory setup type - */ + 4. Regulate how many fastqs are copied at a time given ICAv2 cannot limit this itself and falls over + */ - const generate_copy_manifest_dict_lambda_obj = new PythonFunction( + const generateCopyManifestDictLambdaObj = new PythonFunction( this, 'generate_copy_manifest_dict_lambda_python_function', { @@ -100,13 +102,13 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { memorySize: 1024, timeout: Duration.seconds(60), environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, }, } ); // upload_samplesheet_to_cache_dir_py lambda - const upload_samplesheet_to_cache_dir_lambda_obj = new PythonFunction( + const uploadSamplesheetToCacheDirLambdaObj = new PythonFunction( this, 'upload_samplesheet_to_cache_dir_lambda_python_function', { @@ -118,17 +120,43 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { memorySize: 1024, timeout: Duration.seconds(60), environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, }, } ); + const getRandomNumberLambdaObj = new PythonFunction( + this, + 'get_random_number_lambda_python_function', + { + entry: path.join(__dirname, '../lambdas/get_random_number_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_random_number.py', + handler: 'handler', + } + ); + + // We need to add SFN_ARN to the env var list once we have it + // We also need to update the permissions to allow this function to list executions + const checkNumRunningSfns = new PythonFunction( + this, + 'check_num_running_sfns_lambda_python_function', + { + entry: path.join(__dirname, '../lambdas/check_num_running_sfns_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'check_num_running_sfns.py', + handler: 'handler', + } + ); + /* - Part 2: Build lambdas for output json generation - */ + Part 2: Build lambdas for output json generation + */ // Delete the cache uri directory lambda - const delete_cache_uri_lambda_function = new PythonFunction( + const deleteCacheUriLambdaFunction = new PythonFunction( this, 'delete_cache_uri_lambda_python_function', { @@ -140,13 +168,13 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { memorySize: 1024, timeout: Duration.seconds(60), environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, }, } ); // Set the output json lambda - const set_output_json_lambda_function = new PythonFunction( + const setOutputJsonLambdaFunction = new PythonFunction( this, 'set_output_json_lambda_python_function', { @@ -158,13 +186,13 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { memorySize: 1024, timeout: Duration.seconds(60), environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, }, } ); // Get vcfs - const get_vcfs_lambda_function = new PythonFunction(this, 'get_vcfs_lambda_python_function', { + const getVcfsLambdaFunction = new PythonFunction(this, 'get_vcfs_lambda_python_function', { entry: path.join(__dirname, '../lambdas/find_all_vcf_files_py'), runtime: lambda.Runtime.PYTHON_3_12, architecture: lambda.Architecture.ARM_64, @@ -173,13 +201,13 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { memorySize: 1024, timeout: Duration.seconds(60), environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, }, }); // Compress vcf const architecture = lambda.Architecture.ARM_64; - const compress_vcf_lambda_function = new DockerImageFunction(this, 'compress_vcf_lambda', { + const compressVcfLambdaFunction = new DockerImageFunction(this, 'compress_vcf_lambda', { description: 'Compress Vcfs', code: DockerImageCode.fromImageAsset(path.join(__dirname, '../lambdas/compress_icav2_vcf'), { file: 'Dockerfile', @@ -193,43 +221,41 @@ export class Cttsov2Icav2PipelineManagerStack extends cdk.Stack { memorySize: 2048, // Don't want pandas to kill the lambda architecture: architecture, environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, }, }); // Check success lambda - const check_success_lambda_function = new PythonFunction( - this, - 'check_success_lambda_function', - { - entry: path.join(__dirname, '../lambdas/check_success_py'), - runtime: lambda.Runtime.PYTHON_3_12, - architecture: lambda.Architecture.ARM_64, - index: 'check_success.py', - handler: 'handler', - memorySize: 1024, - timeout: Duration.seconds(60), - environment: { - ICAV2_ACCESS_TOKEN_SECRET_ID: icav2_access_token_secret_obj.secretName, - }, - } - ); + const checkSuccessLambdaFunction = new PythonFunction(this, 'check_success_lambda_function', { + entry: path.join(__dirname, '../lambdas/check_success_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'check_success.py', + handler: 'handler', + memorySize: 1024, + timeout: Duration.seconds(60), + environment: { + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, + }, + }); // Create the state machine to launch the nextflow workflow on ICAv2 - const cttso_v2_launch_state_machine = new Cttsov2Icav2PipelineManagerConstruct(this, id, { + const cttsov2LaunchStateMachine = new Cttsov2Icav2PipelineManagerConstruct(this, id, { /* Stack Objects */ - dynamodbTableObj: dynamodb_table_obj, - icav2AccessTokenSecretObj: icav2_access_token_secret_obj, - icav2CopyFilesStateMachineObj: icav2_copy_files_state_machine_obj.icav2CopyFilesSfnObj, - pipelineIdSsmObj: pipeline_id_ssm_obj_list, + dynamodbTableObj: dynamodbTableObj, + icav2AccessTokenSecretObj: icav2AccessTokenSecretObj, + icav2CopyFilesStateMachineObj: icav2CopyFilesStateMachineObj.icav2CopyFilesSfnObj, + pipelineIdSsmObj: pipelineIdSsmObjList, /* Lambdas paths */ - uploadSamplesheetToCacheDirLambdaObj: upload_samplesheet_to_cache_dir_lambda_obj, // __dirname + '/../../../lambdas/upload_samplesheet_to_cache_dir_py' - generateCopyManifestDictLambdaObj: generate_copy_manifest_dict_lambda_obj, // __dirname + '/../../../lambdas/generate_copy_manifest_dict_py' - deleteCacheUriLambdaObj: delete_cache_uri_lambda_function, - setOutputJsonLambdaObj: set_output_json_lambda_function, - getVcfsLambdaObj: get_vcfs_lambda_function, - compressVcfLambdaObj: compress_vcf_lambda_function, - checkSuccessSampleLambdaObj: check_success_lambda_function, + uploadSamplesheetToCacheDirLambdaObj: uploadSamplesheetToCacheDirLambdaObj, // __dirname + '/../../../lambdas/upload_samplesheet_to_cache_dir_py' + generateCopyManifestDictLambdaObj: generateCopyManifestDictLambdaObj, // __dirname + '/../../../lambdas/generate_copy_manifest_dict_py' + getRandomNumberLambdaObj: getRandomNumberLambdaObj, + checkNumRunningSfnsLambdaObj: checkNumRunningSfns, + deleteCacheUriLambdaObj: deleteCacheUriLambdaFunction, + setOutputJsonLambdaObj: setOutputJsonLambdaFunction, + getVcfsLambdaObj: getVcfsLambdaFunction, + compressVcfLambdaObj: compressVcfLambdaFunction, + checkSuccessSampleLambdaObj: checkSuccessLambdaFunction, /* Step function templates */ generateInputJsonSfnTemplatePath: path.join( __dirname, diff --git a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/check_num_running_sfns_py/check_num_running_sfns.py b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/check_num_running_sfns_py/check_num_running_sfns.py new file mode 100644 index 000000000..6e3591418 --- /dev/null +++ b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/check_num_running_sfns_py/check_num_running_sfns.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python + +""" +Given the environment variable 'SFN_ARN' which represents the ARN of a Step Function, +this script will check the number of running executions of the Step Function +and return the number of running executions +""" + +# Imports +import boto3 +import typing +from os import environ + +# Mypy type hints +if typing.TYPE_CHECKING: + from mypy_boto3_stepfunctions import SFNClient + +# Constants +MAX_CONCURRENCY_ALLOWED = 5 + + +def get_sfn_client() -> 'SFNClient': + """ + Get the Step Functions client + :return: SFNClient + """ + return boto3.client('stepfunctions') + + +def list_running_executions(sfn_arn: str) -> int: + """ + List the number of running executions + """ + sfn_client = get_sfn_client() + response = sfn_client.list_executions( + stateMachineArn=sfn_arn, + statusFilter='RUNNING' + ) + return len(response['executions']) + + + +def handler(event, context=None): + """ + Given the environment variable 'SFN_ARN' which represents the ARN of a Step Function, + this script will check the number of running executions of the Step Function + and return the number of running executions + :param event: + :param context: + :return: + """ + + sfn_arn = environ.get('SFN_ARN', None) + if not sfn_arn: + raise ValueError('SFN_ARN environment variable is required') + + # Get the number of running executions + running_executions = list_running_executions(sfn_arn) + + # Check if the number of running executions is + # less than the maximum concurrency allowed + if running_executions < MAX_CONCURRENCY_ALLOWED: + return { + "run_copy_job_step_bool": True, + } + else: + return { + "run_copy_job_step_bool": False, + } + + +# if __name__ == '__main__': +# from os import environ +# import json +# environ["AWS_PROFILE"] = "umccr-production" +# environ["AWS_DEFAULT_REGION"] = "ap-southeast-2" +# environ['SFN_ARN'] = 'arn:aws:states:ap-southeast-2:472057503814:stateMachine:cttsov2Sfn-icav2-copy-files-sfn' +# +# print( +# json.dumps( +# handler({}, None), +# indent=4 +# ) +# ) +# +# # { +# # "run_copy_job_step_bool": true +# # } diff --git a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/get_random_number_py/get_random_number.py b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/get_random_number_py/get_random_number.py new file mode 100644 index 000000000..e5d91caee --- /dev/null +++ b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/lambdas/get_random_number_py/get_random_number.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +""" +Get a random number +""" + +# Imports +import random +from typing import Dict + +# Globals +MAX_NUMBER = 60 + + + +def handler(event, context) -> Dict[str, int]: + """ + Get a random number + """ + + return { + "random_number": random.randint(0, MAX_NUMBER) + } + + +# if __name__ == "__main__": +# import json +# print( +# json.dumps( +# handler({}, None), +# indent=4 +# ) +# ) \ No newline at end of file diff --git a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/step_functions_templates/set_cttso_v2_nf_inputs.asl.json b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/step_functions_templates/set_cttso_v2_nf_inputs.asl.json index 4f1b13bd1..e473bac6a 100644 --- a/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/step_functions_templates/set_cttso_v2_nf_inputs.asl.json +++ b/lib/workload/stateless/stacks/cttso-v2-pipeline-manager/step_functions_templates/set_cttso_v2_nf_inputs.asl.json @@ -104,7 +104,7 @@ "BackoffRate": 2 } ], - "Next": "Copy Fastq Files to Cache Directory", + "Next": "Get Variable number of seconds", "Comment": "Generate a copy manifest object, ready to parse into the icav2 copy batch utility step function\n\nWe expect the following inputs:\n\n* cache_path\n* project_id\n* sample_id\n* fastq_list_rows\n\nAnd we expect the following outputs:\n\n* manifest", "ResultSelector": { "dest_uri.$": "$.Payload.dest_uri", @@ -112,6 +112,78 @@ }, "ResultPath": "$.generate_copy_manifest_dict_step" }, + "Get Variable number of seconds": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__get_variable_number_of_seconds_lambda_function_arn__}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "num_seconds.$": "$.Payload.random_number" + }, + "ResultPath": "$.get_variable_seconds_step", + "Next": "Wait a variable amount of time" + }, + "Wait a variable amount of time": { + "Type": "Wait", + "SecondsPath": "$.get_variable_seconds_step.num_seconds", + "Next": "Check Running Copy Fastq Job Number" + }, + "Check Running Copy Fastq Job Number": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__check_number_of_copy_jobs_running_lambda_function_arn__}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "run_copy_job_step_bool.$": "$.Payload.run_copy_job_step_bool" + }, + "ResultPath": "$.run_copy_job_step", + "Next": "Allowed to run" + }, + "Allowed to run": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.run_copy_job_step.run_copy_job_step_bool", + "BooleanEquals": false, + "Next": "Wait A Minute", + "Comment": "Too Many jobs running already" + } + ], + "Default": "Copy Fastq Files to Cache Directory" + }, + "Wait A Minute": { + "Type": "Wait", + "Seconds": 60, + "Next": "Check Running Copy Fastq Job Number" + }, "Copy Fastq Files to Cache Directory": { "Type": "Task", "End": true, diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/Readme.md b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/Readme.md new file mode 100644 index 000000000..988c1f6c7 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/Readme.md @@ -0,0 +1,247 @@ +# PierianDx Pipeline Manager + +Giant, serverless pipeline attached to a serverless database + +## Using the AWS Launch Step function + +The AWS Launch step function takes in the following inputs + +* dag: Object + * name: string # The name of this case dag + * description: string # The description of this case dag +* case_metadata: Object + * panel_name: string # The name of this case’s panel _tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne_ # pragma: allowlist secret + * specimen_label: string # The label of the specimen _primarySpecimen_ + * sample_type: Enum # patientcare, clinical_trial, validation, proficiency_testing + * indication: String # Optional input + * disease: Object + * code: string # The disease id + * label: string # The name of the disease (optional) + * is_identified: bool # Boolean is this a boolean identified case or de-identified case + * case_accession_number: string - must be unique - uses syntax SBJID__LIBID__NNN + * specimen_type: + * code: string # The SNOMED-CT term for a specimen type + * label: Optional label for the specimen type + * external_specimen_id: string # The external specimen id + * date_accessioned: Datetime # The date the case was accessioned _2021-01-01T00:00:00Z_ + * date_collected: Datetime # The date the specimen was collected _2021-01-01T00:00:00Z_ + * date_received: Datetime # The date the specimen was received _2021-01-01T00:00:00Z_ + * gender: Enum # unknown, male, femail, unspecified, other, ambiguous, not_applicable # Optional + * ethnicity: Enum # unknown, hispanic_or_latino, not_hispanic_or_latino, not_reported # Optional + * race: Enum # american_indian_or_alaska_native, asian, black_or_african_american, native_hawaiian_or_other_pacific_islander, not_reported, unknown, white # Optional + + > Note: If the case is de-identified, the following fields are required + * study_id: String # Only required if is_identified is false + * participant_id: String # Only required if is_identified is false + + > Note: If the case is identified, the following fields are required + * date_of_birth: Datetime # Only required if is_identified is true + * first_name: String # Only required if is_identified is true + * last_name: String # Only required if is_identified is true + * medical_record_numbers: Object # Only required if is_identified is true + * mrn: string # The medical record number + * medical_facility: Object + * facility: string # The name of the facility + * hospital_number: string # The hospital number + * requesting_physicians: Object # The requesting physician - required for identified cases + * first_name: string + * last_name: string + +* data_files: Object + * microsat_output: uri + * tmb_metrics: uri + * cnv: uri + * hard_filtered: uri + * fusions: uri + * metrics_output: uri + +* samplesheet_b64gz: str +* portal_run_id: str +* sequencerrun_s3_path_root: str + +## DataBase Structure + +> IDs +* db_uuid +* portal_run_id +* case_id +* sequencerun_id +* informaticsjob_id +* report_id +* case_accession_number + +> Objects +* samplesheet_b64gz: str +* case_creation_obj: CaseCreationObject +* sequencerrun_creation_obj: SequencerRunCreationObject +* informaticsjob_creation_obj: InformaticsJobCreationObject +* data_files: List[DataFileObject] + +> Status +* job_status: waiting, ready, running, complete, failed, canceled +* report_status: waiting, ready, running, complete, failed, canceled, report_generation_complete + +## Step Function execution structure + +### Launch Overview + +This will trigger three sub-step-functions, case creation, sequencerrun creation and informatics job creation + +![images/launch_overview.png](images/launch_overview.png) + +The whole process should not take more than 30 seconds + +### Case Creation Overview + +Generate the case object, this defines the case metadata and will generate a case id (required for the informatics job section) + +![images/case_creation_overview.png](images/case_overview.png) + +### Sequencer Run Overview + +Upload the data files from ICAv2 to the PierianDx S3 bucket. + +Generate a samplesheet and upload it to the PierianDx S3 bucket. + +Create a sequencer run object, this defines the sequencer run metadata and will generate a sequencer run id (required for the informatics job section) + +![images/sequencerrun_overview.png](images/sequencerrun_overview.png) + +### Informatics Job Overview + +Generate an informatics job for the case. + +This will return an informatics job id. + +![images/informaticsjob_launch_overview.png](images/informaticsjob_launch_overview.png) + + +## Launch Execution Example + + +``` +aws events put-events --no-cli-pager --cli-input-json "$( \ + jq --raw-output --compact-output \ + ' + { + "Entries": [ + { + "EventBusName": "OrcaBusMain", + "DetailType": "workflowRunStateChange", + "Source": "orcabus.manual", + "Time": $utc_time, + "Resources": [], + "Detail": { + "portalRunId": "abcd1234", + "timestamp": "20240920T113200Z", + "status": "READY", + "workflowName": "pieriandx", + "workflowVersion": "2.1.1", + "workflowRunName": "umccr-automated--pieriandx--2-1-1--abcd1234", + "linkedLibraries": [ + { + "libraryId": "L2400161", + "orcabusId": "NA" + } + ], + "payload": { + "version": "2024.10.01", + "data": { + "inputs": { + "instrumentRunId": "231116_A01052_0172_BHVLM5DSX7", + "dagVersion": "2.1.1", + "panelVersion": "main", + "caseMetadata": { + "isIdentified": true, + "caseAccessionNumber": "SBJ04407__L2400161__V2__abcd1238", + "externalSpecimenId": "externalspecimenid", + "sampleType": "PatientCare", + "specimenLabel": "primarySpecimen", + "indication": "Test", + "diseaseCode": 64572001, + "specimenCode": 122561005, + "sampleReception": { + "dateAccessioned": "2021-01-01T00:00:00Z", + "dateCollected": "2024-02-20T20:17:00Z", + "dateReceived": "2021-01-01T00:00:00Z" + }, + "patientInformation": { + "dateOfBirth": "1970-01-01", + "firstName": "John", + "lastName": "Doe" + }, + "medicalRecordNumbers": { + "mrn": "3069999", + "medicalFacility": { + "facility": "Not Available", + "hospitalNumber": "99" + } + }, + "requestingPhysician": { + "firstName": "Meredith", + "lastName": "Gray" + } + }, + "dataFiles": { + "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/DragenCaller/L2400161/L2400161.microsat_output.json", + "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/Tmb/L2400161/L2400161.tmb.metrics.csv", + "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161.cnv.vcf.gz", + "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161.hard-filtered.vcf.gz", + "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_Fusions.csv", + "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_MetricsOutput.tsv", + "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" + } + }, + "engineParameters": {}, + "tags": { + "libraryId": "L2400161", + "subjectId": "SBJ04407", + "instrumentRunId": "231116_A01052_0172_BHVLM5DSX7", + "isIdentified": true, + "metadataFromRedCap": true + } + } + } + } + } + ] + } + ' +)" +``` + +DAGs (PROD) + +```json +{ + "name": "cromwell_tso500_ctdna_workflow_1.0.4", + "description": "tso500_ctdna_workflow" +} +``` + +Panels (PROD) + +```json5 +[ + { + "name": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", // pragma: allowlist secret + "label": "TSO500 DRAGEN ctDNA v2.1" + }, + { + "name": "tso500_ctDNA_vcf_subpanel_workflow_university_of_melbourne", + "label": "TruSight Oncology 500 ctDNA (VCF) Sub-panel" + }, + { + "name": "Melbourne_WGS_Somatic_Textual", + "label": "Melbourne WGS Somatic" + }, + { + "name": "tso500_ctDNA_vcf_workflow_university_of_melbourne", + "label": "TruSight Oncology 500 ctDNA (VCF) University of Melbourne" + }, + { + "name": "Melbourne_WGS_Somatic_Textual_SAS", + "label": "Melbourne WGS Somatic New" + } +] +``` \ No newline at end of file diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/lambda_layer.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/lambda_layer.ts new file mode 100644 index 000000000..df0e89ee1 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/lambda_layer.ts @@ -0,0 +1,40 @@ +import { Construct } from 'constructs'; +import { PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; + +export interface LambdaLayerConstructProps { + layer_directory: string; +} + +export class LambdaLayerConstruct extends Construct { + public readonly lambda_layer_arn: string; + public readonly lambda_layer_version_obj: PythonLayerVersion; + + constructor(scope: Construct, id: string, props: LambdaLayerConstructProps) { + super(scope, id); + + this.lambda_layer_version_obj = new PythonLayerVersion(this, 'cttso_v2_tool_layer', { + entry: props.layer_directory, + compatibleRuntimes: [lambda.Runtime.PYTHON_3_12], + compatibleArchitectures: [lambda.Architecture.ARM_64], + license: 'GPL3', + description: 'A layer to enable the cttso_v2 manager tools layer', + bundling: { + commandHooks: { + beforeBundling(inputDir: string, outputDir: string): string[] { + return []; + }, + afterBundling(inputDir: string, outputDir: string): string[] { + return [ + `python -m pip install ${inputDir} -t ${outputDir}`, + `find ${outputDir} -type d -name "pandas" -exec echo rm -rf {}/tests \;`, + ]; + }, + }, + }, + }); + + // Set outputs + this.lambda_layer_arn = this.lambda_layer_version_obj.layerVersionArn; + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_case_creation_step_function.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_case_creation_step_function.ts new file mode 100644 index 000000000..24fdf06ee --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_case_creation_step_function.ts @@ -0,0 +1,67 @@ +import * as cdk from 'aws-cdk-lib'; +import { Duration } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import { DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions'; + +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; +import { LambdaLayerConstruct } from './lambda_layer'; + +interface PieriandxLaunchCaseCreationStepFunctionConstructProps { + /* Stack Objects */ + dynamodbTableObj: dynamodb.ITableV2; + /* Lambdas paths */ + generateCaseLambdaObj: PythonFunction; + /* Step function templates */ + launchPieriandxCaseCreationStepfunctionTemplatePath: string; + /* Custom */ + prefix: string; +} + +export class PieriandxLaunchCaseCreationStepFunctionStateMachineConstruct extends Construct { + public readonly stateMachineObj: sfn.IStateMachine; + + constructor( + scope: Construct, + id: string, + props: PieriandxLaunchCaseCreationStepFunctionConstructProps + ) { + super(scope, id); + + // Specify the statemachine and replace the arn placeholders with the lambda arns defined above + const stateMachine = new sfn.StateMachine( + this, + 'pieriandx_launch_step_functions_state_machine', + { + // stateMachineName + stateMachineName: `${props.prefix}-sub-case-sfn`, + // defintiontemplate + definitionBody: DefinitionBody.fromFile( + props.launchPieriandxCaseCreationStepfunctionTemplatePath + ), + // definitionSubstitutions + definitionSubstitutions: { + /* Tables */ + __table_name__: props.dynamodbTableObj.tableName, + /* Lambdas */ + __generate_case_lambda_function_arn__: + props.generateCaseLambdaObj.currentVersion.functionArn, + }, + } + ); + + // Grant lambda invoke permissions to the state machine + props.generateCaseLambdaObj.currentVersion.grantInvoke(stateMachine); + + // Allow state machine to read/write to dynamodb table + props.dynamodbTableObj.grantReadWriteData(stateMachine); + + // Set outputs + this.stateMachineObj = stateMachine; + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_informaticsjob_creation_step_function.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_informaticsjob_creation_step_function.ts new file mode 100644 index 000000000..6b86e5e28 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_informaticsjob_creation_step_function.ts @@ -0,0 +1,58 @@ +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import { DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions'; +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; + +interface PieriandxLaunchInformaticsjobCreationStepFunctionsStateMachineConstructProps { + /* Stack Objects */ + dynamodbTableObj: dynamodb.ITableV2; + /* Lambdas paths */ + generateInformaticsjobLambdaObj: PythonFunction; // __dirname + '/../../../lambdas/upload_samplesheet_to_cache_dir' + /* Step function templates */ + launchPieriandxInformaticsjobCreationStepfunctionTemplate: string; // __dirname + '/../../../step_functions_templates/launch_pieriandx_informaticsjob_creation.asl.json' + /* Prefix */ + prefix: string; +} + +export class PieriandxLaunchInformaticsjobCreationStepFunctionsStateMachineConstruct extends Construct { + public readonly stateMachineObj: sfn.IStateMachine; + + constructor( + scope: Construct, + id: string, + props: PieriandxLaunchInformaticsjobCreationStepFunctionsStateMachineConstructProps + ) { + super(scope, id); + + // Specify the statemachine and replace the arn placeholders with the lambda arns defined above + const stateMachine = new sfn.StateMachine( + this, + 'pieriandx_launch_step_functions_state_machine', + { + // stateMachineName + stateMachineName: `${props.prefix}-sub-job-sfn`, + // defintiontemplate + definitionBody: DefinitionBody.fromFile( + props.launchPieriandxInformaticsjobCreationStepfunctionTemplate + ), + // definitionSubstitutions + definitionSubstitutions: { + __generate_informaticsjob_lambda_function_arn__: + props.generateInformaticsjobLambdaObj.currentVersion.functionArn, + __table_name__: props.dynamodbTableObj.tableName, + }, + } + ); + + // Grant lambda invoke permissions to the state machine + props.generateInformaticsjobLambdaObj.currentVersion.grantInvoke(stateMachine); + + // Allow state machine to read/write to dynamodb table + props.dynamodbTableObj.grantReadWriteData(stateMachine.role); + + // Set outputs + this.stateMachineObj = stateMachine; + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_sequencerrun_creation_step_function.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_sequencerrun_creation_step_function.ts new file mode 100644 index 000000000..af8bf0909 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_sequencerrun_creation_step_function.ts @@ -0,0 +1,70 @@ +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import { DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions'; +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; + +interface PieriandxLaunchSequencerrunCreationStepFunctionsStateMachineConstructProps { + /* Stack Objects */ + dynamodbTableObj: dynamodb.ITableV2; + /* Lambdas paths */ + uploadDataToS3LambdaObj: PythonFunction; + generateSamplesheetLambdaObj: PythonFunction; + generateSequencerrunLambdaObj: PythonFunction; + /* Step function templates */ + launchPieriandxSequencerrunCreationStepfunctionTemplate: string; // __dirname + '/../../../step_functions_templates/launch_pieriandx_sequencerrun_creation.asl.json' + /* Custom props */ + prefix: string; +} + +export class PieriandxLaunchSequencerrunCreationStepFunctionsStateMachineConstruct extends Construct { + public readonly stateMachineObj: sfn.IStateMachine; + + constructor( + scope: Construct, + id: string, + props: PieriandxLaunchSequencerrunCreationStepFunctionsStateMachineConstructProps + ) { + super(scope, id); + + // Specify the statemachine and replace the arn placeholders with the lambda arns defined above + const stateMachine = new sfn.StateMachine( + this, + 'pieriandx_launch_step_functions_state_machine', + { + // stateMachineName + stateMachineName: `${props.prefix}-sub-sqrrun-sfn`, + // defintiontemplate + definitionBody: DefinitionBody.fromFile( + props.launchPieriandxSequencerrunCreationStepfunctionTemplate + ), + // definitionSubstitutions + definitionSubstitutions: { + __upload_data_to_s3_lambda_function_arn__: + props.uploadDataToS3LambdaObj.currentVersion.functionArn, + __generate_samplesheet_lambda_function_arn__: + props.generateSamplesheetLambdaObj.currentVersion.functionArn, + __generate_sequencerrun_case_lambda_function_arn__: + props.generateSequencerrunLambdaObj.currentVersion.functionArn, + __table_name__: props.dynamodbTableObj.tableName, + }, + } + ); + + // Grant lambda invoke permissions to the state machine + [ + props.uploadDataToS3LambdaObj, + props.generateSamplesheetLambdaObj, + props.generateSequencerrunLambdaObj, + ].forEach((lambda_obj) => { + lambda_obj.currentVersion.grantInvoke(stateMachine); + }); + + // Allow state machine to read/write to dynamodb table + props.dynamodbTableObj.grantReadWriteData(stateMachine); + + // Set outputs + this.stateMachineObj = stateMachine; + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_step_function.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_step_function.ts new file mode 100644 index 000000000..44e40692c --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_launch_step_function.ts @@ -0,0 +1,161 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as events from 'aws-cdk-lib/aws-events'; +import { DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions'; + +import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha'; +import { LambdaLayerConstruct } from './lambda_layer'; +import * as events_targets from 'aws-cdk-lib/aws-events-targets'; + +interface PieriandxLaunchStepFunctionConstructProps { + /* Stack Objects */ + dynamodbTableObj: dynamodb.ITableV2; + /* workflow */ + workflowName: string; + workflowVersion: string; + /* lambda paths */ + generatePieriandxObjectsLambdaObj: PythonFunction; // __dirname + '/../../../lambdas/generate_trimmed_samplesheet_lambda_path' + /* Defaults */ + defaultDagVersion: string; + defaultPanelName: string; + /* SSM Parameters */ + dagSsmParameterObj: ssm.IStringParameter; + panelNameSsmParameterObj: ssm.IStringParameter; + s3SequencerRunRootSsmParameterObj: ssm.IStringParameter; + /* Step function templates */ + launchPieriandxStepfunctionTemplate: string; // __dirname + '/../../../step_functions_templates/cttso_v2_launch_workflow_state_machine.json' + launchPieriandxCaseCreationStepfunctionObj: sfn.IStateMachine; + launchPieriandxInformaticsjobCreationStepfunctionObj: sfn.IStateMachine; + launchPieriandxSequencerrunCreationStepfunctionObj: sfn.IStateMachine; + /* Events */ + payloadVersion: string; + eventBusName: string; + detailType: string; + eventSource: string; + triggerLaunchSource: string; + /* Custom */ + prefix: string; +} + +export class PieriandxLaunchStepFunctionStateMachineConstruct extends Construct { + public readonly stateMachineObj: sfn.StateMachine; + + constructor(scope: Construct, id: string, props: PieriandxLaunchStepFunctionConstructProps) { + super(scope, id); + + // Specify the statemachine and replace the arn placeholders with the lambda arns defined above + const stateMachine = new sfn.StateMachine( + this, + 'pieriandx_launch_step_functions_state_machine', + { + // state machine name + stateMachineName: `${props.prefix}-submit-sfn`, + // definition template + definitionBody: DefinitionBody.fromFile(props.launchPieriandxStepfunctionTemplate), + // definitionSubstitutions + definitionSubstitutions: { + /* Table */ + __table_name__: props.dynamodbTableObj.tableName, + __workflow_name__: props.workflowName, + __workflow_version__: props.workflowVersion, + + /* Lambda Functions */ + __generate_pieriandx_objects_lambda_function_arn__: + props.generatePieriandxObjectsLambdaObj.currentVersion.functionArn, + + /* Child step functions */ + __create_case_sfn__: props.launchPieriandxCaseCreationStepfunctionObj.stateMachineArn, + __create_informaticsjob_sfn__: + props.launchPieriandxInformaticsjobCreationStepfunctionObj.stateMachineArn, + __create_sequencerrun_sfn__: + props.launchPieriandxSequencerrunCreationStepfunctionObj.stateMachineArn, + + /* SSM Parameters */ + __dag_versions_ssm_parameter__: props.dagSsmParameterObj.parameterName, + __panel_names_ssm_parameter__: props.panelNameSsmParameterObj.parameterName, + + /* Defaults / Hardcoded values */ + __default_dag_version__: props.defaultDagVersion, + __default_panel_name__: props.defaultPanelName, + __sequencerrun_s3_path_root__: props.s3SequencerRunRootSsmParameterObj.stringValue, + + /* Events */ + __payload_version__: props.payloadVersion, + __event_bus_name__: props.eventBusName, + __event_detail_type__: props.detailType, + __event_source__: props.eventSource, + }, + } + ); + + // Grant lambda invoke permissions to the state machine + props.generatePieriandxObjectsLambdaObj.currentVersion.grantInvoke(stateMachine); + + // Grant read permissions to the ssm parameters + [ + props.dagSsmParameterObj, + props.panelNameSsmParameterObj, + props.s3SequencerRunRootSsmParameterObj, + ].forEach((ssmParameterObj: ssm.IStringParameter) => { + // Grant read permissions to the state machine + ssmParameterObj.grantRead(stateMachine); + }); + + // Allow state machine to read/write to dynamodb table + props.dynamodbTableObj.grantReadWriteData(stateMachine.role); + + // Because we run a nested state machine, we need to add the permissions to the state machine role + // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr + stateMachine.addToRolePolicy( + new iam.PolicyStatement({ + resources: [ + `arn:aws:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule`, + ], + actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], + }) + ); + + // Allow sub-state launch machines to be invoked by this statemachine + [ + props.launchPieriandxCaseCreationStepfunctionObj, + props.launchPieriandxInformaticsjobCreationStepfunctionObj, + props.launchPieriandxSequencerrunCreationStepfunctionObj, + ].forEach((state_machine_obj) => { + state_machine_obj.grantStartExecution(stateMachine); + }); + + // Get event bus from event bus name + const eventBusObj = events.EventBus.fromEventBusName(this, 'eventBus', props.eventBusName); + + // Add permissions to the state machine to send events to the event bus + eventBusObj.grantPutEventsTo(stateMachine); + + // Create a rule for this state machine + const rule = new events.Rule(this, 'rule', { + eventBus: eventBusObj, + ruleName: `${props.prefix}-launch-wrsc-rule`, + eventPattern: { + source: [props.triggerLaunchSource], + detailType: [props.detailType], + detail: { + status: ['READY'], + workflowName: [{ 'equals-ignore-case': props.workflowName }], + }, + }, + }); + + /* Add rule as a target to the state machine */ + rule.addTarget( + new events_targets.SfnStateMachine(stateMachine, { + input: events.RuleTargetInput.fromEventPath('$.detail'), + }) + ); + + // Set outputs + this.stateMachineObj = stateMachine; + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_monitor_runs_step_function.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_monitor_runs_step_function.ts new file mode 100644 index 000000000..15836e794 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/constructs/pieriandx_monitor_runs_step_function.ts @@ -0,0 +1,116 @@ +import { Construct } from 'constructs'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as events from 'aws-cdk-lib/aws-events'; +import { DefinitionBody } from 'aws-cdk-lib/aws-stepfunctions'; +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; +import { Duration } from 'aws-cdk-lib'; +import * as events_targets from 'aws-cdk-lib/aws-events-targets'; + +interface PieriandxMonitorRunsStepFunctionConstructProps { + /* Stack Objects */ + dynamodbTableObj: dynamodb.ITableV2; + + /* Workflow */ + workflowName: string; + workflowVersion: string; + + /* Lambdas paths */ + getInformaticsjobAndReportStatusLambdaObj: PythonFunction; + generateOutputPayloadDataLambdaObj: PythonFunction; + + /* Step function templates */ + launchPieriandxMonitorRunsStepfunctionTemplatePath: string; + + /* SSM Parameters */ + pierianDxBaseUrlSsmParameterObj: ssm.IStringParameter; + + /* Event Bus */ + eventBusName: string; + eventDetailType: string; + eventSource: string; + payloadVersion: string; + + /* Custom */ + prefix: string; +} + +export class PieriandxMonitorRunsStepFunctionStateMachineConstruct extends Construct { + public readonly stateMachineObj: sfn.IStateMachine; + + constructor(scope: Construct, id: string, props: PieriandxMonitorRunsStepFunctionConstructProps) { + super(scope, id); + + // Specify the statemachine and replace the arn placeholders with the lambda arns defined above + const stateMachine = new sfn.StateMachine( + this, + 'pieriandx_launch_step_functions_state_machine', + { + // State Machine Name + stateMachineName: `${props.prefix}-monitor-runs-sfn`, + // defintiontemplate + definitionBody: DefinitionBody.fromFile( + props.launchPieriandxMonitorRunsStepfunctionTemplatePath + ), + // definitionSubstitutions + definitionSubstitutions: { + /* Tables */ + __table_name__: props.dynamodbTableObj.tableName, + + /* Workflow Name */ + __workflow_name__: props.workflowName, + __workflow_version__: props.workflowVersion, + + /* Lambdas */ + __get_current_job_status_lambda_function_arn__: + props.getInformaticsjobAndReportStatusLambdaObj.currentVersion.functionArn, + __generate_data_payload_lambda_function_arn__: + props.generateOutputPayloadDataLambdaObj.currentVersion.functionArn, + + /* Hardcoded ssm parameters */ + __pieriandx_base_url__: props.pierianDxBaseUrlSsmParameterObj.stringValue, + + /* Event Bus Name */ + __event_bus_name__: props.eventBusName, + __event_detail_type__: props.eventDetailType, + __event_source__: props.eventSource, + __payload_version__: props.payloadVersion, + }, + } + ); + + // Grant lambda invoke permissions to the state machine + [ + props.getInformaticsjobAndReportStatusLambdaObj, + props.generateOutputPayloadDataLambdaObj, + ].forEach((lambdaFunction) => { + lambdaFunction.currentVersion.grantInvoke(stateMachine); + }); + + // Get the event bus from the event bus name + const eventBusObj = events.EventBus.fromEventBusName(this, 'eventBus', props.eventBusName); + + // Allow state machine to read/write to dynamodb table + props.dynamodbTableObj.grantReadWriteData(stateMachine); + + // Add rule for this sfn to run every 5 minutes + const rule = new events.Rule(this, 'rule', { + ruleName: `${props.prefix}-pieriandx-monitor-runs-rule`, + schedule: events.Schedule.rate(Duration.minutes(5)), + }); + + // Add permissions to the state machine to send events to the event bus + eventBusObj.grantPutEventsTo(stateMachine); + + /* Add rule as a target to the state machine */ + rule.addTarget( + new events_targets.SfnStateMachine(stateMachine, { + input: events.RuleTargetInput.fromEventPath('$.detail'), + }) + ); + + // Set outputs + this.stateMachineObj = stateMachine; + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/index.ts b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/index.ts new file mode 100644 index 000000000..d775bda1f --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/deploy/index.ts @@ -0,0 +1,441 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { PieriandxLaunchStepFunctionStateMachineConstruct } from './constructs/pieriandx_launch_step_function'; +import { PieriandxLaunchCaseCreationStepFunctionStateMachineConstruct } from './constructs/pieriandx_launch_case_creation_step_function'; +import { PieriandxLaunchInformaticsjobCreationStepFunctionsStateMachineConstruct } from './constructs/pieriandx_launch_informaticsjob_creation_step_function'; +import { PieriandxLaunchSequencerrunCreationStepFunctionsStateMachineConstruct } from './constructs/pieriandx_launch_sequencerrun_creation_step_function'; +import * as path from 'path'; +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; +import { Duration } from 'aws-cdk-lib'; +import { PieriandxMonitorRunsStepFunctionStateMachineConstruct } from './constructs/pieriandx_monitor_runs_step_function'; +import { PythonLambdaLayerConstruct } from '../../../../components/python-lambda-layer'; + +export interface PierianDxPipelineManagerConfig { + /* DynamoDB Table */ + dynamodbTableName: string; + /* Workflow knowledge */ + workflowName: string; + workflowVersion: string; + /* Default values */ + defaultDagVersion: string; + defaultPanelName: string; + /* Secrets */ + icav2AccessTokenSecretId: string; // "/icav2/umccr-prod/service-production-jwt-token-secret-arn" + pieriandxS3AccessTokenSecretId: string; // "/pieriandx/s3AccessCredentials" + /* SSM Parameters */ + dagSsmParameterPath: string; + panelNameSsmParameterPath: string; + s3SequencerRunRootSsmParameterPath: string; + /* + Pieriandx specific parameters + */ + pieriandxUserEmailSsmParameterPath: string; + pieriandxInstitutionSsmParameterPath: string; + pieriandxBaseUrlSsmParameterPath: string; + pieriandxAuthTokenCollectionLambdaFunctionName: string; + /* Event info */ + eventDetailType: string; + eventBusName: string; + eventSource: string; + payloadVersion: string; + triggerLaunchSource: string; + /* Custom */ + prefix: string; +} + +export type PierianDxPipelineManagerStackProps = PierianDxPipelineManagerConfig & cdk.StackProps; + +export class PieriandxPipelineManagerStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: PierianDxPipelineManagerStackProps) { + super(scope, id, props); + + // Get dynamodb table for construct + const dynamodbTableObj = dynamodb.TableV2.fromTableName( + this, + 'dynamodb_table', + props.dynamodbTableName + ); + + // Get ICAv2 Access token secret object for construct + const icav2AccessTokenSecretObj = secretsManager.Secret.fromSecretNameV2( + this, + 'Icav2SecretsObject', + props.icav2AccessTokenSecretId + ); + + const pieriandxS3AccessTokenSecretObj = secretsManager.Secret.fromSecretNameV2( + this, + 'PieriandxS3SecretsObject', + props.pieriandxS3AccessTokenSecretId + ); + + /* + Get the ssm parameters + */ + const dagSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'dag', + props.dagSsmParameterPath + ); + const panelNameSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'panel_name', + props.panelNameSsmParameterPath + ); + const s3SequencerRunRootSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 's3_sequencerrun_root', + props.s3SequencerRunRootSsmParameterPath + ); + + /* + Get the pieriandx parameters + */ + const pieriandxUserEmailSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'pieriandx_useremail', + props.pieriandxUserEmailSsmParameterPath + ); + const pieriandxInstitutionSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'pieriandx_institution', + props.pieriandxInstitutionSsmParameterPath + ); + const pieriandxBaseUrlSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'pieriandx_baseurl', + props.pieriandxBaseUrlSsmParameterPath + ); + + // Get lambda layer object + const lambdaLayerObj = new PythonLambdaLayerConstruct(this, 'lambda_layer', { + layerName: 'pieriandx-tools-lambda-layer', + layerDirectory: path.join(__dirname, '../layers'), + layerDescription: 'PierianDx Tools Lambda Layer', + }); + + // Collect the pieriandx access token + const pieriandxTokenCollectionLambdaObj: lambda.IFunction = lambda.Function.fromFunctionName( + this, + 'pieriandx_auth_token_collection_lambda', + props.pieriandxAuthTokenCollectionLambdaFunctionName + ); + + // Set Pieriandx secret env for lambdas + const pieriandxEnvs = { + PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME: pieriandxTokenCollectionLambdaObj.functionName, + PIERIANDX_USER_EMAIL: pieriandxUserEmailSsmParameterObj.stringValue, + PIERIANDX_INSTITUTION: pieriandxInstitutionSsmParameterObj.stringValue, + PIERIANDX_BASE_URL: pieriandxBaseUrlSsmParameterObj.stringValue, + }; + + const pieriandxSecretEnvs = { + PIERIANDX_S3_ACCESS_CREDENTIALS_SECRET_ID: pieriandxS3AccessTokenSecretObj.secretName, + }; + + const icav2Envs = { + ICAV2_BASE_URL: 'https://ica.illumina.com/ica/rest', + ICAV2_ACCESS_TOKEN_SECRET_ID: icav2AccessTokenSecretObj.secretName, + }; + + /* + Build lambdas + */ + /* Part 1: Lambdas for generating the PierianDx API objects */ + const generatePieriandxObjectsLambdaObj = new PythonFunction( + this, + 'generate_pieriandx_objects_lambda_py', + { + entry: path.join(__dirname + '/../lambdas/generate_pieriandx_objects_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_pieriandx_objects.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + timeout: Duration.seconds(20), + environment: icav2Envs, + } + ); + + /* Part 2: Lambdas used by the case creation step function */ + /* Generate case lambda object */ + const generateCaseLambdaObj = new PythonFunction(this, 'generate_case_lambda_py', { + entry: path.join(__dirname + '/../lambdas/generate_case_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_case.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + timeout: Duration.seconds(30), + environment: pieriandxEnvs, + }); + + /* Part 3 - Lambdas used by the sequencerrun creation step function */ + /* Generate sequencerrun and samplesheet lambda objects */ + // Simple samplesheet generator object, no env or permissions needed + const generateSamplesheetLambdaObj = new PythonFunction( + this, + 'generate_samplesheet_lambda_py', + { + entry: path.join(__dirname + '/../lambdas/generate_samplesheet_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_samplesheet.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + timeout: Duration.seconds(30), + environment: icav2Envs, + } + ); + + // Generate sequencerrun object + const generateSequencerrunLambdaObj = new PythonFunction( + this, + 'generate_sequencerrun_lambda_py', + { + entry: path.join(__dirname + '/../lambdas/generate_sequencerrun_case_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_sequencerrun_case.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + environment: pieriandxEnvs, + } + ); + + // Upload data to S3 lambda object + const uploadDataToS3LambdaObj = new PythonFunction( + this, + 'upload_pieriandx_sample_data_to_s3_py', + { + entry: path.join(__dirname + '/../lambdas/upload_pieriandx_sample_data_to_s3_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'upload_pieriandx_sample_data_to_s3.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + timeout: Duration.seconds(300), + environment: { ...pieriandxEnvs, ...pieriandxSecretEnvs, ...icav2Envs }, + } + ); + + /* Part 4 - Lambdas used by the informatics job creation step function */ + /* Generate informatics job lambda object */ + const generateInformaticsjobLambdaObj = new PythonFunction( + this, + 'generate_informatics_job_lambda_py', + { + entry: path.join(__dirname + '/../lambdas/generate_informaticsjob_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_informaticsjob.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + timeout: Duration.seconds(30), + environment: pieriandxEnvs, + } + ); + + /* Part 5 - Lambdas used by the monitor runs step function */ + const getInformaticsjobAndReportStatusLambdaObj = new PythonFunction( + this, + 'get_informaticsjob_and_report_status_lambda_obj', + { + entry: path.join(__dirname + '/../lambdas/get_informaticsjob_and_report_status_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_informaticsjob_and_report_status.py', + handler: 'handler', + memorySize: 1024, + layers: [lambdaLayerObj.lambdaLayerVersionObj], + timeout: Duration.seconds(30), + environment: pieriandxEnvs, + } + ); + + const generateOutputPayloadDataLambdaObj = new PythonFunction( + this, + 'generate_output_payload_data_lambda_obj', + { + entry: path.join(__dirname + '/../lambdas/generate_output_data_payload_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_output_data_payload.py', + handler: 'handler', + } + ); + + /* + Give the lambda permission to access the pieriandx apis + */ + [ + generateCaseLambdaObj, + generateInformaticsjobLambdaObj, + generateSequencerrunLambdaObj, + uploadDataToS3LambdaObj, + getInformaticsjobAndReportStatusLambdaObj, + ].forEach((lambdaFunction) => { + // Give the lambda permission to access the pieriandx apis + pieriandxTokenCollectionLambdaObj.grantInvoke(lambdaFunction); + }); + + /* + Give the upload lambda access to the pieriandx s3 bucket + */ + // @ts-ignore + pieriandxS3AccessTokenSecretObj.grantRead(uploadDataToS3LambdaObj); + + /* + Give the lambdas permission to access the icav2 apis + */ + [ + generatePieriandxObjectsLambdaObj, + generateSamplesheetLambdaObj, + uploadDataToS3LambdaObj, + ].forEach((lambdaFunction) => { + // @ts-ignore + icav2AccessTokenSecretObj.grantRead(lambdaFunction); + }); + + /* + Generate State Machines + */ + + /* Generate case creation statemachine object */ + const pieriandxLaunchCaseCreationStateMachine = + new PieriandxLaunchCaseCreationStepFunctionStateMachineConstruct(this, 'case_creation', { + /* Stack Objects */ + dynamodbTableObj: dynamodbTableObj, + /* Lambda objs */ + generateCaseLambdaObj: generateCaseLambdaObj, + /* Step function template */ + launchPieriandxCaseCreationStepfunctionTemplatePath: path.join( + __dirname, + '/../step_function_templates/launch_pieriandx_case_creation.asl.json' + ), + /* Prefix */ + prefix: props.prefix, + }); + + /* Generate informatics job creation statemachine object */ + const pieriandxInformaticsjobCreationStateMachine = + new PieriandxLaunchInformaticsjobCreationStepFunctionsStateMachineConstruct( + this, + 'informaticsjob_creation_sfn', + { + /* Stack Objects */ + dynamodbTableObj: dynamodbTableObj, + /* Lambda paths */ + generateInformaticsjobLambdaObj: generateInformaticsjobLambdaObj, + /* Step function templates */ + launchPieriandxInformaticsjobCreationStepfunctionTemplate: path.join( + __dirname, + '/../step_function_templates/launch_pieriandx_informaticsjob_creation.asl.json' + ), + /* Custom */ + prefix: props.prefix, + } + ); + + /* Generate Sequence Run Creation StateMachine object */ + const pieriandxSequencerrunCreationStateMachine = + new PieriandxLaunchSequencerrunCreationStepFunctionsStateMachineConstruct( + this, + 'sequencerrun_creation_sfn', + { + /* Stack Objects */ + dynamodbTableObj: dynamodbTableObj, + /* Lambda paths */ + uploadDataToS3LambdaObj: uploadDataToS3LambdaObj, + generateSamplesheetLambdaObj: generateSamplesheetLambdaObj, + generateSequencerrunLambdaObj: generateSequencerrunLambdaObj, + /* Step function templates */ + launchPieriandxSequencerrunCreationStepfunctionTemplate: path.join( + __dirname, + '/../step_function_templates/launch_pieriandx_sequencerrun_creation.asl.json' + ), + /* Custom */ + prefix: props.prefix, + } + ); + + /* Generate parent statemachine object to launch pieriandx analysis */ + const pieriandxLaunchStateMachine = new PieriandxLaunchStepFunctionStateMachineConstruct( + this, + id, + { + /* Stack Objects */ + dynamodbTableObj: dynamodbTableObj, + /* Workflow information */ + workflowName: props.workflowName, + workflowVersion: props.workflowVersion, + /* Defaults */ + defaultDagVersion: props.defaultDagVersion, + defaultPanelName: props.defaultPanelName, + /* Lambdas paths */ + generatePieriandxObjectsLambdaObj: generatePieriandxObjectsLambdaObj, + /* SSM Parameters */ + dagSsmParameterObj: dagSsmParameterObj, + panelNameSsmParameterObj: panelNameSsmParameterObj, + s3SequencerRunRootSsmParameterObj: s3SequencerRunRootSsmParameterObj, + /* Step function templates */ + launchPieriandxStepfunctionTemplate: path.join( + __dirname, + '/../step_function_templates/launch_pieriandx.asl.json' + ), + /* Step function objects */ + launchPieriandxCaseCreationStepfunctionObj: + pieriandxLaunchCaseCreationStateMachine.stateMachineObj, + launchPieriandxInformaticsjobCreationStepfunctionObj: + pieriandxInformaticsjobCreationStateMachine.stateMachineObj, + launchPieriandxSequencerrunCreationStepfunctionObj: + pieriandxSequencerrunCreationStateMachine.stateMachineObj, + /* Events */ + detailType: props.eventDetailType, + eventBusName: props.eventBusName, + eventSource: props.eventSource, + payloadVersion: props.payloadVersion, + triggerLaunchSource: props.triggerLaunchSource, + /* Custom */ + prefix: props.prefix, + } + ); + + /* Create the PierianDx Monitor Runs SFN */ + const pieriandxMonitorRunsStateMachine = + new PieriandxMonitorRunsStepFunctionStateMachineConstruct(this, 'monitor_runs', { + /* Stack Objects */ + dynamodbTableObj: dynamodbTableObj, + /* workflow info */ + workflowName: props.workflowName, + workflowVersion: props.workflowVersion, + /* Lambda objs */ + getInformaticsjobAndReportStatusLambdaObj: getInformaticsjobAndReportStatusLambdaObj, + generateOutputPayloadDataLambdaObj: generateOutputPayloadDataLambdaObj, + /* SSM Parameters */ + pierianDxBaseUrlSsmParameterObj: pieriandxBaseUrlSsmParameterObj, + /* Step function template */ + launchPieriandxMonitorRunsStepfunctionTemplatePath: path.join( + __dirname, + '/../step_function_templates/monitor_runs.asl.json' + ), + /* Event info */ + eventBusName: props.eventBusName, + eventDetailType: props.eventDetailType, + eventSource: props.eventSource, + payloadVersion: props.payloadVersion, + /* Custom */ + prefix: props.prefix, + }); + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/case_overview.png b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/case_overview.png new file mode 100755 index 0000000000000000000000000000000000000000..ffc777d8054fc4a0c18f41c4f00fdf28fbb8fff3 GIT binary patch literal 58423 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 literal 0 HcmV?d00001 diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/launch_overview.png b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/launch_overview.png new file mode 100755 index 0000000000000000000000000000000000000000..bcb95cd8584720df79a76d4f04b9699071876292 GIT binary patch literal 129007 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 literal 0 HcmV?d00001 diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/sequencerrun_overview.png b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/images/sequencerrun_overview.png new file mode 100755 index 0000000000000000000000000000000000000000..ebde4a3b194c17767093fcb479671f33bebf380e GIT binary patch literal 96617 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 hvtGz}e@h@;GDX87gziayPzW`}#rEdTL literal 0 HcmV?d00001 diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_case_py/generate_case.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_case_py/generate_case.py new file mode 100644 index 000000000..3d6cf7e63 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_case_py/generate_case.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python + +""" +Generate a case from a case object + +Environment variables +PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME: The secret name for the PierianDx Auth Token 'PierianDx/JwtKey' +PIERIANDX_USER_EMAIL: +PIERIANDX_INSTITUTION +PIERIANDX_BASE_URL + +A Case object will look like this + +{ + "identified": true, + "indication": "indication", + "panelName": "panelname", + "sampleType": "patientcare", + "specimens": [ + { + "accessionNumber": "caseaccessionnumber", + "dateAccessioned": "2021-01-01TZ:00+Z", + "dateReceived": "2021-01-01TZ:00+Z", + "datecollected": "2021-01-01TZ:00+Z", + "externalSpecimenId": "externalspecimenid", + "name": "panelspecimenscheme", + "type": { + "code": "specimentypecode", + "label": "specimentypelabel" + }, + "firstName": "John", + "lastName": "Doe", + "dateOfBirth": "1970-01-01", + "medicalRecordNumbers": [ + { + "mrn": "mrn", + "medicalFacility": { + "facility": "facility", + "hospitalNumber": "hospitalnumber" + } + } + ] + } + ], + "dagDescription": "dagdescription", + "dagName": "dagname", + "disease": { + "code": "diseasecode", + "label": "diseaselabel" + }, + "physicians": [ + { + "firstName": "Meredith", + "lastName": "Gray" + } + ] + } +""" + +import logging +from os import environ + +from pieriandx_pipeline_tools.utils.pieriandx_helpers import get_pieriandx_client +from requests import Response, HTTPError + +from pieriandx_pipeline_tools.utils.secretsmanager_helpers import set_pieriandx_env_vars + + +def handler(event, context): + case_creation_obj = event.get("case_creation_obj", {}) + set_pieriandx_env_vars() + pyriandx_client = get_pieriandx_client( + email=environ['PIERIANDX_USER_EMAIL'], + token=environ['PIERIANDX_USER_AUTH_TOKEN'], + instiution=environ['PIERIANDX_INSTITUTION'], + base_url=environ['PIERIANDX_BASE_URL'], + ) + + try: + response: Response = pyriandx_client._post_api( + endpoint="/case", + data=case_creation_obj + ) + response.raise_for_status() + except HTTPError as e: + logging.error(f"Failed to create case: {e}") + raise Exception(f"Failed to create case: {e}") + + if response.status_code != 200: + logging.error(f"Failed to create case: {response.json()}") + raise Exception(f"Failed to create case: {response.json()}") + + return response.json() + + +# if __name__ == "__main__": +# import json +# +# logging.basicConfig(level=logging.DEBUG) +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['PIERIANDX_BASE_URL'] = "https://app.uat.pieriandx.com/cgw-api/v2.0.0" +# environ['PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME'] = "collectPierianDxAccessToken" +# environ['PIERIANDX_INSTITUTION'] = "melbournetest" +# environ['PIERIANDX_USER_EMAIL'] = "services@umccr.org" +# +# print( +# json.dumps( +# handler( +# { +# "case_creation_obj": { +# "identified": True, +# "indication": "Test", +# "panelName": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", # // pragma: allowlist secret +# "sampleType": "patientcare", +# "specimens": [ +# { +# "accessionNumber": "SBJ04407__L2301368__V2__abcd12345", +# "dateAccessioned": "2021-01-01T00:00:00Z", +# "dateReceived": "2021-01-01T00:00:00Z", +# "datecollected": "2024-02-20T20:17:00Z", +# "externalSpecimenId": "externalspecimenid", +# "name": "primarySpecimen", +# "type": { +# "code": "122561005", +# "label": "Blood specimen from patient" +# }, +# "firstName": "John", +# "lastName": "Doe", +# "dateOfBirth": "1970-01-01", +# "medicalRecordNumbers": [ +# { +# "mrn": "3069999", +# "medicalFacility": { +# "facility": "Not Available", +# "hospitalNumber": "99" +# } +# } +# ] +# } +# ], +# "dagDescription": "tso500_ctdna_workflow", +# "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", +# "disease": { +# "code": "64572001", +# "label": "Disease" +# }, +# "physicians": [ +# { +# "firstName": "Meredith", +# "lastName": "Gray" +# } +# ] +# }, +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # Yields +# # { +# # 'id': '100937', +# # 'accessionNumber': 'SBJ04405__L2301368__ot__002', +# # 'dateCreated': '2024-04-14' +# # } + +# +# if __name__ == "__main__": +# import json +# +# logging.basicConfig(level=logging.DEBUG) +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['PIERIANDX_BASE_URL'] = "https://app.uat.pieriandx.com/cgw-api/v2.0.0" +# environ['PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME'] = "collectPierianDxAccessToken" +# environ['PIERIANDX_INSTITUTION'] = "melbournetest" +# environ['PIERIANDX_USER_EMAIL'] = "services@umccr.org" +# +# print( +# json.dumps( +# handler( +# { +# "case_creation_obj": { +# "identified": False, +# "indication": "NA", +# "panelName": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", # // pragma: allowlist secret +# "sampleType": "patientcare", +# "specimens": [ +# { +# "accessionNumber": "L2400160__V2__20241003f3149835", +# "dateAccessioned": "2024-10-04T09:01:32+1000", +# "dateReceived": "2024-10-04T09:01:32+1000", +# "datecollected": "2024-10-04T09:01:32+1000", +# "externalSpecimenId": "SSq-CompMM-1pc-10646259ilm", +# "name": "primarySpecimen", +# "type": { +# "code": "122561005", +# "label": "Blood specimen from patient" +# }, +# "studyIdentifier": "Testing", +# "studySubjectIdentifier": "CMM1pc-10646259ilm" +# } +# ], +# "dagDescription": "tso500_ctdna_workflow", +# "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", +# "disease": { +# "code": "55342001", +# "label": "Neoplastic disease" +# } +# } +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # Yields +# # { +# # 'id': '100937', +# # 'accessionNumber': 'SBJ04405__L2301368__ot__002', +# # 'dateCreated': '2024-04-14' +# # } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_informaticsjob_py/generate_informaticsjob.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_informaticsjob_py/generate_informaticsjob.py new file mode 100644 index 000000000..98af0e052 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_informaticsjob_py/generate_informaticsjob.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +""" +Generate a case from a case object + +Environment variables +PIERIANDX_AUTH_TOKEN_SECRET_ID: The secret name for the PierianDx Auth Token + +An informatics object will look like this + +{ + "input": [ + { + "accessionNumber": "caseaccessionnumber", + "sequencerRunInfos": [ + { + "accessionNumber": "caseaccessionnumber", + "barcode": "GACTGAGTAG+CACTATCAAC", + "lane": "1", + "sampleId": "L2301368", + "sampleType": "DNA" + } + ] + } + ] + } +""" + +import logging +from os import environ + +from pieriandx_pipeline_tools.utils.pieriandx_helpers import get_pieriandx_client +from requests import Response + +from pieriandx_pipeline_tools.utils.secretsmanager_helpers import set_pieriandx_env_vars + + +def handler(event, context): + # Get inputs + informaticsjob_creation_obj = event.get("informaticsjob_creation_obj", {}) + case_id = event.get("case_id", None) + set_pieriandx_env_vars() + pyriandx_client = get_pieriandx_client( + email=environ['PIERIANDX_USER_EMAIL'], + token=environ['PIERIANDX_USER_AUTH_TOKEN'], + instiution=environ['PIERIANDX_INSTITUTION'], + base_url=environ['PIERIANDX_BASE_URL'], + ) + response: Response = pyriandx_client._post_api( + endpoint=f"/case/{case_id}/informaticsJobs", + data=informaticsjob_creation_obj + ) + + if response.status_code != 200: + logging.error(f"Failed to create informaticsjob: {response.json()}") + raise Exception(f"Failed to create informaticsjob: {response.json()}") + + return response.json() + +# +# if __name__ == "__main__": +# import json +# +# print( +# json.dumps( +# handler( +# { +# "informaticsjob_creation_obj": { +# "input": [ +# { +# "accessionNumber": "SBJ04405__L2301368__ot__003", +# "sequencerRunInfos": [ +# { +# "runId": "231116_A01052_0172_BHVLM5DSX7__SBJ04405__L2301368__ot__003__20240415abcd0001", +# "barcode": "GACTGAGTAG-CACTATCAAC", +# "lane": "1", +# "sampleId": "L2301368", +# "sampleType": "DNA" +# } +# ] +# } +# ] +# }, +# "case_id": "100938" +# }, +# None +# ), +# indent=2 +# ) +# ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_output_data_payload_py/generate_output_data_payload.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_output_data_payload_py/generate_output_data_payload.py new file mode 100644 index 000000000..5b67785cf --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_output_data_payload_py/generate_output_data_payload.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +""" +Get the data payload python + +The outputs (if report status is complete) will comprise the following + +# Case url where pieriandx_base_url is one of https://app.pieriandx.com +{pieriandx_base_url}/cgw/order/viewOrderDetails/{case_id} +# VCF Output URL +{pieriandx_base_url}/cgw/informatics/downloadJobOutputAnalysisFile?caseId={case_id}&jobId={job_id}&accessionNumber={case_accession_number}&fileName=main.vcf +# Biomarker Report PDF URL +{pieriandx_base_url}/cgw/informatics/downloadJobOutputAnalysisFile?caseId={case_id}&jobId={job_id}&accessionNumber={case_accession_number}&fileName={sample_name}_BiomarkerReport.txt +# Report PDF URL +{pieriandx_base_url}/cgw/report/openPdfReport/{report_id} + +""" + + +def handler(event, context): + """ + Get the data payload with or without the outputs + :param event: + :param context: + :return: + """ + + # Get inputs + inputs = event.get("inputs") + engine_parameters = event.get("engine_parameters") + tags = event.get("tags") + report_status = event.get("report_status") + case_id = event.get("case_id") + job_id = event.get("job_id") + case_accession_number = event.get("case_accession_number") + report_id = event.get("report_id") + pieriandx_base_url = event.get("pieriandx_base_url") + sample_name = event.get("sample_name") + + # Initial dict + return_dict = { + "data_payload": { + "inputs": inputs, + "engineParameters": engine_parameters, + "tags": tags + } + } + + # Return if the report generation is not complete yet + if not report_status in ["report_generation_complete", "complete"]: + # Return as id + return return_dict + + # Set the outputs + return_dict["data_payload"]["outputs"] = { + "caseUrl": f"{pieriandx_base_url.replace("cgw-api/v2.0.0/", "")}/cgw/order/viewOrderDetails/{case_id}", + "vcfOutputUrl": f"{pieriandx_base_url.replace("cgw-api/v2.0.0/", "")}/cgw/informatics/downloadJobOutputAnalysisFile?caseId={case_id}&jobId={job_id}&accessionNumber={case_accession_number}&fileName=main.vcf", + "reportPdfUrl": f"{pieriandx_base_url.replace("cgw-api/v2.0.0/", "")}/cgw/report/openPdfReport/{report_id}", + "biomarkerReportUrl": f"{pieriandx_base_url.replace("cgw-api/v2.0.0/", "")}/cgw/informatics/downloadJobOutputAnalysisFile?caseId={case_id}&jobId={job_id}&accessionNumber={case_accession_number}&fileName={sample_name}_BiomarkerReport.txt" + } + + return return_dict + + +# if __name__ == "__main__": +# import json +# +# print( +# json.dumps( +# handler( +# { +# "inputs": { +# "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# "panelVersion": "main", +# "caseMetadata": { +# "isIdentified": False, +# "caseAccessionNumber": "L2400160__V2__2024100405a12a95", +# "externalSpecimenId": "SSq-CompMM-1pc-10646259ilm", +# "sampleType": "patientcare", +# "specimenLabel": "primarySpecimen", +# "indication": "NA", +# "diseaseCode": 55342001, +# "specimenCode": "122561005", +# "sampleReception": { +# "dateAccessioned": "2024-10-04T10:03:11+1000", +# "dateCollected": "2024-10-04T10:03:11+1000", +# "dateReceived": "2024-10-04T10:03:11+1000" +# }, +# "study": { +# "id": "Testing", +# "subjectIdentifier": "CMM1pc-10646259ilm" +# } +# }, +# "dataFiles": { +# "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/DragenCaller/L2400160/L2400160.microsat_output.json", +# "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/Tmb/L2400160/L2400160.tmb.metrics.csv", +# "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.cnv.vcf.gz", +# "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.hard-filtered.vcf.gz", +# "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_Fusions.csv", +# "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_MetricsOutput.tsv", +# "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" +# } +# }, +# "engine_parameters": { +# "caseId": "103779", +# "informaticsJobId": "46014" +# }, +# "tags": { +# "metadataFromRedCap": False, +# "isIdentified": False, +# "libraryId": "L2400160", +# "sampleType": "patientcare", +# "projectId": "Testing", +# "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC" +# }, +# "report_status": "complete", +# "case_id": "103779", +# "job_id": "46014", +# "case_accession_number": "L2400160__V2__2024100405a12a95", +# "report_id": "38152", +# "pieriandx_base_url": "https://app.uat.pieriandx.com/cgw-api/v2.0.0", +# "sample_name": "L2400160" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "data_payload": { +# # "inputs": { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "panelVersion": "main", +# # "caseMetadata": { +# # "isIdentified": false, +# # "caseAccessionNumber": "L2400160__V2__2024100405a12a95", +# # "externalSpecimenId": "SSq-CompMM-1pc-10646259ilm", +# # "sampleType": "patientcare", +# # "specimenLabel": "primarySpecimen", +# # "indication": "NA", +# # "diseaseCode": 55342001, +# # "specimenCode": "122561005", +# # "sampleReception": { +# # "dateAccessioned": "2024-10-04T10:03:11+1000", +# # "dateCollected": "2024-10-04T10:03:11+1000", +# # "dateReceived": "2024-10-04T10:03:11+1000" +# # }, +# # "study": { +# # "id": "Testing", +# # "subjectIdentifier": "CMM1pc-10646259ilm" +# # } +# # }, +# # "dataFiles": { +# # "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/DragenCaller/L2400160/L2400160.microsat_output.json", +# # "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/Tmb/L2400160/L2400160.tmb.metrics.csv", +# # "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.cnv.vcf.gz", +# # "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.hard-filtered.vcf.gz", +# # "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_Fusions.csv", +# # "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_MetricsOutput.tsv", +# # "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" +# # } +# # }, +# # "engineParameters": { +# # "caseId": "103779", +# # "informaticsJobId": "46014" +# # }, +# # "tags": { +# # "metadataFromRedCap": false, +# # "isIdentified": false, +# # "libraryId": "L2400160", +# # "sampleType": "patientcare", +# # "projectId": "Testing", +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC" +# # }, +# # "outputs": { +# # "caseUrl": "https://app.uat.pieriandx.com/cgw-api/v2.0.0/cgw/order/viewOrderDetails/103779", +# # "vcfOutputUrl": "https://app.uat.pieriandx.com/cgw-api/v2.0.0/cgw/informatics/downloadJobOutputAnalysisFile?caseId=103779&jobId=46014&accessionNumber=L2400160__V2__2024100405a12a95&fileName=main.vcf", +# # "reportPdfUrl": "https://app.uat.pieriandx.com/cgw-api/v2.0.0/cgw/report/openPdfReport/38152", +# # "biomarkerReportUrl": "https://app.uat.pieriandx.com/cgw-api/v2.0.0/cgw/informatics/downloadJobOutputAnalysisFile?caseId=103779&jobId=46014&accessionNumber=L2400160__V2__2024100405a12a95&fileName=L2400160_BiomarkerReport.txt" +# # } +# # } +# # } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_pieriandx_objects_py/generate_pieriandx_objects.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_pieriandx_objects_py/generate_pieriandx_objects.py new file mode 100644 index 000000000..064600506 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_pieriandx_objects_py/generate_pieriandx_objects.py @@ -0,0 +1,623 @@ +#!/usr/bin/env python3 + +""" +Tackle workflow inputs. + +By far the most complicated lambda of the lot. + +Expects event input in the following syntax (how this is built is someone else's problem) + +* dag: Object + * name: string # The name of this case dag + * description: string # The description of this case dag +* case_metadata: Object + * panel_name: string # The name of this case’s panel + * specimen_label: string # The mapping to the panels specimen scheme + * sample_type: Enum # patientcare, clinical_trial, validation, proficiency_testing + * indication: String # Optional input + * disease: Object + * code: string # The disease id + * label: string # The name of the disease (optional) + * is_identified: bool # Boolean + * case_accession_number: string - must be unique - uses syntax SBJID__LIBID__NNN + * specimen_type: + * code: string # The SNOMED-CT term for a specimen type + * label: Optional label for the specimen type + * external_specimen_id: string # The external specimen id + * date_accessioned: Datetime # The date the case was accessioned + * date_collected: Datetime # The date the specimen was collected + * date_received: Datetime # The date the specimen was received + * gender: Enum # unknown, male, femail, unspecified, other, ambiguous, not_applicable + * ethnicity: Enum # unknown, hispanic_or_latino, not_hispanic_or_latino, not_reported + * race: Enum # american_indian_or_alaska_native, asian, black_or_african_american, native_hawaiian_or_other_pacific_islander, not_reported, unknown, white + + > Note: If the case is de-identified, the following fields are required + * study_id: String # Only required if is_identified is false + * study_subject_identifier: String # Only required if is_identified is false + + > Note: If the case is identified, the following fields are required + * date_of_birth: Datetime # Only required if is_identified is true + * first_name: String # Only required if is_identified is true + * last_name: String # Only required if is_identified is true + * medical_record_numbers: Object # Only required if is_identified is true + * mrn: string # The medical record number + * medical_facility: Object + * facility: string # The name of the facility + * hospital_number: string # The hospital number + * requesting_physician: Object + * first_name: string + * last_name: string + +* data_files: Object + * microsat_output: uri + * tmb_metrics: uri + * cnv: uri + * hard_filtered: uri + * fusions: uri + * metrics_output: uri + +* samplesheet_b64gz: str +* instrument_run_id: str +* portal_run_id: str +* sequencerrun_s3_prefix: str + +We then use pydantic to validate the input and generate the following outputs + +* case_creation_obj: A CaseCreation object +* sequencerrun_creation_obj: A SequencerrunCreation object +* informaticsjob_creation_obj: An InformaticsjobCreation object +* data_files: List of DataFile objects (each containing a src_uri, dest_uri and file_type) +* sequencerrun_s3_path_root: The root s3 path we will upload data to. +* This is the same as the input sequencerrun_s3_path but we will extend the run id to it +""" + +import logging +import pandas as pd + +from pieriandx_pipeline_tools.utils.secretsmanager_helpers import set_icav2_env_vars +from pieriandx_pipeline_tools.pieriandx_classes.data_file import DataFile, DataType +from pieriandx_pipeline_tools.pieriandx_enums.specimen_type import SpecimenType +from pieriandx_pipeline_tools.utils.samplesheet_helpers import read_v2_samplesheet + +from pieriandx_pipeline_tools.pieriandx_classes.physician import Physician +from pieriandx_pipeline_tools.pieriandx_classes.sequencerrun import SequencerrunCreation +from pieriandx_pipeline_tools.pieriandx_classes.informaticsjob import InformaticsjobCreation +from pieriandx_pipeline_tools.pieriandx_classes.specimen_sequencer_info import SpecimenSequencerInfo +from pieriandx_pipeline_tools.pieriandx_classes.dag import Dag +from pieriandx_pipeline_tools.pieriandx_classes.disease import Disease +from pieriandx_pipeline_tools.pieriandx_classes.medical_record_number import MedicalRecordNumber +from pieriandx_pipeline_tools.pieriandx_classes.medical_facility import MedicalFacility + +from pieriandx_pipeline_tools.pieriandx_enums.sequencing_type import SequencingType +from pieriandx_pipeline_tools.pieriandx_enums.sample_type import SampleType + +TOP_LEVEL_KEYS = [ + "dag", + "case_metadata", + "data_files", + "panel_name", + "instrument_run_id", + "portal_run_id", + "sequencerrun_s3_path_root", +] + +# Set basic logger +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + + + +def handler(event, context): + # Set env vars + set_icav2_env_vars() + + # Basic housekeeping + if event is None: + raise ValueError("Event is required") + if not isinstance(event, dict): + raise ValueError("Event must be a dictionary") + + # Check for top level keys + if not all([key in event for key in TOP_LEVEL_KEYS]): + logger.error(f"Could not find keys {' '.join([key for key in TOP_LEVEL_KEYS if key not in event])}") + raise ValueError("Event is missing required top level keys") + + # Read samplesheet - we need this for the sequencer run infos + v2_samplesheet_dict = read_v2_samplesheet(event.get("samplesheet_uri")) + + # Collect the tso500l_data section + if not len(v2_samplesheet_dict.get("tso500l_data")) == 1: + logger.error("Should only be one item in the tso500l data section") + raise ValueError + + tso500l_data_samplesheet_obj = v2_samplesheet_dict.get("tso500l_data")[0] + sample_name = tso500l_data_samplesheet_obj['sample_id'] + + if event.get("case_metadata").get("isIdentified"): + from pieriandx_pipeline_tools.pieriandx_classes.case import IdentifiedCaseCreation as CaseCreation + from pieriandx_pipeline_tools.pieriandx_classes.specimen import IdentifiedSpecimen as Specimen + else: + from pieriandx_pipeline_tools.pieriandx_classes.case import DeIdentifiedCaseCreation as CaseCreation + from pieriandx_pipeline_tools.pieriandx_classes.specimen import DeIdentifiedSpecimen as Specimen + + # Other imports + case_creation_obj = CaseCreation( + # Standard case collection + dag=Dag( + name=event.get("dag").get("dagName"), + description=event.get("dag").get("dagDescription") + ), + disease=Disease(code=int(event.get("case_metadata").get("diseaseCode"))), + indication=event.get("case_metadata").get("indication", None), + panel_name=event.get("panel_name"), + sample_type=SampleType(event.get("case_metadata").get("sampleType").lower()), + # Identified only fields + requesting_physician=( + Physician( + first_name=event.get("case_metadata").get("requestingPhysician").get("firstName"), + last_name=event.get("case_metadata").get("requestingPhysician").get("lastName") + ) if event.get("case_metadata").get("isIdentified") + else None + ), + # Specimen collection + specimen=Specimen( + # Standard specimen collection + case_accession_number=event.get("case_metadata").get("caseAccessionNumber"), + date_accessioned=pd.to_datetime( + event.get("case_metadata").get("sampleReception").get("dateAccessioned"), + utc=True + ), + date_received=pd.to_datetime( + event.get("case_metadata").get("sampleReception").get("dateReceived"), + utc=True + ), + date_collected=pd.to_datetime( + event.get("case_metadata").get("sampleReception").get("dateCollected"), + utc=True + ), + external_specimen_id=event.get("case_metadata").get("externalSpecimenId"), + specimen_label=event.get("case_metadata").get("specimenLabel"), + gender=event.get("case_metadata").get("gender", None), + ethnicity=event.get("case_metadata").get("ethnicity", None), + race=event.get("case_metadata").get("race", None), + specimen_type=SpecimenType(code=int(event.get("case_metadata").get("specimenCode"))), + # Identified only fields + date_of_birth=pd.to_datetime( + event.get("case_metadata").get("patientInformation", {}).get("dateOfBirth", None) + ), + first_name=event.get("case_metadata").get("patientInformation", {}).get("firstName", None), + last_name=event.get("case_metadata").get("patientInformation", {}).get("lastName", None), + medical_record_number=MedicalRecordNumber( + mrn=event.get("case_metadata").get("medicalRecordNumbers").get("mrn"), + medical_facility=( + MedicalFacility( + facility=( + event.get("case_metadata") + .get("medicalRecordNumbers") + .get("medicalFacility") + .get("facility") + ), + hospital_number=( + event.get("case_metadata") + .get("medicalRecordNumbers") + .get("medicalFacility") + .get("hospitalNumber") + ) + ) + ) + ) if event.get("case_metadata").get("isIdentified") else None, + # De-identified only fields + study_identifier=event.get("case_metadata").get("study", {}).get("id", None), + study_subject_identifier=event.get("case_metadata").get("study", {}).get("subjectIdentifier", None) + ) + ) + + # Set run id (used for sequencer run path) + run_id = "__".join( + [ + event.get("instrument_run_id"), + event.get("case_metadata").get("caseAccessionNumber"), + event.get('portal_run_id') + ] + ) + + # Get sequencer runinfo object (used for both sequencer run and informatics job creation) + specimen_sequencer_info = SpecimenSequencerInfo( + run_id=run_id, + case_accession_number=event.get("case_metadata").get("caseAccessionNumber"), + barcode=f"{tso500l_data_samplesheet_obj.get('index')}-{tso500l_data_samplesheet_obj.get('index2')}", + lane=tso500l_data_samplesheet_obj.get("lane", 1), + sample_id=tso500l_data_samplesheet_obj.get("sample_id"), + sample_type=tso500l_data_samplesheet_obj.get("sample_type") + ) + + # Sequencerrun creation object + sequencer_run_creation = SequencerrunCreation( + run_id=run_id, + specimen_sequence_info=specimen_sequencer_info, + sequencing_type=SequencingType.PAIRED_END + ) + + # Informatics job + informatics_job_creation = InformaticsjobCreation( + case_accession_number=event.get("case_metadata").get("caseAccessionNumber"), + specimen_sequencer_run_info=specimen_sequencer_info + ) + + # Add sequencerrun path + sequencerrun_s3_path = f"{event.get('sequencerrun_s3_path_root').rstrip('/')}/{run_id}" + + # Data files + data_files = list( + map( + lambda data_file_iter_kv: DataFile( + sequencerrun_path_root=sequencerrun_s3_path, + file_type=DataType(data_file_iter_kv[0]), + sample_id=tso500l_data_samplesheet_obj.get("sample_id"), + src_uri=data_file_iter_kv[1], + contents=None + ), + filter( + lambda data_file_iter_kv: ( + data_file_iter_kv[0] in + list(map(lambda enum_iter: enum_iter.value, DataType._member_map_.values())) + ), + event.get("data_files").items() + ) + ) + ) + + # Return list of objects for downstream sfns to consume + return { + "case_creation_obj": case_creation_obj.to_dict(), + "sequencerrun_creation_obj": sequencer_run_creation.to_dict(), + "informaticsjob_creation_obj": informatics_job_creation.to_dict(), + "data_files": list(map(lambda data_file_iter: data_file_iter.to_dict(), data_files)), + "sequencerrun_s3_path": sequencerrun_s3_path, + "sample_name": sample_name, + } + + + +# # Idenitified Patient +# if __name__ == "__main__": +# import json +# from os import environ +# +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['ICAV2_ACCESS_TOKEN_SECRET_ID'] = "ICAv2JWTKey-umccr-prod-service-dev" +# print( +# json.dumps( +# handler( +# { +# "sequencerrun_s3_path_root": "s3://pdx-cgwxfer-test/melbournetest", +# "portal_run_id": "abcd1234", +# "samplesheet_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv", +# "panel_name": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", # pragma: allowlist secret +# "dag": { +# "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", +# "dagDescription": "tso500_ctdna_workflow" +# }, +# "data_files": { +# "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/DragenCaller/L2301368/L2301368.microsat_output.json", +# "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/Tmb/L2301368/L2301368.tmb.metrics.csv", +# "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368.cnv.vcf.gz", +# "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368.hard-filtered.vcf.gz", +# "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368_Fusions.csv", +# "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368_MetricsOutput.tsv", +# "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" +# }, +# "case_metadata": { +# "isIdentified": True, +# "caseAccessionNumber": "SBJ04407__L2301368__V2__abcd1234", +# "externalSpecimenId": "externalspecimenid", +# "sampleType": "PatientCare", +# "specimenLabel": "primarySpecimen", +# "indication": "Test", +# "diseaseCode": 64572001, +# "specimenCode": 122561005, +# "sampleReception": { +# "dateAccessioned": "2021-01-01", +# "dateCollected": "2024-02-20", +# "dateReceived": "2021-01-01" +# }, +# "patientInformation": { +# "dateOfBirth": "1970-01-01", +# "firstName": "John", +# "lastName": "Doe" +# }, +# "medicalRecordNumbers": { +# "mrn": "3069999", +# "medicalFacility": { +# "facility": "Not Available", +# "hospitalNumber": "99" +# } +# }, +# "requestingPhysician": { +# "firstName": "Meredith", +# "lastName": "Gray" +# } +# }, +# "instrument_run_id": "231116_A01052_0172_BHVLM5DSX7" +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # Yields +# # { +# # "case_creation_obj": { +# # "identified": true, +# # "indication": "Test", +# # "panelName": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", # pragma: allowlist secret +# # "sampleType": "patientcare", +# # "specimens": [ +# # { +# # "accessionNumber": "SBJ04407__L2301368__V2__abcd1234", +# # "dateAccessioned": "2021-01-01T00:00:00Z", +# # "dateReceived": "2021-01-01T00:00:00Z", +# # "datecollected": "2024-02-20T00:00:00Z", +# # "externalSpecimenId": "externalspecimenid", +# # "name": "primarySpecimen", +# # "type": { +# # "code": "122561005", +# # "label": "Blood specimen from patient" +# # }, +# # "firstName": "John", +# # "lastName": "Doe", +# # "dateOfBirth": "1970-01-01", +# # "medicalRecordNumbers": [ +# # { +# # "mrn": "3069999", +# # "medicalFacility": { +# # "facility": "Not Available", +# # "hospitalNumber": "99" +# # } +# # } +# # ] +# # } +# # ], +# # "dagDescription": "tso500_ctdna_workflow", +# # "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", +# # "disease": { +# # "code": "64572001", +# # "label": "Disease" +# # }, +# # "physicians": [ +# # { +# # "firstName": "Meredith", +# # "lastName": "Gray" +# # } +# # ] +# # }, +# # "sequencerrun_creation_obj": { +# # "runId": "231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234", +# # "specimens": [ +# # { +# # "accessionNumber": "SBJ04407__L2301368__V2__abcd1234", +# # "barcode": "CCATCATTAG-AGAGGCAACC", +# # "lane": "1", +# # "sampleId": "L2400161", +# # "sampleType": "DNA" +# # } +# # ], +# # "type": "pairedEnd" +# # }, +# # "informaticsjob_creation_obj": { +# # "input": [ +# # { +# # "accessionNumber": "SBJ04407__L2301368__V2__abcd1234", +# # "sequencerRunInfos": [ +# # { +# # "runId": "231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234", +# # "barcode": "CCATCATTAG-AGAGGCAACC", +# # "lane": "1", +# # "sampleId": "L2400161", +# # "sampleType": "DNA" +# # } +# # ] +# # } +# # ] +# # }, +# # "data_files": [ +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/DragenCaller/L2301368/L2301368.microsat_output.json", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234/Data/Intensities/BaseCalls/L2400161.microsat_output.json", +# # "needs_decompression": false, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/Tmb/L2301368/L2301368.tmb.metrics.csv", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234/Data/Intensities/BaseCalls/L2400161.tmb.metrics.csv", +# # "needs_decompression": false, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368.cnv.vcf.gz", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234/Data/Intensities/BaseCalls/L2400161.cnv.vcf", +# # "needs_decompression": true, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368.hard-filtered.vcf.gz", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234/Data/Intensities/BaseCalls/L2400161.hard-filtered.vcf", +# # "needs_decompression": true, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368_Fusions.csv", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234/Data/Intensities/BaseCalls/L2400161_Fusions.csv", +# # "needs_decompression": false, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2301368/L2301368_MetricsOutput.tsv", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234/Data/Intensities/BaseCalls/L2400161_MetricsOutput.tsv", +# # "needs_decompression": false, +# # "contents": null +# # } +# # ], +# # "sequencerrun_s3_path": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2301368__V2__abcd1234__abcd1234", +# # "sample_name": "L2400161" +# # } + +# +# # # De-Idenitified Patient +# if __name__ == "__main__": +# import json +# from os import environ +# +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['ICAV2_ACCESS_TOKEN_SECRET_ID'] = "ICAv2JWTKey-umccr-prod-service-dev" +# print( +# json.dumps( +# handler( +# { +# "sequencerrun_s3_path_root": "s3://pdx-cgwxfer-test/melbournetest", +# "portal_run_id": "20241003f44a5496", +# "samplesheet_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv", +# "panel_name": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", # pragma: allowlist secret +# "dag": { +# "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", +# "dagDescription": "tso500_ctdna_workflow" +# }, +# "data_files": { +# "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/DragenCaller/L2400160/L2400160.microsat_output.json", +# "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/Tmb/L2400160/L2400160.tmb.metrics.csv", +# "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.cnv.vcf.gz", +# "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.hard-filtered.vcf.gz", +# "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_Fusions.csv", +# "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_MetricsOutput.tsv", +# "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" +# }, +# "case_metadata": { +# "isIdentified": False, +# "caseAccessionNumber": "L2400160__V2__20241003f44a5496", +# "externalSpecimenId": "SSq-CompMM-1pc-10646259ilm", +# "sampleType": "patientcare", +# "specimenLabel": "primarySpecimen", +# "indication": None, +# "diseaseCode": 55342001, +# "specimenCode": "122561005", +# "sampleReception": { +# "dateAccessioned": "2024-10-03", +# "dateCollected": "2024-10-03", +# "dateReceived": "2024-10-03" +# }, +# "study": { +# "id": "Testing", +# "subjectIdentifier": "CMM1pc-10646259ilm" +# } +# }, +# "instrument_run_id": "240229_A00130_0288_BH5HM2DSXC" +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # Yields +# # { +# # "case_creation_obj": { +# # "identified": false, +# # "panelName": "tso500_DRAGEN_ctDNA_v2_1_Universityofmelbourne", # pragma: allowlist secret +# # "sampleType": "patientcare", +# # "specimens": [ +# # { +# # "accessionNumber": "L2400160__V2__20241003f44a5496", +# # "dateAccessioned": "2024-10-03T00:00:00Z", +# # "dateReceived": "2024-10-03T00:00:00Z", +# # "datecollected": "2024-10-03T00:00:00Z", +# # "externalSpecimenId": "SSq-CompMM-1pc-10646259ilm", +# # "name": "primarySpecimen", +# # "type": { +# # "code": "122561005", +# # "label": "Blood specimen from patient" +# # }, +# # "studyIdentifier": "Testing", +# # "studySubjectIdentifier": "CMM1pc-10646259ilm" +# # } +# # ], +# # "dagDescription": "tso500_ctdna_workflow", +# # "dagName": "cromwell_tso500_ctdna_workflow_1.0.4", +# # "disease": { +# # "code": "55342001", +# # "label": "Neoplastic disease" +# # } +# # }, +# # "sequencerrun_creation_obj": { +# # "runId": "240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496", +# # "specimens": [ +# # { +# # "accessionNumber": "L2400160__V2__20241003f44a5496", +# # "barcode": "AGAGGCAACC-CCATCATTAG", +# # "lane": "1", +# # "sampleId": "L2400160", +# # "sampleType": "DNA" +# # } +# # ], +# # "type": "pairedEnd" +# # }, +# # "informaticsjob_creation_obj": { +# # "input": [ +# # { +# # "accessionNumber": "L2400160__V2__20241003f44a5496", +# # "sequencerRunInfos": [ +# # { +# # "runId": "240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496", +# # "barcode": "AGAGGCAACC-CCATCATTAG", +# # "lane": "1", +# # "sampleId": "L2400160", +# # "sampleType": "DNA" +# # } +# # ] +# # } +# # ] +# # }, +# # "data_files": [ +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/DragenCaller/L2400160/L2400160.microsat_output.json", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496/Data/Intensities/BaseCalls/L2400160.microsat_output.json", +# # "needs_decompression": false, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Logs_Intermediates/Tmb/L2400160/L2400160.tmb.metrics.csv", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496/Data/Intensities/BaseCalls/L2400160.tmb.metrics.csv", +# # "needs_decompression": false, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.cnv.vcf.gz", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496/Data/Intensities/BaseCalls/L2400160.cnv.vcf", +# # "needs_decompression": true, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160.hard-filtered.vcf.gz", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496/Data/Intensities/BaseCalls/L2400160.hard-filtered.vcf", +# # "needs_decompression": true, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_Fusions.csv", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496/Data/Intensities/BaseCalls/L2400160_Fusions.csv", +# # "needs_decompression": false, +# # "contents": null +# # }, +# # { +# # "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/Results/L2400160/L2400160_MetricsOutput.tsv", +# # "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496/Data/Intensities/BaseCalls/L2400160_MetricsOutput.tsv", +# # "needs_decompression": false, +# # "contents": null +# # } +# # ], +# # "sequencerrun_s3_path": "s3://pdx-cgwxfer-test/melbournetest/240229_A00130_0288_BH5HM2DSXC__L2400160__V2__20241003f44a5496__20241003f44a5496", +# # "sample_name": "L2400160" +# # } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_samplesheet_py/generate_samplesheet.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_samplesheet_py/generate_samplesheet.py new file mode 100644 index 000000000..573866520 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_samplesheet_py/generate_samplesheet.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +""" +Generate a samplesheet from a cttso v2 samplesheet file + +{ + "samplesheet_uri": "s3://.../Logs_Intermediates/SampleSheet_Validation/SampleSheet_Intermediate.csv" +} + +Returns + +{ + "samplesheet_str": "" +} + +""" + +# Standard imports +from typing import Dict +import logging +import boto3 +from os import environ + +# Custom libraries +from v2_samplesheet_maker.functions.v2_samplesheet_writer import v2_samplesheet_writer + +# Local imports +from pieriandx_pipeline_tools.utils.samplesheet_helpers import read_v2_samplesheet + +# Globals +ICAV2_BASE_URL = "https://ica.illumina.com/ica/rest" + +# Set loggers +logger = logging.getLogger() +logger.setLevel(logging.INFO) + + +def get_secrets_manager_client() -> 'SecretsManagerClient': + """ + Return Secrets Manager client + """ + return boto3.client("secretsmanager") + + +def get_secret(secret_id: str) -> str: + """ + Return secret value + """ + return get_secrets_manager_client().get_secret_value(SecretId=secret_id)["SecretString"] + + +# Functions +def set_icav2_env_vars(): + """ + Set the icav2 environment variables + :return: + """ + environ["ICAV2_BASE_URL"] = ICAV2_BASE_URL + environ["ICAV2_ACCESS_TOKEN"] = get_secret( + environ["ICAV2_ACCESS_TOKEN_SECRET_ID"] + ) + + +def handler(event, context) -> Dict[str, str]: + # Set ICAv2 env variables + logger.info("Setting icav2 env vars from secrets manager") + set_icav2_env_vars() + + # Get the samplesheet uri + samplesheet_uri = event.get("samplesheet_uri", None) + + # Get the samplesheet as an icav2 projectdata object + samplesheet_dict = read_v2_samplesheet(samplesheet_uri) + + # Convert to string (as a samplesheet) + samplesheet_str = str(v2_samplesheet_writer(samplesheet_dict).read()) + + # Replace TSO500L_Data header line + # Sample_ID,Sample_Type,Lane,Index,Index2,I7_Index_ID,I5_Index_ID + # With + # Sample_ID,Sample_Type,Lane,index,index2,I7_Index_ID,I5_Index_ID + # Without changing the Index1Cycles and Index2Cycles of the Reads section + # Hacky and dirty workaround required because PierianDx is not able to handle Index / Index2 in uppercase + # Assumes Index and Index2 fall within the middle of the index line + samplesheet_str = samplesheet_str.replace(',Index', ',index') + + return { + "samplesheet_str": samplesheet_str + } + + +# if __name__ == "__main__": +# import json +# from os import environ +# +# environ['ICAV2_ACCESS_TOKEN_SECRET_ID'] = "ICAv2JWTKey-umccr-prod-service-dev" +# environ['AWS_REGION'] = "ap-southeast-2" +# environ['AWS_PROFILE'] = 'umccr-development' +# print( +# json.dumps( +# handler( +# { +# "samplesheet_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # Yields +# # { +# # "samplesheet_str": "[Header]\nFileFormatVersion,2\nRunName,240229_A00130_0288_BH5HM2DSXC\nInstrumentType,NovaSeq\n\n[TSO500L_Settings]\n\n\n[TSO500L_Data]\nSample_ID,index_ID,Sample_Type,index,index2,I7_Index_ID,I5_Index_ID\nL2400161,UDP0019,DNA,CCATCATTAG,AGAGGCAACC,UDP0019,UDP0019\n" +# # } + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_sequencerrun_case_py/generate_sequencerrun_case.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_sequencerrun_case_py/generate_sequencerrun_case.py new file mode 100644 index 000000000..459388cc2 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/generate_sequencerrun_case_py/generate_sequencerrun_case.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +""" +Generate a case from a case object + +Environment variables +PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME: The lambda used to collect the auth token + +A sequencerrun object will look like this + +{ + "runId": "20201203_A00123_0001_BHJGJFDS__caseaccessionnumber__20240411235959", + "specimens": [ + { + "accessionNumber": "caseaccessionnumber", + "barcode": "GACTGAGTAG+CACTATCAAC", + "lane": "1", + "sampleId": "L2301368", + "sampleType": "DNA" + } + ], + "type": "pairedEnd" +} +""" + +# Standard Imports +import logging +from requests import Response +from os import environ + + +# Local imports +from pieriandx_pipeline_tools.utils.pieriandx_helpers import get_pieriandx_client +from pieriandx_pipeline_tools.utils.secretsmanager_helpers import set_pieriandx_env_vars + + +def handler(event, context): + # Get inputs + sequencerrun_creation_obj = event.get("sequencerrun_creation_obj", {}) + + set_pieriandx_env_vars() + pyriandx_client = get_pieriandx_client( + email=environ['PIERIANDX_USER_EMAIL'], + token=environ['PIERIANDX_USER_AUTH_TOKEN'], + instiution=environ['PIERIANDX_INSTITUTION'], + base_url=environ['PIERIANDX_BASE_URL'], + ) + + response: Response = pyriandx_client._post_api( + endpoint=f"/sequencerRun", + data=sequencerrun_creation_obj + ) + + if response.status_code != 200: + logging.error(f"Failed to create sequencerrun: {response.json()}") + raise Exception(f"Failed to create sequencerrun: {response.json()}") + + return response.json() + +# +# if __name__ == "__main__": +# import json +# print( +# json.dumps( +# handler( +# { +# "sequencerrun_creation_obj": { +# "runId": "231116_A01052_0172_BHVLM5DSX7__SBJ04405__L2301368__ot__003__20240415abcd0001", +# "specimens": [ +# { +# "accessionNumber": "SBJ04405__L2301368__ot__003", +# "barcode": "GACTGAGTAG-CACTATCAAC", +# "lane": "1", +# "sampleId": "L2301368", +# "sampleType": "DNA" +# } +# ], +# "type": "pairedEnd" +# } +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # Yields +# # { +# # "id": "38862" +# # } \ No newline at end of file diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/get_informaticsjob_and_report_status_py/get_informaticsjob_and_report_status.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/get_informaticsjob_and_report_status_py/get_informaticsjob_and_report_status.py new file mode 100644 index 000000000..88141a68f --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/get_informaticsjob_and_report_status_py/get_informaticsjob_and_report_status.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 + +""" +Get informatics job status + +Given a case id and an informatics job id, return the status of the job + +The job status can be one of the following: +* waiting # PROCESSING +* ready # PROCESSING +* running # PROCESSING +* complete # TERMINAL +* failed # TERMINAL +* canceled # TERMINAL + +If the job is complete, check the reports for the case to see if the report generation is also complete + +Also return the DynamoDB object to the expression reference and the object dict since +the object can vary depending on what status the job / report is at + +job_status: STR VALUE OF THE JOB STATUS +job_status_bool: BOOL VALUE OF THE JOB STATUS TRUE IF COMPLETE, FALSE IF FAILED, NONE OTHERWISE +report_id: INT VALUE OF THE REPORT ID +report_status: STR VALUE OF THE REPORT STATUS +report_status_bool: BOOL VALUE OF THE REPORT STATUS TRUE IF COMPLETE, FALSE IF FAILED, NONE OTHERWISE +job_status_changed: BOOL VALUE OF WHETHER THE JOB STATUS HAS CHANGED TRUE OR FALSE +expression_attribute_values_dict: DICT OF THE EXPRESSION ATTRIBUTE VALUES FOR DYNAMODB UPDATE EXPRESSION +update_expression_str: STR OF THE UPDATE EXPRESSION FOR DYNAMODB + +""" + +# Standard imports +import logging +from os import environ + +# Layer imports +from pieriandx_pipeline_tools.utils.pieriandx_helpers import get_pieriandx_client +from pieriandx_pipeline_tools.utils.secretsmanager_helpers import set_pieriandx_env_vars + +# Set logger +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +JOB_STATUS_BOOL = { + "waiting": None, + "ready": None, + "running": None, + "complete": True, + "failed": False, + "canceled": False +} + + +REPORT_STATUS_BOOL = { + "waiting": None, + "ready": None, + "running": None, + "report_generation_complete": True, + "complete": True, + "failed": False, + "canceled": False +} + + +def handler(event, context): + """ + Get informatics job status + Args: + event: + context: + + Returns: + + """ + + # Get event values + case_id = event.get("case_id", None) + job_id = event.get("informaticsjob_id", None) + report_id = event.get("report_id", None) + current_job_status = event.get("current_job_status", None) + current_report_status = event.get("current_report_status", None) + + # Initialise job status + job_status = None + + # Cannot query the job id directly, instead query the case id and get the job id from there + set_pieriandx_env_vars() + + # Set pyriandx client + pyriandx_client = get_pieriandx_client( + email=environ['PIERIANDX_USER_EMAIL'], + token=environ['PIERIANDX_USER_AUTH_TOKEN'], + instiution=environ['PIERIANDX_INSTITUTION'], + base_url=environ['PIERIANDX_BASE_URL'], + ) + + case_data = pyriandx_client._get_api( + endpoint=f"/case/{case_id}", + ) + + if report_id is None or report_id == -1: + # Get the informatics job object + try: + informaticsjob_obj = next( + filter( + lambda informaticsjob_iter: int(informaticsjob_iter.get("id")) == int(job_id), + case_data.get("informaticsJobs") + ) + ) + except StopIteration: + logger.error(f"Failed to get informatics job {job_id}") + return { + "status": "failed", + "message": f"Failed to get informatics job {job_id} from the case id {case_id}" + } + + # Get job status + job_status = informaticsjob_obj.get("status") + + # Job has either failed or is incomplete - return as is + if ( + ( + # Job not yet complete + not JOB_STATUS_BOOL[job_status] + ) or + ( + # Reports empty + case_data.get("reports") is None + ) or + ( + # Reports length is empty + len(case_data.get("reports")) == 0 + ) + ): + # Set the expression attribute values dict + expression_attribute_values_dict = { + ":job_status": { + "S": job_status + } + } + update_expression_str = "SET job_status = :job_status" + + if JOB_STATUS_BOOL[job_status] is not None: + expression_attribute_values_dict[":job_status_bool"] = { + "BOOL": JOB_STATUS_BOOL[job_status] + } + update_expression_str = f"{update_expression_str}, job_status_bool = :job_status_bool" + + if JOB_STATUS_BOOL[job_status] is False: + expression_attribute_values_dict[":workflow_status"] = { + "S": "FAILED" + } + update_expression_str = f"{update_expression_str}, workflow_status = :workflow_status" + + # Return the job status + return { + "job_status": job_status, + "job_status_bool": JOB_STATUS_BOOL[job_status], + "report_id": None, + "report_status": None, + "report_status_bool": None, + "job_status_changed": False if job_status == current_job_status else True, + "expression_attribute_values_dict": expression_attribute_values_dict, + "update_expression_str": update_expression_str + } + + # Job is complete and reports not empty, check reports + reportjob_obj = case_data.get("reports")[0] + + else: + # Report id is not None, get the report object + try: + reportjob_obj = next( + filter( + lambda reportjob_iter: int(reportjob_iter.get("id")) == int(report_id), + case_data.get("reports") + ) + ) + except StopIteration: + logger.error(f"Failed to get report id {report_id}") + return { + "status": "failed", + "message": f"Failed to get report id {report_id} from the case id {case_id}" + } + + # Reinitialise job status + job_status = "complete" if job_status is None else job_status + + # Get report status + report_status = reportjob_obj.get("status") + + # Return the job status with the report status + # expression_attribute_values_dict + # update_expression_str + + # Set the expression attribute values dict + expression_attribute_values_dict = { + ":job_status": { + "S": job_status + }, + ":report_id": { + "S": reportjob_obj.get("id") + }, + ":report_status": { + "S": report_status + }, + } + update_expression_str = "SET job_status = :job_status, report_id = :report_id, report_status = :report_status" + + # Add the bool values if they are not None + if JOB_STATUS_BOOL[job_status] is not None: + expression_attribute_values_dict[":job_status_bool"] = { + "BOOL": JOB_STATUS_BOOL[job_status] + } + update_expression_str = f"{update_expression_str}, job_status_bool = :job_status_bool" + + if REPORT_STATUS_BOOL[report_status] is not None: + expression_attribute_values_dict[":report_status_bool"] = { + "BOOL": REPORT_STATUS_BOOL[report_status] + } + update_expression_str = f"{update_expression_str}, report_status_bool = :report_status_bool" + + # Add the workflow status + # If one of the job status or report status are false, then the workflow status is failed + if JOB_STATUS_BOOL[job_status] is False or REPORT_STATUS_BOOL[report_status] is False: + expression_attribute_values_dict[":workflow_status"] = { + "S": "FAILED" + } + update_expression_str = f"{update_expression_str}, workflow_status = :workflow_status" + # If both the job status and report status are true, then the workflow status is complete + elif JOB_STATUS_BOOL[job_status] is True and REPORT_STATUS_BOOL[report_status] is True: + expression_attribute_values_dict[":workflow_status"] = { + "S": "COMPLETE" + } + update_expression_str = f"{update_expression_str}, workflow_status = :workflow_status" + + return { + "job_status": job_status, + "job_status_bool": JOB_STATUS_BOOL[job_status], + "report_id": reportjob_obj.get("id"), + "report_status": report_status, + "report_status_bool": REPORT_STATUS_BOOL[report_status], + "job_status_changed": False if report_status == current_report_status else True, + "expression_attribute_values_dict": expression_attribute_values_dict, + "update_expression_str": update_expression_str + } + + + +# if __name__ == "__main__": +# import json +# from os import environ +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['PIERIANDX_BASE_URL'] = "https://app.uat.pieriandx.com/cgw-api/v2.0.0" +# environ['PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME'] = "collectPierianDxAccessToken" +# environ['PIERIANDX_INSTITUTION'] = "melbournetest" +# environ['PIERIANDX_USER_EMAIL'] = "services@umccr.org" +# print( +# json.dumps( +# handler( +# { +# "current_report_status": "", +# "informaticsjob_id": 45813, +# "report_id": -1, +# "case_id": 103511, +# "current_job_status": "waiting" +# }, +# None +# ), +# indent=2 +# ) +# ) +# +# # { +# # "job_status": "complete", +# # "job_status_bool": true, +# # "report_id": "37981", +# # "report_status": "complete", +# # "report_status_bool": true, +# # "job_status_changed": false, +# # "expression_attribute_values_dict": { +# # ":job_status": { +# # "S": "complete" +# # }, +# # ":report_status": { +# # "S": "complete" +# # }, +# # ":job_status_bool": { +# # "BOOL": true +# # }, +# # ":report_status_bool": { +# # "BOOL": true +# # }, +# # ":workflow_status": { +# # "S": "COMPLETE" +# # } +# # }, +# # "update_expression_str": "SET job_status = :job_status, report_status = :report_status, job_status_bool = :job_status_bool, report_status_bool = :report_status_bool, workflow_status = :workflow_status" +# # } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/upload_pieriandx_sample_data_to_s3_py/upload_pieriandx_sample_data_to_s3.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/upload_pieriandx_sample_data_to_s3_py/upload_pieriandx_sample_data_to_s3.py new file mode 100644 index 000000000..23d6f945e --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/lambdas/upload_pieriandx_sample_data_to_s3_py/upload_pieriandx_sample_data_to_s3.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 + +""" + +Upload a file to pieriandx sample data s3 bucket + +Given an icav2 uri, and a destination uri, download and upload the file into the destination uri + +If needs_decompression is set to true, the downloaded file will be decompressed before uploading + +Environment variables required are: +PIERIANDX_S3_ACCESS_CREDENTIALS_SECRET_ID -> The secret id for the s3 access credentials +ICAV2_ACCESS_TOKEN_SECRET_ID -> The secret id for the icav2 access token + +Input will look like this + +{ + "src_uri": "icav2://project-id/path/to/sample-microsat_output.txt", + "dest_uri": "s3://pieriandx/melbourne/20201203_A00123_0001_BHJGJFDS__caseaccessionnumber__20240411235959/L2301368.microsat_output.json", + "needs_decompression": false, + "contents": null +} + +""" + +from tempfile import TemporaryDirectory +from pathlib import Path +from urllib.parse import urlparse +from wrapica.project_data import read_icav2_file_contents, convert_uri_to_project_data_obj + +from pieriandx_pipeline_tools.utils.s3_helpers import set_s3_access_cred_env_vars, upload_file +from pieriandx_pipeline_tools.utils.secretsmanager_helpers import set_icav2_env_vars +from pieriandx_pipeline_tools.utils.compression_helpers import decompress_file + + +def handler(event, context): + """ + Upload pieriandx sample data to s3 bucket + Args: + event: + context: + + Returns: + + """ + + # Set env vars + set_icav2_env_vars() + set_s3_access_cred_env_vars() + + # Get uris + needs_decompression = event.get("needs_decompression", False) + dest_uri = event.get("dest_uri") + dest_bucket = urlparse(dest_uri).netloc + dest_key = urlparse(dest_uri).path + + if event.get("src_uri", None) is not None: + icav2_data_obj = convert_uri_to_project_data_obj(event.get("src_uri")) + + with TemporaryDirectory() as temp_dir: + # Set output path + output_path = Path(temp_dir) / icav2_data_obj.data.details.name + + # Read icav2 file contents + read_icav2_file_contents( + project_id=icav2_data_obj.project_id, + data_id=icav2_data_obj.data.id, + output_path=output_path + ) + + if needs_decompression: + decompress_file(output_path, output_path.parent / output_path.name.replace(".gz", "")) + output_path = output_path.parent / output_path.name.replace(".gz", "") + + if output_path.name.endswith("MetricsOutput.tsv"): + with open(output_path, "r") as f: + contents = f.read() + contents = contents.replace('[Run QC Metrics]', '[Run Metrics]') + with open(output_path, "w") as f: + f.write(contents) + + # Upload to s3 + upload_file(dest_bucket, dest_key, output_path) + else: + contents = event.get("contents") + + with TemporaryDirectory() as temp_dir: + output_path = Path(temp_dir) / Path(dest_key).name + output_path.write_text(contents) + + upload_file(dest_bucket, dest_key, output_path) + + +# if __name__ == "__main__": +# from os import environ +# environ["AWS_PROFILE"] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ["ICAV2_ACCESS_TOKEN_SECRET_ID"] = "ICAv2JWTKey-umccr-prod-service-dev" +# environ['PIERIANDX_S3_ACCESS_CREDENTIALS_SECRET_ID'] = "PierianDx/S3Credentials" +# +# handler( +# { +# "needs_decompression": False, +# "src_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_MetricsOutput.tsv", +# "contents": None, +# "dest_uri": "s3://pdx-cgwxfer-test/melbournetest/231116_A01052_0172_BHVLM5DSX7__SBJ04407__L2400161__V2__abcd1235__abcd1234/Data/Intensities/BaseCalls/L2400161_MetricsOutput.tsv" +# }, +# None +# ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/pyproject.toml b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/pyproject.toml new file mode 100644 index 000000000..1a201ac47 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "pieriandx_pipeline_tools" +version = "0.0.1" +description = "PierianDx Pipeline Lambda Layers" +license = "GPL-3.0-or-later" +authors = [ + "Alexis Lucattini" +] +homepage = "https://github.com/umccr/orcabus" +repository = "https://github.com/umccr/orcabus" + +[tool.poetry.dependencies] +python = "^3.12, <3.13" +boto3 = "^1.28" +botocore = "^1.31" +aws_requests_auth = "^0.4.3" +v2_samplesheet_maker = "^4.2.4" +wrapica = ">=2.27.1.post20240830140737" +# Git dependencies +pyriandx = { git = "https://github.com/umccr/pyriandx.git", branch = "0.4.0" } +pandas = "^2.2.2" +urllib3 = "^1.26" + +[tool.poetry.group.dev] +optional = true + +[tool.poetry.group.dev.dependencies] +pyarrow = "^15.0.0" # Pandas throws a warning if this is not installed +pytest = "^7.0.0" # For testing only +# For typehinting only, not required at runtime +mypy-boto3-ssm = "^1.34" +mypy-boto3-s3 = "^1.34" +mypy-boto3-secretsmanager = "^1.34" +mypy-boto3-stepfunctions = "^1.34" +mypy-boto3-lambda = "^1.34" diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/__init__.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/__init__.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/__init__.py new file mode 100644 index 000000000..ba4263622 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/__init__.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +from copy import deepcopy +from typing import Dict + +from pydantic import BaseModel + + +class BaseClass: + _model = None + + def __init__(self, **kwargs): + self._raw_kwargs_dict = deepcopy(kwargs) + self.section_dict = {} + kwargs_dict = deepcopy(kwargs) + for key in self._model.model_fields.keys(): + if key in kwargs_dict.keys(): + setattr(self, key, kwargs.pop(key, None)) + + if not hasattr(self, key) or getattr(self, key) is None: + # Set default attribute + setattr(self, key, self._model.model_fields[key].default) + + self.validate_model() + + self.coerce_values() + + self.build_section_dict() + + def validate_model(self): + """ + Validate inputs against pydantic model of class + :return: + """ + pass #self._model.model_validate(self) + + def build_section_dict(self): + # Collect original objects + self.section_dict = self._model(**self.get_dict_object()).to_dict() + self.section_dict = self.filter_dict(self.section_dict) + + def get_dict_object(self): + def get_dict_object_recursively(dict_object): + if isinstance(dict_object, BaseClass): + return dict_object.get_dict_object() + # if isinstance(dict_object, BaseModel): + # pass + elif isinstance(dict_object, dict): + return { + key: get_dict_object_recursively(value) + for key, value in dict_object.items() + } + elif isinstance(dict_object, list): + return [ + get_dict_object_recursively(value) + for value in dict_object + ] + else: + return dict_object + + return { + kv[0]: get_dict_object_recursively(kv[1]) + for kv in filter( + lambda kv_iter: not kv_iter[0] in ["section_dict", "_raw_kwargs_dict"], + self.__dict__.items() + ) + } + + def filter_dict(self, initial_dict) -> Dict: + """ + Filter out any values that are None + :param initial_dict: + :return: + """ + return dict( + filter( + lambda kv: kv[1] is not None, + initial_dict.items() + ) + ) + + def coerce_values(self): + # Coerce with model dump + coerced_dict = self._model(**self.get_dict_object()).model_dump() + + for key, value in coerced_dict.items(): + self.__setattr__(key, value) + + def to_dict(self) -> Dict: + return self.section_dict diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/case.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/case.py new file mode 100644 index 000000000..8524cf62c --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/case.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +from . import BaseClass + + +class CaseCreation(BaseClass): + from ..pieriandx_models.case_creation import CaseCreation + _model = CaseCreation + + +class IdentifiedCaseCreation(BaseClass): + from ..pieriandx_models.case_creation import IdentifiedCaseCreation + _model = IdentifiedCaseCreation + + +class DeIdentifiedCaseCreation(BaseClass): + from ..pieriandx_models.case_creation import DeIdentifiedCaseCreation + _model = DeIdentifiedCaseCreation diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/dag.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/dag.py new file mode 100644 index 000000000..84c08a3b6 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/dag.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class Dag(BaseClass): + from ..pieriandx_models.dag import Dag + _model = Dag + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/data_file.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/data_file.py new file mode 100644 index 000000000..3abb95c13 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/data_file.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +from enum import Enum +from typing import Dict, Optional + + +class DataType(Enum): + MICROSAT_OUTPUT = "microsatOutputUri" + TMB_METRICS = "tmbMetricsUri" + CNV = "cnvVcfUri" + HARD_FILTERED = "hardFilteredVcfUri" + FUSIONS = "fusionsUri" + METRICS_OUTPUT = "metricsOutputUri" + + +class DataNameSuffixByDataType(Enum): + MICROSAT_OUTPUT = ".microsat_output.json" + TMB_METRICS = ".tmb.metrics.csv" + CNV = ".cnv.vcf" + HARD_FILTERED = ".hard-filtered.vcf" + FUSIONS = "_Fusions.csv" + METRICS_OUTPUT = "_MetricsOutput.tsv" + + +PATH_EXTENSION = "Data/Intensities/BaseCalls" + + +class DataFile: + + def __init__( + self, + sequencerrun_path_root, + file_type: DataType, + sample_id: str, + src_uri: Optional[str] = None, + contents: Optional[str] = None, + ): + # Initialise the class variables + self.sequencerrun_path_root = sequencerrun_path_root + self.file_type = file_type + self.sample_id = sample_id + self.src_uri = src_uri + self.contents = contents + + # Make sure that the src_uri or contents are provided + if self.src_uri is None and self.contents is None: + raise ValueError("Either src_uri or contents must be provided") + + # Determine compression status by src uri file extension + if self.src_uri.endswith(".gz"): + self.needs_decompression = True + else: + self.needs_decompression = False + + # Determine the destination uri + self.dest_uri = ( + self.sequencerrun_path_root.rstrip("/") + "/" + PATH_EXTENSION + "/" + + self.sample_id + DataNameSuffixByDataType[self.file_type.name].value + ) + + def to_dict(self) -> Dict: + return { + "src_uri": self.src_uri, + "dest_uri": self.dest_uri, + "needs_decompression": self.needs_decompression, + "contents": self.contents + } + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/disease.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/disease.py new file mode 100644 index 000000000..17496f9bf --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/disease.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class Disease(BaseClass): + from ..pieriandx_models.disease import Disease + _model = Disease + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/informaticsjob.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/informaticsjob.py new file mode 100644 index 000000000..5ac24bb6a --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/informaticsjob.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class InformaticsjobCreation(BaseClass): + from ..pieriandx_models.informaticsjob_creation import InformaticsJobCreation + _model = InformaticsJobCreation diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_facility.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_facility.py new file mode 100644 index 000000000..58cabfedc --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_facility.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class MedicalFacility(BaseClass): + from ..pieriandx_models.medical_facility import MedicalFacility + _model = MedicalFacility + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_record_number.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_record_number.py new file mode 100644 index 000000000..bf58a9dbb --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/medical_record_number.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class MedicalRecordNumber(BaseClass): + from ..pieriandx_models.medical_record_number import MedicalRecordNumber + _model = MedicalRecordNumber + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/physician.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/physician.py new file mode 100644 index 000000000..810ca75b8 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/physician.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class Physician(BaseClass): + from ..pieriandx_models.physician import Physician + _model = Physician + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/sequencerrun.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/sequencerrun.py new file mode 100644 index 000000000..ab766f64b --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/sequencerrun.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class SequencerrunCreation(BaseClass): + from ..pieriandx_models.sequencerrun_creation import SequencerrunCreation + _model = SequencerrunCreation diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen.py new file mode 100644 index 000000000..849c8bf41 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class Specimen(BaseClass): + from ..pieriandx_models.specimen import Specimen + _model = Specimen + + +class IdentifiedSpecimen(Specimen): + from ..pieriandx_models.specimen import IdentifiedSpecimen + _model = IdentifiedSpecimen + + +class DeIdentifiedSpecimen(Specimen): + from ..pieriandx_models.specimen import DeIdentifiedSpecimen + _model = DeIdentifiedSpecimen + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen_sequencer_info.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen_sequencer_info.py new file mode 100644 index 000000000..27dcbac74 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_classes/specimen_sequencer_info.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +from . import BaseClass + + +class SpecimenSequencerInfo(BaseClass): + from ..pieriandx_models.specimen_sequencer_info import SpecimenSequencerInfo + _model = SpecimenSequencerInfo diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/__init__.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/ethnicity.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/ethnicity.py new file mode 100644 index 000000000..b02bd3bdd --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/ethnicity.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +from enum import Enum + + +class Ethnicity(Enum): + HISPANIC_OR_LATINO = "hispanic_or_latino" + NOT_HISPANIC_OR_LATINO = "not_hispanic_or_latino" + NOT_REPORTED = "not_reported" + UNKNOWN = "unknown" diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/gender.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/gender.py new file mode 100644 index 000000000..233e50505 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/gender.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +from enum import Enum + + +class Gender(Enum): + UNKNOWN = "unknown" + MALE = "male" + FEMALE = "female" + UNSPECIFIED = "unspecified" + OTHER = "other" + AMBIGUOUS = "ambiguous" + NOT_APPLICABLE = "not_applicable" diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/race.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/race.py new file mode 100644 index 000000000..d6ef3d5ff --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/race.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + + +from enum import Enum + + +class Race(Enum): + AMERICAN_INDIAN_OR_ALASKA_NATIVE = "american_indian_or_alaska_native" + ASIAN = "asian" + BLACK_OR_AFRICAN_AMERICAN = "black_or_african_american" + NATIVE_HAWAIIAN_OR_OTHER_PACIFIC_ISLANDER = "native_hawaiian_or_other_pacific_islander" + NOT_REPORTED = "not_reported" + UNKNOWN = "unknown" + WHITE = "white" diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sample_type.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sample_type.py new file mode 100644 index 000000000..519427b2f --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sample_type.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 + +from enum import Enum + + +class SampleType(Enum): + PATIENTCARE = 'patientcare' + CLINICAL_TRIAL = 'clinical_trial' + VALIDATION = 'validation' + PROFICIENCY_TESTING = 'proficiency_testing' diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sequencing_type.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sequencing_type.py new file mode 100644 index 000000000..fd98ff11a --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/sequencing_type.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +from enum import Enum + + +class SequencingType(Enum): + PAIRED_END = "pairedEnd" + SINGLE_END = "singleEnd" diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/specimen_type.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/specimen_type.py new file mode 100644 index 000000000..8aa6dbe4c --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_enums/specimen_type.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +from pydantic import BaseModel, ConfigDict +from typing import Optional + +from pieriandx_pipeline_tools.pieriandx_lookup.get_specimen_label import get_specimen_label_from_specimen_code + + +class SpecimenType(BaseModel): + code: int + label: Optional[str] = None + + model_config = ConfigDict(from_attributes=True) + + def to_dict(self): + return dict( + filter( + lambda dict_object: dict_object[1] is not None, + { + "code": str(self.code), + "label": + self.label if self.label is not None + else get_specimen_label_from_specimen_code(int(self.code)) + }.items() + ) + ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/__init__.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_disease_label.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_disease_label.py new file mode 100644 index 000000000..fc6dbf73b --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_disease_label.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +""" +Given a disease code, get the disease label +""" + +# Imports +from pathlib import Path +import pandas as pd +from tempfile import NamedTemporaryFile + +from ..utils.compression_helpers import decompress_file + +# Compressed version of +# https://velserapm.atlassian.net/wiki/download/attachments/86704490/SNOMED_CT%20Disease_trees.xlsx?version=1&modificationDate=1561395438000&api=v2 +SNOMED_CT_DISEASE_TREE_FILE = Path(__file__).parent / "snomed_ct_disease_tree.json.gz" + + +def get_disease_tree() -> pd.DataFrame: + """ + Returns a dataframe with the following columns + * Code + * CodeSystem + * Label + :return: + """ + # Decompress the disease tree file into a temp file + decompressed_disease_tree_file = NamedTemporaryFile(suffix=".json") + decompress_file(SNOMED_CT_DISEASE_TREE_FILE, Path(decompressed_disease_tree_file.name)) + + return pd.read_json(decompressed_disease_tree_file.name) + + +def get_disease_label_from_disease_code(disease_code: int) -> str: + """ + Given the disease code, get the disease label + :param disease_code: + :return: + """ + + # Get the disease tree + disease_tree_df = get_disease_tree() + + # Query disease code + query_df = disease_tree_df.query( + f"Code=={disease_code}" + ) + + # Assert that the query df is of length 1 + assert query_df.shape[0] == 1, f"Failed to get disease code {disease_code}" + + # Return the label + return query_df['Label'].item() diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_specimen_label.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_specimen_label.py new file mode 100644 index 000000000..5a2f656b5 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/get_specimen_label.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +""" +Given a specimen code, get the specimen label +""" + +#!/usr/bin/env python3 + +""" +Given a disease code, get the disease label +""" + +# Imports +from pathlib import Path +import pandas as pd +from tempfile import NamedTemporaryFile +from ..utils.compression_helpers import decompress_file + +# Compressed version of +# https://velserapm.atlassian.net/wiki/download/attachments/86704490/SnomedCT-Term_For_SpecimenType.xls?version=1&modificationDate=1561395451000&api=v2 +SNOMED_CT_SPECIMEN_TYPE_FILE = Path(__file__).parent / "snomed_ct_specimen_type.json.gz" + + +def get_specimen_df() -> pd.DataFrame: + """ + Returns a dataframe with the following columns + * Code + * CodeLabel + * CodeSystem + :return: + """ + # Decompress the specimen file into a temp file + decompressed_specimen_df_file = NamedTemporaryFile(suffix=".json") + decompress_file(SNOMED_CT_SPECIMEN_TYPE_FILE, Path(decompressed_specimen_df_file.name)) + + return pd.read_json(decompressed_specimen_df_file.name) + + +def get_specimen_label_from_specimen_code(specimen_code: int) -> str: + """ + Given the specimen code, get the specimen label + :param specimen_code: + :return: + """ + + # Get the disease tree + specimen_tree = get_specimen_df() + + # Query disease code + query_df = specimen_tree.query( + f"Code=={specimen_code}" + ) + + # Assert that the query df is of length 1 + assert query_df.shape[0] == 1, f"Failed to get specimen code {specimen_code}" + + # Return the label + return query_df['CodeLabel'].item() diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_disease_tree.json.gz b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_disease_tree.json.gz new file mode 100755 index 000000000..4e329f792 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_disease_tree.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:612df3709957814cd15577a2112653c21ab7e3f1dca95ad055c60a6041250966 +size 1237630 diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_specimen_type.json.gz b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_specimen_type.json.gz new file mode 100755 index 000000000..136132538 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_lookup/snomed_ct_specimen_type.json.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cee28e63d0cf996a192d152f9ca1aa79bd3e4a6fcd4751f8e1f3da712903af39 +size 27435 diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/__init__.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/case_creation.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/case_creation.py new file mode 100644 index 000000000..682eac509 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/case_creation.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +from typing import Optional, Dict +from pydantic import BaseModel, ConfigDict +# Local imported attributes +from .dag import Dag +from .disease import Disease + +from pieriandx_pipeline_tools.pieriandx_enums.sample_type import SampleType +from .specimen import Specimen +from .physician import Physician +from .specimen import IdentifiedSpecimen, DeIdentifiedSpecimen + + +class CaseCreation(BaseModel): + dag: Dag + disease: Disease + is_identified: bool + indication: Optional[str] = None + panel_name: str + sample_type: SampleType + specimen: Specimen + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self) -> Dict: + # Initialise case dict + case_dict = { + "identified": self.is_identified, + "indication": self.indication, + "panelName": self.panel_name, + "sampleType": self.sample_type.value, + "specimens": [self.specimen.to_dict()], + } + + # Update dag + case_dict.update( + self.dag.to_dict() + ) + + # Update disease + case_dict.update( + { + "disease": self.disease.to_dict() + } + ) + + return case_dict + + +class IdentifiedCaseCreation(CaseCreation): + requesting_physician: Physician + specimen: IdentifiedSpecimen + is_identified: bool = True + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self) -> Dict: + # Get the dictionary from the parent class + initial_dict = super().to_dict() + + # Update the dictionary with the physician and specimen + initial_dict.update( + { + "physicians": [self.requesting_physician.to_dict()], + } + ) + + return initial_dict + + +class DeIdentifiedCaseCreation(CaseCreation): + specimen: DeIdentifiedSpecimen + is_identified: bool = False + + # Model configuration + model_config = ConfigDict(from_attributes=True) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/dag.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/dag.py new file mode 100644 index 000000000..e16f15891 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/dag.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +from typing import Dict +from pydantic import BaseModel, ConfigDict + + +class Dag(BaseModel): + # Attributes + name: str + description: str + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self) -> Dict: + return { + "dagDescription": self.description, + "dagName": self.name, + } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/disease.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/disease.py new file mode 100644 index 000000000..8394d6936 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/disease.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +from typing import Optional +from pydantic import BaseModel, ConfigDict + + + +class Disease(BaseModel): + # Attributes + code: int + label: Optional[str] = None + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self): + from ..pieriandx_lookup.get_disease_label import get_disease_label_from_disease_code + return dict( + filter( + lambda dict_object: dict_object[1] is not None, + { + "code": str(self.code), + "label": + self.label if self.label is not None + else get_disease_label_from_disease_code(int(self.code)) + }.items() + ) + ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/informaticsjob_creation.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/informaticsjob_creation.py new file mode 100644 index 000000000..76eaab728 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/informaticsjob_creation.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 + +from pydantic import BaseModel, ConfigDict +from typing import Dict +from .specimen_sequencer_info import SpecimenSequencerInfo + + +class InformaticsJobCreation(BaseModel): + # Local imported attributes + + case_accession_number: str + specimen_sequencer_run_info: SpecimenSequencerInfo + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self) -> Dict: + # Initialise case dict + return { + "input": [ + { + "accessionNumber": self.case_accession_number, + "sequencerRunInfos": [ + self.specimen_sequencer_run_info.to_informaticsjob_dict() + ] + } + ] + } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_facility.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_facility.py new file mode 100644 index 000000000..76213d93c --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_facility.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +from typing import Optional +from pydantic import BaseModel + + +class MedicalFacility(BaseModel): + facility: Optional[str] + hospital_number: str + + def to_dict(self): + return dict( + filter( + lambda dict_object: dict_object[1] is not None, + { + "facility": self.facility, + "hospitalNumber": self.hospital_number + }.items() + ) + ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_record_number.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_record_number.py new file mode 100644 index 000000000..4331e98b9 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/medical_record_number.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from pydantic import BaseModel +from .medical_facility import MedicalFacility + + +class MedicalRecordNumber(BaseModel): + mrn: str + medical_facility: MedicalFacility + + def to_dict(self): + return { + "mrn": self.mrn, + "medicalFacility": self.medical_facility.to_dict() + } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/physician.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/physician.py new file mode 100644 index 000000000..ca461453c --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/physician.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +from typing import Dict +from pydantic import BaseModel, ConfigDict + + +class Physician(BaseModel): + # Attributes + first_name: str + last_name: str + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self) -> Dict: + return { + "firstName": self.first_name, + "lastName": self.last_name + } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/sequencerrun_creation.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/sequencerrun_creation.py new file mode 100644 index 000000000..e16811edb --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/sequencerrun_creation.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +from pydantic import BaseModel, ConfigDict +from .specimen_sequencer_info import SpecimenSequencerInfo +from pieriandx_pipeline_tools.pieriandx_enums.sequencing_type import SequencingType + + +class SequencerrunCreation(BaseModel): + + # Sequencer run information + run_id: str + specimen_sequence_info: SpecimenSequencerInfo + sequencing_type: SequencingType + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self): + return { + "runId": self.run_id, + "specimens": [self.specimen_sequence_info.to_dict()], + "type": self.sequencing_type.value + } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen.py new file mode 100644 index 000000000..1956aeaf5 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +from typing import Optional, Dict + +from pydantic import BaseModel, ConfigDict +from datetime import datetime, timezone +# Local imported attributes +from pieriandx_pipeline_tools.pieriandx_enums.ethnicity import Ethnicity +from pieriandx_pipeline_tools.pieriandx_enums.race import Race +from pieriandx_pipeline_tools.pieriandx_enums.gender import Gender +from pieriandx_pipeline_tools.pieriandx_enums.specimen_type import SpecimenType +from .medical_record_number import MedicalRecordNumber + + +class Specimen(BaseModel): + case_accession_number: str + date_accessioned: datetime + date_received: datetime + date_collected: datetime + ethnicity: Optional[Ethnicity] = None + external_specimen_id: str + specimen_label: str + race: Optional[Race] = None + gender: Optional[Gender] = None + hl_7_specimen_id: Optional[str] = None + specimen_type: SpecimenType + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self) -> Dict: + return dict( + filter( + lambda dict_object: dict_object[1] is not None, + { + "accessionNumber": self.case_accession_number, + "dateAccessioned": self.date_accessioned.astimezone(timezone.utc).isoformat(sep="T", timespec="seconds").replace("+00:00", "Z"), + "dateReceived": self.date_received.astimezone(timezone.utc).isoformat(sep="T", timespec="seconds").replace("+00:00", "Z"), + "datecollected": self.date_collected.astimezone(timezone.utc).isoformat(sep="T", timespec="seconds").replace("+00:00", "Z"), + "ethnicity": self.ethnicity.value if self.ethnicity is not None else None, + "externalSpecimenId": self.external_specimen_id, + "gender": self.gender.value if self.gender is not None else None, + "hl7SpecimenId": self.hl_7_specimen_id, + "name": self.specimen_label, + "race": self.race.value if self.race is not None else None, + "type": self.specimen_type.to_dict() + }.items() + ) + ) + + +class IdentifiedSpecimen(Specimen): + # Required for Identified specimens + first_name: str + last_name: str + date_of_birth: datetime + medical_record_number: MedicalRecordNumber + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self): + return dict( + filter( + lambda dict_object: dict_object[1] is not None, + { + **super().to_dict(), + "firstName": self.first_name, + "lastName": self.last_name, + "dateOfBirth": self.date_of_birth.strftime("%Y-%m-%d"), + "medicalRecordNumbers": [self.medical_record_number.to_dict()] + }.items() + ) + ) + + +class DeIdentifiedSpecimen(Specimen): + # Required for De-Identified specimens + study_identifier: str + study_subject_identifier: str + + # Model configuration + model_config = ConfigDict(from_attributes=True) + + def to_dict(self): + return dict( + filter( + lambda dict_object: dict_object[1] is not None, + { + **super().to_dict(), + "studyIdentifier": self.study_identifier, + "studySubjectIdentifier": self.study_subject_identifier, + }.items() + ) + ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen_sequencer_info.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen_sequencer_info.py new file mode 100644 index 000000000..51255c9bd --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/pieriandx_models/specimen_sequencer_info.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +from pydantic import BaseModel, ConfigDict +from typing import Dict + +class SpecimenSequencerInfo(BaseModel): + # Model configuration + model_config = ConfigDict(from_attributes=True) + + run_id: str + case_accession_number: str + barcode: str + lane: int + sample_id: str + sample_type: str + + def to_dict(self) -> Dict: + return { + "accessionNumber": self.case_accession_number, + "barcode": self.barcode, + "lane": str(self.lane), + "sampleId": self.sample_id, + "sampleType": self.sample_type + } + + def to_informaticsjob_dict(self) -> Dict: + return { + "runId": self.run_id, + "barcode": self.barcode, + "lane": str(self.lane), + "sampleId": self.sample_id, + "sampleType": self.sample_type + } diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/compression_helpers.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/compression_helpers.py new file mode 100644 index 000000000..8deca2b34 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/compression_helpers.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +""" +Miscellaneous utilities for parsing through compressed strings +""" + +import json +from base64 import b64encode, b64decode +import gzip +from pathlib import Path +from typing import Dict, List, Union + + +def compress_dict(input_dict: Union[Dict, List]) -> str: + """ + Given a json input, compress to a base64 encoded string + + param: input_dict: input dictionary to compress + + Returns: gzipped compressed base64 encoded string + """ + + # Compress + return b64encode( + gzip.compress( + json.dumps(input_dict).encode('utf-8') + ) + ).decode("utf-8") + + +def decompress_dict(input_compressed_b64gz_str: str) -> Union[Dict, List]: + """ + Given a base64 encoded string, decompress and return the original dictionary + Args: + input_compressed_b64gz_str: + + Returns: decompressed dictionary or list + """ + + # Decompress + return json.loads( + gzip.decompress( + b64decode(input_compressed_b64gz_str.encode('utf-8')) + ) + ) + + +def decompress_file(input_file: Path, output_file: Path): + """ + Given a gzipped compressed file as an input, decompress and write to output file + Args: + input_file: + output_file: + + Returns: + + """ + with gzip.open(input_file, 'rb') as f_in: + with open(output_file, 'wb') as f_out: + f_out.write(f_in.read()) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/lambda_helpers.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/lambda_helpers.py new file mode 100644 index 000000000..6310af5b1 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/lambda_helpers.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +""" +Lambda +""" + + +import boto3 +import typing + +if typing.TYPE_CHECKING: + from mypy_boto3_lambda import LambdaClient + from mypy_boto3_lambda.type_defs import InvocationResponseTypeDef + + +def get_aws_lambda_client() -> 'LambdaClient': + return boto3.client('lambda') + + +def run_lambda_function(function_name: str, payload: str) -> str: + client = get_aws_lambda_client() + response: InvocationResponseTypeDef = client.invoke( + FunctionName=function_name, + InvocationType='RequestResponse', + Payload=payload + ) + return response['Payload'].read().decode('utf-8') diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/pieriandx_helpers.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/pieriandx_helpers.py new file mode 100644 index 000000000..3d8c66696 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/pieriandx_helpers.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +""" +Use low-level pyriandx commands +""" + +from pyriandx.client import Client +from os import environ +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def get_pieriandx_client( + email: str = environ.get("PIERIANDX_USER_EMAIL", None), + token: str = environ.get("PIERIANDX_USER_AUTH_TOKEN", None), + instiution: str = environ.get("PIERIANDX_INSTITUTION", None), + base_url: str = environ.get("PIERIANDX_BASE_URL", None) +) -> Client: + """ + Get the pieriandx client, validate environment variables + PIERIANDX_BASE_URL + PIERIANDX_INSTITUTION + PIERIANDX_USER_EMAIL + PIERIANDX_USER_AUTH_TOKEN + :return: + """ + + missing_env_vars = False + + # Check inputs + if email is None: + logger.error(f"Please set the environment variable 'PIERIANDX_USER_EMAIL'") + missing_env_vars = True + if token is None: + logger.error(f"Please set the environment variable 'PIERIANDX_USER_AUTH_TOKEN'") + missing_env_vars = True + if instiution is None: + logger.error(f"Please set the environment variable 'PIERIANDX_INSTITUTION'") + missing_env_vars = True + if base_url is None: + logger.error(f"Please set the environment variable 'PIERIANDX_BASE_URL'") + missing_env_vars = True + + if missing_env_vars: + logger.error("Missing PIERIANDX environment variable") + raise EnvironmentError + + # Return client object + return Client( + email=email, + key=token, + institution=instiution, + base_url=base_url, + key_is_auth_token=True + ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/s3_helpers.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/s3_helpers.py new file mode 100644 index 000000000..dd17f71e0 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/s3_helpers.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +""" +Download file from s3 +""" + +import typing +from pathlib import Path + +import boto3 +from os import environ + +if typing.TYPE_CHECKING: + from mypy_boto3_s3 import S3Client + + +def get_s3_client() -> 'S3Client': + return boto3.client( + 's3', + aws_access_key_id=environ['AWS_ACCESS_KEY_ID'], + aws_secret_access_key=environ['AWS_SECRET_ACCESS_KEY'] + ) + + +def upload_file(bucket: str, key: str, input_file_path: Path) -> None: + s3 = get_s3_client() + s3.upload_file(str(input_file_path), bucket, key.lstrip("/")) + + +def set_s3_access_cred_env_vars(): + from .secretsmanager_helpers import get_pieriandx_s3_access_credentials + access_creds = get_pieriandx_s3_access_credentials() + + for key, value in access_creds.items(): + environ[key] = value diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/samplesheet_helpers.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/samplesheet_helpers.py new file mode 100644 index 000000000..73592d5d1 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/samplesheet_helpers.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +# Standard imports +from typing import Dict +from pathlib import Path +from tempfile import NamedTemporaryFile + +# Custom imports +from wrapica.project_data import ( + read_icav2_file_contents, convert_uri_to_project_data_obj, + ProjectData +) +from v2_samplesheet_maker.functions.v2_samplesheet_reader import v2_samplesheet_reader + + +def read_v2_samplesheet(samplesheet_uri: str) -> Dict: + """ + Read in a v2 samplesheet from the given uri and return as a dictionary + + Args: + samplesheet_uri: Path to the samplesheet s3 or icav2 uri + + Returns: + + """ + + # Initialise tempfile object + temp_file = NamedTemporaryFile(suffix=".csv") + + # Get samplesheet uri as an icav2 projectdata object + icav2_project_data_obj: ProjectData = convert_uri_to_project_data_obj(samplesheet_uri) + + # Write icav2 file contents to temp file + read_icav2_file_contents( + icav2_project_data_obj.project_id, + icav2_project_data_obj.data.id, + output_path=Path(temp_file.name) + ) + + # Read the v2 samplesheet from the temp file + return v2_samplesheet_reader( + Path(temp_file.name) + ) diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/secretsmanager_helpers.py b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/secretsmanager_helpers.py new file mode 100644 index 000000000..d8200ce9d --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/layers/src/pieriandx_pipeline_tools/utils/secretsmanager_helpers.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +import typing +from copy import copy +from typing import Dict +import boto3 +from time import sleep +from os import environ +import json + +if typing.TYPE_CHECKING: + from mypy_boto3_secretsmanager import SecretsManagerClient + from mypy_boto3_secretsmanager.type_defs import GetSecretValueResponseTypeDef + +ICAV2_BASE_URL = "https://ica.illumina.com/ica/rest" + + +def get_secrets_manager_client() -> 'SecretsManagerClient': + return boto3.client('secretsmanager') + + +def get_secret_value_from_aws_secrets_manager(secret_id: str) -> str: + client = get_secrets_manager_client() + + secret_value_obj: GetSecretValueResponseTypeDef = client.get_secret_value(SecretId=secret_id) + + return secret_value_obj['SecretString'] + + +def get_pieriandx_auth_token() -> str: + from .lambda_helpers import run_lambda_function + collection_token_lambda = environ.get("PIERIANDX_COLLECT_AUTH_TOKEN_LAMBDA_NAME") + + auth_token = run_lambda_function(collection_token_lambda, "") + + while auth_token is None or auth_token == 'null' or json.loads(auth_token).get("auth_token") is None: + sleep(5) + auth_token = run_lambda_function(collection_token_lambda, "") + + return json.loads(auth_token).get("auth_token") + + +def get_pieriandx_s3_access_credentials() -> Dict: + secret_id = environ.get("PIERIANDX_S3_ACCESS_CREDENTIALS_SECRET_ID") + + access_credentials = get_secret_value_from_aws_secrets_manager(secret_id) + + access_credentials_dict = json.loads(access_credentials) + + for key in copy(access_credentials_dict).keys(): + access_credentials_dict[key.replace("s3", "aws").upper()] = access_credentials_dict.pop(key) + + return access_credentials_dict + + +def set_pieriandx_env_vars(): + environ["PIERIANDX_USER_AUTH_TOKEN"] = get_pieriandx_auth_token() + + +def set_icav2_env_vars(): + """ + Set the icav2 environment variables + :return: + """ + environ["ICAV2_BASE_URL"] = ICAV2_BASE_URL + environ["ICAV2_ACCESS_TOKEN"] = get_secret_value_from_aws_secrets_manager( + environ["ICAV2_ACCESS_TOKEN_SECRET_ID"] + ) + + diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx.asl.json b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx.asl.json new file mode 100644 index 000000000..428266574 --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx.asl.json @@ -0,0 +1,451 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "move_inputs", + "States": { + "move_inputs": { + "Type": "Pass", + "Parameters": { + "workflow_inputs.$": "$" + }, + "Next": "Get attributes from SSM" + }, + "Get attributes from SSM": { + "Type": "Parallel", + "Next": "generate_pieriandx_objects", + "Branches": [ + { + "StartAt": "Get Available Dag Versions", + "States": { + "Get Available Dag Versions": { + "Type": "Task", + "Parameters": { + "Name": "${__dag_versions_ssm_parameter__}" + }, + "Resource": "arn:aws:states:::aws-sdk:ssm:getParameter", + "Next": "Dag Version set", + "ResultPath": "$.get_dag_versions_from_ssm_parameter_step", + "ResultSelector": { + "dag_versions_uri_map.$": "States.StringToJson($.Parameter.Value)" + } + }, + "Dag Version set": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.workflow_inputs.dagVersion", + "IsPresent": true, + "Comment": "Dag Version Is Set", + "Next": "Get Dag Version Value" + } + ], + "Default": "Get Default Dag Version Value" + }, + "Get Default Dag Version Value": { + "Type": "Pass", + "Next": "Get Dag Dict from SSM Parameter", + "Parameters": { + "dag_version": "${__default_dag_version__}" + }, + "ResultPath": "$.get_dag_version_step" + }, + "Get Dag Version Value": { + "Type": "Pass", + "Next": "Get Dag Dict from SSM Parameter", + "Parameters": { + "dag_version.$": "$.workflow_inputs.dagVersion" + }, + "ResultPath": "$.get_dag_version_step" + }, + "Get Dag Dict from SSM Parameter": { + "Type": "Pass", + "End": true, + "Parameters": { + "dag_version_obj.$": "States.ArrayGetItem($.get_dag_versions_from_ssm_parameter_step.dag_versions_uri_map[?(@.dagName==$.get_dag_version_step.dag_version)], 0)" + }, + "ResultPath": "$.get_dag_version_step" + } + } + }, + { + "StartAt": "Get available panel names", + "States": { + "Get available panel names": { + "Type": "Task", + "Parameters": { + "Name": "${__panel_names_ssm_parameter__}" + }, + "Resource": "arn:aws:states:::aws-sdk:ssm:getParameter", + "Next": "Panel Name Set", + "ResultSelector": { + "panel_names_map.$": "States.StringToJson($.Parameter.Value)" + }, + "ResultPath": "$.get_panel_name_versions_from_ssm_parameter_step" + }, + "Panel Name Set": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.workflowInputs.panelName", + "IsPresent": true, + "Next": "Get Panel Name from inputs", + "Comment": "Panel Name is set" + } + ], + "Default": "Get Default Panel Name" + }, + "Get Default Panel Name": { + "Type": "Pass", + "Next": "Get Panel Name Value from SSM Parameter", + "Parameters": { + "panel_name": "${__default_panel_name__}" + }, + "ResultPath": "$.get_panel_name_step" + }, + "Get Panel Name from inputs": { + "Type": "Pass", + "Next": "Get Panel Name Value from SSM Parameter", + "Parameters": { + "panel_name.$": "$.workflow_inputs.panelName" + }, + "ResultPath": "$.get_panel_name_step" + }, + "Get Panel Name Value from SSM Parameter": { + "Type": "Pass", + "End": true, + "Parameters": { + "panel_name.$": "States.ArrayGetItem($.get_panel_name_versions_from_ssm_parameter_step.panel_names_map[?(@.panelName==$.get_panel_name_step.panel_name)].panelId, 0)" + }, + "ResultPath": "$.get_panel_name_step" + } + } + } + ], + "ResultSelector": { + "dag_version_obj.$": "$[0].get_dag_version_step.dag_version_obj", + "panel_name.$": "$[1].get_panel_name_step.panel_name" + }, + "ResultPath": "$.get_ssm_parameters_step" + }, + "generate_pieriandx_objects": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__generate_pieriandx_objects_lambda_function_arn__}", + "Payload": { + "dag.$": "$.get_ssm_parameters_step.dag_version_obj", + "panel_name.$": "$.get_ssm_parameters_step.panel_name", + "case_metadata.$": "$.workflow_inputs.payload.data.inputs.caseMetadata", + "data_files.$": "$.workflow_inputs.payload.data.inputs.dataFiles", + "samplesheet_uri.$": "$.workflow_inputs.payload.data.inputs.dataFiles.samplesheetUri", + "sequencerrun_s3_path_root": "${__sequencerrun_s3_path_root__}", + "instrument_run_id.$": "$.workflow_inputs.payload.data.inputs.instrumentRunId", + "portal_run_id.$": "$.workflow_inputs.portalRunId" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultPath": "$.generate_pieriandx_objects_step", + "ResultSelector": { + "case_creation_obj.$": "$.Payload.case_creation_obj", + "sequencerrun_creation_obj.$": "$.Payload.sequencerrun_creation_obj", + "informaticsjob_creation_obj.$": "$.Payload.informaticsjob_creation_obj", + "data_files.$": "$.Payload.data_files", + "sequencerrun_s3_path.$": "$.Payload.sequencerrun_s3_path", + "sample_name.$": "$.Payload.sample_name" + }, + "Next": "add_pieriandx_db_entries" + }, + "add_pieriandx_db_entries": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "add_case_accession_number_partition_key", + "States": { + "add_case_accession_number_partition_key": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.workflow_inputs.payload.data.inputs.caseMetadata.caseAccessionNumber", + "id_type": "case_accession_number", + "portal_run_id": { + "S.$": "$.workflow_inputs.portalRunId" + } + } + }, + "End": true + } + } + }, + { + "StartAt": "add_portal_run_id_partition_key", + "States": { + "add_portal_run_id_partition_key": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.workflow_inputs.portalRunId", + "id_type": "portal_run_id", + "state_machine_execution_arn": { + "S.$": "$$.Execution.Id" + }, + "workflow_run_name": { + "S.$": "$.workflow_inputs.workflowRunName" + }, + "linked_libraries": { + "S.$": "States.JsonToString($.workflow_inputs.linkedLibraries)" + }, + "case_accession_number": { + "S.$": "$.workflow_inputs.payload.data.inputs.caseMetadata.caseAccessionNumber" + }, + "event_inputs": { + "S.$": "States.JsonToString($.workflow_inputs.payload.data.inputs)" + }, + "engine_parameters": { + "S.$": "States.JsonToString($.workflow_inputs.payload.data.engineParameters)" + }, + "tags": { + "S.$": "States.JsonToString($.workflow_inputs.payload.data.tags)" + }, + "data_files": { + "S.$": "States.JsonToString($.generate_pieriandx_objects_step.data_files)" + }, + "sample_name": { + "S.$": "$.generate_pieriandx_objects_step.sample_name" + }, + "samplesheet_uri": { + "S.$": "$.workflow_inputs.payload.data.inputs.dataFiles.samplesheetUri" + }, + "sequencerrun_s3_path": { + "S.$": "$.generate_pieriandx_objects_step.sequencerrun_s3_path" + }, + "case_creation_obj": { + "S.$": "States.JsonToString($.generate_pieriandx_objects_step.case_creation_obj)" + }, + "case_id": { + "N": "-1" + }, + "sequencerrun_creation_obj": { + "S.$": "States.JsonToString($.generate_pieriandx_objects_step.sequencerrun_creation_obj)" + }, + "sequencerrun_id": { + "N": "-1" + }, + "informaticsjob_creation_obj": { + "S.$": "States.JsonToString($.generate_pieriandx_objects_step.informaticsjob_creation_obj)" + }, + "informaticsjob_id": { + "N": "-1" + }, + "report_id": { + "N": "-1" + }, + "job_status": { + "S": "" + }, + "report_status": { + "S": "" + } + } + }, + "End": true + } + } + } + ], + "ResultPath": "$.add_pieriandx_db_entries_step", + "Next": "wait_one_second" + }, + "wait_one_second": { + "Type": "Wait", + "Seconds": 1, + "Comment": "Allow the database to sync\n", + "Next": "create_pieriandx_prelaunch_objects" + }, + "create_pieriandx_prelaunch_objects": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "create_case_execution", + "States": { + "create_case_execution": { + "Type": "Task", + "Resource": "arn:aws:states:::states:startExecution.sync:2", + "Parameters": { + "StateMachineArn": "${__create_case_sfn__}", + "Input": { + "portal_run_id.$": "$.workflow_inputs.portalRunId" + } + }, + "ResultPath": "$.create_case_execution_step", + "End": true + } + } + }, + { + "StartAt": "create_sequencerrun_execution", + "States": { + "create_sequencerrun_execution": { + "Type": "Task", + "Resource": "arn:aws:states:::states:startExecution.sync:2", + "Parameters": { + "StateMachineArn": "${__create_sequencerrun_sfn__}", + "Input": { + "portal_run_id.$": "$.workflow_inputs.portalRunId" + } + }, + "ResultPath": "$.create_sequencerrun_execution_step", + "End": true + } + } + } + ], + "ResultPath": "$.create_pieriandx_prelaunch_objects_step", + "Next": "create_informaticsjob_execution" + }, + "create_informaticsjob_execution": { + "Type": "Task", + "Resource": "arn:aws:states:::states:startExecution.sync:2", + "Parameters": { + "StateMachineArn": "${__create_informaticsjob_sfn__}", + "Input": { + "portal_run_id.$": "$.workflow_inputs.portalRunId" + } + }, + "ResultPath": "$.create_informaticsjob_execution_step", + "Next": "Get Portal Run Id" + }, + "Get Portal Run Id": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portalRunId", + "id_type": "portal_run_id" + } + }, + "Next": "Set engine parameters", + "ResultPath": "$.get_portal_run_id_db_step" + }, + "Set engine parameters": { + "Type": "Pass", + "Next": "Update dbs", + "ResultPath": "$.set_engine_parameters_step", + "Parameters": { + "engine_parameters.$": "States.JsonMerge($.workflow_inputs.payload.data.engineParameters, States.StringToJson(States.Format('\\{\"{}\":\"{}\",\"{}\":\"{}\"\\}', 'caseId', $.get_portal_run_id_db_step.Item.case_id.N, 'informaticsJobId', $.get_portal_run_id_db_step.Item.informaticsjob_id.N)), false)" + } + }, + "Update dbs": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Register running informatics job", + "States": { + "Register running informatics job": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.workflow_inputs.portalRunId", + "id_type": "running_jobs", + "case_id": { + "N.$": "States.Format('{}', $.get_portal_run_id_db_step.Item.case_id.N)" + }, + "informaticsjob_id": { + "N.$": "States.Format('{}', $.get_portal_run_id_db_step.Item.informaticsjob_id.N)" + }, + "report_id": { + "N": "-1" + }, + "job_status": { + "S": "waiting" + }, + "report_status": { + "S": "" + }, + "workflow_status": { + "S": "RUNNING" + } + } + }, + "ResultPath": null, + "End": true + } + } + }, + { + "StartAt": "Update engine parameters", + "States": { + "Update engine parameters": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:updateItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portalRunId", + "id_type": "portal_run_id" + }, + "UpdateExpression": "SET engine_parameters = :engine_parameters", + "ExpressionAttributeValues": { + ":engine_parameters": { + "S.$": "States.JsonToString($.set_engine_parameters_step.engine_parameters)" + } + } + }, + "End": true + } + } + } + ], + "ResultPath": null, + "Next": "Initialised PierianDx Job" + }, + "Initialised PierianDx Job": { + "Type": "Task", + "Resource": "arn:aws:states:::events:putEvents", + "Parameters": { + "Entries": [ + { + "DetailType": "${__event_detail_type__}", + "EventBusName": "${__event_bus_name__}", + "Source": "${__event_source__}", + "Detail": { + "portalRunId.$": "$.workflow_inputs.portalRunId", + "timestamp.$": "$$.State.EnteredTime", + "status": "RUNNING", + "workflowName": "${__workflow_name__}", + "workflowVersion": "${__workflow_version__}", + "workflowRunName.$": "$.workflow_inputs.workflowRunName", + "linkedLibraries.$": "$.workflow_inputs.linkedLibraries", + "payload": { + "version": "${__payload_version__}", + "data": { + "inputs.$": "$.workflow_inputs.payload.data.inputs", + "engineParameters.$": "$.set_engine_parameters_step.engine_parameters", + "tags.$": "$.workflow_inputs.payload.data.tags" + } + } + } + } + ] + }, + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_case_creation.asl.json b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_case_creation.asl.json new file mode 100644 index 000000000..df3dd4f9a --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_case_creation.asl.json @@ -0,0 +1,114 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "move_inputs", + "States": { + "move_inputs": { + "Type": "Pass", + "Parameters": { + "workflow_inputs.$": "$" + }, + "Next": "get_case_creation_object" + }, + "get_case_creation_object": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portal_run_id", + "id_type": "portal_run_id" + } + }, + "ResultPath": "$.get_case_creation_object_step", + "ResultSelector": { + "portal_run_id.$": "$.Item.id.S", + "case_creation_obj.$": "States.StringToJson($.Item.case_creation_obj.S)" + }, + "Next": "generate_case" + }, + "generate_case": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "Payload": { + "case_creation_obj.$": "$.get_case_creation_object_step.case_creation_obj" + }, + "FunctionName": "${__generate_case_lambda_function_arn__}" + }, + "TimeoutSeconds": 30, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "Next": "update_database", + "ResultPath": "$.generate_case_step", + "ResultSelector": { + "case_id.$": "$.Payload.id" + } + }, + "update_database": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "add_case_id_partition_key", + "States": { + "add_case_id_partition_key": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.generate_case_step.case_id", + "id_type": "case_id", + "portal_run_id": { + "S.$": "$.workflow_inputs.portal_run_id" + } + } + }, + "End": true + } + } + }, + { + "StartAt": "update_portal_run_id_partition_key_with_case_id", + "States": { + "update_portal_run_id_partition_key_with_case_id": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:updateItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portal_run_id", + "id_type": "portal_run_id" + }, + "UpdateExpression": "SET case_id = :case_id", + "ExpressionAttributeValues": { + ":case_id": { + "N.$": "$.generate_case_step.case_id" + } + } + }, + "End": true + } + } + } + ], + "Next": "wait_one_second" + }, + "wait_one_second": { + "Type": "Wait", + "Seconds": 1, + "End": true, + "Comment": "Wait for database to sync" + } + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_informaticsjob_creation.asl.json b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_informaticsjob_creation.asl.json new file mode 100644 index 000000000..603f47d5b --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_informaticsjob_creation.asl.json @@ -0,0 +1,119 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "move_inputs", + "States": { + "move_inputs": { + "Type": "Pass", + "Parameters": { + "workflow_inputs.$": "$" + }, + "Next": "get_informaticsjob_object" + }, + "get_informaticsjob_object": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portal_run_id", + "id_type": "portal_run_id" + } + }, + "ResultSelector": { + "portal_run_id.$": "$.Item.id.S", + "informaticsjob_creation_obj.$": "States.StringToJson($.Item.informaticsjob_creation_obj.S)", + "case_id.$": "$.Item.case_id.N" + }, + "Next": "generate_informaticsjob", + "ResultPath": "$.get_informaticsjob_object" + }, + "generate_informaticsjob": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "TimeoutSeconds": 30, + "Parameters": { + "FunctionName": "${__generate_informaticsjob_lambda_function_arn__}", + "Payload": { + "informaticsjob_creation_obj.$": "$.get_informaticsjob_object.informaticsjob_creation_obj", + "case_id.$": "$.get_informaticsjob_object.case_id" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "Next": "update_database", + "ResultSelector": { + "informaticsjob_id.$": "$.Payload.jobId" + }, + "ResultPath": "$.generate_informaticsjob_step" + }, + "update_database": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "add_informaticsjob_id_partition_key", + "States": { + "add_informaticsjob_id_partition_key": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.generate_informaticsjob_step.informaticsjob_id", + "id_type": "informaticsjob_id", + "portal_run_id": { + "S.$": "$.workflow_inputs.portal_run_id" + } + } + }, + "End": true + } + } + }, + { + "StartAt": "update_portal_run_id_partition_key_with_informaticsjob_id", + "States": { + "update_portal_run_id_partition_key_with_informaticsjob_id": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:updateItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portal_run_id", + "id_type": "portal_run_id" + }, + "UpdateExpression": "SET informaticsjob_id = :informaticsjob_id, job_status = :job_status", + "ExpressionAttributeValues": { + ":informaticsjob_id": { + "N.$": "$.generate_informaticsjob_step.informaticsjob_id" + }, + ":job_status": { + "S": "waiting" + } + } + }, + "End": true + } + } + } + ], + "ResultPath": "$.update_database", + "Next": "wait_one_second" + }, + "wait_one_second": { + "Type": "Wait", + "Seconds": 1, + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_sequencerrun_creation.asl.json b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_sequencerrun_creation.asl.json new file mode 100644 index 000000000..dd5ea644d --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/launch_pieriandx_sequencerrun_creation.asl.json @@ -0,0 +1,280 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "move_inputs", + "States": { + "move_inputs": { + "Type": "Pass", + "Parameters": { + "workflow_inputs.$": "$" + }, + "Next": "get_sequencerrun_creation_object" + }, + "get_sequencerrun_creation_object": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portal_run_id", + "id_type": "portal_run_id" + } + }, + "ResultPath": "$.get_sequencerrun_creation_object_step", + "ResultSelector": { + "portal_run_id": "$.Item.id.S", + "sequencerrun_creation_obj.$": "States.StringToJson($.Item.sequencerrun_creation_obj.S)", + "data_files.$": "States.StringToJson($.Item.data_files.S)", + "samplesheet_uri.$": "$.Item.samplesheet_uri.S", + "sequencerrun_s3_path.$": "$.Item.sequencerrun_s3_path.S" + }, + "Next": "upload_data" + }, + "upload_data": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "upload_data_files_to_s3", + "States": { + "upload_data_files_to_s3": { + "Type": "Map", + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "upload_pieriandx_sample_data_to_s3", + "States": { + "upload_pieriandx_sample_data_to_s3": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "Payload": { + "src_uri.$": "$.src_uri", + "dest_uri.$": "$.dest_uri", + "needs_decompression.$": "$.needs_decompression", + "contents.$": "$.contents" + }, + "FunctionName": "${__upload_data_to_s3_lambda_function_arn__}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "End": true + } + } + }, + "ItemsPath": "$.get_sequencerrun_creation_object_step.data_files", + "End": true + } + } + }, + { + "StartAt": "generate_samplesheet_str", + "States": { + "generate_samplesheet_str": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__generate_samplesheet_lambda_function_arn__}", + "Payload": { + "samplesheet_uri.$": "$.get_sequencerrun_creation_object_step.samplesheet_uri" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "samplesheet_str.$": "$.Payload.samplesheet_str" + }, + "ResultPath": "$.generate_samplesheet_str_step", + "Next": "upload_samplesheet_str_to_s3" + }, + "upload_samplesheet_str_to_s3": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "OutputPath": "$.Payload", + "Parameters": { + "FunctionName": "${__upload_data_to_s3_lambda_function_arn__}", + "Payload": { + "dest_uri.$": "States.Format('{}/SampleSheet.csv', $.get_sequencerrun_creation_object_step.sequencerrun_s3_path)", + "contents.$": "$.generate_samplesheet_str_step.samplesheet_str" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "End": true + } + } + } + ], + "ResultPath": "$.upload_data_step", + "Next": "generate_sequencerrun_creation_obj" + }, + "generate_sequencerrun_creation_obj": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__generate_sequencerrun_case_lambda_function_arn__}", + "Payload": { + "sequencerrun_creation_obj.$": "$.get_sequencerrun_creation_object_step.sequencerrun_creation_obj" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultPath": "$.generate_sequencerrun_creation_object_step", + "ResultSelector": { + "sequencerrun_id.$": "$.Payload.id" + }, + "Next": "update_database" + }, + "update_database": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "add_sequencer_run_partition_key", + "States": { + "add_sequencer_run_partition_key": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.generate_sequencerrun_creation_object_step.sequencerrun_id", + "id_type": "sequencerrun_id", + "portal_run_id": { + "S.$": "$.workflow_inputs.portal_run_id" + } + } + }, + "End": true + } + } + }, + { + "StartAt": "update_portal_run_id_partition_key_with_sequencerrun_id", + "States": { + "update_portal_run_id_partition_key_with_sequencerrun_id": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:updateItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.workflow_inputs.portal_run_id", + "id_type": "portal_run_id" + }, + "UpdateExpression": "SET sequencerrun_id = :sequencerrun_id", + "ExpressionAttributeValues": { + ":sequencerrun_id": { + "N.$": "$.generate_sequencerrun_creation_object_step.sequencerrun_id" + } + } + }, + "End": true + } + } + } + ], + "Next": "add_vcf_workflow_txt", + "ResultPath": "$.update_database_step" + }, + "add_vcf_workflow_txt": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__upload_data_to_s3_lambda_function_arn__}", + "Payload": { + "contents": "", + "dest_uri.$": "States.Format('{}/VcfWorkflow.txt', $.get_sequencerrun_creation_object_step.sequencerrun_s3_path)" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultPath": "$.add_vcf_workflow_txt_step", + "Next": "add_done_txt" + }, + "add_done_txt": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__upload_data_to_s3_lambda_function_arn__}", + "Payload": { + "contents": "", + "dest_uri.$": "States.Format('{}/done.txt', $.get_sequencerrun_creation_object_step.sequencerrun_s3_path)" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultPath": "$.add_done_txt_step", + "Next": "wait_one_second" + }, + "wait_one_second": { + "Type": "Wait", + "Seconds": 1, + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/monitor_runs.asl.json b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/monitor_runs.asl.json new file mode 100644 index 000000000..1bfb364fd --- /dev/null +++ b/lib/workload/stateless/stacks/pieriandx-pipeline-manager/step_function_templates/monitor_runs.asl.json @@ -0,0 +1,277 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "Get all current running jobs", + "States": { + "Get all current running jobs": { + "Type": "Task", + "Parameters": { + "TableName": "${__table_name__}", + "ExpressionAttributeValues": { + ":id_type": { + "S": "running_jobs" + } + }, + "ExpressionAttributeNames": { + "#id_type": "id_type" + }, + "FilterExpression": "#id_type = :id_type" + }, + "Resource": "arn:aws:states:::aws-sdk:dynamodb:scan", + "ResultPath": "$.get_current_running_jobs_step", + "Next": "Check items not empty" + }, + "Check items not empty": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_current_running_jobs_step.Items", + "IsPresent": true, + "Comment": "Items list exists", + "Next": "Get num of running jobs" + } + ], + "Default": "Pass" + }, + "Get num of running jobs": { + "Type": "Pass", + "Next": "Check items list is more than 0", + "Parameters": { + "num_jobs.$": "States.ArrayLength($.get_current_running_jobs_step.Items)" + }, + "ResultPath": "$.get_num_jobs_step" + }, + "Check items list is more than 0": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_num_jobs_step.num_jobs", + "NumericGreaterThan": 0, + "Next": "Iterate running jobs partition", + "Comment": "At least one job running" + } + ], + "Default": "Pass" + }, + "Iterate running jobs partition": { + "Type": "Map", + "ItemsPath": "$.get_current_running_jobs_step.Items", + "ItemSelector": { + "job_db_item.$": "$$.Map.Item.Value" + }, + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "Get Current Job Status", + "States": { + "Get Current Job Status": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__get_current_job_status_lambda_function_arn__}", + "Payload": { + "current_job_status.$": "$.job_db_item.job_status.S", + "current_report_status.$": "$.job_db_item.report_status.S", + "case_id.$": "States.StringToJson($.job_db_item.case_id.N)", + "informaticsjob_id.$": "States.StringToJson($.job_db_item.informaticsjob_id.N)", + "report_id.$": "States.StringToJson($.job_db_item.report_id.N)" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "job_status.$": "$.Payload.job_status", + "job_status_bool.$": "$.Payload.job_status_bool", + "report_id.$": "$.Payload.report_id", + "report_status.$": "$.Payload.report_status", + "report_status_bool.$": "$.Payload.report_status_bool", + "expression_attribute_values_dict.$": "$.Payload.expression_attribute_values_dict", + "update_expression_str.$": "$.Payload.update_expression_str", + "job_status_changed.$": "$.Payload.job_status_changed" + }, + "ResultPath": "$.get_current_status_step", + "Next": "Job Status Changed" + }, + "Job Status Changed": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_current_status_step.job_status_changed", + "BooleanEquals": true, + "Next": "Update Changes", + "Comment": "The job status has changed" + } + ], + "Default": "No Change" + }, + "Update Changes": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Update portal run id job status", + "States": { + "Update portal run id job status": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:updateItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.job_db_item.id.S", + "id_type": "portal_run_id" + }, + "UpdateExpression.$": "$.get_current_status_step.update_expression_str", + "ExpressionAttributeValues.$": "$.get_current_status_step.expression_attribute_values_dict" + }, + "End": true + } + } + }, + { + "StartAt": "Is Report Status Terminal", + "States": { + "Is Report Status Terminal": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_current_status_step.report_status_bool", + "IsBoolean": true, + "Next": "Delete Row from Running Jobs Partition", + "Comment": "Report Status Terminal" + } + ], + "Default": "Update running jobs partition row" + }, + "Update running jobs partition row": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:updateItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.job_db_item.id.S", + "id_type": "running_jobs" + }, + "UpdateExpression.$": "$.get_current_status_step.update_expression_str", + "ExpressionAttributeValues.$": "$.get_current_status_step.expression_attribute_values_dict" + }, + "End": true + }, + "Delete Row from Running Jobs Partition": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:deleteItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.job_db_item.id.S", + "id_type": "running_jobs" + } + }, + "End": true + } + } + } + ], + "ResultPath": null, + "Next": "Get Portal Run ID Row Partition" + }, + "Get Portal Run ID Row Partition": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.job_db_item.id.S", + "id_type": "portal_run_id" + } + }, + "ResultPath": "$.get_portal_run_db_step", + "Next": "Generate Data Payload" + }, + "Generate Data Payload": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__generate_data_payload_lambda_function_arn__}", + "Payload": { + "inputs.$": "States.StringToJson($.get_portal_run_db_step.Item.event_inputs.S)", + "engine_parameters.$": "States.StringToJson($.get_portal_run_db_step.Item.engine_parameters.S)", + "tags.$": "States.StringToJson($.get_portal_run_db_step.Item.tags.S)", + "report_status.$": "$.get_portal_run_db_step.Item.report_status.S", + "case_id.$": "$.get_portal_run_db_step.Item.case_id.N", + "job_id.$": "$.get_portal_run_db_step.Item.informaticsjob_id.N", + "case_accession_number.$": "$.get_portal_run_db_step.Item.case_accession_number.S", + "report_id.$": "$.get_portal_run_db_step.Item.report_id.N", + "pieriandx_base_url": "https://app.uat.pieriandx.com/cgw-api/v2.0.0", + "sample_name.$": "$.get_portal_run_db_step.Item.sample_name.S" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "Next": "Push event of Informatics Job Change", + "ResultPath": "$.get_data_payload_step", + "ResultSelector": { + "data_payload.$": "$.Payload.data_payload" + } + }, + "Push event of Informatics Job Change": { + "Type": "Task", + "Resource": "arn:aws:states:::events:putEvents", + "Parameters": { + "Entries": [ + { + "DetailType": "${__event_detail_type__}", + "EventBusName": "${__event_bus_name__}", + "Source": "${__event_source__}", + "Detail": { + "portalRunId.$": "$.get_portal_run_db_step.Item.id.S", + "timestamp.$": "$$.State.EnteredTime", + "status.$": "$.get_portal_run_db_step.Item.workflow_status.S", + "workflowName": "${__workflow_name__}", + "workflowVersion": "${__workflow_version__}", + "workflowRunName.$": "$.get_portal_run_db_step.Item.workflow_run_name.S", + "linkedLibraries.$": "States.StringToJson($.get_portal_run_db_step.Item.linkedLibraries.S)", + "payload": { + "version": "${__payload_version__}", + "data.$": "$.get_data_payload_step.data_payload" + } + } + } + ] + }, + "End": true + }, + "No Change": { + "Type": "Pass", + "End": true + } + } + }, + "End": true + }, + "Pass": { + "Type": "Pass", + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/index.ts index e30728d3c..dfbcd7e52 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/index.ts @@ -20,7 +20,7 @@ export class NewSamplesheetEventShowerConstruct extends Construct { prefix: 'clag-new-ss-event-shower', // Tables tablePartition: { - samplesheetByInstrumentRun: 'samplesheet_by_instrument_run', + instrumentRun: 'instrument_run', subject: 'subject', library: 'library', project: 'project', @@ -149,8 +149,8 @@ export class NewSamplesheetEventShowerConstruct extends Construct { __table_name__: props.tableObj.tableName, __subject_table_partition_name__: this.newSamplesheetEventShowerMap.tablePartition.subject, __library_table_partition_name__: this.newSamplesheetEventShowerMap.tablePartition.library, - __samplesheet_table_partition_name__: - this.newSamplesheetEventShowerMap.tablePartition.samplesheetByInstrumentRun, + __instrument_run_table_partition_name__: + this.newSamplesheetEventShowerMap.tablePartition.instrumentRun, __project_table_partition_name__: this.newSamplesheetEventShowerMap.tablePartition.project, // Lambdas diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py index ec8a2859d..37b204e98 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py @@ -3,59 +3,21 @@ """ Generate event data objects -Given an instrument run id, samplesheet, library_obj_list, specimen_obj_list, subject_obj_list, +Given an instrument run id, samplesheet, library_obj_list, sample_obj_list, subject_obj_list, Generate the events for * Start of the SampleSheet Shower -* Subject Event Data Objects (subject id, plus event data) * Library Event Data Objects (library id, plus event data) * End of the SampleSheet Shower """ -from typing import Dict, List - - -def generate_subject_event_data_object_from_subject(subject_obj: Dict, instrument_run_id: str) -> Dict: - return { - "event_data": { - "instrumentRunId": instrument_run_id, - "subject": { - "orcabusId": subject_obj.get("orcabusId"), - "subjectId": subject_obj.get("subjectId") - } - } - } - - -def get_specimen_obj_from_library_obj(library_obj: Dict, specimen_obj_list: List[Dict]) -> Dict: - """ - Given a list of specimen objects, filter - :param library_obj: - :param specimen_obj_list: - :return: - """ - return next( - filter( - lambda specimen_object_iter: specimen_object_iter.get("orcabusId") == library_obj.get("specimen"), - specimen_obj_list - ) - ) - -def get_subject_obj_from_specimen_obj(specimen_obj: Dict, subject_obj_list: List[Dict]) -> Dict: - """ - Given a specimen object, return the subject id - :param specimen_obj: - :param subject_obj_list: - :return: - """ - return next( - filter( - lambda subject_obj_iter: subject_obj_iter.get("orcabusId") == specimen_obj.get("subject"), - subject_obj_list - ) - ) +# Imports +from typing import Dict, List +import pandas as pd +from more_itertools import flatten +# Functions def get_library_bclconvert_rows(library_obj: Dict, bclconvert_data: List[Dict]) -> List[Dict]: """ Get library bclconvert rows @@ -71,19 +33,15 @@ def get_library_bclconvert_rows(library_obj: Dict, bclconvert_data: List[Dict]) ) -def generate_library_event_data_object_from_library_specimen_and_subject( +def generate_library_event_data_object_from_library( library_obj: Dict, instrument_run_id: str, - specimen_obj: Dict, - subject_obj: Dict, bclconvert_rows: List[Dict] ) -> Dict: """ Generate library event data object from library specimen and subject :param library_obj: :param instrument_run_id: - :param specimen_obj: - :param subject_obj: :param bclconvert_rows: :return: """ @@ -97,16 +55,6 @@ def generate_library_event_data_object_from_library_specimen_and_subject( "type": library_obj.get("type", None), "assay": library_obj.get("assay", None), "coverage": library_obj.get("coverage", None), - "projectOwner": library_obj.get("projectOwner", None), - "projectName": library_obj.get("projectName", None), - "specimen": { - "orcabusId": specimen_obj.get("orcabusId"), - "specimenId": specimen_obj.get("specimenId") - }, - "subject": { - "orcabusId": subject_obj.get("orcabusId"), - "subjectId": subject_obj.get("subjectId") - } } # Trim library event object to non-null values @@ -164,8 +112,11 @@ def generate_library_event_data_object_from_library_specimen_and_subject( "event_data": { "instrumentRunId": instrument_run_id, "library": library_event_obj, + "sample": library_obj.get("sample", None), + "subject": library_obj.get("subject", None), + "projectSet": library_obj.get("projectSet", None), "bclconvertDataRows": bclconvert_data_rows_event_obj, - "fastqListRows": fastq_list_row_ids + "fastqListRows": fastq_list_row_ids, } } @@ -187,22 +138,31 @@ def handler(event, context): # Get the library object list library_obj_list = event['library_obj_list'] + library_set = list(set(list( + map( + lambda library_obj_iter_: library_obj_iter_.get("orcabusId"), + library_obj_list + ) + ))) - # Get the specimen object list - specimen_obj_list = event['specimen_obj_list'] - - # Get the subject object list - subject_obj_list = event['subject_obj_list'] + # Get sample set from the library object list + sample_set = list(set(list( + filter( + lambda sample_iter_: sample_iter_ is not None, + map( + lambda library_obj_iter_: library_obj_iter_.get("sample", {}).get("orcabusId", None), + library_obj_list + ) + ) + ))) - # For each subject, generate the subject event data object - subject_event_data_list = list( + # Get the subject set list + subject_set = list(set(list( map( - lambda subject_obj_iter: ( - generate_subject_event_data_object_from_subject(subject_obj_iter, instrument_run_id) - ), - subject_obj_list + lambda library_obj_iter_: library_obj_iter_.get("subject", {}).get("orcabusId", None), + library_obj_list ) - ) + ))) library_event_data_list = [] @@ -210,56 +170,71 @@ def handler(event, context): # Get the bclconvert data rows bclconvert_rows = get_library_bclconvert_rows(library_obj, samplesheet['bclconvert_data']) - # Get the specimen object - specimen_obj = get_specimen_obj_from_library_obj(library_obj, specimen_obj_list) - - # Get the subject object - subject_obj = get_subject_obj_from_specimen_obj(specimen_obj, subject_obj_list) - # Generate the library event data object library_event_data_list.append( - generate_library_event_data_object_from_library_specimen_and_subject( + generate_library_event_data_object_from_library( library_obj, instrument_run_id, - specimen_obj, - subject_obj, bclconvert_rows ) ) # Generate project data level events - project_list = list( - set( - map( - lambda library_iter: ( - library_iter.get("projectOwner"), library_iter.get("projectName") - ), - library_obj_list + project_obj_list = list( + flatten( + list( + map( + lambda library_iter_: library_iter_.get('projectSet'), + library_obj_list + ) ) ) ) + # Drop duplicates + project_obj_list = pd.DataFrame( + project_obj_list + ).drop_duplicates( + subset='orcabusId' + ).to_dict( + orient='records' + ) + + # Generate project set + project_set = list(set(list( + map( + lambda project_obj_iter_: project_obj_iter_.get("orcabusId"), + project_obj_list + ) + ))) + project_event_data_list = [] - for project_owner, project_name in project_list: + for project_obj in project_obj_list: + # Get the library set for this project + library_proj_obj_list = list( + filter( + lambda library_obj_iter: any( + map( + lambda library_project_iter_: library_project_iter_['orcabusId'] == project_obj['orcabusId'], + library_obj_iter.get("projectSet") + ) + ), + library_obj_list + ) + ) + project_event_data_list.append( { "event_data": { "instrumentRunId": instrument_run_id, - "projectOwner": project_owner, - "projectName": project_name, - "libraries": list( + "project": project_obj, + "librarySet": list( map( lambda library_obj_iter: { "orcabusId": library_obj_iter.get("orcabusId"), "libraryId": library_obj_iter.get("libraryId") }, - filter( - lambda library_obj_iter: ( - library_obj_iter.get("projectOwner") == project_owner and - library_obj_iter.get("projectName") == project_name - ), - library_obj_list - ) + library_proj_obj_list ) ) } @@ -280,2592 +255,3022 @@ def handler(event, context): "start_samplesheet_shower_event_data": start_shower_event_data, "complete_samplesheet_shower_event_data": complete_shower_event_data, "project_event_data_list": project_event_data_list, - "subject_event_data_list": subject_event_data_list, - "library_event_data_list": library_event_data_list + "library_event_data_list": library_event_data_list, + "library_set": library_set, + "subject_set": subject_set, + "project_set": project_set, + "sample_set": sample_set, } -if __name__ == "__main__": - import json - - print( - json.dumps( - handler( - { - "instrument_run_id": "240424_A01052_0193_BH7JMMDRX5", - "samplesheet": - { - "header": { - "file_format_version": 2, - "run_name": "Tsqn240214-26-ctTSOv2_29Feb24", - "instrument_type": "NovaSeq" - }, - "reads": { - "read_1_cycles": 151, - "read_2_cycles": 151, - "index_1_cycles": 10, - "index_2_cycles": 10 - }, - "bclconvert_settings": { - "minimum_trimmed_read_length": 35, - "minimum_adapter_overlap": 3, - "mask_short_reads": 35, - "software_version": "4.2.7" - }, - "bclconvert_data": [ - { - "lane": 1, - "sample_id": "L2400102", - "index": "GAATTCGT", - "index2": "TTATGAGT", - "override_cycles": "U7N1Y143;I8N2;I8N2;U7N1Y143" - }, - { - "lane": 1, - "sample_id": "L2400159", - "index": "GAGAATGGTT", - "index2": "TTGCTGCCGA", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 1, - "sample_id": "L2400160", - "index": "AGAGGCAACC", - "index2": "CCATCATTAG", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 1, - "sample_id": "L2400161", - "index": "CCATCATTAG", - "index2": "AGAGGCAACC", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 1, - "sample_id": "L2400162", - "index": "GATAGGCCGA", - "index2": "GCCATGTGCG", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 1, - "sample_id": "L2400163", - "index": "ATGGTTGACT", - "index2": "AGGACAGGCC", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 1, - "sample_id": "L2400164", - "index": "TATTGCGCTC", - "index2": "CCTAACACAG", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 1, - "sample_id": "L2400166", - "index": "TTCTACATAC", - "index2": "TTACAGTTAG", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 2, - "sample_id": "L2400195", - "index": "ATGAGGCC", - "index2": "CAATTAAC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 2, - "sample_id": "L2400196", - "index": "ACTAAGAT", - "index2": "CCGCGGTT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 2, - "sample_id": "L2400197", - "index": "GTCGGAGC", - "index2": "TTATAACC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 2, - "sample_id": "L2400231", - "index": "TCGTAGTG", - "index2": "CCAAGTCT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 2, - "sample_id": "L2400238", - "index": "GGAGCGTC", - "index2": "GCACGGAC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 2, - "sample_id": "L2400239", - "index": "ATGGCATG", - "index2": "GGTACCTT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 2, - "sample_id": "L2400240", - "index": "GCAATGCA", - "index2": "AACGTTCC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400195", - "index": "ATGAGGCC", - "index2": "CAATTAAC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400196", - "index": "ACTAAGAT", - "index2": "CCGCGGTT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400197", - "index": "GTCGGAGC", - "index2": "TTATAACC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400231", - "index": "TCGTAGTG", - "index2": "CCAAGTCT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400238", - "index": "GGAGCGTC", - "index2": "GCACGGAC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400239", - "index": "ATGGCATG", - "index2": "GGTACCTT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 3, - "sample_id": "L2400240", - "index": "GCAATGCA", - "index2": "AACGTTCC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400165", - "index": "ACGCCTTGTT", - "index2": "ACGTTCCTTA", - "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", - "adapter_read_1": "CTGTCTCTTATACACATCT", - "adapter_read_2": "CTGTCTCTTATACACATCT" - }, - { - "lane": 4, - "sample_id": "L2400191", - "index": "GCACGGAC", - "index2": "TGCGAGAC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400197", - "index": "GTCGGAGC", - "index2": "TTATAACC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400198", - "index": "CTTGGTAT", - "index2": "GGACTTGG", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400241", - "index": "GTTCCAAT", - "index2": "GCAGAATT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400242", - "index": "ACCTTGGC", - "index2": "ATGAGGCC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400249", - "index": "AGTTTCGA", - "index2": "CCTACGAT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400250", - "index": "GAACCTCT", - "index2": "GTCTGCGC", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400251", - "index": "GCCCAGTG", - "index2": "CCGCAATT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400252", - "index": "TGACAGCT", - "index2": "CCCGTAGG", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400253", - "index": "CATCACCC", - "index2": "ATATAGCA", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400254", - "index": "CTGGAGTA", - "index2": "GTTCGGTT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400255", - "index": "GATCCGGG", - "index2": "AAGCAGGT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400256", - "index": "AACACCTG", - "index2": "CGCATGGG", - "override_cycles": "Y151;I8N2;I8N2;Y151" - }, - { - "lane": 4, - "sample_id": "L2400257", - "index": "GTGACGTT", - "index2": "TCCCAGAT", - "override_cycles": "Y151;I8N2;I8N2;Y151" - } - ], - "cloud_settings": { - "generated_version": "0.0.0", - "cloud_workflow": "ica_workflow_1", - "bclconvert_pipeline": "urn:ilmn:ica:pipeline:bf93b5cf-cb27-4dfa-846e-acd6eb081aca#BclConvert_v4_2_7" - }, - "cloud_data": [ - { - "sample_id": "L2400102", - "library_name": "L2400102_GAATTCGT_TTATGAGT", - "library_prep_kit_name": "ctTSO" - }, - { - "sample_id": "L2400159", - "library_name": "L2400159_GAGAATGGTT_TTGCTGCCGA", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400160", - "library_name": "L2400160_AGAGGCAACC_CCATCATTAG", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400161", - "library_name": "L2400161_CCATCATTAG_AGAGGCAACC", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400162", - "library_name": "L2400162_GATAGGCCGA_GCCATGTGCG", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400163", - "library_name": "L2400163_ATGGTTGACT_AGGACAGGCC", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400164", - "library_name": "L2400164_TATTGCGCTC_CCTAACACAG", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400165", - "library_name": "L2400165_ACGCCTTGTT_ACGTTCCTTA", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400166", - "library_name": "L2400166_TTCTACATAC_TTACAGTTAG", - "library_prep_kit_name": "ctTSOv2" - }, - { - "sample_id": "L2400191", - "library_name": "L2400191_GCACGGAC_TGCGAGAC", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400195", - "library_name": "L2400195_ATGAGGCC_CAATTAAC", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400196", - "library_name": "L2400196_ACTAAGAT_CCGCGGTT", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400197", - "library_name": "L2400197_GTCGGAGC_TTATAACC", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400198", - "library_name": "L2400198_CTTGGTAT_GGACTTGG", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400231", - "library_name": "L2400231_TCGTAGTG_CCAAGTCT", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400238", - "library_name": "L2400238_GGAGCGTC_GCACGGAC", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400239", - "library_name": "L2400239_ATGGCATG_GGTACCTT", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400240", - "library_name": "L2400240_GCAATGCA_AACGTTCC", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400241", - "library_name": "L2400241_GTTCCAAT_GCAGAATT", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400242", - "library_name": "L2400242_ACCTTGGC_ATGAGGCC", - "library_prep_kit_name": "TsqNano" - }, - { - "sample_id": "L2400249", - "library_name": "L2400249_AGTTTCGA_CCTACGAT", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400250", - "library_name": "L2400250_GAACCTCT_GTCTGCGC", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400251", - "library_name": "L2400251_GCCCAGTG_CCGCAATT", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400252", - "library_name": "L2400252_TGACAGCT_CCCGTAGG", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400253", - "library_name": "L2400253_CATCACCC_ATATAGCA", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400254", - "library_name": "L2400254_CTGGAGTA_GTTCGGTT", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400255", - "library_name": "L2400255_GATCCGGG_AAGCAGGT", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400256", - "library_name": "L2400256_AACACCTG_CGCATGGG", - "library_prep_kit_name": "NebRNA" - }, - { - "sample_id": "L2400257", - "library_name": "L2400257_GTGACGTT_TCCCAGAT", - "library_prep_kit_name": "NebRNA" - } - ] - }, - "library_obj_list": [ - { - "orcabusId": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX", - "libraryId": "L2400102", - "phenotype": "tumor", - "workflow": "research", - "quality": "borderline", - "type": "WGS", - "assay": "ctTSO", - "coverage": 50.0, - "projectOwner": "VCCC", - "projectName": "PO", - "specimen": "spc.01J5S9C4V269YTNA17TTP6NF76" - }, - { - "orcabusId": "lib.01J5S9CBG0NF8QBNVKM6ESCD60", - "libraryId": "L2400159", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBFDVZX7ZT3Y6TH28SY4" - }, - { - "orcabusId": "lib.01J5S9CBHP6NSB42RVFAP9PGJP", - "libraryId": "L2400160", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBH4V5B56CEJ5Q1XQKQ9" - }, - { - "orcabusId": "lib.01J5S9CBKCATYSFY40BRX6WJWX", - "libraryId": "L2400161", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBJTBJB72KJ74VSCHKJF" - }, - { - "orcabusId": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC", - "libraryId": "L2400162", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBMKTX5KN1XMPN479R2M" - }, - { - "orcabusId": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W", - "libraryId": "L2400163", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBPSPN6S3TQCVJZF0XFE" - }, - { - "orcabusId": "lib.01J5S9CBS64DNTHK6CE850CCNZ", - "libraryId": "L2400164", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBRM3Y6PPF6E5NWZA7HG" - }, - { - "orcabusId": "lib.01J5S9CBTZRYQNTGAHPC2T601D", - "libraryId": "L2400165", - "phenotype": "tumor", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 38.6, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBTACFBNJKE8C523B0A7" - }, - { - "orcabusId": "lib.01J5S9CBX10204CK7EKGTH9TMB", - "libraryId": "L2400166", - "phenotype": "negative-control", - "workflow": "manual", - "quality": "good", - "type": "ctDNA", - "assay": "ctTSOv2", - "coverage": 0.1, - "projectOwner": "UMCCR", - "projectName": "Testing", - "specimen": "spc.01J5S9CBWCGKQMG5S3ZSWA2ATE" - }, - { - "orcabusId": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7", - "libraryId": "L2400191", - "phenotype": "normal", - "workflow": "research", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 40.0, - "projectOwner": "TJohn", - "projectName": "CAVATAK", - "specimen": "spc.01J5S9CDEH0ATXYAK52KW807R4" - }, - { - "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S", - "libraryId": "L2400195", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 80.0, - "projectOwner": "TJohn", - "projectName": "CAVATAK", - "specimen": "spc.01J5S9CDQ0V9T98EGRPQJAP11S" - }, - { - "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8", - "libraryId": "L2400196", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 80.0, - "projectOwner": "TJohn", - "projectName": "CAVATAK", - "specimen": "spc.01J5S9CDRZMMR9S784BYSMVWCT" - }, - { - "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ", - "libraryId": "L2400197", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 80.0, - "projectOwner": "TJohn", - "projectName": "CAVATAK", - "specimen": "spc.01J5S9CDTSHGYMMJHE3SXEB2JG" - }, - { - "orcabusId": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q", - "libraryId": "L2400198", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 80.0, - "projectOwner": "TJohn", - "projectName": "CAVATAK", - "specimen": "spc.01J5S9CDWHAYG4RRG75GYZEK25" - }, - { - "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N", - "libraryId": "L2400231", - "phenotype": "tumor", - "workflow": "clinical", - "quality": "poor", - "type": "WGS", - "assay": "TsqNano", - "coverage": 100.0, - "projectOwner": "Tothill", - "projectName": "CUP", - "specimen": "spc.01J5S9CFWAQTGK4MZB3HM5NVBC" - }, - { - "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9", - "libraryId": "L2400238", - "phenotype": "normal", - "workflow": "clinical", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 40.0, - "projectOwner": "Tothill", - "projectName": "CUP", - "specimen": "spc.01J5S9CGBQCSQCS7XR3T89A82F" - }, - { - "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA", - "libraryId": "L2400239", - "phenotype": "normal", - "workflow": "clinical", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 40.0, - "projectOwner": "Tothill", - "projectName": "CUP", - "specimen": "spc.01J5S9CGE05BJCJ20M2KP4QWWB" - }, - { - "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB", - "libraryId": "L2400240", - "phenotype": "tumor", - "workflow": "clinical", - "quality": "poor", - "type": "WGS", - "assay": "TsqNano", - "coverage": 100.0, - "projectOwner": "Tothill", - "projectName": "CUP", - "specimen": "spc.01J5S9CGFQM3BQKADX8TWQ4ZH5" - }, - { - "orcabusId": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD", - "libraryId": "L2400241", - "phenotype": "negative-control", - "workflow": "control", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 0.1, - "projectOwner": "UMCCR", - "projectName": "Control", - "specimen": "spc.01J5S9CGHK1G7YXD7C4FCXXS52" - }, - { - "orcabusId": "lib.01J5S9CGKWDN7STKZKQM3KH9XR", - "libraryId": "L2400242", - "phenotype": "normal", - "workflow": "control", - "quality": "good", - "type": "WGS", - "assay": "TsqNano", - "coverage": 15.0, - "projectOwner": "UMCCR", - "projectName": "Control", - "specimen": "spc.01J5S9CGKAT4GHZ1VJFHVV15AD" - }, - { - "orcabusId": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE", - "libraryId": "L2400249", - "phenotype": "tumor", - "workflow": "control", - "quality": "good", - "type": "WTS", - "assay": "NebRNA", - "coverage": 1.0, - "projectOwner": "UMCCR", - "projectName": "Control", - "specimen": "spc.01J5S9CH267XEJP5GMZK31MJWS" - }, - { - "orcabusId": "lib.01J5S9CH4CYPA4SP05H8KRX4W9", - "libraryId": "L2400250", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Whittle", - "projectName": "BPOP-retro", - "specimen": "spc.01J5S9C0QC2TBZD7XA26D7WGTW" - }, - { - "orcabusId": "lib.01J5S9CH65E4EE5QJEJ1C60GGG", - "libraryId": "L2400251", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Whittle", - "projectName": "BPOP-retro", - "specimen": "spc.01J5S9CH5KXY3VMB9M9J2RCR7B" - }, - { - "orcabusId": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP", - "libraryId": "L2400252", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Whittle", - "projectName": "BPOP-retro", - "specimen": "spc.01J5S9CH774HEZFEVWWP2XADK1" - }, - { - "orcabusId": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY", - "libraryId": "L2400253", - "phenotype": "tumor", - "workflow": "research", - "quality": "good", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Whittle", - "projectName": "BPOP-retro", - "specimen": "spc.01J5S9CH98MQ2B1G2BQFEY0XZH" - }, - { - "orcabusId": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY", - "libraryId": "L2400254", - "phenotype": "tumor", - "workflow": "research", - "quality": "borderline", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Whittle", - "projectName": "BPOP-retro", - "specimen": "spc.01J5S9CHAX3XKJE5XE4VQWYN5H" - }, - { - "orcabusId": "lib.01J5S9CHE4ERQ4H209DH397W8A", - "libraryId": "L2400255", - "phenotype": "tumor", - "workflow": "clinical", - "quality": "very-poor", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Tothill", - "projectName": "CUP", - "specimen": "spc.01J5S9CHDGRNK70B043K887RP2" - }, - { - "orcabusId": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY", - "libraryId": "L2400256", - "phenotype": "tumor", - "workflow": "clinical", - "quality": "very-poor", - "type": "WTS", - "assay": "NebRNA", - "coverage": 6.0, - "projectOwner": "Tothill", - "projectName": "CUP", - "specimen": "spc.01J5S9CHFAPXYKK49FAGVF5CQF" - }, - { - "orcabusId": "lib.01J5S9CHHNGFJN73NPRQMSYGN9", - "libraryId": "L2400257", - "phenotype": "negative-control", - "workflow": "control", - "quality": "good", - "type": "WTS", - "assay": "NebRNA", - "coverage": 0.1, - "projectOwner": "UMCCR", - "projectName": "Control", - "specimen": "spc.01J5S9CHH24VFM443RD8Q8X4B3" - } - ], - "specimen_obj_list": [ - { - "orcabusId": "spc.01J5S9C0QC2TBZD7XA26D7WGTW", - "specimenId": "PRJ240003", - "source": "tissue", - "subject": "sbj.01J5S9C0PVB4QNVGK4Q1WSYEGV" - }, - { - "orcabusId": "spc.01J5S9C4V269YTNA17TTP6NF76", - "specimenId": "MDX210402", - "source": "plasma-serum", - "subject": "sbj.01J5S9C4TE1GCWA1QGNCWHB1Y9" - }, - { - "orcabusId": "spc.01J5S9CBFDVZX7ZT3Y6TH28SY4", - "specimenId": "PTC_SCMM1pc2", - "source": "cfDNA", - "subject": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB" - }, - { - "orcabusId": "spc.01J5S9CBH4V5B56CEJ5Q1XQKQ9", - "specimenId": "PTC_SCMM1pc3", - "source": "cfDNA", - "subject": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB" - }, - { - "orcabusId": "spc.01J5S9CBJTBJB72KJ74VSCHKJF", - "specimenId": "PTC_SCMM1pc4", - "source": "cfDNA", - "subject": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB" - }, - { - "orcabusId": "spc.01J5S9CBMKTX5KN1XMPN479R2M", - "specimenId": "PTC_SCMM01pc20", - "source": "cfDNA", - "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" - }, - { - "orcabusId": "spc.01J5S9CBPSPN6S3TQCVJZF0XFE", - "specimenId": "PTC_SCMM01pc15", - "source": "cfDNA", - "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" - }, - { - "orcabusId": "spc.01J5S9CBRM3Y6PPF6E5NWZA7HG", - "specimenId": "PTC_SCMM01pc10", - "source": "cfDNA", - "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" - }, - { - "orcabusId": "spc.01J5S9CBTACFBNJKE8C523B0A7", - "specimenId": "PTC_SCMM01pc5", - "source": "cfDNA", - "subject": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0" - }, - { - "orcabusId": "spc.01J5S9CBWCGKQMG5S3ZSWA2ATE", - "specimenId": "NTC_v2ctTSO240207", - "source": "water", - "subject": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6" - }, - { - "orcabusId": "spc.01J5S9CDEH0ATXYAK52KW807R4", - "specimenId": "PRJ240169", - "source": "blood", - "subject": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS" - }, - { - "orcabusId": "spc.01J5S9CDQ0V9T98EGRPQJAP11S", - "specimenId": "PRJ240180", - "source": "tissue", - "subject": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS" - }, - { - "orcabusId": "spc.01J5S9CDRZMMR9S784BYSMVWCT", - "specimenId": "PRJ240181", - "source": "tissue", - "subject": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS" - }, - { - "orcabusId": "spc.01J5S9CDTSHGYMMJHE3SXEB2JG", - "specimenId": "PRJ240182", - "source": "tissue", - "subject": "sbj.01J5S9CDG7B0KA8YEDK876VVDP" - }, - { - "orcabusId": "spc.01J5S9CDWHAYG4RRG75GYZEK25", - "specimenId": "PRJ240183", - "source": "tissue", - "subject": "sbj.01J5S9CDG7B0KA8YEDK876VVDP" - }, - { - "orcabusId": "spc.01J5S9CFWAQTGK4MZB3HM5NVBC", - "specimenId": "PRJ240199", - "source": "FFPE", - "subject": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5" - }, - { - "orcabusId": "spc.01J5S9CGBQCSQCS7XR3T89A82F", - "specimenId": "PRJ240643", - "source": "blood", - "subject": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5" - }, - { - "orcabusId": "spc.01J5S9CGE05BJCJ20M2KP4QWWB", - "specimenId": "PRJ240646", - "source": "blood", - "subject": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3" - }, - { - "orcabusId": "spc.01J5S9CGFQM3BQKADX8TWQ4ZH5", - "specimenId": "PRJ240647", - "source": "FFPE", - "subject": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3" - }, - { - "orcabusId": "spc.01J5S9CGHK1G7YXD7C4FCXXS52", - "specimenId": "NTC_TSqN240226", - "source": "water", - "subject": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6" - }, - { - "orcabusId": "spc.01J5S9CGKAT4GHZ1VJFHVV15AD", - "specimenId": "PTC_TSqN240226", - "source": "cell-line", - "subject": "sbj.01J5S9BYVWZDS8AW7A94CDQBXK" - }, - { - "orcabusId": "spc.01J5S9CH267XEJP5GMZK31MJWS", - "specimenId": "PTC_NebRNA240226", - "source": "cell-line", - "subject": "sbj.01J5S9C1S3XV8PNB78XYJ1EQM1" - }, - { - "orcabusId": "spc.01J5S9CH5KXY3VMB9M9J2RCR7B", - "specimenId": "PRJ240561", - "source": "tissue", - "subject": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN" - }, - { - "orcabusId": "spc.01J5S9CH774HEZFEVWWP2XADK1", - "specimenId": "PRJ240562", - "source": "tissue", - "subject": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN" - }, - { - "orcabusId": "spc.01J5S9CH98MQ2B1G2BQFEY0XZH", - "specimenId": "PRJ240566", - "source": "tissue", - "subject": "sbj.01J5S9CG5GEWYBK0065C49HT23" - }, - { - "orcabusId": "spc.01J5S9CHAX3XKJE5XE4VQWYN5H", - "specimenId": "PRJ240567", - "source": "tissue", - "subject": "sbj.01J5S9CG5GEWYBK0065C49HT23" - }, - { - "orcabusId": "spc.01J5S9CHDGRNK70B043K887RP2", - "specimenId": "PRJ240200", - "source": "FFPE", - "subject": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5" - }, - { - "orcabusId": "spc.01J5S9CHFAPXYKK49FAGVF5CQF", - "specimenId": "PRJ240648", - "source": "FFPE", - "subject": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3" - }, - { - "orcabusId": "spc.01J5S9CHH24VFM443RD8Q8X4B3", - "specimenId": "NTC_NebRNA240226", - "source": "water", - "subject": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6" - } - ], - "subject_obj_list": [ - { - "orcabusId": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6", - "subjectId": "SBJ00006" - }, - { - "orcabusId": "sbj.01J5S9BYVWZDS8AW7A94CDQBXK", - "subjectId": "SBJ00005" - }, - { - "orcabusId": "sbj.01J5S9C0PVB4QNVGK4Q1WSYEGV", - "subjectId": "SBJ04488" - }, - { - "orcabusId": "sbj.01J5S9C1S3XV8PNB78XYJ1EQM1", - "subjectId": "SBJ00029" - }, - { - "orcabusId": "sbj.01J5S9C4TE1GCWA1QGNCWHB1Y9", - "subjectId": "SBJ01143" - }, - { - "orcabusId": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB", - "subjectId": "SBJ04407" - }, - { - "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", - "subjectId": "SBJ04648" - }, - { - "orcabusId": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS", - "subjectId": "SBJ04653" - }, - { - "orcabusId": "sbj.01J5S9CDG7B0KA8YEDK876VVDP", - "subjectId": "SBJ04654" - }, - { - "orcabusId": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5", - "subjectId": "SBJ04659" - }, - { - "orcabusId": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN", - "subjectId": "SBJ04660" - }, - { - "orcabusId": "sbj.01J5S9CG5GEWYBK0065C49HT23", - "subjectId": "SBJ04661" - }, - { - "orcabusId": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3", - "subjectId": "SBJ04662" - } - ] - }, - None - ), - indent=2 - ) - ) - # { - # "start_samplesheet_shower_event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5" - # }, - # "complete_samplesheet_shower_event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5" - # }, - # "project_event_data_list": [ - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "libraries": [ - # { - # "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N", - # "libraryId": "L2400231" - # }, - # { - # "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9", - # "libraryId": "L2400238" - # }, - # { - # "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA", - # "libraryId": "L2400239" - # }, - # { - # "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB", - # "libraryId": "L2400240" - # }, - # { - # "orcabusId": "lib.01J5S9CHE4ERQ4H209DH397W8A", - # "libraryId": "L2400255" - # }, - # { - # "orcabusId": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY", - # "libraryId": "L2400256" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "projectOwner": "VCCC", - # "projectName": "PO", - # "libraries": [ - # { - # "orcabusId": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX", - # "libraryId": "L2400102" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "projectOwner": "TJohn", - # "projectName": "CAVATAK", - # "libraries": [ - # { - # "orcabusId": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7", - # "libraryId": "L2400191" - # }, - # { - # "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S", - # "libraryId": "L2400195" - # }, - # { - # "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8", - # "libraryId": "L2400196" - # }, - # { - # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ", - # "libraryId": "L2400197" - # }, - # { - # "orcabusId": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q", - # "libraryId": "L2400198" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "projectOwner": "UMCCR", - # "projectName": "Control", - # "libraries": [ - # { - # "orcabusId": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD", - # "libraryId": "L2400241" - # }, - # { - # "orcabusId": "lib.01J5S9CGKWDN7STKZKQM3KH9XR", - # "libraryId": "L2400242" - # }, - # { - # "orcabusId": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE", - # "libraryId": "L2400249" - # }, - # { - # "orcabusId": "lib.01J5S9CHHNGFJN73NPRQMSYGN9", - # "libraryId": "L2400257" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "projectOwner": "Whittle", - # "projectName": "BPOP-retro", - # "libraries": [ - # { - # "orcabusId": "lib.01J5S9CH4CYPA4SP05H8KRX4W9", - # "libraryId": "L2400250" - # }, - # { - # "orcabusId": "lib.01J5S9CH65E4EE5QJEJ1C60GGG", - # "libraryId": "L2400251" - # }, - # { - # "orcabusId": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP", - # "libraryId": "L2400252" - # }, - # { - # "orcabusId": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY", - # "libraryId": "L2400253" - # }, - # { - # "orcabusId": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY", - # "libraryId": "L2400254" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "libraries": [ - # { - # "orcabusId": "lib.01J5S9CBG0NF8QBNVKM6ESCD60", - # "libraryId": "L2400159" - # }, - # { - # "orcabusId": "lib.01J5S9CBHP6NSB42RVFAP9PGJP", - # "libraryId": "L2400160" - # }, - # { - # "orcabusId": "lib.01J5S9CBKCATYSFY40BRX6WJWX", - # "libraryId": "L2400161" - # }, - # { - # "orcabusId": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC", - # "libraryId": "L2400162" - # }, - # { - # "orcabusId": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W", - # "libraryId": "L2400163" - # }, - # { - # "orcabusId": "lib.01J5S9CBS64DNTHK6CE850CCNZ", - # "libraryId": "L2400164" - # }, - # { - # "orcabusId": "lib.01J5S9CBTZRYQNTGAHPC2T601D", - # "libraryId": "L2400165" - # }, - # { - # "orcabusId": "lib.01J5S9CBX10204CK7EKGTH9TMB", - # "libraryId": "L2400166" - # } - # ] - # } - # } - # ], - # "subject_event_data_list": [ - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6", - # "subjectId": "SBJ00006" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9BYVWZDS8AW7A94CDQBXK", - # "subjectId": "SBJ00005" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9C0PVB4QNVGK4Q1WSYEGV", - # "subjectId": "SBJ04488" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9C1S3XV8PNB78XYJ1EQM1", - # "subjectId": "SBJ00029" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9C4TE1GCWA1QGNCWHB1Y9", - # "subjectId": "SBJ01143" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB", - # "subjectId": "SBJ04407" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", - # "subjectId": "SBJ04648" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS", - # "subjectId": "SBJ04653" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CDG7B0KA8YEDK876VVDP", - # "subjectId": "SBJ04654" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5", - # "subjectId": "SBJ04659" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN", - # "subjectId": "SBJ04660" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CG5GEWYBK0065C49HT23", - # "subjectId": "SBJ04661" - # } - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "subject": { - # "orcabusId": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3", - # "subjectId": "SBJ04662" - # } - # } - # } - # ], - # "library_event_data_list": [ - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX", - # "libraryId": "L2400102", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "borderline", - # "type": "WGS", - # "assay": "ctTSO", - # "coverage": 50.0, - # "projectOwner": "VCCC", - # "projectName": "PO", - # "specimen": { - # "orcabusId": "spc.01J5S9C4V269YTNA17TTP6NF76", - # "specimenId": "MDX210402" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9C4TE1GCWA1QGNCWHB1Y9", - # "subjectId": "SBJ01143" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400102", - # "index": "GAATTCGT", - # "index2": "TTATGAGT", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I8N2;I8N2;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GAATTCGT.TTATGAGT.1.240424_A01052_0193_BH7JMMDRX5.L2400102" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBG0NF8QBNVKM6ESCD60", - # "libraryId": "L2400159", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBFDVZX7ZT3Y6TH28SY4", - # "specimenId": "PTC_SCMM1pc2" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB", - # "subjectId": "SBJ04407" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400159", - # "index": "GAGAATGGTT", - # "index2": "TTGCTGCCGA", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GAGAATGGTT.TTGCTGCCGA.1.240424_A01052_0193_BH7JMMDRX5.L2400159" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBHP6NSB42RVFAP9PGJP", - # "libraryId": "L2400160", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBH4V5B56CEJ5Q1XQKQ9", - # "specimenId": "PTC_SCMM1pc3" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB", - # "subjectId": "SBJ04407" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400160", - # "index": "AGAGGCAACC", - # "index2": "CCATCATTAG", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "AGAGGCAACC.CCATCATTAG.1.240424_A01052_0193_BH7JMMDRX5.L2400160" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBKCATYSFY40BRX6WJWX", - # "libraryId": "L2400161", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBJTBJB72KJ74VSCHKJF", - # "specimenId": "PTC_SCMM1pc4" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBEQ3DM8XDV2G2ZQJDXB", - # "subjectId": "SBJ04407" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400161", - # "index": "CCATCATTAG", - # "index2": "AGAGGCAACC", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "CCATCATTAG.AGAGGCAACC.1.240424_A01052_0193_BH7JMMDRX5.L2400161" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC", - # "libraryId": "L2400162", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBMKTX5KN1XMPN479R2M", - # "specimenId": "PTC_SCMM01pc20" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", - # "subjectId": "SBJ04648" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400162", - # "index": "GATAGGCCGA", - # "index2": "GCCATGTGCG", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GATAGGCCGA.GCCATGTGCG.1.240424_A01052_0193_BH7JMMDRX5.L2400162" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W", - # "libraryId": "L2400163", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBPSPN6S3TQCVJZF0XFE", - # "specimenId": "PTC_SCMM01pc15" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", - # "subjectId": "SBJ04648" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400163", - # "index": "ATGGTTGACT", - # "index2": "AGGACAGGCC", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "ATGGTTGACT.AGGACAGGCC.1.240424_A01052_0193_BH7JMMDRX5.L2400163" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBS64DNTHK6CE850CCNZ", - # "libraryId": "L2400164", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBRM3Y6PPF6E5NWZA7HG", - # "specimenId": "PTC_SCMM01pc10" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", - # "subjectId": "SBJ04648" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400164", - # "index": "TATTGCGCTC", - # "index2": "CCTAACACAG", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "TATTGCGCTC.CCTAACACAG.1.240424_A01052_0193_BH7JMMDRX5.L2400164" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBTZRYQNTGAHPC2T601D", - # "libraryId": "L2400165", - # "phenotype": "tumor", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 38.6, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBTACFBNJKE8C523B0A7", - # "specimenId": "PTC_SCMM01pc5" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CBM3AT89QTXD7PT0BKA0", - # "subjectId": "SBJ04648" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400165", - # "index": "ACGCCTTGTT", - # "index2": "ACGTTCCTTA", - # "lane": 4, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "ACGCCTTGTT.ACGTTCCTTA.4.240424_A01052_0193_BH7JMMDRX5.L2400165" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CBX10204CK7EKGTH9TMB", - # "libraryId": "L2400166", - # "phenotype": "negative-control", - # "workflow": "manual", - # "quality": "good", - # "type": "ctDNA", - # "assay": "ctTSOv2", - # "coverage": 0.1, - # "projectOwner": "UMCCR", - # "projectName": "Testing", - # "specimen": { - # "orcabusId": "spc.01J5S9CBWCGKQMG5S3ZSWA2ATE", - # "specimenId": "NTC_v2ctTSO240207" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6", - # "subjectId": "SBJ00006" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400166", - # "index": "TTCTACATAC", - # "index2": "TTACAGTTAG", - # "lane": 1, - # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "TTCTACATAC.TTACAGTTAG.1.240424_A01052_0193_BH7JMMDRX5.L2400166" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7", - # "libraryId": "L2400191", - # "phenotype": "normal", - # "workflow": "research", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 40.0, - # "projectOwner": "TJohn", - # "projectName": "CAVATAK", - # "specimen": { - # "orcabusId": "spc.01J5S9CDEH0ATXYAK52KW807R4", - # "specimenId": "PRJ240169" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS", - # "subjectId": "SBJ04653" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400191", - # "index": "GCACGGAC", - # "index2": "TGCGAGAC", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GCACGGAC.TGCGAGAC.4.240424_A01052_0193_BH7JMMDRX5.L2400191" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S", - # "libraryId": "L2400195", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 80.0, - # "projectOwner": "TJohn", - # "projectName": "CAVATAK", - # "specimen": { - # "orcabusId": "spc.01J5S9CDQ0V9T98EGRPQJAP11S", - # "specimenId": "PRJ240180" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS", - # "subjectId": "SBJ04653" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400195", - # "index": "ATGAGGCC", - # "index2": "CAATTAAC", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400195", - # "index": "ATGAGGCC", - # "index2": "CAATTAAC", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "ATGAGGCC.CAATTAAC.2.240424_A01052_0193_BH7JMMDRX5.L2400195" - # }, - # { - # "fastqListRowRgid": "ATGAGGCC.CAATTAAC.3.240424_A01052_0193_BH7JMMDRX5.L2400195" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8", - # "libraryId": "L2400196", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 80.0, - # "projectOwner": "TJohn", - # "projectName": "CAVATAK", - # "specimen": { - # "orcabusId": "spc.01J5S9CDRZMMR9S784BYSMVWCT", - # "specimenId": "PRJ240181" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CDDP20JX8V63ZKMPBJQS", - # "subjectId": "SBJ04653" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400196", - # "index": "ACTAAGAT", - # "index2": "CCGCGGTT", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400196", - # "index": "ACTAAGAT", - # "index2": "CCGCGGTT", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "ACTAAGAT.CCGCGGTT.2.240424_A01052_0193_BH7JMMDRX5.L2400196" - # }, - # { - # "fastqListRowRgid": "ACTAAGAT.CCGCGGTT.3.240424_A01052_0193_BH7JMMDRX5.L2400196" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ", - # "libraryId": "L2400197", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 80.0, - # "projectOwner": "TJohn", - # "projectName": "CAVATAK", - # "specimen": { - # "orcabusId": "spc.01J5S9CDTSHGYMMJHE3SXEB2JG", - # "specimenId": "PRJ240182" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CDG7B0KA8YEDK876VVDP", - # "subjectId": "SBJ04654" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400197", - # "index": "GTCGGAGC", - # "index2": "TTATAACC", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400197", - # "index": "GTCGGAGC", - # "index2": "TTATAACC", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400197", - # "index": "GTCGGAGC", - # "index2": "TTATAACC", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GTCGGAGC.TTATAACC.2.240424_A01052_0193_BH7JMMDRX5.L2400197" - # }, - # { - # "fastqListRowRgid": "GTCGGAGC.TTATAACC.3.240424_A01052_0193_BH7JMMDRX5.L2400197" - # }, - # { - # "fastqListRowRgid": "GTCGGAGC.TTATAACC.4.240424_A01052_0193_BH7JMMDRX5.L2400197" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q", - # "libraryId": "L2400198", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 80.0, - # "projectOwner": "TJohn", - # "projectName": "CAVATAK", - # "specimen": { - # "orcabusId": "spc.01J5S9CDWHAYG4RRG75GYZEK25", - # "specimenId": "PRJ240183" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CDG7B0KA8YEDK876VVDP", - # "subjectId": "SBJ04654" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400198", - # "index": "CTTGGTAT", - # "index2": "GGACTTGG", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "CTTGGTAT.GGACTTGG.4.240424_A01052_0193_BH7JMMDRX5.L2400198" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N", - # "libraryId": "L2400231", - # "phenotype": "tumor", - # "workflow": "clinical", - # "quality": "poor", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 100.0, - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "specimen": { - # "orcabusId": "spc.01J5S9CFWAQTGK4MZB3HM5NVBC", - # "specimenId": "PRJ240199" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5", - # "subjectId": "SBJ04659" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400231", - # "index": "TCGTAGTG", - # "index2": "CCAAGTCT", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400231", - # "index": "TCGTAGTG", - # "index2": "CCAAGTCT", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "TCGTAGTG.CCAAGTCT.2.240424_A01052_0193_BH7JMMDRX5.L2400231" - # }, - # { - # "fastqListRowRgid": "TCGTAGTG.CCAAGTCT.3.240424_A01052_0193_BH7JMMDRX5.L2400231" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9", - # "libraryId": "L2400238", - # "phenotype": "normal", - # "workflow": "clinical", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 40.0, - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "specimen": { - # "orcabusId": "spc.01J5S9CGBQCSQCS7XR3T89A82F", - # "specimenId": "PRJ240643" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5", - # "subjectId": "SBJ04659" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400238", - # "index": "GGAGCGTC", - # "index2": "GCACGGAC", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400238", - # "index": "GGAGCGTC", - # "index2": "GCACGGAC", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GGAGCGTC.GCACGGAC.2.240424_A01052_0193_BH7JMMDRX5.L2400238" - # }, - # { - # "fastqListRowRgid": "GGAGCGTC.GCACGGAC.3.240424_A01052_0193_BH7JMMDRX5.L2400238" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA", - # "libraryId": "L2400239", - # "phenotype": "normal", - # "workflow": "clinical", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 40.0, - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "specimen": { - # "orcabusId": "spc.01J5S9CGE05BJCJ20M2KP4QWWB", - # "specimenId": "PRJ240646" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3", - # "subjectId": "SBJ04662" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400239", - # "index": "ATGGCATG", - # "index2": "GGTACCTT", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400239", - # "index": "ATGGCATG", - # "index2": "GGTACCTT", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "ATGGCATG.GGTACCTT.2.240424_A01052_0193_BH7JMMDRX5.L2400239" - # }, - # { - # "fastqListRowRgid": "ATGGCATG.GGTACCTT.3.240424_A01052_0193_BH7JMMDRX5.L2400239" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB", - # "libraryId": "L2400240", - # "phenotype": "tumor", - # "workflow": "clinical", - # "quality": "poor", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 100.0, - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "specimen": { - # "orcabusId": "spc.01J5S9CGFQM3BQKADX8TWQ4ZH5", - # "specimenId": "PRJ240647" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3", - # "subjectId": "SBJ04662" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400240", - # "index": "GCAATGCA", - # "index2": "AACGTTCC", - # "lane": 2, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # }, - # { - # "sampleId": "L2400240", - # "index": "GCAATGCA", - # "index2": "AACGTTCC", - # "lane": 3, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GCAATGCA.AACGTTCC.2.240424_A01052_0193_BH7JMMDRX5.L2400240" - # }, - # { - # "fastqListRowRgid": "GCAATGCA.AACGTTCC.3.240424_A01052_0193_BH7JMMDRX5.L2400240" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD", - # "libraryId": "L2400241", - # "phenotype": "negative-control", - # "workflow": "control", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 0.1, - # "projectOwner": "UMCCR", - # "projectName": "Control", - # "specimen": { - # "orcabusId": "spc.01J5S9CGHK1G7YXD7C4FCXXS52", - # "specimenId": "NTC_TSqN240226" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6", - # "subjectId": "SBJ00006" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400241", - # "index": "GTTCCAAT", - # "index2": "GCAGAATT", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GTTCCAAT.GCAGAATT.4.240424_A01052_0193_BH7JMMDRX5.L2400241" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CGKWDN7STKZKQM3KH9XR", - # "libraryId": "L2400242", - # "phenotype": "normal", - # "workflow": "control", - # "quality": "good", - # "type": "WGS", - # "assay": "TsqNano", - # "coverage": 15.0, - # "projectOwner": "UMCCR", - # "projectName": "Control", - # "specimen": { - # "orcabusId": "spc.01J5S9CGKAT4GHZ1VJFHVV15AD", - # "specimenId": "PTC_TSqN240226" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9BYVWZDS8AW7A94CDQBXK", - # "subjectId": "SBJ00005" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400242", - # "index": "ACCTTGGC", - # "index2": "ATGAGGCC", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "ACCTTGGC.ATGAGGCC.4.240424_A01052_0193_BH7JMMDRX5.L2400242" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE", - # "libraryId": "L2400249", - # "phenotype": "tumor", - # "workflow": "control", - # "quality": "good", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 1.0, - # "projectOwner": "UMCCR", - # "projectName": "Control", - # "specimen": { - # "orcabusId": "spc.01J5S9CH267XEJP5GMZK31MJWS", - # "specimenId": "PTC_NebRNA240226" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9C1S3XV8PNB78XYJ1EQM1", - # "subjectId": "SBJ00029" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400249", - # "index": "AGTTTCGA", - # "index2": "CCTACGAT", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "AGTTTCGA.CCTACGAT.4.240424_A01052_0193_BH7JMMDRX5.L2400249" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CH4CYPA4SP05H8KRX4W9", - # "libraryId": "L2400250", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Whittle", - # "projectName": "BPOP-retro", - # "specimen": { - # "orcabusId": "spc.01J5S9C0QC2TBZD7XA26D7WGTW", - # "specimenId": "PRJ240003" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9C0PVB4QNVGK4Q1WSYEGV", - # "subjectId": "SBJ04488" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400250", - # "index": "GAACCTCT", - # "index2": "GTCTGCGC", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GAACCTCT.GTCTGCGC.4.240424_A01052_0193_BH7JMMDRX5.L2400250" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CH65E4EE5QJEJ1C60GGG", - # "libraryId": "L2400251", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Whittle", - # "projectName": "BPOP-retro", - # "specimen": { - # "orcabusId": "spc.01J5S9CH5KXY3VMB9M9J2RCR7B", - # "specimenId": "PRJ240561" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN", - # "subjectId": "SBJ04660" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400251", - # "index": "GCCCAGTG", - # "index2": "CCGCAATT", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GCCCAGTG.CCGCAATT.4.240424_A01052_0193_BH7JMMDRX5.L2400251" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP", - # "libraryId": "L2400252", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Whittle", - # "projectName": "BPOP-retro", - # "specimen": { - # "orcabusId": "spc.01J5S9CH774HEZFEVWWP2XADK1", - # "specimenId": "PRJ240562" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CFY1BV2Z0SGKYNF1VHQN", - # "subjectId": "SBJ04660" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400252", - # "index": "TGACAGCT", - # "index2": "CCCGTAGG", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "TGACAGCT.CCCGTAGG.4.240424_A01052_0193_BH7JMMDRX5.L2400252" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY", - # "libraryId": "L2400253", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "good", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Whittle", - # "projectName": "BPOP-retro", - # "specimen": { - # "orcabusId": "spc.01J5S9CH98MQ2B1G2BQFEY0XZH", - # "specimenId": "PRJ240566" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CG5GEWYBK0065C49HT23", - # "subjectId": "SBJ04661" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400253", - # "index": "CATCACCC", - # "index2": "ATATAGCA", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "CATCACCC.ATATAGCA.4.240424_A01052_0193_BH7JMMDRX5.L2400253" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY", - # "libraryId": "L2400254", - # "phenotype": "tumor", - # "workflow": "research", - # "quality": "borderline", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Whittle", - # "projectName": "BPOP-retro", - # "specimen": { - # "orcabusId": "spc.01J5S9CHAX3XKJE5XE4VQWYN5H", - # "specimenId": "PRJ240567" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CG5GEWYBK0065C49HT23", - # "subjectId": "SBJ04661" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400254", - # "index": "CTGGAGTA", - # "index2": "GTTCGGTT", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "CTGGAGTA.GTTCGGTT.4.240424_A01052_0193_BH7JMMDRX5.L2400254" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CHE4ERQ4H209DH397W8A", - # "libraryId": "L2400255", - # "phenotype": "tumor", - # "workflow": "clinical", - # "quality": "very-poor", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "specimen": { - # "orcabusId": "spc.01J5S9CHDGRNK70B043K887RP2", - # "specimenId": "PRJ240200" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CFVJ9GVEHZK6CD9WAAV5", - # "subjectId": "SBJ04659" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400255", - # "index": "GATCCGGG", - # "index2": "AAGCAGGT", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GATCCGGG.AAGCAGGT.4.240424_A01052_0193_BH7JMMDRX5.L2400255" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY", - # "libraryId": "L2400256", - # "phenotype": "tumor", - # "workflow": "clinical", - # "quality": "very-poor", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 6.0, - # "projectOwner": "Tothill", - # "projectName": "CUP", - # "specimen": { - # "orcabusId": "spc.01J5S9CHFAPXYKK49FAGVF5CQF", - # "specimenId": "PRJ240648" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9CGDGTF5VZJSSE4ADBNJ3", - # "subjectId": "SBJ04662" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400256", - # "index": "AACACCTG", - # "index2": "CGCATGGG", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "AACACCTG.CGCATGGG.4.240424_A01052_0193_BH7JMMDRX5.L2400256" - # } - # ] - # } - # }, - # { - # "event_data": { - # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", - # "library": { - # "orcabusId": "lib.01J5S9CHHNGFJN73NPRQMSYGN9", - # "libraryId": "L2400257", - # "phenotype": "negative-control", - # "workflow": "control", - # "quality": "good", - # "type": "WTS", - # "assay": "NebRNA", - # "coverage": 0.1, - # "projectOwner": "UMCCR", - # "projectName": "Control", - # "specimen": { - # "orcabusId": "spc.01J5S9CHH24VFM443RD8Q8X4B3", - # "specimenId": "NTC_NebRNA240226" - # }, - # "subject": { - # "orcabusId": "sbj.01J5S9BYKC1RH7DY68GF1JNSR6", - # "subjectId": "SBJ00006" - # } - # }, - # "bclconvertDataRows": [ - # { - # "sampleId": "L2400257", - # "index": "GTGACGTT", - # "index2": "TCCCAGAT", - # "lane": 4, - # "overrideCycles": "Y151;I8N2;I8N2;Y151" - # } - # ], - # "fastqListRows": [ - # { - # "fastqListRowRgid": "GTGACGTT.TCCCAGAT.4.240424_A01052_0193_BH7JMMDRX5.L2400257" - # } - # ] - # } - # } - # ] - # } +# if __name__ == "__main__": +# import json +# +# print( +# json.dumps( +# handler( +# { +# "instrument_run_id": "240424_A01052_0193_BH7JMMDRX5", +# "samplesheet": +# { +# "header": { +# "file_format_version": 2, +# "run_name": "Tsqn240214-26-ctTSOv2_29Feb24", +# "instrument_type": "NovaSeq" +# }, +# "reads": { +# "read_1_cycles": 151, +# "read_2_cycles": 151, +# "index_1_cycles": 10, +# "index_2_cycles": 10 +# }, +# "bclconvert_settings": { +# "minimum_trimmed_read_length": 35, +# "minimum_adapter_overlap": 3, +# "mask_short_reads": 35, +# "software_version": "4.2.7" +# }, +# "bclconvert_data": [ +# { +# "lane": 1, +# "sample_id": "L2400102", +# "index": "GAATTCGT", +# "index2": "TTATGAGT", +# "override_cycles": "U7N1Y143;I8N2;I8N2;U7N1Y143" +# }, +# { +# "lane": 1, +# "sample_id": "L2400159", +# "index": "GAGAATGGTT", +# "index2": "TTGCTGCCGA", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 1, +# "sample_id": "L2400160", +# "index": "AGAGGCAACC", +# "index2": "CCATCATTAG", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 1, +# "sample_id": "L2400161", +# "index": "CCATCATTAG", +# "index2": "AGAGGCAACC", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 1, +# "sample_id": "L2400162", +# "index": "GATAGGCCGA", +# "index2": "GCCATGTGCG", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 1, +# "sample_id": "L2400163", +# "index": "ATGGTTGACT", +# "index2": "AGGACAGGCC", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 1, +# "sample_id": "L2400164", +# "index": "TATTGCGCTC", +# "index2": "CCTAACACAG", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 1, +# "sample_id": "L2400166", +# "index": "TTCTACATAC", +# "index2": "TTACAGTTAG", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 2, +# "sample_id": "L2400195", +# "index": "ATGAGGCC", +# "index2": "CAATTAAC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 2, +# "sample_id": "L2400196", +# "index": "ACTAAGAT", +# "index2": "CCGCGGTT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 2, +# "sample_id": "L2400197", +# "index": "GTCGGAGC", +# "index2": "TTATAACC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 2, +# "sample_id": "L2400231", +# "index": "TCGTAGTG", +# "index2": "CCAAGTCT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 2, +# "sample_id": "L2400238", +# "index": "GGAGCGTC", +# "index2": "GCACGGAC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 2, +# "sample_id": "L2400239", +# "index": "ATGGCATG", +# "index2": "GGTACCTT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 2, +# "sample_id": "L2400240", +# "index": "GCAATGCA", +# "index2": "AACGTTCC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400195", +# "index": "ATGAGGCC", +# "index2": "CAATTAAC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400196", +# "index": "ACTAAGAT", +# "index2": "CCGCGGTT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400197", +# "index": "GTCGGAGC", +# "index2": "TTATAACC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400231", +# "index": "TCGTAGTG", +# "index2": "CCAAGTCT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400238", +# "index": "GGAGCGTC", +# "index2": "GCACGGAC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400239", +# "index": "ATGGCATG", +# "index2": "GGTACCTT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 3, +# "sample_id": "L2400240", +# "index": "GCAATGCA", +# "index2": "AACGTTCC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400165", +# "index": "ACGCCTTGTT", +# "index2": "ACGTTCCTTA", +# "override_cycles": "U7N1Y143;I10;I10;U7N1Y143", +# "adapter_read_1": "CTGTCTCTTATACACATCT", +# "adapter_read_2": "CTGTCTCTTATACACATCT" +# }, +# { +# "lane": 4, +# "sample_id": "L2400191", +# "index": "GCACGGAC", +# "index2": "TGCGAGAC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400197", +# "index": "GTCGGAGC", +# "index2": "TTATAACC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400198", +# "index": "CTTGGTAT", +# "index2": "GGACTTGG", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400241", +# "index": "GTTCCAAT", +# "index2": "GCAGAATT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400242", +# "index": "ACCTTGGC", +# "index2": "ATGAGGCC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400249", +# "index": "AGTTTCGA", +# "index2": "CCTACGAT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400250", +# "index": "GAACCTCT", +# "index2": "GTCTGCGC", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400251", +# "index": "GCCCAGTG", +# "index2": "CCGCAATT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400252", +# "index": "TGACAGCT", +# "index2": "CCCGTAGG", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400253", +# "index": "CATCACCC", +# "index2": "ATATAGCA", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400254", +# "index": "CTGGAGTA", +# "index2": "GTTCGGTT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400255", +# "index": "GATCCGGG", +# "index2": "AAGCAGGT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400256", +# "index": "AACACCTG", +# "index2": "CGCATGGG", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# }, +# { +# "lane": 4, +# "sample_id": "L2400257", +# "index": "GTGACGTT", +# "index2": "TCCCAGAT", +# "override_cycles": "Y151;I8N2;I8N2;Y151" +# } +# ], +# "cloud_settings": { +# "generated_version": "0.0.0", +# "cloud_workflow": "ica_workflow_1", +# "bclconvert_pipeline": "urn:ilmn:ica:pipeline:bf93b5cf-cb27-4dfa-846e-acd6eb081aca#BclConvert_v4_2_7" +# }, +# "cloud_data": [ +# { +# "sample_id": "L2400102", +# "library_name": "L2400102_GAATTCGT_TTATGAGT", +# "library_prep_kit_name": "ctTSO" +# }, +# { +# "sample_id": "L2400159", +# "library_name": "L2400159_GAGAATGGTT_TTGCTGCCGA", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400160", +# "library_name": "L2400160_AGAGGCAACC_CCATCATTAG", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400161", +# "library_name": "L2400161_CCATCATTAG_AGAGGCAACC", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400162", +# "library_name": "L2400162_GATAGGCCGA_GCCATGTGCG", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400163", +# "library_name": "L2400163_ATGGTTGACT_AGGACAGGCC", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400164", +# "library_name": "L2400164_TATTGCGCTC_CCTAACACAG", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400165", +# "library_name": "L2400165_ACGCCTTGTT_ACGTTCCTTA", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400166", +# "library_name": "L2400166_TTCTACATAC_TTACAGTTAG", +# "library_prep_kit_name": "ctTSOv2" +# }, +# { +# "sample_id": "L2400191", +# "library_name": "L2400191_GCACGGAC_TGCGAGAC", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400195", +# "library_name": "L2400195_ATGAGGCC_CAATTAAC", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400196", +# "library_name": "L2400196_ACTAAGAT_CCGCGGTT", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400197", +# "library_name": "L2400197_GTCGGAGC_TTATAACC", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400198", +# "library_name": "L2400198_CTTGGTAT_GGACTTGG", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400231", +# "library_name": "L2400231_TCGTAGTG_CCAAGTCT", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400238", +# "library_name": "L2400238_GGAGCGTC_GCACGGAC", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400239", +# "library_name": "L2400239_ATGGCATG_GGTACCTT", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400240", +# "library_name": "L2400240_GCAATGCA_AACGTTCC", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400241", +# "library_name": "L2400241_GTTCCAAT_GCAGAATT", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400242", +# "library_name": "L2400242_ACCTTGGC_ATGAGGCC", +# "library_prep_kit_name": "TsqNano" +# }, +# { +# "sample_id": "L2400249", +# "library_name": "L2400249_AGTTTCGA_CCTACGAT", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400250", +# "library_name": "L2400250_GAACCTCT_GTCTGCGC", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400251", +# "library_name": "L2400251_GCCCAGTG_CCGCAATT", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400252", +# "library_name": "L2400252_TGACAGCT_CCCGTAGG", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400253", +# "library_name": "L2400253_CATCACCC_ATATAGCA", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400254", +# "library_name": "L2400254_CTGGAGTA_GTTCGGTT", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400255", +# "library_name": "L2400255_GATCCGGG_AAGCAGGT", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400256", +# "library_name": "L2400256_AACACCTG_CGCATGGG", +# "library_prep_kit_name": "NebRNA" +# }, +# { +# "sample_id": "L2400257", +# "library_name": "L2400257_GTGACGTT_TCCCAGAT", +# "library_prep_kit_name": "NebRNA" +# } +# ] +# }, +# "library_obj_list": [ +# { +# "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EBXK08WDWB97BSCX1C9", +# "projectId": "PO", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4MPHSX7MRCTTFWJBYTT7", +# "sampleId": "MDX210402", +# "externalSampleId": "ZUHR111121", +# "source": "plasma-serum" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4MNXJSDRR406DAXFZP2N", +# "subjectId": "PM3045106" +# }, +# "libraryId": "L2400102", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "borderline", +# "type": "WGS", +# "assay": "ctTSO", +# "coverage": 50.0 +# }, +# { +# "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4XMDW0FV1YMWHSZZQ4TX", +# "sampleId": "PTC_SCMM1pc2", +# "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# "subjectId": "CMM1pc-10646259ilm" +# }, +# "libraryId": "L2400159", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4XQ071BF3WZN111SNJ2B", +# "sampleId": "PTC_SCMM1pc3", +# "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# "subjectId": "CMM1pc-10646259ilm" +# }, +# "libraryId": "L2400160", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4XRG9NB38N03688M2CCB", +# "sampleId": "PTC_SCMM1pc4", +# "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# "subjectId": "CMM1pc-10646259ilm" +# }, +# "libraryId": "L2400161", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4XWXFANT7P0T3AFXA85G", +# "sampleId": "PTC_SCMM01pc20", +# "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 20ng", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# "subjectId": "CMM0.1pc-10624819" +# }, +# "libraryId": "L2400162", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4XYTSVQVRSBA9M26NSZY", +# "sampleId": "PTC_SCMM01pc15", +# "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 15ng", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# "subjectId": "CMM0.1pc-10624819" +# }, +# "libraryId": "L2400163", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4Y0V0ZBKAE91TDSY0BBB", +# "sampleId": "PTC_SCMM01pc10", +# "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 10ng", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# "subjectId": "CMM0.1pc-10624819" +# }, +# "libraryId": "L2400164", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4Y37JTPEEJSED9BXH8N2", +# "sampleId": "PTC_SCMM01pc5", +# "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 5ng", +# "source": "cfDNA" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# "subjectId": "CMM0.1pc-10624819" +# }, +# "libraryId": "L2400165", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# "projectId": "Testing", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4Y4XK1WX4WCPD6XY8KNM", +# "sampleId": "NTC_v2ctTSO240207", +# "externalSampleId": "negative control", +# "source": "water" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# "subjectId": "negative control" +# }, +# "libraryId": "L2400166", +# "phenotype": "negative-control", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 0.1 +# }, +# { +# "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# "projectId": "CAVATAK", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4ZDAFRK3K3PY33F8XS0W", +# "sampleId": "PRJ240169", +# "externalSampleId": "AUS-006-DRW_C1D1PRE", +# "source": "blood" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# "subjectId": "AUS-006-DRW" +# }, +# "libraryId": "L2400191", +# "phenotype": "normal", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 40.0 +# }, +# { +# "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# "projectId": "CAVATAK", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4ZMETZP255WMFC8TSCYT", +# "sampleId": "PRJ240180", +# "externalSampleId": "AUS-006-DRW_Day0", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# "subjectId": "AUS-006-DRW" +# }, +# "libraryId": "L2400195", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80.0 +# }, +# { +# "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# "projectId": "CAVATAK", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4ZNT47EM37QKMT12JPPJ", +# "sampleId": "PRJ240181", +# "externalSampleId": "AUS-006-DRW_Day33", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# "subjectId": "AUS-006-DRW" +# }, +# "libraryId": "L2400196", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80.0 +# }, +# { +# "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# "projectId": "CAVATAK", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4ZQ76H8P0Q7S618F3BMA", +# "sampleId": "PRJ240182", +# "externalSampleId": "AUS-007-JMA_Day0", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# "subjectId": "AUS-007-JMA" +# }, +# "libraryId": "L2400197", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80.0 +# }, +# { +# "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# "projectId": "CAVATAK", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4ZVAR9NQM55Z2TXCDY9V", +# "sampleId": "PRJ240183", +# "externalSampleId": "AUS-007-JMA_Day15", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# "subjectId": "AUS-007-JMA" +# }, +# "libraryId": "L2400198", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80.0 +# }, +# { +# "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# "projectId": "CUP", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES51T84KVVVSEPYQFGW0EV", +# "sampleId": "PRJ240199", +# "externalSampleId": "DNA188239", +# "source": "FFPE" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# "subjectId": "SN_PMC-141" +# }, +# "libraryId": "L2400231", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "poor", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 100.0 +# }, +# { +# "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# "projectId": "CUP", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES527QKB5Y5RVZWZ8HQX0H", +# "sampleId": "PRJ240643", +# "externalSampleId": "DNA188378", +# "source": "blood" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# "subjectId": "SN_PMC-141" +# }, +# "libraryId": "L2400238", +# "phenotype": "normal", +# "workflow": "clinical", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 40.0 +# }, +# { +# "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# "projectId": "CUP", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES52A5QX0GQ6RB78Z8DGYQ", +# "sampleId": "PRJ240646", +# "externalSampleId": "DNA189922", +# "source": "blood" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# "subjectId": "SN_PMC-145" +# }, +# "libraryId": "L2400239", +# "phenotype": "normal", +# "workflow": "clinical", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 40.0 +# }, +# { +# "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# "projectId": "CUP", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES52BM8BVS3PX47E6FM7D5", +# "sampleId": "PRJ240647", +# "externalSampleId": "DNA189848", +# "source": "FFPE" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# "subjectId": "SN_PMC-145" +# }, +# "libraryId": "L2400240", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "poor", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 100.0 +# }, +# { +# "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# "projectId": "Control", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES52D076FQM5K8128AQ593", +# "sampleId": "NTC_TSqN240226", +# "externalSampleId": "NTC_TSqN240226", +# "source": "water" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# "subjectId": "negative control" +# }, +# "libraryId": "L2400241", +# "phenotype": "negative-control", +# "workflow": "control", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 0.1 +# }, +# { +# "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# "projectId": "Control", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES52EEX67YRYAJS3F5GMJ5", +# "sampleId": "PTC_TSqN240226", +# "externalSampleId": "NA24385-3", +# "source": "cell-line" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4DRJ31Z2H1GJQZGVDXZR", +# "subjectId": "NA24385" +# }, +# "libraryId": "L2400242", +# "phenotype": "normal", +# "workflow": "control", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 15.0 +# }, +# { +# "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# "projectId": "Control", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES52XE661E8V8XTWD02QCK", +# "sampleId": "PTC_NebRNA240226", +# "externalSampleId": "Colo829", +# "source": "cell-line" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4GNVGZSJVTHGVKS9VW7F", +# "subjectId": "Colo829" +# }, +# "libraryId": "L2400249", +# "phenotype": "tumor", +# "workflow": "control", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 1.0 +# }, +# { +# "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# "projectId": "BPOP-retro", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES4FP9WTFBDNGKVG3D9BD4", +# "sampleId": "PRJ240003", +# "externalSampleId": "3-23BCRL057T", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4FNJ2FCAK0RJST0428X0", +# "subjectId": "23BCRL057T" +# }, +# "libraryId": "L2400250", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# "projectId": "BPOP-retro", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES530355YNZ3VHQQQ204PF", +# "sampleId": "PRJ240561", +# "externalSampleId": "4-218-004_Bx", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# "subjectId": "218-004" +# }, +# "libraryId": "L2400251", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# "projectId": "BPOP-retro", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES531H420JM9MG5R4AE1AZ", +# "sampleId": "PRJ240562", +# "externalSampleId": "5-218-004_04", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# "subjectId": "218-004" +# }, +# "libraryId": "L2400252", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# "projectId": "BPOP-retro", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES532ZBHWY3DWY0DWQ223R", +# "sampleId": "PRJ240566", +# "externalSampleId": "9-218-007_Bx", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# "subjectId": "218-007" +# }, +# "libraryId": "L2400253", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# "projectId": "BPOP-retro", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES534BX7B89X5EKSCFRDDZ", +# "sampleId": "PRJ240567", +# "externalSampleId": "10-218-007_04", +# "source": "tissue" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# "subjectId": "218-007" +# }, +# "libraryId": "L2400254", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "borderline", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# "projectId": "CUP", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES535VGG93023KWAFMWGH4", +# "sampleId": "PRJ240200", +# "externalSampleId": "RNA036747", +# "source": "FFPE" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# "subjectId": "SN_PMC-141" +# }, +# "libraryId": "L2400255", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "very-poor", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# "projectId": "CUP", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES5379C40K08YG3JDMZJN7", +# "sampleId": "PRJ240648", +# "externalSampleId": "RNA037080", +# "source": "FFPE" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# "subjectId": "SN_PMC-145" +# }, +# "libraryId": "L2400256", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "very-poor", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6.0 +# }, +# { +# "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP", +# "projectSet": [ +# { +# "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# "projectId": "Control", +# "name": None, +# "description": None +# } +# ], +# "sample": { +# "orcabusId": "smp.01J8ES538PFF6MQQ35PTC00JAY", +# "sampleId": "NTC_NebRNA240226", +# "externalSampleId": "NTC_NebRNA240226", +# "source": "water" +# }, +# "subject": { +# "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# "subjectId": "negative control" +# }, +# "libraryId": "L2400257", +# "phenotype": "negative-control", +# "workflow": "control", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 0.1 +# } +# ], +# }, +# None +# ), +# indent=2 +# ) +# ) +# # { +# # "start_samplesheet_shower_event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5" +# # }, +# # "complete_samplesheet_shower_event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5" +# # }, +# # "project_event_data_list": [ +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "project": { +# # "orcabusId": "prj.01J8ES4EBXK08WDWB97BSCX1C9", +# # "projectId": "PO", +# # "name": null, +# # "description": null +# # }, +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M", +# # "libraryId": "L2400102" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "project": { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # }, +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# # "libraryId": "L2400159" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# # "libraryId": "L2400160" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# # "libraryId": "L2400161" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# # "libraryId": "L2400162" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# # "libraryId": "L2400163" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# # "libraryId": "L2400164" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# # "libraryId": "L2400165" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# # "libraryId": "L2400166" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "project": { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # }, +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# # "libraryId": "L2400191" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# # "libraryId": "L2400195" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# # "libraryId": "L2400196" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ", +# # "libraryId": "L2400197" +# # }, +# # { +# # "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# # "libraryId": "L2400198" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "project": { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # }, +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED", +# # "libraryId": "L2400231" +# # }, +# # { +# # "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0", +# # "libraryId": "L2400238" +# # }, +# # { +# # "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# # "libraryId": "L2400239" +# # }, +# # { +# # "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83", +# # "libraryId": "L2400240" +# # }, +# # { +# # "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# # "libraryId": "L2400255" +# # }, +# # { +# # "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9", +# # "libraryId": "L2400256" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "project": { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # }, +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# # "libraryId": "L2400241" +# # }, +# # { +# # "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# # "libraryId": "L2400242" +# # }, +# # { +# # "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T", +# # "libraryId": "L2400249" +# # }, +# # { +# # "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP", +# # "libraryId": "L2400257" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "project": { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # }, +# # "librarySet": [ +# # { +# # "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# # "libraryId": "L2400250" +# # }, +# # { +# # "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV", +# # "libraryId": "L2400251" +# # }, +# # { +# # "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# # "libraryId": "L2400252" +# # }, +# # { +# # "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# # "libraryId": "L2400253" +# # }, +# # { +# # "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# # "libraryId": "L2400254" +# # } +# # ] +# # } +# # } +# # ], +# # "library_event_data_list": [ +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M", +# # "libraryId": "L2400102", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "borderline", +# # "type": "WGS", +# # "assay": "ctTSO", +# # "coverage": 50.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4MPHSX7MRCTTFWJBYTT7", +# # "sampleId": "MDX210402", +# # "externalSampleId": "ZUHR111121", +# # "source": "plasma-serum" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4MNXJSDRR406DAXFZP2N", +# # "subjectId": "PM3045106" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EBXK08WDWB97BSCX1C9", +# # "projectId": "PO", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400102", +# # "index": "GAATTCGT", +# # "index2": "TTATGAGT", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I8N2;I8N2;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GAATTCGT.TTATGAGT.1.240424_A01052_0193_BH7JMMDRX5.L2400102" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# # "libraryId": "L2400159", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4XMDW0FV1YMWHSZZQ4TX", +# # "sampleId": "PTC_SCMM1pc2", +# # "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "subjectId": "CMM1pc-10646259ilm" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400159", +# # "index": "GAGAATGGTT", +# # "index2": "TTGCTGCCGA", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GAGAATGGTT.TTGCTGCCGA.1.240424_A01052_0193_BH7JMMDRX5.L2400159" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# # "libraryId": "L2400160", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4XQ071BF3WZN111SNJ2B", +# # "sampleId": "PTC_SCMM1pc3", +# # "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "subjectId": "CMM1pc-10646259ilm" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400160", +# # "index": "AGAGGCAACC", +# # "index2": "CCATCATTAG", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "AGAGGCAACC.CCATCATTAG.1.240424_A01052_0193_BH7JMMDRX5.L2400160" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# # "libraryId": "L2400161", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4XRG9NB38N03688M2CCB", +# # "sampleId": "PTC_SCMM1pc4", +# # "externalSampleId": "SSq-CompMM-1pc-10646259ilm", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "subjectId": "CMM1pc-10646259ilm" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400161", +# # "index": "CCATCATTAG", +# # "index2": "AGAGGCAACC", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "CCATCATTAG.AGAGGCAACC.1.240424_A01052_0193_BH7JMMDRX5.L2400161" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# # "libraryId": "L2400162", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4XWXFANT7P0T3AFXA85G", +# # "sampleId": "PTC_SCMM01pc20", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 20ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400162", +# # "index": "GATAGGCCGA", +# # "index2": "GCCATGTGCG", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GATAGGCCGA.GCCATGTGCG.1.240424_A01052_0193_BH7JMMDRX5.L2400162" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# # "libraryId": "L2400163", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4XYTSVQVRSBA9M26NSZY", +# # "sampleId": "PTC_SCMM01pc15", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 15ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400163", +# # "index": "ATGGTTGACT", +# # "index2": "AGGACAGGCC", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "ATGGTTGACT.AGGACAGGCC.1.240424_A01052_0193_BH7JMMDRX5.L2400163" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# # "libraryId": "L2400164", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4Y0V0ZBKAE91TDSY0BBB", +# # "sampleId": "PTC_SCMM01pc10", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 10ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400164", +# # "index": "TATTGCGCTC", +# # "index2": "CCTAACACAG", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "TATTGCGCTC.CCTAACACAG.1.240424_A01052_0193_BH7JMMDRX5.L2400164" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# # "libraryId": "L2400165", +# # "phenotype": "tumor", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 38.6 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4Y37JTPEEJSED9BXH8N2", +# # "sampleId": "PTC_SCMM01pc5", +# # "externalSampleId": "SSq-CompMM-0.1pc-10624819 - 5ng", +# # "source": "cfDNA" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "subjectId": "CMM0.1pc-10624819" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400165", +# # "index": "ACGCCTTGTT", +# # "index2": "ACGTTCCTTA", +# # "lane": 4, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "ACGCCTTGTT.ACGTTCCTTA.4.240424_A01052_0193_BH7JMMDRX5.L2400165" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# # "libraryId": "L2400166", +# # "phenotype": "negative-control", +# # "workflow": "manual", +# # "quality": "good", +# # "type": "ctDNA", +# # "assay": "ctTSOv2", +# # "coverage": 0.1 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4Y4XK1WX4WCPD6XY8KNM", +# # "sampleId": "NTC_v2ctTSO240207", +# # "externalSampleId": "negative control", +# # "source": "water" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "subjectId": "negative control" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1", +# # "projectId": "Testing", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400166", +# # "index": "TTCTACATAC", +# # "index2": "TTACAGTTAG", +# # "lane": 1, +# # "overrideCycles": "U7N1Y143;I10;I10;U7N1Y143" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "TTCTACATAC.TTACAGTTAG.1.240424_A01052_0193_BH7JMMDRX5.L2400166" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# # "libraryId": "L2400191", +# # "phenotype": "normal", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 40.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZDAFRK3K3PY33F8XS0W", +# # "sampleId": "PRJ240169", +# # "externalSampleId": "AUS-006-DRW_C1D1PRE", +# # "source": "blood" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "subjectId": "AUS-006-DRW" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400191", +# # "index": "GCACGGAC", +# # "index2": "TGCGAGAC", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GCACGGAC.TGCGAGAC.4.240424_A01052_0193_BH7JMMDRX5.L2400191" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# # "libraryId": "L2400195", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 80.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZMETZP255WMFC8TSCYT", +# # "sampleId": "PRJ240180", +# # "externalSampleId": "AUS-006-DRW_Day0", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "subjectId": "AUS-006-DRW" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400195", +# # "index": "ATGAGGCC", +# # "index2": "CAATTAAC", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400195", +# # "index": "ATGAGGCC", +# # "index2": "CAATTAAC", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "ATGAGGCC.CAATTAAC.2.240424_A01052_0193_BH7JMMDRX5.L2400195" +# # }, +# # { +# # "fastqListRowRgid": "ATGAGGCC.CAATTAAC.3.240424_A01052_0193_BH7JMMDRX5.L2400195" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# # "libraryId": "L2400196", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 80.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZNT47EM37QKMT12JPPJ", +# # "sampleId": "PRJ240181", +# # "externalSampleId": "AUS-006-DRW_Day33", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "subjectId": "AUS-006-DRW" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400196", +# # "index": "ACTAAGAT", +# # "index2": "CCGCGGTT", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400196", +# # "index": "ACTAAGAT", +# # "index2": "CCGCGGTT", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "ACTAAGAT.CCGCGGTT.2.240424_A01052_0193_BH7JMMDRX5.L2400196" +# # }, +# # { +# # "fastqListRowRgid": "ACTAAGAT.CCGCGGTT.3.240424_A01052_0193_BH7JMMDRX5.L2400196" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ", +# # "libraryId": "L2400197", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 80.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZQ76H8P0Q7S618F3BMA", +# # "sampleId": "PRJ240182", +# # "externalSampleId": "AUS-007-JMA_Day0", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# # "subjectId": "AUS-007-JMA" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400197", +# # "index": "GTCGGAGC", +# # "index2": "TTATAACC", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400197", +# # "index": "GTCGGAGC", +# # "index2": "TTATAACC", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400197", +# # "index": "GTCGGAGC", +# # "index2": "TTATAACC", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GTCGGAGC.TTATAACC.2.240424_A01052_0193_BH7JMMDRX5.L2400197" +# # }, +# # { +# # "fastqListRowRgid": "GTCGGAGC.TTATAACC.3.240424_A01052_0193_BH7JMMDRX5.L2400197" +# # }, +# # { +# # "fastqListRowRgid": "GTCGGAGC.TTATAACC.4.240424_A01052_0193_BH7JMMDRX5.L2400197" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# # "libraryId": "L2400198", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 80.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4ZVAR9NQM55Z2TXCDY9V", +# # "sampleId": "PRJ240183", +# # "externalSampleId": "AUS-007-JMA_Day15", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# # "subjectId": "AUS-007-JMA" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "projectId": "CAVATAK", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400198", +# # "index": "CTTGGTAT", +# # "index2": "GGACTTGG", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "CTTGGTAT.GGACTTGG.4.240424_A01052_0193_BH7JMMDRX5.L2400198" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED", +# # "libraryId": "L2400231", +# # "phenotype": "tumor", +# # "workflow": "clinical", +# # "quality": "poor", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 100.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES51T84KVVVSEPYQFGW0EV", +# # "sampleId": "PRJ240199", +# # "externalSampleId": "DNA188239", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "subjectId": "SN_PMC-141" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400231", +# # "index": "TCGTAGTG", +# # "index2": "CCAAGTCT", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400231", +# # "index": "TCGTAGTG", +# # "index2": "CCAAGTCT", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "TCGTAGTG.CCAAGTCT.2.240424_A01052_0193_BH7JMMDRX5.L2400231" +# # }, +# # { +# # "fastqListRowRgid": "TCGTAGTG.CCAAGTCT.3.240424_A01052_0193_BH7JMMDRX5.L2400231" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0", +# # "libraryId": "L2400238", +# # "phenotype": "normal", +# # "workflow": "clinical", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 40.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES527QKB5Y5RVZWZ8HQX0H", +# # "sampleId": "PRJ240643", +# # "externalSampleId": "DNA188378", +# # "source": "blood" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "subjectId": "SN_PMC-141" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400238", +# # "index": "GGAGCGTC", +# # "index2": "GCACGGAC", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400238", +# # "index": "GGAGCGTC", +# # "index2": "GCACGGAC", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GGAGCGTC.GCACGGAC.2.240424_A01052_0193_BH7JMMDRX5.L2400238" +# # }, +# # { +# # "fastqListRowRgid": "GGAGCGTC.GCACGGAC.3.240424_A01052_0193_BH7JMMDRX5.L2400238" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# # "libraryId": "L2400239", +# # "phenotype": "normal", +# # "workflow": "clinical", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 40.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES52A5QX0GQ6RB78Z8DGYQ", +# # "sampleId": "PRJ240646", +# # "externalSampleId": "DNA189922", +# # "source": "blood" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "subjectId": "SN_PMC-145" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400239", +# # "index": "ATGGCATG", +# # "index2": "GGTACCTT", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400239", +# # "index": "ATGGCATG", +# # "index2": "GGTACCTT", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "ATGGCATG.GGTACCTT.2.240424_A01052_0193_BH7JMMDRX5.L2400239" +# # }, +# # { +# # "fastqListRowRgid": "ATGGCATG.GGTACCTT.3.240424_A01052_0193_BH7JMMDRX5.L2400239" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83", +# # "libraryId": "L2400240", +# # "phenotype": "tumor", +# # "workflow": "clinical", +# # "quality": "poor", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 100.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES52BM8BVS3PX47E6FM7D5", +# # "sampleId": "PRJ240647", +# # "externalSampleId": "DNA189848", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "subjectId": "SN_PMC-145" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400240", +# # "index": "GCAATGCA", +# # "index2": "AACGTTCC", +# # "lane": 2, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # }, +# # { +# # "sampleId": "L2400240", +# # "index": "GCAATGCA", +# # "index2": "AACGTTCC", +# # "lane": 3, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GCAATGCA.AACGTTCC.2.240424_A01052_0193_BH7JMMDRX5.L2400240" +# # }, +# # { +# # "fastqListRowRgid": "GCAATGCA.AACGTTCC.3.240424_A01052_0193_BH7JMMDRX5.L2400240" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# # "libraryId": "L2400241", +# # "phenotype": "negative-control", +# # "workflow": "control", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 0.1 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES52D076FQM5K8128AQ593", +# # "sampleId": "NTC_TSqN240226", +# # "externalSampleId": "NTC_TSqN240226", +# # "source": "water" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "subjectId": "negative control" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400241", +# # "index": "GTTCCAAT", +# # "index2": "GCAGAATT", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GTTCCAAT.GCAGAATT.4.240424_A01052_0193_BH7JMMDRX5.L2400241" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# # "libraryId": "L2400242", +# # "phenotype": "normal", +# # "workflow": "control", +# # "quality": "good", +# # "type": "WGS", +# # "assay": "TsqNano", +# # "coverage": 15.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES52EEX67YRYAJS3F5GMJ5", +# # "sampleId": "PTC_TSqN240226", +# # "externalSampleId": "NA24385-3", +# # "source": "cell-line" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DRJ31Z2H1GJQZGVDXZR", +# # "subjectId": "NA24385" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400242", +# # "index": "ACCTTGGC", +# # "index2": "ATGAGGCC", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "ACCTTGGC.ATGAGGCC.4.240424_A01052_0193_BH7JMMDRX5.L2400242" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T", +# # "libraryId": "L2400249", +# # "phenotype": "tumor", +# # "workflow": "control", +# # "quality": "good", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 1.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES52XE661E8V8XTWD02QCK", +# # "sampleId": "PTC_NebRNA240226", +# # "externalSampleId": "Colo829", +# # "source": "cell-line" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4GNVGZSJVTHGVKS9VW7F", +# # "subjectId": "Colo829" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400249", +# # "index": "AGTTTCGA", +# # "index2": "CCTACGAT", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "AGTTTCGA.CCTACGAT.4.240424_A01052_0193_BH7JMMDRX5.L2400249" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# # "libraryId": "L2400250", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES4FP9WTFBDNGKVG3D9BD4", +# # "sampleId": "PRJ240003", +# # "externalSampleId": "3-23BCRL057T", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4FNJ2FCAK0RJST0428X0", +# # "subjectId": "23BCRL057T" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400250", +# # "index": "GAACCTCT", +# # "index2": "GTCTGCGC", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GAACCTCT.GTCTGCGC.4.240424_A01052_0193_BH7JMMDRX5.L2400250" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV", +# # "libraryId": "L2400251", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES530355YNZ3VHQQQ204PF", +# # "sampleId": "PRJ240561", +# # "externalSampleId": "4-218-004_Bx", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# # "subjectId": "218-004" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400251", +# # "index": "GCCCAGTG", +# # "index2": "CCGCAATT", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GCCCAGTG.CCGCAATT.4.240424_A01052_0193_BH7JMMDRX5.L2400251" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# # "libraryId": "L2400252", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES531H420JM9MG5R4AE1AZ", +# # "sampleId": "PRJ240562", +# # "externalSampleId": "5-218-004_04", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# # "subjectId": "218-004" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400252", +# # "index": "TGACAGCT", +# # "index2": "CCCGTAGG", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "TGACAGCT.CCCGTAGG.4.240424_A01052_0193_BH7JMMDRX5.L2400252" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# # "libraryId": "L2400253", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "good", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES532ZBHWY3DWY0DWQ223R", +# # "sampleId": "PRJ240566", +# # "externalSampleId": "9-218-007_Bx", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# # "subjectId": "218-007" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400253", +# # "index": "CATCACCC", +# # "index2": "ATATAGCA", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "CATCACCC.ATATAGCA.4.240424_A01052_0193_BH7JMMDRX5.L2400253" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# # "libraryId": "L2400254", +# # "phenotype": "tumor", +# # "workflow": "research", +# # "quality": "borderline", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES534BX7B89X5EKSCFRDDZ", +# # "sampleId": "PRJ240567", +# # "externalSampleId": "10-218-007_04", +# # "source": "tissue" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# # "subjectId": "218-007" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "projectId": "BPOP-retro", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400254", +# # "index": "CTGGAGTA", +# # "index2": "GTTCGGTT", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "CTGGAGTA.GTTCGGTT.4.240424_A01052_0193_BH7JMMDRX5.L2400254" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# # "libraryId": "L2400255", +# # "phenotype": "tumor", +# # "workflow": "clinical", +# # "quality": "very-poor", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES535VGG93023KWAFMWGH4", +# # "sampleId": "PRJ240200", +# # "externalSampleId": "RNA036747", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "subjectId": "SN_PMC-141" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400255", +# # "index": "GATCCGGG", +# # "index2": "AAGCAGGT", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GATCCGGG.AAGCAGGT.4.240424_A01052_0193_BH7JMMDRX5.L2400255" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9", +# # "libraryId": "L2400256", +# # "phenotype": "tumor", +# # "workflow": "clinical", +# # "quality": "very-poor", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 6.0 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES5379C40K08YG3JDMZJN7", +# # "sampleId": "PRJ240648", +# # "externalSampleId": "RNA037080", +# # "source": "FFPE" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "subjectId": "SN_PMC-145" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "projectId": "CUP", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400256", +# # "index": "AACACCTG", +# # "index2": "CGCATGGG", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "AACACCTG.CGCATGGG.4.240424_A01052_0193_BH7JMMDRX5.L2400256" +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240424_A01052_0193_BH7JMMDRX5", +# # "library": { +# # "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP", +# # "libraryId": "L2400257", +# # "phenotype": "negative-control", +# # "workflow": "control", +# # "quality": "good", +# # "type": "WTS", +# # "assay": "NebRNA", +# # "coverage": 0.1 +# # }, +# # "sample": { +# # "orcabusId": "smp.01J8ES538PFF6MQQ35PTC00JAY", +# # "sampleId": "NTC_NebRNA240226", +# # "externalSampleId": "NTC_NebRNA240226", +# # "source": "water" +# # }, +# # "subject": { +# # "orcabusId": "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "subjectId": "negative control" +# # }, +# # "projectSet": [ +# # { +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "projectId": "Control", +# # "name": null, +# # "description": null +# # } +# # ], +# # "bclconvertDataRows": [ +# # { +# # "sampleId": "L2400257", +# # "index": "GTGACGTT", +# # "index2": "TCCCAGAT", +# # "lane": 4, +# # "overrideCycles": "Y151;I8N2;I8N2;Y151" +# # } +# # ], +# # "fastqListRows": [ +# # { +# # "fastqListRowRgid": "GTGACGTT.TCCCAGAT.4.240424_A01052_0193_BH7JMMDRX5.L2400257" +# # } +# # ] +# # } +# # } +# # ], +# # "library_set": [ +# # "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# # "lib.01J8ES530H895X4WA3NQ6CY2QV", +# # "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# # "lib.01J8ES4MPZ5B201R50K42XXM4M", +# # "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# # "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# # "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# # "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# # "lib.01J8ES52XYMVGRB1Q458THNG4T", +# # "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# # "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# # "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# # "lib.01J8ES4ZST489C712CG3R9NQSQ", +# # "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# # "lib.01J8ES537S0W1AX9PQPST13GM9", +# # "lib.01J8ES52C3N585BGGY4VNXHC83", +# # "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# # "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# # "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# # "lib.01J8ES51V0RSVT6C7WQR72QQED", +# # "lib.01J8ES52889Q8826P5SH9HDPP0", +# # "lib.01J8ES5395KETT9T2NJSVNDKNP", +# # "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# # "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# # "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# # "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# # "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# # "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# # "lib.01J8ES4XQG3MPBW94TTVT4STVG" +# # ], +# # "subject_set": [ +# # "sbj.01J8ES4XKHKNQ1NF8EKKACZ032", +# # "sbj.01J8ES4ZEQ3FVD6DDVEG8MW60Q", +# # "sbj.01J8ES51S87R4EJ61QJ0DMDYWZ", +# # "sbj.01J8ES4DRJ31Z2H1GJQZGVDXZR", +# # "sbj.01J8ES4MNXJSDRR406DAXFZP2N", +# # "sbj.01J8ES4GNVGZSJVTHGVKS9VW7F", +# # "sbj.01J8ES51WC4GV5YDJNTMAK2YY1", +# # "sbj.01J8ES4ZCKNW6QKP006SYNZ5RA", +# # "sbj.01J8ES522WN7YPZS1Z9NGSPNDA", +# # "sbj.01J8ES4DFMNF0SX6P8P8Y9J6K1", +# # "sbj.01J8ES4XW2TXGEJBQWCVMRZRTS", +# # "sbj.01J8ES529GSPBV64SESK9SWD76", +# # "sbj.01J8ES4FNJ2FCAK0RJST0428X0" +# # ], +# # "project_set": [ +# # "prj.01J8ES4FC6DVW20AR33FBX2SA8", +# # "prj.01J8ES4FH3XMPZQNDJ9J000BXX", +# # "prj.01J8ES4EZAA5YMHX82664GJQB3", +# # "prj.01J8ES4EBXK08WDWB97BSCX1C9", +# # "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B", +# # "prj.01J8ES4XMWD0DH7MDRNER5TZS1" +# # ], +# # "sample_set": [ +# # "smp.01J8ES5379C40K08YG3JDMZJN7", +# # "smp.01J8ES4XWXFANT7P0T3AFXA85G", +# # "smp.01J8ES4Y0V0ZBKAE91TDSY0BBB", +# # "smp.01J8ES535VGG93023KWAFMWGH4", +# # "smp.01J8ES4FP9WTFBDNGKVG3D9BD4", +# # "smp.01J8ES52EEX67YRYAJS3F5GMJ5", +# # "smp.01J8ES4XYTSVQVRSBA9M26NSZY", +# # "smp.01J8ES4ZMETZP255WMFC8TSCYT", +# # "smp.01J8ES51T84KVVVSEPYQFGW0EV", +# # "smp.01J8ES538PFF6MQQ35PTC00JAY", +# # "smp.01J8ES4XMDW0FV1YMWHSZZQ4TX", +# # "smp.01J8ES52D076FQM5K8128AQ593", +# # "smp.01J8ES4Y37JTPEEJSED9BXH8N2", +# # "smp.01J8ES4ZQ76H8P0Q7S618F3BMA", +# # "smp.01J8ES4Y4XK1WX4WCPD6XY8KNM", +# # "smp.01J8ES531H420JM9MG5R4AE1AZ", +# # "smp.01J8ES534BX7B89X5EKSCFRDDZ", +# # "smp.01J8ES4ZNT47EM37QKMT12JPPJ", +# # "smp.01J8ES4MPHSX7MRCTTFWJBYTT7", +# # "smp.01J8ES52A5QX0GQ6RB78Z8DGYQ", +# # "smp.01J8ES532ZBHWY3DWY0DWQ223R", +# # "smp.01J8ES4ZVAR9NQM55Z2TXCDY9V", +# # "smp.01J8ES4XRG9NB38N03688M2CCB", +# # "smp.01J8ES527QKB5Y5RVZWZ8HQX0H", +# # "smp.01J8ES530355YNZ3VHQQQ204PF", +# # "smp.01J8ES4ZDAFRK3K3PY33F8XS0W", +# # "smp.01J8ES52BM8BVS3PX47E6FM7D5", +# # "smp.01J8ES52XE661E8V8XTWD02QCK", +# # "smp.01J8ES4XQ071BF3WZN111SNJ2B" +# # ] +# # } \ No newline at end of file diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/requirements.txt b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/requirements.txt new file mode 100644 index 000000000..44c70391c --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/lambdas/generate_event_data_objects_py/requirements.txt @@ -0,0 +1,2 @@ +more-itertools>=10.5.0 +pandas>=2.2.3 \ No newline at end of file diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/step_functions_templates/samplesheet_event_shower_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/step_functions_templates/samplesheet_event_shower_sfn_template.asl.json index 43c6d8341..ff4bf0d96 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/step_functions_templates/samplesheet_event_shower_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_1/samplesheet-event-shower/step_functions_templates/samplesheet_event_shower_sfn_template.asl.json @@ -37,9 +37,9 @@ "ResultSelector": { "samplesheet.$": "$.Payload.decompressed_dict" }, - "Next": "Get Library / Subject Map from SampleSheet" + "Next": "Get Library Map from SampleSheet" }, - "Get Library / Subject Map from SampleSheet": { + "Get Library Map from SampleSheet": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { @@ -63,9 +63,7 @@ ], "ResultPath": "$.get_subject_library_map_from_samplesheet", "ResultSelector": { - "library_obj_list.$": "$.Payload.library_obj_list", - "specimen_obj_list.$": "$.Payload.specimen_obj_list", - "subject_obj_list.$": "$.Payload.subject_obj_list" + "library_obj_list.$": "$.Payload.library_obj_list" }, "Next": "Save SampleSheet and Get Subject / Library Map Objects" }, @@ -84,9 +82,7 @@ "Payload": { "instrument_run_id.$": "$.inputs.payload.data.instrumentRunId", "samplesheet.$": "$.decompress_samplesheet_step.samplesheet", - "library_obj_list.$": "$.get_subject_library_map_from_samplesheet.library_obj_list", - "specimen_obj_list.$": "$.get_subject_library_map_from_samplesheet.specimen_obj_list", - "subject_obj_list.$": "$.get_subject_library_map_from_samplesheet.subject_obj_list" + "library_obj_list.$": "$.get_subject_library_map_from_samplesheet.library_obj_list" } }, "Retry": [ @@ -105,35 +101,51 @@ "ResultSelector": { "start_samplesheet_shower_event_data.$": "$.Payload.start_samplesheet_shower_event_data", "complete_samplesheet_shower_event_data.$": "$.Payload.complete_samplesheet_shower_event_data", - "subject_event_data_list.$": "$.Payload.subject_event_data_list", "project_event_data_list.$": "$.Payload.project_event_data_list", - "library_event_data_list.$": "$.Payload.library_event_data_list" + "library_event_data_list.$": "$.Payload.library_event_data_list", + "library_set.$": "$.Payload.library_set", + "subject_set.$": "$.Payload.subject_set", + "project_set.$": "$.Payload.project_set", + "sample_set.$": "$.Payload.sample_set" }, "ResultPath": "$.generate_event_objects_step", - "End": true - } - } - }, - { - "StartAt": "Register SampleSheet by RunID (Instrument Run DB)", - "States": { - "Register SampleSheet by RunID (Instrument Run DB)": { + "Next": "Register Instrument Run DB" + }, + "Register Instrument Run DB": { "Type": "Task", "Resource": "arn:aws:states:::dynamodb:putItem", "Parameters": { "TableName": "${__table_name__}", "Item": { "id.$": "$.inputs.payload.data.instrumentRunId", - "id_type": "${__samplesheet_table_partition_name__}", + "id_type": "${__instrument_run_table_partition_name__}", "samplesheet_dict": { "S.$": "States.JsonToString($.decompress_samplesheet_step.samplesheet)" }, - "library_ids_set": { - "SS.$": "$.get_subject_library_map_from_samplesheet.library_obj_list[*].libraryId" + "library_set": { + "SS.$": "$.generate_event_objects_step.library_set" + }, + "subject_set": { + "SS.$": "$.generate_event_objects_step.sample_set" + }, + "project_set": { + "SS.$": "$.generate_event_objects_step.project_set" + }, + "sample_set": { + "SS.$": "$.generate_event_objects_step.sample_set" } } }, - "ResultPath": null, + "End": true, + "ResultPath": null + } + } + }, + { + "StartAt": "Pass", + "States": { + "Pass": { + "Type": "Pass", "End": true } } @@ -142,7 +154,6 @@ "ResultSelector": { "start_samplesheet_shower_event_data.$": "$.[0].generate_event_objects_step.start_samplesheet_shower_event_data", "project_event_data_list.$": "$.[0].generate_event_objects_step.project_event_data_list", - "subject_event_data_list.$": "$.[0].generate_event_objects_step.subject_event_data_list", "library_event_data_list.$": "$.[0].generate_event_objects_step.library_event_data_list", "complete_samplesheet_shower_event_data.$": "$.[0].generate_event_objects_step.complete_samplesheet_shower_event_data" }, @@ -181,6 +192,7 @@ "ItemsPath": "$.generate_event_objects_step.project_event_data_list", "ItemSelector": { "project_event_data.$": "$$.Map.Item.Value.event_data", + "project_orcabus_id.$": "$$.Map.Item.Value.event_data.project.orcabusId", "instrument_run_id.$": "$.inputs.payload.data.instrumentRunId" }, "ItemProcessor": { @@ -195,7 +207,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "States.Format('{}__{}', $.project_event_data.projectName, $.project_event_data.projectOwner)", + "id.$": "$.project_orcabus_id", "id_type": "${__project_table_partition_name__}" } }, @@ -225,17 +237,16 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id": { - "S.$": "States.Format('{}__{}', $.project_event_data.projectName, $.project_event_data.projectOwner)" - }, - "id_type": { - "S": "${__project_table_partition_name__}" - } + "id.$": "$.project_orcabus_id", + "id_type": "${__project_table_partition_name__}" }, - "UpdateExpression": "ADD instrument_run_id_set :instrument_run_id_set", + "UpdateExpression": "ADD instrument_run_id_set :instrument_run_id_set, library_set :library_set", "ExpressionAttributeValues": { ":instrument_run_id_set": { "SS.$": "States.Array($.instrument_run_id)" + }, + ":library_set": { + "SS.$": "$.project_event_data.librarySet[*].orcabusId" } } }, @@ -248,20 +259,16 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id": { - "S.$": "States.Format('{}__{}', $.project_event_data.projectName, $.project_event_data.projectOwner)" - }, - "id_type": { - "S": "${__project_table_partition_name__}" - }, - "project_name": { - "S.$": "$.project_event_data.projectName" - }, - "project_owner": { - "S.$": "$.project_event_data.projectOwner" + "id.$": "$.project_orcabus_id", + "id_type": "${__project_table_partition_name__}", + "project_obj": { + "S.$": "States.JsonToString($.project_event_data.project)" }, "instrument_run_id_set": { "SS.$": "States.Array($.instrument_run_id)" + }, + "library_set": { + "SS.$": "$.project_event_data.librarySet[*].orcabusId" } } }, @@ -298,129 +305,6 @@ } }, "ResultPath": null, - "Next": "Push Subject Events" - }, - "Push Subject Events": { - "Type": "Map", - "ItemsPath": "$.generate_event_objects_step.subject_event_data_list", - "ItemSelector": { - "subject_id.$": "$$.Map.Item.Value.event_data.subject.subjectId", - "subject_orcabus_id.$": "$$.Map.Item.Value.event_data.subject.orcabusId", - "subject_event_data.$": "$$.Map.Item.Value.event_data", - "instrument_run_id.$": "$$.Map.Item.Value.event_data.instrumentRunId" - }, - "ItemProcessor": { - "ProcessorConfig": { - "Mode": "INLINE" - }, - "StartAt": "Get Subject (Instrument Run DB)", - "States": { - "Get Subject (Instrument Run DB)": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.subject_id", - "id_type": "${__subject_table_partition_name__}" - } - }, - "Next": "Check if Subject in Instrument Run DB", - "ResultPath": "$.get_subject_from_db_step", - "ResultSelector": { - "db_response.$": "$" - } - }, - "Check if Subject in Instrument Run DB": { - "Type": "Choice", - "Choices": [ - { - "Not": { - "Variable": "$.get_subject_from_db_step.db_response.Item", - "IsPresent": true - }, - "Next": "Add Instrument Run ID to Subject (Instrument Run DB)", - "Comment": "New Subject" - } - ], - "Default": "Append Instrument Run ID to Subject (Instrument Run DB)" - }, - "Add Instrument Run ID to Subject (Instrument Run DB)": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id": { - "S.$": "$.subject_id" - }, - "id_type": { - "S": "${__subject_table_partition_name__}" - }, - "orcabus_id": { - "S.$": "$.subject_orcabus_id" - }, - "subject_id": { - "S.$": "$.subject_id" - }, - "instrument_run_id_set": { - "SS.$": "States.Array($.instrument_run_id)" - } - } - }, - "Next": "Wait Subject DB Sync", - "ResultPath": null - }, - "Append Instrument Run ID to Subject (Instrument Run DB)": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.subject_id", - "id_type": "${__subject_table_partition_name__}" - }, - "UpdateExpression": "ADD instrument_run_id_set :instrument_run_id_set", - "ExpressionAttributeValues": { - ":instrument_run_id_set": { - "SS.$": "States.Array($.instrument_run_id)" - } - } - }, - "Next": "Wait Subject DB Sync", - "ResultPath": null - }, - "Wait Subject DB Sync": { - "Type": "Wait", - "Seconds": 1, - "Next": "Subject In SampleSheet" - }, - "Subject In SampleSheet": { - "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", - "Parameters": { - "Entries": [ - { - "Detail": { - "timestamp.$": "$$.Execution.StartTime", - "status": "${__subject_in_samplesheet_status__}", - "payload": { - "version": "${__subject_in_samplesheet_payload_version__}", - "data.$": "$.subject_event_data" - } - }, - "DetailType": "${__subject_in_samplesheet_detail_type__}", - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}" - } - ] - }, - "End": true, - "ResultPath": null - } - } - }, - "ResultPath": null, "Next": "Push Library Events" }, "Push Library Events": { @@ -444,7 +328,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_table_partition_name__}" } }, @@ -493,23 +377,13 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id": { - "S.$": "$.library_id" - }, - "id_type": { - "S": "${__library_table_partition_name__}" - }, - "orcabus_id": { - "S.$": "$.library_orcabus_id" - }, + "id.$": "$.library_orcabus_id", + "id_type": "${__library_table_partition_name__}", "library_id": { - "S.$": "$.library_event_data.library.libraryId" - }, - "project_name": { - "S.$": "$.library_event_data.library.projectName" + "S.$": "$.library_id" }, - "project_owner": { - "S.$": "$.library_event_data.library.projectOwner" + "library_obj": { + "S.$": "States.JsonToString($.library_event_data.library)" }, "instrument_run_id_set": { "SS.$": "States.Array($.instrument_run_id)" diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/index.ts index 01cc523e2..2b1b03bbf 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/index.ts @@ -30,7 +30,7 @@ export class NewFastqListRowsEventShowerConstruct extends Construct { // Tables tablePartition: { fastqListRowsByInstrumentRun: 'fastqlistrows_by_instrument_run', - samplesheetByInstrumentRun: 'samplesheet_by_instrument_run', + instrumentRun: 'instrument_run', subject: 'subject', library: 'library', project: 'project', @@ -143,8 +143,10 @@ export class NewFastqListRowsEventShowerConstruct extends Construct { this.newFastqListRowsEventShowerMap.tablePartition.fastqListRowsByInstrumentRun, __library_table_partition_name__: this.newFastqListRowsEventShowerMap.tablePartition.library, - __samplesheet_table_partition_name__: - this.newFastqListRowsEventShowerMap.tablePartition.samplesheetByInstrumentRun, + __instrument_run_table_partition_name__: + this.newFastqListRowsEventShowerMap.tablePartition.instrumentRun, + __project_table_partition_name__: + this.newFastqListRowsEventShowerMap.tablePartition.project, /* Lambda functions */ __decompress_fastq_list_rows_lambda_function_arn__: diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py index c90f48121..67be21985 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/lambdas/generate_event_data_objects_py/generate_event_data_objects.py @@ -4,6 +4,8 @@ Given a set of fastq list rows, generate a set of event maps for each fastq file, along with a create event shower as well """ + +# Imports from typing import Dict @@ -54,12 +56,12 @@ def generate_fastq_list_row_event(fastq_list_row: Dict, library: Dict, instrumen new_fastq_list_row_dict["rgid"] = fastq_list_row_rgid return { - "fastqListRow": new_fastq_list_row_dict, "instrumentRunId": instrument_run_id, "library": { - "libraryId": library.get("library_id"), - "orcabusId": library.get("orcabus_id") - } + "libraryId": library.get("libraryId"), + "orcabusId": library.get("orcabusId") + }, + "fastqListRow": new_fastq_list_row_dict, } @@ -74,6 +76,7 @@ def handler(event, context): # Get the fastq list rows and instrument run id fastq_list_rows = event['fastq_list_rows'] library_obj_list = event['library_objs'] + project_obj_list = event['project_objs'] instrument_run_id = event['instrument_run_id'] # Generate the fastq list row events @@ -83,7 +86,7 @@ def handler(event, context): fastq_list_row_iter, next( filter( - lambda library_iter: library_iter['library_id'] == fastq_list_row_iter['RGSM'], + lambda library_iter: library_iter['libraryId'] == fastq_list_row_iter['RGSM'], library_obj_list ) ), @@ -102,52 +105,61 @@ def handler(event, context): "instrumentRunId": instrument_run_id, } - # Generate project data level events - project_list = list( - set( + project_event_data_list = [] + for project_obj_iter_ in project_obj_list: + library_objects_in_project = list( map( - lambda library_iter: ( - library_iter.get("project_owner"), library_iter.get("project_name") - ), - library_obj_list + lambda library_obj_iter: { + "library": { + "orcabusId": library_obj_iter.get("orcabusId"), + "libraryId": library_obj_iter.get("libraryId"), + }, + "fastqPairs": list( + map( + lambda fastq_list_row_event_data_iter: ( + fastq_list_row_event_data_iter.get("fastqListRow") + ), + filter( + lambda fastq_list_row_event_data_iter: ( + fastq_list_row_event_data_iter.get("library").get("libraryId") == + library_obj_iter.get("libraryId") + ), + fastq_list_row_event_data_list + ) + ) + ) + }, + filter( + lambda library_obj_iter_: ( + any( + map( + lambda project_obj_iter_: ( + library_obj_iter_.get("orcabusId") == + project_obj_iter_ + ), + project_obj_iter_.get("librarySet") + ) + ) + ), + library_obj_list + ) + ) + ) + + # Get project object + project_obj = dict( + filter( + lambda kv: not kv[0] == 'librarySet', + project_obj_iter_.items() ) ) - ) - project_event_data_list = [] - for project_owner, project_name in project_list: project_event_data_list.append( { "event_data": { "instrumentRunId": instrument_run_id, - "projectOwner": project_owner, - "projectName": project_name, - "libraries": list( - map( - lambda library_obj_iter: { - "orcabusId": library_obj_iter.get("orcabus_id"), - "libraryId": library_obj_iter.get("library_id"), - "fastqPairs": list( - map( - lambda fastq_list_row_event_data_iter: fastq_list_row_event_data_iter.get("fastqListRow"), - filter( - lambda fastq_list_row_event_data_iter: ( - fastq_list_row_event_data_iter.get("library").get("libraryId") == library_obj_iter.get("library_id") - ), - fastq_list_row_event_data_list - ) - ) - ) - }, - filter( - lambda library_obj_iter: ( - library_obj_iter.get("project_owner") == project_owner and - library_obj_iter.get("project_name") == project_name - ), - library_obj_list - ) - ) - ) + "project": project_obj, + "libraryFastqSet": library_objects_in_project } } ) @@ -161,7 +173,7 @@ def handler(event, context): } -# Test the function +# # Test the function # if __name__ == "__main__": # import json # @@ -171,178 +183,373 @@ def handler(event, context): # { # "library_objs": [ # { -# "library_id": "L2400102", -# "project_owner": "VCCC", -# "orcabus_id": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX", -# "project_name": "PO" -# }, -# { -# "library_id": "L2400159", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBG0NF8QBNVKM6ESCD60", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400160", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBHP6NSB42RVFAP9PGJP", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400161", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBKCATYSFY40BRX6WJWX", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400162", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400163", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400164", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBS64DNTHK6CE850CCNZ", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400165", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBTZRYQNTGAHPC2T601D", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400166", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CBX10204CK7EKGTH9TMB", -# "project_name": "Testing" -# }, -# { -# "library_id": "L2400191", -# "project_owner": "TJohn", -# "orcabus_id": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7", -# "project_name": "CAVATAK" -# }, -# { -# "library_id": "L2400195", -# "project_owner": "TJohn", -# "orcabus_id": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S", -# "project_name": "CAVATAK" -# }, -# { -# "library_id": "L2400196", -# "project_owner": "TJohn", -# "orcabus_id": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8", -# "project_name": "CAVATAK" -# }, -# { -# "library_id": "L2400197", -# "project_owner": "TJohn", -# "orcabus_id": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ", -# "project_name": "CAVATAK" -# }, -# { -# "library_id": "L2400198", -# "project_owner": "TJohn", -# "orcabus_id": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q", -# "project_name": "CAVATAK" -# }, -# { -# "library_id": "L2400231", -# "project_owner": "Tothill", -# "orcabus_id": "lib.01J5S9CFX5P69S4KZRQGDFKV1N", -# "project_name": "CUP" -# }, -# { -# "library_id": "L2400238", -# "project_owner": "Tothill", -# "orcabus_id": "lib.01J5S9CGCAKQWHD9RBM9VXENY9", -# "project_name": "CUP" -# }, -# { -# "library_id": "L2400239", -# "project_owner": "Tothill", -# "orcabus_id": "lib.01J5S9CGEM1DHRQP72EP09B2TA", -# "project_name": "CUP" -# }, -# { -# "library_id": "L2400240", -# "project_owner": "Tothill", -# "orcabus_id": "lib.01J5S9CGG9N9GH5879SY6A6BJB", -# "project_name": "CUP" -# }, -# { -# "library_id": "L2400241", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD", -# "project_name": "Control" -# }, -# { -# "library_id": "L2400242", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CGKWDN7STKZKQM3KH9XR", -# "project_name": "Control" -# }, -# { -# "library_id": "L2400249", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE", -# "project_name": "Control" -# }, -# { -# "library_id": "L2400250", -# "project_owner": "Whittle", -# "orcabus_id": "lib.01J5S9CH4CYPA4SP05H8KRX4W9", -# "project_name": "BPOP-retro" -# }, -# { -# "library_id": "L2400251", -# "project_owner": "Whittle", -# "orcabus_id": "lib.01J5S9CH65E4EE5QJEJ1C60GGG", -# "project_name": "BPOP-retro" -# }, -# { -# "library_id": "L2400252", -# "project_owner": "Whittle", -# "orcabus_id": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP", -# "project_name": "BPOP-retro" -# }, -# { -# "library_id": "L2400253", -# "project_owner": "Whittle", -# "orcabus_id": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY", -# "project_name": "BPOP-retro" -# }, -# { -# "library_id": "L2400254", -# "project_owner": "Whittle", -# "orcabus_id": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY", -# "project_name": "BPOP-retro" -# }, -# { -# "library_id": "L2400255", -# "project_owner": "Tothill", -# "orcabus_id": "lib.01J5S9CHE4ERQ4H209DH397W8A", -# "project_name": "CUP" -# }, -# { -# "library_id": "L2400256", -# "project_owner": "Tothill", -# "orcabus_id": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY", -# "project_name": "CUP" -# }, -# { -# "library_id": "L2400257", -# "project_owner": "UMCCR", -# "orcabus_id": "lib.01J5S9CHHNGFJN73NPRQMSYGN9", -# "project_name": "Control" +# "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M", +# "libraryId": "L2400102", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "borderline", +# "type": "WGS", +# "assay": "ctTSO", +# "coverage": 50 +# }, +# { +# "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# "libraryId": "L2400159", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# "libraryId": "L2400160", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# "libraryId": "L2400161", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# "libraryId": "L2400162", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# "libraryId": "L2400163", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# "libraryId": "L2400164", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# "libraryId": "L2400165", +# "phenotype": "tumor", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 38.6 +# }, +# { +# "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# "libraryId": "L2400166", +# "phenotype": "negative-control", +# "workflow": "manual", +# "quality": "good", +# "type": "ctDNA", +# "assay": "ctTSOv2", +# "coverage": 0.1 +# }, +# { +# "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# "libraryId": "L2400191", +# "phenotype": "normal", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 40 +# }, +# { +# "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# "libraryId": "L2400195", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80 +# }, +# { +# "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# "libraryId": "L2400196", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80 +# }, +# { +# "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ", +# "libraryId": "L2400197", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80 +# }, +# { +# "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# "libraryId": "L2400198", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 80 +# }, +# { +# "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED", +# "libraryId": "L2400231", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "poor", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 100 +# }, +# { +# "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0", +# "libraryId": "L2400238", +# "phenotype": "normal", +# "workflow": "clinical", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 40 +# }, +# { +# "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# "libraryId": "L2400239", +# "phenotype": "normal", +# "workflow": "clinical", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 40 +# }, +# { +# "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83", +# "libraryId": "L2400240", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "poor", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 100 +# }, +# { +# "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# "libraryId": "L2400241", +# "phenotype": "negative-control", +# "workflow": "control", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 0.1 +# }, +# { +# "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# "libraryId": "L2400242", +# "phenotype": "normal", +# "workflow": "control", +# "quality": "good", +# "type": "WGS", +# "assay": "TsqNano", +# "coverage": 15 +# }, +# { +# "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T", +# "libraryId": "L2400249", +# "phenotype": "tumor", +# "workflow": "control", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 1 +# }, +# { +# "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# "libraryId": "L2400250", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV", +# "libraryId": "L2400251", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# "libraryId": "L2400252", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# "libraryId": "L2400253", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# "libraryId": "L2400254", +# "phenotype": "tumor", +# "workflow": "research", +# "quality": "borderline", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# "libraryId": "L2400255", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "very-poor", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9", +# "libraryId": "L2400256", +# "phenotype": "tumor", +# "workflow": "clinical", +# "quality": "very-poor", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 6 +# }, +# { +# "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP", +# "libraryId": "L2400257", +# "phenotype": "negative-control", +# "workflow": "control", +# "quality": "good", +# "type": "WTS", +# "assay": "NebRNA", +# "coverage": 0.1 +# } +# ], +# "project_objs": [ +# { +# "name": None, +# "description": None, +# "librarySet": [ +# "lib.01J8ES4MPZ5B201R50K42XXM4M" +# ], +# "projectId": "PO", +# "orcabusId": "prj.01J8ES4EBXK08WDWB97BSCX1C9" +# }, +# { +# "name": None, +# "description": None, +# "librarySet": [ +# "lib.01J8ES51V0RSVT6C7WQR72QQED", +# "lib.01J8ES52889Q8826P5SH9HDPP0", +# "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# "lib.01J8ES52C3N585BGGY4VNXHC83", +# "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# "lib.01J8ES537S0W1AX9PQPST13GM9" +# ], +# "projectId": "CUP", +# "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3" +# }, +# { +# "name": None, +# "description": None, +# "librarySet": [ +# "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# "lib.01J8ES52XYMVGRB1Q458THNG4T", +# "lib.01J8ES5395KETT9T2NJSVNDKNP" +# ], +# "projectId": "Control", +# "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8" +# }, +# { +# "name": None, +# "description": None, +# "librarySet": [ +# "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# "lib.01J8ES530H895X4WA3NQ6CY2QV", +# "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# "lib.01J8ES534XGBFYDVYV8ZG6SYS0" +# ], +# "projectId": "BPOP-retro", +# "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX" +# }, +# { +# "name": None, +# "description": None, +# "librarySet": [ +# "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# "lib.01J8ES4Y5D52202JVBXHJ9Q9WF" +# ], +# "projectId": "Testing", +# "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1" +# }, +# { +# "name": None, +# "description": None, +# "librarySet": [ +# "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# "lib.01J8ES4ZST489C712CG3R9NQSQ", +# "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9" +# ], +# "projectId": "CAVATAK", +# "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B" # } # ], # "fastq_list_rows": [ @@ -351,300 +558,301 @@ def handler(event, context): # "RGSM": "L2400102", # "RGLB": "L2400102", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400102/L2400102_S1_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400102/L2400102_S1_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400102/L2400102_S1_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400102/L2400102_S1_L001_R2_001.fastq.gz" # }, # { # "RGID": "GAGAATGGTT.TTGCTGCCGA.1", # "RGSM": "L2400159", # "RGLB": "L2400159", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400159/L2400159_S2_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400159/L2400159_S2_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400159/L2400159_S2_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400159/L2400159_S2_L001_R2_001.fastq.gz" # }, # { # "RGID": "AGAGGCAACC.CCATCATTAG.1", # "RGSM": "L2400160", # "RGLB": "L2400160", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400160/L2400160_S3_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400160/L2400160_S3_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400160/L2400160_S3_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400160/L2400160_S3_L001_R2_001.fastq.gz" # }, # { # "RGID": "CCATCATTAG.AGAGGCAACC.1", # "RGSM": "L2400161", # "RGLB": "L2400161", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400161/L2400161_S4_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400161/L2400161_S4_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400161/L2400161_S4_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400161/L2400161_S4_L001_R2_001.fastq.gz" # }, # { # "RGID": "GATAGGCCGA.GCCATGTGCG.1", # "RGSM": "L2400162", # "RGLB": "L2400162", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400162/L2400162_S5_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400162/L2400162_S5_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400162/L2400162_S5_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400162/L2400162_S5_L001_R2_001.fastq.gz" # }, # { # "RGID": "ATGGTTGACT.AGGACAGGCC.1", # "RGSM": "L2400163", # "RGLB": "L2400163", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400163/L2400163_S6_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400163/L2400163_S6_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400163/L2400163_S6_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400163/L2400163_S6_L001_R2_001.fastq.gz" # }, # { # "RGID": "TATTGCGCTC.CCTAACACAG.1", # "RGSM": "L2400164", # "RGLB": "L2400164", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400164/L2400164_S7_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400164/L2400164_S7_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400164/L2400164_S7_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400164/L2400164_S7_L001_R2_001.fastq.gz" # }, # { # "RGID": "TTCTACATAC.TTACAGTTAG.1", # "RGSM": "L2400166", # "RGLB": "L2400166", # "Lane": 1, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400166/L2400166_S8_L001_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400166/L2400166_S8_L001_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400166/L2400166_S8_L001_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400166/L2400166_S8_L001_R2_001.fastq.gz" # }, # { # "RGID": "ATGAGGCC.CAATTAAC.2", # "RGSM": "L2400195", # "RGLB": "L2400195", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400195/L2400195_S9_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400195/L2400195_S9_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400195/L2400195_S9_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400195/L2400195_S9_L002_R2_001.fastq.gz" # }, # { # "RGID": "ACTAAGAT.CCGCGGTT.2", # "RGSM": "L2400196", # "RGLB": "L2400196", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400196/L2400196_S10_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400196/L2400196_S10_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400196/L2400196_S10_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400196/L2400196_S10_L002_R2_001.fastq.gz" # }, # { # "RGID": "GTCGGAGC.TTATAACC.2", # "RGSM": "L2400197", # "RGLB": "L2400197", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400197/L2400197_S11_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400197/L2400197_S11_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400197/L2400197_S11_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400197/L2400197_S11_L002_R2_001.fastq.gz" # }, # { # "RGID": "TCGTAGTG.CCAAGTCT.2", # "RGSM": "L2400231", # "RGLB": "L2400231", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400231/L2400231_S12_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400231/L2400231_S12_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400231/L2400231_S12_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400231/L2400231_S12_L002_R2_001.fastq.gz" # }, # { # "RGID": "GGAGCGTC.GCACGGAC.2", # "RGSM": "L2400238", # "RGLB": "L2400238", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400238/L2400238_S13_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400238/L2400238_S13_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400238/L2400238_S13_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400238/L2400238_S13_L002_R2_001.fastq.gz" # }, # { # "RGID": "ATGGCATG.GGTACCTT.2", # "RGSM": "L2400239", # "RGLB": "L2400239", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400239/L2400239_S14_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400239/L2400239_S14_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400239/L2400239_S14_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400239/L2400239_S14_L002_R2_001.fastq.gz" # }, # { # "RGID": "GCAATGCA.AACGTTCC.2", # "RGSM": "L2400240", # "RGLB": "L2400240", # "Lane": 2, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400240/L2400240_S15_L002_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400240/L2400240_S15_L002_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400240/L2400240_S15_L002_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400240/L2400240_S15_L002_R2_001.fastq.gz" # }, # { # "RGID": "ATGAGGCC.CAATTAAC.3", # "RGSM": "L2400195", # "RGLB": "L2400195", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400195/L2400195_S9_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400195/L2400195_S9_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400195/L2400195_S9_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400195/L2400195_S9_L003_R2_001.fastq.gz" # }, # { # "RGID": "ACTAAGAT.CCGCGGTT.3", # "RGSM": "L2400196", # "RGLB": "L2400196", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400196/L2400196_S10_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400196/L2400196_S10_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400196/L2400196_S10_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400196/L2400196_S10_L003_R2_001.fastq.gz" # }, # { # "RGID": "GTCGGAGC.TTATAACC.3", # "RGSM": "L2400197", # "RGLB": "L2400197", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400197/L2400197_S11_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400197/L2400197_S11_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400197/L2400197_S11_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400197/L2400197_S11_L003_R2_001.fastq.gz" # }, # { # "RGID": "TCGTAGTG.CCAAGTCT.3", # "RGSM": "L2400231", # "RGLB": "L2400231", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400231/L2400231_S12_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400231/L2400231_S12_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400231/L2400231_S12_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400231/L2400231_S12_L003_R2_001.fastq.gz" # }, # { # "RGID": "GGAGCGTC.GCACGGAC.3", # "RGSM": "L2400238", # "RGLB": "L2400238", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400238/L2400238_S13_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400238/L2400238_S13_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400238/L2400238_S13_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400238/L2400238_S13_L003_R2_001.fastq.gz" # }, # { # "RGID": "ATGGCATG.GGTACCTT.3", # "RGSM": "L2400239", # "RGLB": "L2400239", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400239/L2400239_S14_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400239/L2400239_S14_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400239/L2400239_S14_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400239/L2400239_S14_L003_R2_001.fastq.gz" # }, # { # "RGID": "GCAATGCA.AACGTTCC.3", # "RGSM": "L2400240", # "RGLB": "L2400240", # "Lane": 3, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400240/L2400240_S15_L003_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400240/L2400240_S15_L003_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400240/L2400240_S15_L003_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400240/L2400240_S15_L003_R2_001.fastq.gz" # }, # { # "RGID": "ACGCCTTGTT.ACGTTCCTTA.4", # "RGSM": "L2400165", # "RGLB": "L2400165", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400165/L2400165_S16_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400165/L2400165_S16_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400165/L2400165_S16_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400165/L2400165_S16_L004_R2_001.fastq.gz" # }, # { # "RGID": "GCACGGAC.TGCGAGAC.4", # "RGSM": "L2400191", # "RGLB": "L2400191", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400191/L2400191_S17_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400191/L2400191_S17_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400191/L2400191_S17_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400191/L2400191_S17_L004_R2_001.fastq.gz" # }, # { # "RGID": "GTCGGAGC.TTATAACC.4", # "RGSM": "L2400197", # "RGLB": "L2400197", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400197/L2400197_S11_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400197/L2400197_S11_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400197/L2400197_S11_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400197/L2400197_S11_L004_R2_001.fastq.gz" # }, # { # "RGID": "CTTGGTAT.GGACTTGG.4", # "RGSM": "L2400198", # "RGLB": "L2400198", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400198/L2400198_S18_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400198/L2400198_S18_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400198/L2400198_S18_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400198/L2400198_S18_L004_R2_001.fastq.gz" # }, # { # "RGID": "GTTCCAAT.GCAGAATT.4", # "RGSM": "L2400241", # "RGLB": "L2400241", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400241/L2400241_S19_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400241/L2400241_S19_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400241/L2400241_S19_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400241/L2400241_S19_L004_R2_001.fastq.gz" # }, # { # "RGID": "ACCTTGGC.ATGAGGCC.4", # "RGSM": "L2400242", # "RGLB": "L2400242", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400242/L2400242_S20_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400242/L2400242_S20_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400242/L2400242_S20_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400242/L2400242_S20_L004_R2_001.fastq.gz" # }, # { # "RGID": "AGTTTCGA.CCTACGAT.4", # "RGSM": "L2400249", # "RGLB": "L2400249", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400249/L2400249_S21_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400249/L2400249_S21_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400249/L2400249_S21_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400249/L2400249_S21_L004_R2_001.fastq.gz" # }, # { # "RGID": "GAACCTCT.GTCTGCGC.4", # "RGSM": "L2400250", # "RGLB": "L2400250", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400250/L2400250_S22_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400250/L2400250_S22_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400250/L2400250_S22_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400250/L2400250_S22_L004_R2_001.fastq.gz" # }, # { # "RGID": "GCCCAGTG.CCGCAATT.4", # "RGSM": "L2400251", # "RGLB": "L2400251", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400251/L2400251_S23_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400251/L2400251_S23_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400251/L2400251_S23_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400251/L2400251_S23_L004_R2_001.fastq.gz" # }, # { # "RGID": "TGACAGCT.CCCGTAGG.4", # "RGSM": "L2400252", # "RGLB": "L2400252", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400252/L2400252_S24_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400252/L2400252_S24_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400252/L2400252_S24_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400252/L2400252_S24_L004_R2_001.fastq.gz" # }, # { # "RGID": "CATCACCC.ATATAGCA.4", # "RGSM": "L2400253", # "RGLB": "L2400253", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400253/L2400253_S25_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400253/L2400253_S25_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400253/L2400253_S25_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400253/L2400253_S25_L004_R2_001.fastq.gz" # }, # { # "RGID": "CTGGAGTA.GTTCGGTT.4", # "RGSM": "L2400254", # "RGLB": "L2400254", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400254/L2400254_S26_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400254/L2400254_S26_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400254/L2400254_S26_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400254/L2400254_S26_L004_R2_001.fastq.gz" # }, # { # "RGID": "GATCCGGG.AAGCAGGT.4", # "RGSM": "L2400255", # "RGLB": "L2400255", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400255/L2400255_S27_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400255/L2400255_S27_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400255/L2400255_S27_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400255/L2400255_S27_L004_R2_001.fastq.gz" # }, # { # "RGID": "AACACCTG.CGCATGGG.4", # "RGSM": "L2400256", # "RGLB": "L2400256", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400256/L2400256_S28_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400256/L2400256_S28_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400256/L2400256_S28_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400256/L2400256_S28_L004_R2_001.fastq.gz" # }, # { # "RGID": "GTGACGTT.TCCCAGAT.4", # "RGSM": "L2400257", # "RGLB": "L2400257", # "Lane": 4, -# "Read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400257/L2400257_S29_L004_R1_001.fastq.gz", -# "Read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400257/L2400257_S29_L004_R2_001.fastq.gz" +# "Read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400257/L2400257_S29_L004_R1_001.fastq.gz", +# "Read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400257/L2400257_S29_L004_R2_001.fastq.gz" # } # ], # "instrument_run_id": "240229_A00130_0288_BH5HM2DSXC" # } +# # , # None # ) @@ -661,141 +869,167 @@ def handler(event, context): # # { # # "event_data": { # # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "projectOwner": "UMCCR", -# # "projectName": "Testing", -# # "libraries": [ +# # "project": { +# # "name": null, +# # "description": null, +# # "projectId": "PO", +# # "orcabusId": "prj.01J8ES4EBXK08WDWB97BSCX1C9" +# # }, +# # "libraryFastqSet": [ # # { -# # "orcabusId": "lib.01J5S9CBG0NF8QBNVKM6ESCD60", -# # "libraryId": "L2400159", +# # "library": { +# # "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M", +# # "libraryId": "L2400102" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GAGAATGGTT.TTGCTGCCGA.1.240229_A00130_0288_BH5HM2DSXC.L2400159", -# # "rgsm": "L2400159", -# # "rglb": "L2400159", +# # "rgid": "GAATTCGT.TTATGAGT.1.240229_A00130_0288_BH5HM2DSXC.L2400102", +# # "rgsm": "L2400102", +# # "rglb": "L2400102", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400159/L2400159_S2_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400159/L2400159_S2_L001_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400102/L2400102_S1_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400102/L2400102_S1_L001_R2_001.fastq.gz" # # } # # ] -# # }, +# # } +# # ] +# # } +# # }, +# # { +# # "event_data": { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "project": { +# # "name": null, +# # "description": null, +# # "projectId": "CUP", +# # "orcabusId": "prj.01J8ES4EZAA5YMHX82664GJQB3" +# # }, +# # "libraryFastqSet": [ # # { -# # "orcabusId": "lib.01J5S9CBHP6NSB42RVFAP9PGJP", -# # "libraryId": "L2400160", +# # "library": { +# # "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED", +# # "libraryId": "L2400231" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "AGAGGCAACC.CCATCATTAG.1.240229_A00130_0288_BH5HM2DSXC.L2400160", -# # "rgsm": "L2400160", -# # "rglb": "L2400160", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400160/L2400160_S3_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400160/L2400160_S3_L001_R2_001.fastq.gz" -# # } -# # ] -# # }, -# # { -# # "orcabusId": "lib.01J5S9CBKCATYSFY40BRX6WJWX", -# # "libraryId": "L2400161", -# # "fastqPairs": [ +# # "rgid": "TCGTAGTG.CCAAGTCT.2.240229_A00130_0288_BH5HM2DSXC.L2400231", +# # "rgsm": "L2400231", +# # "rglb": "L2400231", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400231/L2400231_S12_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400231/L2400231_S12_L002_R2_001.fastq.gz" +# # }, # # { -# # "rgid": "CCATCATTAG.AGAGGCAACC.1.240229_A00130_0288_BH5HM2DSXC.L2400161", -# # "rgsm": "L2400161", -# # "rglb": "L2400161", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400161/L2400161_S4_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400161/L2400161_S4_L001_R2_001.fastq.gz" +# # "rgid": "TCGTAGTG.CCAAGTCT.3.240229_A00130_0288_BH5HM2DSXC.L2400231", +# # "rgsm": "L2400231", +# # "rglb": "L2400231", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400231/L2400231_S12_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400231/L2400231_S12_L003_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC", -# # "libraryId": "L2400162", +# # "library": { +# # "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0", +# # "libraryId": "L2400238" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GATAGGCCGA.GCCATGTGCG.1.240229_A00130_0288_BH5HM2DSXC.L2400162", -# # "rgsm": "L2400162", -# # "rglb": "L2400162", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400162/L2400162_S5_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400162/L2400162_S5_L001_R2_001.fastq.gz" +# # "rgid": "GGAGCGTC.GCACGGAC.2.240229_A00130_0288_BH5HM2DSXC.L2400238", +# # "rgsm": "L2400238", +# # "rglb": "L2400238", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400238/L2400238_S13_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400238/L2400238_S13_L002_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "GGAGCGTC.GCACGGAC.3.240229_A00130_0288_BH5HM2DSXC.L2400238", +# # "rgsm": "L2400238", +# # "rglb": "L2400238", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400238/L2400238_S13_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400238/L2400238_S13_L003_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W", -# # "libraryId": "L2400163", +# # "library": { +# # "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8", +# # "libraryId": "L2400239" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "ATGGTTGACT.AGGACAGGCC.1.240229_A00130_0288_BH5HM2DSXC.L2400163", -# # "rgsm": "L2400163", -# # "rglb": "L2400163", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400163/L2400163_S6_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400163/L2400163_S6_L001_R2_001.fastq.gz" +# # "rgid": "ATGGCATG.GGTACCTT.2.240229_A00130_0288_BH5HM2DSXC.L2400239", +# # "rgsm": "L2400239", +# # "rglb": "L2400239", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400239/L2400239_S14_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400239/L2400239_S14_L002_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "ATGGCATG.GGTACCTT.3.240229_A00130_0288_BH5HM2DSXC.L2400239", +# # "rgsm": "L2400239", +# # "rglb": "L2400239", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400239/L2400239_S14_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400239/L2400239_S14_L003_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CBS64DNTHK6CE850CCNZ", -# # "libraryId": "L2400164", +# # "library": { +# # "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83", +# # "libraryId": "L2400240" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "TATTGCGCTC.CCTAACACAG.1.240229_A00130_0288_BH5HM2DSXC.L2400164", -# # "rgsm": "L2400164", -# # "rglb": "L2400164", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400164/L2400164_S7_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400164/L2400164_S7_L001_R2_001.fastq.gz" +# # "rgid": "GCAATGCA.AACGTTCC.2.240229_A00130_0288_BH5HM2DSXC.L2400240", +# # "rgsm": "L2400240", +# # "rglb": "L2400240", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400240/L2400240_S15_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400240/L2400240_S15_L002_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "GCAATGCA.AACGTTCC.3.240229_A00130_0288_BH5HM2DSXC.L2400240", +# # "rgsm": "L2400240", +# # "rglb": "L2400240", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400240/L2400240_S15_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400240/L2400240_S15_L003_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CBTZRYQNTGAHPC2T601D", -# # "libraryId": "L2400165", +# # "library": { +# # "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q", +# # "libraryId": "L2400255" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "ACGCCTTGTT.ACGTTCCTTA.4.240229_A00130_0288_BH5HM2DSXC.L2400165", -# # "rgsm": "L2400165", -# # "rglb": "L2400165", +# # "rgid": "GATCCGGG.AAGCAGGT.4.240229_A00130_0288_BH5HM2DSXC.L2400255", +# # "rgsm": "L2400255", +# # "rglb": "L2400255", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400165/L2400165_S16_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400165/L2400165_S16_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400255/L2400255_S27_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400255/L2400255_S27_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CBX10204CK7EKGTH9TMB", -# # "libraryId": "L2400166", -# # "fastqPairs": [ -# # { -# # "rgid": "TTCTACATAC.TTACAGTTAG.1.240229_A00130_0288_BH5HM2DSXC.L2400166", -# # "rgsm": "L2400166", -# # "rglb": "L2400166", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400166/L2400166_S8_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400166/L2400166_S8_L001_R2_001.fastq.gz" -# # } -# # ] -# # } -# # ] -# # } -# # }, -# # { -# # "event_data": { -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "projectOwner": "VCCC", -# # "projectName": "PO", -# # "libraries": [ -# # { -# # "orcabusId": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX", -# # "libraryId": "L2400102", +# # "library": { +# # "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9", +# # "libraryId": "L2400256" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GAATTCGT.TTATGAGT.1.240229_A00130_0288_BH5HM2DSXC.L2400102", -# # "rgsm": "L2400102", -# # "rglb": "L2400102", -# # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400102/L2400102_S1_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400102/L2400102_S1_L001_R2_001.fastq.gz" +# # "rgid": "AACACCTG.CGCATGGG.4.240229_A00130_0288_BH5HM2DSXC.L2400256", +# # "rgsm": "L2400256", +# # "rglb": "L2400256", +# # "lane": 4, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400256/L2400256_S28_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400256/L2400256_S28_L004_R2_001.fastq.gz" # # } # # ] # # } @@ -805,62 +1039,74 @@ def handler(event, context): # # { # # "event_data": { # # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "projectOwner": "UMCCR", -# # "projectName": "Control", -# # "libraries": [ +# # "project": { +# # "name": null, +# # "description": null, +# # "projectId": "Control", +# # "orcabusId": "prj.01J8ES4FC6DVW20AR33FBX2SA8" +# # }, +# # "libraryFastqSet": [ # # { -# # "orcabusId": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD", -# # "libraryId": "L2400241", +# # "library": { +# # "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT", +# # "libraryId": "L2400241" +# # }, # # "fastqPairs": [ # # { # # "rgid": "GTTCCAAT.GCAGAATT.4.240229_A00130_0288_BH5HM2DSXC.L2400241", # # "rgsm": "L2400241", # # "rglb": "L2400241", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400241/L2400241_S19_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400241/L2400241_S19_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400241/L2400241_S19_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400241/L2400241_S19_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CGKWDN7STKZKQM3KH9XR", -# # "libraryId": "L2400242", +# # "library": { +# # "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F", +# # "libraryId": "L2400242" +# # }, # # "fastqPairs": [ # # { # # "rgid": "ACCTTGGC.ATGAGGCC.4.240229_A00130_0288_BH5HM2DSXC.L2400242", # # "rgsm": "L2400242", # # "rglb": "L2400242", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400242/L2400242_S20_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400242/L2400242_S20_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400242/L2400242_S20_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400242/L2400242_S20_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE", -# # "libraryId": "L2400249", +# # "library": { +# # "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T", +# # "libraryId": "L2400249" +# # }, # # "fastqPairs": [ # # { # # "rgid": "AGTTTCGA.CCTACGAT.4.240229_A00130_0288_BH5HM2DSXC.L2400249", # # "rgsm": "L2400249", # # "rglb": "L2400249", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400249/L2400249_S21_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400249/L2400249_S21_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400249/L2400249_S21_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400249/L2400249_S21_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CHHNGFJN73NPRQMSYGN9", -# # "libraryId": "L2400257", +# # "library": { +# # "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP", +# # "libraryId": "L2400257" +# # }, # # "fastqPairs": [ # # { # # "rgid": "GTGACGTT.TCCCAGAT.4.240229_A00130_0288_BH5HM2DSXC.L2400257", # # "rgsm": "L2400257", # # "rglb": "L2400257", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400257/L2400257_S29_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400257/L2400257_S29_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400257/L2400257_S29_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400257/L2400257_S29_L004_R2_001.fastq.gz" # # } # # ] # # } @@ -870,108 +1116,90 @@ def handler(event, context): # # { # # "event_data": { # # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "projectOwner": "TJohn", -# # "projectName": "CAVATAK", -# # "libraries": [ +# # "project": { +# # "name": null, +# # "description": null, +# # "projectId": "BPOP-retro", +# # "orcabusId": "prj.01J8ES4FH3XMPZQNDJ9J000BXX" +# # }, +# # "libraryFastqSet": [ # # { -# # "orcabusId": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7", -# # "libraryId": "L2400191", +# # "library": { +# # "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10", +# # "libraryId": "L2400250" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GCACGGAC.TGCGAGAC.4.240229_A00130_0288_BH5HM2DSXC.L2400191", -# # "rgsm": "L2400191", -# # "rglb": "L2400191", +# # "rgid": "GAACCTCT.GTCTGCGC.4.240229_A00130_0288_BH5HM2DSXC.L2400250", +# # "rgsm": "L2400250", +# # "rglb": "L2400250", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400191/L2400191_S17_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400191/L2400191_S17_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400250/L2400250_S22_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400250/L2400250_S22_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S", -# # "libraryId": "L2400195", +# # "library": { +# # "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV", +# # "libraryId": "L2400251" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "ATGAGGCC.CAATTAAC.2.240229_A00130_0288_BH5HM2DSXC.L2400195", -# # "rgsm": "L2400195", -# # "rglb": "L2400195", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400195/L2400195_S9_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400195/L2400195_S9_L002_R2_001.fastq.gz" -# # }, -# # { -# # "rgid": "ATGAGGCC.CAATTAAC.3.240229_A00130_0288_BH5HM2DSXC.L2400195", -# # "rgsm": "L2400195", -# # "rglb": "L2400195", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400195/L2400195_S9_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400195/L2400195_S9_L003_R2_001.fastq.gz" +# # "rgid": "GCCCAGTG.CCGCAATT.4.240229_A00130_0288_BH5HM2DSXC.L2400251", +# # "rgsm": "L2400251", +# # "rglb": "L2400251", +# # "lane": 4, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400251/L2400251_S23_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400251/L2400251_S23_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8", -# # "libraryId": "L2400196", +# # "library": { +# # "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD", +# # "libraryId": "L2400252" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "ACTAAGAT.CCGCGGTT.2.240229_A00130_0288_BH5HM2DSXC.L2400196", -# # "rgsm": "L2400196", -# # "rglb": "L2400196", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400196/L2400196_S10_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400196/L2400196_S10_L002_R2_001.fastq.gz" -# # }, -# # { -# # "rgid": "ACTAAGAT.CCGCGGTT.3.240229_A00130_0288_BH5HM2DSXC.L2400196", -# # "rgsm": "L2400196", -# # "rglb": "L2400196", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400196/L2400196_S10_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400196/L2400196_S10_L003_R2_001.fastq.gz" +# # "rgid": "TGACAGCT.CCCGTAGG.4.240229_A00130_0288_BH5HM2DSXC.L2400252", +# # "rgsm": "L2400252", +# # "rglb": "L2400252", +# # "lane": 4, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400252/L2400252_S24_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400252/L2400252_S24_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ", -# # "libraryId": "L2400197", +# # "library": { +# # "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0", +# # "libraryId": "L2400253" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GTCGGAGC.TTATAACC.2.240229_A00130_0288_BH5HM2DSXC.L2400197", -# # "rgsm": "L2400197", -# # "rglb": "L2400197", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400197/L2400197_S11_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400197/L2400197_S11_L002_R2_001.fastq.gz" -# # }, -# # { -# # "rgid": "GTCGGAGC.TTATAACC.3.240229_A00130_0288_BH5HM2DSXC.L2400197", -# # "rgsm": "L2400197", -# # "rglb": "L2400197", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400197/L2400197_S11_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400197/L2400197_S11_L003_R2_001.fastq.gz" -# # }, -# # { -# # "rgid": "GTCGGAGC.TTATAACC.4.240229_A00130_0288_BH5HM2DSXC.L2400197", -# # "rgsm": "L2400197", -# # "rglb": "L2400197", +# # "rgid": "CATCACCC.ATATAGCA.4.240229_A00130_0288_BH5HM2DSXC.L2400253", +# # "rgsm": "L2400253", +# # "rglb": "L2400253", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400197/L2400197_S11_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400197/L2400197_S11_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400253/L2400253_S25_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400253/L2400253_S25_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q", -# # "libraryId": "L2400198", +# # "library": { +# # "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0", +# # "libraryId": "L2400254" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "CTTGGTAT.GGACTTGG.4.240229_A00130_0288_BH5HM2DSXC.L2400198", -# # "rgsm": "L2400198", -# # "rglb": "L2400198", +# # "rgid": "CTGGAGTA.GTTCGGTT.4.240229_A00130_0288_BH5HM2DSXC.L2400254", +# # "rgsm": "L2400254", +# # "rglb": "L2400254", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400198/L2400198_S18_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400198/L2400198_S18_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400254/L2400254_S26_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400254/L2400254_S26_L004_R2_001.fastq.gz" # # } # # ] # # } @@ -981,122 +1209,138 @@ def handler(event, context): # # { # # "event_data": { # # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "projectOwner": "Tothill", -# # "projectName": "CUP", -# # "libraries": [ +# # "project": { +# # "name": null, +# # "description": null, +# # "projectId": "Testing", +# # "orcabusId": "prj.01J8ES4XMWD0DH7MDRNER5TZS1" +# # }, +# # "libraryFastqSet": [ # # { -# # "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N", -# # "libraryId": "L2400231", +# # "library": { +# # "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V", +# # "libraryId": "L2400159" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "TCGTAGTG.CCAAGTCT.2.240229_A00130_0288_BH5HM2DSXC.L2400231", -# # "rgsm": "L2400231", -# # "rglb": "L2400231", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400231/L2400231_S12_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400231/L2400231_S12_L002_R2_001.fastq.gz" -# # }, -# # { -# # "rgid": "TCGTAGTG.CCAAGTCT.3.240229_A00130_0288_BH5HM2DSXC.L2400231", -# # "rgsm": "L2400231", -# # "rglb": "L2400231", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400231/L2400231_S12_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400231/L2400231_S12_L003_R2_001.fastq.gz" +# # "rgid": "GAGAATGGTT.TTGCTGCCGA.1.240229_A00130_0288_BH5HM2DSXC.L2400159", +# # "rgsm": "L2400159", +# # "rglb": "L2400159", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400159/L2400159_S2_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400159/L2400159_S2_L001_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9", -# # "libraryId": "L2400238", +# # "library": { +# # "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG", +# # "libraryId": "L2400160" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GGAGCGTC.GCACGGAC.2.240229_A00130_0288_BH5HM2DSXC.L2400238", -# # "rgsm": "L2400238", -# # "rglb": "L2400238", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400238/L2400238_S13_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400238/L2400238_S13_L002_R2_001.fastq.gz" -# # }, -# # { -# # "rgid": "GGAGCGTC.GCACGGAC.3.240229_A00130_0288_BH5HM2DSXC.L2400238", -# # "rgsm": "L2400238", -# # "rglb": "L2400238", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400238/L2400238_S13_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400238/L2400238_S13_L003_R2_001.fastq.gz" +# # "rgid": "AGAGGCAACC.CCATCATTAG.1.240229_A00130_0288_BH5HM2DSXC.L2400160", +# # "rgsm": "L2400160", +# # "rglb": "L2400160", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400160/L2400160_S3_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400160/L2400160_S3_L001_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA", -# # "libraryId": "L2400239", +# # "library": { +# # "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG", +# # "libraryId": "L2400161" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "ATGGCATG.GGTACCTT.2.240229_A00130_0288_BH5HM2DSXC.L2400239", -# # "rgsm": "L2400239", -# # "rglb": "L2400239", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400239/L2400239_S14_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400239/L2400239_S14_L002_R2_001.fastq.gz" -# # }, +# # "rgid": "CCATCATTAG.AGAGGCAACC.1.240229_A00130_0288_BH5HM2DSXC.L2400161", +# # "rgsm": "L2400161", +# # "rglb": "L2400161", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400161/L2400161_S4_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400161/L2400161_S4_L001_R2_001.fastq.gz" +# # } +# # ] +# # }, +# # { +# # "library": { +# # "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH", +# # "libraryId": "L2400162" +# # }, +# # "fastqPairs": [ # # { -# # "rgid": "ATGGCATG.GGTACCTT.3.240229_A00130_0288_BH5HM2DSXC.L2400239", -# # "rgsm": "L2400239", -# # "rglb": "L2400239", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400239/L2400239_S14_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400239/L2400239_S14_L003_R2_001.fastq.gz" +# # "rgid": "GATAGGCCGA.GCCATGTGCG.1.240229_A00130_0288_BH5HM2DSXC.L2400162", +# # "rgsm": "L2400162", +# # "rglb": "L2400162", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400162/L2400162_S5_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400162/L2400162_S5_L001_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB", -# # "libraryId": "L2400240", +# # "library": { +# # "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X", +# # "libraryId": "L2400163" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GCAATGCA.AACGTTCC.2.240229_A00130_0288_BH5HM2DSXC.L2400240", -# # "rgsm": "L2400240", -# # "rglb": "L2400240", -# # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400240/L2400240_S15_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400240/L2400240_S15_L002_R2_001.fastq.gz" -# # }, +# # "rgid": "ATGGTTGACT.AGGACAGGCC.1.240229_A00130_0288_BH5HM2DSXC.L2400163", +# # "rgsm": "L2400163", +# # "rglb": "L2400163", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400163/L2400163_S6_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400163/L2400163_S6_L001_R2_001.fastq.gz" +# # } +# # ] +# # }, +# # { +# # "library": { +# # "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP", +# # "libraryId": "L2400164" +# # }, +# # "fastqPairs": [ # # { -# # "rgid": "GCAATGCA.AACGTTCC.3.240229_A00130_0288_BH5HM2DSXC.L2400240", -# # "rgsm": "L2400240", -# # "rglb": "L2400240", -# # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400240/L2400240_S15_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400240/L2400240_S15_L003_R2_001.fastq.gz" +# # "rgid": "TATTGCGCTC.CCTAACACAG.1.240229_A00130_0288_BH5HM2DSXC.L2400164", +# # "rgsm": "L2400164", +# # "rglb": "L2400164", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400164/L2400164_S7_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400164/L2400164_S7_L001_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CHE4ERQ4H209DH397W8A", -# # "libraryId": "L2400255", +# # "library": { +# # "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1", +# # "libraryId": "L2400165" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GATCCGGG.AAGCAGGT.4.240229_A00130_0288_BH5HM2DSXC.L2400255", -# # "rgsm": "L2400255", -# # "rglb": "L2400255", +# # "rgid": "ACGCCTTGTT.ACGTTCCTTA.4.240229_A00130_0288_BH5HM2DSXC.L2400165", +# # "rgsm": "L2400165", +# # "rglb": "L2400165", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400255/L2400255_S27_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400255/L2400255_S27_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400165/L2400165_S16_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400165/L2400165_S16_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY", -# # "libraryId": "L2400256", +# # "library": { +# # "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF", +# # "libraryId": "L2400166" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "AACACCTG.CGCATGGG.4.240229_A00130_0288_BH5HM2DSXC.L2400256", -# # "rgsm": "L2400256", -# # "rglb": "L2400256", -# # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400256/L2400256_S28_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400256/L2400256_S28_L004_R2_001.fastq.gz" +# # "rgid": "TTCTACATAC.TTACAGTTAG.1.240229_A00130_0288_BH5HM2DSXC.L2400166", +# # "rgsm": "L2400166", +# # "rglb": "L2400166", +# # "lane": 1, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400166/L2400166_S8_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400166/L2400166_S8_L001_R2_001.fastq.gz" # # } # # ] # # } @@ -1106,76 +1350,122 @@ def handler(event, context): # # { # # "event_data": { # # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "projectOwner": "Whittle", -# # "projectName": "BPOP-retro", -# # "libraries": [ +# # "project": { +# # "name": null, +# # "description": null, +# # "projectId": "CAVATAK", +# # "orcabusId": "prj.01J8ES4ZAWHH3FKYA2CFHSMZ4B" +# # }, +# # "libraryFastqSet": [ # # { -# # "orcabusId": "lib.01J5S9CH4CYPA4SP05H8KRX4W9", -# # "libraryId": "L2400250", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW", +# # "libraryId": "L2400191" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GAACCTCT.GTCTGCGC.4.240229_A00130_0288_BH5HM2DSXC.L2400250", -# # "rgsm": "L2400250", -# # "rglb": "L2400250", +# # "rgid": "GCACGGAC.TGCGAGAC.4.240229_A00130_0288_BH5HM2DSXC.L2400191", +# # "rgsm": "L2400191", +# # "rglb": "L2400191", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400250/L2400250_S22_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400250/L2400250_S22_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400191/L2400191_S17_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400191/L2400191_S17_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CH65E4EE5QJEJ1C60GGG", -# # "libraryId": "L2400251", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6", +# # "libraryId": "L2400195" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "GCCCAGTG.CCGCAATT.4.240229_A00130_0288_BH5HM2DSXC.L2400251", -# # "rgsm": "L2400251", -# # "rglb": "L2400251", -# # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400251/L2400251_S23_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400251/L2400251_S23_L004_R2_001.fastq.gz" +# # "rgid": "ATGAGGCC.CAATTAAC.2.240229_A00130_0288_BH5HM2DSXC.L2400195", +# # "rgsm": "L2400195", +# # "rglb": "L2400195", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400195/L2400195_S9_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400195/L2400195_S9_L002_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "ATGAGGCC.CAATTAAC.3.240229_A00130_0288_BH5HM2DSXC.L2400195", +# # "rgsm": "L2400195", +# # "rglb": "L2400195", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400195/L2400195_S9_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400195/L2400195_S9_L003_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP", -# # "libraryId": "L2400252", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK", +# # "libraryId": "L2400196" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "TGACAGCT.CCCGTAGG.4.240229_A00130_0288_BH5HM2DSXC.L2400252", -# # "rgsm": "L2400252", -# # "rglb": "L2400252", -# # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400252/L2400252_S24_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400252/L2400252_S24_L004_R2_001.fastq.gz" +# # "rgid": "ACTAAGAT.CCGCGGTT.2.240229_A00130_0288_BH5HM2DSXC.L2400196", +# # "rgsm": "L2400196", +# # "rglb": "L2400196", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400196/L2400196_S10_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400196/L2400196_S10_L002_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "ACTAAGAT.CCGCGGTT.3.240229_A00130_0288_BH5HM2DSXC.L2400196", +# # "rgsm": "L2400196", +# # "rglb": "L2400196", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400196/L2400196_S10_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400196/L2400196_S10_L003_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY", -# # "libraryId": "L2400253", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ", +# # "libraryId": "L2400197" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "CATCACCC.ATATAGCA.4.240229_A00130_0288_BH5HM2DSXC.L2400253", -# # "rgsm": "L2400253", -# # "rglb": "L2400253", +# # "rgid": "GTCGGAGC.TTATAACC.2.240229_A00130_0288_BH5HM2DSXC.L2400197", +# # "rgsm": "L2400197", +# # "rglb": "L2400197", +# # "lane": 2, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400197/L2400197_S11_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400197/L2400197_S11_L002_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "GTCGGAGC.TTATAACC.3.240229_A00130_0288_BH5HM2DSXC.L2400197", +# # "rgsm": "L2400197", +# # "rglb": "L2400197", +# # "lane": 3, +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400197/L2400197_S11_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400197/L2400197_S11_L003_R2_001.fastq.gz" +# # }, +# # { +# # "rgid": "GTCGGAGC.TTATAACC.4.240229_A00130_0288_BH5HM2DSXC.L2400197", +# # "rgsm": "L2400197", +# # "rglb": "L2400197", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400253/L2400253_S25_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400253/L2400253_S25_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400197/L2400197_S11_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400197/L2400197_S11_L004_R2_001.fastq.gz" # # } # # ] # # }, # # { -# # "orcabusId": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY", -# # "libraryId": "L2400254", +# # "library": { +# # "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9", +# # "libraryId": "L2400198" +# # }, # # "fastqPairs": [ # # { -# # "rgid": "CTGGAGTA.GTTCGGTT.4.240229_A00130_0288_BH5HM2DSXC.L2400254", -# # "rgsm": "L2400254", -# # "rglb": "L2400254", +# # "rgid": "CTTGGTAT.GGACTTGG.4.240229_A00130_0288_BH5HM2DSXC.L2400198", +# # "rgsm": "L2400198", +# # "rglb": "L2400198", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400254/L2400254_S26_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400254/L2400254_S26_L004_R2_001.fastq.gz" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400198/L2400198_S18_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400198/L2400198_S18_L004_R2_001.fastq.gz" # # } # # ] # # } @@ -1185,558 +1475,558 @@ def handler(event, context): # # ], # # "fastq_list_rows_event_data_list": [ # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400102", +# # "orcabusId": "lib.01J8ES4MPZ5B201R50K42XXM4M" +# # }, # # "fastqListRow": { # # "rgid": "GAATTCGT.TTATGAGT.1.240229_A00130_0288_BH5HM2DSXC.L2400102", # # "rgsm": "L2400102", # # "rglb": "L2400102", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400102/L2400102_S1_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400102/L2400102_S1_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400102", -# # "orcabusId": "lib.01J5S9C4VMJ6PZ8GJ2G189AMXX" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400102/L2400102_S1_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400102/L2400102_S1_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400159", +# # "orcabusId": "lib.01J8ES4XNYFP38JMDV7GMV0V3V" +# # }, # # "fastqListRow": { # # "rgid": "GAGAATGGTT.TTGCTGCCGA.1.240229_A00130_0288_BH5HM2DSXC.L2400159", # # "rgsm": "L2400159", # # "rglb": "L2400159", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400159/L2400159_S2_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400159/L2400159_S2_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400159", -# # "orcabusId": "lib.01J5S9CBG0NF8QBNVKM6ESCD60" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400159/L2400159_S2_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400159/L2400159_S2_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400160", +# # "orcabusId": "lib.01J8ES4XQG3MPBW94TTVT4STVG" +# # }, # # "fastqListRow": { # # "rgid": "AGAGGCAACC.CCATCATTAG.1.240229_A00130_0288_BH5HM2DSXC.L2400160", # # "rgsm": "L2400160", # # "rglb": "L2400160", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400160/L2400160_S3_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400160/L2400160_S3_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400160", -# # "orcabusId": "lib.01J5S9CBHP6NSB42RVFAP9PGJP" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400160/L2400160_S3_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400160/L2400160_S3_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400161", +# # "orcabusId": "lib.01J8ES4XSS97XNRS8DH0B1RJRG" +# # }, # # "fastqListRow": { # # "rgid": "CCATCATTAG.AGAGGCAACC.1.240229_A00130_0288_BH5HM2DSXC.L2400161", # # "rgsm": "L2400161", # # "rglb": "L2400161", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400161/L2400161_S4_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400161/L2400161_S4_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400161", -# # "orcabusId": "lib.01J5S9CBKCATYSFY40BRX6WJWX" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400161/L2400161_S4_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400161/L2400161_S4_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400162", +# # "orcabusId": "lib.01J8ES4XXF6NMEJMM5M4GWS6KH" +# # }, # # "fastqListRow": { # # "rgid": "GATAGGCCGA.GCCATGTGCG.1.240229_A00130_0288_BH5HM2DSXC.L2400162", # # "rgsm": "L2400162", # # "rglb": "L2400162", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400162/L2400162_S5_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400162/L2400162_S5_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400162", -# # "orcabusId": "lib.01J5S9CBN6EAXW4AXG7TQ1H6NC" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400162/L2400162_S5_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400162/L2400162_S5_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400163", +# # "orcabusId": "lib.01J8ES4XZD7T2VRPVQ1GSVZ11X" +# # }, # # "fastqListRow": { # # "rgid": "ATGGTTGACT.AGGACAGGCC.1.240229_A00130_0288_BH5HM2DSXC.L2400163", # # "rgsm": "L2400163", # # "rglb": "L2400163", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400163/L2400163_S6_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400163/L2400163_S6_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400163", -# # "orcabusId": "lib.01J5S9CBQFX8V1QRW7KAV3MD1W" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400163/L2400163_S6_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400163/L2400163_S6_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400164", +# # "orcabusId": "lib.01J8ES4Y1AKAHYD9EW0TW4FBCP" +# # }, # # "fastqListRow": { # # "rgid": "TATTGCGCTC.CCTAACACAG.1.240229_A00130_0288_BH5HM2DSXC.L2400164", # # "rgsm": "L2400164", # # "rglb": "L2400164", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400164/L2400164_S7_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400164/L2400164_S7_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400164", -# # "orcabusId": "lib.01J5S9CBS64DNTHK6CE850CCNZ" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400164/L2400164_S7_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400164/L2400164_S7_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400166", +# # "orcabusId": "lib.01J8ES4Y5D52202JVBXHJ9Q9WF" +# # }, # # "fastqListRow": { # # "rgid": "TTCTACATAC.TTACAGTTAG.1.240229_A00130_0288_BH5HM2DSXC.L2400166", # # "rgsm": "L2400166", # # "rglb": "L2400166", # # "lane": 1, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400166/L2400166_S8_L001_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_1/L2400166/L2400166_S8_L001_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400166", -# # "orcabusId": "lib.01J5S9CBX10204CK7EKGTH9TMB" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400166/L2400166_S8_L001_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_1/L2400166/L2400166_S8_L001_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400195", +# # "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6" +# # }, # # "fastqListRow": { # # "rgid": "ATGAGGCC.CAATTAAC.2.240229_A00130_0288_BH5HM2DSXC.L2400195", # # "rgsm": "L2400195", # # "rglb": "L2400195", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400195/L2400195_S9_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400195/L2400195_S9_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400195", -# # "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400195/L2400195_S9_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400195/L2400195_S9_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400196", +# # "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK" +# # }, # # "fastqListRow": { # # "rgid": "ACTAAGAT.CCGCGGTT.2.240229_A00130_0288_BH5HM2DSXC.L2400196", # # "rgsm": "L2400196", # # "rglb": "L2400196", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400196/L2400196_S10_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400196/L2400196_S10_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400196", -# # "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400196/L2400196_S10_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400196/L2400196_S10_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400197", +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ" +# # }, # # "fastqListRow": { # # "rgid": "GTCGGAGC.TTATAACC.2.240229_A00130_0288_BH5HM2DSXC.L2400197", # # "rgsm": "L2400197", # # "rglb": "L2400197", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400197/L2400197_S11_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400197/L2400197_S11_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400197", -# # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400197/L2400197_S11_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400197/L2400197_S11_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400231", +# # "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED" +# # }, # # "fastqListRow": { # # "rgid": "TCGTAGTG.CCAAGTCT.2.240229_A00130_0288_BH5HM2DSXC.L2400231", # # "rgsm": "L2400231", # # "rglb": "L2400231", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400231/L2400231_S12_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400231/L2400231_S12_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400231", -# # "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400231/L2400231_S12_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400231/L2400231_S12_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400238", +# # "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0" +# # }, # # "fastqListRow": { # # "rgid": "GGAGCGTC.GCACGGAC.2.240229_A00130_0288_BH5HM2DSXC.L2400238", # # "rgsm": "L2400238", # # "rglb": "L2400238", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400238/L2400238_S13_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400238/L2400238_S13_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400238", -# # "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400238/L2400238_S13_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400238/L2400238_S13_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400239", +# # "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8" +# # }, # # "fastqListRow": { # # "rgid": "ATGGCATG.GGTACCTT.2.240229_A00130_0288_BH5HM2DSXC.L2400239", # # "rgsm": "L2400239", # # "rglb": "L2400239", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400239/L2400239_S14_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400239/L2400239_S14_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400239", -# # "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400239/L2400239_S14_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400239/L2400239_S14_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400240", +# # "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83" +# # }, # # "fastqListRow": { # # "rgid": "GCAATGCA.AACGTTCC.2.240229_A00130_0288_BH5HM2DSXC.L2400240", # # "rgsm": "L2400240", # # "rglb": "L2400240", # # "lane": 2, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400240/L2400240_S15_L002_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_2/L2400240/L2400240_S15_L002_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400240", -# # "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400240/L2400240_S15_L002_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_2/L2400240/L2400240_S15_L002_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400195", +# # "orcabusId": "lib.01J8ES4ZMY0G1H9MDN7K2TH9Y6" +# # }, # # "fastqListRow": { # # "rgid": "ATGAGGCC.CAATTAAC.3.240229_A00130_0288_BH5HM2DSXC.L2400195", # # "rgsm": "L2400195", # # "rglb": "L2400195", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400195/L2400195_S9_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400195/L2400195_S9_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400195", -# # "orcabusId": "lib.01J5S9CDQSSAG1WYCRWMD82Z1S" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400195/L2400195_S9_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400195/L2400195_S9_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400196", +# # "orcabusId": "lib.01J8ES4ZP88X2E17X5X1FRMTPK" +# # }, # # "fastqListRow": { # # "rgid": "ACTAAGAT.CCGCGGTT.3.240229_A00130_0288_BH5HM2DSXC.L2400196", # # "rgsm": "L2400196", # # "rglb": "L2400196", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400196/L2400196_S10_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400196/L2400196_S10_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400196", -# # "orcabusId": "lib.01J5S9CDSJ2BGEYM8FTXGKVGV8" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400196/L2400196_S10_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400196/L2400196_S10_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400197", +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ" +# # }, # # "fastqListRow": { # # "rgid": "GTCGGAGC.TTATAACC.3.240229_A00130_0288_BH5HM2DSXC.L2400197", # # "rgsm": "L2400197", # # "rglb": "L2400197", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400197/L2400197_S11_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400197/L2400197_S11_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400197", -# # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400197/L2400197_S11_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400197/L2400197_S11_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400231", +# # "orcabusId": "lib.01J8ES51V0RSVT6C7WQR72QQED" +# # }, # # "fastqListRow": { # # "rgid": "TCGTAGTG.CCAAGTCT.3.240229_A00130_0288_BH5HM2DSXC.L2400231", # # "rgsm": "L2400231", # # "rglb": "L2400231", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400231/L2400231_S12_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400231/L2400231_S12_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400231", -# # "orcabusId": "lib.01J5S9CFX5P69S4KZRQGDFKV1N" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400231/L2400231_S12_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400231/L2400231_S12_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400238", +# # "orcabusId": "lib.01J8ES52889Q8826P5SH9HDPP0" +# # }, # # "fastqListRow": { # # "rgid": "GGAGCGTC.GCACGGAC.3.240229_A00130_0288_BH5HM2DSXC.L2400238", # # "rgsm": "L2400238", # # "rglb": "L2400238", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400238/L2400238_S13_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400238/L2400238_S13_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400238", -# # "orcabusId": "lib.01J5S9CGCAKQWHD9RBM9VXENY9" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400238/L2400238_S13_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400238/L2400238_S13_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400239", +# # "orcabusId": "lib.01J8ES52ANMRT3B7Y96T1Y3RY8" +# # }, # # "fastqListRow": { # # "rgid": "ATGGCATG.GGTACCTT.3.240229_A00130_0288_BH5HM2DSXC.L2400239", # # "rgsm": "L2400239", # # "rglb": "L2400239", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400239/L2400239_S14_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400239/L2400239_S14_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400239", -# # "orcabusId": "lib.01J5S9CGEM1DHRQP72EP09B2TA" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400239/L2400239_S14_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400239/L2400239_S14_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400240", +# # "orcabusId": "lib.01J8ES52C3N585BGGY4VNXHC83" +# # }, # # "fastqListRow": { # # "rgid": "GCAATGCA.AACGTTCC.3.240229_A00130_0288_BH5HM2DSXC.L2400240", # # "rgsm": "L2400240", # # "rglb": "L2400240", # # "lane": 3, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400240/L2400240_S15_L003_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_3/L2400240/L2400240_S15_L003_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400240", -# # "orcabusId": "lib.01J5S9CGG9N9GH5879SY6A6BJB" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400240/L2400240_S15_L003_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_3/L2400240/L2400240_S15_L003_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400165", +# # "orcabusId": "lib.01J8ES4Y3ZKRX3C5JAHA5NBXV1" +# # }, # # "fastqListRow": { # # "rgid": "ACGCCTTGTT.ACGTTCCTTA.4.240229_A00130_0288_BH5HM2DSXC.L2400165", # # "rgsm": "L2400165", # # "rglb": "L2400165", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400165/L2400165_S16_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400165/L2400165_S16_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400165", -# # "orcabusId": "lib.01J5S9CBTZRYQNTGAHPC2T601D" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400165/L2400165_S16_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400165/L2400165_S16_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400191", +# # "orcabusId": "lib.01J8ES4ZDRQAP2BN3SDYYV5PKW" +# # }, # # "fastqListRow": { # # "rgid": "GCACGGAC.TGCGAGAC.4.240229_A00130_0288_BH5HM2DSXC.L2400191", # # "rgsm": "L2400191", # # "rglb": "L2400191", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400191/L2400191_S17_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400191/L2400191_S17_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400191", -# # "orcabusId": "lib.01J5S9CDF8HHG5PJE3ECJMKMY7" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400191/L2400191_S17_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400191/L2400191_S17_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400197", +# # "orcabusId": "lib.01J8ES4ZST489C712CG3R9NQSQ" +# # }, # # "fastqListRow": { # # "rgid": "GTCGGAGC.TTATAACC.4.240229_A00130_0288_BH5HM2DSXC.L2400197", # # "rgsm": "L2400197", # # "rglb": "L2400197", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400197/L2400197_S11_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400197/L2400197_S11_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400197", -# # "orcabusId": "lib.01J5S9CDVEHDZHZR3BZTQ7WNJQ" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400197/L2400197_S11_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400197/L2400197_S11_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400198", +# # "orcabusId": "lib.01J8ES4ZVWA2CGBHJVKAS3Y0G9" +# # }, # # "fastqListRow": { # # "rgid": "CTTGGTAT.GGACTTGG.4.240229_A00130_0288_BH5HM2DSXC.L2400198", # # "rgsm": "L2400198", # # "rglb": "L2400198", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400198/L2400198_S18_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400198/L2400198_S18_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400198", -# # "orcabusId": "lib.01J5S9CDXCR7Q5K6A8VJRSMM4Q" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400198/L2400198_S18_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400198/L2400198_S18_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400241", +# # "orcabusId": "lib.01J8ES52DHAPZM6FZ0VZK89PRT" +# # }, # # "fastqListRow": { # # "rgid": "GTTCCAAT.GCAGAATT.4.240229_A00130_0288_BH5HM2DSXC.L2400241", # # "rgsm": "L2400241", # # "rglb": "L2400241", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400241/L2400241_S19_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400241/L2400241_S19_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400241", -# # "orcabusId": "lib.01J5S9CGJ6G09YQ9KFHPSXMMVD" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400241/L2400241_S19_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400241/L2400241_S19_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400242", +# # "orcabusId": "lib.01J8ES52F2ZHRXQY1AT1N1F81F" +# # }, # # "fastqListRow": { # # "rgid": "ACCTTGGC.ATGAGGCC.4.240229_A00130_0288_BH5HM2DSXC.L2400242", # # "rgsm": "L2400242", # # "rglb": "L2400242", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400242/L2400242_S20_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400242/L2400242_S20_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400242", -# # "orcabusId": "lib.01J5S9CGKWDN7STKZKQM3KH9XR" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400242/L2400242_S20_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400242/L2400242_S20_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400249", +# # "orcabusId": "lib.01J8ES52XYMVGRB1Q458THNG4T" +# # }, # # "fastqListRow": { # # "rgid": "AGTTTCGA.CCTACGAT.4.240229_A00130_0288_BH5HM2DSXC.L2400249", # # "rgsm": "L2400249", # # "rglb": "L2400249", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400249/L2400249_S21_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400249/L2400249_S21_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400249", -# # "orcabusId": "lib.01J5S9CH2SQ0P1SF7WAT5H4DSE" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400249/L2400249_S21_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400249/L2400249_S21_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400250", +# # "orcabusId": "lib.01J8ES52Z2KTVVKZ2ZGVQ6YC10" +# # }, # # "fastqListRow": { # # "rgid": "GAACCTCT.GTCTGCGC.4.240229_A00130_0288_BH5HM2DSXC.L2400250", # # "rgsm": "L2400250", # # "rglb": "L2400250", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400250/L2400250_S22_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400250/L2400250_S22_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400250", -# # "orcabusId": "lib.01J5S9CH4CYPA4SP05H8KRX4W9" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400250/L2400250_S22_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400250/L2400250_S22_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400251", +# # "orcabusId": "lib.01J8ES530H895X4WA3NQ6CY2QV" +# # }, # # "fastqListRow": { # # "rgid": "GCCCAGTG.CCGCAATT.4.240229_A00130_0288_BH5HM2DSXC.L2400251", # # "rgsm": "L2400251", # # "rglb": "L2400251", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400251/L2400251_S23_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400251/L2400251_S23_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400251", -# # "orcabusId": "lib.01J5S9CH65E4EE5QJEJ1C60GGG" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400251/L2400251_S23_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400251/L2400251_S23_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400252", +# # "orcabusId": "lib.01J8ES5320EWBNNYDGXF2SYJBD" +# # }, # # "fastqListRow": { # # "rgid": "TGACAGCT.CCCGTAGG.4.240229_A00130_0288_BH5HM2DSXC.L2400252", # # "rgsm": "L2400252", # # "rglb": "L2400252", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400252/L2400252_S24_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400252/L2400252_S24_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400252", -# # "orcabusId": "lib.01J5S9CH7TGZMV39Z59WJ8H5GP" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400252/L2400252_S24_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400252/L2400252_S24_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400253", +# # "orcabusId": "lib.01J8ES533DJZZNPP9MXYR5TRC0" +# # }, # # "fastqListRow": { # # "rgid": "CATCACCC.ATATAGCA.4.240229_A00130_0288_BH5HM2DSXC.L2400253", # # "rgsm": "L2400253", # # "rglb": "L2400253", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400253/L2400253_S25_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400253/L2400253_S25_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400253", -# # "orcabusId": "lib.01J5S9CH9TGMT2TJGBZX5VXHJY" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400253/L2400253_S25_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400253/L2400253_S25_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400254", +# # "orcabusId": "lib.01J8ES534XGBFYDVYV8ZG6SYS0" +# # }, # # "fastqListRow": { # # "rgid": "CTGGAGTA.GTTCGGTT.4.240229_A00130_0288_BH5HM2DSXC.L2400254", # # "rgsm": "L2400254", # # "rglb": "L2400254", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400254/L2400254_S26_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400254/L2400254_S26_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400254", -# # "orcabusId": "lib.01J5S9CHBGAP2XSN4TG8SAMRYY" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400254/L2400254_S26_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400254/L2400254_S26_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400255", +# # "orcabusId": "lib.01J8ES536AB5A5PBJ8S45SZP7Q" +# # }, # # "fastqListRow": { # # "rgid": "GATCCGGG.AAGCAGGT.4.240229_A00130_0288_BH5HM2DSXC.L2400255", # # "rgsm": "L2400255", # # "rglb": "L2400255", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400255/L2400255_S27_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400255/L2400255_S27_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400255", -# # "orcabusId": "lib.01J5S9CHE4ERQ4H209DH397W8A" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400255/L2400255_S27_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400255/L2400255_S27_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400256", +# # "orcabusId": "lib.01J8ES537S0W1AX9PQPST13GM9" +# # }, # # "fastqListRow": { # # "rgid": "AACACCTG.CGCATGGG.4.240229_A00130_0288_BH5HM2DSXC.L2400256", # # "rgsm": "L2400256", # # "rglb": "L2400256", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400256/L2400256_S28_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400256/L2400256_S28_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400256", -# # "orcabusId": "lib.01J5S9CHFXPDGYQ8TXHRWQR3PY" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400256/L2400256_S28_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400256/L2400256_S28_L004_R2_001.fastq.gz" # # } # # }, # # { +# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", +# # "library": { +# # "libraryId": "L2400257", +# # "orcabusId": "lib.01J8ES5395KETT9T2NJSVNDKNP" +# # }, # # "fastqListRow": { # # "rgid": "GTGACGTT.TCCCAGAT.4.240229_A00130_0288_BH5HM2DSXC.L2400257", # # "rgsm": "L2400257", # # "rglb": "L2400257", # # "lane": 4, -# # "read1FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400257/L2400257_S29_L004_R1_001.fastq.gz", -# # "read2FileUri": "icav2://ea19a3f5-ec7c-4940-a474-c31cd91dbad4/primary/240229_A00130_0288_BH5HM2DSXC/2024071110689063/Samples/Lane_4/L2400257/L2400257_S29_L004_R2_001.fastq.gz" -# # }, -# # "instrumentRunId": "240229_A00130_0288_BH5HM2DSXC", -# # "library": { -# # "libraryId": "L2400257", -# # "orcabusId": "lib.01J5S9CHHNGFJN73NPRQMSYGN9" +# # "read1FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400257/L2400257_S29_L004_R1_001.fastq.gz", +# # "read2FileUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/primary/240229_A00130_0288_BH5HM2DSXC/202409108ed29dcc/Samples/Lane_4/L2400257/L2400257_S29_L004_R2_001.fastq.gz" # # } # # } # # ], diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/step_functions_templates/fastq_list_row_event_shower_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/step_functions_templates/fastq_list_row_event_shower_sfn_template.asl.json index b34aa9377..15d8646d2 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/step_functions_templates/fastq_list_row_event_shower_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/clag/part_2/fastq-list-rows-event-shower/step_functions_templates/fastq_list_row_event_shower_sfn_template.asl.json @@ -78,20 +78,20 @@ "TableName": "${__table_name__}", "Key": { "id.$": "$.inputs.payload.data.outputs.instrumentRunId", - "id_type": "${__samplesheet_table_partition_name__}" + "id_type": "${__instrument_run_table_partition_name__}" } }, "ResultSelector": { - "library_ids.$": "$.Item.library_ids_set.SS" + "library_orcabus_ids.$": "$.Item.library_set.SS" }, "ResultPath": "$.get_libraries_step", "Next": "Get Library Objects Map" }, "Get Library Objects Map": { "Type": "Map", - "ItemsPath": "$.get_libraries_step.library_ids", + "ItemsPath": "$.get_libraries_step.library_orcabus_ids", "ItemSelector": { - "library_id.$": "$$.Map.Item.Value" + "library_orcabus_id.$": "$$.Map.Item.Value" }, "ItemProcessor": { "ProcessorConfig": { @@ -105,17 +105,12 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_table_partition_name__}" } }, "ResultSelector": { - "library_obj": { - "library_id.$": "$.Item.library_id.S", - "orcabus_id.$": "$.Item.orcabus_id.S", - "project_name.$": "$.Item.project_name.S", - "project_owner.$": "$.Item.project_owner.S" - } + "library_obj.$": "States.StringToJson($.Item.library_obj.S)" }, "End": true } @@ -127,11 +122,75 @@ "End": true } } + }, + { + "StartAt": "Get Projects in Instrument Run", + "States": { + "Get Projects in Instrument Run": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.inputs.payload.data.outputs.instrumentRunId", + "id_type": "${__instrument_run_table_partition_name__}" + } + }, + "ResultSelector": { + "project_orcabus_ids.$": "$.Item.project_set.SS" + }, + "ResultPath": "$.get_projects_step", + "Next": "Get Project Objects Map" + }, + "Get Project Objects Map": { + "Type": "Map", + "ItemsPath": "$.get_projects_step.project_orcabus_ids", + "ItemSelector": { + "project_orcabus_id.$": "$$.Map.Item.Value" + }, + "ItemProcessor": { + "ProcessorConfig": { + "Mode": "INLINE" + }, + "StartAt": "Get Project", + "States": { + "Get Project": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.project_orcabus_id", + "id_type": "${__project_table_partition_name__}" + } + }, + "ResultSelector": { + "project_obj.$": "States.StringToJson($.Item.project_obj.S)", + "library_orcabus_ids_set.$": "$.Item.library_set.SS" + }, + "Next": "Append library set to project object" + }, + "Append library set to project object": { + "Type": "Pass", + "End": true, + "Parameters": { + "project_obj.$": "States.JsonMerge($.project_obj, States.StringToJson(States.Format('\\{\"librarySet\":{}\\}', $.library_orcabus_ids_set)), false)" + } + } + } + }, + "ResultSelector": { + "project_objects_list.$": "$.[*].project_obj" + }, + "End": true + } + } } ], "ResultSelector": { "fastq_list_rows.$": "$.[0].decompress_fastq_list_rows_step.fastq_list_rows", - "library_objects_list.$": "$.[1].library_objects_list" + "library_objects_list.$": "$.[1].library_objects_list", + "project_objects_list.$": "$.[2].project_objects_list" }, "ResultPath": "$.get_inputs_step" }, @@ -143,6 +202,7 @@ "Payload": { "fastq_list_rows.$": "$.get_inputs_step.fastq_list_rows", "library_objs.$": "$.get_inputs_step.library_objects_list", + "project_objs.$": "$.get_inputs_step.project_objects_list", "instrument_run_id.$": "$.inputs.payload.data.outputs.instrumentRunId" } }, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/index.ts index 29d86adac..329062673 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/index.ts @@ -3,8 +3,7 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { BsshFastqCopyManagerDraftMakerConstruct } from './part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft'; -import { BsshFastqCopyManagerDraftToReadyMakerConstruct } from './part_2/bssh-fastq-copy-manager-draft-to-ready'; +import { BsshFastqCopyManagerReadyMakerConstruct } from './part_1/bclconvert-succeeded-to-bssh-fastq-copy'; /* Provide the glue to get from the bclconvertmanager success event @@ -12,9 +11,13 @@ To triggering the bsshFastqCopyManager */ export interface BclconvertToBsshFastqCopyEventHandlerConstructProps { + /* Event Bus */ eventBusObj: events.IEventBus; - inputMakerTableObj: dynamodb.ITableV2; + + /* SSM Parameter */ bsshOutputFastqCopyUriSsmParameterObj: ssm.IStringParameter; + + /* Secrets */ icav2AccessTokenSecretObj: secretsManager.ISecret; } @@ -39,36 +42,16 @@ export class BclconvertToBsshFastqCopyEventHandlerConstruct extends Construct { * Subscribes to the BCLConvertManagerEventHandler Stack outputs and creates the input for the BSSHFastqCopyManager * Generates the portal run id and submits a draft event */ - const bclconvertToBsshFastqCopyDraftMaker = new BsshFastqCopyManagerDraftMakerConstruct( + const bclconvertToBsshFastqCopyDraftMaker = new BsshFastqCopyManagerReadyMakerConstruct( this, 'bclconvert_to_bssh_fastq_copy_draft_maker', { + /* Event Bus handler */ eventBusObj: props.eventBusObj, - tableObj: props.inputMakerTableObj, - } - ); - - /* - Part 2 - - Input Event Source: `orcabus.bclconvertmanagerinputeventglue` - Input Event DetailType: `WorkflowDraftRunStateChange` - Input Event status: `draft` - - Output Event source: `orcabus.bclconvertmanagerinputeventglue` - Output Event DetailType: `WorkflowDraftRunStateChange` - Output Event status: `ready` - - * Pushes an event payload of the input for the BsshFastqCopyManagerReadyEventSubmitter - */ - const bsshFastqCopyManagerInputMaker = new BsshFastqCopyManagerDraftToReadyMakerConstruct( - this, - 'bssh_fastq_copy_input_maker', - { - eventBusObj: props.eventBusObj, - outputUriSsmParameterObj: props.bsshOutputFastqCopyUriSsmParameterObj, - tableObj: props.inputMakerTableObj, + /* ICAv2 Secret */ icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + /* Output URI SSM Configuration Obj */ + outputUriSsmParameterObj: props.bsshOutputFastqCopyUriSsmParameterObj, } ); } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy/index.ts similarity index 69% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy/index.ts index f48315a9f..ba3e2f3ad 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy/index.ts @@ -7,10 +7,11 @@ import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import { LambdaB64GzTranslatorConstruct } from '../../../../../../../components/python-lambda-b64gz-translator'; -import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; -import { Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'; import { GetLibraryObjectsFromSamplesheetConstruct } from '../../../../../../../components/python-lambda-get-metadata-objects-from-samplesheet'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; /* Part 1 @@ -22,8 +23,8 @@ Part 1 * Output Event source: `orcabus.bsshfastqcopyinputeventglue` -* Output Event DetailType: `WorkflowDraftRunStateChange` -* Output Event status: `draft` +* Output Event DetailType: `WorkflowRunStateChange` +* Output Event status: `READY` * The BCLConvertSucceededToBsshFastqCopyDraft Construct @@ -33,21 +34,23 @@ Part 1 */ export interface bsshFastqCopyManagerDraftMakerConstructProps { - tableObj: dynamodb.ITableV2; + /* Event bus object handler */ eventBusObj: events.IEventBus; + /* SSM Parameter for the output uri */ + outputUriSsmParameterObj: ssm.IStringParameter; + /* Secret for icav2 access token */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { +export class BsshFastqCopyManagerReadyMakerConstruct extends Construct { public readonly bsshFastqCopyManagerDraftMakerEventMap = { - prefix: 'elmer-bclconv-2-bssh-fq-copy-draft', + prefix: 'elmer-bclconv-2-bssh-fq-copy', portalRunPartitionName: 'portal_run', triggerSource: 'orcabus.workflowmanager', triggerStatus: 'succeeded', triggerDetailType: 'WorkflowRunStateChange', triggerWorkflowName: 'bclconvert', outputSource: 'orcabus.bsshfastqcopyinputeventglue', - outputDetailType: 'WorkflowDraftRunStateChange', - outputStatus: 'DRAFT', payloadVersion: '2024.05.24', workflowName: 'bsshFastqCopy', workflowVersion: '2024.05.24', @@ -59,14 +62,11 @@ export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, `${this.bsshFastqCopyManagerDraftMakerEventMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: - this.bsshFastqCopyManagerDraftMakerEventMap.portalRunPartitionName, stateMachinePrefix: this.bsshFastqCopyManagerDraftMakerEventMap.prefix, - tableObj: props.tableObj, workflowName: this.bsshFastqCopyManagerDraftMakerEventMap.workflowName, workflowVersion: this.bsshFastqCopyManagerDraftMakerEventMap.workflowVersion, } @@ -94,9 +94,35 @@ export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { ).lambdaObj; /* - Part 2: Build the sfn + Part 2: Build the engine parameters sfn through the construct */ - const draftMakerSfn = new sfn.StateMachine(this, 'bclconvert_succeeded_to_bssh_draft', { + const engineParametersAndReadyLaunchSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'bclconvert_succeeded_to_bssh_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.bsshFastqCopyManagerDraftMakerEventMap.outputSource, + payloadVersion: this.bsshFastqCopyManagerDraftMakerEventMap.payloadVersion, + workflowName: this.bsshFastqCopyManagerDraftMakerEventMap.workflowName, + workflowVersion: this.bsshFastqCopyManagerDraftMakerEventMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.bsshFastqCopyManagerDraftMakerEventMap.prefix, + stateMachinePrefix: this.bsshFastqCopyManagerDraftMakerEventMap.prefix, + } + ).stepFunctionObj; + + /* + Part 2: Build the inputs sfn + */ + const inputsMakerSfn = new sfn.StateMachine(this, 'bclconvert_succeeded_to_bssh_draft', { stateMachineName: `${this.bsshFastqCopyManagerDraftMakerEventMap.prefix}-sfn`, definitionBody: sfn.DefinitionBody.fromFile( path.join( @@ -106,16 +132,9 @@ export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { ) ), definitionSubstitutions: { - // Event stuff - __event_bus_name__: props.eventBusObj.eventBusName, - __event_source__: this.bsshFastqCopyManagerDraftMakerEventMap.outputSource, - __detail_type__: this.bsshFastqCopyManagerDraftMakerEventMap.outputDetailType, - // Workflow stuff - __workflow_name__: this.bsshFastqCopyManagerDraftMakerEventMap.workflowName, - __workflow_version__: this.bsshFastqCopyManagerDraftMakerEventMap.workflowVersion, - __payload_version__: this.bsshFastqCopyManagerDraftMakerEventMap.payloadVersion, // Subfunctions - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParametersAndReadyLaunchSfn.stateMachineArn, // Lambda __decompression_samplesheet_lambda_function_arn__: decompressSamplesheetLambda.currentVersion.functionArn, @@ -125,22 +144,17 @@ export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { }); /* - Part 2: Grant the sfn permissions + Part 2a: Grant the sfn permissions */ - // Read/write to the table - props.tableObj.grantReadWriteData(draftMakerSfn); - - // Allow the step function to submit events - props.eventBusObj.grantPutEventsTo(draftMakerSfn); // Allow the step function to launch the lambdas [decompressSamplesheetLambda, getLibrarySubjectMapLambda].forEach((lambda) => { - lambda.currentVersion.grantInvoke(draftMakerSfn); + lambda.currentVersion.grantInvoke(inputsMakerSfn); }); // Because we run a nested state machine, we need to add the permissions to the state machine role // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr - draftMakerSfn.addToRolePolicy( + inputsMakerSfn.addToRolePolicy( new iam.PolicyStatement({ resources: [ `arn:aws:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule`, @@ -150,7 +164,8 @@ export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { ); // Add state machine execution permissions to stateMachine role - sfn_preamble.grantStartExecution(draftMakerSfn.role); + sfnPreamble.grantStartExecution(inputsMakerSfn); + engineParametersAndReadyLaunchSfn.grantStartExecution(inputsMakerSfn); const eventRule = new events.Rule(this, 'update_database_on_new_samplesheet_event_rule', { ruleName: `stacky-${this.bsshFastqCopyManagerDraftMakerEventMap.prefix}-event-rule`, @@ -173,7 +188,7 @@ export class BsshFastqCopyManagerDraftMakerConstruct extends Construct { // Add target to event rule eventRule.addTarget( - new eventsTargets.SfnStateMachine(draftMakerSfn, { + new eventsTargets.SfnStateMachine(inputsMakerSfn, { input: events.RuleTargetInput.fromEventPath('$.detail'), }) ); diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json similarity index 78% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json index 261247c8e..49b0f3679 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy/step_functions_templates/bclconvert_succeeded_to_bssh_fastq_copy_draft_template.asl.json @@ -11,7 +11,7 @@ }, "Get Workflow Inputs": { "Type": "Parallel", - "Next": "Push Draft Event", + "Next": "Launch Ready Event", "Branches": [ { "StartAt": "Get Portal Run ID and Workflow Run Name", @@ -130,42 +130,29 @@ }, "ResultPath": "$.get_workflow_inputs_step" }, - "Push Draft Event": { + "Launch Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}", - "DetailType": "${__detail_type__}", - "Detail": { - "portalRunId.$": "$.get_workflow_inputs_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "DRAFT", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_workflow_inputs_step.workflow_run_name", - "linkedLibraries.$": "$.get_workflow_inputs_step.linked_libraries_list", - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs": { - "instrumentRunId.$": "$.inputs.payload.data.instrumentRunId", - "bsshProjectId.$": "$.inputs.payload.data.projectId", - "bsshAnalysisId.$": "$.inputs.payload.data.analysisId" - }, - "tags": { - "instrumentRunId.$": "$.inputs.payload.data.instrumentRunId" - } - } - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_workflow_inputs_step.portal_run_id", + "workflow_run_name.$": "$.get_workflow_inputs_step.workflow_run_name", + "linked_libraries.$": "$.get_workflow_inputs_step.linked_libraries_list", + "data_inputs": { + "instrumentRunId.$": "$.inputs.payload.data.instrumentRunId", + "bsshProjectId.$": "$.inputs.payload.data.projectId", + "bsshAnalysisId.$": "$.inputs.payload.data.analysisId" + }, + "data_tags": { + "instrumentRunId.$": "$.inputs.payload.data.instrumentRunId" } } - ] + } }, - "End": true, - "ResultPath": null + "ResultPath": null, + "End": true } } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_2/bssh-fastq-copy-manager-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_2/bssh-fastq-copy-manager-draft-to-ready/index.ts deleted file mode 100644 index 35827cd76..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/elmer/part_2/bssh-fastq-copy-manager-draft-to-ready/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 2 - -* Input Event source: `orcabus.bsshfastqcopyinputeventglue` -* Input Event DetailType: `WorkflowDraftRunStateChange` -* Input Event status: `draft` - -* Output Event source: `orcabus.bsshfastqcopyinputeventglue` -* Output Event DetailType: `WorkflowRunStateChange` -* Output Event status: `ready` - -* The BsshFastqCopyManagerDraftToReadyMaker Construct - * Subscribes to the draft events generated by this stack - * Pushes an event payload of the input for the BSSH Fastq Copy Object -*/ - -export interface bsshFastqCopyManagerDraftToReadyMakerConstructProps { - tableObj: dynamodb.ITableV2; - outputUriSsmParameterObj: ssm.IStringParameter; - eventBusObj: events.IEventBus; - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class BsshFastqCopyManagerDraftToReadyMakerConstruct extends Construct { - public readonly bsshFastqCopyManagerDraftToReadyMakerEventMap = { - prefix: 'elmer-bssh-fastq-copy', - tablePartition: 'bssh_fastq_copy', - triggerSource: 'orcabus.bsshfastqcopyinputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.bsshfastqcopyinputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.05.24', - workflowName: 'bsshFastqCopy', - workflowVersion: '2024.05.24', - }; - - constructor( - scope: Construct, - id: string, - props: bsshFastqCopyManagerDraftToReadyMakerConstructProps - ) { - super(scope, id); - - /* - Part 1: Initialise the workflow draft run state change to workflow run state change construct - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'bssh_fastq_copy_manager_input_maker_external', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.prefix, - payloadVersion: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.payloadVersion, - stateMachinePrefix: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.prefix, - rulePrefix: `stacky-${this.bsshFastqCopyManagerDraftToReadyMakerEventMap.prefix}`, - - /* - Table objects - */ - tableObj: props.tableObj, - tablePartitionName: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerSource: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.triggerSource, - triggerStatus: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.triggerStatus, - - /* - Event Outputs - */ - workflowName: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.workflowName, - workflowVersion: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.workflowVersion, - outputSource: this.bsshFastqCopyManagerDraftToReadyMakerEventMap.outputSource, - - /* - Set the output uri - */ - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - - /* - ICAv2 Secret Construct - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/index.ts index a6addec6b..02cc6be96 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/index.ts @@ -3,9 +3,7 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { BsshFastqCopyManagerDraftMakerConstruct } from '../elmer/part_1/bclconvert-succeeded-to-bssh-fastq-copy-draft'; import { BclconvertInteropQcDraftMakerConstruct } from './part_1/bclconvert-interop-qc-draft-event-maker'; -import { BclconvertInteropQcDraftToReadyMakerConstruct } from './part_2/bclconvert-interop-qc-input-maker'; /* Provide the glue to get from the bclconvertmanager success event @@ -15,8 +13,6 @@ To triggering the bsshFastqCopyManager export interface BsshFastqCopyToBclconvertInteropQcConstructProps { /* Event Objects */ eventBusObj: events.IEventBus; - /* Table Objects */ - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameter Ojbects */ analysisLogsUriSsmParameterObj: ssm.IStringParameter; analysisOutputUriSsmParameterObj: ssm.IStringParameter; @@ -42,7 +38,7 @@ export class BsshFastqCopyToBclconvertInteropQcConstruct extends Construct { Output Event source: `orcabus.bclconvertinteropqcinputeventglue` Output Event DetailType: `WorkflowRunStateChange` - Output Event status: `complete` + Output Event status: `READY` * The BCLConvertInteropQCInputMaker Construct * Subscribes to the BSSHFastqCopyManagerEventHandler Construct outputs and creates the input for the BCLConvertInteropQC @@ -53,22 +49,15 @@ export class BsshFastqCopyToBclconvertInteropQcConstruct extends Construct { this, 'bssh_fastq_copy_complete_to_bclconvert_interop_qc_draft_maker', { + /* Event Bus */ eventBusObj: props.eventBusObj, - tableObj: props.inputMakerTableObj, + /* SSM Parameter Objects */ + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + /* Secrets Manager */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, } ); - - const bclconvertInteropqcInputMaker = new BclconvertInteropQcDraftToReadyMakerConstruct( - this, - 'bclconvert_interopqc_input_maker', - { - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - eventBusObj: props.eventBusObj, - tableObj: props.inputMakerTableObj, - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/index.ts index 92f2a4645..7d24da1e6 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/index.ts @@ -1,5 +1,4 @@ import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import path from 'path'; import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; import * as events from 'aws-cdk-lib/aws-events'; @@ -7,8 +6,9 @@ import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; -import { LambdaB64GzTranslatorConstruct } from '../../../../../../../components/python-lambda-b64gz-translator'; -import { GetLibraryObjectsFromSamplesheetConstruct } from '../../../../../../../components/python-lambda-get-metadata-objects-from-samplesheet'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; /* Part 1 @@ -32,8 +32,14 @@ Part 1 */ export interface bclconvertInteropQcDraftMakerConstructProps { - tableObj: dynamodb.ITableV2; + /* Event Bus */ eventBusObj: events.IEventBus; + /* SSM Parameter Objects */ + outputUriSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } export class BclconvertInteropQcDraftMakerConstruct extends Construct { @@ -45,8 +51,6 @@ export class BclconvertInteropQcDraftMakerConstruct extends Construct { triggerDetailType: 'WorkflowRunStateChange', triggerWorkflowName: 'bsshFastqCopy', outputSource: 'orcabus.bclconvertinteropqcinputeventglue', - outputDetailType: 'WorkflowDraftRunStateChange', - outputStatus: 'DRAFT', payloadVersion: '2024.05.24', workflowName: 'bclconvert-interop-qc', workflowVersion: '2024.05.24', @@ -62,19 +66,44 @@ export class BclconvertInteropQcDraftMakerConstruct extends Construct { this, `${this.bclconvertInteropQcDraftMakerEventMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: - this.bclconvertInteropQcDraftMakerEventMap.portalRunPartitionName, stateMachinePrefix: this.bclconvertInteropQcDraftMakerEventMap.prefix, - tableObj: props.tableObj, workflowName: this.bclconvertInteropQcDraftMakerEventMap.workflowName, workflowVersion: this.bclconvertInteropQcDraftMakerEventMap.workflowVersion, } ).stepFunctionObj; + /* + Part 2: Build the engine parameters sfn + */ + const engineParametersAndReadyLaunchSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'bssh_copy_complete_to_bclconvert_interop_qc_ready_ep_sfn', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.bclconvertInteropQcDraftMakerEventMap.outputSource, + payloadVersion: this.bclconvertInteropQcDraftMakerEventMap.payloadVersion, + workflowName: this.bclconvertInteropQcDraftMakerEventMap.workflowName, + workflowVersion: this.bclconvertInteropQcDraftMakerEventMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.bclconvertInteropQcDraftMakerEventMap.prefix, + stateMachinePrefix: this.bclconvertInteropQcDraftMakerEventMap.prefix, + } + ).stepFunctionObj; + /* Part 2: Build the sfn */ - const draftMakerSfn = new sfn.StateMachine(this, 'bssh_complete_to_bclconvert_sfn', { + const inputsMakerSfn = new sfn.StateMachine(this, 'bssh_complete_to_bclconvert_sfn', { stateMachineName: `${this.bclconvertInteropQcDraftMakerEventMap.prefix}-sfn`, definitionBody: sfn.DefinitionBody.fromFile( path.join( @@ -84,31 +113,23 @@ export class BclconvertInteropQcDraftMakerConstruct extends Construct { ) ), definitionSubstitutions: { - // Event stuff - __event_bus_name__: props.eventBusObj.eventBusName, - __event_source__: this.bclconvertInteropQcDraftMakerEventMap.outputSource, - __detail_type__: this.bclconvertInteropQcDraftMakerEventMap.outputDetailType, // Workflow stuff __workflow_name__: this.bclconvertInteropQcDraftMakerEventMap.workflowName, __workflow_version__: this.bclconvertInteropQcDraftMakerEventMap.workflowVersion, __payload_version__: this.bclconvertInteropQcDraftMakerEventMap.payloadVersion, // Subfunctions __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParametersAndReadyLaunchSfn.stateMachineArn, }, }); /* Part 2: Grant the sfn permissions */ - // Read/write to the table - props.tableObj.grantReadWriteData(draftMakerSfn); - - // Allow the step function to submit events - props.eventBusObj.grantPutEventsTo(draftMakerSfn); // Because we run a nested state machine, we need to add the permissions to the state machine role // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr - draftMakerSfn.addToRolePolicy( + inputsMakerSfn.addToRolePolicy( new iam.PolicyStatement({ resources: [ `arn:aws:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule`, @@ -118,7 +139,8 @@ export class BclconvertInteropQcDraftMakerConstruct extends Construct { ); // Add state machine execution permissions to stateMachine role - sfn_preamble.grantStartExecution(draftMakerSfn.role); + sfn_preamble.grantStartExecution(inputsMakerSfn); + engineParametersAndReadyLaunchSfn.grantStartExecution(inputsMakerSfn); /* Part 3: Subscribe to the event bus for this event type @@ -146,7 +168,7 @@ export class BclconvertInteropQcDraftMakerConstruct extends Construct { // Add target of event to be the state machine rule.addTarget( - new eventsTargets.SfnStateMachine(draftMakerSfn, { + new eventsTargets.SfnStateMachine(inputsMakerSfn, { input: events.RuleTargetInput.fromEventPath('$.detail'), }) ); diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/step_functions_templates/generate_bclconvert_interop_qc_draft_event_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/step_functions_templates/generate_bclconvert_interop_qc_draft_event_sfn_template.asl.json index afdda37a5..6062e1842 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/step_functions_templates/generate_bclconvert_interop_qc_draft_event_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_1/bclconvert-interop-qc-draft-event-maker/step_functions_templates/generate_bclconvert_interop_qc_draft_event_sfn_template.asl.json @@ -16,49 +16,36 @@ "StateMachineArn": "${__sfn_preamble_state_machine_arn__}", "Input": {} }, - "Next": "Push Draft Event", - "ResultPath": "$.get_sfn_preamble_outputs_step", "ResultSelector": { "portal_run_id.$": "$.Output.portal_run_id", "workflow_run_name.$": "$.Output.workflow_run_name" - } + }, + "ResultPath": "$.get_sfn_preamble_outputs_step", + "Next": "Launch Ready Event" }, - "Push Draft Event": { + "Launch Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}", - "DetailType": "${__detail_type__}", - "Detail": { - "portalRunId.$": "$.get_sfn_preamble_outputs_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "DRAFT", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_sfn_preamble_outputs_step.workflow_run_name", - "linkedLibraries.$": "$.inputs.linkedLibraries", - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs": { - "bclconvertReportDirectory.$": "States.Format('{}Reports/', $.inputs.payload.data.outputs.outputUri)", - "interopDirectory.$": "States.Format('{}InterOp/', $.inputs.payload.data.outputs.outputUri)", - "instrumentRunId.$": "$.inputs.payload.data.outputs.instrumentRunId" - }, - "tags": { - "instrumentRunId.$": "$.inputs.payload.data.outputs.instrumentRunId" - } - } - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_workflow_inputs_step.portal_run_id", + "workflow_run_name.$": "$.get_workflow_inputs_step.workflow_run_name", + "linked_libraries.$": "$.get_workflow_inputs_step.linked_libraries_list", + "data_inputs": { + "bclconvertReportDirectory.$": "States.Format('{}Reports/', $.inputs.payload.data.outputs.outputUri)", + "interopDirectory.$": "States.Format('{}InterOp/', $.inputs.payload.data.outputs.outputUri)", + "instrumentRunId.$": "$.inputs.payload.data.outputs.instrumentRunId" + }, + "data_tags": { + "instrumentRunId.$": "$.inputs.payload.data.outputs.instrumentRunId" } } - ] + } }, - "End": true, - "ResultPath": null + "ResultPath": null, + "End": true } } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_2/bclconvert-interop-qc-input-maker/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_2/bclconvert-interop-qc-input-maker/index.ts deleted file mode 100644 index 28964b963..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/gorilla/part_2/bclconvert-interop-qc-input-maker/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 1 - -* Input Event Source: `orcabus.bclconvertinteropqcinputeventglue` -* Input Event DetailType: `WorkflowDraftRunStateChange` -* Input Event WorkflowName: bclconvertInteropQc -* Input Event status: `draft` - -* Output Event source: `orcabus.bclconvertinteropqcinputeventglue` -* Output Event DetailType: `WorkflowRunStateChange` -* Output Event status: `ready` - - -* The bclconvertInteropQcDraftToReady Construct - * Subscribes to the bclconvert draft maker Stack outputs and creates the input for the BCLConvert Interop QC Pipeline -*/ - -export interface bclconvertInteropQcDraftToReadyMakerConstructProps { - tableObj: dynamodb.ITableV2; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - icav2AccessTokenSecretObj: secretsManager.ISecret; - eventBusObj: events.IEventBus; -} - -export class BclconvertInteropQcDraftToReadyMakerConstruct extends Construct { - public readonly bclconvertInteropQcDraftToReadyMakerEventMap = { - prefix: 'gorilla-interop-qc', - tablePartition: 'bclconvert_interop_qc', - triggerSource: 'orcabus.bclconvertinteropqcinputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.bclconvertinteropqcinputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.05.24', - workflowName: 'bclconvert-interop-qc', - workflowVersion: '2024.05.24', - }; - - constructor( - scope: Construct, - id: string, - props: bclconvertInteropQcDraftToReadyMakerConstructProps - ) { - super(scope, id); - - /* - Part 1: Initialise the workflow draft run state change to workflow run state change construct - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'bclconvert_interop_qc_draft_to_ready_sfn', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.bclconvertInteropQcDraftToReadyMakerEventMap.prefix, - payloadVersion: this.bclconvertInteropQcDraftToReadyMakerEventMap.payloadVersion, - stateMachinePrefix: this.bclconvertInteropQcDraftToReadyMakerEventMap.prefix, - rulePrefix: `stacky-${this.bclconvertInteropQcDraftToReadyMakerEventMap.prefix}-rule`, - - /* - Table objects - */ - tableObj: props.tableObj, - tablePartitionName: this.bclconvertInteropQcDraftToReadyMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerSource: this.bclconvertInteropQcDraftToReadyMakerEventMap.triggerSource, - triggerStatus: this.bclconvertInteropQcDraftToReadyMakerEventMap.triggerStatus, - - /* - Event Outputs - */ - workflowName: this.bclconvertInteropQcDraftToReadyMakerEventMap.workflowName, - workflowVersion: this.bclconvertInteropQcDraftToReadyMakerEventMap.workflowVersion, - outputSource: this.bclconvertInteropQcDraftToReadyMakerEventMap.outputSource, - - /* - Set the output uri - */ - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - - /* - Set the secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/index.ts index fdee87309..8db1bf7aa 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/index.ts @@ -4,6 +4,7 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as cdk from 'aws-cdk-lib'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; import { showerGlueHandlerConstruct } from './clag'; import { BclconvertToBsshFastqCopyEventHandlerConstruct } from './elmer'; import { BsshFastqCopyToBclconvertInteropQcConstruct } from './gorilla'; @@ -13,6 +14,7 @@ import { TnGlueHandlerConstruct } from './loctite'; import { WtsGlueHandlerConstruct } from './mod-podge'; import { UmccriseGlueHandlerConstruct } from './pva'; import { RnasumGlueHandlerConstruct } from './roket'; +import { PieriandxGlueHandlerConstruct } from './nails/'; /* Provide the glue to get from the bclconvertmanager success event @@ -22,6 +24,7 @@ To triggering the bsshFastqCopyManager export interface GlueConstructProps { /* Event Bus */ eventBusObj: events.IEventBus; + /* Tables */ instrumentRunTableObj: dynamodb.ITableV2; inputMakerTableObj: dynamodb.ITableV2; @@ -32,14 +35,23 @@ export interface GlueConstructProps { wtsGlueTableObj: dynamodb.ITableV2; umccriseGlueTableObj: dynamodb.ITableV2; rnasumGlueTableObj: dynamodb.ITableV2; - /* SSM Parameters */ + pieriandxGlueTableObj: dynamodb.ITableV2; + + /* Standard SSM Parameters */ icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - bsshOutputFastqCopyOutputUriSsmParameterObj: ssm.IStringParameter; analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisCacheUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; - /* Secrests */ + + /* Secrets */ icav2AccessTokenSecretObj: secretsManager.ISecret; + + /* BSSH SSM Parameters */ + bsshOutputFastqCopyOutputUriSsmParameterObj: ssm.IStringParameter; + + /* PierianDX SSM Parameters */ + pieriandxProjectInfoSsmParameterObj: ssm.IStringParameter; + redcapLambdaObj: lambda.IFunction; } export class GlueConstruct extends Construct { @@ -62,8 +74,6 @@ export class GlueConstruct extends Construct { const elmer = new BclconvertToBsshFastqCopyEventHandlerConstruct(this, 'elmer', { /* Event Bus */ eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, /* SSM Parameters */ bsshOutputFastqCopyUriSsmParameterObj: props.bsshOutputFastqCopyOutputUriSsmParameterObj, /* Secrets */ @@ -76,8 +86,6 @@ export class GlueConstruct extends Construct { const gorilla = new BsshFastqCopyToBclconvertInteropQcConstruct(this, 'gorilla', { /* Event Objects */ eventBusObj: props.eventBusObj, - /* Table Objects */ - inputMakerTableObj: props.inputMakerTableObj, /* SSM Parameter Ojbects */ analysisLogsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, analysisOutputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, @@ -93,7 +101,6 @@ export class GlueConstruct extends Construct { /* Event Bus */ eventBusObj: props.eventBusObj, /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, cttsov2GlueTableObj: props.cttsov2GlueTableObj, /* SSM Parameters */ icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, @@ -111,7 +118,6 @@ export class GlueConstruct extends Construct { /* Event Bus */ eventBusObj: props.eventBusObj, /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, wgtsQcGlueTableObj: props.wgtsQcGlueTableObj, /* SSM Parameters */ icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, @@ -129,7 +135,6 @@ export class GlueConstruct extends Construct { /* Event Bus */ eventBusObj: props.eventBusObj, /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, tnGlueTableObj: props.tnGlueTableObj, /* SSM Parameters */ icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, @@ -147,7 +152,6 @@ export class GlueConstruct extends Construct { /* Event Bus */ eventBusObj: props.eventBusObj, /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, wtsGlueTableObj: props.wtsGlueTableObj, /* SSM Parameters */ icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, @@ -159,13 +163,30 @@ export class GlueConstruct extends Construct { }); /* - Part H: Plumber-up the UMCCRise Execution Service to the shower services + Part H: Plumber-up the cttsov2 to pieriandx services + */ + const nails = new PieriandxGlueHandlerConstruct(this, 'nails', { + /* Event Bus */ + eventBusObj: props.eventBusObj, + + /* Tables */ + pieriandxGlueTableObj: props.pieriandxGlueTableObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Extras */ + pieriandxProjectInfoSsmParameterObj: props.pieriandxProjectInfoSsmParameterObj, + redcapLambdaObj: props.redcapLambdaObj, + }); + + /* + Part I: Plumber-up the UMCCRise Execution Service to the shower services */ const pva = new UmccriseGlueHandlerConstruct(this, 'pva', { /* Event Bus */ eventBusObj: props.eventBusObj, /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, umccriseGlueTableObj: props.umccriseGlueTableObj, /* SSM Parameters */ icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, @@ -177,13 +198,12 @@ export class GlueConstruct extends Construct { }); /* - Part I: Plumber-up the RNASum Exection Service to the shower services + Part J: Plumber-up the RNASum Execution Service to the shower services */ const roket = new RnasumGlueHandlerConstruct(this, 'roket', { /* Event Bus */ eventBusObj: props.eventBusObj, /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, rnasumGlueTableObj: props.rnasumGlueTableObj, /* SSM Parameters */ icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, @@ -199,6 +219,7 @@ export class GlueConstruct extends Construct { export interface GlueStackConfig { /* Event Bus */ eventBusName: string; + /* Tables */ instrumentRunTableName: string; inputMakerTableName: string; @@ -209,14 +230,25 @@ export interface GlueStackConfig { wtsGlueTableName: string; umccriseGlueTableName: string; rnasumGlueTableName: string; + pieriandxGlueTableName: string; + /* SSM Parameters */ icav2ProjectIdSsmParameterName: string; - bsshOutputFastqCopyUriSsmParameterName: string; analysisCacheUriSsmParameterName: string; analysisOutputUriSsmParameterName: string; analysisLogsUriSsmParameterName: string; + /* Secrets */ icav2AccessTokenSecretName: string; + + /* BSSH SSM Parameters */ + bsshOutputFastqCopyUriSsmParameterName: string; + + /* PierianDX SSM Parameters */ + pieriandxProjectInfoSsmParameterPath: string; + + /* PierianDX External Functions */ + redcapLambdaFunctionName: string; } export type GlueStackProps = GlueStackConfig & cdk.StackProps; @@ -226,17 +258,17 @@ export class GlueStack extends cdk.Stack { super(scope, id, props); /* - Part 0: Get the inputs as objects - */ + Part 0: Get the inputs as objects + */ /* - Get the event bus - */ + Get the event bus + */ const eventBusObj = events.EventBus.fromEventBusName(this, 'eventBusObj', props.eventBusName); /* - Get the tables - */ + Get the tables + */ const workflowManagerTableObj = dynamodb.Table.fromTableName( this, 'workflowManagerTableObj', @@ -282,6 +314,11 @@ export class GlueStack extends cdk.Stack { 'rnasumGlueTableObj', props.rnasumGlueTableName ); + const pieriandxGlueTableObj = dynamodb.Table.fromTableName( + this, + 'pieriandxGlueTableObj', + props.pieriandxGlueTableName + ); /* Get the SSM Parameters @@ -291,11 +328,6 @@ export class GlueStack extends cdk.Stack { 'icav2ProjectIdSsmParameterObj', props.icav2ProjectIdSsmParameterName ); - const bsshOutputFastqCopyUriSsmParameterObj = ssm.StringParameter.fromStringParameterName( - this, - 'bsshOutputFastqCopyUriPrefixSsmParameterObj', - props.bsshOutputFastqCopyUriSsmParameterName - ); const analysisCacheUriSsmParameterObj = ssm.StringParameter.fromStringParameterName( this, @@ -315,20 +347,48 @@ export class GlueStack extends cdk.Stack { ); /* - Secrets - */ + Secrets + */ const icav2AccessTokenSecretObj = secretsManager.Secret.fromSecretNameV2( this, 'icav2AccessTokenSecretObj', props.icav2AccessTokenSecretName ); + /* + BSSH SSM Parameters + */ + const bsshOutputFastqCopyUriSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'bsshOutputFastqCopyUriPrefixSsmParameterObj', + props.bsshOutputFastqCopyUriSsmParameterName + ); + + /* + PierianDx SSM Parameters + */ + const pieriandxProjectInfoSsmParameterObj = ssm.StringParameter.fromStringParameterName( + this, + 'pieriandxProjectInfoSsmParameterObj', + props.pieriandxProjectInfoSsmParameterPath + ); + + /* + PierianDx External Functions + */ + const redcapLambdaObj = lambda.Function.fromFunctionName( + this, + 'redcapLambdaObj', + props.redcapLambdaFunctionName + ); + /* Call the construct */ new GlueConstruct(this, 'stacky_glue', { /* Event stuff */ eventBusObj: eventBusObj, + /* Tables */ workflowManagerTableObj: workflowManagerTableObj, inputMakerTableObj: inputMakerTableObj, @@ -339,14 +399,25 @@ export class GlueStack extends cdk.Stack { wtsGlueTableObj: wtsGlueTableObj, umccriseGlueTableObj: umccriseGlueTableObj, rnasumGlueTableObj: rnasumGlueTableObj, + pieriandxGlueTableObj: pieriandxGlueTableObj, + /* SSM Parameters */ icav2ProjectIdSsmParameterObj: icav2ProjectIdSsmParameterObj, - bsshOutputFastqCopyOutputUriSsmParameterObj: bsshOutputFastqCopyUriSsmParameterObj, analysisOutputUriSsmParameterObj: analysisOutputUriSsmParameterObj, analysisCacheUriSsmParameterObj: analysisCacheUriSsmParameterObj, analysisLogsUriSsmParameterObj: analysisLogsUriSsmParameterObj, + /* Secrets */ icav2AccessTokenSecretObj: icav2AccessTokenSecretObj, + + /* BSSH SSM Parameters */ + bsshOutputFastqCopyOutputUriSsmParameterObj: bsshOutputFastqCopyUriSsmParameterObj, + + /* PierianDx SSM Parameters */ + pieriandxProjectInfoSsmParameterObj: pieriandxProjectInfoSsmParameterObj, + + /* PierianDx External Functions */ + redcapLambdaObj: redcapLambdaObj, }); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/index.ts index f2e91a446..8b91b42eb 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/index.ts @@ -3,11 +3,9 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { Cttsov2InitialiseInstrumentRunDbRowConstruct } from './part_1/initialise-cttsov2-instrument-dbs'; -import { Cttsov2InitialiseLibraryAndFastqListRowConstruct } from './part_2/initialise-cttsov2-library-dbs'; -import { Cttsov2PopulateFastqListRowConstruct } from './part_3/populate-fastq-list-row-dbs'; -import { Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct } from './part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft'; -import { Cttsov2InputMakerConstruct } from './part_5/cttsov2-draft-to-ready'; +import { Cttsov2InitialiseLibraryAndFastqListRowConstruct } from './part_1/initialise-cttsov2-library-dbs'; +import { Cttsov2PopulateFastqListRowConstruct } from './part_2/populate-fastq-list-row-dbs'; +import { Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct } from './part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready'; /* Provide the glue to get from the bssh fastq copy manager to submitting cttsov2 analyses @@ -18,7 +16,6 @@ export interface cttsov2GlueHandlerConstructProps { eventBusObj: events.IEventBus; /* Tables */ cttsov2GlueTableObj: dynamodb.ITableV2; - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameters */ analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; @@ -33,32 +30,14 @@ export class Cttsov2GlueHandlerConstruct extends Construct { super(scope, id); /* - Part 1 - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `SamplesheetShowerStateChange` - Input Event status: `SamplesheetRegisteredEventShowerStarting` + Part 1 - * Initialise cttsov2 instrument db construct - */ - const cttsov2_initialise_instrument_run_db_row = - new Cttsov2InitialiseInstrumentRunDbRowConstruct( - this, - 'initialise_cttsov2_instrument_run_db_row', - { - eventBusObj: props.eventBusObj, - tableObj: props.cttsov2GlueTableObj, - } - ); - - /* - Part 2 - - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `SamplesheetMetadataUnion` - Input Event status: `LibraryInSamplesheet` + Input Event Source: `orcabus.instrumentrunmanager` + Input Event DetailType: `SamplesheetMetadataUnion` + Input Event status: `LibraryInSamplesheet` - * Initialise cttsov2 instrument db construct - */ + * Initialise cttsov2 instrument db construct + */ const cttsov2_initialise_library_and_fastq_list_row = new Cttsov2InitialiseLibraryAndFastqListRowConstruct( this, @@ -70,14 +49,14 @@ export class Cttsov2GlueHandlerConstruct extends Construct { ); /* - Part 3 + Part 2 - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `FastqListRowStateChange` - Input Event status: `newFastqListRow` + Input Event Source: `orcabus.instrumentrunmanager` + Input Event DetailType: `FastqListRowStateChange` + Input Event status: `newFastqListRow` - * Populate the fastq list row attributes for the rgid for this workflow - */ + * Populate the fastq list row attributes for the rgid for this workflow + */ const cttsov2_populate_fastq_list_row = new Cttsov2PopulateFastqListRowConstruct( this, 'populate_cttsov2_fastq_list_row', @@ -88,56 +67,36 @@ export class Cttsov2GlueHandlerConstruct extends Construct { ); /* - Part 4 + Part 3 - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `FastqListRowStateChange` - Input Event status: `FastqListRowEventShowerComplete` + Input Event Source: `orcabus.instrumentrunmanager` + Input Event DetailType: `FastqListRowStateChange` + Input Event status: `FastqListRowEventShowerComplete` - Output Event source: `orcabus.cttsov2inputeventglue` - Output Event DetailType: `WorkflowDraftRunStateChange` - Output Event status: `draft` + Output Event source: `orcabus.cttsov2inputeventglue` + Output Event DetailType: `WorkflowDraftRunStateChange` + Output Event status: `draft` - * Trigger cttsov2 events collecting all cttsov2 in the run + * Trigger cttsov2 events collecting all cttsov2 in the run - */ - const fastq_list_row_shower_complete_to_workflow_draft = + */ + const fastq_list_row_shower_complete_to_workflow_ready = new Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct( this, 'fastq_list_row_shower_complete_to_workflow_draft', { - workflowsTableObj: props.inputMakerTableObj, + /* Events */ eventBusObj: props.eventBusObj, + /* Tables */ cttsov2GlueTableObj: props.cttsov2GlueTableObj, + /* SSM Param objects */ + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, + cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, } ); - - /* - Part 5 - Input Event source: `orcabus.cttsov2inputeventglue` - Input Event DetailType: `WorkflowDraftRunStateChange` - Input Event status: `draft` - - Output Event source: `orcabus.cttsov2inputeventglue` - Output Event DetailType: `WorkflowRunStateChange` - Output Event status: `ready` - */ - const fastqListRowsToctTSOv2InputMaker = new Cttsov2InputMakerConstruct( - this, - 'fastq_list_rows_to_cttso_v2_input_maker', - { - /* Event bus */ - eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, - /* SSM Param objects */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - /* Secrets */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/index.ts deleted file mode 100644 index 78ea2066f..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 1 - -Input Event Source: `orcabus.instrumentrunmanager` -Input Event DetailType: `SamplesheetShowerStateChange` -Input Event status: `SamplesheetRegisteredEventShowerStarting` - -* Initialise cttsov2 instrument db construct -*/ - -export interface Cttsov2InitialiseInstrumentRunDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class Cttsov2InitialiseInstrumentRunDbRowConstruct extends Construct { - public readonly Cttsov2InitialiseInstrumentRunDbRowMap = { - prefix: 'jbweld-make-instrument-run-row', - tablePartition: 'instrument_run', - triggerSource: 'orcabus.instrumentrunmanager', - triggerStatus: 'SamplesheetRegisteredEventShowerStarting', - triggerDetailType: 'SamplesheetShowerStateChange', - }; - - constructor( - scope: Construct, - id: string, - props: Cttsov2InitialiseInstrumentRunDbRowConstructProps - ) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'initialise_instrument_run_db_row', { - stateMachineName: `${this.Cttsov2InitialiseInstrumentRunDbRowMap.prefix}-initialise-run-db-row`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'initialise_cttsov2_instrument_run_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __instrument_run_partition_name__: - this.Cttsov2InitialiseInstrumentRunDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus and trigger the internal sfn - */ - const rule = new events.Rule(this, 'cttsov2_subscribe_to_samplesheet_shower', { - ruleName: `stacky-${this.Cttsov2InitialiseInstrumentRunDbRowMap.prefix}-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.Cttsov2InitialiseInstrumentRunDbRowMap.triggerSource], - detailType: [this.Cttsov2InitialiseInstrumentRunDbRowMap.triggerDetailType], - detail: { - status: [ - { 'equals-ignore-case': this.Cttsov2InitialiseInstrumentRunDbRowMap.triggerStatus }, - ], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/step_functions_templates/initialise_cttsov2_instrument_run_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/step_functions_templates/initialise_cttsov2_instrument_run_db_sfn_template.asl.json deleted file mode 100644 index d56d8d202..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-instrument-dbs/step_functions_templates/initialise_cttsov2_instrument_run_db_sfn_template.asl.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Set DB Inputs", - "States": { - "Set DB Inputs": { - "Type": "Pass", - "Next": "Initialise Instrument Run Item", - "ResultPath": "$.db_inputs", - "Parameters": { - "instrument_run_id.$": "$.payload.data.instrumentRunId" - } - }, - "Initialise Instrument Run Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id": { - "S.$": "$.db_inputs.instrument_run_id" - }, - "id_type": { - "S": "${__instrument_run_partition_name__}" - } - } - }, - "End": true, - "ResultPath": null - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/initialise-cttsov2-library-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-library-dbs/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/initialise-cttsov2-library-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-library-dbs/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json similarity index 73% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json index 0176936a8..f06d4e1c4 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_1/initialise-cttsov2-library-dbs/step_functions_templates/initialise_cttsov2_library_db_sfn_template.asl.json @@ -50,32 +50,6 @@ "Add DataBase Inputs": { "Type": "Parallel", "Branches": [ - { - "StartAt": "Append Library and FastqListRowID to Instrument Run ID", - "States": { - "Append Library and FastqListRowID to Instrument Run ID": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.input_payload_data.instrumentRunId", - "id_type": "${__instrument_run_partition_name__}" - }, - "UpdateExpression": "ADD library_set :library_set, fastq_list_row_id_set :fastq_list_row_id_set", - "ExpressionAttributeValues": { - ":library_set": { - "SS.$": "States.Array($.input_payload_data.library.libraryId)" - }, - ":fastq_list_row_id_set": { - "SS.$": "$.bclconvert_and_fastq_list_row_id_inputs[*].fastq_list_row_id" - } - } - }, - "End": true - } - } - }, { "StartAt": "Initialise Library ID", "States": { @@ -85,20 +59,16 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id": { - "S.$": "$.input_payload_data.library.libraryId" - }, - "id_type": { - "S": "${__library_partition_name__}" - }, - "orcabus_id": { - "S.$": "$.input_payload_data.library.orcabusId" - }, + "id.$": "$.input_payload_data.library.orcabusId", + "id_type": "${__library_partition_name__}", "library_id": { "S.$": "$.input_payload_data.library.libraryId" }, "fastq_list_row_id_set": { "SS.$": "$.bclconvert_and_fastq_list_row_id_inputs[*].fastq_list_row_id" + }, + "instrument_run_id": { + "S.$": "$.input_payload_data.instrumentRunId" } } }, @@ -145,12 +115,8 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id": { - "S.$": "$.fastq_list_row_id" - }, - "id_type": { - "S": "${__bclconvert_data_row_partition_name__}" - }, + "id.$": "$.fastq_list_row_id", + "id_type": "${__bclconvert_data_row_partition_name__}", "bclconvert_data_row": { "S.$": "States.JsonToString($.set_bclconvert_json)" } @@ -169,12 +135,8 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id": { - "S.$": "$.fastq_list_row_id" - }, - "id_type": { - "S": "${__fastq_list_row_partition_name__}" - } + "id.$": "$.fastq_list_row_id", + "id_type": "${__fastq_list_row_partition_name__}" } }, "End": true diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/populate-fastq-list-row-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/populate-fastq-list-row-dbs/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/populate-fastq-list-row-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/populate-fastq-list-row-dbs/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_2/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/Readme.md b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/Readme.md similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/Readme.md rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/Readme.md diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/index.ts similarity index 73% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/index.ts index e7b31e764..9bcf6e5e1 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/index.ts @@ -9,6 +9,9 @@ import * as lambda from 'aws-cdk-lib/aws-lambda'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; /* Part 4 @@ -19,15 +22,26 @@ Input Event status: `FastqListRowEventShowerComplete` Output Event source: `orcabus.cttsov2inputeventglue` Output Event DetailType: `WorkflowDraftRunStateChange` -Output Event status: `draft` +Output Event status: `READY` * Trigger cttsov2 events collecting all cttsov2 in the run */ export interface Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowConstructProps { - cttsov2GlueTableObj: dynamodb.ITableV2; - workflowsTableObj: dynamodb.ITableV2; + /* Event Obj */ eventBusObj: events.IEventBus; + + /* Tables */ + cttsov2GlueTableObj: dynamodb.ITableV2; + + /* SSM Parameters */ + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + outputUriSsmParameterObj: ssm.IStringParameter; + cacheUriSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends Construct { @@ -36,7 +50,6 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C prefix: 'jbweld-fqlr-shower-to-cttsov2', /* Table Partition Settings */ cttsov2GlueTablePartition: { - instrumentRun: 'instrument_run', library: 'library', bclconvertData: 'bclconvert_data', fastqListRow: 'fastq_list_row', @@ -79,14 +92,11 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, `${this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: - this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.portalRunPartitionName, stateMachinePrefix: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix, - tableObj: props.workflowsTableObj, workflowName: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowName, workflowVersion: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowVersion, @@ -94,9 +104,40 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C ).stepFunctionObj; /* - Part 2: Build the sfn + Part 2: Build the engine parameters sfn */ - const draftMakerSfn = new sfn.StateMachine( + const engineParameterAndReadyEventMakerSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'fastqlistrow_complete_to_cttsov2_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.outputSource, + payloadVersion: + this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.payloadVersion, + workflowName: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowName, + workflowVersion: + this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix, + stateMachinePrefix: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix, + } + ).stepFunctionObj; + + /* + Part 3: Build the sfn + */ + const inputMakerSfn = new sfn.StateMachine( this, 'fastq_list_row_complete_to_workflow_draft_run_events', { @@ -120,9 +161,6 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C __fastq_list_row_partition_name__: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap .cttsov2GlueTablePartition.fastqListRow, - __instrument_run_partition_name__: - this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap - .cttsov2GlueTablePartition.instrumentRun, __library_partition_name__: this.Cttsov2FastqListRowShowerCompleteToWorkflowDraftRunDbRowMap .cttsov2GlueTablePartition.library, @@ -148,7 +186,8 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C buildCttsoV2Samplesheet.currentVersion.functionArn, /* Subfunctions */ - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParameterAndReadyEventMakerSfn.stateMachineArn, }, } ); @@ -157,18 +196,15 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C Part 3: Grant the internal sfn permissions */ // access the dynamodb table - props.cttsov2GlueTableObj.grantReadWriteData(draftMakerSfn.role); + props.cttsov2GlueTableObj.grantReadWriteData(inputMakerSfn); // Allow the sfn to invoke the lambda - buildCttsoV2Samplesheet.currentVersion.grantInvoke(draftMakerSfn.role); - - // Allow the sfn to submit events to the event bus - props.eventBusObj.grantPutEventsTo(draftMakerSfn.role); + buildCttsoV2Samplesheet.currentVersion.grantInvoke(inputMakerSfn); /* Allow step function to call nested state machine */ // Because we run a nested state machine, we need to add the permissions to the state machine role // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr - draftMakerSfn.addToRolePolicy( + inputMakerSfn.addToRolePolicy( new iam.PolicyStatement({ resources: [ `arn:aws:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule`, @@ -177,7 +213,8 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C }) ); // Allow the state machine to be able to invoke the preamble sfn - sfn_preamble.grantStartExecution(draftMakerSfn.role); + sfnPreamble.grantStartExecution(inputMakerSfn); + engineParameterAndReadyEventMakerSfn.grantStartExecution(inputMakerSfn); /* Part 4: Subscribe to the event bus for this event type @@ -203,7 +240,7 @@ export class Cttsov2FastqListRowShowerCompleteToWorkflowDraftConstruct extends C // Add target of event to be the state machine rule.addTarget( - new eventsTargets.SfnStateMachine(draftMakerSfn, { + new eventsTargets.SfnStateMachine(inputMakerSfn, { input: events.RuleTargetInput.fromEventPath('$.detail'), }) ); diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/lambdas/build_cttsov2_samplesheet_py/build_cttso_v2_samplesheet.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/lambdas/build_cttsov2_samplesheet_py/build_cttso_v2_samplesheet.py similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/lambdas/build_cttsov2_samplesheet_py/build_cttso_v2_samplesheet.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/lambdas/build_cttsov2_samplesheet_py/build_cttso_v2_samplesheet.py diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json similarity index 76% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json index 9a940345a..539e41f0d 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_4/fastq-list-row-event-shower-complete-to-cttsov2-draft/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_3/fastq-list-row-event-shower-complete-to-cttsov2-ready/step_functions_templates/fastq_list_row_shower_complete_event_to_cttsov2_draft_events_sfn_template.asl.json @@ -11,25 +11,51 @@ }, "Get Libraries from Instrument Run": { "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", + "Resource": "arn:aws:states:::aws-sdk:dynamodb:scan", "Parameters": { "TableName": "${__table_name__}", - "Key": { - "id.$": "$.fastq_list_row_shower_complete_event_data.instrumentRunId", - "id_type": "${__instrument_run_partition_name__}" - } + "ExpressionAttributeValues": { + ":instrument_run_id": { + "S.$": "$.fastq_list_row_shower_complete_event_data.instrumentRunId" + }, + ":id_type": { + "S": "${__library_partition_name__}" + } + }, + "ExpressionAttributeNames": { + "#instrument_run_id": "instrument_run_id", + "#id_type": "id_type" + }, + "FilterExpression": "#instrument_run_id = :instrument_run_id AND #id_type = :id_type" }, - "ResultSelector": { - "library_list.$": "$.Item.library_set.SS" + "ResultPath": "$.get_libraries_on_instrument_run_step", + "Next": "Check library items is not empty" + }, + "Check library items is not empty": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_libraries_on_instrument_run_step.Items", + "IsPresent": true, + "Comment": "Items list exists", + "Next": "Collect Library Orcabus Ids" + } + ], + "Default": "Pass" + }, + "Collect Library Orcabus Ids": { + "Type": "Pass", + "Next": "Iterate over each library", + "Parameters": { + "library_orcabus_ids_list.$": "$.get_libraries_on_instrument_run_step.Items[*].id" }, - "ResultPath": "$.get_libraries_step", - "Next": "Iterate over each library" + "ResultPath": "$.collect_library_orcabus_ids_step" }, "Iterate over each library": { "Type": "Map", - "ItemsPath": "$.get_libraries_step.library_list", + "ItemsPath": "$.collect_library_orcabus_ids_step.library_orcabus_ids_list", "ItemSelector": { - "library_id.$": "$$.Map.Item.Value", + "library_orcabus_id.$": "$$.Map.Item.Value", "instrument_run_id.$": "$.fastq_list_row_shower_complete_event_data.instrumentRunId" }, "ItemProcessor": { @@ -50,13 +76,13 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_partition_name__}" } }, "ResultSelector": { "library_id.$": "$.Item.library_id.S", - "orcabus_id.$": "$.Item.orcabus_id.S", + "orcabus_id.$": "$.Item.id.S", "fastq_list_row_id_list.$": "$.Item.fastq_list_row_id_set.SS" }, "ResultPath": "$.get_library_obj_step", @@ -201,48 +227,40 @@ ] }, "ResultPath": "$.get_draft_inputs", - "Next": "Push cttsov2 draft event" + "Next": "Launch Ready Event" }, - "Push cttsov2 draft event": { + "Launch Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "Detail": { - "portalRunId.$": "$.get_draft_inputs.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "${__output_status__}", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_draft_inputs.workflow_run_name", - "linkedLibraries.$": "$.get_draft_inputs.linked_libraries", - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs": { - "sampleId.$": "$.get_draft_inputs.sample_id", - "instrumentRunId.$": "$.instrument_run_id", - "samplesheet.$": "$.get_draft_inputs.samplesheet", - "fastqListRows.$": "$.get_draft_inputs.fastq_list_rows" - }, - "tags": { - "libraryId.$": "$.get_draft_inputs.sample_id", - "fastqListRowIds.$": "$.get_draft_inputs.fastq_list_row_ids" - } - } - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_draft_inputs.portal_run_id", + "workflow_run_name.$": "$.get_draft_inputs.workflow_run_name", + "linked_libraries.$": "$.get_draft_inputs.linked_libraries", + "data_inputs": { + "sampleId.$": "$.get_draft_inputs.sample_id", + "instrumentRunId.$": "$.instrument_run_id", + "samplesheet.$": "$.get_draft_inputs.samplesheet", + "fastqListRows.$": "$.get_draft_inputs.fastq_list_rows" }, - "DetailType": "${__detail_type__}", - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}" + "data_tags": { + "libraryId.$": "$.get_draft_inputs.sample_id", + "fastqListRowIds.$": "$.get_draft_inputs.fastq_list_row_ids" + } } - ] + } }, + "ResultPath": null, "End": true } } }, + "Next": "Pass" + }, + "Pass": { + "Type": "Pass", "End": true } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_5/cttsov2-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_5/cttsov2-draft-to-ready/index.ts deleted file mode 100644 index 4a8eae4cb..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/jb-weld/part_5/cttsov2-draft-to-ready/index.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 5 - -Input Event source: `orcabus.cttsov2inputeventglue` -Input Event DetailType: `WorkflowDraftRunStateChange` -Input Event status: `draft` - -Output Event source: `orcabus.cttsov2inputeventglue` -Output Event DetailType: `WorkflowRunStateChange` -Output Event status: `ready` - -* The ctTSOv2InputMaker, subscribes to the cttsov2 input event glue (itself) and generates a ready event for the ctTSOv2ReadySfn - * For the cttso v2 workflow we require a samplesheet, a set of fastq list rows (provided in the last step) - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference -*/ - -export interface Cttsov2InputMakerConstructProps { - /* Event bus object */ - eventBusObj: events.IEventBus; - /* Tables */ - inputMakerTableObj: dynamodb.ITableV2; - /* SSM Parameter Objects */ - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - cacheUriSsmParameterObj: ssm.IStringParameter; - /* Secrets Objects */ - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class Cttsov2InputMakerConstruct extends Construct { - public readonly cttsov2InputMakerEventMap = { - prefix: 'jbweld-cttso-v2', - tablePartition: 'cttso_v2', - triggerSource: 'orcabus.cttsov2inputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - triggerWorkflowName: 'cttsov2', - outputSource: 'orcabus.cttsov2inputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.05.24', - workflowName: 'cttsov2', - workflowVersion: '2.6.0', - }; - - constructor(scope: Construct, id: string, props: Cttsov2InputMakerConstructProps) { - super(scope, id); - - /* - Part 1: Build the draft to ready maker - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'cttso_v2_draft_to_ready_sfn', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.cttsov2InputMakerEventMap.prefix, - payloadVersion: this.cttsov2InputMakerEventMap.payloadVersion, - stateMachinePrefix: this.cttsov2InputMakerEventMap.prefix, - rulePrefix: this.cttsov2InputMakerEventMap.prefix, - - /* - Table objects - */ - tableObj: props.inputMakerTableObj, - tablePartitionName: this.cttsov2InputMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerDetailType: this.cttsov2InputMakerEventMap.triggerDetailType, - triggerSource: this.cttsov2InputMakerEventMap.triggerSource, - triggerStatus: this.cttsov2InputMakerEventMap.triggerStatus, - outputSource: this.cttsov2InputMakerEventMap.outputSource, - workflowName: this.cttsov2InputMakerEventMap.workflowName, - workflowVersion: this.cttsov2InputMakerEventMap.workflowVersion, - - /* - SSM Parameters - */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, - - /* - Secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/index.ts index 816eb6387..b22ceb18a 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/index.ts @@ -3,13 +3,11 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { WgtsQcInitialiseInstrumentRunDbRowConstruct } from './part_1/initialise-wgts-instrument-run-db'; -import { WgtsQcInitialiseLibraryAndFastqListRowConstruct } from './part_2/initialise-wgts-library-dbs'; -import { WgtsQcPopulateFastqListRowConstruct } from './part_3/populate-fastq-list-row-dbs'; -import { WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct } from './part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft'; -import { WgtsQcInputMakerConstruct } from './part_5/wgts-qc-draft-to-ready'; -import { FastqListRowQcCompleteConstruct } from './part_6/push-fastq-list-row-qc-complete-event'; -import { WgtsQcLibraryQcCompleteConstruct } from './part_7/library-qc-complete-event'; +import { WgtsQcInitialiseLibraryAndFastqListRowConstruct } from './part_1/initialise-wgts-library-dbs'; +import { WgtsQcPopulateFastqListRowConstruct } from './part_2/populate-fastq-list-row-dbs'; +import { WgtsQcFastqListRowShowerCompleteToWorkflowReadyConstruct } from './part_3/fastq-list-rows-shower-complete-to-wgts-qc'; +import { FastqListRowQcCompleteConstruct } from './part_4/push-fastq-list-row-qc-complete-event'; +import { WgtsQcLibraryQcCompleteConstruct } from './part_5/library-qc-complete-event'; /* Provide the glue to get from the bssh fastq copy manager to submitting wgts qc analyses @@ -20,7 +18,6 @@ export interface wgtsQcGlueHandlerConstructProps { eventBusObj: events.IEventBus; /* Tables */ wgtsQcGlueTableObj: dynamodb.ITableV2; - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameters */ analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; @@ -36,24 +33,6 @@ export class WgtsQcGlueHandlerConstruct extends Construct { /* Part 1 - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `SamplesheetShowerStateChange` - Input Event status: `SamplesheetRegisteredEventShowerStarting` - - * Initialise wgts qc instrument db construct - */ - const wgts_qc_initialise_instrument_run_db_row = - new WgtsQcInitialiseInstrumentRunDbRowConstruct( - this, - 'initialise_wgts_qc_instrument_run_db_row', - { - eventBusObj: props.eventBusObj, - tableObj: props.wgtsQcGlueTableObj, - } - ); - - /* - Part 2 Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `SamplesheetMetadataUnion` @@ -72,7 +51,7 @@ export class WgtsQcGlueHandlerConstruct extends Construct { ); /* - Part 3 + Part 2 Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `FastqListRowStateChange` @@ -90,7 +69,7 @@ export class WgtsQcGlueHandlerConstruct extends Construct { ); /* - Part 4 + Part 3 Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `FastqListRowStateChange` @@ -98,52 +77,35 @@ export class WgtsQcGlueHandlerConstruct extends Construct { Output Event source: `orcabus.wgtsqcinputeventglue` Output Event DetailType: `WorkflowDraftRunStateChange` - Output Event status: `draft` + Output Event status: `READY` * Trigger wgts qc events collecting all wgts qc libraries in the run */ const fastq_list_row_shower_complete_to_workflow_draft = - new WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct( + new WgtsQcFastqListRowShowerCompleteToWorkflowReadyConstruct( this, 'fastq_list_row_shower_complete_to_workflow_draft', { - workflowsTableObj: props.inputMakerTableObj, + /* Events */ eventBusObj: props.eventBusObj, + + /* Tables */ wgtsQcGlueTableObj: props.wgtsQcGlueTableObj, - } - ); - /* - Part 5 - Input Event source: `orcabus.wgtsqcinputeventglue` - Input Event DetailType: `WorkflowDraftRunStateChange` - Input Event status: `draft` + /* SSM Parameters */ + cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - Output Event source: `orcabus.wgtsqcinputeventglue` - Output Event DetailType: `WorkflowRunStateChange` - Output Event status: `ready` - */ - const fastqListRowsToWgtsQcInputMaker = new WgtsQcInputMakerConstruct( - this, - 'fastq_list_rows_to_wgts_qc_input_maker', - { - /* Event bus */ - eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, - /* SSM Param objects */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - /* Secrets */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + } + ); /* - Part 6 + Part 4 Input Event Source: `orcabus.workflowmanager` Input Event DetailType: `WorkflowRunStateChange` @@ -168,7 +130,7 @@ export class WgtsQcGlueHandlerConstruct extends Construct { ); /* - Part 7 + Part 5 Input Event Source: `orcabus.wgtsqcinputeventglue` Input Event DetailType: `FastqListRowStateChange` diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/index.ts deleted file mode 100644 index 5de6224dc..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 1 - -Input Event Source: `orcabus.instrumentrunmanager` -Input Event DetailType: `SamplesheetShowerStateChange` -Input Event status: `SamplesheetRegisteredEventShowerStarting` - -* Initialise wgts instrument db construct -*/ - -export interface WgtsQcInitialiseInstrumentRunDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class WgtsQcInitialiseInstrumentRunDbRowConstruct extends Construct { - public readonly WgtsQcInitialiseInstrumentRunDbRowMap = { - prefix: 'kwik-make-instrument-run-row', - tablePartition: 'instrument_run', - triggerSource: 'orcabus.instrumentrunmanager', - triggerStatus: 'SamplesheetRegisteredEventShowerStarting', - triggerDetailType: 'SamplesheetShowerStateChange', - }; - - constructor( - scope: Construct, - id: string, - props: WgtsQcInitialiseInstrumentRunDbRowConstructProps - ) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'initialise_instrument_run_db_row', { - stateMachineName: `${this.WgtsQcInitialiseInstrumentRunDbRowMap.prefix}-initialise-run-db-row`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'initialise_wgts_instrument_run_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __instrument_run_partition_name__: - this.WgtsQcInitialiseInstrumentRunDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus and trigger the internal sfn - */ - const rule = new events.Rule(this, 'wgts_subscribe_to_samplesheet_shower', { - ruleName: `stacky-${this.WgtsQcInitialiseInstrumentRunDbRowMap.prefix}-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.WgtsQcInitialiseInstrumentRunDbRowMap.triggerSource], - detailType: [this.WgtsQcInitialiseInstrumentRunDbRowMap.triggerDetailType], - detail: { - status: [ - { 'equals-ignore-case': this.WgtsQcInitialiseInstrumentRunDbRowMap.triggerStatus }, - ], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/step_functions_templates/initialise_wgts_instrument_run_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/step_functions_templates/initialise_wgts_instrument_run_db_sfn_template.asl.json deleted file mode 100644 index d56d8d202..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-instrument-run-db/step_functions_templates/initialise_wgts_instrument_run_db_sfn_template.asl.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Set DB Inputs", - "States": { - "Set DB Inputs": { - "Type": "Pass", - "Next": "Initialise Instrument Run Item", - "ResultPath": "$.db_inputs", - "Parameters": { - "instrument_run_id.$": "$.payload.data.instrumentRunId" - } - }, - "Initialise Instrument Run Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id": { - "S.$": "$.db_inputs.instrument_run_id" - }, - "id_type": { - "S": "${__instrument_run_partition_name__}" - } - } - }, - "End": true, - "ResultPath": null - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/initialise-wgts-library-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-library-dbs/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/initialise-wgts-library-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-library-dbs/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json similarity index 78% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json index 1f95225bc..9c0f702e3 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_1/initialise-wgts-library-dbs/step_functions_templates/initialise_wgts_library_db_sfn_template.asl.json @@ -18,7 +18,8 @@ "index.$": "$$.Map.Item.Index", "fastq_list_row_objs.$": "$.input_payload_data.fastqListRows", "instrument_run_id.$": "$.input_payload_data.instrumentRunId", - "library_id.$": "$.input_payload_data.library.libraryId" + "library_id.$": "$.input_payload_data.library.libraryId", + "library_orcabus_id.$": "$.input_payload_data.library.orcabusId" }, "ItemProcessor": { "ProcessorConfig": { @@ -49,32 +50,6 @@ "Add DataBase Inputs": { "Type": "Parallel", "Branches": [ - { - "StartAt": "Append Library and FastqListRowID to Instrument Run ID", - "States": { - "Append Library and FastqListRowID to Instrument Run ID": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.input_payload_data.instrumentRunId", - "id_type": "${__instrument_run_partition_name__}" - }, - "UpdateExpression": "ADD library_set :library_set, fastq_list_row_id_set :fastq_list_row_id_set", - "ExpressionAttributeValues": { - ":library_set": { - "SS.$": "States.Array($.input_payload_data.library.libraryId)" - }, - ":fastq_list_row_id_set": { - "SS.$": "$.bclconvert_and_fastq_list_row_id_inputs[*].fastq_list_row_id" - } - } - }, - "End": true - } - } - }, { "StartAt": "Initialise Library ID", "States": { @@ -85,14 +60,11 @@ "TableName": "${__table_name__}", "Item": { "id": { - "S.$": "$.input_payload_data.library.libraryId" + "S.$": "$.input_payload_data.library.orcabusId" }, "id_type": { "S": "${__library_partition_name__}" }, - "orcabus_id": { - "S.$": "$.input_payload_data.library.orcabusId" - }, "library_id": { "S.$": "$.input_payload_data.library.libraryId" }, @@ -101,6 +73,9 @@ }, "fastq_list_row_id_set": { "SS.$": "$.bclconvert_and_fastq_list_row_id_inputs[*].fastq_list_row_id" + }, + "instrument_run_id": { + "S.$": "$.input_payload_data.instrumentRunId" } } }, @@ -117,6 +92,7 @@ "ItemSelector": { "fastq_list_row_id.$": "$$.Map.Item.Value.fastq_list_row_id", "library_id.$": "$.input_payload_data.library.libraryId", + "library_orcabus_id.$": "$.input_payload_data.library.orcabusId", "instrument_run_id.$": "$.input_payload_data.instrumentRunId" }, "ItemProcessor": { @@ -140,6 +116,9 @@ "library_id": { "S.$": "$.library_id" }, + "library_orcabus_id": { + "S.$": "$.library_orcabus_id" + }, "instrument_run_id": { "S.$": "$.instrument_run_id" } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/populate-fastq-list-row-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/populate-fastq-list-row-dbs/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/populate-fastq-list-row-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/populate-fastq-list-row-dbs/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json similarity index 96% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json index 718abf09d..c86d99a6d 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_2/populate-fastq-list-row-dbs/step_functions_templates/update_fastq_list_row_sfn_template.asl.json @@ -7,8 +7,7 @@ "Next": "DynamoDB GetItem", "Parameters": { "fastq_list_row_id.$": "$.payload.data.fastqListRow.rgid", - "fastq_list_row_event_data.$": "$.payload.data.fastqListRow", - "instrument_run_id.$": "$.payload.data.instrumentRunId" + "fastq_list_row_event_data.$": "$.payload.data.fastqListRow" } }, "DynamoDB GetItem": { diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/Readme.md b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/Readme.md similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/Readme.md rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/Readme.md diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/index.ts similarity index 72% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/index.ts index 8bed7d003..4d6f679db 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/index.ts @@ -9,9 +9,12 @@ import * as lambda from 'aws-cdk-lib/aws-lambda'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as cdk from 'aws-cdk-lib'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; /* -Part 4 +Part 3 Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `FastqListRowStateChange` @@ -19,24 +22,33 @@ Input Event status: `FastqListRowEventShowerComplete` Output Event source: `orcabus.wgtsinputeventglue` Output Event DetailType: `WorkflowDraftRunStateChange` -Output Event status: `draft` +Output Event status: `READY` * Trigger wgts events collecting all wgts in the run */ export interface WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowConstructProps { - workflowsTableObj: dynamodb.ITableV2; - wgtsQcGlueTableObj: dynamodb.ITableV2; + /* Event Bus Object */ eventBusObj: events.IEventBus; + + /* DynamoDB Table */ + wgtsQcGlueTableObj: dynamodb.ITableV2; + + /* SSM Param objects */ + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + outputUriSsmParameterObj: ssm.IStringParameter; + cacheUriSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Construct { +export class WgtsQcFastqListRowShowerCompleteToWorkflowReadyConstruct extends Construct { public readonly WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap = { /* General settings */ prefix: 'kwik-fqlr-shower-to-wgts-qc', /* Table Partition Settings */ wgtsGlueTablePartition: { - instrumentRun: 'instrument_run', library: 'library', fastqListRow: 'fastq_list_row', }, @@ -45,14 +57,12 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co triggerSource: 'orcabus.instrumentrunmanager', triggerStatus: 'FastqListRowEventShowerComplete', triggerDetailType: 'FastqListRowShowerStateChange', - /* Output Event Settings */ + /* Output Source */ outputSource: 'orcabus.wgtsqcinputeventglue', - outputStatus: 'DRAFT', - outputDetailType: 'WorkflowDraftRunStateChange', /* Payload version */ payloadVersion: '0.1.0', /* Workflow settings */ - workflowName: 'wgtsQc', + workflowName: 'wgts-qc', workflowVersion: '4.2.4', }; @@ -66,14 +76,11 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, `${this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: - this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.portalRunPartitionName, stateMachinePrefix: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix, - tableObj: props.workflowsTableObj, workflowName: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowName, workflowVersion: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowVersion, @@ -81,7 +88,7 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co ).stepFunctionObj; /* - Part 1: Build the lambdas + Part 2: Build the lambdas */ const generateEventDataLambdaObj = new PythonFunction(this, 'generate_event_data', { entry: path.join(__dirname, 'lambdas', 'generate_event_data_py'), @@ -93,7 +100,38 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co }); /* - Part 2: Build the sfn + Part 3: Build the engine parameters sfn + */ + const engineParameterAndReadyEventMakerSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'fastqlistrow_complete_to_wgtsqc_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.outputSource, + payloadVersion: + this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.payloadVersion, + workflowName: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowName, + workflowVersion: + this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix, + stateMachinePrefix: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.prefix, + } + ).stepFunctionObj; + + /* + Part 4: Build the inputs sfn */ const inputMakerSfn = new sfn.StateMachine( this, @@ -116,24 +154,12 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co __fastq_list_row_partition_name__: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.wgtsGlueTablePartition .fastqListRow, - __instrument_run_partition_name__: - this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.wgtsGlueTablePartition - .instrumentRun, __library_partition_name__: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.wgtsGlueTablePartition .library, __portal_run_partition_name__: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.portalRunPartitionName, /* Output event settings */ - // Event detail - __event_source__: - this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.outputSource, - __detail_type__: - this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.outputDetailType, - __output_status__: - this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.outputStatus, - __payload_version__: - this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.payloadVersion, // Workflow detail __workflow_name__: this.WgtsQcFastqListRowShowerCompleteToWorkflowDraftRunDbRowMap.workflowName, @@ -145,7 +171,8 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co generateEventDataLambdaObj.currentVersion.functionArn, /* Nested sfn */ - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParameterAndReadyEventMakerSfn.stateMachineArn, }, } ); @@ -154,13 +181,10 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co Part 3: Grant the internal sfn permissions */ // access the dynamodb table - props.wgtsQcGlueTableObj.grantReadWriteData(inputMakerSfn.role); + props.wgtsQcGlueTableObj.grantReadWriteData(inputMakerSfn); // Allow the sfn to invoke the lambda - generateEventDataLambdaObj.currentVersion.grantInvoke(inputMakerSfn.role); - - // Allow the sfn to submit events to the event bus - props.eventBusObj.grantPutEventsTo(inputMakerSfn.role); + generateEventDataLambdaObj.currentVersion.grantInvoke(inputMakerSfn); /* Allow step function to call nested state machine */ // Because we run a nested state machine, we need to add the permissions to the state machine role @@ -173,8 +197,10 @@ export class WgtsQcFastqListRowShowerCompleteToWorkflowDraftConstruct extends Co actions: ['events:PutTargets', 'events:PutRule', 'events:DescribeRule'], }) ); + // Allow the state machine to be able to invoke the preamble sfn - sfn_preamble.grantStartExecution(inputMakerSfn.role); + sfnPreamble.grantStartExecution(inputMakerSfn); + engineParameterAndReadyEventMakerSfn.grantStartExecution(inputMakerSfn); /* Part 4: Subscribe to the event bus for this event type diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/lambdas/generate_event_data_py/generate_event_data.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/lambdas/generate_event_data_py/generate_event_data.py similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/lambdas/generate_event_data_py/generate_event_data.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/lambdas/generate_event_data_py/generate_event_data.py diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json similarity index 77% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json index 4737ca0b2..041c3cad1 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/fastq-list-rows-shower-complete-to-wgts-qc-draft/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_3/fastq-list-rows-shower-complete-to-wgts-qc/step_functions_templates/fastq_list_rows_shower_complete_to_wgts_qc_draft_sfn_template.asl.json @@ -11,25 +11,55 @@ }, "Get Libraries from Instrument Run": { "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", + "Resource": "arn:aws:states:::aws-sdk:dynamodb:scan", "Parameters": { "TableName": "${__table_name__}", - "Key": { - "id.$": "$.fastq_list_row_shower_complete_event_data.instrumentRunId", - "id_type": "${__instrument_run_partition_name__}" - } - }, - "ResultSelector": { - "library_list.$": "$.Item.library_set.SS" + "ExpressionAttributeValues": { + ":instrument_run_id": { + "S.$": "$.fastq_list_row_shower_complete_event_data.instrumentRunId" + }, + ":id_type": { + "S": "${__library_partition_name__}" + } + }, + "ExpressionAttributeNames": { + "#instrument_run_id": "instrument_run_id", + "#id_type": "id_type" + }, + "FilterExpression": "#instrument_run_id = :instrument_run_id AND #id_type = :id_type" }, + "ResultPath": "$.get_libraries_on_instrument_run_step", + "Next": "Check library items is not empty" + }, + "Check library items is not empty": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_libraries_on_instrument_run_step.Items", + "IsPresent": true, + "Comment": "Items list exists", + "Next": "Collect Library Orcabus Ids" + } + ], + "Default": "Pass" + }, + "Pass": { + "Type": "Pass", + "End": true + }, + "Collect Library Orcabus Ids": { + "Type": "Pass", "Next": "Iterate over each library", - "ResultPath": "$.get_libraries_step" + "Parameters": { + "library_orcabus_ids_list.$": "$.get_libraries_on_instrument_run_step.Items[*].id" + }, + "ResultPath": "$.collect_library_orcabus_ids_step" }, "Iterate over each library": { "Type": "Map", - "ItemsPath": "$.get_libraries_step.library_list", + "ItemsPath": "$.collect_library_orcabus_ids_step.library_orcabus_ids_list", "ItemSelector": { - "library_id.$": "$$.Map.Item.Value", + "library_orcabus_id.$": "$$.Map.Item.Value", "instrument_run_id.$": "$.fastq_list_row_shower_complete_event_data.instrumentRunId" }, "ItemProcessor": { @@ -44,14 +74,14 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_partition_name__}" } }, "Next": "For each FastqListRowID", "ResultSelector": { "library_id.$": "$.Item.library_id.S", - "orcabus_id.$": "$.Item.orcabus_id.S", + "library_orcabus_id.$": "$.Item.id.S", "sample_type.$": "$.Item.sample_type.S", "fastq_list_row_id_list.$": "$.Item.fastq_list_row_id_set.SS" }, @@ -64,7 +94,7 @@ "fastq_list_row_id.$": "$$.Map.Item.Value", "sample_type.$": "$.get_fastq_list_row_ids_step.sample_type", "library_id.$": "$.get_fastq_list_row_ids_step.library_id", - "orcabus_id.$": "$.get_fastq_list_row_ids_step.orcabus_id", + "library_orcabus_id.$": "$.get_fastq_list_row_ids_step.library_orcabus_id", "instrument_run_id.$": "$.instrument_run_id" }, "ItemProcessor": { @@ -94,9 +124,9 @@ "fastq_list_row.$": "States.StringToJson($.Item.fastq_list_row_json.S)" }, "ResultPath": "$.get_fastq_list_row_step", - "Next": "Generate Wgts Draft Event Data" + "Next": "Generate Wgts Input and Tags Event Data" }, - "Generate Wgts Draft Event Data": { + "Generate Wgts Input and Tags Event Data": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", "Parameters": { @@ -214,41 +244,29 @@ "Wait 1 Second": { "Type": "Wait", "Seconds": 1, - "Next": "Push wgts draft event" + "Next": "Push wgts ready event" }, - "Push wgts draft event": { + "Push wgts ready event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "Detail": { - "portalRunId.$": "$.get_per_workflow_run_inputs_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "${__output_status__}", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_per_workflow_run_inputs_step.workflow_run_name", - "linkedLibraries": [ - { - "libraryId.$": "$.library_id", - "orcabusId.$": "$.orcabus_id" - } - ], - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs.$": "$.get_per_workflow_run_inputs_step.event_data", - "tags.$": "$.get_per_workflow_run_inputs_step.event_tags" - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_per_workflow_run_inputs_step.portal_run_id", + "workflow_run_name.$": "$.get_per_workflow_run_inputs_step.workflow_run_name", + "linked_libraries": [ + { + "libraryId.$": "$.library_id", + "orcabusId.$": "$.library_orcabus_id" } - }, - "DetailType": "${__detail_type__}", - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}" + ], + "data_inputs.$": "$.get_per_workflow_run_inputs_step.event_data", + "data_tags.$": "$.get_per_workflow_run_inputs_step.event_tags" } - ] + } }, + "ResultPath": null, "End": true } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/index.ts similarity index 99% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/index.ts index 0539d55ad..c1eefcd67 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/index.ts @@ -40,7 +40,7 @@ export class FastqListRowQcCompleteConstruct extends Construct { triggerSource: 'orcabus.workflowmanager', triggerStatus: 'succeeded', triggerDetailType: 'WorkflowRunStateChange', - triggerWorkflowName: 'wgtsQc', + triggerWorkflowName: 'wgts-qc', outputSource: 'orcabus.wgtsqcinputeventglue', outputDetailType: 'FastqListRowStateChange', outputStatus: 'QC_COMPLETE', diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/collect_qc_metrics_from_alignment_directory.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/collect_qc_metrics_from_alignment_directory.py similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/collect_qc_metrics_from_alignment_directory.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/collect_qc_metrics_from_alignment_directory.py diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/requirements.txt b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/requirements.txt similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/requirements.txt rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/collect_qc_metrics_from_alignment_directory_py/requirements.txt diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py similarity index 90% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py index 36bb89117..bf281a42b 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/lambdas/generate_event_data_objects_py/generate_event_data_objects.py @@ -43,13 +43,17 @@ def handler(event, context) -> Dict[str, Dict[str, str]]: fastq_list_row_id = event['fastq_list_row_id'] sample_type = event['sample_type'] qc_metrics = event['qc_metrics'] + library_orcabus_id = event['library_orcabus_id'] library_id = event['library_id'] # Initialise output dict event_output_dict = { "fastqListRowId": fastq_list_row_id, "sampleType": sample_type, - "libraryId": library_id + "library": { + "libraryId": library_id, + "orcabusId": library_orcabus_id + } } # Update dict per sample type diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json similarity index 95% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json index 7f2fe162c..572fdf7b7 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_6/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_4/push-fastq-list-row-qc-complete-event/step_functions_templates/wgts_qc_complete_sfn_template.asl.json @@ -36,8 +36,7 @@ } }, "ResultSelector": { - "library_id.$": "$.Item.library_id", - "instrument_run_id.$": "$.Item.instrument_run_id" + "library_orcabus_id.$": "$.Item.library_orcabus_id.S" }, "ResultPath": "$.get_library_id_step", "Next": "GetLibraryInfo" @@ -48,13 +47,13 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.get_library_id_step.library_id", + "id.$": "$.get_library_id_step.library_orcabus_id", "id_type": "${__library_partition__}" } }, "ResultSelector": { "sample_type.$": "$.Item.sample_type.S", - "library_id.$": "$.Item.id.S" + "library_id.$": "$.Item.library_id.S" }, "ResultPath": "$.get_library_info_step", "Next": "Collect QC Metrics from alignment directory" @@ -121,6 +120,7 @@ "fastq_list_row_id.$": "$.get_fastq_list_row_id_from_portal_run_id.fastq_list_row_id", "sample_type.$": "$.get_library_info_step.sample_type", "qc_metrics.$": "$.get_qc_metrics_step.qc_metrics", + "library_orcabus_id.$": "$.get_library_id_step.library_orcabus_id", "library_id.$": "$.get_library_info_step.library_id" } }, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/lambdas/sum_coverages_for_rgids_py/sum_coverages_for_rgids.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/lambdas/sum_coverages_for_rgids_py/sum_coverages_for_rgids.py similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/lambdas/sum_coverages_for_rgids_py/sum_coverages_for_rgids.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/lambdas/sum_coverages_for_rgids_py/sum_coverages_for_rgids.py diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json similarity index 96% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json index f3445f348..94af46139 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_7/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/library-qc-complete-event/step_functions_templates/wgts_library_qc_complete_event_template.asl.json @@ -15,7 +15,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition__}" } }, @@ -31,7 +31,7 @@ "ItemsPath": "$.get_library_info_from_library_step.fastq_list_row_ids", "ItemSelector": { "fastq_list_row_id.$": "$$.Map.Item.Value", - "library_id.$": "$.payload_data.libraryId", + "library_id.$": "$.payload_data.library.libraryId", "sample_type.$": "$.get_library_info_from_library_step.sample_type" }, "ItemProcessor": { @@ -139,7 +139,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition__}" }, "UpdateExpression": "SET qc_metrics = :qc_metrics", @@ -172,7 +172,7 @@ "payload": { "version": "${__payload_version__}", "data": { - "libraryId.$": "$.payload_data.libraryId", + "library.$": "$.payload_data.library", "fastqListRowIds.$": "$.get_library_info_from_library_step.fastq_list_row_ids", "qcMetrics.$": "$.collate_metrics_step.library_qc_metrics" } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/wgts-qc-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/wgts-qc-draft-to-ready/index.ts deleted file mode 100644 index 46b3984bd..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/kwik/part_5/wgts-qc-draft-to-ready/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import * as events from 'aws-cdk-lib/aws-events'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 5 - -Input Event source: `orcabus.wgtsqcinputeventglue` -Input Event DetailType: `WorkflowDraftRunStateChange` -Input Event status: `draft` - -Output Event source: `orcabus.wgtsqcinputeventglue` -Output Event DetailType: `WorkflowRunStateChange` -Output Event status: `ready` - -* The wgtsQcInputMaker, subscribes to the wgtsqc input event glue (itself) and generates a ready event for the wgtsqcReadySfn - * For the cttso v2 workflow we require a samplesheet, a set of fastq list rows (provided in the last step) - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference -*/ - -export interface WgtsQcInputMakerConstructProps { - /* Event bus object */ - eventBusObj: events.IEventBus; - /* Tables */ - inputMakerTableObj: dynamodb.ITableV2; - /* SSM Parameter Objects */ - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - cacheUriSsmParameterObj: ssm.IStringParameter; - /* Secrets */ - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class WgtsQcInputMakerConstruct extends Construct { - public readonly wgtsQcInputMakerEventMap = { - prefix: 'kwik-wgtsqc', - tablePartition: 'wgts_qc', - triggerSource: 'orcabus.wgtsqcinputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.wgtsqcinputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.05.24', - workflowName: 'wgtsQc', - workflowVersion: '4.2.4', - }; - - constructor(scope: Construct, id: string, props: WgtsQcInputMakerConstructProps) { - super(scope, id); - - /* - Part 3: Build the external sfn - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'wgts_qc_copy_manager_internal_input_maker', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.wgtsQcInputMakerEventMap.prefix, - payloadVersion: this.wgtsQcInputMakerEventMap.payloadVersion, - stateMachinePrefix: this.wgtsQcInputMakerEventMap.prefix, - rulePrefix: `stacky-${this.wgtsQcInputMakerEventMap.prefix}`, - - /* - Table objects - */ - tableObj: props.inputMakerTableObj, - tablePartitionName: this.wgtsQcInputMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerDetailType: this.wgtsQcInputMakerEventMap.triggerDetailType, - triggerSource: this.wgtsQcInputMakerEventMap.triggerSource, - triggerStatus: this.wgtsQcInputMakerEventMap.triggerStatus, - outputSource: this.wgtsQcInputMakerEventMap.outputSource, - workflowName: this.wgtsQcInputMakerEventMap.workflowName, - workflowVersion: this.wgtsQcInputMakerEventMap.workflowVersion, - - /* - SSM Parameter Objects - */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - - /* - Secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/index.ts index 34a0f25f6..7100d2e1f 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/index.ts @@ -3,12 +3,9 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { TnInitialiseSubjectDbRowConstruct } from './part_1/initialise-tn-subject-dbs'; -import { TnInitialiseLibraryAndFastqListRowConstruct } from './part_2/initialise-tn-library-dbs'; -import { TnPopulateFastqListRowConstruct } from './part_3/update-fastq-list-rows-dbs'; -import { TnFastqListRowQcCompleteConstruct } from './part_4/update-fastq-list-row-qc-complete-dbs'; -import { LibraryQcCompleteToTnDraftConstruct } from './part_5/library-qc-complete-db-to-tn-draft'; -import { TnInputMakerConstruct } from './part_6/tn-draft-to-ready'; +import { TnInitialiseLibraryAndFastqListRowConstruct } from './part_1/initialise-tn-library-dbs'; +import { TnPopulateFastqListRowConstruct } from './part_2/update-fastq-list-rows-dbs'; +import { LibraryQcCompleteToTnReadyConstruct } from './part_3/library-qc-complete-db-to-tn-ready'; /* Provide the glue to get from the bssh fastq copy manager to submitting wgts qc analyses @@ -19,7 +16,6 @@ export interface tnGlueHandlerConstructProps { eventBusObj: events.IEventBus; /* Tables */ tnGlueTableObj: dynamodb.ITableV2; - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameters */ analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; @@ -34,53 +30,17 @@ export class TnGlueHandlerConstruct extends Construct { super(scope, id); /* - Part 1 - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `SamplesheetMetadataUnion` - Input Event status: `SubjectInSamplesheet` + Part 1 - * Initialise tn subject db row construct - */ - const tn_initialise_subject_db_row = new TnInitialiseSubjectDbRowConstruct( - this, - 'tn_initialise_subject_db_row', - { - eventBusObj: props.eventBusObj, - tableObj: props.tnGlueTableObj, - } - ); - - /* - Part 2 - - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `SamplesheetMetadataUnion` - Input Event status: `LibraryInSamplesheet` - - * Initialise wgts qc library and fastq list row constructs - */ - const tn_initialise_library_and_fastq_list_row = - new TnInitialiseLibraryAndFastqListRowConstruct( - this, - 'tn_initialise_library_and_fastq_list_row', - { - eventBusObj: props.eventBusObj, - tableObj: props.tnGlueTableObj, - } - ); - - /* - Part 3 - - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `FastqListRowStateChange` - Input Event status: `newFastqListRow` + Input Event Source: `orcabus.instrumentrunmanager` + Input Event DetailType: `SamplesheetMetadataUnion` + Input Event status: `LibraryInSamplesheet` - * Populate the fastq list row attributes for the rgid for this workflow - */ - const tn_populate_fastq_list_row = new TnPopulateFastqListRowConstruct( + * Initialise wgts qc library and fastq list row constructs + */ + const tnInitialiseLibraryAndFastqListRow = new TnInitialiseLibraryAndFastqListRowConstruct( this, - 'tn_populate_fastq_list_row', + 'tn_initialise_library_and_fastq_list_row', { eventBusObj: props.eventBusObj, tableObj: props.tnGlueTableObj, @@ -88,19 +48,17 @@ export class TnGlueHandlerConstruct extends Construct { ); /* - Part 4 + Part 2 - Input Event Source: `orcabus.wgtsqcinputeventglue` - Input Event DetailType: `FastqListRowStateChange` - Input Event status: `QcComplete` + Input Event Source: `orcabus.instrumentrunmanager` + Input Event DetailType: `FastqListRowStateChange` + Input Event status: `newFastqListRow` - * Populate the fastq list row attributes with the qc metrics for this fastq list row id - * Currently not used by the glue service - - */ - const tn_fastq_list_row_qc_complete = new TnFastqListRowQcCompleteConstruct( + * Populate the fastq list row attributes for the rgid for this workflow + */ + const tnPopulateFastqListRow = new TnPopulateFastqListRowConstruct( this, - 'tn_fastq_list_row_qc_complete', + 'tn_populate_fastq_list_row', { eventBusObj: props.eventBusObj, tableObj: props.tnGlueTableObj, @@ -108,54 +66,31 @@ export class TnGlueHandlerConstruct extends Construct { ); /* - Part 5 - Input Event source: `orcabus.wgtsqcinputeventglue` - Input Event DetailType: `LibraryStateChange` - Input Event status: `QcComplete` - - Output Event source: `orcabus.tninputeventglue` - Output Event DetailType: `WorkflowDraftRunStateChange` - Output Event status: `draft` - */ - const libraryQcCompleteToTnDraft = new LibraryQcCompleteToTnDraftConstruct( + Part 3 + Input Event source: `orcabus.wgtsqcinputeventglue` + Input Event DetailType: `LibraryStateChange` + Input Event status: `QcComplete` + + Output Event source: `orcabus.tninputeventglue` + Output Event DetailType: `WorkflowDraftRunStateChange` + Output Event status: `draft` + */ + const libraryQcCompleteToTnDraft = new LibraryQcCompleteToTnReadyConstruct( this, 'library_qc_complete_to_tn_draft', { // Event bus eventBusObj: props.eventBusObj, - // SSM Param objects + // Table objects tableObj: props.tnGlueTableObj, - workflowsTableObj: props.inputMakerTableObj, + /* SSM Param objects */ + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, + cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, } ); - - /* - Part 6 - - Input Event Source: `orcabus.workflowmanager` - Input Event DetailType: `WorkflowRunStateChange` - Input Event status: `succeeded` - Input Event WorkflowName: `wgts_qc` - - Output Event Source: `orcabus.wgtsqcinputeventglue` - Output Event DetailType: `FastqListRowStateChange` - Output Event status: `QcComplete` - - * Subscribe to workflow run state change events, map the fastq list row id from the portal run id in the data base - * We output the fastq list row id to the event bus with the status `QcComplete` - */ - const tnInputMaker = new TnInputMakerConstruct(this, 'fastq_list_row_qc_complete', { - /* Event bus */ - eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, - /* SSM Param objects */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - /* Secrets */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - }); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/initialise-tn-library-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-library-dbs/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/initialise-tn-library-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-library-dbs/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json similarity index 83% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json index 5660a6a76..0122e12f3 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-library-dbs/step_functions_templates/initialise_tn_library_db_sfn_template.asl.json @@ -15,7 +15,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.library.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}" } }, @@ -26,25 +26,10 @@ "Type": "Parallel", "Branches": [ { - "StartAt": "Add Library to Subject", + "StartAt": "Pass", "States": { - "Add Library to Subject": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.library.subject.subjectId", - "id_type": "${__subject_partition_name__}" - }, - "UpdateExpression": "ADD library_set :library_set", - "ExpressionAttributeValues": { - ":library_set": { - "SS.$": "States.Array($.payload_data.library.libraryId)" - } - } - }, - "ResultPath": null, + "Pass": { + "Type": "Pass", "End": true } } @@ -70,10 +55,10 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id.$": "$.payload_data.library.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}", - "orcabus_id": { - "S.$": "$.payload_data.library.orcabusId" + "library_id": { + "S.$": "$.payload_data.library.libraryId" }, "phenotype": { "S.$": "$.payload_data.library.phenotype" @@ -88,7 +73,10 @@ "S.$": "$.payload_data.library.assay" }, "subject_id": { - "S.$": "$.payload_data.library.subject.subjectId" + "S.$": "$.payload_data.subject.subjectId" + }, + "subject_orcabus_id": { + "S.$": "$.payload_data.subject.orcabusId" } } }, @@ -113,6 +101,7 @@ "bclconvert_data_row.$": "$$.Map.Item.Value", "instrument_run_id.$": "$.payload_data.instrumentRunId", "library_id.$": "$.payload_data.library.libraryId", + "library_orcabus_id.$": "$.payload_data.library.orcabusId", "index.$": "$$.Map.Item.Index", "fastq_list_row_objs.$": "$.payload_data.fastqListRows" }, @@ -146,6 +135,9 @@ "id_type": "${__fastq_list_row_partition_name__}", "library_id": { "S.$": "$.library_id" + }, + "library_orcabus_id": { + "S.$": "$.library_orcabus_id" } } }, @@ -163,7 +155,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_partition_name__}" }, "UpdateExpression": "ADD fastq_list_row_id_set :fastq_list_row_id_set", diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/index.ts deleted file mode 100644 index ed0edbbd1..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 1 - -Input Event Source: `orcabus.instrumentrunmanager` -Input Event DetailType: `SamplesheetMetadataUnion` -Input Event status: `SubjectInSamplesheet` - -* Initialise tn subject db construct -*/ - -export interface TnInitialiseSubjectDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class TnInitialiseSubjectDbRowConstruct extends Construct { - public readonly TnInitialiseSubjectDbRowMap = { - prefix: 'loctite-make-subject-row', - tablePartition: 'subject', - triggerSource: 'orcabus.instrumentrunmanager', - triggerStatus: 'SubjectInSamplesheet', - triggerDetailType: 'SamplesheetMetadataUnion', - }; - - constructor(scope: Construct, id: string, props: TnInitialiseSubjectDbRowConstructProps) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'initialise_subject_db_row', { - stateMachineName: `${this.TnInitialiseSubjectDbRowMap.prefix}-initialise-subject`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'initialise_tn_subject_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __subject_partition_name__: this.TnInitialiseSubjectDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus and trigger the internal sfn - */ - const rule = new events.Rule(this, 'tn_subscribe_to_samplesheet_shower_subject', { - ruleName: `stacky-${this.TnInitialiseSubjectDbRowMap.prefix}-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.TnInitialiseSubjectDbRowMap.triggerSource], - detailType: [this.TnInitialiseSubjectDbRowMap.triggerDetailType], - detail: { - status: [{ 'equals-ignore-case': this.TnInitialiseSubjectDbRowMap.triggerStatus }], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/step_functions_templates/initialise_tn_subject_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/step_functions_templates/initialise_tn_subject_db_sfn_template.asl.json deleted file mode 100644 index e0c23b290..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_1/initialise-tn-subject-dbs/step_functions_templates/initialise_tn_subject_db_sfn_template.asl.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Subject Item" - }, - "Get Subject Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.subject.subjectId", - "id_type": "${__subject_partition_name__}" - } - }, - "ResultPath": "$.get_subject_item_step", - "Next": "Subject in Database" - }, - "Subject in Database": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_subject_item_step.Item", - "IsPresent": false, - "Comment": "Subject Not In Database", - "Next": "Initialise Subject" - } - ], - "Default": "Pass" - }, - "Pass": { - "Type": "Pass", - "End": true - }, - "Initialise Subject": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id.$": "$.payload_data.subject.subjectId", - "id_type": "${__subject_partition_name__}", - "orcabus_id": { - "S.$": "$.payload_data.subject.orcabusId" - } - } - }, - "End": true - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/update-fastq-list-rows-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/update-fastq-list-rows-dbs/index.ts similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/update-fastq-list-rows-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/update-fastq-list-rows-dbs/index.ts diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_2/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/index.ts similarity index 53% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/index.ts index 934af789d..bc5dc3521 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/index.ts @@ -4,14 +4,18 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import path from 'path'; import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import * as events from 'aws-cdk-lib/aws-events'; import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; +import { GetMetadataLambdaConstruct } from '../../../../../../../components/python-lambda-metadata-mapper'; /* -Part 5 +Part 3 Input Event Source: `orcabus.wgtsqcinputeventglue` Input Event DetailType: `LibraryStateChange` @@ -26,28 +30,34 @@ Output Event status: `draft` */ export interface LibraryQcCompleteToTnDraftConstructProps { + /* Events */ eventBusObj: events.IEventBus; + /* Tables */ tableObj: dynamodb.ITableV2; - workflowsTableObj: dynamodb.ITableV2; + /* SSM Parameters */ + outputUriSsmParameterObj: ssm.IStringParameter; + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + cacheUriSsmParameterObj: ssm.IStringParameter; + + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class LibraryQcCompleteToTnDraftConstruct extends Construct { - public readonly TnDraftMap = { +export class LibraryQcCompleteToTnReadyConstruct extends Construct { + public readonly TnReadyMap = { prefix: 'loctite-qc-complete-to-tn', tablePartition: { subject: 'subject', library: 'library', fastq_list_row: 'fastq_list_row', }, - portalRunPartitionName: 'portal_run', triggerSource: 'orcabus.wgtsqcinputeventglue', triggerStatus: 'QC_COMPLETE', triggerDetailType: 'LibraryStateChange', outputSource: 'orcabus.tninputeventglue', - outputDetailType: 'WorkflowDraftRunStateChange', - outputStatus: 'DRAFT', payloadVersion: '2024.07.23', - workflowName: 'tumor_normal', + workflowName: 'tumor-normal', workflowVersion: '4.2.4', }; @@ -76,26 +86,67 @@ export class LibraryQcCompleteToTnDraftConstruct extends Construct { memorySize: 1024, }); + // Generate the lambda to collect the orcabus id from the subject id + const collectOrcaBusIdLambdaObj = new GetMetadataLambdaConstruct( + this, + 'get_orcabus_id_from_subject_id', + { + functionNamePrefix: this.TnReadyMap.prefix, + } + ).lambdaObj; + + // Add CONTEXT, FROM_ID and RETURN_OBJ environment variables to the lambda + collectOrcaBusIdLambdaObj.addEnvironment('CONTEXT', 'subject'); + collectOrcaBusIdLambdaObj.addEnvironment('FROM_ORCABUS', ''); + collectOrcaBusIdLambdaObj.addEnvironment('RETURN_OBJ', ''); + /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, - `${this.TnDraftMap.prefix}_sfn_preamble`, + `${this.TnReadyMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: this.TnDraftMap.portalRunPartitionName, - stateMachinePrefix: this.TnDraftMap.prefix, - tableObj: props.workflowsTableObj, - workflowName: this.TnDraftMap.workflowName, - workflowVersion: this.TnDraftMap.workflowVersion, + stateMachinePrefix: this.TnReadyMap.prefix, + workflowName: this.TnReadyMap.workflowName, + workflowVersion: this.TnReadyMap.workflowVersion, + } + ).stepFunctionObj; + + /* + Part 2: Build the engineparameters event sfn + */ + const engineParameterAndReadyEventMakerSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'fastqlistrow_complete_to_wgtsqc_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.TnReadyMap.outputSource, + payloadVersion: this.TnReadyMap.payloadVersion, + workflowName: this.TnReadyMap.workflowName, + workflowVersion: this.TnReadyMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.TnReadyMap.prefix, + stateMachinePrefix: this.TnReadyMap.prefix, } ).stepFunctionObj; /* Part 2: Build the sfn */ - const qcCompleteToDraftSfn = new sfn.StateMachine(this, 'library_qc_complete_sfn_to_tn_draft', { - stateMachineName: `${this.TnDraftMap.prefix}-sfn`, + const inputMakerSfn = new sfn.StateMachine(this, 'library_qc_complete_sfn_to_tn_draft', { + stateMachineName: `${this.TnReadyMap.prefix}-sfn`, definitionBody: sfn.DefinitionBody.fromFile( path.join( __dirname, @@ -104,29 +155,23 @@ export class LibraryQcCompleteToTnDraftConstruct extends Construct { ) ), definitionSubstitutions: { - /* Events */ - __event_bus_name__: props.eventBusObj.eventBusName, - __event_source__: this.TnDraftMap.outputSource, - __detail_type__: this.TnDraftMap.outputDetailType, - __output_status__: this.TnDraftMap.outputStatus, - __payload_version__: this.TnDraftMap.payloadVersion, - __workflow_name__: this.TnDraftMap.workflowName, - __workflow_version__: this.TnDraftMap.workflowVersion, - /* Lambdas */ __generate_draft_event_payload_lambda_function_arn__: generateEventDataLambdaObj.currentVersion.functionArn, __get_complement_library_pair_lambda_function_arn__: findComplementLibraryPair.currentVersion.functionArn, + __get_orcabus_obj_from_subject_id_lambda_function_arn__: + collectOrcaBusIdLambdaObj.currentVersion.functionArn, /* Tables */ __table_name__: props.tableObj.tableName, - __subject_partition_name__: this.TnDraftMap.tablePartition.subject, - __library_partition_name__: this.TnDraftMap.tablePartition.library, - __fastq_list_row_partition_name__: this.TnDraftMap.tablePartition.fastq_list_row, + __subject_partition_name__: this.TnReadyMap.tablePartition.subject, + __library_partition_name__: this.TnReadyMap.tablePartition.library, + __fastq_list_row_partition_name__: this.TnReadyMap.tablePartition.fastq_list_row, // State Machines - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParameterAndReadyEventMakerSfn.stateMachineArn, }, }); @@ -134,20 +179,19 @@ export class LibraryQcCompleteToTnDraftConstruct extends Construct { Part 2: Grant the sfn permissions */ // access the dynamodb table - props.tableObj.grantReadWriteData(qcCompleteToDraftSfn); - - // allow the step function to submit events - props.eventBusObj.grantPutEventsTo(qcCompleteToDraftSfn.role); + props.tableObj.grantReadWriteData(inputMakerSfn); // allow the step function to invoke the lambdas - [generateEventDataLambdaObj, findComplementLibraryPair].forEach((lambdaObj) => { - lambdaObj.currentVersion.grantInvoke(qcCompleteToDraftSfn.role); - }); + [generateEventDataLambdaObj, findComplementLibraryPair, collectOrcaBusIdLambdaObj].forEach( + (lambdaObj) => { + lambdaObj.currentVersion.grantInvoke(inputMakerSfn); + } + ); /* Allow step function to call nested state machine */ // Because we run a nested state machine, we need to add the permissions to the state machine role // See https://stackoverflow.com/questions/60612853/nested-step-function-in-a-step-function-unknown-error-not-authorized-to-cr - qcCompleteToDraftSfn.addToRolePolicy( + inputMakerSfn.addToRolePolicy( new iam.PolicyStatement({ resources: [ `arn:aws:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:rule/StepFunctionsGetEventsForStepFunctionsExecutionRule`, @@ -156,26 +200,27 @@ export class LibraryQcCompleteToTnDraftConstruct extends Construct { }) ); // Allow the state machine to be able to invoke the preamble sfn - sfn_preamble.grantStartExecution(qcCompleteToDraftSfn.role); + sfnPreamble.grantStartExecution(inputMakerSfn); + engineParameterAndReadyEventMakerSfn.grantStartExecution(inputMakerSfn); /* Part 3: Subscribe to the event bus and trigger the internal sfn */ const rule = new events.Rule(this, 'library_qc_complete_to_tn_draft', { - ruleName: `stacky-${this.TnDraftMap.prefix}-rule`, + ruleName: `stacky-${this.TnReadyMap.prefix}-rule`, eventBus: props.eventBusObj, eventPattern: { - source: [this.TnDraftMap.triggerSource], - detailType: [this.TnDraftMap.triggerDetailType], + source: [this.TnReadyMap.triggerSource], + detailType: [this.TnReadyMap.triggerDetailType], detail: { - status: [{ 'equals-ignore-case': this.TnDraftMap.triggerStatus }], + status: [{ 'equals-ignore-case': this.TnReadyMap.triggerStatus }], }, }, }); // Add target of event to be the state machine rule.addTarget( - new eventsTargets.SfnStateMachine(qcCompleteToDraftSfn, { + new eventsTargets.SfnStateMachine(inputMakerSfn, { input: events.RuleTargetInput.fromEventPath('$.detail'), }) ); diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/find_complement_library_pair_py/find_complement_library_pair.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/find_complement_library_pair_py/find_complement_library_pair.py new file mode 100644 index 000000000..0cc674246 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/find_complement_library_pair_py/find_complement_library_pair.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 + +""" +Given a library object and a list of complementary library objects, find a matching pair for the library object. + +The library objects must match on 'workflow' and 'type' attributes, but must be the opposite phenotype. +""" + +from typing import Dict, List + + +def find_complement_library_pair(library: Dict, complement_libraries: List[Dict]): + """ + Given a library object and a list of complementary library objects, find a matching pair for the library object + within the complement library list. + :param library: + :param complement_libraries: + :return: + """ + for complement_library in complement_libraries: + # Need to both be of the same type + if not library['type'] == complement_library['type']: + continue + + # Need to be of different phenotypes + if library['phenotype'] == complement_library['phenotype']: + continue + + # Can be different workflows IF + # The 'research' workflow is the tumor and the 'clinical' workflow is the normal + # But do not allow clinical tumors to be matched with research normals + # Or if they are the same workflow, but different phenotypes + if ( + # Special case for research + ( + ( + library['workflow'] == 'research' and + complement_library['workflow'] == 'clinical' + ) and ( + library['phenotype'] == 'tumor' and + complement_library['phenotype'] == 'normal' + ) + ) or + # Complement case + ( + ( + library['workflow'] == 'clinical' and + complement_library['workflow'] == 'research' + ) and ( + library['phenotype'] == 'normal' and + complement_library['phenotype'] == 'tumor' + ) + ) or + # Standard clinical+clinical or research+research + ( + ( + library['workflow'] == complement_library['workflow'] + ) + ) + ): + return library, complement_library + + return None, None + + +def handler(event, context): + """ + Lambda handler function + :param event: + :param context: + :return: + """ + + library_obj: Dict = event['library_obj'] + complement_libraries: List[Dict] = event['complementary_library_obj_list'] + + # Filter out empty complement libraries + complement_libraries = list( + filter( + lambda comp_lib_iter_: ( + comp_lib_iter_ is not None and + not comp_lib_iter_['orcabus_id'] == library_obj['orcabus_id'] + ), + complement_libraries + ) + ) + + library, complement_library = find_complement_library_pair(library_obj, complement_libraries) + + if library is None: + return { + 'successful_pairing': False, + 'tumor_library': None, + 'normal_library': None + } + + if library['phenotype'] == 'tumor': + tumor_library = library + normal_library = complement_library + else: + tumor_library = complement_library + normal_library = library + + return { + 'successful_pairing': True, + 'tumor_library': tumor_library, + 'normal_library': normal_library + } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py similarity index 95% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py index 87b2b11b4..9a54672fb 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py @@ -30,6 +30,7 @@ def handler(event, context) -> Dict: :return: draft event payload """ subject_id = event['subject_id'] + individual_id = event['individual_id'] tumor_library_id = event['tumor_library_id'] normal_library_id = event['normal_library_id'] @@ -54,6 +55,7 @@ def handler(event, context) -> Dict: }, "event_tags": { "subjectId": subject_id, + "individualId": individual_id, "tumorLibraryId": tumor_library_id, "normalLibraryId": normal_library_id, "tumorFastqListRowIds": tumor_fastq_list_row_ids, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json similarity index 80% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json index 7bc89329a..a072cad6c 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_3/library-qc-complete-db-to-tn-ready/step_functions_templates/add_library_qc_complete_to_tn_draft_sfn_template.asl.json @@ -15,7 +15,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}" } }, @@ -40,7 +40,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}" }, "UpdateExpression": "SET qc_metrics_json = :qc_metrics_json", @@ -60,16 +60,25 @@ }, "Get Libraries From Subject": { "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", + "Resource": "arn:aws:states:::aws-sdk:dynamodb:scan", "Parameters": { "TableName": "${__table_name__}", - "Key": { - "id.$": "$.get_library_item_step.Item.subject_id.S", - "id_type": "${__subject_partition_name__}" - } + "ExpressionAttributeValues": { + ":subject_orcabus_id": { + "S.$": "$.get_library_item_step.Item.subject_orcabus_id.S" + }, + ":id_type": { + "S": "${__library_partition_name__}" + } + }, + "ExpressionAttributeNames": { + "#subject_orcabus_id": "subject_orcabus_id", + "#id_type": "id_type" + }, + "FilterExpression": "#subject_orcabus_id = :subject_orcabus_id AND #id_type = :id_type" }, "ResultSelector": { - "library_set.$": "$.Item.library_set.SS" + "library_set.$": "$.Items[*].id" }, "ResultPath": "$.get_subject_library_set_step", "Next": "Collect All Libraries in Subject" @@ -78,7 +87,7 @@ "Type": "Map", "ItemsPath": "$.get_subject_library_set_step.library_set", "ItemSelector": { - "library_id.$": "$$.Map.Item.Value" + "library_orcabus_id.$": "$$.Map.Item.Value" }, "ItemProcessor": { "ProcessorConfig": { @@ -92,7 +101,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_partition_name__}" } }, @@ -116,13 +125,14 @@ "Parameters": { "output": { "library": { - "id.$": "$.library_id", + "orcabus_id.$": "$.get_library_map_step.Item.id.S", + "library_id.$": "$.get_library_map_step.Item.library_id.S", "phenotype.$": "$.get_library_map_step.Item.phenotype.S", "workflow.$": "$.get_library_map_step.Item.workflow.S", "type.$": "$.get_library_map_step.Item.type.S", "assay.$": "$.get_library_map_step.Item.assay.S", "subject_id.$": "$.get_library_map_step.Item.subject_id.S", - "orcabus_id.$": "$.get_library_map_step.Item.orcabus_id.S", + "subject_orcabus_id.$": "$.get_library_map_step.Item.subject_orcabus_id.S", "fastq_list_row_id_set.$": "$.get_library_map_step.Item.fastq_list_row_id_set.SS" } } @@ -153,13 +163,13 @@ "FunctionName": "${__get_complement_library_pair_lambda_function_arn__}", "Payload": { "library_obj": { - "id.$": "$.payload_data.libraryId", + "orcabus_id.$": "$.payload_data.library.orcabusId", + "library_id.$": "$.payload_data.library.libraryId", "phenotype.$": "$.get_library_item_step.Item.phenotype.S", "workflow.$": "$.get_library_item_step.Item.workflow.S", "type.$": "$.get_library_item_step.Item.type.S", "assay.$": "$.get_library_item_step.Item.assay.S", - "subject_id.$": "$.get_library_item_step.Item.subject_id.S", - "orcabus_id.$": "$.get_library_item_step.Item.orcabus_id.S", + "subject_orcabus_id.$": "$.get_library_item_step.Item.subject_orcabus_id.S", "fastq_list_row_id_set.$": "$.get_library_item_step.Item.fastq_list_row_id_set.SS" }, "complementary_library_obj_list.$": "$.get_complementary_libraries_step.complementary_library_obj_list" @@ -325,7 +335,7 @@ } } ], - "Next": "Generate Draft Event Payload", + "Next": "Get Subject Object From Orcabus Id", "ResultSelector": { "tumor_fastq_list_rows.$": "$.[0].get_fastq_list_rows.tumor_fastq_list_rows", "tumor_fastq_list_row_ids.$": "$.[0].get_fastq_list_rows.tumor_fastq_list_row_ids", @@ -336,6 +346,35 @@ }, "ResultPath": "$.get_parameters_step" }, + "Get Subject Object From Orcabus Id": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__get_orcabus_obj_from_subject_id_lambda_function_arn__}", + "Payload": { + "value.$": "$.get_library_item_step.Item.subject_orcabus_id.S" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "subject_obj.$": "$.Payload", + "individual_id.$": "States.ArrayGetItem($.Payload.individualSet[?(@.individualId =~ /SBJ.*?/i)].individualId, 0)" + }, + "ResultPath": "$.get_subject_obj_step", + "Next": "Generate Draft Event Payload" + }, "Generate Draft Event Payload": { "Type": "Task", "Resource": "arn:aws:states:::lambda:invoke", @@ -343,8 +382,9 @@ "FunctionName": "${__generate_draft_event_payload_lambda_function_arn__}", "Payload": { "subject_id.$": "$.get_library_item_step.Item.subject_id.S", - "tumor_library_id.$": "$.get_tn_pair_step.tumor_library.id", - "normal_library_id.$": "$.get_tn_pair_step.normal_library.id", + "individual_id.$": "$.get_subject_obj_step.individual_id", + "tumor_library_id.$": "$.get_tn_pair_step.tumor_library.library_id", + "normal_library_id.$": "$.get_tn_pair_step.normal_library.library_id", "tumor_fastq_list_rows.$": "$.get_parameters_step.tumor_fastq_list_rows", "tumor_fastq_list_row_ids.$": "$.get_parameters_step.tumor_fastq_list_row_ids", "fastq_list_rows.$": "$.get_parameters_step.fastq_list_rows", @@ -365,48 +405,35 @@ } ], "ResultPath": "$.generate_draft_event_payload_data_step", - "Next": "Push TN Draft Event", + "Next": "Push TN Ready Event", "ResultSelector": { "input_event_data.$": "$.Payload.input_event_data", "event_tags.$": "$.Payload.event_tags" } }, - "Push TN Draft Event": { + "Push TN Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "Detail": { - "portalRunId.$": "$.get_parameters_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "${__output_status__}", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_parameters_step.workflow_run_name", - "linkedLibraries": [ - { - "libraryId.$": "$.get_tn_pair_step.tumor_library.id", - "orcabusId.$": "$.get_tn_pair_step.tumor_library.orcabus_id" - }, - { - "libraryId.$": "$.get_tn_pair_step.normal_library.id", - "orcabusId.$": "$.get_tn_pair_step.normal_library.orcabus_id" - } - ], - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs.$": "$.generate_draft_event_payload_data_step.input_event_data", - "tags.$": "$.generate_draft_event_payload_data_step.event_tags" - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_parameters_step.portal_run_id", + "workflow_run_name.$": "$.get_parameters_step.workflow_run_name", + "linked_libraries": [ + { + "libraryId.$": "$.get_tn_pair_step.tumor_library.library_id", + "orcabusId.$": "$.get_tn_pair_step.tumor_library.orcabus_id" + }, + { + "libraryId.$": "$.get_tn_pair_step.normal_library.library_id", + "orcabusId.$": "$.get_tn_pair_step.normal_library.orcabus_id" } - }, - "DetailType": "${__detail_type__}", - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}" + ], + "data_inputs.$": "$.generate_draft_event_payload_data_step.input_event_data", + "data_tags.$": "$.generate_draft_event_payload_data_step.event_tags" } - ] + } }, "ResultPath": null, "End": true diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/index.ts deleted file mode 100644 index 27c716088..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 4 - -Input Event Source: `orcabus.wgtsqcinputeventglue` -Input Event DetailType: `FastqListRowStateChange` -Input Event status: `QcComplete` - -* Populate the fastq list row attributes for the rgid for this workflow -*/ - -export interface TnFastqListRowQcCompleteDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class TnFastqListRowQcCompleteConstruct extends Construct { - public readonly TnFastqListRowQcCompleteDbRowMap = { - prefix: 'loctite-fqlr-qc-complete-to-db', - tablePartition: 'fastq_list_row', - triggerSource: 'orcabus.wgtsqcinputeventglue', - triggerStatus: 'QC_COMPLETE', - triggerDetailType: 'FastqListRowStateChange', - }; - - constructor(scope: Construct, id: string, props: TnFastqListRowQcCompleteDbRowConstructProps) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'fastq_list_row_qc_complete', { - stateMachineName: `${this.TnFastqListRowQcCompleteDbRowMap.prefix}-sfn`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __fastq_list_row_partition_name__: this.TnFastqListRowQcCompleteDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus for this event type - */ - const rule = new events.Rule(this, 'tn_populate_fastq_list_row', { - ruleName: `stacky-${this.TnFastqListRowQcCompleteDbRowMap.prefix}-event-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.TnFastqListRowQcCompleteDbRowMap.triggerSource], - detailType: [this.TnFastqListRowQcCompleteDbRowMap.triggerDetailType], - detail: { - status: [{ 'equals-ignore-case': this.TnFastqListRowQcCompleteDbRowMap.triggerStatus }], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json deleted file mode 100644 index 6558f00ad..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_4/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Fastq List Row Item" - }, - "Get Fastq List Row Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.fastqListRowId", - "id_type": "${__fastq_list_row_partition_name__}" - } - }, - "ResultPath": "$.get_fastq_list_row_item_step", - "Next": "Fastq List Row In DataBase" - }, - "Fastq List Row In DataBase": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_fastq_list_row_item_step.Item", - "IsPresent": true, - "Comment": "Fastq List Row In Database", - "Next": "Add QC Metrics To Fastq List Row" - } - ], - "Default": "Pass" - }, - "Add QC Metrics To Fastq List Row": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.fastqListRowId", - "id_type": "${__fastq_list_row_partition_name__}" - }, - "UpdateExpression": "SET qc_metrics_json = :qc_metrics_json", - "ExpressionAttributeValues": { - ":qc_metrics_json": { - "S.$": "States.JsonToString($.payload_data.qcMetrics)" - } - } - }, - "ResultPath": null, - "End": true - }, - "Pass": { - "Type": "Pass", - "End": true - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/find_complement_library_pair_py/find_complement_library_pair.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/find_complement_library_pair_py/find_complement_library_pair.py deleted file mode 100644 index 33e7831c5..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_5/library-qc-complete-db-to-tn-draft/lambdas/find_complement_library_pair_py/find_complement_library_pair.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 - -""" -Given a library object and a list of complementary library objects, find a matching pair for the library object. - -The library objects must match on 'workflow' and 'type' attributes, but must be the opposite phenotype. -""" - -from typing import Dict, List - - -def find_complement_library_pair(library: Dict, complement_libraries: List[Dict]): - """ - Given a library object and a list of complementary library objects, find a matching pair for the library object - within the complement library list. - :param library: - :param complement_libraries: - :return: - """ - for complement_library in complement_libraries: - if library['workflow'] == complement_library['workflow'] and library['type'] == complement_library['type']: - if library['phenotype'] != complement_library['phenotype']: - return library, complement_library - return None, None - - -def handler(event, context): - """ - Lambda handler function - :param event: - :param context: - :return: - """ - - library_obj: Dict = event['library_obj'] - complement_libraries: List[Dict] = event['complementary_library_obj_list'] - - library, complement_library = find_complement_library_pair(library_obj, complement_libraries) - - if library is None: - return { - 'successful_pairing': False, - 'tumor_library': None, - 'normal_library': None - } - - if library['phenotype'] == 'tumor': - tumor_library = library - normal_library = complement_library - else: - tumor_library = complement_library - normal_library = library - - return { - 'successful_pairing': True, - 'tumor_library': tumor_library, - 'normal_library': normal_library - } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_6/tn-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_6/tn-draft-to-ready/index.ts deleted file mode 100644 index 7e533d704..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/loctite/part_6/tn-draft-to-ready/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 6 - -Input Event source: `orcabus.tninputeventglue` -Input Event DetailType: `WorkflowDraftRunStateChange` -Input Event status: `draft` - -Output Event source: `orcabus.tninputeventglue` -Output Event DetailType: `WorkflowRunStateChange` -Output Event status: `ready` - -* The tnInputMaker, subscribes to the tn input event glue (itself) and generates a ready event for the tnReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference -*/ - -export interface TnInputMakerConstructProps { - /* Event bus object */ - eventBusObj: events.IEventBus; - /* Tables */ - inputMakerTableObj: dynamodb.ITableV2; - /* SSM Parameter Objects */ - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - cacheUriSsmParameterObj: ssm.IStringParameter; - /* Secrets */ - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class TnInputMakerConstruct extends Construct { - public readonly tnInputMakerEventMap = { - prefix: 'loctite-tn', - tablePartition: 'tn', - triggerSource: 'orcabus.tninputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.tninputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.07.16', - workflowName: 'tumor_normal', - workflowVersion: '4.2.4', - }; - - constructor(scope: Construct, id: string, props: TnInputMakerConstructProps) { - super(scope, id); - - /* - Part 3: Build the external sfn - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'tn_internal_input_maker', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.tnInputMakerEventMap.prefix, - payloadVersion: this.tnInputMakerEventMap.payloadVersion, - stateMachinePrefix: this.tnInputMakerEventMap.prefix, - rulePrefix: `stacky-${this.tnInputMakerEventMap.prefix}`, - - /* - Table objects - */ - tableObj: props.inputMakerTableObj, - tablePartitionName: this.tnInputMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerDetailType: this.tnInputMakerEventMap.triggerDetailType, - triggerSource: this.tnInputMakerEventMap.triggerSource, - triggerStatus: this.tnInputMakerEventMap.triggerStatus, - outputSource: this.tnInputMakerEventMap.outputSource, - workflowName: this.tnInputMakerEventMap.workflowName, - workflowVersion: this.tnInputMakerEventMap.workflowVersion, - - /* - SSM Parameter Objects - */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - - /* - Secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/index.ts index 87338250e..4bfa25d66 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/index.ts @@ -5,9 +5,7 @@ import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import { WtsInitialiseLibraryAndFastqListRowConstruct } from './part_1/initialise-wts-library-dbs'; import { WtsPopulateFastqListRowConstruct } from './part_2/update-fastq-list-rows-dbs'; -import { WtsFastqListRowQcCompleteConstruct } from './part_3/update-fastq-list-row-qc-complete-dbs'; -import { LibraryQcCompleteToWtsDraftConstruct } from './part_4/library-qc-complete-to-wts-draft'; -import { WtsInputMakerConstruct } from './part_5/wts-draft-to-ready'; +import { LibraryQcCompleteToWtsReadyConstruct } from './part_3/library-qc-complete-to-wts'; /* Provide the glue to get from the bssh fastq copy manager to submitting wgts qc analyses @@ -18,7 +16,6 @@ export interface wtsGlueHandlerConstructProps { eventBusObj: events.IEventBus; /* Tables */ wtsGlueTableObj: dynamodb.ITableV2; - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameters */ analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; @@ -39,31 +36,11 @@ export class WtsGlueHandlerConstruct extends Construct { Input Event DetailType: `SamplesheetMetadataUnion` Input Event status: `LibraryInSamplesheet` - * Initialise wts instrument db construct + * Initialise wts library qc complete */ - const wts_initialise_library_and_fastq_list_row = - new WtsInitialiseLibraryAndFastqListRowConstruct( - this, - 'wts_initialise_library_and_fastq_list_row', - { - eventBusObj: props.eventBusObj, - tableObj: props.wtsGlueTableObj, - } - ); - - /* - Part 2 - - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `FastqListRowStateChange` - Input Event status: `newFastqListRow` - - * Populate the fastq list row attributes for the rgid for this workflow - */ - - const wts_populate_fastq_list_row = new WtsPopulateFastqListRowConstruct( + const wtsInitialiseLibraryAndFastqListRow = new WtsInitialiseLibraryAndFastqListRowConstruct( this, - 'wts_populate_fastq_list_row', + 'wts_initialise_library_and_fastq_list_row', { eventBusObj: props.eventBusObj, tableObj: props.wtsGlueTableObj, @@ -71,18 +48,18 @@ export class WtsGlueHandlerConstruct extends Construct { ); /* - Part 3 + Part 2 - Input Event Source: `orcabus.wgtsqcinputeventglue` + Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `FastqListRowStateChange` - Input Event status: `QcComplete` + Input Event status: `newFastqListRow` * Populate the fastq list row attributes for the rgid for this workflow */ - const wts_fastq_list_row_qc_complete = new WtsFastqListRowQcCompleteConstruct( + const wtsPopulateFastqListRow = new WtsPopulateFastqListRowConstruct( this, - 'wts_fastq_list_row_qc_complete', + 'wts_populate_fastq_list_row', { eventBusObj: props.eventBusObj, tableObj: props.wtsGlueTableObj, @@ -90,7 +67,7 @@ export class WtsGlueHandlerConstruct extends Construct { ); /* - Part 4 + Part 3 Input Event Source: `orcabus.wgtsqcinputeventglue` Input Event DetailType: `LibraryStateChange` @@ -103,49 +80,22 @@ export class WtsGlueHandlerConstruct extends Construct { * Subscribe to the wgts input event glue, library complete event. * Launch a draft event for the wts pipeline if the libraries' subject has a complement library that is also complete */ - const libraryQcCompleteToWtsDraft = new LibraryQcCompleteToWtsDraftConstruct( + const libraryQcCompleteToWtsDraft = new LibraryQcCompleteToWtsReadyConstruct( this, 'library_qc_complete_to_wts_draft', { - // Event bus + /* Event bus */ eventBusObj: props.eventBusObj, - // SSM Param objects + /* Tables */ tableObj: props.wtsGlueTableObj, - workflowsTableObj: props.inputMakerTableObj, + /* SSM Param objects */ + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, + cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + /* Secrets Manager */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, } ); - - /* - Part 5 - - Input Event source: `orcabus.wtsinputeventglue` - Input Event DetailType: `WorkflowDraftRunStateChange` - Input Event status: `draft` - - Output Event source: `orcabus.wtsinputeventglue` - Output Event DetailType: `WorkflowRunStateChange` - Output Event status: `ready` - - * The wtsInputMaker, subscribes to the wts input event glue (itself) and generates a ready event for the wtsReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference - */ - const wtsInputMaker = new WtsInputMakerConstruct(this, 'fastq_list_row_qc_complete', { - /* Event bus */ - eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, - /* SSM Param objects */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - /* Secrets Manager */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - }); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_1/initialise-wts-library-dbs/step_functions_templates/initialise_wts_library_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_1/initialise-wts-library-dbs/step_functions_templates/initialise_wts_library_db_sfn_template.asl.json index e5d7eaf3c..714ae9436 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_1/initialise-wts-library-dbs/step_functions_templates/initialise_wts_library_db_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_1/initialise-wts-library-dbs/step_functions_templates/initialise_wts_library_db_sfn_template.asl.json @@ -15,7 +15,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.library.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}" } }, @@ -40,10 +40,10 @@ "Parameters": { "TableName": "${__table_name__}", "Item": { - "id.$": "$.payload_data.library.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}", - "orcabus_id": { - "S.$": "$.payload_data.library.orcabusId" + "library_id": { + "S.$": "$.payload_data.library.libraryId" }, "phenotype": { "S.$": "$.payload_data.library.phenotype" @@ -58,7 +58,10 @@ "S.$": "$.payload_data.library.assay" }, "subject_id": { - "S.$": "$.payload_data.library.subject.subjectId" + "S.$": "$.payload_data.subject.subjectId" + }, + "subject_orcabus_id": { + "S.$": "$.payload_data.subject.orcabusId" } } }, @@ -73,7 +76,8 @@ "index.$": "$$.Map.Item.Index", "fastq_list_row_objs.$": "$.payload_data.fastqListRows", "instrument_run_id.$": "$.payload_data.instrumentRunId", - "library_id.$": "$.payload_data.library.libraryId" + "library_id.$": "$.payload_data.library.libraryId", + "library_orcabus_id.$": "$.payload_data.library.orcabusId" }, "ItemProcessor": { "ProcessorConfig": { @@ -105,6 +109,9 @@ "id_type": "${__fastq_list_row_partition_name__}", "library_id": { "S.$": "$.library_id" + }, + "library_orcabus_id": { + "S.$": "$.library_orcabus_id" } } }, @@ -122,7 +129,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.library_id", + "id.$": "$.library_orcabus_id", "id_type": "${__library_partition_name__}" }, "UpdateExpression": "ADD fastq_list_row_id_set :fastq_list_row_id_set", diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/index.ts similarity index 61% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/index.ts index c7e6c4418..ec20034b0 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/index.ts @@ -8,7 +8,11 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; +import { GetMetadataLambdaConstruct } from '../../../../../../../components/python-lambda-metadata-mapper'; /* Part 4 @@ -26,25 +30,30 @@ Output Event status: `draft` */ export interface LibraryQcCompleteToWtsDraftConstructProps { + /* Events */ eventBusObj: events.IEventBus; + /* Tables */ tableObj: dynamodb.ITableV2; - workflowsTableObj: dynamodb.ITableV2; + /* SSM */ + outputUriSsmParameterObj: ssm.IStringParameter; + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + cacheUriSsmParameterObj: ssm.IStringParameter; + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class LibraryQcCompleteToWtsDraftConstruct extends Construct { - public readonly WtsDraftMap = { +export class LibraryQcCompleteToWtsReadyConstruct extends Construct { + public readonly WtsReadyMap = { prefix: 'modpodge-library-qc-to-wts', tablePartition: { library: 'library', fastq_list_row: 'fastq_list_row', }, - portalRunPartitionName: 'portal_run', triggerSource: 'orcabus.wgtsqcinputeventglue', triggerStatus: 'QC_COMPLETE', triggerDetailType: 'LibraryStateChange', outputSource: 'orcabus.wtsinputeventglue', - outputDetailType: 'WorkflowDraftRunStateChange', - outputStatus: 'DRAFT', payloadVersion: '2024.07.23', workflowName: 'wts', workflowVersion: '4.2.4', @@ -69,15 +78,42 @@ export class LibraryQcCompleteToWtsDraftConstruct extends Construct { /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, - `${this.WtsDraftMap.prefix}_sfn_preamble`, + `${this.WtsReadyMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: this.WtsDraftMap.portalRunPartitionName, - stateMachinePrefix: this.WtsDraftMap.prefix, - tableObj: props.workflowsTableObj, - workflowName: this.WtsDraftMap.workflowName, - workflowVersion: this.WtsDraftMap.workflowVersion, + stateMachinePrefix: this.WtsReadyMap.prefix, + workflowName: this.WtsReadyMap.workflowName, + workflowVersion: this.WtsReadyMap.workflowVersion, + } + ).stepFunctionObj; + + /* + Part 2: Build the engine parameters sfn + */ + const engineParameterAndReadyEventMakerSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'fastqlistrow_complete_to_wgtsqc_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.WtsReadyMap.outputSource, + payloadVersion: this.WtsReadyMap.payloadVersion, + workflowName: this.WtsReadyMap.workflowName, + workflowVersion: this.WtsReadyMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.WtsReadyMap.prefix, + stateMachinePrefix: this.WtsReadyMap.prefix, } ).stepFunctionObj; @@ -88,7 +124,7 @@ export class LibraryQcCompleteToWtsDraftConstruct extends Construct { this, 'library_qc_complete_sfn_to_wts_draft', { - stateMachineName: `${this.WtsDraftMap.prefix}-sfn`, + stateMachineName: `${this.WtsReadyMap.prefix}-sfn`, definitionBody: sfn.DefinitionBody.fromFile( path.join( __dirname, @@ -97,26 +133,18 @@ export class LibraryQcCompleteToWtsDraftConstruct extends Construct { ) ), definitionSubstitutions: { - /* Events */ - __event_bus_name__: props.eventBusObj.eventBusName, - __event_source__: this.WtsDraftMap.outputSource, - __detail_type__: this.WtsDraftMap.outputDetailType, - __output_status__: this.WtsDraftMap.outputStatus, - __payload_version__: this.WtsDraftMap.payloadVersion, - __workflow_name__: this.WtsDraftMap.workflowName, - __workflow_version__: this.WtsDraftMap.workflowVersion, - /* Lambdas */ __generate_draft_event_payload_lambda_function_arn__: generateEventDataLambdaObj.currentVersion.functionArn, /* Tables */ __table_name__: props.tableObj.tableName, - __library_partition_name__: this.WtsDraftMap.tablePartition.library, - __fastq_list_row_partition_name__: this.WtsDraftMap.tablePartition.fastq_list_row, + __library_partition_name__: this.WtsReadyMap.tablePartition.library, + __fastq_list_row_partition_name__: this.WtsReadyMap.tablePartition.fastq_list_row, - // State Machines - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + /* State Machines */ + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParameterAndReadyEventMakerSfn.stateMachineArn, }, } ); @@ -127,9 +155,6 @@ export class LibraryQcCompleteToWtsDraftConstruct extends Construct { // access the dynamodb table props.tableObj.grantReadWriteData(qcCompleteToDraftSfn); - // allow the step function to submit events - props.eventBusObj.grantPutEventsTo(qcCompleteToDraftSfn); - // allow the step function to invoke the lambdas generateEventDataLambdaObj.currentVersion.grantInvoke(qcCompleteToDraftSfn); @@ -145,19 +170,20 @@ export class LibraryQcCompleteToWtsDraftConstruct extends Construct { }) ); // Allow the state machine to be able to invoke the preamble sfn - sfn_preamble.grantStartExecution(qcCompleteToDraftSfn); + sfnPreamble.grantStartExecution(qcCompleteToDraftSfn); + engineParameterAndReadyEventMakerSfn.grantStartExecution(qcCompleteToDraftSfn); /* Part 3: Subscribe to the event bus and trigger the internal sfn */ const rule = new events.Rule(this, 'library_qc_complete_to_tn_draft', { - ruleName: `stacky-${this.WtsDraftMap.prefix}-rule`, + ruleName: `stacky-${this.WtsReadyMap.prefix}-rule`, eventBus: props.eventBusObj, eventPattern: { - source: [this.WtsDraftMap.triggerSource], - detailType: [this.WtsDraftMap.triggerDetailType], + source: [this.WtsReadyMap.triggerSource], + detailType: [this.WtsReadyMap.triggerDetailType], detail: { - status: [{ 'equals-ignore-case': this.WtsDraftMap.triggerStatus }], + status: [{ 'equals-ignore-case': this.WtsReadyMap.triggerStatus }], }, }, }); diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py similarity index 100% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json similarity index 79% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json index c755fba5c..ba189ec14 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_4/library-qc-complete-to-wts-draft/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/library-qc-complete-to-wts/step_functions_templates/add_library_qc_complete_to_wts_draft_sfn_template.asl.json @@ -15,7 +15,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.libraryId", + "id.$": "$.payload_data.library.orcabusId", "id_type": "${__library_partition_name__}" } }, @@ -115,7 +115,7 @@ "Parameters": { "FunctionName": "${__generate_draft_event_payload_lambda_function_arn__}", "Payload": { - "tumor_library_id.$": "$.payload_data.libraryId", + "tumor_library_id.$": "$.payload_data.library.libraryId", "tumor_fastq_list_rows.$": "$.get_parameters_step.tumor_fastq_list_rows", "tumor_fastq_list_row_ids.$": "$.get_parameters_step.tumor_fastq_list_row_ids", "subject_id.$": "$.get_library_item_step.Item.subject_id.S" @@ -135,44 +135,31 @@ } ], "ResultPath": "$.generate_draft_event_payload_data_step", - "Next": "Push WTS Draft Event", + "Next": "Push WTS Ready Event", "ResultSelector": { "input_event_data.$": "$.Payload.input_event_data", "event_tags.$": "$.Payload.event_tags" } }, - "Push WTS Draft Event": { + "Push WTS Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}", - "DetailType": "${__detail_type__}", - "Detail": { - "portalRunId.$": "$.get_parameters_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "${__output_status__}", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_parameters_step.workflow_run_name", - "linkedLibraries": [ - { - "libraryId.$": "$.get_library_item_step.Item.id.S", - "orcabusId.$": "$.get_library_item_step.Item.orcabus_id.S" - } - ], - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs.$": "$.generate_draft_event_payload_data_step.input_event_data", - "tags.$": "$.generate_draft_event_payload_data_step.event_tags" - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_parameters_step.portal_run_id", + "workflow_run_name.$": "$.get_parameters_step.workflow_run_name", + "linked_libraries": [ + { + "libraryId.$": "$.get_library_item_step.Item.library_id.S", + "orcabusId.$": "$.get_library_item_step.Item.id.S" } - } + ], + "data_inputs.$": "$.generate_draft_event_payload_data_step.input_event_data", + "data_tags.$": "$.generate_draft_event_payload_data_step.event_tags" } - ] + } }, "ResultPath": null, "End": true diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/index.ts deleted file mode 100644 index d1e9d8e52..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 3 - -Input Event Source: `orcabus.wgtsqcinputeventglue` -Input Event DetailType: `FastqListRowStateChange` -Input Event status: `QcComplete` - -* Populate the fastq list row attributes for the rgid for this workflow -*/ - -export interface WtsFastqListRowQcCompleteDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class WtsFastqListRowQcCompleteConstruct extends Construct { - public readonly WtsFastqListRowQcCompleteDbRowMap = { - prefix: 'modpodge-fqlr-qc-complete', - tablePartition: 'fastq_list_row', - triggerSource: 'orcabus.wgtsqcinputeventglue', - triggerStatus: 'QC_COMPLETE', - triggerDetailType: 'FastqListRowStateChange', - }; - - constructor(scope: Construct, id: string, props: WtsFastqListRowQcCompleteDbRowConstructProps) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'fastq_list_row_qc_complete', { - stateMachineName: `${this.WtsFastqListRowQcCompleteDbRowMap.prefix}-sfn`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __fastq_list_row_partition_name__: this.WtsFastqListRowQcCompleteDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus for this event type - */ - const rule = new events.Rule(this, 'wts_populate_fastq_list_row', { - ruleName: `stacky-${this.WtsFastqListRowQcCompleteDbRowMap.prefix}-event-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.WtsFastqListRowQcCompleteDbRowMap.triggerSource], - detailType: [this.WtsFastqListRowQcCompleteDbRowMap.triggerDetailType], - detail: { - status: [{ 'equals-ignore-case': this.WtsFastqListRowQcCompleteDbRowMap.triggerStatus }], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json deleted file mode 100644 index 6558f00ad..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_3/update-fastq-list-row-qc-complete-dbs/step_functions_templates/add_fastq_list_row_qc_complete_to_db_sfn_template.asl.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Fastq List Row Item" - }, - "Get Fastq List Row Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.fastqListRowId", - "id_type": "${__fastq_list_row_partition_name__}" - } - }, - "ResultPath": "$.get_fastq_list_row_item_step", - "Next": "Fastq List Row In DataBase" - }, - "Fastq List Row In DataBase": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_fastq_list_row_item_step.Item", - "IsPresent": true, - "Comment": "Fastq List Row In Database", - "Next": "Add QC Metrics To Fastq List Row" - } - ], - "Default": "Pass" - }, - "Add QC Metrics To Fastq List Row": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.fastqListRowId", - "id_type": "${__fastq_list_row_partition_name__}" - }, - "UpdateExpression": "SET qc_metrics_json = :qc_metrics_json", - "ExpressionAttributeValues": { - ":qc_metrics_json": { - "S.$": "States.JsonToString($.payload_data.qcMetrics)" - } - } - }, - "ResultPath": null, - "End": true - }, - "Pass": { - "Type": "Pass", - "End": true - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_5/wts-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_5/wts-draft-to-ready/index.ts deleted file mode 100644 index 570dfb022..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/mod-podge/part_5/wts-draft-to-ready/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import * as events from 'aws-cdk-lib/aws-events'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 5 - -Input Event source: `orcabus.wtsinputeventglue` -Input Event DetailType: `WorkflowDraftRunStateChange` -Input Event status: `draft` - -Output Event source: `orcabus.wtsinputeventglue` -Output Event DetailType: `WorkflowRunStateChange` -Output Event status: `ready` - -* The wtsInputMaker, subscribes to the wts input event glue (itself) and generates a ready event for the wtsReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference -*/ - -export interface WtsInputMakerConstructProps { - /* Event bus object */ - eventBusObj: events.IEventBus; - /* Tables */ - inputMakerTableObj: dynamodb.ITableV2; - /* SSM Parameter Objects */ - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - cacheUriSsmParameterObj: ssm.IStringParameter; - /* Secrets */ - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class WtsInputMakerConstruct extends Construct { - public readonly wtsInputMakerEventMap = { - prefix: 'modpodge-wts', - tablePartition: 'wts', - triggerSource: 'orcabus.wtsinputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.wtsinputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.07.16', - workflowName: 'wts', - workflowVersion: '4.2.4', - }; - - constructor(scope: Construct, id: string, props: WtsInputMakerConstructProps) { - super(scope, id); - - /* - Part 3: Build the external sfn - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'wts_internal_input_maker', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.wtsInputMakerEventMap.prefix, - payloadVersion: this.wtsInputMakerEventMap.payloadVersion, - stateMachinePrefix: this.wtsInputMakerEventMap.prefix, - rulePrefix: `stacky-${this.wtsInputMakerEventMap.prefix}`, - - /* - Table objects - */ - tableObj: props.inputMakerTableObj, - tablePartitionName: this.wtsInputMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerDetailType: this.wtsInputMakerEventMap.triggerDetailType, - triggerSource: this.wtsInputMakerEventMap.triggerSource, - triggerStatus: this.wtsInputMakerEventMap.triggerStatus, - outputSource: this.wtsInputMakerEventMap.outputSource, - workflowName: this.wtsInputMakerEventMap.workflowName, - workflowVersion: this.wtsInputMakerEventMap.workflowVersion, - - /* - SSM Parameter Objects - */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - - /* - Secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/index.ts new file mode 100644 index 000000000..1c5b0075d --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/index.ts @@ -0,0 +1,88 @@ +/* + +Construct the stacky glue for generating a cttso v2 glue stack + +Connect the cttso v2 outputs to pieriandx + +*/ + +import { Construct } from 'constructs'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { PieriandxInitialiseLibraryConstruct } from './part_1/initialise-library-db'; +import { Cttsov2CompleteToPieriandxConstruct } from './part_2/cttso-v2-output-to-pieriandx-ready-event'; + +/* +Provide the glue to get from the bssh fastq copy manager to submitting wgts qc analyses +*/ + +export interface pieriandxGlueHandlerConstructProps { + /* General */ + eventBusObj: events.IEventBus; + /* Tables */ + pieriandxGlueTableObj: dynamodb.ITableV2; + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; + /* Extras */ + pieriandxProjectInfoSsmParameterObj: ssm.IStringParameter; + redcapLambdaObj: lambda.IFunction; +} + +export class PieriandxGlueHandlerConstruct extends Construct { + constructor(scope: Construct, id: string, props: pieriandxGlueHandlerConstructProps) { + super(scope, id); + /* + Part 1 + + Input Event Source: `orcabus.instrumentrunmanager` + Input Event DetailType: `SamplesheetMetadataUnion` + Input Event status: `LibraryInSamplesheet` + + * Initialise pieriandx instrument db construct + */ + const PieriandxInitialiseLibrary = new PieriandxInitialiseLibraryConstruct( + this, + 'pieriandx_initialise_library', + { + eventBusObj: props.eventBusObj, + tableObj: props.pieriandxGlueTableObj, + } + ); + + /* + Part 2 + + Input Event Source: `orcabus.workflowmanager` + Input Event DetailType: `WorkflowRunStateChange` + Input Event status: `succeeded` + + Output Event source: `orcabus.pieriandxinputeventglue` + Output Event DetailType: `WorkflowDraftRunStateChange` + Output Event status: `draft` + + * Populate the fastq list row attributes for the rgid for this workflow + */ + + const cttsov2CompleteToPieriandxReady = new Cttsov2CompleteToPieriandxConstruct( + this, + 'cttsov2_to_pieriandx', + { + /* Events*/ + eventBusObj: props.eventBusObj, + + /* Tables */ + tableObj: props.pieriandxGlueTableObj, + + /* Secrets Manager */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Extras */ + projectInfoSsmParameterObj: props.pieriandxProjectInfoSsmParameterObj, + redcapLambdaObj: props.redcapLambdaObj, + } + ); + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/index.ts new file mode 100644 index 000000000..aad69e93e --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/index.ts @@ -0,0 +1,112 @@ +/* + +Populate the library database for an instrument run + +We need to collect the following attributes in the library database for each sample in pieriandx: + +subject_id +project_name +project_owner +instrument_run_id +external_subject_id +external_sample_id + +We store this under the library table + +*/ + +import { Construct } from 'constructs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import path from 'path'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; + +export interface PieriandxInitialiseLibraryConstructProps { + tableObj: dynamodb.ITableV2; + eventBusObj: events.IEventBus; +} + +export class PieriandxInitialiseLibraryConstruct extends Construct { + public readonly PieriandxInitialiseLibrary = { + prefix: 'nails-make-library', + tablePartition: { + library: 'library', + }, + triggerSource: 'orcabus.instrumentrunmanager', + triggerStatus: 'LibraryInSamplesheet', + triggerDetailType: 'SamplesheetMetadataUnion', + triggerAssayType: { + v1: 'cttso', + v2: 'cttsov2', + }, + }; + + constructor(scope: Construct, id: string, props: PieriandxInitialiseLibraryConstructProps) { + super(scope, id); + + /* + Part 1: Build the internal sfn + */ + const inputMakerSfn = new sfn.StateMachine(this, 'initialise_pieriandx_library_db_row', { + stateMachineName: `${this.PieriandxInitialiseLibrary.prefix}-initialise-pieriandx-library-db`, + definitionBody: sfn.DefinitionBody.fromFile( + path.join( + __dirname, + 'step_functions_templates', + 'store_cttsov2_metadata_sfn_template.asl.json' + ) + ), + definitionSubstitutions: { + /* General */ + __table_name__: props.tableObj.tableName, + + /* Table Partitions */ + __library_partition_name__: this.PieriandxInitialiseLibrary.tablePartition.library, + }, + }); + + /* + Part 2: Grant the sfn permissions + */ + // access the dynamodb table + props.tableObj.grantReadWriteData(inputMakerSfn.role); + + /* + Part 3: Subscribe to the library events from the event bus where the library assay type + is WGS and the workflow is RESEARCH or CLINICAL + and where the phenotype is NORMAL or TUMOR + */ + const rule = new events.Rule(this, 'initialise_library_assay', { + ruleName: `stacky-${this.PieriandxInitialiseLibrary.prefix}-rule`, + eventBus: props.eventBusObj, + eventPattern: { + source: [this.PieriandxInitialiseLibrary.triggerSource], + detailType: [this.PieriandxInitialiseLibrary.triggerDetailType], + detail: { + payload: { + data: { + library: { + assay: [ + { + 'equals-ignore-case': this.PieriandxInitialiseLibrary.triggerAssayType.v1, + }, + { + 'equals-ignore-case': this.PieriandxInitialiseLibrary.triggerAssayType.v2, + }, + ], + }, + }, + }, + }, + }, + }); + + // Add target of event to be the state machine + rule.addTarget( + new eventsTargets.SfnStateMachine(inputMakerSfn, { + input: events.RuleTargetInput.fromEventPath('$.detail'), + }) + ); + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/step_functions_templates/store_cttsov2_metadata_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/step_functions_templates/store_cttsov2_metadata_sfn_template.asl.json new file mode 100644 index 000000000..de06d01db --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_1/initialise-library-db/step_functions_templates/store_cttsov2_metadata_sfn_template.asl.json @@ -0,0 +1,48 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "Move Inputs", + "States": { + "Move Inputs": { + "Type": "Pass", + "Next": "Initialise Library ID", + "Parameters": { + "inputs.$": "$", + "input_payload_data.$": "$.payload.data" + } + }, + "Initialise Library ID": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id": { + "S.$": "$.input_payload_data.library.orcabusId" + }, + "id_type": { + "S": "${__library_partition_name__}" + }, + "library_id": { + "S.$": "$.input_payload_data.library.libraryId" + }, + "library_obj": { + "S.$": "States.JsonToString($.input_payload_data.library)" + }, + "project_id": { + "S.$": "$.input_payload_data.projectSet[0].projectId" + }, + "external_sample_id": { + "S.$": "$.input_payload_data.sample.externalSampleId" + }, + "external_subject_id": { + "S.$": "$.input_payload_data.subject.subjectId" + }, + "instrument_run_id": { + "S.$": "$.input_payload_data.instrumentRunId" + } + } + }, + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/index.ts new file mode 100644 index 000000000..d80f0e0f7 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/index.ts @@ -0,0 +1,267 @@ +/* + +Given a cttsov2 success event we need to + +1. Generate a portal run id +2. Collect any data available from redcap for this given subject / library combination +3. Collect the project owner and project name configuration for pieriandx +4. Collect the cttsov2 outputs +5. Send the data to pieriandx for processing + +*/ + +import { Construct } from 'constructs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import path from 'path'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; +import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { Duration } from 'aws-cdk-lib'; + +/* +Part 2 + +Input Event Source: `orcabus.workflowmanager` +Input Event DetailType: `WorkflowRunStateChange` +Input Event status: `COMPLETE` +Input Event Workflow Name: `cttsov2` + +Output Event Source: `orcabus.tninputeventglue` +Output Event DetailType: `WorkflowRunStateChange` +Output Event status: `READY` +Output Event Workflow Name: `pieriandx` + +* Subscribe to the wgts input event glue, library complete event. +* Launch a draft event for the tumor normal pipeline if the libraries' subject has a complement library that is also complete +*/ + +export interface Cttsov2CompleteToPieriandxConstructProps { + /* Events */ + eventBusObj: events.IEventBus; + + /* Tables */ + tableObj: dynamodb.ITableV2; + + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; + + /* Extras */ + projectInfoSsmParameterObj: ssm.IStringParameter; + redcapLambdaObj: lambda.IFunction; +} + +export class Cttsov2CompleteToPieriandxConstruct extends Construct { + public readonly PierianDxMap = { + prefix: 'nails-cttsov2-complete-to-pdx', + tablePartition: { + subject: 'subject', + library: 'library', + fastq_list_row: 'fastq_list_row', + }, + + /* Input Rules */ + triggerSource: 'orcabus.workflowmanager', + triggerStatus: 'succeeded', + triggerWorkflowName: 'cttsov2', + triggerDetailType: 'WorkflowRunStateChange', + + /* Output Events */ + eventDetailType: 'WorkflowRunStateChange', + eventStatus: 'READY', + outputSource: 'orcabus.pieriandxinputeventglue', + payloadVersion: '2024.07.23', + workflowName: 'pieriandx', + workflowVersion: '2.1', + + /* Default values */ + defaultSpecimenCode: '122561005', + defaultSpecimenLabel: 'primarySpecimen', + }; + + constructor(scope: Construct, id: string, props: Cttsov2CompleteToPieriandxConstructProps) { + super(scope, id); + + /* + Part 1: Build the lambdas + */ + const generatePortalRunIdPyLambdaObj = new PythonFunction( + this, + 'generatePortalRunIdPyLambdaObj', + { + entry: path.join(__dirname, '/lambdas/generate_portal_run_id_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'generate_portal_run_id.py', + handler: 'handler', + } + ); + + const getDataFromRedCapPyLambdaObj = new PythonFunction(this, 'getDataFromRedCapPyLambdaObj', { + entry: path.join(__dirname, '/lambdas/get_data_from_redcap_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_data_from_redcap.py', + handler: 'handler', + timeout: Duration.seconds(60), + }); + + const getDeidentifiedCaseMetadataPyLambdaObj = new PythonFunction( + this, + 'getDeidentifiedCaseMetadataPyLambdaObj', + { + entry: path.join(__dirname, '/lambdas/get_deidentified_case_metadata_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_deidentified_case_metadata.py', + handler: 'handler', + } + ); + const getIdentifiedCaseMetadataPyLambdaObj = new PythonFunction( + this, + 'getIdentifiedCaseMetadataPyLambdaObj', + { + entry: path.join(__dirname, '/lambdas/get_identified_case_metadata_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_identified_case_metadata.py', + handler: 'handler', + } + ); + const getPieriandxDataFilesPyLambdaObj = new PythonFunction( + this, + 'getPieriandxDataFilesPyLambdaObj', + { + entry: path.join(__dirname, '/lambdas/get_pieriandx_data_files_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_pieriandx_data_files.py', + handler: 'handler', + timeout: Duration.seconds(300), + environment: { + ICAV2_ACCESS_TOKEN_SECRET_ID: props.icav2AccessTokenSecretObj.secretName, + }, + } + ); + const getProjectInfoPyLambdaObj = new PythonFunction(this, 'getProjectInfoPyLambdaObj', { + entry: path.join(__dirname, '/lambdas/get_project_info_py'), + runtime: lambda.Runtime.PYTHON_3_12, + architecture: lambda.Architecture.ARM_64, + index: 'get_project_info.py', + handler: 'handler', + }); + + /* + Handle lambda permissions + */ + props.redcapLambdaObj.grantInvoke(getDataFromRedCapPyLambdaObj.currentVersion); + getDataFromRedCapPyLambdaObj.addEnvironment( + 'REDCAP_LAMBDA_FUNCTION_NAME', + props.redcapLambdaObj.functionName + ); + + // Allow the getPieriandxDataFilesPyLambdaObj to read the secret + props.icav2AccessTokenSecretObj.grantRead(getPieriandxDataFilesPyLambdaObj.currentVersion); + + // Allow the getProjectInfoPyLambdaObj to read the ssm parameters + props.projectInfoSsmParameterObj.grantRead(getProjectInfoPyLambdaObj.currentVersion); + getProjectInfoPyLambdaObj.addEnvironment( + 'PIERIANDX_SAMPLE_CONFIGURATION_SSM_PARAMETER_NAME', + props.projectInfoSsmParameterObj.parameterName + ); + + /* + Part 2: Build the sfn + */ + const inputMakerSfn = new sfn.StateMachine(this, 'cttsov2_outputs_to_pieriandx', { + stateMachineName: `${this.PierianDxMap.prefix}-sfn`, + definitionBody: sfn.DefinitionBody.fromFile( + path.join( + __dirname, + 'step_functions_templates', + 'cttso_v2_outputs_to_pieriandx_ready_event_sfn_template.asl.json' + ) + ), + definitionSubstitutions: { + /* Event handlers */ + __event_bus_name__: props.eventBusObj.eventBusName, + __event_detail_type__: this.PierianDxMap.eventDetailType, + __event_source__: this.PierianDxMap.outputSource, + __event_status__: this.PierianDxMap.eventStatus, + __event_version__: this.PierianDxMap.payloadVersion, + __workflow_name__: this.PierianDxMap.workflowName, + __workflow_version__: this.PierianDxMap.workflowVersion, + __workflow_version_sub__: this.PierianDxMap.workflowVersion.replace(/\./g, '-'), + + /* Lambdas */ + __generate_portal_run_id_lambda_function_arn__: + generatePortalRunIdPyLambdaObj.currentVersion.functionArn, + __get_deidentified_case_metadata_lambda_function_arn__: + getDeidentifiedCaseMetadataPyLambdaObj.currentVersion.functionArn, + __get_identified_case_metadata_lambda_function_arn__: + getIdentifiedCaseMetadataPyLambdaObj.currentVersion.functionArn, + __get_pieriandx_project_pathway_mapping_lambda_function_arn__: + getProjectInfoPyLambdaObj.currentVersion.functionArn, + __get_project_data_files_lambda_function_arn__: + getPieriandxDataFilesPyLambdaObj.currentVersion.functionArn, + __get_sample_redcap_info_lambda_function_arn__: + getDataFromRedCapPyLambdaObj.currentVersion.functionArn, + + /* Tables */ + __table_name__: props.tableObj.tableName, + __library_table_partition_name__: this.PierianDxMap.tablePartition.library, + + /* Extras */ + __specimen_code__: this.PierianDxMap.defaultSpecimenCode, + __specimen_label__: this.PierianDxMap.defaultSpecimenLabel, + }, + }); + + /* + Part 2: Grant the sfn permissions + */ + // access the dynamodb table + props.tableObj.grantReadWriteData(inputMakerSfn); + + // allow the step function to invoke the lambdas + [ + generatePortalRunIdPyLambdaObj, + getDataFromRedCapPyLambdaObj, + getDeidentifiedCaseMetadataPyLambdaObj, + getIdentifiedCaseMetadataPyLambdaObj, + getPieriandxDataFilesPyLambdaObj, + getProjectInfoPyLambdaObj, + ].forEach((lambdaObj) => { + lambdaObj.currentVersion.grantInvoke(inputMakerSfn); + }); + + // Allow step function to submit events to the event bus + props.eventBusObj.grantPutEventsTo(inputMakerSfn); + + /* + Part 3: Subscribe to the event bus and trigger the internal sfn + */ + const rule = new events.Rule(this, 'library_qc_complete_to_tn_draft', { + ruleName: `stacky-${this.PierianDxMap.prefix}-rule`, + eventBus: props.eventBusObj, + eventPattern: { + source: [this.PierianDxMap.triggerSource], + detailType: [this.PierianDxMap.triggerDetailType], + detail: { + status: [{ 'equals-ignore-case': this.PierianDxMap.triggerStatus }], + workflowName: [{ 'equals-ignore-case': this.PierianDxMap.triggerWorkflowName }], + }, + }, + }); + + // Add target of event to be the state machine + rule.addTarget( + new eventsTargets.SfnStateMachine(inputMakerSfn, { + input: events.RuleTargetInput.fromEventPath('$.detail'), + }) + ); + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/generate_portal_run_id_py/generate_portal_run_id.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/generate_portal_run_id_py/generate_portal_run_id.py new file mode 100644 index 000000000..b4eb606b4 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/generate_portal_run_id_py/generate_portal_run_id.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +""" +Generate a portal run id +""" + +# Imports +from datetime import timezone, datetime +import os + + +def handler(event, context): + """ + Generate a portal run id + """ + return { + "portal_run_id": datetime.now(timezone.utc).strftime('%Y%m%d') + os.urandom(4).hex() + } + + +# if __name__ == '__main__': +# import json +# print(json.dumps(handler(None, None), indent=4)) +# +# # { +# # "portal_run_id": "20240923e75c96f6" +# # } \ No newline at end of file diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/get_data_from_redcap.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/get_data_from_redcap.py new file mode 100644 index 000000000..e441efd3a --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/get_data_from_redcap.py @@ -0,0 +1,339 @@ +#!/usr/bin/env python + +""" +Given a library id, retrieve the necessary information from the REDCap database for this library id + +We really only need the disease name if it exists +""" + +# Standard imports +import typing +from typing import List +from time import sleep +from typing import Dict +from os import environ +import pandas as pd +import boto3 +from botocore.exceptions import ClientError +import json +import pytz +from datetime import datetime +import logging + +if typing.TYPE_CHECKING: + from mypy_boto3_lambda import LambdaClient + +# Set logger +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Globals +AUS_TIMEZONE = pytz.timezone("Australia/Melbourne") +AUS_TIME = datetime.now(AUS_TIMEZONE) +AUS_TIME_CURRENT_DEFAULT_DICT = { + "date_accessioned": AUS_TIME.date().isoformat(), + "date_collected": AUS_TIME.date().isoformat(), + "time_collected": AUS_TIME.strftime("%H:%M"), + "date_received": AUS_TIME.date().isoformat() +} +AUS_TIMEZONE_SUFFIX = AUS_TIME.strftime("%z") + +REDCAP_RAW_FIELDS_CLINICAL: List = [ + "record_id", + "clinician_firstname", + "clinician_lastname", + "patient_urn", + "disease", + "date_collection", + "time_collected", + "date_receipt", + "id_sbj", + "libraryid" +] + +REDCAP_LABEL_FIELDS_CLINICAL: List = [ + "record_id", + "report_type", + "disease", + "patient_gender", + "id_sbj", + "libraryid", + "pierian_metadata_complete" +] + + +def get_lambda_client() -> 'LambdaClient': + return boto3.client('lambda') + + +def get_redcap_lambda_from_env(): + """ + Get the redcap lambda from the environment + :return: + """ + return environ['REDCAP_LAMBDA_FUNCTION_NAME'] + + +def warm_up_lambda(): + """ + Warm up the lambda function + :return: + """ + try: + get_lambda_client().invoke( + FunctionName=get_redcap_lambda_from_env(), + InvocationType='RequestResponse' + ) + return True + except ClientError as e: + logger.info(f"Error warming up lambda: {e}") + return False + + +def launch_redcap_raw_lambda(library_id: str) -> pd.DataFrame: + """ + Launch the redcap lambda + :param library_id: + :return: + """ + redcap_raw_df: pd.DataFrame = pd.DataFrame(columns=REDCAP_RAW_FIELDS_CLINICAL) + + raw_list: List = json.loads( + json.loads( + get_lambda_client().invoke( + FunctionName=get_redcap_lambda_from_env(), + InvocationType='RequestResponse', + Payload=json.dumps( + { + "redcapProjectName": "TinyCT", + "queryStringParameters": { + "filter_logic": f"[libraryid] = \"{library_id}\"", + "fields": REDCAP_RAW_FIELDS_CLINICAL, + "raw_or_label": "raw", + } + } + ) + )['Payload'].read() + )['body'] + ) + + # Concat the raw data to the redcap raw df + redcap_raw_df = pd.concat( + [ + redcap_raw_df, + pd.DataFrame(raw_list, columns=REDCAP_RAW_FIELDS_CLINICAL) + ] + ) + + # Rename columns + redcap_raw_df.rename( + columns={ + "clinician_firstname": "requesting_physicians_first_name", + "clinician_lastname": "requesting_physicians_last_name", + "libraryid": "library_id", + "mrn": "patient_urn", + "disease": "disease_id", + "date_collection": "date_collected", + "date_receipt": "date_received" + }, + inplace=True + ) + + # Replace null values with NAs + redcap_raw_df = redcap_raw_df.replace({None: pd.NA, "": pd.NA}) + + return redcap_raw_df + + + +def launch_redcap_label_lambda(library_id: str) -> pd.DataFrame: + """ + Launch the redcap lambda + :param library_id: + :return: + """ + redcap_label_df: pd.DataFrame = pd.DataFrame(columns=REDCAP_LABEL_FIELDS_CLINICAL) + + label_list: List = json.loads( + json.loads( + get_lambda_client().invoke( + FunctionName=get_redcap_lambda_from_env(), + InvocationType='RequestResponse', + Payload=json.dumps( + { + "redcapProjectName": "TinyCT", + "queryStringParameters": { + "filter_logic": f"[libraryid] = \"{library_id}\"", + "fields": REDCAP_LABEL_FIELDS_CLINICAL, + "raw_or_label": "label", + } + } + ) + )['Payload'].read() + )['body'] + ) + + # Concatenate dict with empty columns + redcap_label_df = pd.concat( + [ + redcap_label_df, + pd.DataFrame(label_list, columns=REDCAP_LABEL_FIELDS_CLINICAL) + ] + ) + + # Rename columns + redcap_label_df.rename( + columns={ + "report_type": "sample_type", + "patient_gender": "gender", + "disease": "disease_name", + "libraryid": "library_id" + }, + inplace=True + ) + + # Filter to select columns + redcap_label_df = redcap_label_df[ + [ + "sample_type", + "disease_name", + "gender", + "library_id", + "pierian_metadata_complete" + ] + ] + + return redcap_label_df + +def get_and_merge_raw_and_label_data(library_id: str) -> Dict: + """ + Get the raw and label data from redcap and merge it + :param library_id: + :return: + """ + redcap_raw_df = launch_redcap_raw_lambda(library_id) + redcap_label_df = launch_redcap_label_lambda(library_id) + + # Check we have at least one entry + if redcap_raw_df.shape[0] == 0: + logger.info(f"No entries found for library '{library_id}'") + raise ValueError + + # Update the date field with na values if not set (for validation samples only) + validation_samples_index = redcap_label_df.query( + "sample_type.str.lower()=='validation'" + ).index + # For clinical samples, we only need to update the time_collected field + clinical_samples_index = redcap_label_df.query( + "not sample_type=='validation'" + ).index + + # Replace na values for date_collection or date_received, or date_receipt if None or null + # Update time_collected field in both since it might not exist + # Update for validation samples + for date_column in ["date_collected", "date_received", "time_collected"]: + redcap_raw_df.loc[validation_samples_index, date_column] = \ + redcap_raw_df.loc[validation_samples_index, date_column].fillna(AUS_TIME_CURRENT_DEFAULT_DICT[date_column]) + # Update for clinical samples + for date_column in ["time_collected"]: + redcap_raw_df.loc[clinical_samples_index, date_column] = \ + redcap_raw_df.loc[clinical_samples_index, date_column].fillna(AUS_TIME_CURRENT_DEFAULT_DICT[date_column]) + + # Update date fields + redcap_raw_df["date_collected"] = redcap_raw_df.apply( + lambda date_str: date_str.date_collected + "T" + date_str.time_collected + f":00{AUS_TIMEZONE_SUFFIX}", + axis="columns" + ) + + # Add time to 'date_receipt' string + redcap_raw_df["date_received"] = redcap_raw_df.apply( + lambda x: x.date_received + f"T00:00:00{AUS_TIMEZONE_SUFFIX}", + axis="columns" + ) + + # Subset columns for redcap raw df + redcap_raw_df = redcap_raw_df[ + [ + "disease_id", + "requesting_physicians_first_name", + "requesting_physicians_last_name", + "library_id", + "date_collected", + "date_received", + "patient_urn" + ] + ] + + # Merge redcap data + redcap_df: pd.DataFrame = pd.merge( + redcap_raw_df, redcap_label_df, + on=["library_id"] + ) + + # Merge redcap information and then return + num_entries: int + if not (num_entries := redcap_df.shape[0]) == 1: + logger.info(f"Expected dataframe to be of length 1, not {num_entries}") + raise ValueError(f"Expected dataframe to be of length 1, not {num_entries}") + + return redcap_df.to_dict(orient='records')[0] + + +def handler(event, context) -> Dict: + """ + Handler for the lambda function + :param event: + :param context: + :return: + """ + # Wait for lambda to warm up + while not warm_up_lambda(): + sleep(10) + + # Return + try: + return { + "redcap_data": get_and_merge_raw_and_label_data(event['library_id']), + "in_redcap": True + } + except ValueError: + return { + "redcap_data": None, + "in_redcap": False + } + + +# if __name__ == '__main__': +# # Or 'umccr-staging' / 'umccr-production' +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# # Or 'redcap-apis-stg-lambda-function' / 'redcap-apis-prod-lambda-function' +# environ['REDCAP_LAMBDA_FUNCTION_NAME'] = 'redcap-apis-dev-lambda-function' +# print( +# json.dumps( +# handler( +# event={ +# "library_id": 'L2401380' +# }, +# context=None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "redcap_data": { +# # "disease_id": 254637007, +# # "requesting_physicians_first_name": "XXX", +# # "requesting_physicians_last_name": "XXX", +# # "library_id": "L2401380", +# # "date_collected": "2024-09-06T23:00:00+1000", +# # "date_received": "2024-09-06T00:00:00+1000", +# # "patient_urn": "0038-61302", +# # "sample_type": "Patient Care Sample", +# # "disease_name": "Non-small cell lung cancer", +# # "gender": "Unknown", +# # "pierian_metadata_complete": "Complete" +# # }, +# # "in_redcap": true +# # } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/requirements.txt b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/requirements.txt new file mode 100644 index 000000000..ff6c06e3e --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_data_from_redcap_py/requirements.txt @@ -0,0 +1,2 @@ +pytz==2024.2 +pandas==2.2.3 diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/get_deidentified_case_metadata.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/get_deidentified_case_metadata.py new file mode 100644 index 000000000..214a20a59 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/get_deidentified_case_metadata.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +""" +Return payload of de-identified case metadata + +Payload will look a bit like this: + +{ + "isIdentified": false, + "caseAccessionNumber": "SBJ04407__L2400161__V2__abcd1238", + "externalSpecimenId": "externalspecimenid", + "sampleType": "PatientCare", + "specimenLabel": "primarySpecimen", + "indication": "Test", + "diseaseCode": 64572001, + "specimenCode": 122561005, + "sampleReception": { + "dateAccessioned": "2021-01-01T00:00:00Z", + "dateCollected": "2024-02-20T20:17:00Z", + "dateReceived": "2021-01-01T00:00:00Z" + }, + "study": { + "id": "studyid", + "subjectIdentifier": "subject" + } +} + +""" + +# Imports +import typing +from typing import Dict + +import pytz +from datetime import datetime +import logging + + +# Set logger +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Globals +AUS_TIMEZONE = pytz.timezone("Australia/Melbourne") +AUS_TIME = datetime.now(AUS_TIMEZONE) +AUS_TIME_AS_STR = f"{AUS_TIME.date().isoformat()}T{AUS_TIME.time().isoformat(timespec='seconds')}{AUS_TIME.strftime("%z")}" + +DEFAULT_INDICATION = "NA" + + +def handler(event, context) -> Dict: + # Return payload of de-identified case metadata + + # Get the case accession number from the event + case_accession_number = event.get("case_accession_number") + + # Get the external specimen id from the event + external_sample_id = event.get("external_sample_id") + external_subject_id = event.get("external_subject_id") + project_id = event.get("project_id") + + # Get the sample type from the event + sample_type = event.get("sample_type") + + # Get the specimen label from the event + specimen_label = event.get("specimen_label") + + # Get the indication from the event + indication = event.get("indication", DEFAULT_INDICATION) + + # Get the specimen code from the event + specimen_code = event.get("specimen_code") + + # Get redcap information from the event + # For deidentified samples, we only need to + redcap_dict = event.get("redcap_dict", None) + if redcap_dict is None: + redcap_dict = {} + + # Get the sample reception from the redcap data if it exists + date_accessioned = redcap_dict.get("date_accessioned", AUS_TIME_AS_STR) + date_collected = redcap_dict.get("date_collected", AUS_TIME_AS_STR) + date_received = redcap_dict.get("date_received", AUS_TIME_AS_STR) + + # Set the sample reception dictionary + # Set as camel case for event type + sample_reception = { + "dateAccessioned": date_accessioned, + "dateCollected": date_collected, + "dateReceived": date_received + } + + # Get the disease code from the event if it exists or + disease_code = redcap_dict.get("disease_id", event.get("default_disease_code")) + + # Return the payload + return { + "case_metadata": { + "isIdentified": False, + "caseAccessionNumber": case_accession_number, + "externalSpecimenId": external_sample_id, + "sampleType": sample_type, + "specimenLabel": specimen_label, + "indication": indication, + "diseaseCode": disease_code, + "specimenCode": specimen_code, + "sampleReception": sample_reception, + "study": { + "id": project_id, + "subjectIdentifier": external_subject_id + } + } + } + + +# if __name__ == "__main__": +# import json +# print( +# json.dumps( +# handler( +# { +# "specimen_label": "primarySpecimen", +# "indication": None, +# "specimen_code": "122561005", +# "redcap_dict": None, +# "external_subject_id": "CMM1pc-10646259ilm", +# "default_disease_code": 55342001, +# "external_sample_id": "SSq-CompMM-1pc-10646259ilm", +# "case_accession_number": "L2400160__V2__20241003fc695a2c", +# "sample_type": "patient_care_sample" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "case_metadata": { +# # "case_metadata": { +# # "isIdentified": false, +# # "caseAccessionNumber": "L2400160__V2__20241003fc695a2c", +# # "externalSpecimenId": "SSq-CompMM-1pc-10646259ilm", +# # "sampleType": "patient_care_sample", +# # "specimenLabel": "primarySpecimen", +# # "indication": null, +# # "diseaseCode": 55342001, +# # "specimenCode": "122561005", +# # "sampleReception": { +# # "dateAccessioned": "2024-10-04T09:17:27+1000", +# # "dateCollected": "2024-10-04T09:17:27+1000", +# # "dateReceived": "2024-10-04T09:17:27+1000" +# # }, +# # "study": { +# # "id": null, +# # "subjectIdentifier": "CMM1pc-10646259ilm" +# # } +# # } +# # } \ No newline at end of file diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/requirements.txt b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/requirements.txt new file mode 100644 index 000000000..9d3eb6965 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_deidentified_case_metadata_py/requirements.txt @@ -0,0 +1 @@ +pytz==2024.2 diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/get_identified_case_metadata.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/get_identified_case_metadata.py new file mode 100644 index 000000000..40b07d516 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/get_identified_case_metadata.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 + +""" +Return payload of de-identified case metadata + +Payload will look a bit like this: + +{ + "case_metadata": { + "isIdentified": True, + "caseAccessionNumber": "SBJ04407__L2301368__V2__abcd1234", + "externalSpecimenId": "externalspecimenid", + "sampleType": "PatientCare", + "specimenLabel": "primarySpecimen", + "indication": "Test", + "diseaseCode": 64572001, + "specimenCode": 122561005, + "sampleReception": { + "dateAccessioned": "2021-01-01T00:00:00Z", + "dateCollected": "2024-02-20T20:17:00Z", + "dateReceived": "2021-01-01T00:00:00Z" + }, + "patientInformation": { + "dateOfBirth": "1970-01-01", + "firstName": "John", + "lastName": "Doe" + }, + "medicalRecordNumbers": { + "mrn": "3069999", + "medicalFacility": { + "facility": "Not Available", + "hospitalNumber": "99" + } + }, + "requestingPhysician": { + "firstName": "Meredith", + "lastName": "Gray" + } + } +} + +""" + +# Imports +from typing import Dict + +import pytz +from datetime import datetime +import logging + +# Set logger +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Globals +AUS_TIMEZONE = pytz.timezone("Australia/Melbourne") +AUS_TIME = datetime.now(AUS_TIMEZONE) +AUS_TIME_CURRENT_DEFAULT_DICT = { + "date_accessioned": AUS_TIME.isoformat(timespec='seconds'), + "date_collected": AUS_TIME.isoformat(timespec='seconds'), + "date_received": AUS_TIME.isoformat(timespec='seconds'), +} + +DEFAULT_REQUESTING_PHYSICIAN = { + "first_name": "Sean", + "last_name": "Grimmond" +} + +DEFAULT_INDICATION = "NA" +DEFAULT_HOSPITAL_NUMBER = "99" + + +def handler(event, context) -> Dict: + # Return payload of de-identified case metadata + + # Get the case accession number from the event + case_accession_number = event.get("case_accession_number") + + # Get the external specimen id from the event + external_sample_id = event.get("external_sample_id") + external_subject_id = event.get("external_subject_id") + + # Get the sample type from the event + sample_type = event.get("sample_type") + + # Get the specimen label from the event + specimen_label = event.get("specimen_label") + + # Get the indication from the event + indication = event.get("indication", DEFAULT_INDICATION) + + # Get the specimen code from the event + specimen_code = event.get("specimen_code") + + # Get redcap information from the event + # For deidentified samples, we only need to + redcap_dict = event.get("redcap_dict", None) + if redcap_dict is None: + redcap_dict = {} + + # Get the sample reception from the redcap data if it exists + # Get the sample reception from the redcap data if it exists + date_accessioned = redcap_dict.get("date_accessioned", AUS_TIME_CURRENT_DEFAULT_DICT["date_accessioned"]) + date_collected = redcap_dict.get("date_collected", AUS_TIME_CURRENT_DEFAULT_DICT["date_collected"]) + date_received = redcap_dict.get("date_received", AUS_TIME_CURRENT_DEFAULT_DICT["date_received"]) + + # Set the sample reception dictionary + # Set as camel case for event type + sample_reception = { + "dateAccessioned": date_accessioned, + "dateCollected": date_collected, + "dateReceived": date_received + } + + # Get the disease code from the event if it exists or + disease_code = redcap_dict.get("disease_id", event.get("default_disease_code")) + + # Get patient information from redcap + if redcap_dict is not None: + patient_information = { + "dateOfBirth": "1970-01-01", + "firstName": "Jane" if redcap_dict.get("gender", "female") else "John", + "lastName": "Doe" + } + else: + patient_information = { + "dateOfBirth": "1970-01-01", + "firstName": "John", + "lastName": "Doe" + } + + # Get medical record numbers from redcap + medical_record_numbers = { + "mrn": external_subject_id, + "medicalFacility": { + "facility": "Not Available", + "hospitalNumber": DEFAULT_HOSPITAL_NUMBER + } + } + + # Get requesting physician from redcap + requesting_physician = { + "firstName": redcap_dict.get("requesting_physician_first_name", DEFAULT_REQUESTING_PHYSICIAN["first_name"]), + "lastName": redcap_dict.get("requesting_physician_last_name", DEFAULT_REQUESTING_PHYSICIAN["last_name"]) + } + + # Return the payload + return { + "case_metadata": { + "isIdentified": True, + "caseAccessionNumber": case_accession_number, + "externalSpecimenId": external_sample_id, + "sampleType": sample_type, + "specimenLabel": specimen_label, + "indication": indication, + "diseaseCode": disease_code, + "specimenCode": specimen_code, + "sampleReception": sample_reception, + "patientInformation": patient_information, + "medicalRecordNumbers": medical_record_numbers, + "requestingPhysician": requesting_physician + } + } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/requirements.txt b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/requirements.txt new file mode 100644 index 000000000..9d3eb6965 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_identified_case_metadata_py/requirements.txt @@ -0,0 +1 @@ +pytz==2024.2 diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/get_pieriandx_data_files.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/get_pieriandx_data_files.py new file mode 100644 index 000000000..f671a684c --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/get_pieriandx_data_files.py @@ -0,0 +1,181 @@ +#!/usr/bin/env python3 + +""" +Given the output uri, get the data files ready to upload into the pieriandx s3 bucket + +So given the output uri +'s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/' +And a sample id +'L2400161' + +We would expect the following files to be returned: +{ + "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/DragenCaller/L2400161/L2400161.microsat_output.json", + "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/Tmb/L2400161/L2400161.tmb.metrics.csv", + "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161.cnv.vcf.gz", + "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161.hard-filtered.vcf.gz", + "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_Fusions.csv", + "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_MetricsOutput.tsv", + "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" +} +""" + +# Standard imports +from pathlib import Path +import typing +import logging +import boto3 +from os import environ +from urllib.parse import urlparse, urlunparse + +# Custom imports +from wrapica.project_data import convert_uri_to_project_data_obj +from wrapica.libica_exceptions import ApiException + +# Typing imports +from typing import Dict +if typing.TYPE_CHECKING: + from mypy_boto3_secretsmanager import SecretsManagerClient + + +# Globals +ICAV2_BASE_URL = "https://ica.illumina.com/ica/rest" + +URL_EXTENSION_MAP = { + "microsat_output_uri": "Logs_Intermediates/DragenCaller/{sample_id}/{sample_id}.microsat_output.json", + "tmb_metrics_uri": "Logs_Intermediates/Tmb/{sample_id}/{sample_id}.tmb.metrics.csv", + "cnv_vcf_uri": "Results/{sample_id}/{sample_id}.cnv.vcf.gz", + "hard_filtered_vcf_uri": "Results/{sample_id}/{sample_id}.hard-filtered.vcf.gz", + "fusions_uri": "Results/{sample_id}/{sample_id}_Fusions.csv", + "metrics_output_uri": "Results/{sample_id}/{sample_id}_MetricsOutput.tsv", + "samplesheet_uri": "Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv", +} + +# Set loggers +logger = logging.getLogger() +logger.setLevel(logging.INFO) + + +def get_secrets_manager_client() -> 'SecretsManagerClient': + """ + Return Secrets Manager client + """ + return boto3.client("secretsmanager") + + +def get_secret(secret_id: str) -> str: + """ + Return secret value + """ + return get_secrets_manager_client().get_secret_value(SecretId=secret_id)["SecretString"] + + +# Functions +def set_icav2_env_vars(): + """ + Set the icav2 environment variables + :return: + """ + environ["ICAV2_BASE_URL"] = ICAV2_BASE_URL + environ["ICAV2_ACCESS_TOKEN"] = get_secret( + environ["ICAV2_ACCESS_TOKEN_SECRET_ID"] + ) + + +def extend_url_path(base_url: str, path_ext: Path): + """ + Given a base url, convert to a url object, extend the base url path with the path extension and return the new url + :param base_url: + :param path_ext: + :return: + """ + + base_url_obj = urlparse(base_url) + + # Extend the path + new_path = Path(base_url_obj.path).joinpath(path_ext) + + # Return the new url + return str(urlunparse( + ( + base_url_obj.scheme, + base_url_obj.netloc, + str(new_path), + None, None, None + ) + )) + + +def handler(event, context) -> Dict[str, Dict]: + """ + Firse we need to set the icav2 env vars + :param event: + :param context: + :return: + """ + # Set ICAv2 Env Vars + set_icav2_env_vars() + + # Get the output uri and sample id from the event dict + output_uri = event["output_uri"] + sample_id = event["sample_id"] + + # Get the project name and owner from the output uri + + # Camel case data_files values for the event dict + data_files = { + "microsatOutputUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["microsat_output_uri"].format(sample_id=sample_id))), + "tmbMetricsUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["tmb_metrics_uri"].format(sample_id=sample_id))), + "cnvVcfUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["cnv_vcf_uri"].format(sample_id=sample_id))), + "hardFilteredVcfUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["hard_filtered_vcf_uri"].format(sample_id=sample_id))), + "fusionsUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["fusions_uri"].format(sample_id=sample_id))), + "metricsOutputUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["metrics_output_uri"].format(sample_id=sample_id))), + "samplesheetUri": extend_url_path(output_uri, Path(URL_EXTENSION_MAP["samplesheet_uri"].format(sample_id=sample_id))), + } + + # Check all data files exist + for key, value in data_files.items(): + logger.info(f"Checking {key} exists: {value}") + # Check the url exists + try: + convert_uri_to_project_data_obj(value) + except ApiException as e: + logger.error(f"Error checking {key} exists: {e}") + raise e + + return { + "data_files": data_files + } + + +if __name__ == "__main__": + import json + from os import environ + environ['AWS_PROFILE'] = 'umccr-development' + environ['AWS_DEFAULT_REGION'] = 'ap-southeast-2' + environ['ICAV2_ACCESS_TOKEN_SECRET_ID'] = "ICAv2JWTKey-umccr-prod-service-dev" + + print( + json.dumps( + handler( + { + "output_uri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20241003ead8ad9f/", + "sample_id": "L2400160" + }, + None + ), + indent=4 + ) + ) + + # { + # "data_files": { + # "microsatOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/DragenCaller/L2400161/L2400161.microsat_output.json", + # "tmbMetricsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/Tmb/L2400161/L2400161.tmb.metrics.csv", + # "cnvVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161.cnv.vcf.gz", + # "hardFilteredVcfUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161.hard-filtered.vcf.gz", + # "fusionsUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_Fusions.csv", + # "metricsOutputUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Results/L2400161/L2400161_MetricsOutput.tsv", + # "samplesheetUri": "s3://pipeline-dev-cache-503977275616-ap-southeast-2/byob-icav2/development/analysis/cttsov2/20240910d260200d/Logs_Intermediates/SampleSheetValidation/SampleSheet_Intermediate.csv" + # } + # } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/requirements.txt b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/requirements.txt new file mode 100644 index 000000000..1fa14ec17 --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_pieriandx_data_files_py/requirements.txt @@ -0,0 +1 @@ +wrapica==2.27.1.post20240830140737 diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_project_info_py/get_project_info.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_project_info_py/get_project_info.py new file mode 100644 index 000000000..6450d591b --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/lambdas/get_project_info_py/get_project_info.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 + +""" +Given the results directory of the cttso output directory + +Generate the inputs json for the PierianDx manager + +cttso-lims-project-name-to-pieriandx-mapping + +[ + { + "project_id": "PO", + "panel": "subpanel", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "COUMN", + "panel": "subpanel", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "CUP", + "panel": "main", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": 285645000 + }, + { + "project_id": "PPGL", + "panel": "main", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "MESO", + "panel": "subpanel", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "OCEANiC", + "panel": "subpanel", + "sample_type": "patient_care_sample", + "is_identified": "deidentified", + "default_snomed_disease_code": null + }, + { + "project_id": "SOLACE2", + "panel": "main", + "sample_type": "patient_care_sample", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + }, + { + "project_id": "IMPARP", + "panel": "main", + "sample_type": "patient_care_sample", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + }, + { + "project_id": "Control", + "panel": "main", + "sample_type": "validation", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + }, + { + "project_id": "QAP", + "panel": "subpanel", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "iPredict2", + "panel": "subpanel", + "sample_type": "patient_care_sample", + "is_identified": "identified", + "default_snomed_disease_code": null + }, + { + "project_id": "*", + "panel": "main", + "sample_type": "patient_care_sample", + "is_identified": "deidentified", + "default_snomed_disease_code": 55342001 + } +] + + +""" + +# Standard imports +from os import environ +import boto3 +import typing +import json + + +if typing.TYPE_CHECKING: + from mypy_boto3_ssm import SSMClient + + +def get_ssm_client() -> 'SSMClient': + return boto3.client('ssm') + + +def get_ssm_parameter_value(name: str) -> str: + client = get_ssm_client() + response = client.get_parameter(Name=name, WithDecryption=True) + return response['Parameter']['Value'] + + +def handler(event, context) -> typing.Dict[str, str]: + pieriandx_configuration_dict = json.loads(get_ssm_parameter_value(environ['PIERIANDX_SAMPLE_CONFIGURATION_SSM_PARAMETER_NAME'])) + + # Get the project name / owner + project_id = event.get('project_id', None) + + return { + "project_info": next( + filter( + lambda project_iter: ( + ( + project_iter.get("project_id") == project_id or + project_iter.get("project_id") == "*" + ) + ), + pieriandx_configuration_dict + ) + ) + } + +# # PO +# if __name__ == "__main__": +# import json +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['PIERIANDX_SAMPLE_CONFIGURATION_SSM_PARAMETER_NAME'] = '/umccr/orcabus/pieriandx/project_info' +# +# print( +# json.dumps( +# handler( +# { +# "project_id": "PO" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "project_info": { +# # "project_id": "PO", +# # "panel": "subpanel", +# # "sample_type": "patient_care_sample", +# # "is_identified": "identified", +# # "default_snomed_term": null +# # } +# # } + + + +# # Default value for projects that do not match in list +# if __name__ == "__main__": +# import json +# environ['AWS_PROFILE'] = 'umccr-development' +# environ['AWS_REGION'] = 'ap-southeast-2' +# environ['PIERIANDX_SAMPLE_CONFIGURATION_SSM_PARAMETER_NAME'] = '/umccr/orcabus/pieriandx/project_info' +# +# print( +# json.dumps( +# handler( +# { +# "project_id": "Testing" +# }, +# None +# ), +# indent=4 +# ) +# ) +# +# # { +# # "project_info": { +# # "project_id": "*", +# # "panel": "main", +# # "sample_type": "patient_care_sample", +# # "is_identified": "deidentified", +# # "default_snomed_term": "Neoplastic disease" +# # } +# # } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/step_functions_templates/cttso_v2_outputs_to_pieriandx_ready_event_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/step_functions_templates/cttso_v2_outputs_to_pieriandx_ready_event_sfn_template.asl.json new file mode 100644 index 000000000..b69033d3b --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/nails/part_2/cttso-v2-output-to-pieriandx-ready-event/step_functions_templates/cttso_v2_outputs_to_pieriandx_ready_event_sfn_template.asl.json @@ -0,0 +1,390 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "Move Inputs", + "States": { + "Move Inputs": { + "Type": "Pass", + "Parameters": { + "inputs.$": "$" + }, + "Next": "Get Library Info from DB" + }, + "Get Library Info from DB": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.inputs.linkedLibraries[0].orcabusId", + "id_type": "${__library_table_partition_name__}" + } + }, + "ResultPath": "$.get_library_info_from_db_step", + "Next": "Is Library In DataBase" + }, + "Is Library In DataBase": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_library_info_from_db_step.Item", + "IsPresent": true, + "Next": "Generate Portal Run ID" + } + ], + "Default": "Pass" + }, + "Generate Portal Run ID": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__generate_portal_run_id_lambda_function_arn__}", + "Payload": {} + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "portal_run_id.$": "$.Payload.portal_run_id" + }, + "ResultPath": "$.generate_portal_run_id_step", + "Next": "Generate Case Accession Number" + }, + "Generate Case Accession Number": { + "Type": "Pass", + "Parameters": { + "case_accession_number.$": "States.Format('{}__V2__{}', $.get_library_info_from_db_step.Item.library_id.S, $.generate_portal_run_id_step.portal_run_id)" + }, + "ResultPath": "$.generate_case_accession_number_step", + "Next": "Get Project Info and Project Data Files" + }, + "Get Project Info and Project Data Files": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Get PierianDx Project Pathway mapping", + "States": { + "Get PierianDx Project Pathway mapping": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "Payload": { + "project_id.$": "$.get_library_info_from_db_step.Item.project_id.S" + }, + "FunctionName": "${__get_pieriandx_project_pathway_mapping_lambda_function_arn__}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "project_info.$": "$.Payload.project_info" + }, + "ResultPath": "$.get_pieriandx_project_pathway_mapping_step", + "Next": "Get RedCap Information" + }, + "Get RedCap Information": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Get sample redcap info", + "States": { + "Get sample redcap info": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "Payload": { + "library_id.$": "$.get_library_info_from_db_step.Item.library_id.S" + }, + "FunctionName": "${__get_sample_redcap_info_lambda_function_arn__}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "redcap_data.$": "$.Payload.redcap_data", + "in_redcap.$": "$.Payload.in_redcap" + }, + "ResultPath": "$.get_sample_redcap_info_step", + "Next": "Has Default Disease Code OR In Redcap" + }, + "Has Default Disease Code OR In Redcap": { + "Type": "Choice", + "Choices": [ + { + "And": [ + { + "Variable": "$.get_sample_redcap_info_step.in_redcap", + "BooleanEquals": false + }, + { + "Variable": "$.get_pieriandx_project_pathway_mapping_step.project_info.default_snomed_disease_code", + "IsNull": true + } + ], + "Next": "Fail" + } + ], + "Default": "Option Placeholder 1" + }, + "Fail": { + "Type": "Fail" + }, + "Option Placeholder 1": { + "Type": "Pass", + "End": true + } + } + }, + { + "StartAt": "Parallel Placeholder 1", + "States": { + "Parallel Placeholder 1": { + "Type": "Pass", + "End": true + } + } + } + ], + "ResultSelector": { + "redcap_data.$": "$[0].get_sample_redcap_info_step.redcap_data", + "in_redcap.$": "$[0].get_sample_redcap_info_step.in_redcap" + }, + "ResultPath": "$.get_redcap_info_step", + "Next": "Generate Case Metadata" + }, + "Generate Case Metadata": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Is Identified Sample", + "States": { + "Is Identified Sample": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_pieriandx_project_pathway_mapping_step.project_info.is_identified", + "StringEquals": "identified", + "Next": "Generate Identified Case Metadata" + } + ], + "Default": "Generate DeIdentified case metadata" + }, + "Generate Identified Case Metadata": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__get_identified_case_metadata_lambda_function_arn__}", + "Payload": { + "case_accession_number.$": "$.generate_case_accession_number_step.case_accession_number", + "external_sample_id.$": "$.get_library_info_from_db_step.Item.external_sample_id.S", + "external_subject_id.$": "$.get_library_info_from_db_step.Item.external_subject_id.S", + "sample_type.$": "$.get_pieriandx_project_pathway_mapping_step.project_info.sample_type", + "specimen_label": "${__specimen_label__}", + "specimen_code": "${__specimen_code__}", + "redcap_dict.$": "$.get_redcap_info_step.redcap_data", + "default_disease_code.$": "$.get_pieriandx_project_pathway_mapping_step.project_info.default_snomed_disease_code" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "case_metadata.$": "$.Payload.case_metadata" + }, + "ResultPath": "$.get_case_metadata_step", + "End": true + }, + "Generate DeIdentified case metadata": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__get_deidentified_case_metadata_lambda_function_arn__}", + "Payload": { + "case_accession_number.$": "$.generate_case_accession_number_step.case_accession_number", + "external_sample_id.$": "$.get_library_info_from_db_step.Item.external_sample_id.S", + "external_subject_id.$": "$.get_library_info_from_db_step.Item.external_subject_id.S", + "sample_type.$": "$.get_pieriandx_project_pathway_mapping_step.project_info.sample_type", + "project_id.$": "$.get_library_info_from_db_step.Item.project_id.S", + "specimen_label": "${__specimen_label__}", + "specimen_code": "${__specimen_code__}", + "redcap_dict.$": "$.get_redcap_info_step.redcap_data", + "default_disease_code.$": "$.get_pieriandx_project_pathway_mapping_step.project_info.default_snomed_disease_code" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "case_metadata.$": "$.Payload.case_metadata" + }, + "ResultPath": "$.get_case_metadata_step", + "End": true + } + } + }, + { + "StartAt": "Parallel Placeholder 2", + "States": { + "Parallel Placeholder 2": { + "Type": "Pass", + "End": true + } + } + } + ], + "ResultSelector": { + "case_metadata.$": "$[0].get_case_metadata_step.case_metadata" + }, + "ResultPath": "$.get_case_metadata_step", + "End": true + } + } + }, + { + "StartAt": "Get Project Data Files", + "States": { + "Get Project Data Files": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "Payload": { + "output_uri.$": "$.inputs.payload.data.engineParameters.outputUri", + "sample_id.$": "$.inputs.payload.data.inputs.sampleId" + }, + "FunctionName": "${__get_project_data_files_lambda_function_arn__}" + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "project_data_files.$": "$.Payload.data_files" + }, + "ResultPath": "$.get_project_data_files_step", + "End": true + } + } + } + ], + "ResultSelector": { + "case_metadata.$": "$[0].get_case_metadata_step.case_metadata", + "project_info.$": "$[0].get_pieriandx_project_pathway_mapping_step.project_info", + "project_data_files.$": "$[1].get_project_data_files_step.project_data_files", + "in_redcap.$": "$[0].get_redcap_info_step.in_redcap" + }, + "ResultPath": "$.get_project_info_and_project_data_files_step", + "Next": "Push PierianDx Ready Event" + }, + "Push PierianDx Ready Event": { + "Type": "Task", + "Resource": "arn:aws:states:::events:putEvents", + "Parameters": { + "Entries": [ + { + "Detail": { + "portalRunId.$": "$.generate_portal_run_id_step.portal_run_id", + "status": "${__event_status__}", + "workflowName": "${__workflow_name__}", + "workflowVersion": "${__workflow_version__}", + "workflowRunName.$": "States.Format('${__workflow_name__}--${__workflow_version_sub__}--{}', $.generate_portal_run_id_step.portal_run_id)", + "timestamp.$": "$$.State.EnteredTime", + "linkedLibraries": [ + { + "libraryId.$": "$.get_library_info_from_db_step.Item.library_id.S", + "orcabusId.$": "$.get_library_info_from_db_step.Item.id.S" + } + ], + "payload": { + "version": "${__event_version__}", + "data": { + "inputs": { + "instrumentRunId.$": "$.get_library_info_from_db_step.Item.instrument_run_id.S", + "panelVersion.$": "$.get_project_info_and_project_data_files_step.project_info.panel", + "caseMetadata.$": "$.get_project_info_and_project_data_files_step.case_metadata", + "dataFiles.$": "$.get_project_info_and_project_data_files_step.project_data_files" + }, + "engineParameters": {}, + "tags": { + "projectId.$": "$.get_library_info_from_db_step.Item.project_id.S", + "libraryId.$": "$.get_library_info_from_db_step.Item.library_id.S", + "instrumentRunId.$": "$.get_library_info_from_db_step.Item.instrument_run_id.S", + "isIdentified.$": "$.get_project_info_and_project_data_files_step.case_metadata.isIdentified", + "metadataFromRedCap.$": "$.get_project_info_and_project_data_files_step.in_redcap", + "sampleType.$": "$.get_project_info_and_project_data_files_step.case_metadata.sampleType", + "panelVersion.$": "$.get_project_info_and_project_data_files_step.project_info.panel" + } + } + } + }, + "DetailType": "${__event_detail_type__}", + "EventBusName": "${__event_bus_name__}", + "Source": "${__event_source__}" + } + ] + }, + "End": true + }, + "Pass": { + "Type": "Pass", + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/index.ts index b196e1dfa..3900faa0f 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/index.ts @@ -3,11 +3,8 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { UmccriseInitialiseSubjectDbRowConstruct } from './part_1/initialise-umccrise-subject-dbs'; -import { UmccriseInitialiseLibraryAndFastqListRowConstruct } from './part_2/initialise-umccrise-library-dbs'; -import { UmccrisePopulateFastqListRowConstruct } from './part_3/update-fastq-list-rows-dbs'; -import { TnCompleteToUmccriseDraftConstruct } from './part_4/tn-complete-to-umccrise-draft'; -import { UmccriseInputMakerConstruct } from './part_5/umccrise-draft-to-ready'; +import { UmccriseInitialiseLibraryConstruct } from './part_1/initialise-umccrise-library-dbs'; +import { TnCompleteToUmccriseReadyConstruct } from './part_2/tn-complete-to-umccrise-draft'; /* Provide the glue to get from the bssh fastq copy manager to submitting wgts qc analyses @@ -18,7 +15,6 @@ export interface umccriseGlueHandlerConstructProps { eventBusObj: events.IEventBus; /* Tables */ umccriseGlueTableObj: dynamodb.ITableV2; - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameters */ analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; @@ -31,57 +27,18 @@ export interface umccriseGlueHandlerConstructProps { export class UmccriseGlueHandlerConstruct extends Construct { constructor(scope: Construct, id: string, props: umccriseGlueHandlerConstructProps) { super(scope, id); - /* Part 1 - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `SamplesheetMetadataUnion` - Input Event status: `SubjectInSamplesheet` - - * Initialise umccrise instrument db construct - */ - const umccrise_initialise_subject = new UmccriseInitialiseSubjectDbRowConstruct( - this, - 'umccrise_initialise_subject', - { - eventBusObj: props.eventBusObj, - tableObj: props.umccriseGlueTableObj, - } - ); - - /* - Part 2 - Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `SamplesheetMetadataUnion` Input Event status: `LibraryInSamplesheet` * Initialise umccrise instrument db construct */ - const umccrise_initialise_library_and_fastq_list_row = - new UmccriseInitialiseLibraryAndFastqListRowConstruct( - this, - 'umccrise_initialise_library_and_fastq_list_row', - { - eventBusObj: props.eventBusObj, - tableObj: props.umccriseGlueTableObj, - } - ); - - /* - Part 3 - - Input Event Source: `orcabus.instrumentrunmanager` - Input Event DetailType: `FastqListRowStateChange` - Input Event status: `newFastqListRow` - - * Populate the fastq list row attributes for the rgid for this workflow - */ - - const umccrise_populate_fastq_list_row = new UmccrisePopulateFastqListRowConstruct( + const UmccriseInitialiseLibrary = new UmccriseInitialiseLibraryConstruct( this, - 'umccrise_populate_fastq_list_row', + 'umccrise_initialise_library', { eventBusObj: props.eventBusObj, tableObj: props.umccriseGlueTableObj, @@ -89,7 +46,7 @@ export class UmccriseGlueHandlerConstruct extends Construct { ); /* - Part 4 + Part 2 Input Event Source: `orcabus.workflowmanager` Input Event DetailType: `WorkflowRunStateChange` @@ -102,47 +59,22 @@ export class UmccriseGlueHandlerConstruct extends Construct { * Populate the fastq list row attributes for the rgid for this workflow */ - const tn_to_umccrise_draft = new TnCompleteToUmccriseDraftConstruct( + const tnCompleteToUmccriseReady = new TnCompleteToUmccriseReadyConstruct( this, - 'tn_to_umccrise_draft', + 'tn_to_umccrise', { + /* Events*/ eventBusObj: props.eventBusObj, + /* Tables */ tableObj: props.umccriseGlueTableObj, - workflowsTableObj: props.inputMakerTableObj, + /* SSM Param objects */ + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, + cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + /* Secrets Manager */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, } ); - - /* - Part 5 - - Input Event source: `orcabus.umccriseinputeventglue` - Input Event DetailType: `WorkflowDraftRunStateChange` - Input Event status: `draft` - - Output Event source: `orcabus.umccriseinputeventglue` - Output Event DetailType: `WorkflowRunStateChange` - Output Event status: `ready` - - * The umccriseInputMaker, subscribes to the umccrise input event glue (itself) and generates a ready event for the umccriseReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference - */ - const umccriseInputMaker = new UmccriseInputMakerConstruct(this, 'fastq_list_row_qc_complete', { - /* Event bus */ - eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, - /* SSM Param objects */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - /* Secrets Manager */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - }); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/index.ts similarity index 82% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/index.ts index c70a0cc2d..756d1ad59 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/index.ts @@ -15,18 +15,16 @@ Input Event status: `LibraryInSamplesheet` * Initialise umccrise instrument db construct */ -export interface UmccriseInitialiseLibraryAndFastqListRowConstructProps { +export interface UmccriseInitialiseLibraryConstructProps { tableObj: dynamodb.ITableV2; eventBusObj: events.IEventBus; } -export class UmccriseInitialiseLibraryAndFastqListRowConstruct extends Construct { +export class UmccriseInitialiseLibraryConstruct extends Construct { public readonly UmccriseInitialiseLibraryAndFastqListRowMap = { prefix: 'pva-make-library-and-fqlr-row', tablePartition: { - subject: 'subject', library: 'library', - fastqListRow: 'fastq_list_row', }, triggerSource: 'orcabus.instrumentrunmanager', triggerStatus: 'LibraryInSamplesheet', @@ -39,16 +37,11 @@ export class UmccriseInitialiseLibraryAndFastqListRowConstruct extends Construct CLINICAL: 'clinical', }, triggerPhenotypeType: { - NORMAL: 'normal', TUMOR: 'tumor', }, }; - constructor( - scope: Construct, - id: string, - props: UmccriseInitialiseLibraryAndFastqListRowConstructProps - ) { + constructor(scope: Construct, id: string, props: UmccriseInitialiseLibraryConstructProps) { super(scope, id); /* @@ -68,12 +61,8 @@ export class UmccriseInitialiseLibraryAndFastqListRowConstruct extends Construct __table_name__: props.tableObj.tableName, /* Table Partitions */ - __subject_partition_name__: - this.UmccriseInitialiseLibraryAndFastqListRowMap.tablePartition.subject, __library_partition_name__: this.UmccriseInitialiseLibraryAndFastqListRowMap.tablePartition.library, - __fastq_list_row_partition_name__: - this.UmccriseInitialiseLibraryAndFastqListRowMap.tablePartition.fastqListRow, }, }); @@ -115,10 +104,6 @@ export class UmccriseInitialiseLibraryAndFastqListRowConstruct extends Construct }, ], phenotype: [ - { - 'equals-ignore-case': - this.UmccriseInitialiseLibraryAndFastqListRowMap.triggerPhenotypeType.NORMAL, - }, { 'equals-ignore-case': this.UmccriseInitialiseLibraryAndFastqListRowMap.triggerPhenotypeType.TUMOR, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json new file mode 100644 index 000000000..fbbae715e --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json @@ -0,0 +1,98 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "Move Inputs", + "States": { + "Move Inputs": { + "Type": "Pass", + "Parameters": { + "payload_data.$": "$.payload.data" + }, + "Next": "Get Library Item" + }, + "Get Library Item": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.payload_data.library.orcabusId", + "id_type": "${__library_partition_name__}" + } + }, + "ResultPath": "$.get_library_item_step", + "Next": "Update Databases" + }, + "Update Databases": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Pass", + "States": { + "Pass": { + "Type": "Pass", + "End": true + } + } + }, + { + "StartAt": "Library in Database", + "States": { + "Library in Database": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_library_item_step.Item", + "IsPresent": false, + "Comment": "Library Not In Database", + "Next": "Initialise Library" + } + ], + "Default": "No Need to Initialise Library" + }, + "Initialise Library": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.payload_data.library.orcabusId", + "id_type": "${__library_partition_name__}", + "library_id": { + "S.$": "$.payload_data.library.libraryId" + }, + "phenotype": { + "S.$": "$.payload_data.library.phenotype" + }, + "workflow": { + "S.$": "$.payload_data.library.workflow" + }, + "type": { + "S.$": "$.payload_data.library.type" + }, + "assay": { + "S.$": "$.payload_data.library.assay" + }, + "subject_id": { + "S.$": "$.payload_data.subject.subjectId" + }, + "subject_orcabus_id": { + "S.$": "$.payload_data.subject.orcabusId" + } + } + }, + "ResultPath": null, + "End": true + }, + "No Need to Initialise Library": { + "Type": "Pass", + "End": true, + "ResultPath": null + } + } + } + ], + "ResultPath": null, + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/index.ts deleted file mode 100644 index dfcc7e994..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 1 - -Input Event Source: `orcabus.instrumentrunmanager` -Input Event DetailType: `SamplesheetMetadataUnion` -Input Event status: `SubjectInSamplesheet` - -* Initialise umccrise subject db construct -*/ - -export interface UmccriseInitialiseSubjectDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class UmccriseInitialiseSubjectDbRowConstruct extends Construct { - public readonly UmccriseInitialiseSubjectDbRowMap = { - prefix: 'pva-make-subject-row', - tablePartition: 'subject', - triggerSource: 'orcabus.instrumentrunmanager', - triggerStatus: 'SubjectInSamplesheet', - triggerDetailType: 'SamplesheetMetadataUnion', - }; - - constructor(scope: Construct, id: string, props: UmccriseInitialiseSubjectDbRowConstructProps) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'initialise_subject_db_row', { - stateMachineName: `${this.UmccriseInitialiseSubjectDbRowMap.prefix}-initialise-subject`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'initialise_umccrise_subject_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __subject_partition_name__: this.UmccriseInitialiseSubjectDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus and trigger the internal sfn - */ - const rule = new events.Rule(this, 'umccrise_subscribe_to_samplesheet_shower_subject', { - ruleName: `stacky-${this.UmccriseInitialiseSubjectDbRowMap.prefix}-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.UmccriseInitialiseSubjectDbRowMap.triggerSource], - detailType: [this.UmccriseInitialiseSubjectDbRowMap.triggerDetailType], - detail: { - status: [{ 'equals-ignore-case': this.UmccriseInitialiseSubjectDbRowMap.triggerStatus }], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/step_functions_templates/initialise_umccrise_subject_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/step_functions_templates/initialise_umccrise_subject_db_sfn_template.asl.json deleted file mode 100644 index e0c23b290..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_1/initialise-umccrise-subject-dbs/step_functions_templates/initialise_umccrise_subject_db_sfn_template.asl.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Subject Item" - }, - "Get Subject Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.subject.subjectId", - "id_type": "${__subject_partition_name__}" - } - }, - "ResultPath": "$.get_subject_item_step", - "Next": "Subject in Database" - }, - "Subject in Database": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_subject_item_step.Item", - "IsPresent": false, - "Comment": "Subject Not In Database", - "Next": "Initialise Subject" - } - ], - "Default": "Pass" - }, - "Pass": { - "Type": "Pass", - "End": true - }, - "Initialise Subject": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id.$": "$.payload_data.subject.subjectId", - "id_type": "${__subject_partition_name__}", - "orcabus_id": { - "S.$": "$.payload_data.subject.orcabusId" - } - } - }, - "End": true - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json deleted file mode 100644 index 924a9d807..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/initialise-umccrise-library-dbs/step_functions_templates/initialise_umccrise_library_db_sfn_template.asl.json +++ /dev/null @@ -1,190 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Library Item" - }, - "Get Library Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.library.libraryId", - "id_type": "${__library_partition_name__}" - } - }, - "ResultPath": "$.get_library_item_step", - "Next": "Update Databases" - }, - "Update Databases": { - "Type": "Parallel", - "Branches": [ - { - "StartAt": "Add Library to Subject", - "States": { - "Add Library to Subject": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.library.subject.subjectId", - "id_type": "${__subject_partition_name__}" - }, - "UpdateExpression": "ADD library_set :library_set", - "ExpressionAttributeValues": { - ":library_set": { - "SS.$": "States.Array($.payload_data.library.libraryId)" - } - } - }, - "ResultPath": null, - "End": true - } - } - }, - { - "StartAt": "Library in Database", - "States": { - "Library in Database": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_library_item_step.Item", - "IsPresent": false, - "Comment": "Library Not In Database", - "Next": "Initialise Library" - } - ], - "Default": "No Need to Initialise Library" - }, - "Initialise Library": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id.$": "$.payload_data.library.libraryId", - "id_type": "${__library_partition_name__}", - "orcabus_id": { - "S.$": "$.payload_data.library.orcabusId" - }, - "phenotype": { - "S.$": "$.payload_data.library.phenotype" - }, - "workflow": { - "S.$": "$.payload_data.library.workflow" - }, - "type": { - "S.$": "$.payload_data.library.type" - }, - "assay": { - "S.$": "$.payload_data.library.assay" - }, - "subject_id": { - "S.$": "$.payload_data.library.subject.subjectId" - } - } - }, - "ResultPath": null, - "End": true - }, - "No Need to Initialise Library": { - "Type": "Pass", - "End": true, - "ResultPath": null - } - } - } - ], - "Next": "Initialise Fastq List Rows", - "ResultPath": null - }, - "Initialise Fastq List Rows": { - "Type": "Map", - "ItemsPath": "$.payload_data.bclconvertDataRows", - "ItemSelector": { - "bclconvert_data_row.$": "$$.Map.Item.Value", - "index.$": "$$.Map.Item.Index", - "fastq_list_row_objs.$": "$.payload_data.fastqListRows", - "instrument_run_id.$": "$.payload_data.instrumentRunId", - "library_id.$": "$.payload_data.library.libraryId" - }, - "ItemProcessor": { - "ProcessorConfig": { - "Mode": "INLINE" - }, - "StartAt": "Get Fastq List Row Id", - "States": { - "Get Fastq List Row Id": { - "Type": "Pass", - "Next": "Initialise Fastq List Row and Update Library", - "Parameters": { - "fastq_list_row_id.$": "States.ArrayGetItem($.fastq_list_row_objs[*].fastqListRowRgid, $.index)" - }, - "ResultPath": "$.get_fastq_list_row_id_step" - }, - "Initialise Fastq List Row and Update Library": { - "Type": "Parallel", - "Branches": [ - { - "StartAt": "Initialise Fastq List Row", - "States": { - "Initialise Fastq List Row": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id.$": "$.get_fastq_list_row_id_step.fastq_list_row_id", - "id_type": "${__fastq_list_row_partition_name__}", - "library_id": { - "S.$": "$.library_id" - } - } - }, - "ResultPath": null, - "End": true - } - } - }, - { - "StartAt": "Append Fastq List Row to Library", - "States": { - "Append Fastq List Row to Library": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.library_id", - "id_type": "${__library_partition_name__}" - }, - "UpdateExpression": "ADD fastq_list_row_id_set :fastq_list_row_id_set", - "ExpressionAttributeValues": { - ":fastq_list_row_id_set": { - "SS.$": "States.Array($.get_fastq_list_row_id_step.fastq_list_row_id)" - } - } - }, - "End": true, - "ResultPath": null - } - } - } - ], - "End": true - } - } - }, - "End": true, - "ResultPath": null - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/index.ts similarity index 58% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/index.ts rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/index.ts index 3916e8010..abe258157 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/index.ts @@ -8,45 +8,53 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; /* -Part 4 +Part 2 Input Event Source: `orcabus.workflowmanager` Input Event DetailType: `WorkflowRunStateChange` -Input Event WorkflowName: tumor_normal -Input Event status: `succeeded` +Input Event WorkflowName: tumor-normal +Input Event status: `SUCCEEDED` Output Event Source: `orcabus.umccriseinputeventglue` Output Event DetailType: `WorkflowDraftRunStateChange` -Output Event status: `draft` +Output Event status: `READY` * Subscribe to the workflow manager succeeded event for tumor normal libraries. * Launch a draft event for the umccrise pipeline */ export interface TnCompleteToUmccriseDraftConstructProps { + /* Events */ eventBusObj: events.IEventBus; + /* Tables */ tableObj: dynamodb.ITableV2; - workflowsTableObj: dynamodb.ITableV2; + /* SSM Parameters */ + outputUriSsmParameterObj: ssm.IStringParameter; + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + cacheUriSsmParameterObj: ssm.IStringParameter; + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class TnCompleteToUmccriseDraftConstruct extends Construct { - public readonly UmccriseDraftMap = { - prefix: 'pva-tn-complete-to-umccrise-draft', - portalRunPartitionName: 'portal_run', +export class TnCompleteToUmccriseReadyConstruct extends Construct { + public readonly UmccriseReadyMap = { + prefix: 'pva-tn-complete-to-umccrise', triggerSource: 'orcabus.workflowmanager', triggerStatus: 'succeeded', - triggerWorkflowName: 'tumor_normal', + triggerWorkflowName: 'tumor-normal', triggerDetailType: 'WorkflowRunStateChange', outputSource: 'orcabus.umccriseinputeventglue', - outputDetailType: 'WorkflowDraftRunStateChange', - outputStatus: 'DRAFT', payloadVersion: '2024.07.23', workflowName: 'umccrise', workflowVersion: '2.3.1', - tablePartitionName: 'subject', + tablePartitionName: 'library', }; constructor(scope: Construct, id: string, props: TnCompleteToUmccriseDraftConstructProps) { @@ -68,15 +76,42 @@ export class TnCompleteToUmccriseDraftConstruct extends Construct { /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, - `${this.UmccriseDraftMap.prefix}_sfn_preamble`, + `${this.UmccriseReadyMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: this.UmccriseDraftMap.portalRunPartitionName, - stateMachinePrefix: this.UmccriseDraftMap.prefix, - tableObj: props.workflowsTableObj, - workflowName: this.UmccriseDraftMap.workflowName, - workflowVersion: this.UmccriseDraftMap.workflowVersion, + stateMachinePrefix: this.UmccriseReadyMap.prefix, + workflowName: this.UmccriseReadyMap.workflowName, + workflowVersion: this.UmccriseReadyMap.workflowVersion, + } + ).stepFunctionObj; + + /* + Part 2: Build the engine parameters sfn + */ + const engineParameterAndReadyEventMakerSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'fastqlistrow_complete_to_wgtsqc_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.UmccriseReadyMap.outputSource, + payloadVersion: this.UmccriseReadyMap.payloadVersion, + workflowName: this.UmccriseReadyMap.workflowName, + workflowVersion: this.UmccriseReadyMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.UmccriseReadyMap.prefix, + stateMachinePrefix: this.UmccriseReadyMap.prefix, } ).stepFunctionObj; @@ -84,7 +119,7 @@ export class TnCompleteToUmccriseDraftConstruct extends Construct { Part 2: Build the sfn */ const qcCompleteToDraftSfn = new sfn.StateMachine(this, 'tn_complete_to_umccrise_draft_sfn', { - stateMachineName: `${this.UmccriseDraftMap.prefix}-sfn`, + stateMachineName: `${this.UmccriseReadyMap.prefix}-sfn`, definitionBody: sfn.DefinitionBody.fromFile( path.join( __dirname, @@ -93,15 +128,6 @@ export class TnCompleteToUmccriseDraftConstruct extends Construct { ) ), definitionSubstitutions: { - /* Events */ - __event_bus_name__: props.eventBusObj.eventBusName, - __event_source__: this.UmccriseDraftMap.outputSource, - __detail_type__: this.UmccriseDraftMap.outputDetailType, - __output_status__: this.UmccriseDraftMap.outputStatus, - __payload_version__: this.UmccriseDraftMap.payloadVersion, - __workflow_name__: this.UmccriseDraftMap.workflowName, - __workflow_version__: this.UmccriseDraftMap.workflowVersion, - /* Lambdas */ __generate_draft_event_payload_lambda_function_arn__: generateEventDataLambdaObj.currentVersion.functionArn, @@ -110,10 +136,11 @@ export class TnCompleteToUmccriseDraftConstruct extends Construct { __table_name__: props.tableObj.tableName, /* Table Partitions */ - __subject_table_partition_name__: this.UmccriseDraftMap.tablePartitionName, + __library_partition_name__: this.UmccriseReadyMap.tablePartitionName, // State Machines - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParameterAndReadyEventMakerSfn.stateMachineArn, }, }); @@ -123,9 +150,6 @@ export class TnCompleteToUmccriseDraftConstruct extends Construct { // access the dynamodb table props.tableObj.grantReadWriteData(qcCompleteToDraftSfn); - // allow the step function to submit events - props.eventBusObj.grantPutEventsTo(qcCompleteToDraftSfn); - // allow the step function to invoke the lambdas generateEventDataLambdaObj.currentVersion.grantInvoke(qcCompleteToDraftSfn); @@ -141,22 +165,23 @@ export class TnCompleteToUmccriseDraftConstruct extends Construct { }) ); // Allow the state machine to be able to invoke the preamble sfn - sfn_preamble.grantStartExecution(qcCompleteToDraftSfn); + sfnPreamble.grantStartExecution(qcCompleteToDraftSfn); + engineParameterAndReadyEventMakerSfn.grantStartExecution(qcCompleteToDraftSfn); /* Part 3: Subscribe to the event bus and trigger the internal sfn */ const rule = new events.Rule(this, 'tn_complete_to_umccrise_draft_rule', { - ruleName: `stacky-${this.UmccriseDraftMap.prefix}-rule`, + ruleName: `stacky-${this.UmccriseReadyMap.prefix}-rule`, eventBus: props.eventBusObj, eventPattern: { - source: [this.UmccriseDraftMap.triggerSource], - detailType: [this.UmccriseDraftMap.triggerDetailType], + source: [this.UmccriseReadyMap.triggerSource], + detailType: [this.UmccriseReadyMap.triggerDetailType], detail: { - status: [{ 'equals-ignore-case': this.UmccriseDraftMap.triggerStatus }], + status: [{ 'equals-ignore-case': this.UmccriseReadyMap.triggerStatus }], workflowName: [ { - 'equals-ignore-case': this.UmccriseDraftMap.triggerWorkflowName, + 'equals-ignore-case': this.UmccriseReadyMap.triggerWorkflowName, }, ], }, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py similarity index 93% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py index 043bd1660..3c764dd53 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/lambdas/generate_draft_event_payload_py/generate_draft_event_payload.py @@ -44,6 +44,7 @@ def handler(event, context) -> Dict: """ subject_id = event['subject_id'] + individual_id = event['individual_id'] tumor_library_id = event['tumor_library_id'] normal_library_id = event['normal_library_id'] dragen_somatic_output_s3_uri = event['dragen_somatic_output_s3_uri'] @@ -53,7 +54,7 @@ def handler(event, context) -> Dict: return { "input_event_data": { - "subjectId": subject_id, + "subjectId": individual_id, "dragenSomaticLibraryId": tumor_library_id, "dragenGermlineLibraryId": normal_library_id, "dragenSomaticOutputUri": dragen_somatic_output_s3_uri, @@ -61,6 +62,7 @@ def handler(event, context) -> Dict: }, "event_tags": { "subjectId": subject_id, + "individualId": individual_id, "tumorLibraryId": tumor_library_id, "normalLibraryId": normal_library_id, "tumorFastqListRowIds": tumor_fastq_list_row_ids, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json similarity index 66% rename from lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json rename to lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json index 756332981..19b4d8556 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_4/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_2/tn-complete-to-umccrise-draft/step_functions_templates/tn_complete_to_umccrise_draft_sfn_template.asl.json @@ -8,26 +8,34 @@ "linked_libraries.$": "$.linkedLibraries", "payload_data.$": "$.payload.data" }, - "Next": "Get Subject Item" + "Next": "Get Tumor Library Orcabus Id" }, - "Get Subject Item": { + "Get Tumor Library Orcabus Id": { + "Type": "Pass", + "Next": "Get Library Item", + "Parameters": { + "tumor_orcabus_id.$": "States.ArrayGetItem($.linked_libraries[?(@.libraryId==$.payload_data.tags.tumorLibraryId)].orcabusId, 0)" + }, + "ResultPath": "$.get_tumor_orcabus_id_step" + }, + "Get Library Item": { "Type": "Task", "Resource": "arn:aws:states:::dynamodb:getItem", "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.payload_data.tags.subjectId", - "id_type": "${__subject_table_partition_name__}" + "id.$": "$.get_tumor_orcabus_id_step.tumor_orcabus_id", + "id_type": "${__library_partition_name__}" } }, - "ResultPath": "$.get_subject_item_step", - "Next": "Subject Item In DataBase" + "ResultPath": "$.get_library_item_step", + "Next": "Library Item In DataBase" }, - "Subject Item In DataBase": { + "Library Item In DataBase": { "Type": "Choice", "Choices": [ { - "Variable": "$.get_subject_item_step.Item", + "Variable": "$.get_library_item_step.Item", "IsPresent": true, "Comment": "Subject Item In DataBase", "Next": "Get Portal Run Id and Workflow Run Name" @@ -56,6 +64,7 @@ "FunctionName": "${__generate_draft_event_payload_lambda_function_arn__}", "Payload": { "subject_id.$": "$.payload_data.tags.subjectId", + "individual_id.$": "$.payload_data.tags.individualId", "tumor_library_id.$": "$.payload_data.tags.tumorLibraryId", "normal_library_id.$": "$.payload_data.tags.normalLibraryId", "tumor_fastq_list_row_ids.$": "$.payload_data.tags.tumorFastqListRowIds", @@ -82,35 +91,22 @@ "event_tags.$": "$.Payload.event_tags" }, "ResultPath": "$.generate_draft_event_payload_data_step", - "Next": "Push UMCCRise Draft Event" + "Next": "Push UMCCRise Ready Event" }, - "Push UMCCRise Draft Event": { + "Push UMCCRise Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "Detail": { - "portalRunId.$": "$.get_portal_and_run_name_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "${__output_status__}", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_portal_and_run_name_step.workflow_run_name", - "linkedLibraries.$": "$.linked_libraries", - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs.$": "$.generate_draft_event_payload_data_step.input_event_data", - "tags.$": "$.generate_draft_event_payload_data_step.event_tags" - } - } - }, - "DetailType": "${__detail_type__}", - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}" + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_portal_and_run_name_step.portal_run_id", + "workflow_run_name.$": "$.get_portal_and_run_name_step.workflow_run_name", + "linked_libraries.$": "$.linked_libraries", + "data_inputs.$": "$.generate_draft_event_payload_data_step.input_event_data", + "data_tags.$": "$.generate_draft_event_payload_data_step.event_tags" } - ] + } }, "ResultPath": null, "End": true diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/index.ts deleted file mode 100644 index 890bbbf92..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/index.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 3 - -Input Event Source: `orcabus.instrumentrunmanager` -Input Event DetailType: `FastqListRowStateChange` -Input Event status: `newFastqListRow` - -* Populate the fastq list row attributes for the rgid for this workflow -*/ - -export interface UmccrisePopulateFastqListRowDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class UmccrisePopulateFastqListRowConstruct extends Construct { - public readonly UmccrisePopulateFastqListRowDbRowMap = { - prefix: 'pva-populate-fqlr-row', - tablePartition: 'fastq_list_row', - triggerSource: 'orcabus.instrumentrunmanager', - triggerStatus: 'newFastqListRow', - triggerDetailType: 'FastqListRowStateChange', - }; - - constructor( - scope: Construct, - id: string, - props: UmccrisePopulateFastqListRowDbRowConstructProps - ) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'update_fastq-list-row_db_row', { - stateMachineName: `${this.UmccrisePopulateFastqListRowDbRowMap.prefix}-sfn`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'add_fastq_list_rows_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __fastq_list_row_partition_name__: this.UmccrisePopulateFastqListRowDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus for this event type - */ - const rule = new events.Rule(this, 'umccrise_populate_fastq_list_row', { - ruleName: `stacky-${this.UmccrisePopulateFastqListRowDbRowMap.prefix}-event-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.UmccrisePopulateFastqListRowDbRowMap.triggerSource], - detailType: [this.UmccrisePopulateFastqListRowDbRowMap.triggerDetailType], - detail: { - status: [ - { 'equals-ignore-case': this.UmccrisePopulateFastqListRowDbRowMap.triggerStatus }, - ], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json deleted file mode 100644 index e5cdfae90..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_3/update-fastq-list-rows-dbs/step_functions_templates/add_fastq_list_rows_db_sfn_template.asl.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Fastq List Row Item" - }, - "Get Fastq List Row Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.fastqListRow.rgid", - "id_type": "${__fastq_list_row_partition_name__}" - } - }, - "ResultPath": "$.get_fastq_list_row_item_step", - "Next": "Is Fastq List Row In Db" - }, - "Is Fastq List Row In Db": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_fastq_list_row_item_step.Item", - "IsPresent": true, - "Comment": "Fastq List Row In Database", - "Next": "Populate Fastq List Row" - } - ], - "Default": "Pass" - }, - "Populate Fastq List Row": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:updateItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.id", - "id_type": "${__fastq_list_row_partition_name__}" - }, - "UpdateExpression": "SET fastq_list_row_json = :fastq_list_row_json", - "ExpressionAttributeValues": { - ":fastq_list_row_json": { - "S.$": "States.JsonToString($.payload_data.fastqListRow)" - } - } - }, - "ResultPath": null, - "End": true - }, - "Pass": { - "Type": "Pass", - "End": true - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_5/umccrise-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_5/umccrise-draft-to-ready/index.ts deleted file mode 100644 index 41aa3cc1d..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/pva/part_5/umccrise-draft-to-ready/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import * as events from 'aws-cdk-lib/aws-events'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 5 - -Input Event source: `orcabus.umccriseinputeventglue` -Input Event DetailType: `WorkflowDraftRunStateChange` -Input Event status: `draft` - -Output Event source: `orcabus.umccriseinputeventglue` -Output Event DetailType: `WorkflowRunStateChange` -Output Event status: `ready` - -* The umccriseInputMaker, subscribes to the umccrise input event glue (itself) and generates a ready event for the umccriseReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference -*/ - -export interface UmccriseInputMakerConstructProps { - /* Event bus object */ - eventBusObj: events.IEventBus; - /* Tables */ - inputMakerTableObj: dynamodb.ITableV2; - /* SSM Parameter Objects */ - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - cacheUriSsmParameterObj: ssm.IStringParameter; - /* Secrets */ - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class UmccriseInputMakerConstruct extends Construct { - public readonly umccriseInputMakerEventMap = { - prefix: 'pva-umccrise', - tablePartition: 'umccrise', - triggerSource: 'orcabus.umccriseinputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.umccriseinputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.07.16', - workflowName: 'umccrise', - workflowVersion: '4.2.4', - }; - - constructor(scope: Construct, id: string, props: UmccriseInputMakerConstructProps) { - super(scope, id); - - /* - Part 3: Build the external sfn - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'umccrise_internal_input_maker', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.umccriseInputMakerEventMap.prefix, - payloadVersion: this.umccriseInputMakerEventMap.payloadVersion, - stateMachinePrefix: this.umccriseInputMakerEventMap.prefix, - rulePrefix: this.umccriseInputMakerEventMap.prefix, - - /* - Table objects - */ - tableObj: props.inputMakerTableObj, - tablePartitionName: this.umccriseInputMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerDetailType: this.umccriseInputMakerEventMap.triggerDetailType, - triggerSource: this.umccriseInputMakerEventMap.triggerSource, - triggerStatus: this.umccriseInputMakerEventMap.triggerStatus, - outputSource: this.umccriseInputMakerEventMap.outputSource, - workflowName: this.umccriseInputMakerEventMap.workflowName, - workflowVersion: this.umccriseInputMakerEventMap.workflowVersion, - - /* - SSM Parameter Objects - */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - - /* - Secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/index.ts index 228a07e94..fef1872b8 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/index.ts @@ -3,9 +3,9 @@ import * as events from 'aws-cdk-lib/aws-events'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import { RnasumInitialiseSubjectDbRowConstruct } from './part_1/initialise-rnasum-subject-dbs'; -import { UmccriseAndWtsCompleteToRnasumDraftDraftConstruct } from './part_2/umccrise-and-wts-complete-to-rnasum-draft'; -import { RnasumInputMakerConstruct } from './part_3/rnasum-draft-to-ready'; +import { UmccriseAndWtsCompleteToRnasumReadyConstruct } from './part_2/umccrise-and-wts-complete-to-rnasum-draft'; +import { TnInitialiseLibraryAndFastqListRowConstruct } from '../loctite/part_1/initialise-tn-library-dbs'; +import { RnasumInitialiseLibraryConstruct } from './part_1/initialise-rnasum-library-dbs'; /* Provide the glue to get from the bssh fastq copy manager to submitting wgts qc analyses @@ -16,7 +16,6 @@ export interface umccriseGlueHandlerConstructProps { eventBusObj: events.IEventBus; /* Tables */ rnasumGlueTableObj: dynamodb.ITableV2; - inputMakerTableObj: dynamodb.ITableV2; /* SSM Parameters */ analysisOutputUriSsmParameterObj: ssm.IStringParameter; analysisLogsUriSsmParameterObj: ssm.IStringParameter; @@ -32,16 +31,16 @@ export class RnasumGlueHandlerConstruct extends Construct { /* Part 1 - Input Event Source: `orcabus.instrumentrunmanager` Input Event DetailType: `SamplesheetMetadataUnion` - Input Event status: `SubjectInSamplesheet` + Input Event status: `LibraryInSamplesheet` - * Initialise rnasum instrument db construct + * Initialise rnasum library and fastq list row constructs */ - const rnasum_initialise_subject = new RnasumInitialiseSubjectDbRowConstruct( + + const rnasumInitialiseLibraryAndFastqListRow = new RnasumInitialiseLibraryConstruct( this, - 'rnasum_initialise_subject', + 'rnasum_initialise_library_and_fastq_list_row', { eventBusObj: props.eventBusObj, tableObj: props.rnasumGlueTableObj, @@ -57,52 +56,29 @@ export class RnasumGlueHandlerConstruct extends Construct { Output Event source: `orcabus.umccriseinputeventglue` Output Event DetailType: `WorkflowDraftRunStateChange` - Output Event status: `draft` + Output Event status: `ready` * Populate the fastq list row attributes for the rgid for this workflow */ - - const umccrise_and_wts_to_rnasum_draft = new UmccriseAndWtsCompleteToRnasumDraftDraftConstruct( + const umccriseAndWtsToRnasumDraft = new UmccriseAndWtsCompleteToRnasumReadyConstruct( this, 'umccrise_and_wts_to_rnasum_draft', { + /* Events */ eventBusObj: props.eventBusObj, - tableObj: props.rnasumGlueTableObj, - workflowsTableObj: props.inputMakerTableObj, - } - ); - /* - Part 3 - - Input Event source: `orcabus.rnasuminputeventglue` - Input Event DetailType: `WorkflowDraftRunStateChange` - Input Event status: `draft` + /* Tables */ + tableObj: props.rnasumGlueTableObj, - Output Event source: `orcabus.rnasuminputeventglue` - Output Event DetailType: `WorkflowRunStateChange` - Output Event status: `ready` + /* SSM Parameters */ + outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, + cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, + logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - * The rnasumInputMaker, subscribes to the rnasum input event glue (itself) and generates a ready event for the rnasumReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference - */ - const rnasumInputMaker = new RnasumInputMakerConstruct(this, 'rnasum_input_maker_construct', { - /* Event bus */ - eventBusObj: props.eventBusObj, - /* Tables */ - inputMakerTableObj: props.inputMakerTableObj, - /* SSM Param objects */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.analysisOutputUriSsmParameterObj, - cacheUriSsmParameterObj: props.analysisCacheUriSsmParameterObj, - logsUriSsmParameterObj: props.analysisLogsUriSsmParameterObj, - /* Secrets Manager */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - }); + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + } + ); } } diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/index.ts new file mode 100644 index 000000000..368841c3a --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/index.ts @@ -0,0 +1,127 @@ +import { Construct } from 'constructs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import path from 'path'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; + +/* +Part 2 + +Input Event Source: `orcabus.instrumentrunmanager` +Input Event DetailType: `SamplesheetMetadataUnion` +Input Event status: `LibraryInSamplesheet` + +* Initialise rnasum instrument db construct +*/ + +export interface RnasumInitialiseLibraryConstructProps { + tableObj: dynamodb.ITableV2; + eventBusObj: events.IEventBus; +} + +export class RnasumInitialiseLibraryConstruct extends Construct { + public readonly RnasumInitialiseLibraryAndFastqListRowMap = { + prefix: 'roket-make-library', + tablePartition: { + library: 'library', + }, + triggerSource: 'orcabus.instrumentrunmanager', + triggerStatus: 'LibraryInSamplesheet', + triggerDetailType: 'SamplesheetMetadataUnion', + triggerSampleType: { + WGS: 'WGS', + WTS: 'WTS', + }, + triggerWorkflowType: { + RESEARCH: 'research', + CLINICAL: 'clinical', + }, + triggerPhenotypeType: { + TUMOR: 'tumor', + }, + }; + + constructor(scope: Construct, id: string, props: RnasumInitialiseLibraryConstructProps) { + super(scope, id); + + /* + Part 1: Build the internal sfn + */ + const inputMakerSfn = new sfn.StateMachine(this, 'initialise_rnasum_library_db_row', { + stateMachineName: `${this.RnasumInitialiseLibraryAndFastqListRowMap.prefix}-initialise-rnasum-library-db`, + definitionBody: sfn.DefinitionBody.fromFile( + path.join(__dirname, 'step_functions_templates', 'initialise_library_db_template.asl.json') + ), + definitionSubstitutions: { + /* General */ + __table_name__: props.tableObj.tableName, + + /* Table Partitions */ + __library_partition_name__: + this.RnasumInitialiseLibraryAndFastqListRowMap.tablePartition.library, + }, + }); + + /* + Part 2: Grant the sfn permissions + */ + // access the dynamodb table + props.tableObj.grantReadWriteData(inputMakerSfn.role); + + /* + Part 3: Subscribe to the library events from the event bus where the library assay type + is WGS and the workflow is RESEARCH or CLINICAL + and where the phenotype is NORMAL or TUMOR + */ + const rule = new events.Rule(this, 'initialise_library_assay', { + ruleName: `stacky-${this.RnasumInitialiseLibraryAndFastqListRowMap.prefix}-rule`, + eventBus: props.eventBusObj, + eventPattern: { + source: [this.RnasumInitialiseLibraryAndFastqListRowMap.triggerSource], + detailType: [this.RnasumInitialiseLibraryAndFastqListRowMap.triggerDetailType], + detail: { + payload: { + data: { + library: { + type: [ + { + 'equals-ignore-case': + this.RnasumInitialiseLibraryAndFastqListRowMap.triggerSampleType.WGS, + }, + { + 'equals-ignore-case': + this.RnasumInitialiseLibraryAndFastqListRowMap.triggerSampleType.WTS, + }, + ], + workflow: [ + { + 'equals-ignore-case': + this.RnasumInitialiseLibraryAndFastqListRowMap.triggerWorkflowType.RESEARCH, + }, + { + 'equals-ignore-case': + this.RnasumInitialiseLibraryAndFastqListRowMap.triggerWorkflowType.CLINICAL, + }, + ], + phenotype: [ + { + 'equals-ignore-case': + this.RnasumInitialiseLibraryAndFastqListRowMap.triggerPhenotypeType.TUMOR, + }, + ], + }, + }, + }, + }, + }, + }); + + // Add target of event to be the state machine + rule.addTarget( + new eventsTargets.SfnStateMachine(inputMakerSfn, { + input: events.RuleTargetInput.fromEventPath('$.detail'), + }) + ); + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/step_functions_templates/initialise_library_db_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/step_functions_templates/initialise_library_db_template.asl.json new file mode 100644 index 000000000..fbbae715e --- /dev/null +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-library-dbs/step_functions_templates/initialise_library_db_template.asl.json @@ -0,0 +1,98 @@ +{ + "Comment": "A description of my state machine", + "StartAt": "Move Inputs", + "States": { + "Move Inputs": { + "Type": "Pass", + "Parameters": { + "payload_data.$": "$.payload.data" + }, + "Next": "Get Library Item" + }, + "Get Library Item": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:getItem", + "Parameters": { + "TableName": "${__table_name__}", + "Key": { + "id.$": "$.payload_data.library.orcabusId", + "id_type": "${__library_partition_name__}" + } + }, + "ResultPath": "$.get_library_item_step", + "Next": "Update Databases" + }, + "Update Databases": { + "Type": "Parallel", + "Branches": [ + { + "StartAt": "Pass", + "States": { + "Pass": { + "Type": "Pass", + "End": true + } + } + }, + { + "StartAt": "Library in Database", + "States": { + "Library in Database": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_library_item_step.Item", + "IsPresent": false, + "Comment": "Library Not In Database", + "Next": "Initialise Library" + } + ], + "Default": "No Need to Initialise Library" + }, + "Initialise Library": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.payload_data.library.orcabusId", + "id_type": "${__library_partition_name__}", + "library_id": { + "S.$": "$.payload_data.library.libraryId" + }, + "phenotype": { + "S.$": "$.payload_data.library.phenotype" + }, + "workflow": { + "S.$": "$.payload_data.library.workflow" + }, + "type": { + "S.$": "$.payload_data.library.type" + }, + "assay": { + "S.$": "$.payload_data.library.assay" + }, + "subject_id": { + "S.$": "$.payload_data.subject.subjectId" + }, + "subject_orcabus_id": { + "S.$": "$.payload_data.subject.orcabusId" + } + } + }, + "ResultPath": null, + "End": true + }, + "No Need to Initialise Library": { + "Type": "Pass", + "End": true, + "ResultPath": null + } + } + } + ], + "ResultPath": null, + "End": true + } + } +} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/index.ts deleted file mode 100644 index 1fc8d11f8..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/index.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import path from 'path'; -import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; -import * as events from 'aws-cdk-lib/aws-events'; -import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; - -/* -Part 1 - -Input Event Source: `orcabus.instrumentrunmanager` -Input Event DetailType: `SamplesheetMetadataUnion` -Input Event status: `SubjectInSamplesheet` - -* Initialise rnasum subject db construct -*/ - -export interface RnasumInitialiseSubjectDbRowConstructProps { - tableObj: dynamodb.ITableV2; - eventBusObj: events.IEventBus; -} - -export class RnasumInitialiseSubjectDbRowConstruct extends Construct { - public readonly RnasumInitialiseSubjectDbRowMap = { - prefix: 'roket-make-subject-row', - tablePartition: 'subject', - triggerSource: 'orcabus.instrumentrunmanager', - triggerStatus: 'SubjectInSamplesheet', - triggerDetailType: 'SamplesheetMetadataUnion', - }; - - constructor(scope: Construct, id: string, props: RnasumInitialiseSubjectDbRowConstructProps) { - super(scope, id); - - /* - Part 1: Build the internal sfn - */ - const inputMakerSfn = new sfn.StateMachine(this, 'initialise_subject_db_row', { - stateMachineName: `${this.RnasumInitialiseSubjectDbRowMap.prefix}-initialise-subject`, - definitionBody: sfn.DefinitionBody.fromFile( - path.join( - __dirname, - 'step_functions_templates', - 'initialise_rnasum_subject_db_sfn_template.asl.json' - ) - ), - definitionSubstitutions: { - __table_name__: props.tableObj.tableName, - __subject_partition_name__: this.RnasumInitialiseSubjectDbRowMap.tablePartition, - }, - }); - - /* - Part 2: Grant the internal sfn permissions - */ - // access the dynamodb table - props.tableObj.grantReadWriteData(inputMakerSfn.role); - - /* - Part 3: Subscribe to the event bus and trigger the internal sfn - */ - const rule = new events.Rule(this, 'rnasum_subscribe_to_samplesheet_shower_subject', { - ruleName: `stacky-${this.RnasumInitialiseSubjectDbRowMap.prefix}-rule`, - eventBus: props.eventBusObj, - eventPattern: { - source: [this.RnasumInitialiseSubjectDbRowMap.triggerSource], - detailType: [this.RnasumInitialiseSubjectDbRowMap.triggerDetailType], - detail: { - status: [{ 'equals-ignore-case': this.RnasumInitialiseSubjectDbRowMap.triggerStatus }], - }, - }, - }); - - // Add target of event to be the state machine - rule.addTarget( - new eventsTargets.SfnStateMachine(inputMakerSfn, { - input: events.RuleTargetInput.fromEventPath('$.detail'), - }) - ); - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/step_functions_templates/initialise_rnasum_subject_db_sfn_template.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/step_functions_templates/initialise_rnasum_subject_db_sfn_template.asl.json deleted file mode 100644 index e0c23b290..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_1/initialise-rnasum-subject-dbs/step_functions_templates/initialise_rnasum_subject_db_sfn_template.asl.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "Comment": "A description of my state machine", - "StartAt": "Move Inputs", - "States": { - "Move Inputs": { - "Type": "Pass", - "Parameters": { - "payload_data.$": "$.payload.data" - }, - "Next": "Get Subject Item" - }, - "Get Subject Item": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:getItem", - "Parameters": { - "TableName": "${__table_name__}", - "Key": { - "id.$": "$.payload_data.subject.subjectId", - "id_type": "${__subject_partition_name__}" - } - }, - "ResultPath": "$.get_subject_item_step", - "Next": "Subject in Database" - }, - "Subject in Database": { - "Type": "Choice", - "Choices": [ - { - "Variable": "$.get_subject_item_step.Item", - "IsPresent": false, - "Comment": "Subject Not In Database", - "Next": "Initialise Subject" - } - ], - "Default": "Pass" - }, - "Pass": { - "Type": "Pass", - "End": true - }, - "Initialise Subject": { - "Type": "Task", - "Resource": "arn:aws:states:::dynamodb:putItem", - "Parameters": { - "TableName": "${__table_name__}", - "Item": { - "id.$": "$.payload_data.subject.subjectId", - "id_type": "${__subject_partition_name__}", - "orcabus_id": { - "S.$": "$.payload_data.subject.orcabusId" - } - } - }, - "End": true - } - } -} diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/index.ts index 915903244..401b2c3d0 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/index.ts +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/index.ts @@ -4,18 +4,22 @@ import * as iam from 'aws-cdk-lib/aws-iam'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import path from 'path'; import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; import * as events from 'aws-cdk-lib/aws-events'; import * as eventsTargets from 'aws-cdk-lib/aws-events-targets'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { WorkflowDraftRunStateChangeCommonPreambleConstruct } from '../../../../../../../components/sfn-workflowdraftrunstatechange-common-preamble'; +import { GetMetadataLambdaConstruct } from '../../../../../../../components/python-lambda-metadata-mapper'; +import { GenerateWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/sfn-generate-workflowrunstatechange-ready-event'; /* Part 4 Input Event Source: `orcabus.workflowmanager` Input Event DetailType: `WorkflowRunStateChange` -Input Event WorkflowName: tumor_normal +Input Event WorkflowName: tumor-normal Input Event status: `succeeded` Output Event Source: `orcabus.inputeventglue` @@ -27,16 +31,23 @@ Output Event status: `draft` pipeline */ -export interface UmccriseAndWtsCompleteToRnasumDraftDraftConstructProps { +export interface UmccriseAndWtsCompleteToRnasumReadyConstructProps { + /* Event bus */ eventBusObj: events.IEventBus; + /* Table */ tableObj: dynamodb.ITableV2; - workflowsTableObj: dynamodb.ITableV2; + /* SSM Parameters */ + outputUriSsmParameterObj: ssm.IStringParameter; + icav2ProjectIdSsmParameterObj: ssm.IStringParameter; + logsUriSsmParameterObj: ssm.IStringParameter; + cacheUriSsmParameterObj: ssm.IStringParameter; + /* Secrets */ + icav2AccessTokenSecretObj: secretsManager.ISecret; } -export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct { +export class UmccriseAndWtsCompleteToRnasumReadyConstruct extends Construct { public readonly RnasumDraftMap = { - prefix: 'roket-umccrise-wts-complete-to-rnasum-draft', - portalRunPartitionName: 'portal_run', + prefix: 'roket-umccrise-or-wts-to-rnasum-draft', triggerSource: 'orcabus.workflowmanager', triggerStatus: 'succeeded', triggerWorkflowName: { @@ -47,11 +58,10 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct wtsWorkflowName: 'wts', triggerDetailType: 'WorkflowRunStateChange', tablePartitions: { + library: 'library', subject: 'subject', }, outputSource: 'orcabus.rnasuminputeventglue', - outputDetailType: 'WorkflowDraftRunStateChange', - outputStatus: 'DRAFT', payloadVersion: '2024.07.23', workflowName: 'rnasum', workflowVersion: '4.2.4', @@ -60,7 +70,7 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct constructor( scope: Construct, id: string, - props: UmccriseAndWtsCompleteToRnasumDraftDraftConstructProps + props: UmccriseAndWtsCompleteToRnasumReadyConstructProps ) { super(scope, id); @@ -77,21 +87,62 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct memorySize: 1024, }); + // Generate the lambda to collect the orcabus id from the subject id + const collectOrcaBusIdLambdaObj = new GetMetadataLambdaConstruct( + this, + 'get_orcabus_id_from_subject_id', + { + functionNamePrefix: this.RnasumDraftMap.prefix, + } + ).lambdaObj; + + // Add CONTEXT, FROM_ID and RETURN_STR environment variables to the lambda + collectOrcaBusIdLambdaObj.addEnvironment('CONTEXT', 'subject'); + collectOrcaBusIdLambdaObj.addEnvironment('FROM_ID', ''); + collectOrcaBusIdLambdaObj.addEnvironment('RETURN_STR', ''); + /* Part 1: Generate the preamble (sfn to generate the portal run id and the workflow run name) */ - const sfn_preamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( + const sfnPreamble = new WorkflowDraftRunStateChangeCommonPreambleConstruct( this, `${this.RnasumDraftMap.prefix}_sfn_preamble`, { - portalRunTablePartitionName: this.RnasumDraftMap.portalRunPartitionName, stateMachinePrefix: this.RnasumDraftMap.prefix, - tableObj: props.workflowsTableObj, workflowName: this.RnasumDraftMap.workflowName, workflowVersion: this.RnasumDraftMap.workflowVersion, } ).stepFunctionObj; + /* + Part 2: Build the engine parameters sfn + */ + const engineParameterAndReadyEventMakerSfn = new GenerateWorkflowRunStateChangeReadyConstruct( + this, + 'fastqlistrow_complete_to_wgtsqc_ready_submitter', + { + /* Event Placeholders */ + eventBusObj: props.eventBusObj, + outputSource: this.RnasumDraftMap.outputSource, + payloadVersion: this.RnasumDraftMap.payloadVersion, + workflowName: this.RnasumDraftMap.workflowName, + workflowVersion: this.RnasumDraftMap.workflowVersion, + + /* SSM Parameters */ + outputUriSsmParameterObj: props.outputUriSsmParameterObj, + icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, + logsUriSsmParameterObj: props.logsUriSsmParameterObj, + cacheUriSsmParameterObj: props.cacheUriSsmParameterObj, + + /* Secrets */ + icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, + + /* Prefixes */ + lambdaPrefix: this.RnasumDraftMap.prefix, + stateMachinePrefix: this.RnasumDraftMap.prefix, + } + ).stepFunctionObj; + /* Part 2: Build the sfn */ @@ -108,23 +159,17 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct ) ), definitionSubstitutions: { - /* Events */ - __event_bus_name__: props.eventBusObj.eventBusName, - __event_source__: this.RnasumDraftMap.outputSource, - __detail_type__: this.RnasumDraftMap.outputDetailType, - __output_status__: this.RnasumDraftMap.outputStatus, - __payload_version__: this.RnasumDraftMap.payloadVersion, - __workflow_name__: this.RnasumDraftMap.workflowName, - __workflow_version__: this.RnasumDraftMap.workflowVersion, - /* Lambdas */ __generate_workflow_inputs_lambda_function_arn__: generateEventDataLambdaObj.currentVersion.functionArn, + __get_orcabus_id_from_subject_id_lambda_function_arn__: + collectOrcaBusIdLambdaObj.currentVersion.functionArn, /* Tables */ __table_name__: props.tableObj.tableName, /* Table partitions */ + __library_table_partition_name__: this.RnasumDraftMap.tablePartitions.library, __subject_table_partition_name__: this.RnasumDraftMap.tablePartitions.subject, /* Statuses */ @@ -134,7 +179,8 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct __wts_workflow_name__: this.RnasumDraftMap.wtsWorkflowName, // State Machines - __sfn_preamble_state_machine_arn__: sfn_preamble.stateMachineArn, + __sfn_preamble_state_machine_arn__: sfnPreamble.stateMachineArn, + __launch_ready_event_sfn_arn__: engineParameterAndReadyEventMakerSfn.stateMachineArn, }, } ); @@ -145,11 +191,10 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct // access the dynamodb table props.tableObj.grantReadWriteData(umccriseAndWtsCompleteToDraftSfn); - // allow the step function to submit events - props.eventBusObj.grantPutEventsTo(umccriseAndWtsCompleteToDraftSfn); - // allow the step function to invoke the lambdas - generateEventDataLambdaObj.currentVersion.grantInvoke(umccriseAndWtsCompleteToDraftSfn); + [generateEventDataLambdaObj, collectOrcaBusIdLambdaObj].forEach((lambda) => { + lambda.grantInvoke(umccriseAndWtsCompleteToDraftSfn); + }); /* Allow step function to call nested state machine */ // Because we run a nested state machine, we need to add the permissions to the state machine role @@ -163,7 +208,8 @@ export class UmccriseAndWtsCompleteToRnasumDraftDraftConstruct extends Construct }) ); // Allow the state machine to be able to invoke the preamble sfn - sfn_preamble.grantStartExecution(umccriseAndWtsCompleteToDraftSfn); + sfnPreamble.grantStartExecution(umccriseAndWtsCompleteToDraftSfn); + engineParameterAndReadyEventMakerSfn.grantStartExecution(umccriseAndWtsCompleteToDraftSfn); /* Part 3: Subscribe to the event bus and trigger the internal sfn diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/lambdas/generate_workflow_inputs_py/generate_workflow_inputs.py b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/lambdas/generate_workflow_inputs_py/generate_workflow_inputs.py index cab540cca..14f5953d4 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/lambdas/generate_workflow_inputs_py/generate_workflow_inputs.py +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/lambdas/generate_workflow_inputs_py/generate_workflow_inputs.py @@ -35,6 +35,7 @@ def handler(event, context): wts_tumor_fastq_list_row_ids = event['wts_tumor_fastq_list_row_ids'] wgs_tumor_fastq_list_row_ids = event['wgs_tumor_fastq_list_row_ids'] wgs_normal_fastq_list_row_ids = event['wgs_normal_fastq_list_row_ids'] + individual_id = event['individual_id'] subject_id = event['subject_id'] # Outputs @@ -43,11 +44,12 @@ def handler(event, context): "dragenTranscriptomeUri": dragen_wts_output_uri, "umccriseUri": umccrise_output_uri, "wtsTumorLibraryId": wts_tumor_library_id, - "subjectId": subject_id + "subjectId": individual_id } event_tags = { "subjectId": subject_id, + "individualId": individual_id, "wtsTumorLibraryId": wts_tumor_library_id, "wgsTumorLibraryId": wgs_tumor_library_id, "wgsNormalLibraryId": wgs_normal_library_id, diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/step_functions_templates/umccrise_and_wts_complete_to_rnasum_draft.asl.json b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/step_functions_templates/umccrise_and_wts_complete_to_rnasum_draft.asl.json index 7dc3c1e47..62743c14b 100644 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/step_functions_templates/umccrise_and_wts_complete_to_rnasum_draft.asl.json +++ b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_2/umccrise-and-wts-complete-to-rnasum-draft/step_functions_templates/umccrise_and_wts_complete_to_rnasum_draft.asl.json @@ -7,19 +7,121 @@ "Parameters": { "workflow_inputs.$": "$" }, - "Next": "Get Subject ID from Tags" + "Next": "Scan Libraries for subject" }, - "Get Subject ID from Tags": { + "Scan Libraries for subject": { + "Type": "Task", + "Resource": "arn:aws:states:::aws-sdk:dynamodb:scan", + "Parameters": { + "TableName": "${__table_name__}", + "ExpressionAttributeValues": { + ":subject_id": { + "S.$": "$.workflow_inputs.payload.data.tags.subjectId" + }, + ":id_type": { + "S": "${__library_table_partition_name__}" + } + }, + "ExpressionAttributeNames": { + "#subject_id": "subject_id", + "#id_type": "id_type" + }, + "FilterExpression": "#subject_id = :subject_id AND #id_type = :id_type" + }, + + "ResultPath": "$.scan_libraries_step", + "Next": "Subject In Library DB" + }, + "Subject In Library DB": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.scan_libraries_step.Items", + "IsPresent": false, + "Next": "Success (1)" + } + ], + "Default": "Get Subject Orcabus Id from Subject Id in tags", + "Comment": "Subject Does not exist in db" + }, + "Success (1)": { + "Type": "Succeed" + }, + "Get Subject Orcabus Id from Subject Id in tags": { + "Type": "Task", + "Resource": "arn:aws:states:::lambda:invoke", + "Parameters": { + "FunctionName": "${__get_orcabus_id_from_subject_id_lambda_function_arn__}", + "Payload": { + "value.$": "$.workflow_inputs.payload.data.tags.subjectId" + } + }, + "Retry": [ + { + "ErrorEquals": [ + "Lambda.ServiceException", + "Lambda.AWSLambdaException", + "Lambda.SdkClientException", + "Lambda.TooManyRequestsException" + ], + "IntervalSeconds": 1, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "ResultSelector": { + "subject_orcabus_id.$": "$.Payload.orcabus_id" + }, + "ResultPath": "$.get_subject_orcabus_id_step", + "Next": "Get Subject Orcabus ID" + }, + "Get Subject Orcabus ID": { "Type": "Task", "Resource": "arn:aws:states:::dynamodb:getItem", "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.workflow_inputs.payload.data.tags.subjectId", + "id.$": "$.get_subject_orcabus_id_step.subject_orcabus_id", "id_type": "${__subject_table_partition_name__}" } }, "ResultPath": "$.get_subject_id_step", + "Next": "Is Subject Item Initialised" + }, + "Is Subject Item Initialised": { + "Type": "Choice", + "Choices": [ + { + "Variable": "$.get_subject_id_step.Item", + "IsPresent": false, + "Next": "Initialise Subject", + "Comment": "Subject Needs Initialising" + } + ], + "Default": "Update DB" + }, + "Initialise Subject": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Parameters": { + "TableName": "${__table_name__}", + "Item": { + "id.$": "$.get_subject_orcabus_id_step.subject_orcabus_id", + "id_type": "${__subject_table_partition_name__}", + "individual_id": { + "S.$": "$.workflow_inputs.payload.data.tags.individualId" + }, + "subject_id": { + "S.$": "$.workflow_inputs.payload.data.tags.subjectId" + } + } + }, + "ResultPath": null, + "Next": "Wait For DB Sync" + }, + "Wait For DB Sync": { + "Type": "Wait", + "Seconds": 1, "Next": "Update DB" }, "Update DB": { @@ -47,7 +149,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.workflow_inputs.payload.data.tags.subjectId", + "id.$": "$.get_subject_orcabus_id_step.subject_orcabus_id", "id_type": "${__subject_table_partition_name__}" }, "UpdateExpression": "SET wts_workflow_status = :wts_workflow_status, wts_tumor_library_id = :wts_tumor_library_id, wts_tumor_fastq_list_row_ids = :wts_tumor_fastq_list_row_ids, arriba_output_uri = :arriba_output_uri, dragen_wts_output_uri = :dragen_wts_output_uri", @@ -78,7 +180,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.workflow_inputs.payload.data.tags.subjectId", + "id.$": "$.get_subject_orcabus_id_step.subject_orcabus_id", "id_type": "${__subject_table_partition_name__}" }, "UpdateExpression": "SET umccrise_workflow_status = :umccrise_workflow_status, umccrise_tumor_library_id = :umccrise_tumor_library_id, umccrise_normal_library_id = :umccrise_normal_library_id, umccrise_output_uri = :umccrise_output_uri, umccrise_tumor_fastq_list_row_ids = :umccrise_tumor_fastq_list_row_ids, umccrise_normal_fastq_list_row_ids = :umccrise_normal_fastq_list_row_ids", @@ -150,7 +252,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.workflow_inputs.payload.data.tags.subjectId", + "id.$": "$.get_subject_orcabus_id_step.subject_orcabus_id", "id_type": "${__subject_table_partition_name__}" }, "UpdateExpression": "ADD linked_libraries_set :linked_libraries_set", @@ -161,6 +263,11 @@ } }, "ResultPath": null, + "Next": "Wait For Linked Library DB Sync" + }, + "Wait For Linked Library DB Sync": { + "Type": "Wait", + "Seconds": 1, "End": true } } @@ -174,7 +281,7 @@ "Parameters": { "TableName": "${__table_name__}", "Key": { - "id.$": "$.workflow_inputs.payload.data.tags.subjectId", + "id.$": "$.get_subject_orcabus_id_step.subject_orcabus_id", "id_type": "${__subject_table_partition_name__}" } }, @@ -210,7 +317,7 @@ }, "Get workflow inputs": { "Type": "Parallel", - "Next": "Generate RNASum Draft Event", + "Next": "Generate RNASum Ready Event", "Branches": [ { "StartAt": "Generate workflow inputs", @@ -230,7 +337,8 @@ "wts_tumor_fastq_list_row_ids.$": "States.StringToJson($.get_subject_item_step.Item.wts_tumor_fastq_list_row_ids.S)", "wgs_tumor_fastq_list_row_ids.$": "States.StringToJson($.get_subject_item_step.Item.umccrise_tumor_fastq_list_row_ids.S)", "wgs_normal_fastq_list_row_ids.$": "States.StringToJson($.get_subject_item_step.Item.umccrise_normal_fastq_list_row_ids.S)", - "subject_id.$": "$.get_subject_item_step.Item.id.S" + "subject_id.$": "$.get_subject_item_step.Item.subject_id.S", + "individual_id.$": "$.get_subject_item_step.Item.individual_id.S" } }, "Retry": [ @@ -313,34 +421,22 @@ }, "ResultPath": "$.get_parameters_step" }, - "Generate RNASum Draft Event": { + "Generate RNASum Ready Event": { "Type": "Task", - "Resource": "arn:aws:states:::events:putEvents", + "Resource": "arn:aws:states:::states:startExecution.sync:2", "Parameters": { - "Entries": [ - { - "EventBusName": "${__event_bus_name__}", - "Source": "${__event_source__}", - "DetailType": "${__detail_type__}", - "Detail": { - "portalRunId.$": "$.get_parameters_step.portal_run_id", - "timestamp.$": "$$.State.EnteredTime", - "status": "${__output_status__}", - "workflowName": "${__workflow_name__}", - "workflowVersion": "${__workflow_version__}", - "workflowRunName.$": "$.get_parameters_step.workflow_run_name", - "linkedLibraries.$": "$.get_parameters_step.linked_libraries", - "payload": { - "version": "${__payload_version__}", - "data": { - "inputs.$": "$.get_parameters_step.input_event_data", - "tags.$": "$.get_parameters_step.event_tags" - } - } - } + "StateMachineArn": "${__launch_ready_event_sfn_arn__}", + "Input": { + "StatePayload": { + "portal_run_id.$": "$.get_parameters_step.portal_run_id", + "workflow_run_name.$": "$.get_parameters_step.workflow_run_name", + "linked_libraries.$": "$.get_parameters_step.linked_libraries", + "data_inputs.$": "$.get_parameters_step.input_event_data", + "data_tags.$": "$.get_parameters_step.event_tags" } - ] + } }, + "ResultPath": null, "End": true }, "Success": { diff --git a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_3/rnasum-draft-to-ready/index.ts b/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_3/rnasum-draft-to-ready/index.ts deleted file mode 100644 index da6073b94..000000000 --- a/lib/workload/stateless/stacks/stacky-mcstackface/glue-constructs/roket/part_3/rnasum-draft-to-ready/index.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Construct } from 'constructs'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as ssm from 'aws-cdk-lib/aws-ssm'; -import * as secretsManager from 'aws-cdk-lib/aws-secretsmanager'; -import * as events from 'aws-cdk-lib/aws-events'; -import { WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct } from '../../../../../../../components/event-workflowdraftrunstatechange-to-workflowrunstatechange-ready'; - -/* -Part 5 - -Input Event source: `orcabus.rnasuminputeventglue` -Input Event DetailType: `WorkflowDraftRunStateChange` -Input Event status: `draft` - -Output Event source: `orcabus.rnasuminputeventglue` -Output Event DetailType: `WorkflowRunStateChange` -Output Event status: `ready` - -* The rnasumInputMaker, subscribes to the rnasum input event glue (itself) and generates a ready event for the rnasumReadySfn - * However, in order to be 'READY' we need to use a few more variables such as - * icaLogsUri, - * analysisOutputUri - * cacheUri - * projectId - * userReference -*/ - -export interface RnasumInputMakerConstructProps { - /* Event bus object */ - eventBusObj: events.IEventBus; - /* Tables */ - inputMakerTableObj: dynamodb.ITableV2; - /* SSM Parameter Objects */ - icav2ProjectIdSsmParameterObj: ssm.IStringParameter; - outputUriSsmParameterObj: ssm.IStringParameter; - logsUriSsmParameterObj: ssm.IStringParameter; - cacheUriSsmParameterObj: ssm.IStringParameter; - /* Secrets */ - icav2AccessTokenSecretObj: secretsManager.ISecret; -} - -export class RnasumInputMakerConstruct extends Construct { - public readonly rnasumInputMakerEventMap = { - prefix: 'roket-rnasum', - tablePartition: 'rnasum', - triggerSource: 'orcabus.rnasuminputeventglue', - triggerStatus: 'DRAFT', - triggerDetailType: 'WorkflowDraftRunStateChange', - outputSource: 'orcabus.rnasuminputeventglue', - outputStatus: 'READY', - payloadVersion: '2024.07.16', - workflowName: 'rnasum', - workflowVersion: '4.2.4', - }; - - constructor(scope: Construct, id: string, props: RnasumInputMakerConstructProps) { - super(scope, id); - - /* - Part 3: Build the external sfn - */ - new WorkflowDraftRunStateChangeToWorkflowRunStateChangeReadyConstruct( - this, - 'rnasum_internal_input_maker', - { - /* - Set Input StateMachine Object - */ - lambdaPrefix: this.rnasumInputMakerEventMap.prefix, - payloadVersion: this.rnasumInputMakerEventMap.payloadVersion, - stateMachinePrefix: this.rnasumInputMakerEventMap.prefix, - rulePrefix: `stacky-${this.rnasumInputMakerEventMap.prefix}`, - - /* - Table objects - */ - tableObj: props.inputMakerTableObj, - tablePartitionName: this.rnasumInputMakerEventMap.tablePartition, - - /* - Event Triggers - */ - eventBusObj: props.eventBusObj, - triggerDetailType: this.rnasumInputMakerEventMap.triggerDetailType, - triggerSource: this.rnasumInputMakerEventMap.triggerSource, - triggerStatus: this.rnasumInputMakerEventMap.triggerStatus, - outputSource: this.rnasumInputMakerEventMap.outputSource, - workflowName: this.rnasumInputMakerEventMap.workflowName, - workflowVersion: this.rnasumInputMakerEventMap.workflowVersion, - - /* - SSM Parameter Objects - */ - icav2ProjectIdSsmParameterObj: props.icav2ProjectIdSsmParameterObj, - outputUriSsmParameterObj: props.outputUriSsmParameterObj, - logsUriSsmParameterObj: props.logsUriSsmParameterObj, - - /* - Secrets - */ - icav2AccessTokenSecretObj: props.icav2AccessTokenSecretObj, - } - ); - } -} diff --git a/lib/workload/stateless/statelessStackCollectionClass.ts b/lib/workload/stateless/statelessStackCollectionClass.ts index e60a40749..fe58667a3 100644 --- a/lib/workload/stateless/statelessStackCollectionClass.ts +++ b/lib/workload/stateless/statelessStackCollectionClass.ts @@ -58,6 +58,10 @@ import { RnasumIcav2PipelineManagerStackProps, } from './stacks/rnasum-pipeline-manager/deploy'; import { FMAnnotator, FMAnnotatorConfigurableProps } from './stacks/fmannotator/deploy/stack'; +import { + PieriandxPipelineManagerStack, + PierianDxPipelineManagerStackProps, +} from './stacks/pieriandx-pipeline-manager/deploy'; export interface StatelessStackCollectionProps { metadataManagerStackProps: MetadataManagerStackProps; @@ -72,6 +76,7 @@ export interface StatelessStackCollectionProps { wtsIcav2PipelineManagerStackProps: WtsIcav2PipelineManagerStackProps; umccriseIcav2PipelineManagerStackProps: UmccriseIcav2PipelineManagerStackProps; rnasumIcav2PipelineManagerStackProps: RnasumIcav2PipelineManagerStackProps; + pieriandxPipelineManagerStackProps: PierianDxPipelineManagerStackProps; eventSchemaStackProps: SchemaStackProps; dataSchemaStackProps: SchemaStackProps; bclConvertManagerStackProps: BclConvertManagerStackProps; @@ -94,6 +99,7 @@ export class StatelessStackCollection { readonly wtsIcav2PipelineManagerStack: Stack; readonly umccriseIcav2PipelineManagerStack: Stack; readonly rnasumIcav2PipelineManagerStack: Stack; + readonly pieriandxPipelineManagerStack: Stack; readonly eventSchemaStack: Stack; readonly dataSchemaStack: Stack; readonly bclConvertManagerStack: Stack; @@ -214,6 +220,15 @@ export class StatelessStackCollection { } ); + this.pieriandxPipelineManagerStack = new PieriandxPipelineManagerStack( + scope, + 'PieriandxPipelineManagerStack', + { + ...this.createTemplateProps(env, 'PieriandxPipelineManagerStack'), + ...statelessConfiguration.pieriandxPipelineManagerStackProps, + } + ); + this.bclConvertManagerStack = new BclConvertManagerStack(scope, 'BclConvertManagerStack', { ...this.createTemplateProps(env, 'BclConvertManagerStack'), ...statelessConfiguration.bclConvertManagerStackProps,