gippa 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. gippa/__init__.py +3 -0
  2. gippa/annotate/__init__.py +1 -0
  3. gippa/annotate/antismash.py +82 -0
  4. gippa/annotate/antismash_cmd.py +35 -0
  5. gippa/annotate/bakta.py +324 -0
  6. gippa/annotate/bakta_cmd.py +64 -0
  7. gippa/annotate/bakta_layers.py +108 -0
  8. gippa/annotate/codon_usage.py +182 -0
  9. gippa/annotate/genomad.py +144 -0
  10. gippa/annotate/genomad_cmd.py +25 -0
  11. gippa/annotate/layers.py +433 -0
  12. gippa/annotate/operons.py +209 -0
  13. gippa/annotate/ostir.py +116 -0
  14. gippa/annotate/ostir_cmd.py +14 -0
  15. gippa/annotate/precomputed.py +189 -0
  16. gippa/annotate/promotech_cmd.py +46 -0
  17. gippa/annotate/promoter_calculator.py +145 -0
  18. gippa/annotate/promoter_calculator_cmd.py +15 -0
  19. gippa/annotate/promoters.py +299 -0
  20. gippa/annotate/regions.py +330 -0
  21. gippa/annotate/terminatornet_cmd.py +26 -0
  22. gippa/annotate/terminators.py +340 -0
  23. gippa/annotate/tools.py +246 -0
  24. gippa/annotate/transtermhp_cmd.py +14 -0
  25. gippa/annotate/uniop_adapter.py +112 -0
  26. gippa/annotate/uniop_cmd.py +23 -0
  27. gippa/annotate/user_annotation.py +340 -0
  28. gippa/cli.py +1583 -0
  29. gippa/compare/__init__.py +1 -0
  30. gippa/compare/abundance.py +123 -0
  31. gippa/compare/detection.py +62 -0
  32. gippa/compare/stability.py +120 -0
  33. gippa/compare/versions.py +156 -0
  34. gippa/config/__init__.py +128 -0
  35. gippa/config/defaults.yaml +150 -0
  36. gippa/config/model.py +437 -0
  37. gippa/context/__init__.py +1 -0
  38. gippa/context/autocorrelation.py +353 -0
  39. gippa/context/diagnostics.py +110 -0
  40. gippa/context/islands.py +664 -0
  41. gippa/context/oriter.py +496 -0
  42. gippa/context/regions.py +561 -0
  43. gippa/context/regions_table.py +340 -0
  44. gippa/context/sensitivity.py +314 -0
  45. gippa/context/summary.py +356 -0
  46. gippa/context/support.py +81 -0
  47. gippa/context/tables.py +131 -0
  48. gippa/context/vocab.py +84 -0
  49. gippa/data/__init__.py +1 -0
  50. gippa/data/build_mass_equivalents.py +45 -0
  51. gippa/data/calibration.py +17 -0
  52. gippa/data/calibration.yaml +43 -0
  53. gippa/data/cog_categories.tsv +28 -0
  54. gippa/data/db_sources.yaml +121 -0
  55. gippa/data/demo/PROVENANCE.md +19 -0
  56. gippa/data/demo/layers/bakta.json.gz +0 -0
  57. gippa/data/demo/layers/operon_pairs.tsv.gz +0 -0
  58. gippa/data/demo/layers/promoters.tsv.gz +0 -0
  59. gippa/data/demo/layers/rbs.tsv.gz +0 -0
  60. gippa/data/demo/layers/rbs_novel.tsv.gz +0 -0
  61. gippa/data/demo/layers/regions.tsv.gz +0 -0
  62. gippa/data/demo/layers/terminators.tsv.gz +0 -0
  63. gippa/data/demo/layers/tx_rates.tsv.gz +0 -0
  64. gippa/data/demo/sim100k.fna.gz +0 -0
  65. gippa/data/demo/sim100k_peptides.tsv.gz +0 -0
  66. gippa/data/mass_equivalents.tsv +154 -0
  67. gippa/data/modifications.tsv +18 -0
  68. gippa/data/residue_masses.tsv +25 -0
  69. gippa/determinants/__init__.py +11 -0
  70. gippa/determinants/attribution.py +260 -0
  71. gippa/determinants/features.py +265 -0
  72. gippa/determinants/models.py +405 -0
  73. gippa/determinants/null.py +207 -0
  74. gippa/determinants/sensitivity.py +108 -0
  75. gippa/determinants/support.py +104 -0
  76. gippa/exitcodes.py +14 -0
  77. gippa/function/__init__.py +1 -0
  78. gippa/function/collections.py +228 -0
  79. gippa/function/elim.py +82 -0
  80. gippa/function/gsea.py +665 -0
  81. gippa/function/ora.py +476 -0
  82. gippa/function/running.py +80 -0
  83. gippa/function/sensitivity.py +73 -0
  84. gippa/function/summary.py +108 -0
  85. gippa/genetable/__init__.py +5 -0
  86. gippa/genetable/abundance.py +339 -0
  87. gippa/genetable/assemble.py +261 -0
  88. gippa/genetable/contract.py +641 -0
  89. gippa/genetable/covariates.py +232 -0
  90. gippa/genetable/detectability.py +412 -0
  91. gippa/genetable/detection.py +458 -0
  92. gippa/genetable/eligibility.py +16 -0
  93. gippa/genetable/evidence.py +308 -0
  94. gippa/io/__init__.py +1 -0
  95. gippa/io/_lines.py +37 -0
  96. gippa/io/_open.py +51 -0
  97. gippa/io/abundance.py +204 -0
  98. gippa/io/bed.py +130 -0
  99. gippa/io/cog_categories.py +22 -0
  100. gippa/io/compare_io.py +159 -0
  101. gippa/io/context_tsv.py +150 -0
  102. gippa/io/converters/__init__.py +24 -0
  103. gippa/io/converters/base.py +38 -0
  104. gippa/io/converters/cascadia.py +172 -0
  105. gippa/io/converters/diann.py +203 -0
  106. gippa/io/converters/generic.py +91 -0
  107. gippa/io/converters/strip.py +30 -0
  108. gippa/io/depth.py +59 -0
  109. gippa/io/determinants_tables.py +88 -0
  110. gippa/io/events.py +43 -0
  111. gippa/io/fasta.py +112 -0
  112. gippa/io/fgsea_r.py +519 -0
  113. gippa/io/function_resources.py +61 -0
  114. gippa/io/function_tables.py +59 -0
  115. gippa/io/genes_tsv.py +124 -0
  116. gippa/io/gff3.py +220 -0
  117. gippa/io/image_key.py +144 -0
  118. gippa/io/issues.py +33 -0
  119. gippa/io/manifest_sources.py +204 -0
  120. gippa/io/novel_tables.py +94 -0
  121. gippa/io/operon_tables.py +59 -0
  122. gippa/io/output_checks.py +135 -0
  123. gippa/io/parquet.py +32 -0
  124. gippa/io/peptides.py +402 -0
  125. gippa/io/plot_files.py +193 -0
  126. gippa/io/plot_support.py +48 -0
  127. gippa/io/probed.py +79 -0
  128. gippa/io/protein_fasta.py +141 -0
  129. gippa/io/regions_tsv.py +63 -0
  130. gippa/io/run_view.py +362 -0
  131. gippa/io/sensitivity_tsv.py +30 -0
  132. gippa/io/summary_tsv.py +63 -0
  133. gippa/io/tsv.py +111 -0
  134. gippa/io/variant_tables.py +70 -0
  135. gippa/io/vcf.py +265 -0
  136. gippa/io/work_tables.py +233 -0
  137. gippa/makedb/__init__.py +4 -0
  138. gippa/makedb/build.py +437 -0
  139. gippa/manifest.py +462 -0
  140. gippa/mapping/__init__.py +1 -0
  141. gippa/mapping/chance.py +84 -0
  142. gippa/mapping/contaminants.py +307 -0
  143. gippa/mapping/decoys.py +101 -0
  144. gippa/mapping/denovo_precision.py +205 -0
  145. gippa/mapping/diffops.py +98 -0
  146. gippa/mapping/digest.py +61 -0
  147. gippa/mapping/exact.py +206 -0
  148. gippa/mapping/frameshift.py +780 -0
  149. gippa/mapping/hits.py +46 -0
  150. gippa/mapping/initiator.py +230 -0
  151. gippa/mapping/loci.py +537 -0
  152. gippa/mapping/masses.py +116 -0
  153. gippa/mapping/miniprot.py +226 -0
  154. gippa/mapping/outputs.py +353 -0
  155. gippa/mapping/placement_fdr.py +564 -0
  156. gippa/mapping/protein_assign.py +239 -0
  157. gippa/mapping/qgram.py +236 -0
  158. gippa/mapping/readthrough.py +250 -0
  159. gippa/mapping/sixframe.py +436 -0
  160. gippa/mapping/tblastn.py +378 -0
  161. gippa/mapping/tolerant.py +343 -0
  162. gippa/mapping/tolerant_dp.py +342 -0
  163. gippa/model/__init__.py +43 -0
  164. gippa/model/coords.py +129 -0
  165. gippa/model/genes.py +176 -0
  166. gippa/model/genetic_code.py +77 -0
  167. gippa/novel/__init__.py +1 -0
  168. gippa/novel/candidates.py +352 -0
  169. gippa/novel/class_call.py +194 -0
  170. gippa/novel/evidence.py +376 -0
  171. gippa/novel/fdr.py +430 -0
  172. gippa/novel/outputs.py +367 -0
  173. gippa/operons/__init__.py +1 -0
  174. gippa/operons/detection_limit.py +554 -0
  175. gippa/operons/discordance.py +319 -0
  176. gippa/operons/flags.py +269 -0
  177. gippa/operons/missed.py +263 -0
  178. gippa/operons/pairs.py +293 -0
  179. gippa/operons/tu_table.py +84 -0
  180. gippa/preflight/__init__.py +1 -0
  181. gippa/preflight/checks.py +59 -0
  182. gippa/preflight/environment.py +1075 -0
  183. gippa/preflight/evidence.py +467 -0
  184. gippa/preflight/gbff.py +80 -0
  185. gippa/preflight/genetic_code.py +205 -0
  186. gippa/preflight/genome.py +317 -0
  187. gippa/preflight/report.py +189 -0
  188. gippa/preflight/run.py +668 -0
  189. gippa/preflight/tool_genetic_code.py +113 -0
  190. gippa/py.typed +0 -0
  191. gippa/report/__init__.py +1 -0
  192. gippa/report/build.py +372 -0
  193. gippa/report/checks.py +154 -0
  194. gippa/report/compare.py +66 -0
  195. gippa/report/content.py +378 -0
  196. gippa/report/snippets/islands.html +3 -0
  197. gippa/report/tables.py +41 -0
  198. gippa/report/templates/compare.html.j2 +97 -0
  199. gippa/report/templates/plotly_block.html.j2 +10 -0
  200. gippa/report/templates/report.css +16 -0
  201. gippa/report/templates/report.html.j2 +191 -0
  202. gippa/report/templates/tables.js +47 -0
  203. gippa/rng.py +16 -0
  204. gippa/runner/__init__.py +1 -0
  205. gippa/runner/local.py +393 -0
  206. gippa/runner/nextflow.py +284 -0
  207. gippa/runner/prereqs.py +155 -0
  208. gippa/runner/profiles.py +253 -0
  209. gippa/runner/workflow_path.py +16 -0
  210. gippa/schemas/__init__.py +17 -0
  211. gippa/schemas/annotation/id_map.schema.json +15 -0
  212. gippa/schemas/annotation/operon_pairs.schema.json +11 -0
  213. gippa/schemas/annotation/regions.schema.json +17 -0
  214. gippa/schemas/annotation/terminators_crosscheck.schema.json +12 -0
  215. gippa/schemas/annotation/tus.schema.json +16 -0
  216. gippa/schemas/config.schema.json +1407 -0
  217. gippa/schemas/datapackage.json +21 -0
  218. gippa/schemas/db_manifest.schema.json +52 -0
  219. gippa/schemas/plot_support/determinants_pd.json +12 -0
  220. gippa/schemas/plot_support/determinants_shap.json +11 -0
  221. gippa/schemas/plot_support/gc_skew.json +13 -0
  222. gippa/schemas/plot_support/gsea_running.json +14 -0
  223. gippa/schemas/plot_support/null_determinants.json +11 -0
  224. gippa/schemas/plot_support/null_islands.json +12 -0
  225. gippa/schemas/plot_support/null_regions.json +13 -0
  226. gippa/schemas/plot_support/null_regions_grid.json +13 -0
  227. gippa/schemas/plot_support/replicon_landmarks.json +14 -0
  228. gippa/schemas/plot_support/variant_windows.json +13 -0
  229. gippa/schemas/preflight.schema.json +114 -0
  230. gippa/schemas/run_manifest.schema.json +293 -0
  231. gippa/schemas/tables/autocorrelation.schema.json +24 -0
  232. gippa/schemas/tables/category_summary.schema.json +20 -0
  233. gippa/schemas/tables/compare_abundance.schema.json +21 -0
  234. gippa/schemas/tables/compare_detection.schema.json +15 -0
  235. gippa/schemas/tables/compare_stability.schema.json +16 -0
  236. gippa/schemas/tables/detection_summary.schema.json +16 -0
  237. gippa/schemas/tables/determinants_attribution.schema.json +19 -0
  238. gippa/schemas/tables/determinants_features.schema.json +44 -0
  239. gippa/schemas/tables/determinants_model.schema.json +16 -0
  240. gippa/schemas/tables/enrichment_detection.schema.json +33 -0
  241. gippa/schemas/tables/enrichment_gsea.schema.json +29 -0
  242. gippa/schemas/tables/genes.schema.json +82 -0
  243. gippa/schemas/tables/islands.schema.json +32 -0
  244. gippa/schemas/tables/mapping_fdr.schema.json +22 -0
  245. gippa/schemas/tables/novel_orfs.schema.json +34 -0
  246. gippa/schemas/tables/operon_flags.schema.json +19 -0
  247. gippa/schemas/tables/operons.schema.json +19 -0
  248. gippa/schemas/tables/pairs.schema.json +37 -0
  249. gippa/schemas/tables/peptide_hits.schema.json +30 -0
  250. gippa/schemas/tables/pileup.schema.json +18 -0
  251. gippa/schemas/tables/protein_hits.schema.json +22 -0
  252. gippa/schemas/tables/regions_stats.schema.json +32 -0
  253. gippa/schemas/tables/sensitivity.schema.json +14 -0
  254. gippa/schemas/tables/unmapped.schema.json +13 -0
  255. gippa/schemas/tables/variants.schema.json +28 -0
  256. gippa/setupdb/__init__.py +50 -0
  257. gippa/setupdb/dbdir.py +116 -0
  258. gippa/setupdb/images.py +259 -0
  259. gippa/setupdb/installed.py +29 -0
  260. gippa/setupdb/items.py +342 -0
  261. gippa/setupdb/manifest.py +133 -0
  262. gippa/setupdb/run.py +719 -0
  263. gippa/setupdb/space.py +262 -0
  264. gippa/setupdb/uniop_build.py +429 -0
  265. gippa/stats/__init__.py +1 -0
  266. gippa/stats/_fgsea_kernels.py +110 -0
  267. gippa/stats/autocorr.py +504 -0
  268. gippa/stats/bootstrap.py +168 -0
  269. gippa/stats/cutoffs.py +537 -0
  270. gippa/stats/decoy_fdr.py +394 -0
  271. gippa/stats/fgsea.py +226 -0
  272. gippa/stats/mayu.py +167 -0
  273. gippa/stats/multitest.py +38 -0
  274. gippa/stats/permutation.py +156 -0
  275. gippa/stats/poisson_binomial.py +281 -0
  276. gippa/stats/robust.py +130 -0
  277. gippa/stats/scan.py +299 -0
  278. gippa/stats/scores.py +54 -0
  279. gippa/stats/smooth.py +237 -0
  280. gippa/stats/tda.py +143 -0
  281. gippa/stats/tdist.py +348 -0
  282. gippa/stats/wallenius.py +110 -0
  283. gippa/steps/__init__.py +14 -0
  284. gippa/steps/annotation.py +151 -0
  285. gippa/steps/codon.py +87 -0
  286. gippa/steps/common.py +56 -0
  287. gippa/steps/context.py +165 -0
  288. gippa/steps/context_stats.py +344 -0
  289. gippa/steps/detectability.py +111 -0
  290. gippa/steps/determinants.py +242 -0
  291. gippa/steps/function.py +193 -0
  292. gippa/steps/gene_table.py +164 -0
  293. gippa/steps/manifest.py +165 -0
  294. gippa/steps/mapping.py +631 -0
  295. gippa/steps/novel.py +543 -0
  296. gippa/steps/operons.py +334 -0
  297. gippa/steps/plots.py +298 -0
  298. gippa/steps/promoters.py +96 -0
  299. gippa/steps/rbs.py +68 -0
  300. gippa/steps/regions.py +162 -0
  301. gippa/steps/registry.py +329 -0
  302. gippa/steps/sensitivity.py +302 -0
  303. gippa/steps/terminators.py +148 -0
  304. gippa/steps/transcription.py +55 -0
  305. gippa/steps/variants.py +243 -0
  306. gippa/variants/__init__.py +1 -0
  307. gippa/variants/align.py +373 -0
  308. gippa/variants/error_rates.py +124 -0
  309. gippa/variants/flags.py +211 -0
  310. gippa/variants/pileup.py +357 -0
  311. gippa/variants/qc.py +66 -0
  312. gippa/variants/vcf_records.py +201 -0
  313. gippa/viz/__init__.py +1 -0
  314. gippa/viz/base.py +218 -0
  315. gippa/viz/checklist.py +371 -0
  316. gippa/viz/circos.py +640 -0
  317. gippa/viz/compare.py +301 -0
  318. gippa/viz/correlogram.py +212 -0
  319. gippa/viz/detection.py +409 -0
  320. gippa/viz/determinants.py +425 -0
  321. gippa/viz/diagnostics.py +280 -0
  322. gippa/viz/enrichment.py +557 -0
  323. gippa/viz/interactive.py +60 -0
  324. gippa/viz/landscape.py +302 -0
  325. gippa/viz/locus.py +593 -0
  326. gippa/viz/mapping_fdr.py +332 -0
  327. gippa/viz/mapping_sankey.py +231 -0
  328. gippa/viz/novel_orfs.py +216 -0
  329. gippa/viz/null_panels.py +236 -0
  330. gippa/viz/operon_pairs.py +328 -0
  331. gippa/viz/qc.py +313 -0
  332. gippa/viz/style.py +147 -0
  333. gippa/viz/support.py +211 -0
  334. gippa/viz/twins.py +304 -0
  335. gippa/viz/twins_determinants.py +109 -0
  336. gippa/viz/twins_panels.py +286 -0
  337. gippa/viz/variants.py +368 -0
  338. gippa/workflow/assets/ps/ps +16 -0
  339. gippa/workflow/assets/stub/bakta.json +52 -0
  340. gippa/workflow/bin/gippa_fgsea.R +144 -0
  341. gippa/workflow/bin/gippa_terminatornet_shard +292 -0
  342. gippa/workflow/conf/base.config +73 -0
  343. gippa/workflow/conf/container_common.config +14 -0
  344. gippa/workflow/conf/containers.config +50 -0
  345. gippa/workflow/conf/farmshare.config +41 -0
  346. gippa/workflow/conf/farmshare_local.config +24 -0
  347. gippa/workflow/conf/images.json +65 -0
  348. gippa/workflow/conf/prebuilt_envs.config +20 -0
  349. gippa/workflow/conf/slurm.config +18 -0
  350. gippa/workflow/conf/standard.config +2 -0
  351. gippa/workflow/conf/test.config +4 -0
  352. gippa/workflow/containers/bactermfinder/Dockerfile +26 -0
  353. gippa/workflow/containers/fgsea/Dockerfile +18 -0
  354. gippa/workflow/containers/ostir/Dockerfile +18 -0
  355. gippa/workflow/containers/promotech/Dockerfile +31 -0
  356. gippa/workflow/containers/promoter_calculator/Dockerfile +18 -0
  357. gippa/workflow/containers/step/Dockerfile +42 -0
  358. gippa/workflow/containers/step/Dockerfile.dockerignore +7 -0
  359. gippa/workflow/containers/step/build-requirements.lock +7 -0
  360. gippa/workflow/containers/step/licences.allow +37 -0
  361. gippa/workflow/containers/step/requirements.lock +1871 -0
  362. gippa/workflow/containers/step-r/Dockerfile +46 -0
  363. gippa/workflow/containers/step-r/Dockerfile.dockerignore +7 -0
  364. gippa/workflow/containers/terminatornet/Dockerfile +26 -0
  365. gippa/workflow/containers/uniop/Dockerfile +19 -0
  366. gippa/workflow/containers/uniop/uniop.def +30 -0
  367. gippa/workflow/envs/antismash.yml +5 -0
  368. gippa/workflow/envs/bactermfinder.yml +19 -0
  369. gippa/workflow/envs/bakta.yml +5 -0
  370. gippa/workflow/envs/blast.yml +5 -0
  371. gippa/workflow/envs/cascadia.yml +47 -0
  372. gippa/workflow/envs/fgsea.yml +11 -0
  373. gippa/workflow/envs/genomad.yml +5 -0
  374. gippa/workflow/envs/locks/bactermfinder.explicit.txt +64 -0
  375. gippa/workflow/envs/locks/bactermfinder.pip.txt +50 -0
  376. gippa/workflow/envs/locks/cascadia.explicit.txt +254 -0
  377. gippa/workflow/envs/locks/cascadia.pip.txt +142 -0
  378. gippa/workflow/envs/locks/fgsea.explicit.txt +127 -0
  379. gippa/workflow/envs/locks/miniprot.explicit.txt +9 -0
  380. gippa/workflow/envs/locks/ostir.explicit.txt +48 -0
  381. gippa/workflow/envs/locks/ostir.pip.txt +3 -0
  382. gippa/workflow/envs/locks/promotech.explicit.txt +154 -0
  383. gippa/workflow/envs/locks/promotech.pip.txt +96 -0
  384. gippa/workflow/envs/locks/promoter_calculator.explicit.txt +49 -0
  385. gippa/workflow/envs/locks/promoter_calculator.pip.txt +9 -0
  386. gippa/workflow/envs/locks/terminatornet.explicit.txt +49 -0
  387. gippa/workflow/envs/locks/terminatornet.pip.txt +7 -0
  388. gippa/workflow/envs/locks/timsconvert.explicit.txt +30 -0
  389. gippa/workflow/envs/locks/timsconvert.pip.txt +37 -0
  390. gippa/workflow/envs/locks/uniop.explicit.txt +53 -0
  391. gippa/workflow/envs/locks/uniop.pip.txt +16 -0
  392. gippa/workflow/envs/miniprot.yml +5 -0
  393. gippa/workflow/envs/ostir.yml +10 -0
  394. gippa/workflow/envs/promotech.yml +28 -0
  395. gippa/workflow/envs/promoter_calculator.yml +13 -0
  396. gippa/workflow/envs/terminatornet.yml +12 -0
  397. gippa/workflow/envs/timsconvert.yml +20 -0
  398. gippa/workflow/envs/transtermhp.yml +5 -0
  399. gippa/workflow/envs/uniop.yml +11 -0
  400. gippa/workflow/main.nf +150 -0
  401. gippa/workflow/modules/local/antismash/main.nf +41 -0
  402. gippa/workflow/modules/local/antismash/meta.yml +17 -0
  403. gippa/workflow/modules/local/bactermfinder/main.nf +44 -0
  404. gippa/workflow/modules/local/bactermfinder/meta.yml +7 -0
  405. gippa/workflow/modules/local/bakta/main.nf +52 -0
  406. gippa/workflow/modules/local/bakta/meta.yml +11 -0
  407. gippa/workflow/modules/local/genomad/main.nf +39 -0
  408. gippa/workflow/modules/local/genomad/meta.yml +7 -0
  409. gippa/workflow/modules/local/gippa_step/main.nf +119 -0
  410. gippa/workflow/modules/local/gippa_step/meta.yml +32 -0
  411. gippa/workflow/modules/local/miniprot/main.nf +40 -0
  412. gippa/workflow/modules/local/miniprot/meta.yml +27 -0
  413. gippa/workflow/modules/local/ostir/main.nf +35 -0
  414. gippa/workflow/modules/local/ostir/meta.yml +7 -0
  415. gippa/workflow/modules/local/promotech/main.nf +40 -0
  416. gippa/workflow/modules/local/promotech/meta.yml +7 -0
  417. gippa/workflow/modules/local/promoter_calculator/main.nf +44 -0
  418. gippa/workflow/modules/local/promoter_calculator/meta.yml +7 -0
  419. gippa/workflow/modules/local/tblastn/main.nf +40 -0
  420. gippa/workflow/modules/local/tblastn/meta.yml +27 -0
  421. gippa/workflow/modules/local/terminatornet/main.nf +36 -0
  422. gippa/workflow/modules/local/terminatornet/meta.yml +7 -0
  423. gippa/workflow/modules/local/terminatornet_chunk/main.nf +24 -0
  424. gippa/workflow/modules/local/terminatornet_merge/main.nf +36 -0
  425. gippa/workflow/modules/local/terminatornet_plan/main.nf +26 -0
  426. gippa/workflow/modules/local/transtermhp/main.nf +34 -0
  427. gippa/workflow/modules/local/transtermhp/meta.yml +7 -0
  428. gippa/workflow/modules/local/uniop/main.nf +36 -0
  429. gippa/workflow/modules/local/uniop/meta.yml +7 -0
  430. gippa/workflow/nextflow.config +88 -0
  431. gippa/workflow/subworkflows/local/annotate.nf +273 -0
  432. gippa/workflow/subworkflows/local/context.nf +81 -0
  433. gippa/workflow/subworkflows/local/determinants.nf +39 -0
  434. gippa/workflow/subworkflows/local/function.nf +37 -0
  435. gippa/workflow/subworkflows/local/genetable.nf +58 -0
  436. gippa/workflow/subworkflows/local/mapping.nf +82 -0
  437. gippa/workflow/subworkflows/local/novel.nf +101 -0
  438. gippa/workflow/subworkflows/local/operons.nf +37 -0
  439. gippa/workflow/subworkflows/local/report.nf +26 -0
  440. gippa/workflow/subworkflows/local/sensitivity.nf +38 -0
  441. gippa/workflow/subworkflows/local/terminatornet_sharded.nf +23 -0
  442. gippa/workflow/subworkflows/local/variants.nf +75 -0
  443. gippa-1.0.0.dist-info/METADATA +164 -0
  444. gippa-1.0.0.dist-info/RECORD +447 -0
  445. gippa-1.0.0.dist-info/WHEEL +4 -0
  446. gippa-1.0.0.dist-info/entry_points.txt +2 -0
  447. gippa-1.0.0.dist-info/licenses/LICENSE +21 -0
gippa/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """GIPPA: genome integrated prokaryotic proteomics analysis."""
2
+
3
+ __version__ = "1.0.0"
@@ -0,0 +1 @@
1
+ """Module 1 tool wrappers: build tool inputs and parse tool outputs (never import the tool)."""
@@ -0,0 +1,82 @@
1
+ """antiSMASH 8.0.4 JSON parser: BGC regions (GIPPA_SPEC.md §7.1.1, §7.1.7; M2-18).
2
+
3
+ ``records[].id`` = contig; ``records[].areas[]`` ``start``/``end`` are 0-based half-open;
4
+ ``start > end`` on a circular record is origin-crossing and stored unwrapped (``end + length``),
5
+ on a linear record it is corrupt input. Products keep antiSMASH order, duplicates collapsed.
6
+ Core genes: ``locus_tag`` of CDS features wholly inside the area whose ``gene_kind`` qualifier
7
+ contains exactly ``biosynthetic`` (M2-18 lead decision 1), in feature order.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import re
14
+ from collections.abc import Mapping
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from gippa.model.coords import Interval, Replicon, check_interval
20
+
21
+ # Biopython location parts: ``[a:b](±)``, fuzzy ``[<a:b]`` / ``[a:>b]``, inside ``join{…}``.
22
+ _PART = re.compile(r"\[<?(\d+):>?(\d+)\]")
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class BgcRegion:
27
+ contig: str
28
+ interval: Interval
29
+ products: tuple[str, ...]
30
+ core_genes: tuple[str, ...]
31
+
32
+
33
+ def _area_interval(area: Mapping[str, Any], rep: Replicon) -> Interval:
34
+ start, end = int(area["start"]), int(area["end"])
35
+ if start > end:
36
+ if rep.topology != "circular":
37
+ raise ValueError(f"antiSMASH: area {rep.id}:{start}-{end} wraps on a linear contig")
38
+ end += rep.length
39
+ iv = Interval(rep.id, start, end, ".")
40
+ try:
41
+ check_interval(iv, rep)
42
+ except ValueError as exc:
43
+ raise ValueError(f"antiSMASH: area on contig {rep.id!r} out of range: {exc}") from exc
44
+ return iv
45
+
46
+
47
+ def _inside(location: str, area: Interval, length: int) -> bool:
48
+ parts = [(int(a), int(b)) for a, b in _PART.findall(location)]
49
+ if not parts:
50
+ raise ValueError(f"antiSMASH: cannot parse CDS location {location!r}")
51
+ return all(
52
+ any(area.start <= a + s and b + s <= area.end for s in (0, length)) for a, b in parts
53
+ )
54
+
55
+
56
+ def _core_genes(features: list[Mapping[str, Any]], area: Interval, length: int) -> tuple[str, ...]:
57
+ tags: dict[str, None] = {}
58
+ for f in features:
59
+ q = f.get("qualifiers", {})
60
+ if f.get("type") != "CDS" or "biosynthetic" not in q.get("gene_kind", []):
61
+ continue
62
+ if _inside(f["location"], area, length):
63
+ tags.update(dict.fromkeys(q.get("locus_tag", [])))
64
+ return tuple(tags)
65
+
66
+
67
+ def parse_antismash_json(path: Path, replicons: Mapping[str, Replicon]) -> list[BgcRegion]:
68
+ """BGC regions of an antiSMASH JSON, in record then area order."""
69
+ data = json.loads(path.read_text())
70
+ out: list[BgcRegion] = []
71
+ for rec in data.get("records", []):
72
+ rid = rec["id"]
73
+ if rid not in replicons:
74
+ raise ValueError(f"antiSMASH: record {rid!r} not in the genome")
75
+ rep = replicons[rid]
76
+ for area in rec.get("areas", []):
77
+ iv = _area_interval(area, rep)
78
+ products = tuple(dict.fromkeys(area.get("products", [])))
79
+ out.append(
80
+ BgcRegion(rid, iv, products, _core_genes(rec.get("features", []), iv, rep.length))
81
+ )
82
+ return out
@@ -0,0 +1,35 @@
1
+ """antiSMASH command line (GIPPA_SPEC.md §7.1.1; M2-18).
2
+
3
+ antiSMASH is AGPL-3.0: it is never imported, only run as a separate process (R5). Kept in sync
4
+ with ``workflow/modules/local/antismash/main.nf``, whose task shell passes
5
+ ``--databases <db_dir>/antismash`` only when that path is a directory with content and otherwise
6
+ leaves antiSMASH on the image's bundled databases. This supersedes M2-18 lead decisions 2 and 4
7
+ (``--databases`` always passed and required) per #515.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+
15
+ def antismash_command(gbff: Path, outdir: Path, threads: int, databases: Path | None) -> list[str]:
16
+ """``antismash --genefinding-tool none --minimal [--databases <db>] --cpus <n> --output-dir
17
+ <outdir> <gbff>``.
18
+
19
+ ``databases`` stays a required argument; ``None`` omits the flag (the databases of the image,
20
+ which is what ``gippa setup`` records under a container profile). Supersedes M2-18 lead
21
+ decisions 2 and 4 per #515.
22
+ """
23
+ flag = [] if databases is None else ["--databases", str(databases)]
24
+ return [
25
+ "antismash",
26
+ "--genefinding-tool",
27
+ "none",
28
+ "--minimal",
29
+ *flag,
30
+ "--cpus",
31
+ str(threads),
32
+ "--output-dir",
33
+ str(outdir),
34
+ str(gbff),
35
+ ]
@@ -0,0 +1,324 @@
1
+ """Defensive parser for Bakta 1.12.2 JSON (GIPPA_SPEC.md §7.1.1, §7.1.2, §6.5; M2-03).
2
+
3
+ ``parse_bakta_json`` returns the shared M3-01 ``GeneModel`` plus the contig sequences, the Bakta
4
+ and DB versions and a count of skipped (unknown) feature types. Field-name variations are
5
+ tolerated: a feature's contig is ``sequence`` or ``contig``; sequence records are ``sequences``
6
+ or ``contigs`` with nucleotides in ``nt`` or ``sequence``. Bakta's 1-based closed ``start``/``stop``
7
+ become 0-based half-open intervals; ``start > stop`` (``edge``) is unwrapped to ``end > length`` on
8
+ circular replicons and is an error on linear ones. Every malformed input raises
9
+ ``BaktaParseError`` (a ``ValueError``) naming the feature.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import logging
15
+ import re
16
+ from collections import Counter
17
+ from collections.abc import Mapping
18
+ from dataclasses import dataclass
19
+ from types import MappingProxyType
20
+ from typing import TYPE_CHECKING, Any, Literal, cast
21
+
22
+ from gippa.io._open import read_json
23
+ from gippa.model.coords import (
24
+ Interval,
25
+ Replicon,
26
+ RepliconKind,
27
+ Strand,
28
+ from_one_based_closed,
29
+ interval_sort_key,
30
+ )
31
+ from gippa.model.genes import _NC_TYPES, Gene, GeneModel, GeneType, NcFeature
32
+
33
+ if TYPE_CHECKING:
34
+ from _typeshed import StrPath
35
+
36
+ DbType = Literal["full", "light", "unknown"]
37
+
38
+ _HYPOTHETICAL = re.compile(r"^hypothetical protein")
39
+ _COMP = str.maketrans("ACGTNacgtn", "TGCANtgcan")
40
+ _GENE_TYPES = ("cds", "sorf")
41
+
42
+
43
+ _log = logging.getLogger(__name__)
44
+
45
+
46
+ class BaktaParseError(ValueError):
47
+ """Malformed or inconsistent Bakta JSON."""
48
+
49
+
50
+ class AnnotationTableMismatch(ValueError):
51
+ """The JSON's ``genome.translation_table`` differs from the run's table (M11-16 item 3.4)."""
52
+
53
+
54
+ def _check_table(data: Mapping[str, Any], table: int) -> None:
55
+ genome = data.get("genome")
56
+ recorded = genome.get("translation_table") if isinstance(genome, dict) else None
57
+ if recorded is None:
58
+ _log.info(
59
+ "annotation.table_unrecorded: the annotation JSON records no translation table; "
60
+ "assuming %d",
61
+ table,
62
+ extra={"event": "annotation.table_unrecorded", "data": {"translation_table": table}},
63
+ )
64
+ return
65
+ if not isinstance(recorded, int) or isinstance(recorded, bool):
66
+ raise BaktaParseError(
67
+ f"annotation JSON genome.translation_table must be an integer, got {recorded!r:.100}"
68
+ )
69
+ if recorded != table:
70
+ raise AnnotationTableMismatch(
71
+ f"annotation JSON was made with translation table {recorded}; this run uses {table}: "
72
+ f"re-run Bakta with --translation-table {table} or set "
73
+ f"annotation.translation_table: {recorded}"
74
+ )
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class BaktaAnnotation:
79
+ model: GeneModel
80
+ sequences: Mapping[str, str]
81
+ bakta_version: str
82
+ db_version: str
83
+ db_type: DbType
84
+ skipped_types: Mapping[str, int]
85
+
86
+
87
+ def _revcomp(s: str) -> str:
88
+ return s.translate(_COMP)[::-1]
89
+
90
+
91
+ def interval_nt(seq: str, iv: Interval) -> str:
92
+ """Strand-aware, origin-aware nucleotides of ``iv`` (``end`` may exceed ``len(seq)``)."""
93
+ n = len(seq)
94
+ s = seq[iv.start : iv.end] if iv.end <= n else seq[iv.start :] + seq[: iv.end - n]
95
+ return _revcomp(s) if iv.strand == "-" else s
96
+
97
+
98
+ def _record_nt(rec: Mapping[str, Any], cid: str) -> str | None:
99
+ nt = rec.get("nt")
100
+ if nt is None:
101
+ nt = rec.get("sequence")
102
+ if nt is not None and not isinstance(nt, str):
103
+ raise BaktaParseError(f"sequence {cid!r}: nt must be a string, got {type(nt).__name__}")
104
+ return nt or None
105
+
106
+
107
+ def _replicons_from_json(
108
+ records: list[Any],
109
+ replicons: Mapping[str, Replicon] | None,
110
+ genome: Mapping[str, str] | None,
111
+ ) -> tuple[dict[str, Replicon], dict[str, str]]:
112
+ reps: dict[str, Replicon] = {}
113
+ seqs: dict[str, str] = {}
114
+ for rec in records:
115
+ if not isinstance(rec, dict) or not rec.get("id"):
116
+ raise BaktaParseError(f"sequence record without id: {rec!r:.200}")
117
+ cid = str(rec["id"])
118
+ nt = _record_nt(rec, cid)
119
+ if replicons is not None and cid not in replicons:
120
+ raise BaktaParseError(f"sequence {cid!r} is not among the given replicons")
121
+ if nt is None and genome is not None and cid in genome:
122
+ nt = genome[cid]
123
+ if nt is not None:
124
+ seqs[cid] = nt
125
+ elif replicons is None:
126
+ raise BaktaParseError(
127
+ f"sequence {cid!r} has no nucleotides in the JSON and no genome was given"
128
+ )
129
+ kind = str(rec.get("type", "")).lower()
130
+ topology = str(rec.get("topology") or "linear").lower()
131
+ if topology not in ("circular", "linear"):
132
+ raise BaktaParseError(f"sequence {cid!r}: unknown topology {topology!r}")
133
+ length = replicons[cid].length if replicons is not None else len(seqs[cid])
134
+ declared = rec.get("length")
135
+ if declared is not None and declared != length:
136
+ raise BaktaParseError(
137
+ f"sequence {cid!r}: JSON length {declared!r} != genome sequence length {length}"
138
+ )
139
+ if cid in seqs and len(seqs[cid]) != length:
140
+ raise BaktaParseError(
141
+ f"sequence {cid!r}: nucleotide length {len(seqs[cid])} != replicon length {length}"
142
+ )
143
+ reps[cid] = Replicon(
144
+ cid,
145
+ length,
146
+ "circular" if topology == "circular" else "linear",
147
+ cast(RepliconKind, kind if kind in ("chromosome", "plasmid") else "unknown"),
148
+ )
149
+ if replicons is None:
150
+ return reps, seqs
151
+ for cid in replicons:
152
+ if cid not in seqs and genome is not None and cid in genome:
153
+ seqs[cid] = genome[cid]
154
+ return dict(replicons), seqs
155
+
156
+
157
+ def _int(f: Mapping[str, Any], key: str, name: str) -> int:
158
+ v = f.get(key)
159
+ if isinstance(v, bool) or not isinstance(v, int):
160
+ raise BaktaParseError(f"feature {name!r}: {key} must be an int, got {v!r}")
161
+ return v
162
+
163
+
164
+ def _opt_str(f: Mapping[str, Any], key: str, name: str) -> str | None:
165
+ v = f.get(key)
166
+ if v is not None and not isinstance(v, str):
167
+ raise BaktaParseError(f"feature {name!r}: {key} must be a string, got {type(v).__name__}")
168
+ return v
169
+
170
+
171
+ def _expert(f: Mapping[str, Any], name: str) -> tuple[str, ...]:
172
+ raw = f.get("expert")
173
+ if raw is None:
174
+ return ()
175
+ if not isinstance(raw, list) or not all(isinstance(e, dict) for e in raw):
176
+ raise BaktaParseError(
177
+ f"feature {name!r}: expert must be a list of objects, got {raw!r:.200}"
178
+ )
179
+ out: list[str] = []
180
+ for e in raw:
181
+ src = e.get("type") or e.get("source")
182
+ if src and str(src) not in out:
183
+ out.append(str(src))
184
+ return tuple(out)
185
+
186
+
187
+ def _interval(
188
+ f: Mapping[str, Any], name: str, replicons: Mapping[str, Replicon], *, coding: bool
189
+ ) -> Interval:
190
+ contig = f.get("sequence", f.get("contig"))
191
+ if contig is None:
192
+ raise BaktaParseError(f"feature {name!r}: no 'sequence' or 'contig' field")
193
+ if contig not in replicons:
194
+ raise BaktaParseError(f"feature {name!r}: unknown contig {contig!r}")
195
+ raw = f.get("strand")
196
+ if raw not in ("+", "-"):
197
+ if coding or raw not in (None, "?", "."):
198
+ raise BaktaParseError(f"feature {name!r}: invalid strand {raw!r}")
199
+ raw = "."
200
+ start, stop = _int(f, "start", name), _int(f, "stop", name)
201
+ try:
202
+ return from_one_based_closed(contig, start, stop, cast(Strand, raw), replicons[contig])
203
+ except ValueError as exc:
204
+ raise BaktaParseError(f"feature {name!r}: {exc}") from exc
205
+
206
+
207
+ def _gene(f: Mapping[str, Any], replicons: Mapping[str, Replicon], seqs: Mapping[str, str]) -> Gene:
208
+ if not f.get("locus"):
209
+ raise BaktaParseError(f"{f.get('type')} feature without locus: {f!r:.200}")
210
+ locus = str(f["locus"])
211
+ iv = _interval(f, locus, replicons, coding=True)
212
+ ftype = cast(GeneType, str(f["type"]).lower())
213
+ if ftype == "cds" and (f.get("pseudo") or "pseudogene" in f):
214
+ ftype = "pseudogene"
215
+ xrefs = f.get("db_xrefs") or []
216
+ if not isinstance(xrefs, list | tuple):
217
+ raise BaktaParseError(f"feature {locus!r}: db_xrefs must be a list, got {xrefs!r}")
218
+ nt = _opt_str(f, "nt", locus) or (
219
+ interval_nt(seqs[iv.contig], iv) if iv.contig in seqs else None
220
+ )
221
+ product = f.get("product") or ""
222
+ hypothetical = (
223
+ bool(f["hypothetical"]) if "hypothetical" in f else bool(_HYPOTHETICAL.match(product))
224
+ )
225
+ rep = replicons[iv.contig]
226
+ at_end = rep.topology == "linear" and (iv.start == 0 or iv.end == rep.length)
227
+ aa = _opt_str(f, "aa", locus) or ""
228
+ start_type = f.get("start_type")
229
+ # Pyrodigal partial start ("Edge") is not a start codon (M3-01 Q3).
230
+ start_codon = None if start_type == "Edge" else start_type or (nt[:3] if nt else None)
231
+ return Gene(
232
+ locus_tag=locus,
233
+ feature_type=ftype,
234
+ interval=iv,
235
+ aa=aa[:-1] if aa.endswith("*") else aa,
236
+ nt=nt,
237
+ start_codon=start_codon,
238
+ gene=f.get("gene"),
239
+ product=product,
240
+ partial=bool(f.get("truncated")) or at_end,
241
+ hypothetical=hypothetical,
242
+ db_xrefs=tuple(str(x) for x in xrefs),
243
+ expert=_expert(f, locus),
244
+ )
245
+
246
+
247
+ def _nc(f: Mapping[str, Any], replicons: Mapping[str, Replicon]) -> NcFeature:
248
+ fid = str(f.get("id") or f.get("locus") or "")
249
+ anticodon = f.get("anti_codon")
250
+ return NcFeature(
251
+ id=fid,
252
+ type=_NC_TYPES[str(f["type"]).lower()],
253
+ interval=_interval(f, fid, replicons, coding=False),
254
+ gene=f.get("gene"),
255
+ product=f.get("product") or "",
256
+ amino_acid=f.get("amino_acid"),
257
+ anticodon=str(anticodon).upper().replace("U", "T") if anticodon else None,
258
+ nt=_opt_str(f, "nt", fid),
259
+ )
260
+
261
+
262
+ def _versions(data: Mapping[str, Any]) -> tuple[str, str, DbType]:
263
+ version = data.get("version")
264
+ if not isinstance(version, dict):
265
+ return "", "", "unknown"
266
+ raw_db = version.get("db")
267
+ db: dict[str, Any] = raw_db if isinstance(raw_db, dict) else {}
268
+ db_type = str(db.get("type", "")).lower()
269
+ return (
270
+ str(version.get("bakta") or ""),
271
+ str(db.get("version") or ""),
272
+ cast(DbType, db_type if db_type in ("full", "light") else "unknown"),
273
+ )
274
+
275
+
276
+ def parse_bakta_json(
277
+ path: StrPath,
278
+ replicons: Mapping[str, Replicon] | None = None,
279
+ genome: Mapping[str, str] | None = None,
280
+ *,
281
+ translation_table: int | None = None,
282
+ ) -> BaktaAnnotation:
283
+ """Parse a (possibly gzipped) Bakta JSON; ``replicons`` (preflight's table) wins if given.
284
+
285
+ With ``translation_table``, a recorded ``genome.translation_table`` that differs raises
286
+ ``AnnotationTableMismatch``; an unrecorded one is assumed equal (logged at INFO).
287
+ """
288
+ data = read_json(path)
289
+ if not isinstance(data, dict):
290
+ raise BaktaParseError(f"{path}: top-level JSON must be an object")
291
+ if translation_table is not None:
292
+ _check_table(data, translation_table)
293
+ records = data.get("sequences", data.get("contigs")) or []
294
+ reps, seqs = _replicons_from_json(records, replicons, genome)
295
+ genes: list[Gene] = []
296
+ features: list[NcFeature] = []
297
+ skipped: Counter[str] = Counter()
298
+ seen: set[str] = set()
299
+ for f in data.get("features") or []:
300
+ if not isinstance(f, dict):
301
+ raise BaktaParseError(f"feature must be an object: {f!r:.200}")
302
+ t = str(f.get("type", "")).lower()
303
+ if t in _GENE_TYPES:
304
+ g = _gene(f, reps, seqs)
305
+ if g.locus_tag in seen:
306
+ raise BaktaParseError(f"duplicate locus tag {g.locus_tag}")
307
+ seen.add(g.locus_tag)
308
+ genes.append(g)
309
+ elif t in _NC_TYPES:
310
+ features.append(_nc(f, reps))
311
+ else:
312
+ skipped[str(f.get("type", ""))] += 1
313
+ order = {c: k for k, c in enumerate(reps)}
314
+ genes.sort(key=lambda g: interval_sort_key(g.interval, order, g.locus_tag))
315
+ features.sort(key=lambda x: interval_sort_key(x.interval, order, x.id))
316
+ bakta_version, db_version, db_type = _versions(data)
317
+ return BaktaAnnotation(
318
+ model=GeneModel(replicons=reps, genes=tuple(genes), features=tuple(features)),
319
+ sequences=MappingProxyType(seqs),
320
+ bakta_version=bakta_version,
321
+ db_version=db_version,
322
+ db_type=db_type,
323
+ skipped_types=MappingProxyType(dict(skipped)),
324
+ )
@@ -0,0 +1,64 @@
1
+ """Bakta 1.12.2 command line and database selection (GIPPA_SPEC.md §7.1.1, §5.2; M2-03).
2
+
3
+ Builds ``bakta --db DB --output OUT --prefix P --threads N --keep-contig-headers
4
+ [--complete] [--translation-table T] --replicons replicons.tsv [--regions regions.gff3]
5
+ [extra args] genome.norm.fa``.
6
+ Bakta itself is never imported (R5); it runs as a separate process.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import Sequence
12
+ from pathlib import Path
13
+ from typing import Literal
14
+
15
+ DbType = Literal["full", "light"]
16
+
17
+
18
+ def bakta_db_path(db_setting: str, db_dir: Path) -> tuple[Path, DbType]:
19
+ """Resolve ``annotation.bakta.db``: ``auto`` picks ``bakta/db`` if present, else ``db-light``.
20
+
21
+ ``full``/``light`` select the matching directory under ``db_dir/bakta``; anything else is a
22
+ path to a Bakta DB, typed ``light`` when its name contains ``light``.
23
+ """
24
+ base = db_dir / "bakta"
25
+ if db_setting == "auto":
26
+ return (base / "db", "full") if (base / "db").is_dir() else (base / "db-light", "light")
27
+ if db_setting == "full":
28
+ return base / "db", "full"
29
+ if db_setting == "light":
30
+ return base / "db-light", "light"
31
+ path = Path(db_setting)
32
+ return path, "light" if "light" in path.name else "full"
33
+
34
+
35
+ def bakta_command(
36
+ *,
37
+ genome: Path,
38
+ outdir: Path,
39
+ prefix: str,
40
+ db: Path,
41
+ threads: int,
42
+ all_circular: bool,
43
+ complete: bool | Literal["auto"],
44
+ replicons_tsv: Path,
45
+ regions_gff3: Path | None,
46
+ extra_args: Sequence[str],
47
+ translation_table: int,
48
+ ) -> list[str]:
49
+ """Bakta argv; ``--replicons`` always, ``--complete`` iff forced or (auto) all circular.
50
+
51
+ ``--translation-table T`` only when ``T != 11`` (Bakta's default; M11-16 D2), right before
52
+ ``--replicons``.
53
+ """
54
+ cmd = ["bakta", "--db", str(db), "--output", str(outdir), "--prefix", prefix]
55
+ cmd += ["--threads", str(threads), "--keep-contig-headers"]
56
+ use_complete = all_circular if complete == "auto" else bool(complete)
57
+ cmd += ["--complete"] if use_complete else []
58
+ cmd += [] if translation_table == 11 else ["--translation-table", str(translation_table)]
59
+ cmd += ["--replicons", str(replicons_tsv)]
60
+ if regions_gff3 is not None:
61
+ cmd += ["--regions", str(regions_gff3)]
62
+ cmd += [str(a) for a in extra_args]
63
+ cmd.append(str(genome))
64
+ return cmd
@@ -0,0 +1,108 @@
1
+ """Pure layers derived from a parsed Bakta annotation (GIPPA_SPEC.md §7.1.2, §9.3; M2-04).
2
+
3
+ Ribosomal-protein set (CAI reference), 16S rRNA selection and anti-Shine-Dalgarno 9-mer, tRNA
4
+ copy numbers per anticodon (tAI), db_xref splitting, ``annotation_source`` and origin-aware
5
+ nearest genes (TE6). No file I/O; the ``parse_bakta`` step in ``gippa.steps.annotation`` writes.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from collections import Counter
12
+ from collections.abc import Sequence
13
+ from typing import Literal
14
+
15
+ from gippa.model.coords import Interval, Replicon
16
+ from gippa.model.genes import Gene, NcFeature
17
+
18
+ RIBOSOMAL_PRODUCT: re.Pattern[str] = re.compile(r"^(30S|50S) ribosomal protein")
19
+ RIBOSOMAL_GENE: re.Pattern[str] = re.compile(r"^(rps|rpl|rpm)[A-Z]")
20
+ DEFAULT_ANTI_SD = "ACCTCCTTA"
21
+ ANTI_SD_DEFAULT_FLAG = "anti_sd_default"
22
+
23
+ _DNA9 = re.compile(r"^[ACGT]{9}$")
24
+
25
+ AnnotationSource = Literal["bakta_full", "bakta_light", "user"]
26
+
27
+
28
+ def is_ribosomal_protein(g: Gene) -> bool:
29
+ """§7.1.2: product ``^(30S|50S) ribosomal protein`` or gene ``^(rps|rpl|rpm)[A-Z]``."""
30
+ return bool(RIBOSOMAL_PRODUCT.match(g.product or "")) or bool(
31
+ g.gene and RIBOSOMAL_GENE.match(g.gene)
32
+ )
33
+
34
+
35
+ def rrna16s(features: Sequence[NcFeature]) -> list[NcFeature]:
36
+ """The 16S rRNA copies (product contains ``16S`` or gene ``rrs``), in input order."""
37
+ return [f for f in features if f.type == "rrna" and ("16S" in f.product or f.gene == "rrs")]
38
+
39
+
40
+ def anti_sd(rrna16s_nts: Sequence[str], configured: str = "auto") -> tuple[str, tuple[str, ...]]:
41
+ """Anti-SD 9-mer and run flags (§7.1.2, §5.2).
42
+
43
+ ``configured`` other than ``auto`` must match ``^[ACGT]{9}$`` and is used as is. Otherwise
44
+ the most common 3' 9-mer across 16S copies (ties: first copy in input order; copies shorter
45
+ than 9 nt are skipped) is used if it contains ``CCTCC``, else the E. coli default and the
46
+ ``anti_sd_default`` flag.
47
+ """
48
+ if configured != "auto":
49
+ if not _DNA9.match(configured):
50
+ raise ValueError(
51
+ f"annotation.ostir.anti_sd must be 'auto' or 9 nt ACGT: {configured!r}"
52
+ )
53
+ return configured, ()
54
+ tails = [s[-9:].upper() for s in rrna16s_nts if len(s) >= 9]
55
+ if tails:
56
+ counts = Counter(tails) # insertion-ordered: most_common keeps first-seen order on ties
57
+ best = counts.most_common(1)[0][0]
58
+ if "CCTCC" in best:
59
+ return best, ()
60
+ return DEFAULT_ANTI_SD, (ANTI_SD_DEFAULT_FLAG,)
61
+
62
+
63
+ def trna_counts(features: Sequence[NcFeature]) -> dict[str, int]:
64
+ """tRNA gene copies per anticodon (§7.1.2), keys sorted."""
65
+ counts = Counter(f.anticodon for f in features if f.type == "trna" and f.anticodon)
66
+ return {k: counts[k] for k in sorted(counts)}
67
+
68
+
69
+ def split_xrefs(genes: Sequence[Gene]) -> list[tuple[str, str, str]]:
70
+ """``(locus_tag, db, id)`` per db_xref; db is text before the first ``:``; GO keeps it."""
71
+ rows: list[tuple[str, str, str]] = []
72
+ for g in genes:
73
+ for x in g.db_xrefs:
74
+ db, _, ident = x.partition(":")
75
+ rows.append((g.locus_tag, db, x if db == "GO" else ident))
76
+ return rows
77
+
78
+
79
+ def annotation_source(db_type: str) -> AnnotationSource:
80
+ """§9.3 enum from the Bakta DB type (M2 lead decision 7): ``user`` when unknown."""
81
+ if db_type == "full":
82
+ return "bakta_full"
83
+ if db_type == "light":
84
+ return "bakta_light"
85
+ return "user"
86
+
87
+
88
+ def _gap(a: Interval, b: Interval, rep: Replicon) -> int:
89
+ """Gap in bp between two intervals (0 when overlapping or adjacent); wraps if circular."""
90
+ if rep.topology != "circular":
91
+ return max(0, b.start - a.end, a.start - b.end)
92
+ n = rep.length
93
+ la, lb = a.end - a.start, b.end - b.start
94
+ if (b.start - a.start) % n < la or (a.start - b.start) % n < lb:
95
+ return 0
96
+ return min((b.start - a.end) % n, (a.start - b.end) % n)
97
+
98
+
99
+ def nearest_genes(
100
+ feature: NcFeature, genes: Sequence[Gene], replicon: Replicon, n: int = 2
101
+ ) -> list[tuple[str, int]]:
102
+ """The ``n`` genes on ``feature``'s contig closest to it: ``(locus_tag, gap_bp)`` ascending.
103
+
104
+ Ties keep the input order. Circular replicons are origin-aware; linear contigs never wrap.
105
+ """
106
+ on = [g for g in genes if g.interval.contig == feature.interval.contig]
107
+ dist = [(g.locus_tag, _gap(feature.interval, g.interval, replicon)) for g in on]
108
+ return sorted(dist, key=lambda x: x[1])[:n]