cg 76.0.0__py3-none-any.whl → 83.14.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cg/__init__.py +1 -1
- cg/apps/housekeeper/hk.py +18 -1
- cg/apps/tb/api.py +42 -5
- cg/cli/transfer.py +13 -2
- cg/cli/upload/mutacc.py +16 -3
- cg/cli/upload/scout.py +2 -2
- cg/cli/upload/utils.py +10 -1
- cg/cli/workflow/balsamic/base.py +86 -172
- cg/cli/workflow/balsamic/options.py +3 -48
- cg/cli/workflow/balsamic/umi.py +210 -15
- cg/cli/workflow/microsalt/base.py +4 -2
- cg/cli/workflow/mip_dna/base.py +1 -1
- cg/cli/workflow/nallo/base.py +73 -23
- cg/cli/workflow/nf_analysis.py +5 -207
- cg/cli/workflow/raredisease/base.py +41 -54
- cg/cli/workflow/rnafusion/base.py +38 -8
- cg/cli/workflow/taxprofiler/base.py +31 -18
- cg/cli/workflow/tomte/base.py +83 -10
- cg/constants/constants.py +25 -30
- cg/constants/devices.py +6 -1
- cg/constants/gene_panel.py +3 -1
- cg/constants/housekeeper_tags.py +28 -28
- cg/constants/lims.py +4 -0
- cg/constants/nf_analysis.py +0 -1
- cg/constants/observations.py +21 -5
- cg/constants/orderforms.py +3 -3
- cg/constants/pacbio.py +1 -0
- cg/constants/priority.py +1 -1
- cg/constants/report.py +1 -0
- cg/constants/scout.py +12 -9
- cg/constants/sequencing.py +2 -2
- cg/constants/tb.py +5 -5
- cg/exc.py +27 -5
- cg/meta/compress/compress.py +7 -2
- cg/meta/delivery_report/balsamic.py +3 -1
- cg/meta/delivery_report/delivery_report_api.py +4 -3
- cg/meta/delivery_report/nallo.py +11 -11
- cg/meta/delivery_report/raredisease.py +7 -3
- cg/meta/delivery_report/templates/macros/data_analysis/qc_metrics/balsamic_qc_metrics.html +1 -0
- cg/meta/delivery_report/templates/macros/ticket_system.html +1 -1
- cg/meta/observations/balsamic_observations_api.py +110 -14
- cg/meta/observations/mip_dna_observations_api.py +1 -1
- cg/meta/observations/nallo_observations_api.py +1 -1
- cg/meta/observations/observations_api.py +23 -32
- cg/meta/observations/raredisease_observations_api.py +1 -1
- cg/meta/tar/tar.py +5 -2
- cg/meta/transfer/lims.py +32 -3
- cg/meta/upload/balsamic/balsamic.py +1 -8
- cg/meta/upload/coverage.py +5 -5
- cg/meta/upload/raredisease/raredisease.py +3 -0
- cg/meta/upload/scout/hk_tags.py +1 -0
- cg/meta/upload/scout/nallo_config_builder.py +31 -7
- cg/meta/workflow/balsamic.py +70 -36
- cg/meta/workflow/fastq.py +8 -0
- cg/meta/workflow/microsalt/quality_controller/models.py +0 -2
- cg/meta/workflow/microsalt/quality_controller/quality_controller.py +8 -16
- cg/meta/workflow/microsalt/quality_controller/result_logger.py +3 -6
- cg/meta/workflow/microsalt/quality_controller/utils.py +2 -45
- cg/meta/workflow/nallo.py +21 -99
- cg/meta/workflow/nf_analysis.py +12 -263
- cg/meta/workflow/raredisease.py +3 -112
- cg/meta/workflow/rnafusion.py +2 -34
- cg/meta/workflow/taxprofiler.py +2 -38
- cg/meta/workflow/tomte.py +2 -42
- cg/models/balsamic/config.py +0 -24
- cg/models/balsamic/metrics.py +5 -3
- cg/models/cg_config.py +39 -16
- cg/models/deliverables/metric_deliverables.py +1 -1
- cg/models/delivery_report/metadata.py +2 -1
- cg/models/nallo/nallo.py +14 -64
- cg/models/nf_analysis.py +1 -41
- cg/models/raredisease/raredisease.py +1 -63
- cg/models/rnafusion/rnafusion.py +0 -26
- cg/models/scout/scout_load_config.py +5 -2
- cg/models/taxprofiler/taxprofiler.py +0 -42
- cg/models/tomte/tomte.py +0 -69
- cg/resources/nallo_bundle_filenames.yaml +292 -22
- cg/resources/raredisease_bundle_filenames.yaml +11 -1
- cg/resources/taxprofiler_bundle_filenames.yaml +20 -0
- cg/server/admin.py +106 -25
- cg/server/app.py +15 -4
- cg/server/endpoints/sequencing_run/dtos.py +21 -3
- cg/server/endpoints/sequencing_run/pacbio_sequencing_run.py +29 -10
- cg/server/endpoints/sequencing_run/pacbio_smrt_cell_metrics.py +20 -0
- cg/services/analysis_starter/{service.py → analysis_starter.py} +11 -9
- cg/services/analysis_starter/configurator/abstract_model.py +8 -0
- cg/services/analysis_starter/configurator/configurator.py +1 -1
- cg/services/analysis_starter/configurator/extensions/nallo.py +27 -0
- cg/services/analysis_starter/configurator/extensions/{abstract.py → pipeline_extension.py} +1 -1
- cg/services/analysis_starter/configurator/extensions/raredisease.py +3 -1
- cg/services/analysis_starter/configurator/extensions/tomte_extension.py +28 -0
- cg/services/analysis_starter/configurator/file_creators/balsamic_config.py +240 -0
- cg/services/analysis_starter/configurator/file_creators/gene_panel.py +10 -5
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/abstract.py +2 -1
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/models.py +40 -1
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/nallo.py +37 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/raredisease.py +8 -5
- cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/tomte_params_file_creator.py +64 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/creator.py +1 -1
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/nallo_sample_sheet_creator.py +65 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/protocol.py +12 -0
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{raredisease.py → raredisease_sample_sheet_creator.py} +2 -2
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{rnafusion.py → rnafusion_sample_sheet_creator.py} +2 -2
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{taxprofiler.py → taxprofiler_sample_sheet_creator.py} +2 -2
- cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/tomte_sample_sheet_creator.py +36 -0
- cg/services/analysis_starter/configurator/implementations/balsamic.py +68 -0
- cg/services/analysis_starter/configurator/implementations/nextflow.py +22 -5
- cg/services/analysis_starter/configurator/models/balsamic.py +152 -0
- cg/services/analysis_starter/configurator/models/mip_dna.py +6 -8
- cg/services/analysis_starter/configurator/models/nextflow.py +9 -0
- cg/services/analysis_starter/constants.py +2 -0
- cg/services/analysis_starter/factories/configurator_factory.py +131 -51
- cg/services/analysis_starter/factories/starter_factory.py +36 -7
- cg/services/analysis_starter/input_fetcher/implementations/bam_fetcher.py +57 -0
- cg/services/analysis_starter/input_fetcher/implementations/fastq_fetcher.py +3 -3
- cg/services/analysis_starter/submitters/seqera_platform/{client.py → seqera_platform_client.py} +19 -3
- cg/services/analysis_starter/submitters/seqera_platform/seqera_platform_submitter.py +73 -0
- cg/services/analysis_starter/submitters/submitter.py +1 -1
- cg/services/analysis_starter/submitters/subprocess/submitter.py +2 -1
- cg/services/analysis_starter/tracker/implementations/balsamic.py +22 -0
- cg/services/analysis_starter/tracker/implementations/microsalt.py +4 -4
- cg/services/analysis_starter/tracker/implementations/mip_dna.py +4 -1
- cg/services/analysis_starter/tracker/implementations/{nextflow.py → nextflow_tracker.py} +6 -4
- cg/services/analysis_starter/tracker/tracker.py +19 -15
- cg/services/deliver_files/factory.py +1 -1
- cg/services/delivery_message/messages/__init__.py +24 -14
- cg/services/delivery_message/messages/{microsalt_mwr_message.py → microsalt_message.py} +1 -1
- cg/services/delivery_message/utils.py +4 -40
- cg/services/illumina/backup/backup_service.py +29 -7
- cg/services/orders/validation/constants.py +3 -0
- cg/services/orders/validation/index_sequences.py +558 -0
- cg/services/orders/validation/order_types/microsalt/models/sample.py +2 -3
- cg/services/run_devices/pacbio/data_storage_service/pacbio_store_service.py +39 -18
- cg/services/run_devices/pacbio/data_transfer_service/data_transfer_service.py +8 -2
- cg/services/run_devices/pacbio/data_transfer_service/dto.py +9 -3
- cg/services/run_devices/pacbio/data_transfer_service/utils.py +14 -7
- cg/services/run_devices/pacbio/metrics_parser/models.py +1 -0
- cg/services/run_devices/pacbio/sequencing_runs_service.py +35 -7
- cg/services/sequencing_qc_service/quality_checks/checks.py +18 -16
- cg/services/sequencing_qc_service/quality_checks/utils.py +82 -18
- cg/services/sequencing_qc_service/sequencing_qc_service.py +12 -10
- cg/store/crud/create.py +73 -42
- cg/store/crud/read.py +73 -7
- cg/store/crud/update.py +14 -3
- cg/store/models.py +98 -35
- cg/store/store.py +8 -1
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/METADATA +1 -1
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/RECORD +150 -138
- cg/services/analysis_starter/submitters/seqera_platform/submitter.py +0 -39
- cg/services/delivery_message/messages/microsalt_mwx_message.py +0 -18
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/WHEEL +0 -0
- {cg-76.0.0.dist-info → cg-83.14.0.dist-info}/entry_points.txt +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
from datetime import datetime
|
|
5
5
|
|
|
6
|
+
from cg.exc import PacbioSequencingRunAlreadyExistsError
|
|
6
7
|
from cg.services.run_devices.abstract_classes import PostProcessingStoreService
|
|
7
8
|
from cg.services.run_devices.error_handler import handle_post_processing_errors
|
|
8
9
|
from cg.services.run_devices.exc import (
|
|
@@ -17,9 +18,10 @@ from cg.services.run_devices.pacbio.data_transfer_service.dto import (
|
|
|
17
18
|
PacBioSampleSequencingMetricsDTO,
|
|
18
19
|
PacBioSequencingRunDTO,
|
|
19
20
|
PacBioSMRTCellDTO,
|
|
21
|
+
PacBioSMRTCellMetricsDTO,
|
|
20
22
|
)
|
|
21
23
|
from cg.services.run_devices.pacbio.run_data_generator.run_data import PacBioRunData
|
|
22
|
-
from cg.store.models import PacbioSequencingRun, PacbioSMRTCell
|
|
24
|
+
from cg.store.models import PacbioSequencingRun, PacbioSMRTCell, PacbioSMRTCellMetrics
|
|
23
25
|
from cg.store.store import Store
|
|
24
26
|
|
|
25
27
|
LOG = logging.getLogger(__name__)
|
|
@@ -33,21 +35,35 @@ class PacBioStoreService(PostProcessingStoreService):
|
|
|
33
35
|
def _create_run_device(self, run_device_dto: PacBioSMRTCellDTO) -> PacbioSMRTCell:
|
|
34
36
|
return self.store.create_pac_bio_smrt_cell(run_device_dto)
|
|
35
37
|
|
|
36
|
-
def
|
|
37
|
-
self,
|
|
38
|
+
def _create_pacbio_sequencing_run_if_non_existent(
|
|
39
|
+
self, sequencing_run_dto: PacBioSequencingRunDTO
|
|
38
40
|
) -> PacbioSequencingRun:
|
|
39
|
-
|
|
40
|
-
sequencing_run_dto
|
|
41
|
+
try:
|
|
42
|
+
return self.store.create_pacbio_sequencing_run(sequencing_run_dto)
|
|
43
|
+
except PacbioSequencingRunAlreadyExistsError:
|
|
44
|
+
LOG.debug(f"Sequencing run {sequencing_run_dto.run_name} already exists")
|
|
45
|
+
return self.store.get_pacbio_sequencing_run_by_run_name(sequencing_run_dto.run_name)
|
|
46
|
+
|
|
47
|
+
def _create_pacbio_smrt_cell_metrics(
|
|
48
|
+
self,
|
|
49
|
+
instrument_run_dto: PacBioSMRTCellMetricsDTO,
|
|
50
|
+
sequencing_run: PacbioSequencingRun,
|
|
51
|
+
smrt_cell: PacbioSMRTCell,
|
|
52
|
+
) -> PacbioSMRTCellMetrics:
|
|
53
|
+
return self.store.create_pacbio_smrt_cell_metrics(
|
|
54
|
+
smrt_cell_metrics_dto=instrument_run_dto,
|
|
55
|
+
sequencing_run=sequencing_run,
|
|
56
|
+
smrt_cell=smrt_cell,
|
|
41
57
|
)
|
|
42
58
|
|
|
43
59
|
def _create_sample_run_metrics(
|
|
44
60
|
self,
|
|
45
61
|
sample_run_metrics_dtos: list[PacBioSampleSequencingMetricsDTO],
|
|
46
|
-
|
|
62
|
+
smrt_cell_metrics: PacbioSMRTCellMetrics,
|
|
47
63
|
) -> None:
|
|
48
64
|
for sample_run_metric in sample_run_metrics_dtos:
|
|
49
65
|
self.store.create_pac_bio_sample_sequencing_run(
|
|
50
|
-
sample_run_metrics_dto=sample_run_metric,
|
|
66
|
+
sample_run_metrics_dto=sample_run_metric, smrt_cell_metrics=smrt_cell_metrics
|
|
51
67
|
)
|
|
52
68
|
|
|
53
69
|
def _update_sample(
|
|
@@ -56,13 +72,12 @@ class PacBioStoreService(PostProcessingStoreService):
|
|
|
56
72
|
sequencing_date: datetime,
|
|
57
73
|
) -> None:
|
|
58
74
|
"""Update the reads and last sequenced date for the SMRT cell samples."""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
self.store.
|
|
64
|
-
|
|
65
|
-
)
|
|
75
|
+
sample_ids_to_update: set[str] = {
|
|
76
|
+
sample_dto.sample_internal_id for sample_dto in sample_run_metrics_dtos
|
|
77
|
+
}
|
|
78
|
+
for sample_id in sample_ids_to_update:
|
|
79
|
+
self.store.recalculate_sample_reads_pacbio(sample_id)
|
|
80
|
+
self.store.update_sample_sequenced_at(internal_id=sample_id, date=sequencing_date)
|
|
66
81
|
|
|
67
82
|
@handle_post_processing_errors(
|
|
68
83
|
to_except=(PostProcessingDataTransferError, ValueError),
|
|
@@ -70,16 +85,22 @@ class PacBioStoreService(PostProcessingStoreService):
|
|
|
70
85
|
)
|
|
71
86
|
def store_post_processing_data(self, run_data: PacBioRunData, dry_run: bool = False) -> None:
|
|
72
87
|
dtos: PacBioDTOs = self.data_transfer_service.get_post_processing_dtos(run_data)
|
|
88
|
+
sequencing_run: PacbioSequencingRun = self._create_pacbio_sequencing_run_if_non_existent(
|
|
89
|
+
dtos.sequencing_run
|
|
90
|
+
)
|
|
73
91
|
smrt_cell: PacbioSMRTCell = self._create_run_device(dtos.run_device)
|
|
74
|
-
|
|
75
|
-
instrument_run_dto=dtos.
|
|
92
|
+
smrt_cell_metrics: PacbioSMRTCellMetrics = self._create_pacbio_smrt_cell_metrics(
|
|
93
|
+
instrument_run_dto=dtos.smrt_cell_metrics,
|
|
94
|
+
sequencing_run=sequencing_run,
|
|
95
|
+
smrt_cell=smrt_cell,
|
|
76
96
|
)
|
|
77
97
|
self._create_sample_run_metrics(
|
|
78
|
-
sample_run_metrics_dtos=dtos.sample_sequencing_metrics,
|
|
98
|
+
sample_run_metrics_dtos=dtos.sample_sequencing_metrics,
|
|
99
|
+
smrt_cell_metrics=smrt_cell_metrics,
|
|
79
100
|
)
|
|
80
101
|
self._update_sample(
|
|
81
102
|
sample_run_metrics_dtos=dtos.sample_sequencing_metrics,
|
|
82
|
-
sequencing_date=
|
|
103
|
+
sequencing_date=smrt_cell_metrics.completed_at,
|
|
83
104
|
)
|
|
84
105
|
if dry_run:
|
|
85
106
|
self.store.rollback()
|
|
@@ -15,11 +15,13 @@ from cg.services.run_devices.pacbio.data_transfer_service.dto import (
|
|
|
15
15
|
PacBioSampleSequencingMetricsDTO,
|
|
16
16
|
PacBioSequencingRunDTO,
|
|
17
17
|
PacBioSMRTCellDTO,
|
|
18
|
+
PacBioSMRTCellMetricsDTO,
|
|
18
19
|
)
|
|
19
20
|
from cg.services.run_devices.pacbio.data_transfer_service.utils import (
|
|
20
21
|
get_sample_sequencing_metrics_dtos,
|
|
21
22
|
get_sequencing_run_dto,
|
|
22
23
|
get_smrt_cell_dto,
|
|
24
|
+
get_smrt_cell_metrics_dto,
|
|
23
25
|
)
|
|
24
26
|
from cg.services.run_devices.pacbio.metrics_parser.metrics_parser import PacBioMetricsParser
|
|
25
27
|
from cg.services.run_devices.pacbio.metrics_parser.models import PacBioMetrics
|
|
@@ -38,16 +40,20 @@ class PacBioDataTransferService(PostProcessingDataTransferService):
|
|
|
38
40
|
)
|
|
39
41
|
def get_post_processing_dtos(self, run_data: PacBioRunData) -> PacBioDTOs:
|
|
40
42
|
metrics: PacBioMetrics = self.metrics_service.parse_metrics(run_data)
|
|
41
|
-
smrt_cell_dto: PacBioSMRTCellDTO = get_smrt_cell_dto(metrics)
|
|
42
43
|
sequencing_run_dto: PacBioSequencingRunDTO = get_sequencing_run_dto(
|
|
43
44
|
metrics=metrics, run_data=run_data
|
|
44
45
|
)
|
|
46
|
+
smrt_cell_dto: PacBioSMRTCellDTO = get_smrt_cell_dto(metrics)
|
|
47
|
+
smrt_cell_metrics_dto: PacBioSMRTCellMetricsDTO = get_smrt_cell_metrics_dto(
|
|
48
|
+
metrics=metrics, run_data=run_data
|
|
49
|
+
)
|
|
45
50
|
sample_sequencing_metrics_dtos: list[PacBioSampleSequencingMetricsDTO] = (
|
|
46
51
|
get_sample_sequencing_metrics_dtos(metrics.samples)
|
|
47
52
|
)
|
|
48
53
|
LOG.debug("DTOs created")
|
|
49
54
|
return PacBioDTOs(
|
|
50
55
|
run_device=smrt_cell_dto,
|
|
51
|
-
|
|
56
|
+
smrt_cell_metrics=smrt_cell_metrics_dto,
|
|
52
57
|
sample_sequencing_metrics=sample_sequencing_metrics_dtos,
|
|
58
|
+
sequencing_run=sequencing_run_dto,
|
|
53
59
|
)
|
|
@@ -4,11 +4,11 @@ from datetime import datetime
|
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
7
|
-
from cg.constants.devices import DeviceType
|
|
7
|
+
from cg.constants.devices import DeviceType, RevioNames
|
|
8
8
|
from cg.services.run_devices.abstract_models import PostProcessingDTOs
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class PacBioSMRTCellMetricsDTO(BaseModel):
|
|
12
12
|
type: DeviceType
|
|
13
13
|
well: str
|
|
14
14
|
plate: int
|
|
@@ -62,7 +62,13 @@ class PacBioSampleSequencingMetricsDTO(BaseModel):
|
|
|
62
62
|
polymerase_mean_read_length: int
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
class PacBioSequencingRunDTO(BaseModel):
|
|
66
|
+
instrument_name: RevioNames
|
|
67
|
+
run_name: str
|
|
68
|
+
|
|
69
|
+
|
|
65
70
|
class PacBioDTOs(PostProcessingDTOs):
|
|
66
71
|
run_device: PacBioSMRTCellDTO
|
|
67
|
-
|
|
72
|
+
smrt_cell_metrics: PacBioSMRTCellMetricsDTO
|
|
68
73
|
sample_sequencing_metrics: list[PacBioSampleSequencingMetricsDTO]
|
|
74
|
+
sequencing_run: PacBioSequencingRunDTO
|
|
@@ -3,11 +3,9 @@ from cg.services.run_devices.pacbio.data_transfer_service.dto import (
|
|
|
3
3
|
PacBioSampleSequencingMetricsDTO,
|
|
4
4
|
PacBioSequencingRunDTO,
|
|
5
5
|
PacBioSMRTCellDTO,
|
|
6
|
+
PacBioSMRTCellMetricsDTO,
|
|
6
7
|
)
|
|
7
|
-
from cg.services.run_devices.pacbio.metrics_parser.models import
|
|
8
|
-
PacBioMetrics,
|
|
9
|
-
SampleMetrics,
|
|
10
|
-
)
|
|
8
|
+
from cg.services.run_devices.pacbio.metrics_parser.models import PacBioMetrics, SampleMetrics
|
|
11
9
|
from cg.services.run_devices.pacbio.run_data_generator.run_data import PacBioRunData
|
|
12
10
|
|
|
13
11
|
|
|
@@ -16,11 +14,11 @@ def get_smrt_cell_dto(metrics: PacBioMetrics) -> PacBioSMRTCellDTO:
|
|
|
16
14
|
return PacBioSMRTCellDTO(type=DeviceType.PACBIO, internal_id=internal_id)
|
|
17
15
|
|
|
18
16
|
|
|
19
|
-
def
|
|
17
|
+
def get_smrt_cell_metrics_dto(
|
|
20
18
|
metrics: PacBioMetrics, run_data: PacBioRunData
|
|
21
|
-
) ->
|
|
19
|
+
) -> PacBioSMRTCellMetricsDTO:
|
|
22
20
|
hifi_mean_read_quality: str = f"Q{metrics.read.hifi_median_read_quality}"
|
|
23
|
-
return
|
|
21
|
+
return PacBioSMRTCellMetricsDTO(
|
|
24
22
|
type=DeviceType.PACBIO,
|
|
25
23
|
well=metrics.dataset_metrics.well,
|
|
26
24
|
plate=metrics.dataset_metrics.plate,
|
|
@@ -76,3 +74,12 @@ def get_sample_sequencing_metrics_dtos(
|
|
|
76
74
|
)
|
|
77
75
|
sample_metrics_dtos.append(sample_sequencing_metrics_dto)
|
|
78
76
|
return sample_metrics_dtos
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_sequencing_run_dto(
|
|
80
|
+
metrics: PacBioMetrics, run_data: PacBioRunData
|
|
81
|
+
) -> PacBioSequencingRunDTO:
|
|
82
|
+
return PacBioSequencingRunDTO(
|
|
83
|
+
instrument_name=metrics.dataset_metrics.instrument_name, # type: ignore - pydantic will convert to correct type
|
|
84
|
+
run_name=run_data.sequencing_run_name,
|
|
85
|
+
)
|
|
@@ -98,6 +98,7 @@ class SmrtlinkDatasetsMetrics(BaseModel):
|
|
|
98
98
|
cell_index: int = Field(..., alias=SmrtLinkDatabasesAliases.CELL_INDEX)
|
|
99
99
|
path: str = Field(..., alias=SmrtLinkDatabasesAliases.PATH)
|
|
100
100
|
plate: int
|
|
101
|
+
instrument_name: str = Field(..., alias=SmrtLinkDatabasesAliases.INSTRUMENT_NAME)
|
|
101
102
|
|
|
102
103
|
@model_validator(mode="before")
|
|
103
104
|
@classmethod
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from cg.server.endpoints.sequencing_run.dtos import (
|
|
2
2
|
PacbioSequencingRunDTO,
|
|
3
|
-
|
|
3
|
+
PacbioSequencingRunResponse,
|
|
4
|
+
PacbioSequencingRunUpdateRequest,
|
|
5
|
+
PacbioSmrtCellMetricsDTO,
|
|
6
|
+
PacbioSmrtCellMetricsResponse,
|
|
4
7
|
)
|
|
5
|
-
from cg.store.models import
|
|
8
|
+
from cg.store.models import PacbioSMRTCellMetrics
|
|
6
9
|
from cg.store.store import Store
|
|
7
10
|
|
|
8
11
|
|
|
@@ -10,14 +13,39 @@ class PacbioSequencingRunsService:
|
|
|
10
13
|
def __init__(self, store: Store):
|
|
11
14
|
self.store = store
|
|
12
15
|
|
|
13
|
-
def get_sequencing_runs_by_name(self, run_name: str) ->
|
|
14
|
-
runs: list[
|
|
15
|
-
db_runs: list[
|
|
16
|
+
def get_sequencing_runs_by_name(self, run_name: str) -> PacbioSmrtCellMetricsResponse:
|
|
17
|
+
runs: list[PacbioSmrtCellMetricsDTO] = []
|
|
18
|
+
db_runs: list[PacbioSMRTCellMetrics] = self.store.get_pacbio_smrt_cell_metrics_by_run_name(
|
|
16
19
|
run_name
|
|
17
20
|
)
|
|
18
21
|
for db_run in db_runs:
|
|
19
22
|
run_dict = db_run.to_dict()
|
|
20
23
|
run_dict["internal_id"] = db_run.device.internal_id
|
|
21
|
-
|
|
24
|
+
run_dict["run_name"] = db_run.sequencing_run.run_name
|
|
25
|
+
run = PacbioSmrtCellMetricsDTO.model_validate(run_dict)
|
|
22
26
|
runs.append(run)
|
|
23
|
-
return
|
|
27
|
+
return PacbioSmrtCellMetricsResponse(runs=runs)
|
|
28
|
+
|
|
29
|
+
def get_sequencing_runs(self, page: int = 0, page_size: int = 0) -> PacbioSequencingRunResponse:
|
|
30
|
+
db_runs, total_count = self.store.get_pacbio_sequencing_runs(page=page, page_size=page_size)
|
|
31
|
+
runs: list[PacbioSequencingRunDTO] = []
|
|
32
|
+
for db_run in db_runs:
|
|
33
|
+
run = PacbioSequencingRunDTO(
|
|
34
|
+
id=db_run.id,
|
|
35
|
+
run_name=db_run.run_name,
|
|
36
|
+
comment=db_run.comment,
|
|
37
|
+
processed=db_run.processed,
|
|
38
|
+
)
|
|
39
|
+
runs.append(run)
|
|
40
|
+
return PacbioSequencingRunResponse(pacbio_sequencing_runs=runs, total_count=total_count)
|
|
41
|
+
|
|
42
|
+
def update_sequencing_run(self, update_request: PacbioSequencingRunUpdateRequest) -> None:
|
|
43
|
+
if update_request.comment is not None:
|
|
44
|
+
self.store.update_pacbio_sequencing_run_comment(
|
|
45
|
+
id=update_request.id, comment=update_request.comment
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if update_request.processed is not None:
|
|
49
|
+
self.store.update_pacbio_sequencing_run_processed(
|
|
50
|
+
id=update_request.id, processed=update_request.processed
|
|
51
|
+
)
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
1
2
|
from enum import Enum
|
|
2
|
-
from typing import Callable
|
|
3
3
|
|
|
4
4
|
from cg.constants import Workflow
|
|
5
5
|
from cg.services.sequencing_qc_service.quality_checks.utils import (
|
|
6
6
|
all_samples_in_case_have_reads,
|
|
7
7
|
any_sample_in_case_has_reads,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
case_pass_sequencing_qc_on_hifi_yield,
|
|
9
|
+
case_pass_sequencing_qc_on_reads,
|
|
10
|
+
sample_pass_sequencing_qc_on_reads,
|
|
10
11
|
)
|
|
11
12
|
from cg.store.models import Case
|
|
12
13
|
|
|
@@ -18,8 +19,9 @@ class QualityCheck(Enum):
|
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
class SequencingQCCheck(QualityCheck):
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
CASE_PASSES_ON_READS: Callable = case_pass_sequencing_qc_on_reads
|
|
23
|
+
CASE_PASSES_ON_YIELD: Callable = case_pass_sequencing_qc_on_hifi_yield
|
|
24
|
+
SAMPLE_PASSES: Callable = sample_pass_sequencing_qc_on_reads
|
|
23
25
|
ALL_SAMPLES_IN_CASE_HAVE_READS: Callable = all_samples_in_case_have_reads
|
|
24
26
|
ANY_SAMPLE_IN_CASE_HAS_READS: Callable = any_sample_in_case_has_reads
|
|
25
27
|
|
|
@@ -29,20 +31,20 @@ def get_sequencing_quality_check_for_case(case: Case) -> Callable:
|
|
|
29
31
|
workflow: Workflow = case.data_analysis
|
|
30
32
|
|
|
31
33
|
workflow_qc_mapping = {
|
|
32
|
-
Workflow.BALSAMIC: SequencingQCCheck.
|
|
33
|
-
Workflow.BALSAMIC_PON: SequencingQCCheck.
|
|
34
|
-
Workflow.BALSAMIC_UMI: SequencingQCCheck.
|
|
35
|
-
Workflow.MIP_DNA: SequencingQCCheck.CASE_PASSES,
|
|
36
|
-
Workflow.MIP_RNA: SequencingQCCheck.CASE_PASSES,
|
|
37
|
-
Workflow.RAREDISEASE: SequencingQCCheck.CASE_PASSES,
|
|
38
|
-
Workflow.RNAFUSION: SequencingQCCheck.CASE_PASSES,
|
|
39
|
-
Workflow.TOMTE: SequencingQCCheck.CASE_PASSES,
|
|
34
|
+
Workflow.BALSAMIC: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
35
|
+
Workflow.BALSAMIC_PON: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
36
|
+
Workflow.BALSAMIC_UMI: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
40
37
|
Workflow.FLUFFY: SequencingQCCheck.ANY_SAMPLE_IN_CASE_HAS_READS,
|
|
41
|
-
Workflow.RAW_DATA: SequencingQCCheck.ANY_SAMPLE_IN_CASE_HAS_READS,
|
|
42
38
|
Workflow.MICROSALT: SequencingQCCheck.ANY_SAMPLE_IN_CASE_HAS_READS,
|
|
39
|
+
Workflow.MIP_DNA: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
40
|
+
Workflow.MIP_RNA: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
43
41
|
Workflow.MUTANT: SequencingQCCheck.ANY_SAMPLE_IN_CASE_HAS_READS,
|
|
42
|
+
Workflow.NALLO: SequencingQCCheck.CASE_PASSES_ON_YIELD,
|
|
43
|
+
Workflow.RAREDISEASE: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
44
|
+
Workflow.RAW_DATA: SequencingQCCheck.ANY_SAMPLE_IN_CASE_HAS_READS,
|
|
45
|
+
Workflow.RNAFUSION: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
44
46
|
Workflow.TAXPROFILER: SequencingQCCheck.ALL_SAMPLES_IN_CASE_HAVE_READS,
|
|
45
|
-
Workflow.
|
|
47
|
+
Workflow.TOMTE: SequencingQCCheck.CASE_PASSES_ON_READS,
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
if workflow in workflow_qc_mapping:
|
|
@@ -55,5 +57,5 @@ def get_sample_sequencing_quality_check() -> Callable:
|
|
|
55
57
|
return SequencingQCCheck.SAMPLE_PASSES
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
def run_quality_checks(quality_checks: list[
|
|
60
|
+
def run_quality_checks(quality_checks: list[Callable], **kwargs) -> bool:
|
|
59
61
|
return all(quality_check(**kwargs) for quality_check in quality_checks)
|
|
@@ -2,12 +2,13 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
from cg.constants.priority import Priority
|
|
4
4
|
from cg.constants.sequencing import SeqLibraryPrepCategory
|
|
5
|
+
from cg.exc import ApplicationDoesNotHaveHiFiYieldError
|
|
5
6
|
from cg.store.models import Case, Sample
|
|
6
7
|
|
|
7
8
|
LOG = logging.getLogger(__name__)
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def
|
|
11
|
+
def case_pass_sequencing_qc_on_reads(case: Case) -> bool:
|
|
11
12
|
"""
|
|
12
13
|
Get the sequencing QC of a case. The checks are performed in the following order:
|
|
13
14
|
1. If the case is a ready-made library, the ready made library QC is used.
|
|
@@ -21,22 +22,66 @@ def case_pass_sequencing_qc(case: Case) -> bool:
|
|
|
21
22
|
if is_case_ready_made_library(case):
|
|
22
23
|
return ready_made_library_case_pass_sequencing_qc(case)
|
|
23
24
|
if is_case_express_priority(case):
|
|
24
|
-
return
|
|
25
|
+
return express_case_pass_sequencing_qc_on_reads(case)
|
|
25
26
|
return all(sample_has_enough_reads(sample) for sample in case.samples)
|
|
26
27
|
|
|
27
28
|
|
|
28
|
-
def
|
|
29
|
+
def case_pass_sequencing_qc_on_hifi_yield(case: Case) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Get the sequencing QC of a case using yield. The checks are performed in the following order:
|
|
32
|
+
1. If the case is express priority, the express QC is used.
|
|
33
|
+
2. If the above condition is not met then it checks if all samples have enough yield.
|
|
34
|
+
|
|
35
|
+
The express QC should be checked before the standard QC.
|
|
36
|
+
"""
|
|
37
|
+
if is_case_express_priority(case):
|
|
38
|
+
return express_case_pass_sequencing_qc_on_hifi_yield(case)
|
|
39
|
+
return all(sample_has_enough_hifi_yield(sample) for sample in case.samples)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def express_case_pass_sequencing_qc_on_reads(case: Case) -> bool:
|
|
29
43
|
"""
|
|
30
44
|
Checks if all samples in an express case have enough reads.
|
|
31
45
|
"""
|
|
32
46
|
return all(express_sample_has_enough_reads(sample) for sample in case.samples)
|
|
33
47
|
|
|
34
48
|
|
|
35
|
-
def
|
|
49
|
+
def express_case_pass_sequencing_qc_on_hifi_yield(case: Case) -> bool:
|
|
50
|
+
"""
|
|
51
|
+
Checks if all samples in an express case have enough hifi yield.
|
|
52
|
+
"""
|
|
53
|
+
return all(express_sample_has_enough_yield(sample) for sample in case.samples)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def express_sample_pass_sequencing_qc_on_reads(sample: Sample) -> bool:
|
|
36
57
|
return express_sample_has_enough_reads(sample)
|
|
37
58
|
|
|
38
59
|
|
|
39
|
-
def
|
|
60
|
+
def express_sample_has_enough_reads(sample: Sample) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Checks if given express sample has enough reads. Gets the threshold from the sample's
|
|
63
|
+
application version.
|
|
64
|
+
"""
|
|
65
|
+
express_reads_threshold: int = get_express_reads_threshold_for_sample(sample)
|
|
66
|
+
enough_reads: bool = sample.reads >= express_reads_threshold
|
|
67
|
+
if not enough_reads:
|
|
68
|
+
LOG.warning(f"Sample {sample.internal_id} has too few reads.")
|
|
69
|
+
return enough_reads
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def express_sample_has_enough_yield(sample: Sample) -> bool:
|
|
73
|
+
if not sample.hifi_yield:
|
|
74
|
+
LOG.debug(f"Sample {sample.internal_id} has no hifi yield.")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
express_yield_threshold: int = get_express_yield_threshold_for_sample(sample)
|
|
78
|
+
enough_yield: bool = sample.hifi_yield >= express_yield_threshold
|
|
79
|
+
if not enough_yield:
|
|
80
|
+
LOG.warning(f"Sample {sample.internal_id} does not have enough yield.")
|
|
81
|
+
return enough_yield
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def sample_pass_sequencing_qc_on_reads(sample: Sample) -> bool:
|
|
40
85
|
"""
|
|
41
86
|
Get the standard sequencing QC of a sample. The checks are performed in the following order:
|
|
42
87
|
1. If the sample is express priority, the express QC is used.
|
|
@@ -47,7 +92,7 @@ def sample_pass_sequencing_qc(sample: Sample) -> bool:
|
|
|
47
92
|
checked before the standard QC.
|
|
48
93
|
"""
|
|
49
94
|
if is_sample_express_priority(sample):
|
|
50
|
-
return
|
|
95
|
+
return express_sample_pass_sequencing_qc_on_reads(sample)
|
|
51
96
|
if is_sample_ready_made_library(sample):
|
|
52
97
|
return ready_made_library_sample_has_enough_reads(sample)
|
|
53
98
|
return sample_has_enough_reads(sample)
|
|
@@ -94,18 +139,6 @@ def is_case_express_priority(case: Case) -> bool:
|
|
|
94
139
|
return case.priority == Priority.express
|
|
95
140
|
|
|
96
141
|
|
|
97
|
-
def express_sample_has_enough_reads(sample: Sample) -> bool:
|
|
98
|
-
"""
|
|
99
|
-
Checks if given express sample has enough reads. Gets the threshold from the sample's
|
|
100
|
-
application version.
|
|
101
|
-
"""
|
|
102
|
-
express_reads_threshold: int = get_express_reads_threshold_for_sample(sample)
|
|
103
|
-
enough_reads: bool = sample.reads >= express_reads_threshold
|
|
104
|
-
if not enough_reads:
|
|
105
|
-
LOG.warning(f"Sample {sample.internal_id} has too few reads.")
|
|
106
|
-
return enough_reads
|
|
107
|
-
|
|
108
|
-
|
|
109
142
|
def get_express_reads_threshold_for_sample(sample: Sample) -> int:
|
|
110
143
|
"""
|
|
111
144
|
Get the express reads threshold for a sample.
|
|
@@ -113,6 +146,15 @@ def get_express_reads_threshold_for_sample(sample: Sample) -> int:
|
|
|
113
146
|
return round(sample.application_version.application.target_reads / 2)
|
|
114
147
|
|
|
115
148
|
|
|
149
|
+
def get_express_yield_threshold_for_sample(sample: Sample) -> int:
|
|
150
|
+
if threshold := sample.application_version.application.expected_express_hifi_yield:
|
|
151
|
+
return threshold
|
|
152
|
+
else:
|
|
153
|
+
raise ApplicationDoesNotHaveHiFiYieldError(
|
|
154
|
+
f"Application for sample {sample.internal_id} does not have target HiFi yield."
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
116
158
|
def is_sample_ready_made_library(sample: Sample) -> bool:
|
|
117
159
|
return sample.prep_category == SeqLibraryPrepCategory.READY_MADE_LIBRARY
|
|
118
160
|
|
|
@@ -143,6 +185,28 @@ def sample_has_enough_reads(sample: Sample) -> bool:
|
|
|
143
185
|
return enough_reads
|
|
144
186
|
|
|
145
187
|
|
|
188
|
+
def sample_has_enough_hifi_yield(sample: Sample) -> bool:
|
|
189
|
+
"""
|
|
190
|
+
Return true if the sample's HiFi yield is greater than or equal to the threshold.
|
|
191
|
+
Returns false if the HiFi yield is lower than the threshold or None.
|
|
192
|
+
Raises:
|
|
193
|
+
ApplicationDoesNotHaveHiFiYieldError if the sample doesn't have expected HiFi yield.
|
|
194
|
+
"""
|
|
195
|
+
if not sample.expected_hifi_yield:
|
|
196
|
+
raise ApplicationDoesNotHaveHiFiYieldError(
|
|
197
|
+
f"Application for sample {sample.internal_id} does not have target HiFi yield."
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if not sample.hifi_yield:
|
|
201
|
+
LOG.debug(f"Sample {sample.internal_id} has no hifi yield.")
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
enough_hifi_yield: bool = sample.hifi_yield >= sample.expected_hifi_yield
|
|
205
|
+
if not enough_hifi_yield:
|
|
206
|
+
LOG.warning(f"Sample {sample.internal_id} does not have enough HiFi yield.")
|
|
207
|
+
return enough_hifi_yield
|
|
208
|
+
|
|
209
|
+
|
|
146
210
|
def is_sample_express_priority(sample: Sample) -> bool:
|
|
147
211
|
"""
|
|
148
212
|
Check if a sample is express priority.
|
|
@@ -2,15 +2,13 @@ import logging
|
|
|
2
2
|
from typing import Callable
|
|
3
3
|
|
|
4
4
|
from cg.constants.constants import SequencingQCStatus
|
|
5
|
-
from cg.services.sequencing_qc_service.utils import qc_bool_to_status
|
|
6
|
-
from typing import Callable
|
|
7
|
-
|
|
8
|
-
from cg.store.models import Case, Sample
|
|
9
5
|
from cg.services.sequencing_qc_service.quality_checks.checks import (
|
|
10
|
-
run_quality_checks,
|
|
11
|
-
get_sequencing_quality_check_for_case,
|
|
12
6
|
get_sample_sequencing_quality_check,
|
|
7
|
+
get_sequencing_quality_check_for_case,
|
|
8
|
+
run_quality_checks,
|
|
13
9
|
)
|
|
10
|
+
from cg.services.sequencing_qc_service.utils import qc_bool_to_status
|
|
11
|
+
from cg.store.models import Case, Sample
|
|
14
12
|
from cg.store.store import Store
|
|
15
13
|
|
|
16
14
|
LOG = logging.getLogger(__name__)
|
|
@@ -24,10 +22,14 @@ class SequencingQCService:
|
|
|
24
22
|
"""Run QC for samples in pending or failed cases and store the aggregated score on each case."""
|
|
25
23
|
cases: list[Case] = self.store.get_cases_for_sequencing_qc()
|
|
26
24
|
for case in cases:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
LOG.debug(f"Performing sequencing QC for case: {case.internal_id}")
|
|
26
|
+
try:
|
|
27
|
+
passes_qc: bool = self.case_pass_sequencing_qc(case)
|
|
28
|
+
qc_status: SequencingQCStatus = qc_bool_to_status(passes_qc)
|
|
29
|
+
self.store.update_sequencing_qc_status(case=case, status=qc_status)
|
|
30
|
+
LOG.info(f"Sequencing QC status for case {case.internal_id}: {qc_status}")
|
|
31
|
+
except Exception as e:
|
|
32
|
+
LOG.error(f"Error found during sequencing QC of case: {case.internal_id}: {e}")
|
|
31
33
|
|
|
32
34
|
@staticmethod
|
|
33
35
|
def case_pass_sequencing_qc(case: Case) -> bool:
|