diff --git a/bioflow_kernel/stage_image_map.json b/bioflow_kernel/stage_image_map.json index 330b63d..dbebb5f 100644 --- a/bioflow_kernel/stage_image_map.json +++ b/bioflow_kernel/stage_image_map.json @@ -76,132 +76,784 @@ "wgs_qc": { "image": "bioflow-wgs", "module": "cli.run_wgs_qc", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--wgs-samples", + "param:wgs_samples" + ] + ] }, "wgs_index": { "image": "bioflow-wgs", "module": "cli.run_wgs_index", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--reference-fasta", + "param:reference_fasta" + ] + ] }, "wgs_align": { "image": "bioflow-wgs", "module": "cli.run_wgs_align", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--qc-manifest", + "prev_manifest:wgs_qc" + ], + [ + "--index-manifest", + "prev_manifest:wgs_index" + ] + ] }, "wgs_markdup": { "image": "bioflow-wgs", "module": "cli.run_wgs_markdup", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--align-manifest", + "prev_manifest:wgs_align" + ], + [ + "--gatk", + "tool:gatk" + ], + [ + "--samtools", + "tool:samtools" + ], + [ + "--max-jobs", + "param:max_jobs" + ], + [ + "--remove-duplicates", + "param:remove_duplicates" + ] + ] }, "wgs_bam_qc": { "image": "bioflow-wgs", "module": "cli.run_wgs_bam_qc", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--markdup-manifest", + "prev_manifest:wgs_markdup" + ], + [ + "--index-manifest", + "prev_manifest:wgs_index" + ], + [ + "--samtools", + "tool:samtools" + ], + [ + "--threads", + "param:threads" + ], + [ + "--max-jobs", + "param:max_jobs" + ] + ] }, "wgs_haplotype_calling": { "image": "bioflow-wgs", "module": "cli.run_wgs_haplotype_calling", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--markdup-manifest", + "prev_manifest:wgs_markdup" + ], + [ + "--bam-qc-manifest", + "prev_manifest:wgs_bam_qc" + ], + [ + "--index-manifest", + "prev_manifest:wgs_index" + ], + [ + "--gatk", + "tool:gatk" + ], + [ + "--native-pair-hmm-threads", + "param:native_pair_hmm_threads" + ], + [ + "--max-jobs", + "param:max_jobs" + ] + ] }, "wgs_gvcf_combine": { "image": "bioflow-wgs", "module": "cli.run_wgs_gvcf_combine", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--haplotype-manifest", + "prev_manifest:wgs_haplotype_calling" + ], + [ + "--index-manifest", + "prev_manifest:wgs_index" + ] + ] }, "wgs_joint_genotyping": { "image": "bioflow-wgs", "module": "cli.run_wgs_joint_genotyping", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--combine-manifest", + "prev_manifest:wgs_gvcf_combine" + ], + [ + "--index-manifest", + "prev_manifest:wgs_index" + ] + ] }, "wgs_variant_select": { "image": "bioflow-wgs", "module": "cli.run_wgs_variant_select", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--joint-manifest", + "prev_manifest:wgs_joint_genotyping" + ] + ] }, "wgs_variant_filter": { "image": "bioflow-wgs", "module": "cli.run_wgs_variant_filter", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--select-manifest", + "prev_manifest:wgs_variant_select" + ] + ] }, "wgs_impute": { "image": "bioflow-wgs", "module": "cli.run_wgs_impute", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--filter-manifest", + "prev_manifest:wgs_variant_filter" + ], + [ + "--beagle-jar", + "param:beagle_jar" + ] + ] }, "wgs_maf_missing_filter": { "image": "bioflow-wgs", "module": "cli.run_wgs_maf_missing_filter", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--impute-manifest", + "prev_manifest:wgs_impute" + ] + ] }, "pop_pca": { "image": "bioflow-wgs", "module": "cli.run_pop_pca", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "pop_phylogeny": { "image": "bioflow-wgs", "module": "cli.run_pop_phylogeny", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "pop_structure": { "image": "bioflow-wgs", "module": "cli.run_pop_structure", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "pop_ld_decay": { "image": "bioflow-wgs", "module": "cli.run_pop_ld_decay", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "pop_snp_density": { "image": "bioflow-wgs", "module": "cli.run_pop_snp_density", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "gwas_fmt": { "image": "bioflow-wgs", "module": "cli.run_gwas_tassel", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "gwas_kinship": { "image": "bioflow-wgs", "module": "cli.run_gwas_kinship", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ] + ] }, "gwas_trait": { "image": "bioflow-wgs", "module": "cli.run_gwas_trait", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--phenotype", + "param:phenotype" + ], + [ + "--sample-column", + "param:sample_column" + ], + [ + "--trait-column", + "param:trait_column" + ], + [ + "--trait-name", + "param:trait_name" + ] + ] }, "gwas_cov": { "image": "bioflow-wgs", "module": "cli.run_gwas_covariate", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--q", + "param:q" + ], + [ + "--fam", + "param:fam" + ], + [ + "--k", + "param:k" + ] + ] }, "gwas_mlm": { "image": "bioflow-wgs", "module": "cli.run_gwas_tassel_mlm", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ], + [ + "--kinship", + "param:kinship" + ], + [ + "--trait", + "param:trait" + ], + [ + "--covariate", + "param:covariate" + ], + [ + "--genotype-scope-id", + "param:genotype_scope_id" + ] + ] }, "gwas_plot": { "image": "bioflow-wgs", "module": "cli.run_gwas_plot", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--mlm-pvalues", + "param:mlm_pvalues" + ], + [ + "--cmlm-pvalues", + "param:cmlm_pvalues" + ], + [ + "--genotype-scope-id", + "param:genotype_scope_id" + ], + [ + "--phenotype-scope-id", + "param:phenotype_scope_id" + ], + [ + "--covariate-scope-id", + "param:covariate_scope_id" + ] + ] }, "gwas_emmax": { "image": "bioflow-wgs", "module": "cli.run_gwas_emmax", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ], + [ + "--trait", + "param:trait" + ], + [ + "--vcf-manifest", + "param:vcf_manifest" + ], + [ + "--trait-manifest", + "param:trait_manifest" + ], + [ + "--genotype-scope-id", + "param:genotype_scope_id" + ] + ] }, "gwas_emplot": { "image": "bioflow-wgs", "module": "cli.run_gwas_emmax_plot", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--emmax-pvalues", + "param:emmax_pvalues" + ], + [ + "--emmax-manifest", + "param:emmax_manifest" + ], + [ + "--genotype-scope-id", + "param:genotype_scope_id" + ], + [ + "--phenotype-scope-id", + "param:phenotype_scope_id" + ], + [ + "--source-model-scope-id", + "param:source_model_scope_id" + ] + ] }, "gwas_glm": { "image": "bioflow-wgs", "module": "cli.run_gwas_tassel_glm", - "executable": true + "executable": true, + "cli_contract": [ + [ + "--workspace", + "workspace" + ], + [ + "--job-id", + "job_id" + ], + [ + "--prefix", + "prefix" + ], + [ + "--vcf", + "param:vcf" + ], + [ + "--trait", + "param:trait" + ], + [ + "--covariate", + "param:covariate" + ], + [ + "--vcf-manifest", + "param:vcf_manifest" + ], + [ + "--trait-manifest", + "param:trait_manifest" + ], + [ + "--covariate-manifest", + "param:covariate_manifest" + ], + [ + "--genotype-scope-id", + "param:genotype_scope_id" + ], + [ + "--phenotype-scope-id", + "param:phenotype_scope_id" + ], + [ + "--covariate-scope-id", + "param:covariate_scope_id" + ] + ] }, "genome_prepare": { "image": "bioflow-gf", diff --git a/bioflow_service/app/services/bioflow/catalog.py b/bioflow_service/app/services/bioflow/catalog.py index 3458104..c184c15 100644 --- a/bioflow_service/app/services/bioflow/catalog.py +++ b/bioflow_service/app/services/bioflow/catalog.py @@ -395,6 +395,46 @@ _RNASEQ_REPORT_PARAMS = [ "help": "分析前缀"}, ] +# wgs/pop/gwas 参数 schema(契约 param: 键 → 确认表单字段) +_CPU4 = {"name": "cpu", "type": "choice", "choices": ["4", "8", "16", "32"], + "default": "4", "source": "runtime", "help": "容器 CPU 数"} + + +def _u(name: str, help_: str) -> dict[str, str]: + return {"name": name, "type": "string", "default": "", "source": "user", "help": help_} + + +_WGS_COMMON = [_u("prefix", "分析前缀"), _CPU4] +_WGS_QC_PARAMS = [*_WGS_COMMON, _u("wgs_samples", "WGS 样本清单(逗号分隔或路径)")] +_WGS_INDEX_PARAMS = [*_WGS_COMMON, _u("reference_fasta", "参考基因组 FASTA 路径(容器内可访问)")] +_WGS_ALIGN_PARAMS = [*_WGS_COMMON] +_WGS_MARKDUP_PARAMS = [*_WGS_COMMON, + {"name": "max_jobs", "type": "string", "default": "8", "source": "user", + "help": "并行任务上限"}, + {"name": "remove_duplicates", "type": "choice", "choices": ["true"], + "default": "true", "source": "user", "help": "去重复(固定 true)"}] +_WGS_BAM_QC_PARAMS = [*_WGS_COMMON, + {"name": "threads", "type": "string", "default": "8", "source": "user", + "help": "线程数"}, + {"name": "max_jobs", "type": "string", "default": "8", "source": "user", + "help": "并行任务上限"}] +_WGS_HAPLOTYPE_PARAMS = [*_WGS_COMMON, + {"name": "native_pair_hmm_threads", "type": "string", "default": "4", + "source": "user", "help": "GATK PairHMM 线程"}, + {"name": "max_jobs", "type": "string", "default": "8", "source": "user", + "help": "并行任务上限"}] +_WGS_IMPUTE_PARAMS = [*_WGS_COMMON, _u("beagle_jar", "Beagle JAR 路径(容器内可访问)")] +_POP_COMMON = [*_WGS_COMMON, _u("vcf", "VCF 文件路径(容器内可访问)")] +_GWAS_TRAIT_PARAMS = [*_WGS_COMMON, + _u("phenotype", "表型数据文件路径"), + _u("sample_column", "样本列名"), + _u("trait_column", "性状列名"), + _u("trait_name", "性状名")] +_GWAS_COV_PARAMS = [*_WGS_COMMON, + _u("q", "Q 矩阵路径"), + _u("fam", "FAM 文件路径"), + _u("k", "主成分数 K")] + # 通用输入/输出描述(契约文档摘要;契约文件为权威,这里给目录层最小描述) _IDENTIFY_INPUTS = [ {"name": "protein_fasta", "type": "fasta", "required": True, @@ -468,6 +508,32 @@ _STAGE_PARAM_SCHEMAS: dict[str, tuple[list[dict[str, Any]], list[dict[str, Any]] {"name": "prefix", "type": "string", "default": "", "source": "contract", "help": "分析前缀"}, ], _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), "rnaseq_qc": (_RNASEQ_QC_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_qc": (_WGS_QC_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_index": (_WGS_INDEX_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_align": (_WGS_ALIGN_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_markdup": (_WGS_MARKDUP_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_bam_qc": (_WGS_BAM_QC_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_haplotype_calling": (_WGS_HAPLOTYPE_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_gvcf_combine": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_joint_genotyping": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_variant_select": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_variant_filter": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_impute": (_WGS_IMPUTE_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "wgs_maf_missing_filter": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "pop_pca": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "pop_phylogeny": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "pop_structure": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "pop_ld_decay": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "pop_snp_density": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_fmt": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_kinship": (_POP_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_trait": (_GWAS_TRAIT_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_cov": (_GWAS_COV_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_mlm": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_plot": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_emmax": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_emplot": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), + "gwas_glm": (_WGS_COMMON, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), "rnaseq_align": (_RNASEQ_ALIGN_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), "rnaseq_quant": (_RNASEQ_QUANT_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), "rnaseq_sample_qc": (_RNASEQ_SAMPLE_QC_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), diff --git a/bioflow_service/app/services/bioflow/kernel.py b/bioflow_service/app/services/bioflow/kernel.py index a8d8cd9..b80278b 100644 --- a/bioflow_service/app/services/bioflow/kernel.py +++ b/bioflow_service/app/services/bioflow/kernel.py @@ -200,8 +200,11 @@ class Kernel: 故 -o 必须指向 workspace/final,manifest 也落此处与 runner 检索对齐) - samples_yaml:/job/input/samples.yaml(宿主侧不存在则跳过该 flag, 交由 CLI 报缺失,错误信息比服务端更可读) + - prev_manifest::上游阶段 manifest 路径 + /job/workspace/final/{prefix}.{stage}.manifest.json(缺失跳过) - prefix/job_id:从 params 取值,缺失跳过 - param::从 params[name] 取值,缺失跳过(用户经确认表单提供) + - tool::容器内工具命令(镜像 PATH 内裸命令,如 gatk/samtools) """ args: list[str] = [] for flag, source in contract: @@ -211,6 +214,15 @@ class Kernel: args += [flag, self.cfg.container_workspace_dir] elif source == "output_final": args += [flag, f"{self.cfg.container_workspace_dir}/final"] + elif str(source).startswith("prev_manifest:"): + stage_name = str(source).split(":", 1)[1] + value = str(params.get("prefix") or "").strip() + if value: + args += [flag, + f"{self.cfg.container_workspace_dir}/final/{value}.{stage_name}.manifest.json"] + elif str(source).startswith("tool:"): + name = str(source).split(":", 1)[1] + args += [flag, name] elif source == "samples_yaml": if (Path(job_input_dir) / "samples.yaml").is_file(): args += [flag, f"{self.cfg.container_input_dir}/samples.yaml"] diff --git a/bioflow_service/tests/test_kernel.py b/bioflow_service/tests/test_kernel.py index 792f1f7..ec352ac 100644 --- a/bioflow_service/tests/test_kernel.py +++ b/bioflow_service/tests/test_kernel.py @@ -127,6 +127,50 @@ def test_build_command_cli_contract(tmp_path): assert "--input-dir" in cmd4 and "--workspace" in cmd4 +def test_build_command_wgs_contract(tmp_path): + """wgs 系契约:--workspace/--job-id/--prefix + prev_manifest 路径注入。""" + from app.services.bioflow.kernel import Kernel, KernelConfig, ImageMap + cfg = KernelConfig(host_uid=1234, data_root=str(tmp_path / "data")) + img = ImageMap( + images={"wgs": {"docker_tag": "bioflow-wgs"}}, + stages={ + "wgs_qc": {"image": "wgs", "module": "cli.run_wgs_qc", + "cli_contract": [["--workspace", "workspace"], + ["--job-id", "job_id"], + ["--prefix", "prefix"], + ["--wgs-samples", "param:wgs_samples"]]}, + "wgs_align": {"image": "wgs", "module": "cli.run_wgs_align", + "cli_contract": [["--workspace", "workspace"], + ["--job-id", "job_id"], + ["--prefix", "prefix"], + ["--qc-manifest", "prev_manifest:wgs_qc"], + ["--index-manifest", "prev_manifest:wgs_index"], + ["--gatk", "tool:gatk"]]}, + }, + ) + k = Kernel(img, cfg) + # wgs_qc:公共三元组 + param 注入 + cmd = k.build_command("wgs_qc", tmp_path / "in", tmp_path / "ws", + params={"job_id": "j1", "prefix": "rice", + "wgs_samples": "s1,s2"}) + assert "--workspace" in cmd and "/job/workspace" in cmd + assert "--job-id" in cmd and "j1" in cmd + assert "--prefix" in cmd and "rice" in cmd + assert "--wgs-samples" in cmd and "s1,s2" in cmd + assert "--input-dir" not in cmd + # wgs_align:prev_manifest 注入(prefix 有值时) + cmd2 = k.build_command("wgs_align", tmp_path / "in", tmp_path / "ws", + params={"job_id": "j1", "prefix": "rice"}) + assert "--qc-manifest" in cmd2 + assert "/job/workspace/final/rice.wgs_qc.manifest.json" in cmd2 + assert "/job/workspace/final/rice.wgs_index.manifest.json" in cmd2 + assert "--gatk" in cmd2 and "gatk" in cmd2 # tool: 注入裸命令 + # prefix 缺失时 prev_manifest 跳过 + cmd3 = k.build_command("wgs_align", tmp_path / "in", tmp_path / "ws", + params={"job_id": "j1"}) + assert "--qc-manifest" not in cmd3 + + def test_build_command_params(kernel, tmp_path): cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws", {"prefix": "Ft.SPL", "required_pfams": "PF03110",