Skip to content

Commit

Permalink
Add mip rna messages (#3902) (patch)
Browse files Browse the repository at this point in the history
### Added

- crud functions to retrieve dna cases related to an case.
- Logic for generation of delivery messages for mip-rna.
  • Loading branch information
beatrizsavinhas authored Nov 28, 2024
1 parent 0fadd58 commit 5aba7cb
Show file tree
Hide file tree
Showing 15 changed files with 294 additions and 109 deletions.
1 change: 1 addition & 0 deletions cg/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
CAPTUREKIT_OPTIONS,
CONTAINER_OPTIONS,
DEFAULT_CAPTURE_KIT,
DNA_WORKFLOWS_WITH_SCOUT_UPLOAD,
STATUS_OPTIONS,
DataDelivery,
FileExtensions,
Expand Down
7 changes: 7 additions & 0 deletions cg/constants/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ class Workflow(StrEnum):
TOMTE: str = "tomte"


DNA_WORKFLOWS_WITH_SCOUT_UPLOAD: list[Workflow] = [
Workflow.MIP_DNA,
Workflow.BALSAMIC,
Workflow.BALSAMIC_UMI,
]


class FileFormat(StrEnum):
CSV: str = "csv"
FASTQ: str = "fastq"
Expand Down
7 changes: 7 additions & 0 deletions cg/constants/sequencing.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,10 @@ class SeqLibraryPrepCategory(StrEnum):
WHOLE_EXOME_SEQUENCING: str = "wes"
WHOLE_GENOME_SEQUENCING: str = "wgs"
WHOLE_TRANSCRIPTOME_SEQUENCING: str = "wts"


DNA_PREP_CATEGORIES: list[SeqLibraryPrepCategory] = [
SeqLibraryPrepCategory.WHOLE_GENOME_SEQUENCING,
SeqLibraryPrepCategory.TARGETED_GENOME_SEQUENCING,
SeqLibraryPrepCategory.WHOLE_EXOME_SEQUENCING,
]
8 changes: 5 additions & 3 deletions cg/server/endpoints/cases.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
from http import HTTPStatus

from flask import Blueprint, abort, g, jsonify, request
from cg.exc import CaseNotFoundError, OrderMismatchError

from cg.exc import CaseNotFoundError, CgDataError, OrderMismatchError
from cg.server.dto.delivery_message.delivery_message_request import DeliveryMessageRequest
from cg.server.dto.delivery_message.delivery_message_response import DeliveryMessageResponse
from cg.server.endpoints.utils import before_request
Expand Down Expand Up @@ -62,7 +64,7 @@ def get_cases_delivery_message():
delivery_message_request
)
return jsonify(response.model_dump()), HTTPStatus.OK
except (CaseNotFoundError, OrderMismatchError) as error:
except (CaseNotFoundError, OrderMismatchError, CgDataError) as error:
return jsonify({"error": str(error)}), HTTPStatus.BAD_REQUEST


Expand All @@ -74,7 +76,7 @@ def get_case_delivery_message(case_id: str):
delivery_message_request
)
return jsonify(response.model_dump()), HTTPStatus.OK
except CaseNotFoundError as error:
except (CaseNotFoundError, CgDataError) as error:
return jsonify({"error": str(error)}), HTTPStatus.BAD_REQUEST


