feat(bioflow): wgs/pop/gwas 26 阶段参数契约(与 rnaseq 同款机制)

- stage_image_map 加 26 个 wgs/pop/gwas 阶段 cli_contract:
  --workspace/--job-id/--prefix 公共三元组 + 上游 manifest 注入
  (prev_manifest:<stage> 解析为 workspace/final/{prefix}.{stage}.manifest.json)
  + param:<name> 用户参数 + tool:<name> 镜像内工具
- kernel _contract_args 扩展 prev_manifest/tool 两种 source
- catalog 补 wgs/pop/gwas 参数 schema(确认表单收集参考基因组/样本清单/
  表型文件/主成分等)
- 339 用例全绿;已部署 176

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 15:33:43 +08:00
co-authored by Claude Fable 5
parent 3b512fffd0
commit dcb810a758
4 changed files with 800 additions and 26 deletions
+678 -26
View File
@@ -76,132 +76,784 @@
"wgs_qc": { "wgs_qc": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_qc", "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": { "wgs_index": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_index", "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": { "wgs_align": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_align", "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": { "wgs_markdup": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_markdup", "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": { "wgs_bam_qc": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_bam_qc", "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": { "wgs_haplotype_calling": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_haplotype_calling", "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": { "wgs_gvcf_combine": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_gvcf_combine", "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": { "wgs_joint_genotyping": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_joint_genotyping", "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": { "wgs_variant_select": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_variant_select", "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": { "wgs_variant_filter": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_variant_filter", "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": { "wgs_impute": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_impute", "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": { "wgs_maf_missing_filter": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_wgs_maf_missing_filter", "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": { "pop_pca": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_pop_pca", "module": "cli.run_pop_pca",
"executable": true "executable": true,
"cli_contract": [
[
"--workspace",
"workspace"
],
[
"--job-id",
"job_id"
],
[
"--prefix",
"prefix"
],
[
"--vcf",
"param:vcf"
]
]
}, },
"pop_phylogeny": { "pop_phylogeny": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_pop_phylogeny", "module": "cli.run_pop_phylogeny",
"executable": true "executable": true,
"cli_contract": [
[
"--workspace",
"workspace"
],
[
"--job-id",
"job_id"
],
[
"--prefix",
"prefix"
],
[
"--vcf",
"param:vcf"
]
]
}, },
"pop_structure": { "pop_structure": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_pop_structure", "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": { "pop_ld_decay": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_pop_ld_decay", "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": { "pop_snp_density": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_pop_snp_density", "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": { "gwas_fmt": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_tassel", "module": "cli.run_gwas_tassel",
"executable": true "executable": true,
"cli_contract": [
[
"--workspace",
"workspace"
],
[
"--job-id",
"job_id"
],
[
"--prefix",
"prefix"
],
[
"--vcf",
"param:vcf"
]
]
}, },
"gwas_kinship": { "gwas_kinship": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_kinship", "module": "cli.run_gwas_kinship",
"executable": true "executable": true,
"cli_contract": [
[
"--workspace",
"workspace"
],
[
"--job-id",
"job_id"
],
[
"--prefix",
"prefix"
],
[
"--vcf",
"param:vcf"
]
]
}, },
"gwas_trait": { "gwas_trait": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_trait", "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": { "gwas_cov": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_covariate", "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": { "gwas_mlm": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_tassel_mlm", "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": { "gwas_plot": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_plot", "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": { "gwas_emmax": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_emmax", "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": { "gwas_emplot": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_emmax_plot", "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": { "gwas_glm": {
"image": "bioflow-wgs", "image": "bioflow-wgs",
"module": "cli.run_gwas_tassel_glm", "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": { "genome_prepare": {
"image": "bioflow-gf", "image": "bioflow-gf",
@@ -395,6 +395,46 @@ _RNASEQ_REPORT_PARAMS = [
"help": "分析前缀"}, "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 = [ _IDENTIFY_INPUTS = [
{"name": "protein_fasta", "type": "fasta", "required": True, {"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": "分析前缀"}, {"name": "prefix", "type": "string", "default": "", "source": "contract", "help": "分析前缀"},
], _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS), ], _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS),
"rnaseq_qc": (_RNASEQ_QC_PARAMS, _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_align": (_RNASEQ_ALIGN_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS),
"rnaseq_quant": (_RNASEQ_QUANT_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), "rnaseq_sample_qc": (_RNASEQ_SAMPLE_QC_PARAMS, _DOWNSTREAM_INPUTS, _DOWNSTREAM_OUTPUTS),
@@ -200,8 +200,11 @@ class Kernel:
故 -o 必须指向 workspace/final,manifest 也落此处与 runner 检索对齐) 故 -o 必须指向 workspace/final,manifest 也落此处与 runner 检索对齐)
- samples_yaml:/job/input/samples.yaml(宿主侧不存在则跳过该 flag, - samples_yaml:/job/input/samples.yaml(宿主侧不存在则跳过该 flag,
交由 CLI 报缺失,错误信息比服务端更可读) 交由 CLI 报缺失,错误信息比服务端更可读)
- prev_manifest:<stage>:上游阶段 manifest 路径
/job/workspace/final/{prefix}.{stage}.manifest.json(缺失跳过)
- prefix/job_id:从 params 取值,缺失跳过 - prefix/job_id:从 params 取值,缺失跳过
- param:<name>:从 params[name] 取值,缺失跳过(用户经确认表单提供) - param:<name>:从 params[name] 取值,缺失跳过(用户经确认表单提供)
- tool:<name>:容器内工具命令(镜像 PATH 内裸命令,如 gatk/samtools)
""" """
args: list[str] = [] args: list[str] = []
for flag, source in contract: for flag, source in contract:
@@ -211,6 +214,15 @@ class Kernel:
args += [flag, self.cfg.container_workspace_dir] args += [flag, self.cfg.container_workspace_dir]
elif source == "output_final": elif source == "output_final":
args += [flag, f"{self.cfg.container_workspace_dir}/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": elif source == "samples_yaml":
if (Path(job_input_dir) / "samples.yaml").is_file(): if (Path(job_input_dir) / "samples.yaml").is_file():
args += [flag, f"{self.cfg.container_input_dir}/samples.yaml"] args += [flag, f"{self.cfg.container_input_dir}/samples.yaml"]
+44
View File
@@ -127,6 +127,50 @@ def test_build_command_cli_contract(tmp_path):
assert "--input-dir" in cmd4 and "--workspace" in cmd4 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:<name> 注入裸命令
# 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): def test_build_command_params(kernel, tmp_path):
cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws", cmd = kernel.build_command("identify", tmp_path / "in", tmp_path / "ws",
{"prefix": "Ft.SPL", "required_pfams": "PF03110", {"prefix": "Ft.SPL", "required_pfams": "PF03110",