Skip to content

Commit

Permalink
Merge branch 'master' into delete-flowcell-post-processing
Browse files Browse the repository at this point in the history
  • Loading branch information
Vince-janv authored Dec 7, 2023
2 parents c919959 + e458ae1 commit c161fbe
Show file tree
Hide file tree
Showing 37 changed files with 451 additions and 202 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 53.5.4
current_version = 53.6.0
commit = True
tag = True
tag_name = v{new_version}
Expand Down
2 changes: 1 addition & 1 deletion cg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__title__ = "cg"
__version__ = "53.5.4"
__version__ = "53.6.0"
4 changes: 2 additions & 2 deletions cg/cli/workflow/mip/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ def config_case(context: CGConfig, case_id: str, panel_bed: str, dry_run: bool):
@OPTION_DRY
@ARGUMENT_CASE_ID
@click.pass_obj
def panel(context: CGConfig, case_id: str, dry_run: bool):
def panel(context: CGConfig, case_id: str, dry_run: bool) -> None:
"""Write aggregated gene panel file exported from Scout."""

analysis_api: MipAnalysisAPI = context.meta_apis["analysis_api"]
analysis_api.status_db.verify_case_exists(case_internal_id=case_id)

bed_lines: list[str] = analysis_api.panel(case_id=case_id)
bed_lines: list[str] = analysis_api.get_gene_panel(case_id=case_id)
if dry_run:
for bed_line in bed_lines:
click.echo(bed_line)
Expand Down
2 changes: 1 addition & 1 deletion cg/cli/workflow/mip_dna/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Commands to start MIP rare disease DNA workflow"""
"""Commands to start MIP rare disease DNA workflow."""

import logging

Expand Down
22 changes: 21 additions & 1 deletion cg/cli/workflow/raredisease/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,38 @@

import click

from cg.cli.workflow.commands import ARGUMENT_CASE_ID, OPTION_DRY
from cg.constants.constants import MetaApis
from cg.meta.workflow.analysis import AnalysisAPI
from cg.meta.workflow.raredisease import RarediseaseAnalysisAPI
from cg.models.cg_config import CGConfig

LOG = logging.getLogger(__name__)


@click.group(invoke_without_command=True)
@click.pass_context
def raredisease(context: click.Context) -> None:
"""nf-core/raredisease analysis workflow."""
"""NF-core/raredisease analysis workflow."""
AnalysisAPI.get_help(context)
context.obj.meta_apis[MetaApis.ANALYSIS_API] = RarediseaseAnalysisAPI(
config=context.obj,
)


@raredisease.command("panel")
@OPTION_DRY
@ARGUMENT_CASE_ID
@click.pass_obj
def panel(context: CGConfig, case_id: str, dry_run: bool) -> None:
"""Write aggregated gene panel file exported from Scout."""

analysis_api: RarediseaseAnalysisAPI = context.meta_apis["analysis_api"]
analysis_api.status_db.verify_case_exists(case_internal_id=case_id)

bed_lines: list[str] = analysis_api.get_gene_panel(case_id=case_id)
if dry_run:
for bed_line in bed_lines:
click.echo(bed_line)
return
analysis_api.write_panel(case_id=case_id, content=bed_lines)
2 changes: 0 additions & 2 deletions cg/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from cg.constants.constants import (
CAPTUREKIT_CANCER_OPTIONS,
CAPTUREKIT_OPTIONS,
COLLABORATORS,
COMBOS,
CONTAINER_OPTIONS,
DEFAULT_CAPTURE_KIT,
PREP_CATEGORIES,
Expand Down
30 changes: 12 additions & 18 deletions cg/constants/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,25 @@ def actions(cls) -> list[str]:
return list(cls)


COLLABORATORS = ("cust000", "cust002", "cust003", "cust004", "cust042")

COMBOS = {
"DSD": ("DSD", "DSD-S", "HYP", "SEXDIF", "SEXDET"),
"CM": ("CNM", "CM"),
"Horsel": ("Horsel", "141217", "141201"),
"OPHTHALMO": (
"OPHTHALMO",
"ANTE-ED",
"CATARACT",
"CORNEA",
"GLAUCOMA",
"RETINA",
"SED",
"ALBINISM",
),
}

CONTAINER_OPTIONS = ("Tube", "96 well plate", "No container")

CONTROL_OPTIONS = ("", "negative", "positive")

DEFAULT_CAPTURE_KIT = "twistexomerefseq_9.1_hg19_design.bed"


class CustomerId(StrEnum):
CG_INTERNAL_CUSTOMER: str = "cust000"
CUST001: str = "cust001"
CUST002: str = "cust002"
CUST003: str = "cust003"
CUST004: str = "cust004"
CUST032: str = "cust032"
CUST042: str = "cust042"
CUST132: str = "cust132"
CUST999: str = "cust999"


class FlowCellStatus(StrEnum):
ON_DISK: str = "ondisk"
REMOVED: str = "removed"
Expand Down
31 changes: 31 additions & 0 deletions cg/constants/gene_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from enum import StrEnum

from cg.constants.constants import CustomerId

GENOME_BUILD_37: str = "37"
GENOME_BUILD_38: str = "GRCh38"

Expand Down Expand Up @@ -44,3 +46,32 @@ class GenePanelMasterList(StrEnum):
def get_panel_names(cls, panels=None) -> list[str]:
"""Return requested panel names from the Master list, or all panels if none are specified."""
return list(panels) if panels else list(cls)

@staticmethod
def collaborators() -> set[str]:
"""Return collaborators of the Master list."""
return {
CustomerId.CG_INTERNAL_CUSTOMER,
CustomerId.CUST002,
CustomerId.CUST003,
CustomerId.CUST004,
CustomerId.CUST042,
}


class GenePanelCombo:
COMBO_1: dict[str, set[str]] = {
"DSD": {"DSD", "DSD-S", "HYP", "SEXDIF", "SEXDET"},
"CM": {"CNM", "CM"},
"Horsel": {"Horsel", "141217", "141201"},
"OPHTHALMO": (
"OPHTHALMO",
"ANTE-ED",
"CATARACT",
"CORNEA",
"GLAUCOMA",
"RETINA",
"SED",
"ALBINISM",
),
}
8 changes: 0 additions & 8 deletions cg/constants/invoice.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
from enum import StrEnum


class CustomerNames(StrEnum):
cust999: str = "cust999"
cust032: str = "cust032"
cust001: str = "cust001"
cust132: str = "cust132"
CG_INTERNAL_CUSTOMER: str = "cust000"


class CostCenters(StrEnum):
ki: str = "ki"
kth: str = "kth"
3 changes: 2 additions & 1 deletion cg/io/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from cg.io.api import delete, get, patch, post, put
from cg.io.csv import read_csv, read_csv_stream, write_csv, write_csv_stream
from cg.io.json import read_json, read_json_stream, write_json, write_json_stream
from cg.io.txt import read_txt
from cg.io.txt import read_txt, write_txt
from cg.io.xml import read_xml, write_xml
from cg.io.yaml import read_yaml, read_yaml_stream, write_yaml, write_yaml_stream

Expand Down Expand Up @@ -50,6 +50,7 @@ class WriteFile:
write_file = {
FileFormat.CSV: write_csv,
FileFormat.JSON: write_json,
FileFormat.TXT: write_txt,
FileFormat.YAML: write_yaml,
FileFormat.XML: write_xml,
}
Expand Down
6 changes: 6 additions & 0 deletions cg/io/txt.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ def read_txt(file_path: Path, read_to_string: bool = False) -> list[str] | str:
if read_to_string:
return file.read()
return list(file)


def write_txt(content: list[str], file_path: Path) -> None:
"""Write content to a text file."""
with open(file_path, "w") as file:
file.writelines(content)
7 changes: 4 additions & 3 deletions cg/meta/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from pydantic.v1 import ValidationError

from cg.apps.lims import LimsAPI
from cg.constants.invoice import CostCenters, CustomerNames
from cg.constants.constants import CustomerId
from cg.constants.invoice import CostCenters
from cg.constants.priority import PriorityTerms
from cg.constants.sequencing import RecordType
from cg.models.invoice.invoice import (
Expand Down Expand Up @@ -44,7 +45,7 @@ def _set_record_type(self):
def get_customer_by_cost_center(self, cost_center: str) -> Customer | str:
"""Return the costumer based on cost center."""
return (
self.db.get_customer_by_internal_id(customer_internal_id=CustomerNames.cust999)
self.db.get_customer_by_internal_id(customer_internal_id=CustomerId.CUST999)
if cost_center.lower() == CostCenters.kth
else self.customer_obj
)
Expand Down Expand Up @@ -212,7 +213,7 @@ def get_lims_id(self, record: Sample or Pool) -> str:

def get_priority(self, record: Sample or Pool, for_discount_price: bool = False) -> str:
"""Return the priority."""
if self.customer_obj.internal_id == CustomerNames.cust032:
if self.customer_obj.internal_id == CustomerId.CUST032:
priority = PriorityTerms.STANDARD
elif self.record_type == RecordType.Pool or (
record.priority_human == "clinical trials" and for_discount_price
Expand Down
7 changes: 3 additions & 4 deletions cg/meta/orders/fastq_submitter.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import datetime as dt

from cg.constants import DataDelivery, GenePanelMasterList
from cg.constants.constants import Pipeline, PrepCategory
from cg.constants.invoice import CustomerNames
from cg.constants.constants import CustomerId, Pipeline, PrepCategory
from cg.constants.priority import Priority
from cg.exc import OrderError
from cg.meta.orders.lims import process_lims
from cg.meta.orders.submitter import Submitter
from cg.models.orders.order import OrderIn
from cg.models.orders.sample_base import StatusEnum
from cg.store.models import ApplicationVersion, Customer, Case, CaseSample, Sample
from cg.store.models import ApplicationVersion, Case, CaseSample, Customer, Sample


class FastqSubmitter(Submitter):
Expand Down Expand Up @@ -66,7 +65,7 @@ def create_maf_case(self, sample_obj: Sample) -> None:
ticket=sample_obj.original_ticket,
)
case.customer = self.status.get_customer_by_internal_id(
customer_internal_id=CustomerNames.CG_INTERNAL_CUSTOMER
customer_internal_id=CustomerId.CG_INTERNAL_CUSTOMER
)
relationship: CaseSample = self.status.relate_sample(
case=case, sample=sample_obj, status=StatusEnum.unknown
Expand Down
7 changes: 4 additions & 3 deletions cg/meta/upload/scout/mip_config_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ def build_load_config(self, rank_score_threshold: int = 5) -> None:
self.load_config.rank_model_version = mip_analysis_data.rank_model_version
self.load_config.sv_rank_model_version = mip_analysis_data.sv_rank_model_version

self.load_config.gene_panels = (
self.mip_analysis_api.convert_panels(
self.analysis_obj.case.customer.internal_id, self.analysis_obj.case.panels
self.load_config.gene_panels: list[str] | None = (
self.mip_analysis_api.get_aggregated_panels(
customer_id=self.analysis_obj.case.customer.internal_id,
default_panels=set(self.analysis_obj.case.panels),
)
or None
)
Expand Down
18 changes: 18 additions & 0 deletions cg/meta/workflow/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from cg.apps.environ import environ_email
from cg.constants import EXIT_FAIL, EXIT_SUCCESS, Pipeline, Priority
from cg.constants.constants import AnalysisType, CaseActions, WorkflowManager
from cg.constants.gene_panel import GenePanelCombo
from cg.exc import AnalysisNotReadyError, BundleAlreadyAddedError, CgDataError, CgError
from cg.meta.meta import MetaAPI
from cg.meta.workflow.fastq import FastqHandler
Expand All @@ -21,6 +22,15 @@
LOG = logging.getLogger(__name__)


def add_gene_panel_combo(default_panels: set[str]) -> set[str]:
"""Add gene panels combinations for gene panels being part of gene panel combination and return updated gene panels."""
all_panels = default_panels
for panel in default_panels:
if panel in GenePanelCombo.COMBO_1:
all_panels |= GenePanelCombo.COMBO_1.get(panel)
return all_panels


class AnalysisAPI(MetaAPI):
"""
Parent class containing all methods that are either shared or overridden by other workflow APIs
Expand Down Expand Up @@ -498,3 +508,11 @@ def prepare_fastq_files(self, case_id: str, dry_run: bool) -> None:
self.resolve_decompression(case_id, dry_run=dry_run)
if not self.is_case_ready_for_analysis(case_id):
raise AnalysisNotReadyError("FASTQ file are not present for the analysis to start")

def _get_gene_panel(self, case_id: str, genome_build: str) -> list[str]:
"""Create and return the aggregated gene panel file."""
case: Case = self.status_db.get_case_by_internal_id(internal_id=case_id)
all_panels: list[str] = self.get_aggregated_panels(
customer_id=case.customer.internal_id, default_panels=set(case.panels)
)
return self.scout_api.export_panels(build=genome_build, panels=all_panels)
51 changes: 19 additions & 32 deletions cg/meta/workflow/mip.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,12 @@
from pydantic.v1 import ValidationError

from cg.apps.mip.confighandler import ConfigHandler
from cg.constants import (
COLLABORATORS,
COMBOS,
FileExtensions,
GenePanelMasterList,
Pipeline,
)
from cg.constants import FileExtensions, GenePanelMasterList, Pipeline
from cg.constants.constants import FileFormat
from cg.constants.housekeeper_tags import HkMipAnalysisTag
from cg.exc import CgError
from cg.io.controller import ReadFile, WriteFile
from cg.meta.workflow.analysis import AnalysisAPI
from cg.meta.workflow.analysis import AnalysisAPI, add_gene_panel_combo
from cg.meta.workflow.fastq import MipFastqHandler
from cg.models.cg_config import CGConfig
from cg.models.mip.mip_analysis import MipAnalysis
Expand Down Expand Up @@ -156,36 +150,29 @@ def link_fastq_files(self, case_id: str, dry_run: bool = False) -> None:
sample_obj=link.sample,
)

