Skip to content

Commit

Permalink
Make delivery report mandatory for Scout uploads (#2704)
Browse files Browse the repository at this point in the history
### Changed
* Delivery report mandatory for Scout uploads
* Renamed function get_report_accreditation to is_report_accredited
  • Loading branch information
ivadym authored Jan 9, 2024
1 parent 9ac3137 commit ad692bc
Show file tree
Hide file tree
Showing 19 changed files with 116 additions and 96 deletions.
5 changes: 5 additions & 0 deletions cg/constants/scout.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ class ScoutExportFileName(StrEnum):
PANELS: str = f"gene_panels{FileExtensions.BED}"


class UploadTrack(StrEnum):
RARE_DISEASE: str = "rare"
CANCER: str = "cancer"


class ScoutCustomCaseReportTags(StrEnum):
DELIVERY: str = "delivery_report"
CNV: str = "cnv_report"
Expand Down
4 changes: 2 additions & 2 deletions cg/meta/report/balsamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,10 @@ def get_variant_caller_version(var_caller_name: str, var_caller_versions: dict)
return versions[0]
return None

def get_report_accreditation(
def is_report_accredited(
self, samples: list[SampleModel], analysis_metadata: BalsamicAnalysis
) -> bool:
"""Checks if the report is accredited or not."""
"""Check if the Balsamic report is accredited."""
if analysis_metadata.config.analysis.sequencing_type == "targeted" and next(
(
panel
Expand Down
4 changes: 2 additions & 2 deletions cg/meta/report/mip_dna.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ def get_genome_build(self, analysis_metadata: MipAnalysis) -> str:
"""Return build version of the genome reference of a specific case."""
return analysis_metadata.genome_build

def get_report_accreditation(
def is_report_accredited(
self, samples: list[SampleModel], analysis_metadata: MipAnalysis = None
) -> bool:
"""Checks if the report is accredited or not by evaluating each of the sample process accreditations."""
"""Check if the MIP-DNA report is accredited by evaluating each of the sample process accreditations."""
for sample in samples:
if not sample.application.accredited:
return False
Expand Down
6 changes: 3 additions & 3 deletions cg/meta/report/report_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def get_report_data(self, case_id: str, analysis_date: datetime) -> ReportModel:
version=self.get_report_version(analysis=analysis),
date=datetime.today(),
case=case_model,
accredited=self.get_report_accreditation(
accredited=self.is_report_accredited(
samples=case_model.samples, analysis_metadata=analysis_metadata
),
)
Expand Down Expand Up @@ -407,10 +407,10 @@ def get_variant_callers(self, _analysis_metadata: AnalysisModel) -> list:
"""Return list of variant-calling filters used during analysis."""
return []

def get_report_accreditation(
def is_report_accredited(
self, samples: list[SampleModel], analysis_metadata: AnalysisModel
) -> bool:
"""Checks if the report is accredited or not."""
"""Check if the report is accredited."""
raise NotImplementedError

def get_required_fields(self, case: CaseModel) -> dict:
Expand Down
4 changes: 2 additions & 2 deletions cg/meta/report/rnafusion.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ def get_genome_build(self, analysis_metadata: AnalysisModel) -> str:
"""Return build version of the genome reference of a specific case."""
return GenomeVersion.hg38.value

def get_report_accreditation(
def is_report_accredited(
self, samples: list[SampleModel], analysis_metadata: AnalysisModel
) -> bool:
"""Checks if the report is accredited or not. Rnafusion is an accredited workflow."""
"""Check if the report is accredited. Rnafusion is an accredited workflow."""
return True

def get_template_name(self) -> str:
Expand Down
9 changes: 6 additions & 3 deletions cg/meta/upload/scout/balsamic_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from cg.apps.lims import LimsAPI
from cg.constants.constants import SampleType
from cg.constants.scout import BALSAMIC_CASE_TAGS, BALSAMIC_SAMPLE_TAGS
from cg.constants.housekeeper_tags import HK_DELIVERY_REPORT_TAG
from cg.constants.scout import BALSAMIC_CASE_TAGS, BALSAMIC_SAMPLE_TAGS, UploadTrack
from cg.constants.subject import PhenotypeStatus
from cg.meta.upload.scout.hk_tags import CaseTags, SampleTags
from cg.meta.upload.scout.scout_config_builder import ScoutConfigBuilder
Expand All @@ -22,7 +23,10 @@ def __init__(self, hk_version_obj: Version, analysis_obj: Analysis, lims_api: Li
)
self.case_tags: CaseTags = CaseTags(**BALSAMIC_CASE_TAGS)
self.sample_tags: SampleTags = SampleTags(**BALSAMIC_SAMPLE_TAGS)
self.load_config: BalsamicLoadConfig = BalsamicLoadConfig(track="cancer")
self.load_config: BalsamicLoadConfig = BalsamicLoadConfig(
track=UploadTrack.CANCER.value,
delivery_report=self.get_file_from_hk({HK_DELIVERY_REPORT_TAG}),
)

def include_case_files(self):
LOG.info("Including BALSAMIC specific case level files")
Expand All @@ -34,7 +38,6 @@ def include_case_files(self):
)
self.include_cnv_report()
self.include_multiqc_report()
self.include_delivery_report()

def include_sample_files(self, config_sample: ScoutCancerIndividual) -> None:
LOG.info("Including BALSAMIC specific sample level files.")
Expand Down
16 changes: 8 additions & 8 deletions cg/meta/upload/scout/balsamic_umi_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@
from housekeeper.store.models import Version

from cg.apps.lims import LimsAPI
from cg.constants.scout import BALSAMIC_UMI_CASE_TAGS, BALSAMIC_UMI_SAMPLE_TAGS
from cg.constants.housekeeper_tags import HK_DELIVERY_REPORT_TAG
from cg.constants.scout import BALSAMIC_UMI_CASE_TAGS, BALSAMIC_UMI_SAMPLE_TAGS, UploadTrack
from cg.meta.upload.scout.balsamic_config_builder import BalsamicConfigBuilder
from cg.meta.upload.scout.hk_tags import CaseTags, SampleTags
from cg.models.scout.scout_load_config import (
BalsamicUmiLoadConfig,
ScoutCancerIndividual,
)
from cg.models.scout.scout_load_config import BalsamicUmiLoadConfig, ScoutCancerIndividual
from cg.store.models import Analysis, Sample

LOG = logging.getLogger(__name__)
Expand All @@ -22,12 +20,14 @@ def __init__(self, hk_version_obj: Version, analysis_obj: Analysis, lims_api: Li
)
self.case_tags: CaseTags = CaseTags(**BALSAMIC_UMI_CASE_TAGS)
self.sample_tags: SampleTags = SampleTags(**BALSAMIC_UMI_SAMPLE_TAGS)
self.load_config: BalsamicUmiLoadConfig = BalsamicUmiLoadConfig(track="cancer")
self.load_config: BalsamicUmiLoadConfig = BalsamicUmiLoadConfig(
track=UploadTrack.CANCER.value,
delivery_report=self.get_file_from_hk({HK_DELIVERY_REPORT_TAG}),
)

def include_sample_files(self, config_sample: ScoutCancerIndividual) -> None:
LOG.info("Including BALSAMIC specific sample level files")

def get_balsamic_analysis_type(self, sample: Sample) -> str:
"""Returns a formatted balsamic analysis type"""

"""Returns a formatted balsamic analysis type."""
return "panel-umi"
15 changes: 7 additions & 8 deletions cg/meta/upload/scout/mip_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@

from cg.apps.lims import LimsAPI
from cg.apps.madeline.api import MadelineAPI
from cg.constants.scout import MIP_CASE_TAGS, MIP_SAMPLE_TAGS
from cg.constants.housekeeper_tags import HK_DELIVERY_REPORT_TAG
from cg.constants.scout import MIP_CASE_TAGS, MIP_SAMPLE_TAGS, UploadTrack
from cg.constants.subject import RelationshipStatus
from cg.meta.upload.scout.hk_tags import CaseTags, SampleTags
from cg.meta.upload.scout.scout_config_builder import ScoutConfigBuilder
from cg.meta.workflow.mip import MipAnalysisAPI
from cg.models.mip.mip_analysis import MipAnalysis
from cg.models.scout.scout_load_config import (
MipLoadConfig,
ScoutLoadConfig,
ScoutMipIndividual,
)
from cg.models.scout.scout_load_config import MipLoadConfig, ScoutLoadConfig, ScoutMipIndividual
from cg.store.models import Analysis, Case, CaseSample

LOG = logging.getLogger(__name__)
Expand All @@ -36,7 +33,10 @@ def __init__(
)
self.case_tags: CaseTags = CaseTags(**MIP_CASE_TAGS)
self.sample_tags: SampleTags = SampleTags(**MIP_SAMPLE_TAGS)
self.load_config: MipLoadConfig = MipLoadConfig(track="rare")
self.load_config: MipLoadConfig = MipLoadConfig(
track=UploadTrack.RARE_DISEASE.value,
delivery_report=self.get_file_from_hk({HK_DELIVERY_REPORT_TAG}),
)
self.mip_analysis_api: MipAnalysisAPI = mip_analysis_api
self.lims_api: LimsAPI = lims_api
self.madeline_api: MadelineAPI = madeline_api
Expand Down Expand Up @@ -116,7 +116,6 @@ def include_case_files(self):
self.load_config.vcf_sv = self.get_file_from_hk(self.case_tags.sv_vcf)
self.load_config.vcf_sv_research = self.get_file_from_hk(self.case_tags.sv_research_vcf)
self.include_multiqc_report()
self.include_delivery_report()

def include_sample_files(self, config_sample: ScoutMipIndividual) -> None:
"""Include sample level files that are optional for mip samples"""
Expand Down
8 changes: 6 additions & 2 deletions cg/meta/upload/scout/rnafusion_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from cg.apps.lims import LimsAPI
from cg.constants.constants import PrepCategory
from cg.constants.scout import RNAFUSION_CASE_TAGS, RNAFUSION_SAMPLE_TAGS, GenomeBuild
from cg.constants.housekeeper_tags import HK_DELIVERY_REPORT_TAG
from cg.constants.scout import RNAFUSION_CASE_TAGS, RNAFUSION_SAMPLE_TAGS, GenomeBuild, UploadTrack
from cg.meta.upload.scout.hk_tags import CaseTags, SampleTags
from cg.meta.upload.scout.scout_config_builder import ScoutConfigBuilder
from cg.models.scout.scout_load_config import (
Expand All @@ -26,7 +27,10 @@ def __init__(self, hk_version_obj: Version, analysis_obj: Analysis, lims_api: Li
)
self.case_tags: CaseTags = CaseTags(**RNAFUSION_CASE_TAGS)
self.sample_tags: SampleTags = SampleTags(**RNAFUSION_SAMPLE_TAGS)
self.load_config: RnafusionLoadConfig = RnafusionLoadConfig(track="cancer")
self.load_config: RnafusionLoadConfig = RnafusionLoadConfig(
track=UploadTrack.CANCER.value,
delivery_report=self.get_file_from_hk({HK_DELIVERY_REPORT_TAG}),
)

def build_load_config(self) -> None:
"""Build a rnafusion-specific load config for uploading a case to scout."""
Expand Down
12 changes: 5 additions & 7 deletions cg/meta/upload/scout/scout_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@

from cg.apps.housekeeper.hk import HousekeeperAPI
from cg.apps.lims import LimsAPI
from cg.constants.housekeeper_tags import HK_DELIVERY_REPORT_TAG
from cg.meta.upload.scout.hk_tags import CaseTags, SampleTags
from cg.models.scout.scout_load_config import ScoutIndividual, ScoutLoadConfig
from cg.store.models import Analysis, CaseSample, Sample

LOG = logging.getLogger(__name__)


# Maps keys that are used in scout load config on tags that are used in scout


Expand All @@ -24,7 +26,9 @@ def __init__(self, hk_version_obj: Version, analysis_obj: Analysis, lims_api: Li
self.lims_api: LimsAPI = lims_api
self.case_tags: CaseTags
self.sample_tags: SampleTags
self.load_config: ScoutLoadConfig = ScoutLoadConfig()
self.load_config: ScoutLoadConfig = ScoutLoadConfig(
delivery_report=self.get_file_from_hk({HK_DELIVERY_REPORT_TAG})
)

def add_common_info_to_load_config(self) -> None:
"""Add the mandatory common information to a scout load config object"""
Expand Down Expand Up @@ -137,12 +141,6 @@ def include_multiqc_report(self) -> None:
hk_tags=self.case_tags.multiqc_report, latest=True
)

def include_delivery_report(self) -> None:
LOG.info("Include delivery report to case")
self.load_config.delivery_report = self.get_file_from_hk(
hk_tags=self.case_tags.delivery_report, latest=True
)

def include_sample_alignment_file(self, config_sample: ScoutIndividual) -> None:
"""Include the alignment file for a sample
Expand Down
8 changes: 5 additions & 3 deletions cg/models/scout/scout_load_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from pydantic import BaseModel, BeforeValidator, ConfigDict
from typing_extensions import Annotated, Literal

from cg.constants.scout import UploadTrack
from cg.models.scout.validators import field_not_none


Expand Down Expand Up @@ -91,12 +92,13 @@ class ScoutLoadConfig(BaseModel):
sv_rank_model_version: str | None = None
analysis_date: datetime | None = None
samples: list[ScoutIndividual] = []

delivery_report: str | None = None
delivery_report: str
coverage_qc_report: str | None = None
cnv_report: str | None = None
multiqc: str | None = None
track: Literal["rare", "cancer"] = "rare"
track: Literal[
UploadTrack.RARE_DISEASE.value, UploadTrack.CANCER.value
] = UploadTrack.RARE_DISEASE.value

model_config = ConfigDict(validate_assignment=True)

Expand Down
34 changes: 20 additions & 14 deletions tests/apps/scout/test_scout_load_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Tests for the models in scout load config"""
"""Tests for the models in Scout load config."""
from pathlib import Path
from typing import Any

import pytest
Expand All @@ -11,35 +12,40 @@

@pytest.mark.parametrize("key, value", list(SCOUT_INDIVIDUAL.items()))
def test_validate_scout_individual_attributes(scout_individual: dict, key: str, value: Any):
"""Test to validate that all attributes of a ScoutMipIndividual are correctly set."""
"""Test that all attributes of a ScoutMipIndividual are correctly set."""

# GIVEN some sample information

# WHEN instantiating a ScoutMipIndividual
ind_obj: ScoutMipIndividual = scout_load_config.ScoutMipIndividual(**scout_individual)

# THEN assert that the attribute is set correctly
assert getattr(ind_obj, key) == value


def test_instantiate_empty_mip_config():
"""Tests whether a MipLoadConfig can be instantiate without arguments."""
# GIVEN nothing
def test_instantiate_empty_mip_config(delivery_report_html: Path):
"""Tests whether a MipLoadConfig can be instantiated only with mandatory arguments."""

# GIVEN a delivery report file

# WHEN instantiating a empty mip load config
config: MipLoadConfig = scout_load_config.MipLoadConfig()
# WHEN instantiating an empty MIP load config
config: MipLoadConfig = scout_load_config.MipLoadConfig(
delivery_report=delivery_report_html.as_posix()
)

# THEN assert it is possible to instantiate without any information
# THEN assert it is possible to instantiate without any not mandatory information
assert isinstance(config, scout_load_config.ScoutLoadConfig)


def test_set_mandatory_to_none():
"""The scout load config object should validate fields as they are set.
def test_set_mandatory_to_none(delivery_report_html: Path):
"""Test that a value error is raised when a mandatory field is set to None."""

This test will check that a value error is raised when a mandatory field is set to None.
"""
# GIVEN a load config object
config: MipLoadConfig = scout_load_config.MipLoadConfig()
config: MipLoadConfig = scout_load_config.MipLoadConfig(
delivery_report=delivery_report_html.as_posix()
)

# WHEN setting a mandatory field to None
with pytest.raises(ValidationError):
# THEN assert a validation error was raised
# THEN a validation error should be raised
config.vcf_snv = None
10 changes: 3 additions & 7 deletions tests/cli/upload/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@
from cg.models.scout.scout_load_config import ScoutLoadConfig
from cg.store import Store
from cg.store.models import Analysis
from tests.cli.workflow.mip.conftest import (
mip_case_id,
mip_case_ids,
mip_dna_context,
mip_rna_context,
)
from tests.meta.upload.scout.conftest import mip_load_config
from tests.mocks.hk_mock import MockHousekeeperAPI
from tests.mocks.madeline import MockMadelineAPI
Expand Down Expand Up @@ -248,7 +242,9 @@ def __init__(self, **kwargs):
self.housekeeper = None
self.madeline_api = MockMadelineAPI()
self.analysis = MockAnalysisApi()
self.config = ScoutLoadConfig()
self.config = ScoutLoadConfig(
delivery_report=Path("path", "to", "delivery-report.html").as_posix()
)
self.file_exists = False
self.lims = MockLims()
self.missing_mandatory_field = False
Expand Down
9 changes: 8 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from cg.constants import FileExtensions, Pipeline, SequencingFileTag
from cg.constants.constants import CaseActions, FileFormat, Strandedness
from cg.constants.demultiplexing import BclConverter, DemultiplexingDirsAndFiles
from cg.constants.housekeeper_tags import HK_DELIVERY_REPORT_TAG
from cg.constants.nanopore_files import NanoporeDirsAndFiles
from cg.constants.priority import SlurmQos
from cg.constants.sequencing import SequencingPlatform
Expand Down Expand Up @@ -1895,6 +1896,7 @@ def hk_bundle_sample_path(sample_id: str, timestamp: datetime) -> Path:
def hk_bundle_data(
case_id: str,
bed_file: Path,
delivery_report_html: Path,
timestamp_yesterday: datetime,
sample_id: str,
father_sample_id: str,
Expand All @@ -1910,7 +1912,12 @@ def hk_bundle_data(
"path": bed_file.as_posix(),
"archive": False,
"tags": ["bed", sample_id, father_sample_id, mother_sample_id, "coverage"],
}
},
{
"path": delivery_report_html.as_posix(),
"archive": False,
"tags": [HK_DELIVERY_REPORT_TAG],
},
],
}

Expand Down
Loading

0 comments on commit ad692bc

Please sign in to comment.