echtvar.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. use std::path::{Path, PathBuf};
  2. use anyhow::{Ok, Result};
  3. use uuid::Uuid;
  4. use crate::{
  5. commands::{
  6. CapturedOutput, Command as JobCommand, LocalBatchRunner, LocalRunner, SbatchRunner,
  7. SlurmParams, SlurmRunner,
  8. },
  9. config::Config,
  10. run,
  11. };
  12. use super::{cosmic::Cosmic, gnomad::GnomAD};
  13. pub struct EchtvarJob {
  14. pub input_vcf: PathBuf,
  15. pub output_vcf: PathBuf,
  16. pub config: Config,
  17. }
  18. impl JobCommand for EchtvarJob {
  19. fn cmd(&self) -> String {
  20. let sources = self
  21. .config
  22. .echtvar_sources
  23. .iter()
  24. .map(|e| format!("-e {e}"))
  25. .collect::<Vec<String>>()
  26. .join(" ");
  27. format!(
  28. "{echtvar_bin} anno {sources} {input_vcf} {output_vcf}",
  29. echtvar_bin = self.config.echtvar_bin,
  30. sources = sources,
  31. input_vcf = self.input_vcf.display(),
  32. output_vcf = self.output_vcf.display()
  33. )
  34. }
  35. }
  36. impl LocalRunner for EchtvarJob {}
  37. impl LocalBatchRunner for EchtvarJob {}
  38. impl SlurmRunner for EchtvarJob {
  39. fn slurm_args(&self) -> Vec<String> {
  40. SlurmParams {
  41. job_name: Some(format!("echtvar_{}", Uuid::new_v4())),
  42. cpus_per_task: Some(1),
  43. mem: Some("20G".into()),
  44. partition: Some("shortq".into()),
  45. gres: None,
  46. }
  47. .to_args()
  48. }
  49. }
  50. impl SbatchRunner for EchtvarJob {
  51. fn slurm_params(&self) -> SlurmParams {
  52. SlurmParams {
  53. job_name: Some(format!("echtvar_{}", Uuid::new_v4())),
  54. cpus_per_task: Some(1),
  55. mem: Some("20G".into()),
  56. partition: Some("shortq".into()),
  57. gres: None,
  58. }
  59. }
  60. }
  61. pub fn run_echtvar(
  62. in_path: impl AsRef<Path>,
  63. output_vcf: impl AsRef<Path>,
  64. config: &Config,
  65. ) -> Result<CapturedOutput> {
  66. let mut job = EchtvarJob {
  67. input_vcf: in_path.as_ref().into(),
  68. output_vcf: output_vcf.as_ref().into(),
  69. config: config.clone(),
  70. };
  71. run!(config, &mut job)
  72. }
  73. pub fn parse_echtvar_val(s: &str) -> Result<(Option<Cosmic>, Option<GnomAD>)> {
  74. let mut cosmic_parts = Vec::new();
  75. let mut gnomad_parts = Vec::new();
  76. for part in s.split(';').map(str::trim).filter(|p| !p.is_empty()) {
  77. if part.starts_with("gnomad_") {
  78. gnomad_parts.push(part);
  79. } else {
  80. cosmic_parts.push(part);
  81. }
  82. }
  83. // COSMIC: missing if CNT is absent or CNT is -1 or MISSING
  84. let cosmic = {
  85. let cnt = cosmic_parts.iter().find(|p| p.starts_with("CNT=")).copied();
  86. match cnt {
  87. Some("CNT=-1") | Some("CNT=MISSING") | None => None,
  88. Some(_) => Some(cosmic_parts.join(";").parse::<Cosmic>()?),
  89. }
  90. };
  91. // gnomAD: your choice; this keeps your "all -1 => None" behavior
  92. let gnomad = if gnomad_parts.is_empty() || gnomad_parts.iter().all(|p| p.ends_with("=-1")) {
  93. None
  94. } else {
  95. Some(gnomad_parts.join(";").parse::<GnomAD>()?)
  96. };
  97. Ok((cosmic, gnomad))
  98. }
  99. #[cfg(test)]
  100. mod tests {
  101. use super::*;
  102. use crate::{
  103. annotation::{Annotation, Annotations, Caller, Sample},
  104. helpers::test_init,
  105. variant::{variant_collection::ExternalAnnotation, vcf_variant::VcfVariant},
  106. };
  107. #[test]
  108. fn echtvar_parse() -> anyhow::Result<()> {
  109. test_init();
  110. let s = "gnomad_ac=1;gnomad_an=-1;gnomad_af=-1;gnomad_af_oth=-1;gnomad_af_ami=-1;gnomad_af_sas=-1;gnomad_af_fin=-1;gnomad_af_eas=-1;gnomad_af_amr=-1;gnomad_af_afr=-1;gnomad_af_mid=-1;gnomad_af_asj=-1;gnomad_af_nfe=-1;CNT=188";
  111. let (cosmic, _gnomad) = parse_echtvar_val(s)?;
  112. println!("{cosmic:#?}");
  113. Ok(())
  114. }
  115. #[test]
  116. fn echtvar_run() -> anyhow::Result<()> {
  117. test_init();
  118. let config = Config::default();
  119. let variants: Vec<VcfVariant> = vec![
  120. "chr12\t25116560\t.\tC\tG\t18.2\tPASS\t.\tGT:GQ:DP:AD:VAF:PL\t1/1:18:45:37,7:0.155556:18,25,0".parse()?,
  121. "chr1\t5619\trs1470452993\tA\tC \t.\tPASS\t.\t.\t.".parse()?
  122. ];
  123. let annotations = Annotations::default();
  124. let caller = Annotation::Callers(Caller::ClairS, Sample::Somatic);
  125. variants.iter().for_each(|v| {
  126. annotations.insert_update(v.hash(), std::slice::from_ref(&caller));
  127. });
  128. let ext_annot = ExternalAnnotation::init("TEST", &config)?;
  129. ext_annot.annotate(&variants, &annotations)?;
  130. println!("{annotations:#?}");
  131. Ok(())
  132. }
  133. }