cg 80.1.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.
Files changed (91) hide show
  1. cg/__init__.py +1 -1
  2. cg/apps/housekeeper/hk.py +1 -1
  3. cg/apps/tb/api.py +1 -1
  4. cg/cli/upload/mutacc.py +16 -3
  5. cg/cli/upload/scout.py +2 -2
  6. cg/cli/upload/utils.py +10 -1
  7. cg/cli/workflow/balsamic/base.py +29 -4
  8. cg/cli/workflow/microsalt/base.py +3 -1
  9. cg/cli/workflow/nallo/base.py +18 -38
  10. cg/cli/workflow/nf_analysis.py +2 -203
  11. cg/cli/workflow/raredisease/base.py +33 -51
  12. cg/cli/workflow/rnafusion/base.py +28 -3
  13. cg/cli/workflow/taxprofiler/base.py +21 -13
  14. cg/cli/workflow/tomte/base.py +17 -19
  15. cg/constants/constants.py +3 -3
  16. cg/constants/devices.py +6 -1
  17. cg/constants/gene_panel.py +3 -1
  18. cg/constants/lims.py +4 -0
  19. cg/constants/orderforms.py +1 -1
  20. cg/constants/pacbio.py +1 -0
  21. cg/constants/scout.py +6 -4
  22. cg/exc.py +12 -4
  23. cg/meta/compress/compress.py +7 -2
  24. cg/meta/delivery_report/nallo.py +1 -1
  25. cg/meta/delivery_report/templates/macros/ticket_system.html +1 -1
  26. cg/meta/observations/balsamic_observations_api.py +1 -1
  27. cg/meta/observations/mip_dna_observations_api.py +1 -1
  28. cg/meta/observations/nallo_observations_api.py +1 -1
  29. cg/meta/observations/observations_api.py +1 -1
  30. cg/meta/observations/raredisease_observations_api.py +1 -1
  31. cg/meta/tar/tar.py +5 -2
  32. cg/meta/upload/coverage.py +5 -5
  33. cg/meta/upload/raredisease/raredisease.py +3 -0
  34. cg/meta/upload/scout/nallo_config_builder.py +14 -0
  35. cg/meta/workflow/nallo.py +22 -95
  36. cg/meta/workflow/nf_analysis.py +11 -262
  37. cg/meta/workflow/raredisease.py +3 -112
  38. cg/meta/workflow/rnafusion.py +2 -34
  39. cg/meta/workflow/taxprofiler.py +2 -38
  40. cg/meta/workflow/tomte.py +2 -42
  41. cg/models/deliverables/metric_deliverables.py +1 -1
  42. cg/models/nallo/nallo.py +14 -64
  43. cg/models/nf_analysis.py +1 -41
  44. cg/models/raredisease/raredisease.py +0 -62
  45. cg/models/rnafusion/rnafusion.py +0 -26
  46. cg/models/scout/scout_load_config.py +1 -0
  47. cg/models/taxprofiler/taxprofiler.py +0 -42
  48. cg/models/tomte/tomte.py +0 -69
  49. cg/resources/nallo_bundle_filenames.yaml +282 -22
  50. cg/resources/raredisease_bundle_filenames.yaml +11 -1
  51. cg/resources/taxprofiler_bundle_filenames.yaml +20 -0
  52. cg/server/admin.py +51 -24
  53. cg/server/app.py +15 -4
  54. cg/server/endpoints/sequencing_run/dtos.py +21 -3
  55. cg/server/endpoints/sequencing_run/pacbio_sequencing_run.py +29 -10
  56. cg/server/endpoints/sequencing_run/pacbio_smrt_cell_metrics.py +20 -0
  57. cg/services/analysis_starter/configurator/configurator.py +1 -1
  58. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/models.py +40 -1
  59. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/nallo.py +3 -1
  60. cg/services/analysis_starter/configurator/file_creators/nextflow/params_file/tomte_params_file_creator.py +3 -1
  61. cg/services/analysis_starter/factories/configurator_factory.py +4 -4
  62. cg/services/analysis_starter/tracker/implementations/balsamic.py +4 -1
  63. cg/services/analysis_starter/tracker/implementations/microsalt.py +4 -4
  64. cg/services/analysis_starter/tracker/implementations/mip_dna.py +4 -1
  65. cg/services/analysis_starter/tracker/implementations/nextflow_tracker.py +6 -4
  66. cg/services/analysis_starter/tracker/tracker.py +10 -6
  67. cg/services/illumina/backup/backup_service.py +29 -7
  68. cg/services/orders/validation/constants.py +3 -0
  69. cg/services/orders/validation/index_sequences.py +558 -0
  70. cg/services/run_devices/pacbio/data_storage_service/pacbio_store_service.py +39 -18
  71. cg/services/run_devices/pacbio/data_transfer_service/data_transfer_service.py +8 -2
  72. cg/services/run_devices/pacbio/data_transfer_service/dto.py +9 -3
  73. cg/services/run_devices/pacbio/data_transfer_service/utils.py +14 -7
  74. cg/services/run_devices/pacbio/metrics_parser/models.py +1 -0
  75. cg/services/run_devices/pacbio/sequencing_runs_service.py +35 -7
  76. cg/services/sequencing_qc_service/quality_checks/checks.py +18 -16
  77. cg/services/sequencing_qc_service/quality_checks/utils.py +82 -18
  78. cg/services/sequencing_qc_service/sequencing_qc_service.py +12 -10
  79. cg/store/crud/create.py +73 -42
  80. cg/store/crud/read.py +50 -2
  81. cg/store/crud/update.py +14 -3
  82. cg/store/models.py +88 -31
  83. cg/store/store.py +8 -1
  84. {cg-80.1.0.dist-info → cg-83.14.0.dist-info}/METADATA +1 -1
  85. {cg-80.1.0.dist-info → cg-83.14.0.dist-info}/RECORD +91 -90
  86. /cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{nallo.py → nallo_sample_sheet_creator.py} +0 -0
  87. /cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{raredisease.py → raredisease_sample_sheet_creator.py} +0 -0
  88. /cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{rnafusion.py → rnafusion_sample_sheet_creator.py} +0 -0
  89. /cg/services/analysis_starter/configurator/file_creators/nextflow/sample_sheet/{taxprofiler.py → taxprofiler_sample_sheet_creator.py} +0 -0
  90. {cg-80.1.0.dist-info → cg-83.14.0.dist-info}/WHEEL +0 -0
  91. {cg-80.1.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 _create_instrument_run(
37
- self, instrument_run_dto: PacBioSequencingRunDTO, smrt_cell: PacbioSMRTCell
38
+ def _create_pacbio_sequencing_run_if_non_existent(
39
+ self, sequencing_run_dto: PacBioSequencingRunDTO
38
40
  ) -> PacbioSequencingRun:
39
- return self.store.create_pac_bio_sequencing_run(
40
- sequencing_run_dto=instrument_run_dto, smrt_cell=smrt_cell
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
- sequencing_run: PacbioSequencingRun,
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, sequencing_run=sequencing_run
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
- for sample_dto in sample_run_metrics_dtos:
60
- self.store.update_sample_reads(
61
- internal_id=sample_dto.sample_internal_id, reads=sample_dto.hifi_reads
62
- )
63
- self.store.update_sample_sequenced_at(
64
- internal_id=sample_dto.sample_internal_id, date=sequencing_date
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
- sequencing_run: PacbioSequencingRun = self._create_instrument_run(
75
- instrument_run_dto=dtos.sequencing_run, smrt_cell=smrt_cell
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, sequencing_run=sequencing_run
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=sequencing_run.completed_at,
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
- sequencing_run=sequencing_run_dto,
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 PacBioSequencingRunDTO(BaseModel):
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
- sequencing_run: PacBioSequencingRunDTO
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 get_sequencing_run_dto(
17
+ def get_smrt_cell_metrics_dto(
20
18
  metrics: PacBioMetrics, run_data: PacBioRunData
21
- ) -> PacBioSequencingRunDTO:
19
+ ) -> PacBioSMRTCellMetricsDTO:
22
20
  hifi_mean_read_quality: str = f"Q{metrics.read.hifi_median_read_quality}"
23
- return PacBioSequencingRunDTO(
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
- PacbioSequencingRunsResponse,
3
+ PacbioSequencingRunResponse,
4
+ PacbioSequencingRunUpdateRequest,
5
+ PacbioSmrtCellMetricsDTO,
6
+ PacbioSmrtCellMetricsResponse,
4
7
  )
5
- from cg.store.models import PacbioSequencingRun
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) -> PacbioSequencingRunsResponse:
14
- runs: list[PacbioSequencingRunDTO] = []
15
- db_runs: list[PacbioSequencingRun] = self.store.get_pacbio_sequencing_runs_by_run_name(
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
- run = PacbioSequencingRunDTO.model_validate(run_dict)
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 PacbioSequencingRunsResponse(runs=runs)
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
- case_pass_sequencing_qc,
9
- sample_pass_sequencing_qc,
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
- CASE_PASSES: Callable = case_pass_sequencing_qc
22
- SAMPLE_PASSES: Callable = sample_pass_sequencing_qc
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.CASE_PASSES,
33
- Workflow.BALSAMIC_PON: SequencingQCCheck.CASE_PASSES,
34
- Workflow.BALSAMIC_UMI: SequencingQCCheck.CASE_PASSES,
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.NALLO: SequencingQCCheck.ALL_SAMPLES_IN_CASE_HAVE_READS,
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[QualityCheck], **kwargs) -> bool:
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 case_pass_sequencing_qc(case: Case) -> bool:
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 express_case_pass_sequencing_qc(case)
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 express_case_pass_sequencing_qc(case: Case) -> bool:
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 express_sample_pass_sequencing_qc(sample: Sample) -> bool:
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 sample_pass_sequencing_qc(sample: Sample) -> bool:
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 express_sample_pass_sequencing_qc(sample)
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
- 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}")
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: