diff --git a/modules/nf-core/sativaepang/looplace/environment.yml b/modules/nf-core/sativaepang/looplace/environment.yml new file mode 100644 index 000000000000..715a97b37f38 --- /dev/null +++ b/modules/nf-core/sativaepang/looplace/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::sativa-epang=0.9.3.4 diff --git a/modules/nf-core/sativaepang/looplace/main.nf b/modules/nf-core/sativaepang/looplace/main.nf new file mode 100644 index 000000000000..ecd1faff6885 --- /dev/null +++ b/modules/nf-core/sativaepang/looplace/main.nf @@ -0,0 +1,55 @@ +process SATIVAEPANG_LOOPLACE { + tag "$meta.id" + label 'process_high' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/sativa-epang:0.9.3.4--py314hab16a5f_0' : + 'quay.io/biocontainers/sativa-epang:0.9.3.4--py314hab16a5f_0' }" + + input: + tuple val(meta), path(taskdir) + + output: + tuple val(meta), path(taskdir), emit: taskdir + tuple val("${task.process}"), val('sativaepang'), eval("grep -m1 -oE '[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' \$(command -v sativa-epang)"), topic: versions, emit: versions_sativaepang + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + """ + # Private writable taskdir: loo-place writes a jplace/logs into every fold, which + # would otherwise mutate lootasks' own output in place (nf-core/modules#12799-style + # -resume bug). fold_*/{ref.nwk,ref.fasta,query.fasta} stay symlinks -- at GTDB scale + # these are already near-full-alignment copies per fold, so duplicating them again + # here isn't affordable. + mv "$taskdir" "${taskdir}.orig" + mkdir "$taskdir" + ln -s "\$(readlink -f "${taskdir}.orig/manifest.json")" "$taskdir/manifest.json" + for fold in "${taskdir}.orig"/fold_*; do + d="$taskdir/\$(basename "\$fold")" + mkdir "\$d" + ln -s "\$(readlink -f "\$fold")"/* "\$d/" + done + + sativa-epang \\ + -stage loo-place \\ + -taskdir $taskdir \\ + -T ${task.cpus} \\ + ${args} + """ + + stub: + """ + mv "$taskdir" "${taskdir}.orig" + mkdir "$taskdir" + ln -s "\$(readlink -f "${taskdir}.orig/manifest.json")" "$taskdir/manifest.json" + for fold in "${taskdir}.orig"/fold_*; do + d="$taskdir/\$(basename "\$fold")" + mkdir "\$d" + touch "\$d/epa_result.jplace" + done + """ +} diff --git a/modules/nf-core/sativaepang/looplace/meta.yml b/modules/nf-core/sativaepang/looplace/meta.yml new file mode 100644 index 000000000000..b01d5dc66bbf --- /dev/null +++ b/modules/nf-core/sativaepang/looplace/meta.yml @@ -0,0 +1,68 @@ +name: "sativaepang_looplace" +description: Place every leave-one-out fold with EPA-ng via -stage loo-place, writing a jplace into each fold directory +keywords: + - phylogenetics + - taxonomy + - mislabelling + - placement + - leave-one-out +tools: + - "sativaepang": + description: "SATIVA with EPA-ng as the placement engine" + homepage: "https://github.com/Aaramis/sativa-epang" + documentation: "https://github.com/Aaramis/sativa-epang/blob/main/CHANGES-epa-ng.md" + tool_dev_url: "https://github.com/Aaramis/sativa-epang" + doi: "10.1093/nar/gkw396" + licence: + - "GPL-3.0-or-later" + identifier: "" +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - taskdir: + type: directory + description: | + The `*.l1o_tasks` directory produced by `sativaepang/lootasks`: one + subdirectory per fold (`ref.nwk`, `ref.fasta`, `query.fasta`) plus a + `manifest.json`. + pattern: "*.l1o_tasks" +output: + taskdir: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - taskdir: + type: directory + description: | + The same taskdir, with an `epa_result.jplace` (plus `epang.log` and + `epa_info.log`) added to every fold subdirectory. Input to + `sativaepang/looscore`. + pattern: "*.l1o_tasks" + versions_sativaepang: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@erikrikarddaniel" +maintainers: + - "@erikrikarddaniel" diff --git a/modules/nf-core/sativaepang/looplace/tests/main.nf.test b/modules/nf-core/sativaepang/looplace/tests/main.nf.test new file mode 100644 index 000000000000..35522ae9a500 --- /dev/null +++ b/modules/nf-core/sativaepang/looplace/tests/main.nf.test @@ -0,0 +1,124 @@ +nextflow_process { + + name "Test Process SATIVAEPANG_LOOPLACE" + script "../main.nf" + process "SATIVAEPANG_LOOPLACE" + + tag "modules" + tag "modules_nfcore" + tag "sativaepang" + tag "sativaepang/lootasks" + tag "sativaepang/looplace" + + test("sativa-epang tiny example") { + + setup { + run("SATIVAEPANG_LOOTASKS") { + script "../../lootasks/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.model', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + input[0] = SATIVAEPANG_LOOTASKS.out.taskdir + """ + } + } + + then { + // epa_result.jplace/epa_info.log/epang.log aren't byte-reproducible across + // engines (conda vs. container placement likelihoods, wall-clock timing) -- + // masked below, same precedent as sigprofiler/snapaligner. + def taskdir = file(process.out.taskdir[0][1]) + def foldDirs = taskdir.listFiles().findAll { it.isDirectory() && it.name.startsWith("fold_") } + assertAll( + { assert process.success }, + { assert foldDirs.size() > 0 }, + { assert foldDirs.every { new File(it.toString(), "epa_result.jplace").exists() } }, + { assert snapshot(sanitizeOutput(process.out, unstablePatterns: ["**/epa_result.jplace", "**/epa_info.log", "**/epang.log"])).match() } + ) + } + } + + test("gtdb archaea 16S") { + + setup { + run("SATIVAEPANG_LOOTASKS") { + script "../../lootasks/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.model', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + input[0] = SATIVAEPANG_LOOTASKS.out.taskdir + """ + } + } + + then { + def taskdir = file(process.out.taskdir[0][1]) + def foldDirs = taskdir.listFiles().findAll { it.isDirectory() && it.name.startsWith("fold_") } + assertAll( + { assert process.success }, + { assert foldDirs.size() > 0 }, + { assert foldDirs.every { new File(it.toString(), "epa_result.jplace").exists() } }, + { assert snapshot(sanitizeOutput(process.out, unstablePatterns: ["**/epa_result.jplace", "**/epa_info.log", "**/epang.log"])).match() } + ) + } + } + + test("sativa-epang tiny example - stub") { + + options "-stub" + + setup { + run("SATIVAEPANG_LOOTASKS") { + script "../../lootasks/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.model', checkIfExists: true) + ] + """ + } + } + } + + when { + process { + """ + input[0] = SATIVAEPANG_LOOTASKS.out.taskdir + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/sativaepang/looplace/tests/main.nf.test.snap b/modules/nf-core/sativaepang/looplace/tests/main.nf.test.snap new file mode 100644 index 000000000000..6abe66fa006c --- /dev/null +++ b/modules/nf-core/sativaepang/looplace/tests/main.nf.test.snap @@ -0,0 +1,509 @@ +{ + "sativa-epang tiny example": { + "content": [ + { + "taskdir": [ + [ + { + "id": "test" + }, + [ + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,bb1134dbc29097462fd661fb9e6b3caf", + "ref.fasta:md5,c233ac425c37d8114d4f527fbcaad4e4", + "ref.nwk:md5,515134b8f59fce29a6b0a830045e33fd" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,395287470e986a5a5118f502a6519371", + "ref.fasta:md5,ac1852ea58ee41be31add31cb2b8bdc0", + "ref.nwk:md5,e0270e89e35547ab1e059e74d2eaeb60" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,2da991f6e5ba67bbee463eec6c24a0b0", + "ref.fasta:md5,373d2330ababf9188652f1eb96a8e01d", + "ref.nwk:md5,4322823fd1e2f8dfe1b2e1cdf11e46f8" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,72bc25a128226335b547d2332baee050", + "ref.fasta:md5,f11bc779097b6aa834cd1b6b2d482523", + "ref.nwk:md5,3738d49684f10de4857ee83432adb09b" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,07ce27080ad138528e537f40dca2af2f", + "ref.fasta:md5,e1387c8c512479e3c674540c9c29015a", + "ref.nwk:md5,04ece512cfd9322506c1e4157474568d" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,5d37e60905b04d3086a445f52a11859d", + "ref.fasta:md5,608fe31ad59c3d13dac3c5f240048f5c", + "ref.nwk:md5,0edc7cf606ee05e2f9bb6870d4e5600d" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,c3fcf6674c525cdaf5454c90469e824c", + "ref.fasta:md5,210c997772e9c60da2f8d02d4102a046", + "ref.nwk:md5,8df89c51b740310de1cf49a0da9c62e2" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,20ddb01492094f943bd0494cb02a15b8", + "ref.fasta:md5,a30656ace710906715976820ea63eb76", + "ref.nwk:md5,ec3db4cf5a7c34fc805968ed2fcadc1e" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,17b1663be9044492b82a3ae6e34cb585", + "ref.fasta:md5,4fdd4de7313531c94e33c5761637e739", + "ref.nwk:md5,2c3710d75c8605fe5bb737e8e223f5f0" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,7665fd1fdffc803fe33737a32f92fa14", + "ref.fasta:md5,bff72d8ecacb5591accb37b567556c11", + "ref.nwk:md5,b5c595174cfa351e1e8737f045a97f7f" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,ed4010a967c1f4696e4f86a58635582c", + "ref.fasta:md5,dfcd45a37f752f70c482f8c478c91fe7", + "ref.nwk:md5,1e744d07de34a8a8c743e7353a26bee8" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,1ca513bb4f1e4bda541243d4bfe730f3", + "ref.fasta:md5,5ace691400200db6ce84f222bbd58a8e", + "ref.nwk:md5,d355f5b8442efad5d72219ece88358cf" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,4a1f075faefac306bf1f50bcd03d08ab", + "ref.fasta:md5,38870ca5392a4d7823cddfc074bcecd9", + "ref.nwk:md5,c8e78a1adce550965db021be745809d1" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,86aa7a0043b8a5b24208cb43c0f2b7c2", + "ref.fasta:md5,edccc7a1f9ec7124160391bad4d9f223", + "ref.nwk:md5,22c8562ee89a50ca044dd665948ab1ba" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,be7c5a8733fc16439b336c2d531d955c", + "ref.fasta:md5,4e52196552423a43122e342fabfd5f7e", + "ref.nwk:md5,7720495e4ef7703238782a3b0ae88656" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,478341083be2492c6166ec672a3cc9c5", + "ref.fasta:md5,7e9e9784637d79e103b10ddfb1a47c97", + "ref.nwk:md5,fd45ed38daa5f79f9c978dd77a0384cb" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,f7218387d75e670896d8f29d3185daa2", + "ref.fasta:md5,cf42203f9283a22b04b57af465d3b150", + "ref.nwk:md5,da85fe38b9f571a6a51819454a66804c" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,ebfa1e95c1cbd7919b8400bba7eca7e7", + "ref.fasta:md5,662dea7abf7c91b9cc2f89be24f3f2ee", + "ref.nwk:md5,145a107c538f1779600b13b46324d798" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,04b554ca505d335ea966458678ca8f3c", + "ref.fasta:md5,e8cf8c50a7f0ab8569a52199e818e498", + "ref.nwk:md5,312851efa3352808072ba3d5e320fb6e" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,1ccb6f84e671c387a966bd3a0052b657", + "ref.fasta:md5,ba956e77e9494f6e8558ff993ad91e61", + "ref.nwk:md5,f0a66e16124ee43a91dfd17ca23a2057" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,3057a9190900983ffbe109c3e5e9b6d4", + "ref.fasta:md5,33008e40ca8aa75bbe31be397bc2118b", + "ref.nwk:md5,32b90106fddeae8dca854d7685838ca2" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,5f51d83110548f44499a9a80ece5a06f", + "ref.fasta:md5,889b9749ca71642a81d2bcf8b4437c92", + "ref.nwk:md5,167254522515b306b139ac610bc93063" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,07a70443560d1d9135f7b9ffa3fa52e4", + "ref.fasta:md5,1b85d69396fc78214f303115383ed6f4", + "ref.nwk:md5,8d323193e01cdee473b9d4a5a59595d2" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,b0dbd43f8a0df9d6e35b49f40474b6e9", + "ref.fasta:md5,b81abc11c0444797bbcff24971f4da81", + "ref.nwk:md5,1a01e18a462e62434e620e7d14d4763b" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,fe30dc746180eb737b8ec4ec4a521830", + "ref.fasta:md5,8dae797db2bffbc07991db4cf8c488ae", + "ref.nwk:md5,8695490ad63b25cfa2491683924aaac2" + ], + "manifest.json:md5,b74a2494581d6de5042c0d6bdf712bdf" + ] + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOPLACE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-09T16:11:15.113393983", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "gtdb archaea 16S": { + "content": [ + { + "taskdir": [ + [ + { + "id": "test" + }, + [ + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,82b842a62d8bc20f8ec8f148934ea97d", + "ref.fasta:md5,e63b197d48fcc6bb672b90107f36745d", + "ref.nwk:md5,56c1615f11f5cd67afde319964ab5f7a" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,89780d9787a5058955d445566841867c", + "ref.fasta:md5,ae18870cf2485ceaabe496966b6cf939", + "ref.nwk:md5,dd3067692cf8705752f2008ef0931b4a" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,2c88202f5799db19a9f7eae8989e734f", + "ref.fasta:md5,bd3fb9f5e1c1f007b0f8e534fc77e504", + "ref.nwk:md5,3c25d3905329f2c2ad4b380e45c72cb0" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,1f1c30368f8df78af888d3ba244708e5", + "ref.fasta:md5,572af34b79d2e223dc047819453def78", + "ref.nwk:md5,afacdaf9d97ed6937751ae2cb1e33b27" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,415f0344749dc3eedb97adf88d2fb5e4", + "ref.fasta:md5,d7c8f658bb9291e42d8e837752087708", + "ref.nwk:md5,81382b90ab8b4972008ea565618c9375" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,2c571505b60420376b91286b7612928c", + "ref.fasta:md5,680d5afee0b05f0868828dcebe35763a", + "ref.nwk:md5,d6f8eb36ca1c6db2f6c737f0c2140faf" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,dc6fbed32ad6d6bbc21f9b20e60d10a0", + "ref.fasta:md5,0869367e693d93760c5b779163f6352f", + "ref.nwk:md5,3d0b4ba3cdc130d6dfa7f94d3d3c4810" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,daec8ce0fb63f3d4c05c3c2acb6a74f7", + "ref.fasta:md5,c71057df32eb7a52164c9339e24e3d65", + "ref.nwk:md5,5c59be9fdda32c87f406ec43554a7a05" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,06498b1e82941030c9f657c39b13c3bd", + "ref.fasta:md5,c1a669f07f1372ab474c4b43a2a771aa", + "ref.nwk:md5,a9cdacc4ad93774f81f229d93b39c317" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,ec1cfa372a8d0a905c3063c3eee1b326", + "ref.fasta:md5,9298eb735aa4c27b0040bf10117fb306", + "ref.nwk:md5,a6993a05254bc6bec6d841ce08c6294e" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,2f3b68779af6a8d8ab820bb8fc2f4a04", + "ref.fasta:md5,342fe4933ea9135fbbe60ef1196cc591", + "ref.nwk:md5,53989563c2e34c96123adb189bf9e598" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,09f4867b3c48d71dfb3512ba967824a9", + "ref.fasta:md5,d8651924a748c1d8f759cc95ba5b28db", + "ref.nwk:md5,772607632ab4da862067630bd9c0112f" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,f3e729cb639316c8db63d9adda31053c", + "ref.fasta:md5,efe0e24fdf28c7c5e2e4f79dd0610ce2", + "ref.nwk:md5,379c711c352347e18050654ca3c70e42" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,2f025540c29db736987a86fefdd3aebf", + "ref.fasta:md5,2eaa08b981a0da3ac6d968573e01edb1", + "ref.nwk:md5,33b2ab9def12f59da6321c251877a2dd" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,e7afb2cbcff9c44910ed8ec50e3551cd", + "ref.fasta:md5,6e8281f59e8c4a934c09f09c44c1a7d6", + "ref.nwk:md5,bc2b033a423220550d9397cc6086b721" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,91d95e391ddc4c7a904c07c73f819322", + "ref.fasta:md5,075d2c0d16e655f885b9f68f0c5537e5", + "ref.nwk:md5,5624b36fa9a77f2b13aaa481e324d14c" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,5fb4b88fc04c198df291324104987572", + "ref.fasta:md5,e648cda497b169ccf52e7892cb57fef1", + "ref.nwk:md5,005b65e00fb089c5ffd56a44211bf18a" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,50745b0f7c2578e1d5093cc49fc7dafa", + "ref.fasta:md5,9cdd03aeaaed0c5e3ad47b88811b5f9e", + "ref.nwk:md5,492ac77788164697f8d46911fa0d9c41" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,47122277199ac6956bd203e03c78a69f", + "ref.fasta:md5,bf495e9233c6e464eed2956a3db5127b", + "ref.nwk:md5,b02b799bd3244d13e2c3df0052c8aca8" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,ac84046328eb8e4453c9a992a2d948eb", + "ref.fasta:md5,06c0d16ee1ac9566368d9f0df056a46b", + "ref.nwk:md5,b284ec648df2888d911acac9efe5ed9d" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,006e5da29d856e3e7ddecd884a50741d", + "ref.fasta:md5,670c074215cfa18df8fe52f20a89143b", + "ref.nwk:md5,abd034eff95774a464d77002ff559c0c" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,93cd76c015fe5f7d675243eff4f79fb9", + "ref.fasta:md5,9a7440abb4c19ca067ad0c6748a93eb8", + "ref.nwk:md5,e104e0465ef1c6e7fccdd83354981cbb" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,77b70c125ce5d02c369eb7980b4bd10a", + "ref.fasta:md5,b9a84fde66bb8763608ff41fdad24fd2", + "ref.nwk:md5,0664d05ebb2be91c8d16fca1aa93edfe" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,7eae5d737652b5309b0a54e11556e721", + "ref.fasta:md5,4ba66b45ff3c5a7fc3f8f1adb0d46d0b", + "ref.nwk:md5,537c1acfbb9aa15aa30dab9f6b0c3132" + ], + [ + "epa_info.log", + "epa_result.jplace", + "epang.log", + "query.fasta:md5,88b3dc5b0ea1a2c2ab826bfa106fe851", + "ref.fasta:md5,156fa4bad33b587c2fc48968276c6eae", + "ref.nwk:md5,fe2290debf33386b0bcd726011f13fc5" + ], + "manifest.json:md5,6cd4a35936e179e6405c5e8103ace0ee" + ] + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOPLACE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-09T16:11:30.263621994", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "sativa-epang tiny example - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + [ + "epa_result.jplace:md5,d41d8cd98f00b204e9800998ecf8427e" + ], + "manifest.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + "SATIVAEPANG_LOOPLACE", + "sativaepang", + "0.9.3.4" + ] + ], + "taskdir": [ + [ + { + "id": "test" + }, + [ + [ + "epa_result.jplace:md5,d41d8cd98f00b204e9800998ecf8427e" + ], + "manifest.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOPLACE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T16:32:29.928305228", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/sativaepang/looscore/environment.yml b/modules/nf-core/sativaepang/looscore/environment.yml new file mode 100644 index 000000000000..715a97b37f38 --- /dev/null +++ b/modules/nf-core/sativaepang/looscore/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::sativa-epang=0.9.3.4 diff --git a/modules/nf-core/sativaepang/looscore/main.nf b/modules/nf-core/sativaepang/looscore/main.nf new file mode 100644 index 000000000000..c433fc0b97eb --- /dev/null +++ b/modules/nf-core/sativaepang/looscore/main.nf @@ -0,0 +1,39 @@ +process SATIVAEPANG_LOOSCORE { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/sativa-epang:0.9.3.4--py314hab16a5f_0' : + 'quay.io/biocontainers/sativa-epang:0.9.3.4--py314hab16a5f_0' }" + + input: + tuple val(meta), path(refjson), path(taskdir) + + output: + tuple val(meta), path("*.mis"), emit: mis + tuple val("${task.process}"), val('sativaepang'), eval("grep -m1 -oE '[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' \$(command -v sativa-epang)"), topic: versions, emit: versions_sativaepang + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // Unlike looplace, loo-score only reads taskdir -- writes its own output via -o. + """ + sativa-epang \\ + -r ${refjson} \\ + -n ${prefix} \\ + -o . \\ + -taskdir ${taskdir} \\ + -stage loo-score \\ + ${args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.mis + """ +} diff --git a/modules/nf-core/sativaepang/looscore/meta.yml b/modules/nf-core/sativaepang/looscore/meta.yml new file mode 100644 index 000000000000..4a96555ef386 --- /dev/null +++ b/modules/nf-core/sativaepang/looscore/meta.yml @@ -0,0 +1,73 @@ +name: "sativaepang_looscore" +description: Read the placed leave-one-out folds and report mislabelled sequences via -stage loo-score +keywords: + - phylogenetics + - taxonomy + - mislabelling + - placement + - leave-one-out +tools: + - "sativaepang": + description: "SATIVA with EPA-ng as the placement engine" + homepage: "https://github.com/Aaramis/sativa-epang" + documentation: "https://github.com/Aaramis/sativa-epang/blob/main/CHANGES-epa-ng.md" + tool_dev_url: "https://github.com/Aaramis/sativa-epang" + doi: "10.1093/nar/gkw396" + licence: + - "GPL-3.0-or-later" + identifier: "" +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - refjson: + type: file + description: Reference produced by `sativaepang/reference`'s `-stage reference` + pattern: "*.refjson" + ontologies: [] + - taskdir: + type: directory + description: | + The `*.l1o_tasks` directory produced by `sativaepang/looplace`, with a jplace + in every fold subdirectory. + pattern: "*.l1o_tasks" +output: + mis: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - "*.mis": + type: file + description: | + Mislabelled sequences, one per line: sequence ID, the rank at which the + mislabelling was detected, original and proposed taxonomy paths, confidence, + and per-rank confidence. + pattern: "*.mis" + ontologies: [] + versions_sativaepang: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@erikrikarddaniel" +maintainers: + - "@erikrikarddaniel" diff --git a/modules/nf-core/sativaepang/looscore/tests/main.nf.test b/modules/nf-core/sativaepang/looscore/tests/main.nf.test new file mode 100644 index 000000000000..f047e7971952 --- /dev/null +++ b/modules/nf-core/sativaepang/looscore/tests/main.nf.test @@ -0,0 +1,158 @@ +nextflow_process { + + name "Test Process SATIVAEPANG_LOOSCORE" + script "../main.nf" + process "SATIVAEPANG_LOOSCORE" + + tag "modules" + tag "modules_nfcore" + tag "sativaepang" + tag "sativaepang/lootasks" + tag "sativaepang/looplace" + tag "sativaepang/looscore" + + test("sativa-epang tiny example") { + + setup { + run("SATIVAEPANG_LOOTASKS") { + script "../../lootasks/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.model', checkIfExists: true) + ] + """ + } + } + run("SATIVAEPANG_LOOPLACE") { + script "../../looplace/main.nf" + process { + """ + input[0] = SATIVAEPANG_LOOTASKS.out.taskdir + """ + } + } + } + + when { + process { + """ + input[0] = SATIVAEPANG_LOOPLACE.out.taskdir.map { meta, dir -> [ + meta, + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + dir + ] } + """ + } + } + + then { + // The tool's own bundled example is a clean smoke-test set with no injected + // mislabels -- see the gtdb test below for a real detection assertion. + assertAll( + { assert process.success }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("gtdb archaea 16S") { + + setup { + run("SATIVAEPANG_LOOTASKS") { + script "../../lootasks/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.model', checkIfExists: true) + ] + """ + } + } + run("SATIVAEPANG_LOOPLACE") { + script "../../looplace/main.nf" + process { + """ + input[0] = SATIVAEPANG_LOOTASKS.out.taskdir + """ + } + } + } + + when { + process { + """ + input[0] = SATIVAEPANG_LOOPLACE.out.taskdir.map { meta, dir -> [ + meta, + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.refjson', checkIfExists: true), + dir + ] } + """ + } + } + + then { + // This dataset carries a deliberate positive control: DupHaloA/DupSulfoA, a + // swapped-label pair (see the test-datasets README). A real mislabel-detection + // assertion, not just "the process completed". + def mis = path(process.out.mis[0][1]).text + assertAll( + { assert process.success }, + { assert mis.contains("DupHaloA") }, + { assert mis.contains("DupSulfoA") }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sativa-epang tiny example - stub") { + + options "-stub" + + setup { + run("SATIVAEPANG_LOOTASKS") { + script "../../lootasks/main.nf" + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.model', checkIfExists: true) + ] + """ + } + } + run("SATIVAEPANG_LOOPLACE") { + script "../../looplace/main.nf" + process { + """ + input[0] = SATIVAEPANG_LOOTASKS.out.taskdir + """ + } + } + } + + when { + process { + """ + input[0] = SATIVAEPANG_LOOPLACE.out.taskdir.map { meta, dir -> [ + meta, + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + dir + ] } + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/sativaepang/looscore/tests/main.nf.test.snap b/modules/nf-core/sativaepang/looscore/tests/main.nf.test.snap new file mode 100644 index 000000000000..700c53889492 --- /dev/null +++ b/modules/nf-core/sativaepang/looscore/tests/main.nf.test.snap @@ -0,0 +1,95 @@ +{ + "sativa-epang tiny example": { + "content": [ + { + "mis": [ + [ + { + "id": "test" + }, + "test.mis:md5,746e8aa384163a29d78d8b87d33b41d1" + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOSCORE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-09T20:25:31.143375356", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "gtdb archaea 16S": { + "content": [ + { + "mis": [ + [ + { + "id": "test" + }, + "test.mis:md5,c8758fa09adee193eba3f0f5f11d4822" + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOSCORE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-09T20:25:43.713337811", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "sativa-epang tiny example - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.mis:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + "SATIVAEPANG_LOOSCORE", + "sativaepang", + "0.9.3.4" + ] + ], + "mis": [ + [ + { + "id": "test" + }, + "test.mis:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOSCORE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T17:07:29.67996963", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/sativaepang/lootasks/environment.yml b/modules/nf-core/sativaepang/lootasks/environment.yml new file mode 100644 index 000000000000..715a97b37f38 --- /dev/null +++ b/modules/nf-core/sativaepang/lootasks/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::sativa-epang=0.9.3.4 diff --git a/modules/nf-core/sativaepang/lootasks/main.nf b/modules/nf-core/sativaepang/lootasks/main.nf new file mode 100644 index 000000000000..c06994c5412e --- /dev/null +++ b/modules/nf-core/sativaepang/lootasks/main.nf @@ -0,0 +1,45 @@ +process SATIVAEPANG_LOOTASKS { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/sativa-epang:0.9.3.4--py314hab16a5f_0' : + 'quay.io/biocontainers/sativa-epang:0.9.3.4--py314hab16a5f_0' }" + + input: + tuple val(meta), path(refjson), path(model) + + output: + tuple val(meta), path("*.l1o_tasks"), emit: taskdir + tuple val("${task.process}"), val('sativaepang'), eval("grep -m1 -oE '[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' \$(command -v sativa-epang)"), topic: versions, emit: versions_sativaepang + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // Works around sativa-epang not recovering the model from a -reftree/-refmodel + // refjson (falls back to generic GTR+G per fold otherwise) -- see PR description. + """ + export SATIVA_EPANG_MODEL="\$(cut -d ',' -f1 "${model}")" + + sativa-epang \\ + -r ${refjson} \\ + -n ${prefix} \\ + -o . \\ + -stage loo-tasks \\ + ${args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + mkdir -p ${prefix}.l1o_tasks/fold_000 + touch ${prefix}.l1o_tasks/manifest.json + touch ${prefix}.l1o_tasks/fold_000/ref.nwk + touch ${prefix}.l1o_tasks/fold_000/ref.fasta + touch ${prefix}.l1o_tasks/fold_000/query.fasta + """ +} diff --git a/modules/nf-core/sativaepang/lootasks/meta.yml b/modules/nf-core/sativaepang/lootasks/meta.yml new file mode 100644 index 000000000000..200cdacc481e --- /dev/null +++ b/modules/nf-core/sativaepang/lootasks/meta.yml @@ -0,0 +1,77 @@ +name: "sativaepang_lootasks" +description: Deal a SATIVA reference into leave-one-out folds, one self-contained task directory per fold, ready for independent EPA-ng placement +keywords: + - phylogenetics + - taxonomy + - mislabelling + - placement + - leave-one-out +tools: + - "sativaepang": + description: "SATIVA with EPA-ng as the placement engine" + homepage: "https://github.com/Aaramis/sativa-epang" + documentation: "https://github.com/Aaramis/sativa-epang/blob/main/CHANGES-epa-ng.md" + tool_dev_url: "https://github.com/Aaramis/sativa-epang" + doi: "10.1093/nar/gkw396" + licence: + - "GPL-3.0-or-later" + identifier: "" +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - refjson: + type: file + description: Reference produced by `sativaepang/reference`'s `-stage reference` + pattern: "*.refjson" + ontologies: [] + - model: + type: file + description: | + The substitution model file produced alongside `refjson` by + `sativaepang/reference`. Its content (minus any trailing RAxML-NG partition + clause) is exported as `SATIVA_EPANG_MODEL`, since a refjson built from an + externally-supplied tree leaves no `RAxML_info` file for sativa-epang to + recover the model from on its own. + pattern: "*.model" + ontologies: [] +output: + taskdir: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - "*.l1o_tasks": + type: directory + description: | + One self-contained subdirectory per leave-one-out fold (`ref.nwk`, + `ref.fasta`, `query.fasta`), plus a `manifest.json` describing every fold, + its model, and the EPA-ng command to run in it. Input to + `sativaepang/looplace` and, later, `sativaepang/looscore`. + pattern: "*.l1o_tasks" + versions_sativaepang: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@erikrikarddaniel" +maintainers: + - "@erikrikarddaniel" diff --git a/modules/nf-core/sativaepang/lootasks/tests/main.nf.test b/modules/nf-core/sativaepang/lootasks/tests/main.nf.test new file mode 100644 index 000000000000..2595d178b7a1 --- /dev/null +++ b/modules/nf-core/sativaepang/lootasks/tests/main.nf.test @@ -0,0 +1,89 @@ +nextflow_process { + + name "Test Process SATIVAEPANG_LOOTASKS" + script "../main.nf" + process "SATIVAEPANG_LOOTASKS" + + tag "modules" + tag "modules_nfcore" + tag "sativaepang" + tag "sativaepang/lootasks" + + test("sativa-epang tiny example") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.model', checkIfExists: true) + ] + """ + } + } + + then { + // manifest.json's model must be the bare model expression, not RAxML-NG's + // partition clause -- see the SATIVA_EPANG_MODEL workaround in main.nf. + def taskdir = file(process.out.taskdir[0][1]) + def manifest = new groovy.json.JsonSlurper().parse(new File(taskdir.toString(), "manifest.json")) + assertAll( + { assert process.success }, + { assert !manifest.model.contains(",") }, + { assert manifest.n_folds > 0 }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("gtdb archaea 16S") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.model', checkIfExists: true) + ] + """ + } + } + + then { + def taskdir = file(process.out.taskdir[0][1]) + def manifest = new groovy.json.JsonSlurper().parse(new File(taskdir.toString(), "manifest.json")) + assertAll( + { assert process.success }, + { assert !manifest.model.contains(",") }, + { assert manifest.n_folds > 0 }, + { assert snapshot(sanitizeOutput(process.out)).match() } + ) + } + } + + test("sativa-epang tiny example - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.refjson', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.model', checkIfExists: true) + ] + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/sativaepang/lootasks/tests/main.nf.test.snap b/modules/nf-core/sativaepang/lootasks/tests/main.nf.test.snap new file mode 100644 index 000000000000..5c7d9c2bedcc --- /dev/null +++ b/modules/nf-core/sativaepang/lootasks/tests/main.nf.test.snap @@ -0,0 +1,363 @@ +{ + "sativa-epang tiny example": { + "content": [ + { + "taskdir": [ + [ + { + "id": "test" + }, + [ + [ + "query.fasta:md5,bb1134dbc29097462fd661fb9e6b3caf", + "ref.fasta:md5,c233ac425c37d8114d4f527fbcaad4e4", + "ref.nwk:md5,515134b8f59fce29a6b0a830045e33fd" + ], + [ + "query.fasta:md5,395287470e986a5a5118f502a6519371", + "ref.fasta:md5,ac1852ea58ee41be31add31cb2b8bdc0", + "ref.nwk:md5,e0270e89e35547ab1e059e74d2eaeb60" + ], + [ + "query.fasta:md5,2da991f6e5ba67bbee463eec6c24a0b0", + "ref.fasta:md5,373d2330ababf9188652f1eb96a8e01d", + "ref.nwk:md5,4322823fd1e2f8dfe1b2e1cdf11e46f8" + ], + [ + "query.fasta:md5,72bc25a128226335b547d2332baee050", + "ref.fasta:md5,f11bc779097b6aa834cd1b6b2d482523", + "ref.nwk:md5,3738d49684f10de4857ee83432adb09b" + ], + [ + "query.fasta:md5,07ce27080ad138528e537f40dca2af2f", + "ref.fasta:md5,e1387c8c512479e3c674540c9c29015a", + "ref.nwk:md5,04ece512cfd9322506c1e4157474568d" + ], + [ + "query.fasta:md5,5d37e60905b04d3086a445f52a11859d", + "ref.fasta:md5,608fe31ad59c3d13dac3c5f240048f5c", + "ref.nwk:md5,0edc7cf606ee05e2f9bb6870d4e5600d" + ], + [ + "query.fasta:md5,c3fcf6674c525cdaf5454c90469e824c", + "ref.fasta:md5,210c997772e9c60da2f8d02d4102a046", + "ref.nwk:md5,8df89c51b740310de1cf49a0da9c62e2" + ], + [ + "query.fasta:md5,20ddb01492094f943bd0494cb02a15b8", + "ref.fasta:md5,a30656ace710906715976820ea63eb76", + "ref.nwk:md5,ec3db4cf5a7c34fc805968ed2fcadc1e" + ], + [ + "query.fasta:md5,17b1663be9044492b82a3ae6e34cb585", + "ref.fasta:md5,4fdd4de7313531c94e33c5761637e739", + "ref.nwk:md5,2c3710d75c8605fe5bb737e8e223f5f0" + ], + [ + "query.fasta:md5,7665fd1fdffc803fe33737a32f92fa14", + "ref.fasta:md5,bff72d8ecacb5591accb37b567556c11", + "ref.nwk:md5,b5c595174cfa351e1e8737f045a97f7f" + ], + [ + "query.fasta:md5,ed4010a967c1f4696e4f86a58635582c", + "ref.fasta:md5,dfcd45a37f752f70c482f8c478c91fe7", + "ref.nwk:md5,1e744d07de34a8a8c743e7353a26bee8" + ], + [ + "query.fasta:md5,1ca513bb4f1e4bda541243d4bfe730f3", + "ref.fasta:md5,5ace691400200db6ce84f222bbd58a8e", + "ref.nwk:md5,d355f5b8442efad5d72219ece88358cf" + ], + [ + "query.fasta:md5,4a1f075faefac306bf1f50bcd03d08ab", + "ref.fasta:md5,38870ca5392a4d7823cddfc074bcecd9", + "ref.nwk:md5,c8e78a1adce550965db021be745809d1" + ], + [ + "query.fasta:md5,86aa7a0043b8a5b24208cb43c0f2b7c2", + "ref.fasta:md5,edccc7a1f9ec7124160391bad4d9f223", + "ref.nwk:md5,22c8562ee89a50ca044dd665948ab1ba" + ], + [ + "query.fasta:md5,be7c5a8733fc16439b336c2d531d955c", + "ref.fasta:md5,4e52196552423a43122e342fabfd5f7e", + "ref.nwk:md5,7720495e4ef7703238782a3b0ae88656" + ], + [ + "query.fasta:md5,478341083be2492c6166ec672a3cc9c5", + "ref.fasta:md5,7e9e9784637d79e103b10ddfb1a47c97", + "ref.nwk:md5,fd45ed38daa5f79f9c978dd77a0384cb" + ], + [ + "query.fasta:md5,f7218387d75e670896d8f29d3185daa2", + "ref.fasta:md5,cf42203f9283a22b04b57af465d3b150", + "ref.nwk:md5,da85fe38b9f571a6a51819454a66804c" + ], + [ + "query.fasta:md5,ebfa1e95c1cbd7919b8400bba7eca7e7", + "ref.fasta:md5,662dea7abf7c91b9cc2f89be24f3f2ee", + "ref.nwk:md5,145a107c538f1779600b13b46324d798" + ], + [ + "query.fasta:md5,04b554ca505d335ea966458678ca8f3c", + "ref.fasta:md5,e8cf8c50a7f0ab8569a52199e818e498", + "ref.nwk:md5,312851efa3352808072ba3d5e320fb6e" + ], + [ + "query.fasta:md5,1ccb6f84e671c387a966bd3a0052b657", + "ref.fasta:md5,ba956e77e9494f6e8558ff993ad91e61", + "ref.nwk:md5,f0a66e16124ee43a91dfd17ca23a2057" + ], + [ + "query.fasta:md5,3057a9190900983ffbe109c3e5e9b6d4", + "ref.fasta:md5,33008e40ca8aa75bbe31be397bc2118b", + "ref.nwk:md5,32b90106fddeae8dca854d7685838ca2" + ], + [ + "query.fasta:md5,5f51d83110548f44499a9a80ece5a06f", + "ref.fasta:md5,889b9749ca71642a81d2bcf8b4437c92", + "ref.nwk:md5,167254522515b306b139ac610bc93063" + ], + [ + "query.fasta:md5,07a70443560d1d9135f7b9ffa3fa52e4", + "ref.fasta:md5,1b85d69396fc78214f303115383ed6f4", + "ref.nwk:md5,8d323193e01cdee473b9d4a5a59595d2" + ], + [ + "query.fasta:md5,b0dbd43f8a0df9d6e35b49f40474b6e9", + "ref.fasta:md5,b81abc11c0444797bbcff24971f4da81", + "ref.nwk:md5,1a01e18a462e62434e620e7d14d4763b" + ], + [ + "query.fasta:md5,fe30dc746180eb737b8ec4ec4a521830", + "ref.fasta:md5,8dae797db2bffbc07991db4cf8c488ae", + "ref.nwk:md5,8695490ad63b25cfa2491683924aaac2" + ], + "manifest.json:md5,b74a2494581d6de5042c0d6bdf712bdf" + ] + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOTASKS", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T15:41:55.460063452", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "gtdb archaea 16S": { + "content": [ + { + "taskdir": [ + [ + { + "id": "test" + }, + [ + [ + "query.fasta:md5,82b842a62d8bc20f8ec8f148934ea97d", + "ref.fasta:md5,e63b197d48fcc6bb672b90107f36745d", + "ref.nwk:md5,56c1615f11f5cd67afde319964ab5f7a" + ], + [ + "query.fasta:md5,89780d9787a5058955d445566841867c", + "ref.fasta:md5,ae18870cf2485ceaabe496966b6cf939", + "ref.nwk:md5,dd3067692cf8705752f2008ef0931b4a" + ], + [ + "query.fasta:md5,2c88202f5799db19a9f7eae8989e734f", + "ref.fasta:md5,bd3fb9f5e1c1f007b0f8e534fc77e504", + "ref.nwk:md5,3c25d3905329f2c2ad4b380e45c72cb0" + ], + [ + "query.fasta:md5,1f1c30368f8df78af888d3ba244708e5", + "ref.fasta:md5,572af34b79d2e223dc047819453def78", + "ref.nwk:md5,afacdaf9d97ed6937751ae2cb1e33b27" + ], + [ + "query.fasta:md5,415f0344749dc3eedb97adf88d2fb5e4", + "ref.fasta:md5,d7c8f658bb9291e42d8e837752087708", + "ref.nwk:md5,81382b90ab8b4972008ea565618c9375" + ], + [ + "query.fasta:md5,2c571505b60420376b91286b7612928c", + "ref.fasta:md5,680d5afee0b05f0868828dcebe35763a", + "ref.nwk:md5,d6f8eb36ca1c6db2f6c737f0c2140faf" + ], + [ + "query.fasta:md5,dc6fbed32ad6d6bbc21f9b20e60d10a0", + "ref.fasta:md5,0869367e693d93760c5b779163f6352f", + "ref.nwk:md5,3d0b4ba3cdc130d6dfa7f94d3d3c4810" + ], + [ + "query.fasta:md5,daec8ce0fb63f3d4c05c3c2acb6a74f7", + "ref.fasta:md5,c71057df32eb7a52164c9339e24e3d65", + "ref.nwk:md5,5c59be9fdda32c87f406ec43554a7a05" + ], + [ + "query.fasta:md5,06498b1e82941030c9f657c39b13c3bd", + "ref.fasta:md5,c1a669f07f1372ab474c4b43a2a771aa", + "ref.nwk:md5,a9cdacc4ad93774f81f229d93b39c317" + ], + [ + "query.fasta:md5,ec1cfa372a8d0a905c3063c3eee1b326", + "ref.fasta:md5,9298eb735aa4c27b0040bf10117fb306", + "ref.nwk:md5,a6993a05254bc6bec6d841ce08c6294e" + ], + [ + "query.fasta:md5,2f3b68779af6a8d8ab820bb8fc2f4a04", + "ref.fasta:md5,342fe4933ea9135fbbe60ef1196cc591", + "ref.nwk:md5,53989563c2e34c96123adb189bf9e598" + ], + [ + "query.fasta:md5,09f4867b3c48d71dfb3512ba967824a9", + "ref.fasta:md5,d8651924a748c1d8f759cc95ba5b28db", + "ref.nwk:md5,772607632ab4da862067630bd9c0112f" + ], + [ + "query.fasta:md5,f3e729cb639316c8db63d9adda31053c", + "ref.fasta:md5,efe0e24fdf28c7c5e2e4f79dd0610ce2", + "ref.nwk:md5,379c711c352347e18050654ca3c70e42" + ], + [ + "query.fasta:md5,2f025540c29db736987a86fefdd3aebf", + "ref.fasta:md5,2eaa08b981a0da3ac6d968573e01edb1", + "ref.nwk:md5,33b2ab9def12f59da6321c251877a2dd" + ], + [ + "query.fasta:md5,e7afb2cbcff9c44910ed8ec50e3551cd", + "ref.fasta:md5,6e8281f59e8c4a934c09f09c44c1a7d6", + "ref.nwk:md5,bc2b033a423220550d9397cc6086b721" + ], + [ + "query.fasta:md5,91d95e391ddc4c7a904c07c73f819322", + "ref.fasta:md5,075d2c0d16e655f885b9f68f0c5537e5", + "ref.nwk:md5,5624b36fa9a77f2b13aaa481e324d14c" + ], + [ + "query.fasta:md5,5fb4b88fc04c198df291324104987572", + "ref.fasta:md5,e648cda497b169ccf52e7892cb57fef1", + "ref.nwk:md5,005b65e00fb089c5ffd56a44211bf18a" + ], + [ + "query.fasta:md5,50745b0f7c2578e1d5093cc49fc7dafa", + "ref.fasta:md5,9cdd03aeaaed0c5e3ad47b88811b5f9e", + "ref.nwk:md5,492ac77788164697f8d46911fa0d9c41" + ], + [ + "query.fasta:md5,47122277199ac6956bd203e03c78a69f", + "ref.fasta:md5,bf495e9233c6e464eed2956a3db5127b", + "ref.nwk:md5,b02b799bd3244d13e2c3df0052c8aca8" + ], + [ + "query.fasta:md5,ac84046328eb8e4453c9a992a2d948eb", + "ref.fasta:md5,06c0d16ee1ac9566368d9f0df056a46b", + "ref.nwk:md5,b284ec648df2888d911acac9efe5ed9d" + ], + [ + "query.fasta:md5,006e5da29d856e3e7ddecd884a50741d", + "ref.fasta:md5,670c074215cfa18df8fe52f20a89143b", + "ref.nwk:md5,abd034eff95774a464d77002ff559c0c" + ], + [ + "query.fasta:md5,93cd76c015fe5f7d675243eff4f79fb9", + "ref.fasta:md5,9a7440abb4c19ca067ad0c6748a93eb8", + "ref.nwk:md5,e104e0465ef1c6e7fccdd83354981cbb" + ], + [ + "query.fasta:md5,77b70c125ce5d02c369eb7980b4bd10a", + "ref.fasta:md5,b9a84fde66bb8763608ff41fdad24fd2", + "ref.nwk:md5,0664d05ebb2be91c8d16fca1aa93edfe" + ], + [ + "query.fasta:md5,7eae5d737652b5309b0a54e11556e721", + "ref.fasta:md5,4ba66b45ff3c5a7fc3f8f1adb0d46d0b", + "ref.nwk:md5,537c1acfbb9aa15aa30dab9f6b0c3132" + ], + [ + "query.fasta:md5,88b3dc5b0ea1a2c2ab826bfa106fe851", + "ref.fasta:md5,156fa4bad33b587c2fc48968276c6eae", + "ref.nwk:md5,fe2290debf33386b0bcd726011f13fc5" + ], + "manifest.json:md5,6cd4a35936e179e6405c5e8103ace0ee" + ] + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOTASKS", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T15:42:01.547301501", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "sativa-epang tiny example - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + [ + [ + "query.fasta:md5,d41d8cd98f00b204e9800998ecf8427e", + "ref.fasta:md5,d41d8cd98f00b204e9800998ecf8427e", + "ref.nwk:md5,d41d8cd98f00b204e9800998ecf8427e" + ], + "manifest.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "1": [ + [ + "SATIVAEPANG_LOOTASKS", + "sativaepang", + "0.9.3.4" + ] + ], + "taskdir": [ + [ + { + "id": "test" + }, + [ + [ + "query.fasta:md5,d41d8cd98f00b204e9800998ecf8427e", + "ref.fasta:md5,d41d8cd98f00b204e9800998ecf8427e", + "ref.nwk:md5,d41d8cd98f00b204e9800998ecf8427e" + ], + "manifest.json:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_LOOTASKS", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T15:42:07.053580458", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file diff --git a/modules/nf-core/sativaepang/reference/environment.yml b/modules/nf-core/sativaepang/reference/environment.yml new file mode 100644 index 000000000000..715a97b37f38 --- /dev/null +++ b/modules/nf-core/sativaepang/reference/environment.yml @@ -0,0 +1,7 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::sativa-epang=0.9.3.4 diff --git a/modules/nf-core/sativaepang/reference/main.nf b/modules/nf-core/sativaepang/reference/main.nf new file mode 100644 index 000000000000..fa144a27c675 --- /dev/null +++ b/modules/nf-core/sativaepang/reference/main.nf @@ -0,0 +1,50 @@ +process SATIVAEPANG_REFERENCE { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine in ['singularity', 'apptainer'] && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/sativa-epang:0.9.3.4--py314hab16a5f_0' : + 'quay.io/biocontainers/sativa-epang:0.9.3.4--py314hab16a5f_0' }" + + input: + tuple val(meta), path(alignment), path(taxonomy), val(taxcode) + path(reftree) + path(refmodel) + + output: + tuple val(meta), path("*.refjson"), emit: refjson + tuple val(meta), path("*.model") , emit: model + tuple val("${task.process}"), val('sativaepang'), eval("grep -m1 -oE '[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+' \$(command -v sativa-epang)"), topic: versions, emit: versions_sativaepang + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + // -reftree/-refmodel hand off a tree built elsewhere (e.g. RAxML-NG) instead of + // running sativa-epang's own constrained RAxML search. + def reftree_arg = reftree ? "-reftree ${reftree}" : '' + def refmodel_arg = refmodel ? "-refmodel ${refmodel}" : '' + """ + sativa-epang \\ + -s ${alignment} \\ + -t ${taxonomy} \\ + -x ${taxcode} \\ + ${reftree_arg} \\ + ${refmodel_arg} \\ + -n ${prefix} \\ + -o . \\ + -T ${task.cpus} \\ + -stage reference \\ + ${args} + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch ${prefix}.refjson + touch ${prefix}.model + """ +} diff --git a/modules/nf-core/sativaepang/reference/meta.yml b/modules/nf-core/sativaepang/reference/meta.yml new file mode 100644 index 000000000000..5c4cb60937e1 --- /dev/null +++ b/modules/nf-core/sativaepang/reference/meta.yml @@ -0,0 +1,108 @@ +name: "sativaepang_reference" +description: Build a SATIVA reference (refjson) from an alignment and taxonomy, optionally from a tree built elsewhere instead of running RAxML +keywords: + - phylogenetics + - taxonomy + - mislabelling + - placement +tools: + - "sativaepang": + description: "SATIVA with EPA-ng as the placement engine" + homepage: "https://github.com/Aaramis/sativa-epang" + documentation: "https://github.com/Aaramis/sativa-epang/blob/main/CHANGES-epa-ng.md" + tool_dev_url: "https://github.com/Aaramis/sativa-epang" + doi: "10.1093/nar/gkw396" + licence: + - "GPL-3.0-or-later" + identifier: "" +input: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - alignment: + type: file + description: Reference multiple sequence alignment (PHYLIP or FASTA) + pattern: "*.{phy,phylip,fa,fasta,fna}" + ontologies: + - edam: http://edamontology.org/format_1656 + - taxonomy: + type: file + description: | + Reference taxonomy, one sequence ID per line followed by its taxonomic path, + matching the alignment's sequence IDs. + pattern: "*.{tsv,tax,txt}" + ontologies: + - edam: http://edamontology.org/format_3475 # TSV + - taxcode: + type: string + description: "Taxonomic code: bac(teriological), bot(anical), zoo(logical) or vir(ological)" + pattern: "^(?i)(bac|bot|zoo|vir)$" + - reftree: + type: file + description: | + Reference tree topology built elsewhere (e.g. RAxML-NG), with the alignment's + leaf names. Skips sativa-epang's own constrained RAxML search: EPA-ng numbers + the branches and the taxonomy map/node heights are computed from the given + tree. Pass an empty list to build the tree with sativa-epang's own RAxML + search instead. + pattern: "*.{nwk,newick,treefile}" + ontologies: + - edam: http://edamontology.org/format_1919 + - refmodel: + type: file + description: | + Substitution model matching `reftree`, as a RAxML-NG model string or file (EPA-ng + reads either). Required alongside `reftree`; ignored otherwise. + pattern: "*.{bestModel,txt}" + ontologies: [] +output: + refjson: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - "*.refjson": + type: file + description: | + The reference tree, taxonomy map, node heights and speciation rate, in + sativa-epang's own JSON format. Input to the loo-tasks and loo-score stages. + pattern: "*.refjson" + ontologies: [] + model: + - - meta: + type: map + description: Groovy Map containing sample information. e.g. `[ id:'sample1' ]` + - "*.model": + type: file + description: | + The substitution model the reference tree was built/scored under. Needed by + the leave-one-out stages, since it otherwise lives only in a temp directory + that gets deleted. + pattern: "*.model" + ontologies: [] + versions_sativaepang: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +topics: + versions: + - - ${task.process}: + type: string + description: The name of the process + - sativaepang: + type: string + description: The name of the tool + - grep -m1 -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' \$(command -v sativa-epang): + type: eval + description: The expression to obtain the version of the tool + +authors: + - "@erikrikarddaniel" +maintainers: + - "@erikrikarddaniel" diff --git a/modules/nf-core/sativaepang/reference/tests/main.nf.test b/modules/nf-core/sativaepang/reference/tests/main.nf.test new file mode 100644 index 000000000000..98068730377f --- /dev/null +++ b/modules/nf-core/sativaepang/reference/tests/main.nf.test @@ -0,0 +1,98 @@ +nextflow_process { + + name "Test Process SATIVAEPANG_REFERENCE" + script "../main.nf" + process "SATIVAEPANG_REFERENCE" + + tag "modules" + tag "modules_nfcore" + tag "sativaepang" + tag "sativaepang/reference" + + test("sativa-epang tiny example - given RAxML-NG tree") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.phy', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.tax', checkIfExists: true), + 'bac' + ] + input[1] = file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.raxml.bestTree', checkIfExists: true) + input[2] = file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.raxml.bestModel', checkIfExists: true) + """ + } + } + + then { + // refjson embeds its own build invocation (paths and all), so it isn't + // byte-reproducible across engines -- check structure instead. + def refjson = new groovy.json.JsonSlurper().parse(file(process.out.refjson[0][1])) + assertAll( + { assert process.success }, + { assert refjson.taxcode == "bac" }, + { assert refjson.tree }, + { assert refjson.branch_tax_map }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys:["refjson"])).match() } + ) + } + } + + test("gtdb archaea 16S - given RAxML-NG tree") { + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s_aligned.fasta', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.tax', checkIfExists: true), + 'bac' + ] + input[1] = file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.raxml.bestTree', checkIfExists: true) + input[2] = file(params.modules_testdata_base_path + 'delete_me/sativaepang/gtdb_archaea_16s.raxml.bestModel', checkIfExists: true) + """ + } + } + + then { + def refjson = new groovy.json.JsonSlurper().parse(file(process.out.refjson[0][1])) + assertAll( + { assert process.success }, + { assert refjson.taxcode == "bac" }, + { assert refjson.tree }, + { assert refjson.branch_tax_map }, + { assert snapshot(sanitizeOutput(process.out, unstableKeys:["refjson"])).match() } + ) + } + } + + test("sativa-epang tiny example - given RAxML-NG tree - stub") { + + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test' ], + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.phy', checkIfExists: true), + file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.tax', checkIfExists: true), + 'bac' + ] + input[1] = file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.raxml.bestTree', checkIfExists: true) + input[2] = file(params.modules_testdata_base_path + 'delete_me/sativaepang/sativaepang_tiny.raxml.bestModel', checkIfExists: true) + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } +} diff --git a/modules/nf-core/sativaepang/reference/tests/main.nf.test.snap b/modules/nf-core/sativaepang/reference/tests/main.nf.test.snap new file mode 100644 index 000000000000..a8a5d974b3e1 --- /dev/null +++ b/modules/nf-core/sativaepang/reference/tests/main.nf.test.snap @@ -0,0 +1,127 @@ +{ + "sativa-epang tiny example - given RAxML-NG tree": { + "content": [ + { + "model": [ + [ + { + "id": "test" + }, + "test.model:md5,85af62a8c9e0aea4693d022aa9adc0b8" + ] + ], + "refjson": [ + [ + { + "id": "test" + }, + "test.refjson" + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_REFERENCE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T15:43:39.281532561", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "sativa-epang tiny example - given RAxML-NG tree - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test" + }, + "test.refjson:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test" + }, + "test.model:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + "SATIVAEPANG_REFERENCE", + "sativaepang", + "0.9.3.4" + ] + ], + "model": [ + [ + { + "id": "test" + }, + "test.model:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "refjson": [ + [ + { + "id": "test" + }, + "test.refjson:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_REFERENCE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T15:43:51.141418153", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + }, + "gtdb archaea 16S - given RAxML-NG tree": { + "content": [ + { + "model": [ + [ + { + "id": "test" + }, + "test.model:md5,5f08a25b6a21e23522a6eb51b4db22f0" + ] + ], + "refjson": [ + [ + { + "id": "test" + }, + "test.refjson" + ] + ], + "versions_sativaepang": [ + [ + "SATIVAEPANG_REFERENCE", + "sativaepang", + "0.9.3.4" + ] + ] + } + ], + "timestamp": "2026-09-08T15:43:45.731394688", + "meta": { + "nf-test": "0.9.5", + "nextflow": "26.04.6" + } + } +} \ No newline at end of file