def write_panel(self, case_id: str, content: list[str]):
"""Write the gene panel to case dir"""
def write_panel(self, case_id: str, content: list[str]) -> None:
"""Write the gene panel to case dir."""
out_dir = Path(self.root, case_id)
out_dir.mkdir(parents=True, exist_ok=True)
out_path = Path(out_dir, "gene_panels.bed")
with out_path.open("w") as out_handle:
out_handle.write("\n".join(content))
WriteFile.write_file_from_content(
content="\n".join(content),
file_format=FileFormat.TXT,
file_path=Path(out_dir, f"gene_panels{FileExtensions.BED}"),
)

@staticmethod
def convert_panels(customer: str, default_panels: list[str]) -> list[str]:
"""Convert between default panels and all panels included in gene list."""
# check if all default panels are part of master list
def get_aggregated_panels(customer_id: str, default_panels: set[str]) -> list[str]:
"""Check if customer should use the gene panel master list
and if all default panels are included in the gene panel master list.
If not, add gene panel combo and OMIM-AUTO.
Return an aggregated gene panel."""
master_list: list[str] = GenePanelMasterList.get_panel_names()
if customer in COLLABORATORS and set(default_panels).issubset(master_list):
if customer_id in GenePanelMasterList.collaborators() and default_panels.issubset(
master_list
):
return master_list

# the rest are handled the same way
all_panels = set(default_panels)

# fill in extra panels if selection is part of a combo
for panel in default_panels:
if panel in COMBOS:
for extra_panel in COMBOS[panel]:
all_panels.add(extra_panel)

# add OMIM to every panel choice
all_panels.add(GenePanelMasterList.OMIM_AUTO)
# add PANELAPP-GREEN to every panel choice
all_panels.add(GenePanelMasterList.PANELAPP_GREEN)

all_panels: set[str] = add_gene_panel_combo(default_panels=default_panels)
all_panels |= {GenePanelMasterList.OMIM_AUTO, GenePanelMasterList.PANELAPP_GREEN}
return list(all_panels)

def _get_latest_raw_file(self, family_id: str, tags: list[str]) -> Any:
Expand Down
Loading

0 comments on commit c161fbe

Please sign in to comment.