diff --git a/cg/constants/constants.py b/cg/constants/constants.py index 1c8a9691ee..514a9927be 100644 --- a/cg/constants/constants.py +++ b/cg/constants/constants.py @@ -213,6 +213,7 @@ class APIMethods(StrEnum): class MicrosaltQC: QC_PERCENT_THRESHOLD_MWX: float = 0.1 COVERAGE_10X_THRESHOLD: float = 0.75 + MAPPED_RATE_THRESHOLD: float = 0.3 NEGATIVE_CONTROL_READS_THRESHOLD: float = 0.2 TARGET_READS: int = 6000000 TARGET_READS_FAIL_THRESHOLD: float = 0.7 diff --git a/cg/meta/workflow/microsalt/models.py b/cg/meta/workflow/microsalt/models.py index d43ec10a2f..c2bac742f3 100644 --- a/cg/meta/workflow/microsalt/models.py +++ b/cg/meta/workflow/microsalt/models.py @@ -29,7 +29,7 @@ class MicrosaltSamtoolsStats(BaseModel): coverage_100x: float -class Sample(BaseModel): +class SampleMetrics(BaseModel): blast_pubmlst: BlastPubmlst quast_assembly: QuastAssembly blast_resfinder_resistence: List[str] @@ -38,4 +38,10 @@ class Sample(BaseModel): class QualityMetrics(BaseModel): - samples: Dict[str, Sample] + samples: Dict[str, SampleMetrics] + + +class QualityResult(BaseModel): + sample_id: str + passed: bool + fail_message: str | None = None diff --git a/cg/meta/workflow/microsalt/quality_checker.py b/cg/meta/workflow/microsalt/quality_checker.py index e55ed71a30..88193603a2 100644 --- a/cg/meta/workflow/microsalt/quality_checker.py +++ b/cg/meta/workflow/microsalt/quality_checker.py @@ -4,7 +4,13 @@ from cg.io.json import read_json, write_json from cg.constants.constants import MicrosaltAppTags, MicrosaltQC -from cg.meta.workflow.microsalt.utils import is_valid_total_reads, is_valid_total_reads_for_control +from cg.meta.workflow.microsalt.models import QualityMetrics, QualityResult, SampleMetrics +from cg.meta.workflow.microsalt.utils import ( + is_valid_mapped_rate, + is_valid_total_reads, + is_valid_total_reads_for_control, + parse_quality_metrics, +) from cg.models.orders.sample_base import ControlEnum from cg.store.api.core import Store from cg.store.models import Sample @@ -16,6 +22,27 @@ class QualityChecker: def __init__(self, status_db: Store): self.status_db = status_db + def quality_control(self, run_dir_path: Path, lims_project: str): + metrics_file_path: Path = Path(run_dir_path, f"{lims_project}.json") + quality_metrics: QualityMetrics = parse_quality_metrics(metrics_file_path) + + sample_results: list[QualityResult] = [] + + for sample_metrics in quality_metrics: + result = self.quality_control_sample(sample_metrics) + sample_results.append(result) + + self.quality_control_case(sample_results) + + def quality_control_sample( + self, sample_id: str, sample_metrics: SampleMetrics + ) -> QualityResult: + reads_passes_qc: bool = self.is_valid_total_reads(sample_id) + mapped_rate_passes_qc: bool = self.is_valid_mapped_rate(sample_metrics) + + def quality_control_case(self, sample_results: list[QualityResult]) -> bool: + pass + def microsalt_qc(self, case_id: str, run_dir_path: Path, lims_project: str) -> bool: """Check if given microSALT case passes QC check.""" failed_samples: dict = {} @@ -131,3 +158,7 @@ def is_valid_total_reads(self, sample_id: str) -> bool: sample_reads=sample_reads, target_reads=target_reads ) return is_valid_total_reads(sample_reads=sample_reads, target_reads=target_reads) + + def is_valid_mapped_rate(self, metrics: SampleMetrics) -> bool: + mapped_rate: float = metrics.microsalt_samtools_stats.mapped_rate + return is_valid_mapped_rate(mapped_rate) diff --git a/cg/meta/workflow/microsalt/utils.py b/cg/meta/workflow/microsalt/utils.py index fc4fcd5ba1..c08e7f0eb0 100644 --- a/cg/meta/workflow/microsalt/utils.py +++ b/cg/meta/workflow/microsalt/utils.py @@ -1,4 +1,8 @@ +from pathlib import Path + from cg.constants.constants import MicrosaltQC +from cg.io.json import read_json +from cg.meta.workflow.microsalt.models import QualityMetrics def is_valid_total_reads(sample_reads: int, target_reads: int) -> bool: @@ -7,3 +11,12 @@ def is_valid_total_reads(sample_reads: int, target_reads: int) -> bool: def is_valid_total_reads_for_control(sample_reads: int, target_reads: int) -> bool: return sample_reads < target_reads * MicrosaltQC.NEGATIVE_CONTROL_READS_THRESHOLD + + +def is_valid_mapped_rate(sample_mapped_rate: float) -> bool: + return sample_mapped_rate > MicrosaltQC.MAPPED_RATE_THRESHOLD + + +def parse_quality_metrics(file_path: Path) -> QualityMetrics: + data = read_json(file_path) + return QualityMetrics.model_validate_json(data)