Expand Down
6 changes: 2 additions & 4 deletions cg/services/delivery_message/delivery_message_service.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
from cg.apps.tb import TrailblazerAPI
from cg.apps.tb.models import TrailblazerAnalysis
from cg.exc import OrderNotDeliverableError
from cg.server.dto.delivery_message.delivery_message_request import (
DeliveryMessageRequest,
)
from cg.server.dto.delivery_message.delivery_message_request import DeliveryMessageRequest
from cg.server.dto.delivery_message.delivery_message_response import (
DeliveryMessageOrderResponse,
DeliveryMessageResponse,
Expand Down Expand Up @@ -54,5 +52,5 @@ def _get_validated_analyses(
def _get_delivery_message(self, case_ids: set[str]) -> str:
cases: list[Case] = self.store.get_cases_by_internal_ids(case_ids)
validate_cases(cases=cases, case_ids=case_ids)
message: str = get_message(cases)
message: str = get_message(cases=cases, store=self.store)
return message
24 changes: 19 additions & 5 deletions cg/services/delivery_message/messages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
from cg.services.delivery_message.messages.analysis_scout_message import AnalysisScoutMessage
from cg.services.delivery_message.messages.analysis_scout_message import (
AnalysisScoutMessage,
)
from cg.services.delivery_message.messages.covid_message import CovidMessage
from cg.services.delivery_message.messages.fastq_message import FastqMessage
from cg.services.delivery_message.messages.fastq_scout_message import FastqScoutMessage
from cg.services.delivery_message.messages.fastq_analysis_scout_message import (
FastqAnalysisScoutMessage,
)
from cg.services.delivery_message.messages.microsalt_mwr_message import MicrosaltMwrMessage
from cg.services.delivery_message.messages.microsalt_mwx_message import MicrosaltMwxMessage
from cg.services.delivery_message.messages.fastq_message import FastqMessage
from cg.services.delivery_message.messages.fastq_scout_message import FastqScoutMessage
from cg.services.delivery_message.messages.microsalt_mwr_message import (
MicrosaltMwrMessage,
)
from cg.services.delivery_message.messages.microsalt_mwx_message import (
MicrosaltMwxMessage,
)
from cg.services.delivery_message.messages.scout_message import ScoutMessage
from cg.services.delivery_message.messages.statina_message import StatinaMessage
from cg.services.delivery_message.messages.rna_delivery_message import (
RNAScoutStrategy,
RNAFastqStrategy,
RNAAnalysisStrategy,
RNAFastqAnalysisStrategy,
RNAUploadMessageStrategy,
RNADeliveryMessage,
)
81 changes: 81 additions & 0 deletions cg/services/delivery_message/messages/rna_delivery_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from abc import abstractmethod, ABC

from cg.services.delivery_message.messages.utils import (
get_scout_links_row_separated,
get_caesar_delivery_path,
)
from cg.store.models import Case


class RNAUploadMessageStrategy(ABC):
"""Abstract base class for delivery message strategies."""

@abstractmethod
def get_file_upload_message(self, delivery_path: str) -> str:
"""Generate the file upload message part."""
pass


class RNAAnalysisStrategy(RNAUploadMessageStrategy):
def get_file_upload_message(self, delivery_path: str) -> str:
return (
f"The analysis files are currently being uploaded to your inbox on Caesar:\n\n"
f"{delivery_path}"
)


class RNAFastqAnalysisStrategy(RNAUploadMessageStrategy):
def get_file_upload_message(self, delivery_path: str) -> str:
return (
f"The fastq and analysis files are currently being uploaded to your inbox on Caesar:\n\n"
f"{delivery_path}"
)


class RNAFastqStrategy(RNAUploadMessageStrategy):
def get_file_upload_message(self, delivery_path: str) -> str:
return (
f"The fastq files are currently being uploaded to your inbox on Caesar:\n\n"
f"{delivery_path}"
)


class RNAScoutStrategy(RNAUploadMessageStrategy):
def get_file_upload_message(self, delivery_path: str) -> str:
return "" # No file upload message needed for this case.


class RNADeliveryMessage:
def __init__(self, store, strategy: RNAUploadMessageStrategy):
self.store = store
self.strategy = strategy

def create_message(self, cases: list[Case]) -> str:
if len(cases) == 1:
return self._get_case_message(cases[0])
return self._get_cases_message(cases)

def _get_case_message(self, case: Case) -> str:
related_uploaded_dna_cases = self.store.get_uploaded_related_dna_cases(rna_case=case)
scout_links = get_scout_links_row_separated(cases=related_uploaded_dna_cases)
delivery_path = get_caesar_delivery_path(case)
file_upload_message = self.strategy.get_file_upload_message(delivery_path)
return (
f"Hello,\n\n"
f"The analysis for case {case.name} has been uploaded to the corresponding DNA case(s) on Scout at:\n\n"
f"{scout_links}\n\n"
f"{file_upload_message}"
)

def _get_cases_message(self, cases: list[Case]) -> str:
message = "Hello,\n\n"
for case in cases:
related_uploaded_dna_cases = self.store.get_uploaded_related_dna_cases(rna_case=case)
scout_links = get_scout_links_row_separated(cases=related_uploaded_dna_cases)
message += (
f"The analysis for case {case.name} has been uploaded to the corresponding DNA case(s) on Scout at:\n\n"
f"{scout_links}\n\n"
)
delivery_path = get_caesar_delivery_path(cases[0])
file_upload_message = self.strategy.get_file_upload_message(delivery_path)
return message + file_upload_message
5 changes: 5 additions & 0 deletions cg/services/delivery_message/messages/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ def get_scout_link(case: Case) -> str:
return f"https://scout.scilifelab.se/{customer_id}/{case_name}"


def get_scout_links_row_separated(cases: list[Case]) -> str:
scout_links: list[str] = [get_scout_link(case) for case in cases]
return "\n".join(scout_links)


def get_pangolin_delivery_path(case: Case) -> str:
customer_id: str = case.customer.internal_id
return f"/home/{customer_id}/inbox/wwLab_automatisk_hamtning"
Expand Down
80 changes: 50 additions & 30 deletions cg/services/delivery_message/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
from cg.constants.constants import DataDelivery, MicrosaltAppTags, Workflow
from cg.exc import (
CaseNotFoundError,
DeliveryMessageNotSupportedError,
OrderMismatchError,
)
from cg.exc import CaseNotFoundError, OrderMismatchError
from cg.services.delivery_message.messages import (
AnalysisScoutMessage,
CovidMessage,
Expand All @@ -17,47 +13,76 @@
from cg.services.delivery_message.messages.analysis_message import AnalysisMessage
from cg.services.delivery_message.messages.bam_message import BamMessage
from cg.services.delivery_message.messages.delivery_message import DeliveryMessage
from cg.services.delivery_message.messages.fastq_analysis_message import (
FastqAnalysisMessage,
)
from cg.services.delivery_message.messages.microsalt_mwx_message import (
MicrosaltMwxMessage,
from cg.services.delivery_message.messages.fastq_analysis_message import FastqAnalysisMessage
from cg.services.delivery_message.messages.microsalt_mwx_message import MicrosaltMwxMessage
from cg.services.delivery_message.messages.rna_delivery_message import (
RNAScoutStrategy,
RNAFastqStrategy,
RNAAnalysisStrategy,
RNAFastqAnalysisStrategy,
RNAUploadMessageStrategy,
RNADeliveryMessage,
)
from cg.store.models import Case, Sample
from cg.store.store import Store

MESSAGE_MAP = {
DataDelivery.ANALYSIS_FILES: AnalysisMessage,
DataDelivery.FASTQ: FastqMessage,
DataDelivery.SCOUT: ScoutMessage,
DataDelivery.FASTQ_SCOUT: FastqScoutMessage,
DataDelivery.FASTQ_ANALYSIS: FastqAnalysisMessage,
DataDelivery.ANALYSIS_SCOUT: AnalysisScoutMessage,
DataDelivery.FASTQ_ANALYSIS_SCOUT: FastqAnalysisScoutMessage,
DataDelivery.STATINA: StatinaMessage,
DataDelivery.BAM: BamMessage,
}


def get_message(cases: list[Case]) -> str:
message_strategy: DeliveryMessage = get_message_strategy(cases[0])
RNA_STRATEGY_MAP: dict[DataDelivery, type[RNAUploadMessageStrategy]] = {
# Only returns a message strategy if there is a scout delivery for the case.
DataDelivery.SCOUT: RNAScoutStrategy,
DataDelivery.FASTQ_SCOUT: RNAFastqStrategy,
DataDelivery.ANALYSIS_SCOUT: RNAAnalysisStrategy,
DataDelivery.FASTQ_ANALYSIS_SCOUT: RNAFastqAnalysisStrategy,
}


def get_message(cases: list[Case], store: Store) -> str:
message_strategy: DeliveryMessage = get_message_strategy(cases[0], store)
return message_strategy.create_message(cases)


def get_message_strategy(case: Case) -> DeliveryMessage:
def get_message_strategy(case: Case, store: Store) -> DeliveryMessage:
if case.data_analysis == Workflow.MICROSALT:
return get_microsalt_message_strategy(case)

if case.data_analysis == Workflow.MUTANT:
return CovidMessage()

if case.data_analysis == Workflow.MIP_RNA:
return get_rna_message_strategy_from_data_delivery(case=case, store=store)

message_strategy: DeliveryMessage = get_message_strategy_from_data_delivery(case)
return message_strategy


def get_message_strategy_from_data_delivery(case: Case) -> DeliveryMessage:
message_strategy: DeliveryMessage = message_map[case.data_delivery]()
message_strategy: DeliveryMessage = MESSAGE_MAP[case.data_delivery]()
return message_strategy


message_map = {
DataDelivery.ANALYSIS_FILES: AnalysisMessage,
DataDelivery.FASTQ: FastqMessage,
DataDelivery.SCOUT: ScoutMessage,
DataDelivery.FASTQ_SCOUT: FastqScoutMessage,
DataDelivery.FASTQ_ANALYSIS: FastqAnalysisMessage,
DataDelivery.ANALYSIS_SCOUT: AnalysisScoutMessage,
DataDelivery.FASTQ_ANALYSIS_SCOUT: FastqAnalysisScoutMessage,
DataDelivery.STATINA: StatinaMessage,
DataDelivery.BAM: BamMessage,
}
def get_rna_message_strategy_from_data_delivery(
case: Case, store: Store
) -> DeliveryMessage | RNADeliveryMessage:
"""Get the RNA delivery message strategy based on the data delivery type.
If a scout delivery is required it will use the RNADeliveryMessage class that links RNA to DNA cases.
Otherwise it used the conventional delivery message strategy.
"""
message_strategy = RNA_STRATEGY_MAP[case.data_delivery]
if message_strategy:
return RNADeliveryMessage(store=store, strategy=message_strategy())
return MESSAGE_MAP[case.data_delivery]()


def get_microsalt_message_strategy(case: Case) -> DeliveryMessage:
Expand Down Expand Up @@ -100,11 +125,6 @@ def validate_cases(cases: list[Case], case_ids: list[str]) -> None:
raise CaseNotFoundError("Internal id not found in the database")
if not is_matching_order(cases):
raise OrderMismatchError("Cases do not belong to the same order")
cases_with_mip_rna: list[Case] = [
case for case in cases if case.data_analysis == Workflow.MIP_RNA
]
if cases_with_mip_rna:
raise DeliveryMessageNotSupportedError("Workflow is not supported.")


def is_matching_order(cases: list[Case]) -> bool:
Expand Down
Loading

0 comments on commit 5aba7cb

Please sign in to comment.