Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
e31d232
dev(Balsamic Start) Add BalsamicConfigurator (#4516)
Vince-janv Jul 24, 2025
2b01005
dev(Balsamic start) Add Fastq linking (#4518)
Vince-janv Jul 24, 2025
b1e4d3f
dev(Balsamic Start) Add Balsamic launch command (#4525)
Vince-janv Jul 25, 2025
b7d2ea9
dev(Balsamic Start) Add tracker (#4526)
Vince-janv Jul 28, 2025
7b5db74
dev(Balsamic Start) Add Balsamic to AnalysisStarterFactory (#4531)
Vince-janv Jul 29, 2025
52dde7f
dev(balsamic start) Add dev commands (#4533)
Vince-janv Jul 29, 2025
0919ca2
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Jul 31, 2025
3f70ab7
re-run black
diitaz93 Jul 31, 2025
2392d7a
dev(Balsamic) Address bugs in CLI (#4535)
Vince-janv Sep 30, 2025
c8ff596
Merge master, and conform to new CLI-dumping method
Vince-janv Sep 30, 2025
ff3b020
Add config changes to integration test file
Vince-janv Sep 30, 2025
ac8e70a
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Oct 6, 2025
c651b6e
fix failing tests in conflict merge
diitaz93 Oct 6, 2025
76ae954
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Oct 13, 2025
b19ab43
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Oct 13, 2025
d2eea97
Add loqusdb dump files (#4633)
diitaz93 Oct 20, 2025
9ee163c
Merge branch 'master' into dev-new-start-balsamic
islean Oct 20, 2025
c54692d
Fix test
islean Oct 20, 2025
79158d0
Fix integration test
islean Oct 20, 2025
e32946b
address type hint comments
diitaz93 Oct 20, 2025
2bb8afd
Start working in configurator factory
islean Oct 21, 2025
e722feb
WIP
mogmi Oct 21, 2025
13f080c
Test implemented
RasmusBurge-CG Oct 21, 2025
ff3ad79
WIP - tests analysis starter factory
diitaz93 Oct 21, 2025
0b935d1
Add Balsamic to analysis starter test
islean Oct 21, 2025
9c2a93f
WIP
mogmi Oct 21, 2025
f7e78c7
WIP
RasmusBurge-CG Oct 21, 2025
e647e28
WIP - clearing to-dos
diitaz93 Oct 21, 2025
72ee086
Start on start test
islean Oct 21, 2025
2eb5391
WIP
mogmi Oct 21, 2025
0be19ad
Green state
RasmusBurge-CG Oct 21, 2025
c1d1696
WIP - test flags
diitaz93 Oct 21, 2025
853fc8a
Start on start test
islean Oct 21, 2025
5b0a8c8
Test cases with male and unknown samples (#4659)
mogmi Oct 21, 2025
2f4af56
Add dev CLI tests
diitaz93 Oct 21, 2025
8404db6
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Oct 21, 2025
e70c8fb
Add a TODO
islean Oct 22, 2025
4d2f1bb
WIP
mogmi Oct 22, 2025
1b9283e
WIP
RasmusBurge-CG Oct 22, 2025
07883d2
WIP workflow profile
diitaz93 Oct 22, 2025
171c0f7
Fix workflow profile
islean Oct 22, 2025
fdd0b5e
WIP
mogmi Oct 22, 2025
72a0ac0
WIP
RasmusBurge-CG Oct 22, 2025
5a6269f
added head-job profile
diitaz93 Oct 22, 2025
bba93ed
Remove benchmark and mail-user
RasmusBurge-CG Oct 22, 2025
816f8b0
Test balsamic start command (#4664)
mogmi Oct 23, 2025
5ecd2fd
Add loqusdb dump file second attempt (#4663)
mogmi Oct 24, 2025
ec7456b
Merge branch 'master' into dev-new-start-balsamic
islean Oct 24, 2025
6714249
Change expected commands in integration tests to match Balsamic v18 (…
mogmi Oct 24, 2025
4f61c10
Adress comments
RasmusBurge-CG Oct 24, 2025
0260087
Address comments
RasmusBurge-CG Oct 24, 2025
4713edd
Address comments
RasmusBurge-CG Oct 24, 2025
cabd41f
Address comments
RasmusBurge-CG Oct 24, 2025
90f054f
Address comments
RasmusBurge-CG Oct 24, 2025
e6e1f72
Address comments
RasmusBurge-CG Oct 24, 2025
527d5a1
Address comments
RasmusBurge-CG Oct 24, 2025
b4eb6b5
Update tests/cli/workflow/balsamic/test_cli_balsamic.py
RasmusBurge-CG Oct 24, 2025
905f96c
Update tests/cli/workflow/balsamic/test_cli_balsamic.py
RasmusBurge-CG Oct 24, 2025
7a10d89
Update tests/cli/workflow/balsamic/test_cli_balsamic.py
RasmusBurge-CG Oct 24, 2025
53e863d
Address comments
RasmusBurge-CG Oct 24, 2025
9d41e59
Merge remote-tracking branch 'origin/dev-new-start-balsamic' into dev…
RasmusBurge-CG Oct 24, 2025
2cae80e
Pass fastq path from fastq handler to config file creator
diitaz93 Oct 27, 2025
6c8183c
Refactor fastq path owner
islean Oct 27, 2025
fe926d5
Rename method
islean Oct 27, 2025
f05b02a
Activate the configured conda environment when starting the analysis …
mogmi Nov 5, 2025
9703713
Fix real store test balsamic (#4673)
diitaz93 Nov 6, 2025
d8806f2
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Nov 7, 2025
31726d2
Balsamic dev commands integration test (#4693)
mogmi Nov 10, 2025
e5a8221
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Nov 10, 2025
c8ab97d
order fixtures
diitaz93 Nov 11, 2025
632d8d8
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Nov 11, 2025
402226f
Patch balsamic config2 (#4707)
diitaz93 Nov 13, 2025
267676a
Merge branch 'master' into dev-new-start-balsamic
diitaz93 Nov 13, 2025
0d40fa5
WIP - test for failing balsamic command
diitaz93 Nov 13, 2025
4e477f6
Finished test and implementation
islean Nov 13, 2025
3f73647
Log balsamic error from config case command
mogmi Nov 13, 2025
7f4f1b9
Fix exome flag order
islean Nov 13, 2025
05c11f9
Use named function to test check=False and logging Balsamic error output
mogmi Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion cg/cli/workflow/balsamic/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import logging
import traceback
from typing import cast


import rich_click as click
from pydantic.v1 import ValidationError
Expand All @@ -20,12 +22,16 @@
)
from cg.cli.workflow.commands import ARGUMENT_CASE_ID, link, resolve_compression
from cg.cli.workflow.utils import validate_force_store_option
from cg.constants import EXIT_FAIL, EXIT_SUCCESS
from cg.constants import EXIT_FAIL, EXIT_SUCCESS, Workflow
from cg.constants.cli_options import COMMENT, DRY_RUN, FORCE, LIMIT
from cg.exc import AnalysisNotReadyError, CgError
from cg.meta.workflow.analysis import AnalysisAPI
from cg.meta.workflow.balsamic import BalsamicAnalysisAPI
from cg.models.cg_config import CGConfig
from cg.services.analysis_starter.configurator.implementations.balsamic import BalsamicConfigurator
from cg.services.analysis_starter.factories.configurator_factory import ConfiguratorFactory
from cg.services.analysis_starter.factories.starter_factory import AnalysisStarterFactory
from cg.services.analysis_starter.service import AnalysisStarter
from cg.store.models import Case
from cg.store.store import Store

Expand Down Expand Up @@ -299,3 +305,54 @@ def store_available(context: click.Context, dry_run: bool) -> None:
was_successful = False
if not was_successful:
raise click.Abort()


@balsamic.command()
@OPTION_PANEL_BED
@ARGUMENT_CASE_ID
@click.pass_obj
def dev_config_case(cg_config: CGConfig, case_id: str, panel_bed: str | None):
"""Configure a Balsamic case so that it is ready to be run."""
factory = ConfiguratorFactory(cg_config)
configurator = cast(BalsamicConfigurator, factory.get_configurator(Workflow.BALSAMIC))
configurator.configure(case_id=case_id, panel_bed=panel_bed)


@balsamic.command("dev-run")
@OPTION_WORKFLOW_PROFILE
@ARGUMENT_CASE_ID
@click.pass_obj
def dev_run(cg_config: CGConfig, case_id: str, workflow_profile: click.Path | None):
"""Run a preconfigured Balsamic case."""
factory = AnalysisStarterFactory(cg_config)
analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.BALSAMIC)
analysis_starter.run(case_id=case_id, workflow_profile=workflow_profile)


@balsamic.command("dev-start")
@OPTION_PANEL_BED
@OPTION_WORKFLOW_PROFILE
@ARGUMENT_CASE_ID
@click.pass_obj
def dev_start(
cg_config: CGConfig,
case_id: str,
panel_bed: str | None,
workflow_profile: click.Path | None,
):
"""Start a Balsamic case. Configures the case if needed."""
factory = AnalysisStarterFactory(cg_config)
analysis_starter: AnalysisStarter = factory.get_analysis_starter_for_workflow(Workflow.BALSAMIC)
analysis_starter.start(case_id=case_id, workflow_profile=workflow_profile, panel_bed=panel_bed)


@balsamic.command("dev-start-available")
@click.pass_obj
def dev_start_available(cg_config: CGConfig):
"""Starts all available raredisease cases."""
LOG.info("Starting Balsamic workflow for all available cases.")
factory = AnalysisStarterFactory(cg_config)
analysis_starter = factory.get_analysis_starter_for_workflow(Workflow.BALSAMIC)
succeeded: bool = analysis_starter.start_available()
if not succeeded:
raise click.Abort
10 changes: 5 additions & 5 deletions cg/constants/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,12 @@ class FileFormat(StrEnum):


class GenomeVersion(StrEnum):
GRCh37: str = "GRCh37"
GRCh38: str = "GRCh38"
T2T_CHM13: str = "T2T-CHM13v2.0"
GRCh37 = "GRCh37"
GRCh38 = "GRCh38"
T2T_CHM13 = "T2T-CHM13v2.0"
CANFAM3 = auto()
HG19: str = "hg19"
HG38: str = "hg38"
HG19 = "hg19"
HG38 = "hg38"


class SampleType(StrEnum):
Expand Down
4 changes: 4 additions & 0 deletions cg/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,5 +334,9 @@ class Chanjo2ResponseError(Chanjo2APIClientError):
"""Exception raised when the response from Chanjo2 API client fails validation."""


class CaseNotConfiguredError(CgError):
"""Exception raised when a case is being run without a configuration."""


class MissingConfigFilesError(CgError):
"""Exception raised when a case is being run with missing configuration files."""
36 changes: 19 additions & 17 deletions cg/meta/workflow/balsamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

from housekeeper.store.models import File, Version
from pydantic import EmailStr
from pydantic.v1 import ValidationError

from cg.constants import Workflow
Expand Down Expand Up @@ -55,23 +56,24 @@ def __init__(
):
super().__init__(workflow=workflow, config=config)
self.account: str = config.balsamic.slurm.account
self.balsamic_cache: str = config.balsamic.balsamic_cache
self.bed_path: str = config.balsamic.bed_path
self.binary_path: str = config.balsamic.binary_path
self.cadd_path: str = config.balsamic.cadd_path
self.conda_binary: str = config.balsamic.conda_binary
self.balsamic_cache: str = str(config.balsamic.balsamic_cache)
self.bed_path: str = str(config.balsamic.bed_path)
self.binary_path: str = str(config.balsamic.binary_path)
self.cadd_path: str = str(config.balsamic.cadd_path)
self.conda_binary: str = str(config.balsamic.conda_binary)
self.conda_env: str = config.balsamic.conda_env
self.genome_interval_path: str = config.balsamic.genome_interval_path
self.gens_coverage_female_path: str = config.balsamic.gens_coverage_female_path
self.gens_coverage_male_path: str = config.balsamic.gens_coverage_male_path
self.gnomad_af5_path: str = config.balsamic.gnomad_af5_path
self.email: EmailStr = config.balsamic.slurm.mail_user
self.genome_interval_path: str = str(config.balsamic.genome_interval_path)
self.gens_coverage_female_path: str = str(config.balsamic.gens_coverage_female_path)
self.gens_coverage_male_path: str = str(config.balsamic.gens_coverage_male_path)
self.gnomad_af5_path: str = str(config.balsamic.gnomad_af5_path)
self.head_job_partition: str = config.balsamic.head_job_partition
self.loqusdb_path: str = config.balsamic.loqusdb_path
self.pon_path: str = config.balsamic.pon_path
self.loqusdb_path: str = str(config.balsamic.loqusdb_path)
self.pon_path: str = str(config.balsamic.pon_path)
self.qos: SlurmQos = config.balsamic.slurm.qos
self.root_dir: str = config.balsamic.root
self.sentieon_licence_path: str = config.balsamic.sentieon_licence_path
self.sentieon_licence_server: str = config.sentieon_licence_server
self.root_dir: str = str(config.balsamic.root)
self.sentieon_licence_path: str = str(config.balsamic.sentieon_licence_path)
self.sentieon_licence_server: str = config.balsamic.sentieon_licence_server
self.swegen_path: str = config.balsamic.swegen_path

@property
Expand Down Expand Up @@ -589,11 +591,11 @@ def config_case(
{
"--analysis-dir": self.root_dir,
"--analysis-workflow": arguments.get("analysis_workflow"),
"--artefact-snv-observations": arguments.get("artefact_somatic_snv"),
"--artefact-sv-observations": arguments.get("loqusdb_wgs_dump_file"),
"--balsamic-cache": self.balsamic_cache,
"--cache-version": cache_version,
"--cadd-annotations": self.cadd_path,
"--artefact-snv-observations": arguments.get("artefact_somatic_snv"),
"--artefact-sv-observations": arguments.get("loqusdb_wgs_dump_file"),
"--cancer-germline-snv-observations": arguments.get("cancer_germline_snv"),
"--cancer-germline-sv-observations": arguments.get("cancer_germline_sv"),
"--cancer-somatic-snv-observations": arguments.get("cancer_somatic_snv"),
Expand All @@ -612,8 +614,8 @@ def config_case(
"--gnomad-min-af5": arguments.get("gnomad_min_af5"),
"--normal-sample-name": arguments.get("normal_sample_name"),
"--panel-bed": arguments.get("panel_bed"),
"--exome": arguments.get("exome"), # MUST be after panel bed
"--pon-cnn": arguments.get("pon_cnn"),
"--exome": arguments.get("exome"),
"--sentieon-install-dir": self.sentieon_licence_path,
"--sentieon-license": self.sentieon_licence_server,
"--soft-filter-normal": arguments.get("soft_filter_normal"),
Expand Down
8 changes: 8 additions & 0 deletions cg/meta/workflow/fastq.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,14 @@ def create_fastq_name(
date: str = date if isinstance(date, str) else date.strftime("%y%m%d")
return f"{lane}_{date}_{flow_cell}_{sample}_{index}_R_{read_direction}{FileExtensions.FASTQ}{FileExtensions.GZIP}"

def get_sample_fastq_destination_dir(self, case: Case, sample: Sample) -> Path:
"""Get fastq paths for a Balsamic case (sample-independent)."""
return self.get_fastq_dir(case_id=case.internal_id)

def get_fastq_dir(self, case_id: str) -> Path:
"""Get fastq directory for a Balsamic case."""
return Path(self.root_dir, case_id, "fastq")


class MipFastqHandler(FastqHandler):
@staticmethod
Expand Down
47 changes: 31 additions & 16 deletions cg/models/cg_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from cg.clients.arnold.api import ArnoldAPIClient
from cg.clients.chanjo2.client import Chanjo2APIClient
from cg.clients.janus.api import JanusAPIClient
from cg.constants.observations import LoqusdbInstance
from cg.constants.observations import BalsamicObservationPanel, LoqusdbInstance
from cg.constants.priority import SlurmQos
from cg.meta.delivery.delivery import DeliveryAPI
from cg.services.analysis_service.analysis_service import AnalysisService
Expand Down Expand Up @@ -178,24 +178,40 @@ class MutaccAutoConfig(CommonAppConfig):
padding: int = 300


class LoqusDBDumpFiles(BaseModel):
artefact_sv: Path # WGS
artefact_snv: Path
cancer_germline_snv: Path
cancer_somatic_snv: Path
cancer_somatic_sv: Path
clinical_snv: Path
clinical_sv: Path
cancer_somatic_snv_panels: dict[BalsamicObservationPanel, Path] # Panel


class BalsamicConfig(CommonAppConfig):
balsamic_cache: str
bed_path: str
binary_path: str
cadd_path: str
conda_binary: str
balsamic_cache: Path
bed_path: Path
binary_path: Path
cadd_path: Path
conda_binary: Path
conda_env: str
genome_interval_path: str
gens_coverage_female_path: str
gens_coverage_male_path: str
gnomad_af5_path: str
genome_interval_path: Path
gens_coverage_female_path: Path
gens_coverage_male_path: Path
gnomad_af5_path: Path
head_job_partition: str
loqusdb_path: str
pon_path: str
root: str
sentieon_licence_path: str
loqusdb_path: Path
loqusdb_dump_files: LoqusDBDumpFiles
panel_of_normals: dict[str, Path] # For TGS and Exome
pon_path: Path
root: Path
sentieon_licence_path: Path
sentieon_licence_server: str
slurm: SlurmConfig
swegen_path: str
swegen_path: Path
swegen_snv: Path
swegen_sv: Path


class MutantConfig(BaseModel):
Expand Down Expand Up @@ -416,7 +432,6 @@ class CGConfig(BaseModel):
max_flowcells: int | None = None
nanopore_data_directory: str
run_instruments: RunInstruments
sentieon_licence_server: str
tower_binary_path: str

# Base APIs that always should exist
Expand Down
Loading
Loading