From bb039edfd7405563e59246a938c0ebd7829a0183 Mon Sep 17 00:00:00 2001 From: ivan-aksamentov Date: Thu, 23 May 2024 18:04:23 +0200 Subject: [PATCH] feat: make `clade_membership` node attribute optional Not all trees have `clade_membership` attribute on the nodes. Here I made this attribute optional in Nextclade. If `clade_membership` is not present: - output JSON/NDJSON result entries will not contain `clade` field - `clade` column in output CSV/TSV will be empty - `clade` column in Nextclade Web will be empty it does not affect any other parts of the application. Notably clade-like attributes (from `.meta.extensions.nextclade.clade_node_attrs` are still assigned and being written to the output). Tested on `sars-cov-2` dataset and `clade_membership` node attribute removed using `jq`: ```bash jq 'walk(if (type == "object" and .clade_membership) then del(.clade_membership) else . end )' tree.original.json > tree.json ``` (in real trees you might also need to change metadata, such as colorings) ### Further work: - We might remove empty `clade` column from CSV/TSV and from Web. Thought it might be a bit involved - the results are streamed one at a time and we don't know whether there will be any clades or not until very end. But at that point it is too late - everything has been already written. --- .../src/components/Results/ColumnClade.tsx | 2 +- .../nextclade-web/src/filtering/filterByClades.ts | 2 +- packages/nextclade/src/io/nextclade_csv.rs | 2 +- packages/nextclade/src/run/nextclade_run_one.rs | 8 +++++--- packages/nextclade/src/tree/tree.rs | 11 ++++++++--- packages/nextclade/src/tree/tree_attach_new_nodes.rs | 2 +- packages/nextclade/src/types/outputs.rs | 3 ++- 7 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/nextclade-web/src/components/Results/ColumnClade.tsx b/packages/nextclade-web/src/components/Results/ColumnClade.tsx index 8fa25a374..c686b60f4 100644 --- a/packages/nextclade-web/src/components/Results/ColumnClade.tsx +++ b/packages/nextclade-web/src/components/Results/ColumnClade.tsx @@ -15,7 +15,7 @@ export function ColumnClade({ analysisResult }: ColumnCladeProps) { const { clade, seqName, index } = analysisResult const id = getSafeId('col-clade', { index, seqName }) - const cladeText = clade ?? t('Pending...') + const cladeText = clade ?? '' const onMouseEnter = useCallback(() => setShowTooltip(true), []) const onMouseLeave = useCallback(() => setShowTooltip(false), []) diff --git a/packages/nextclade-web/src/filtering/filterByClades.ts b/packages/nextclade-web/src/filtering/filterByClades.ts index 5de3d657d..66bba402a 100644 --- a/packages/nextclade-web/src/filtering/filterByClades.ts +++ b/packages/nextclade-web/src/filtering/filterByClades.ts @@ -11,6 +11,6 @@ export function filterByClades(cladesFilter: string) { } const { clade } = result.result.analysisResult - return cladesFilters.some((filter) => clade.toLowerCase().startsWith(filter.toLowerCase())) + return cladesFilters.some((filter) => clade?.toLowerCase().startsWith(filter.toLowerCase())) } } diff --git a/packages/nextclade/src/io/nextclade_csv.rs b/packages/nextclade/src/io/nextclade_csv.rs index 5a97c7b3b..5bea6bca6 100644 --- a/packages/nextclade/src/io/nextclade_csv.rs +++ b/packages/nextclade/src/io/nextclade_csv.rs @@ -351,7 +351,7 @@ impl NextcladeResultsCsvWriter { self.add_entry("index", index)?; self.add_entry("seqName", seq_name)?; - self.add_entry("clade", clade)?; + self.add_entry("clade", &clade.as_deref().unwrap_or_default())?; self.add_entry("qc.overallScore", &format_qc_score(qc.overall_score))?; self.add_entry("qc.overallStatus", &qc.overall_status.to_string())?; self.add_entry("totalSubstitutions", &total_substitutions.to_string())?; diff --git a/packages/nextclade/src/run/nextclade_run_one.rs b/packages/nextclade/src/run/nextclade_run_one.rs index bdc3fd322..1fd99de25 100644 --- a/packages/nextclade/src/run/nextclade_run_one.rs +++ b/packages/nextclade/src/run/nextclade_run_one.rs @@ -58,7 +58,7 @@ struct NextcladeResultWithAa { #[derive(Default)] struct NextcladeResultWithGraph { - clade: String, + clade: Option, private_nuc_mutations: PrivateNucMutations, private_aa_mutations: BTreeMap, phenotype_values: Option>, @@ -306,8 +306,10 @@ pub fn nextclade_run_one( .iter() .filter_map(|phenotype_data| { let PhenotypeData { name, cds, ignore, .. } = phenotype_data; - if ignore.clades.contains(&clade) { - return None; + if let Some(clade) = &clade { + if ignore.clades.contains(clade) { + return None; + } } let phenotype = calculate_phenotype(phenotype_data, &aa_substitutions); Some(PhenotypeValue { diff --git a/packages/nextclade/src/tree/tree.rs b/packages/nextclade/src/tree/tree.rs index dd9ed05f3..5bafd8607 100644 --- a/packages/nextclade/src/tree/tree.rs +++ b/packages/nextclade/src/tree/tree.rs @@ -136,7 +136,8 @@ pub struct TreeNodeAttrs { #[serde(skip_serializing_if = "Option::is_none")] pub div: Option, - pub clade_membership: TreeNodeAttr, + #[serde(skip_serializing_if = "Option::is_none")] + pub clade_membership: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "Node type")] @@ -243,8 +244,12 @@ impl From<&AuspiceTreeNode> for AuspiceGraphNodePayload { impl AuspiceGraphNodePayload { /// Extracts clade of the node - pub fn clade(&self) -> String { - self.node_attrs.clade_membership.value.clone() + pub fn clade(&self) -> Option { + self + .node_attrs + .clade_membership + .as_ref() + .map(|clade_membership| clade_membership.value.clone()) } /// Extracts clade-like node attributes, given a list of key descriptions diff --git a/packages/nextclade/src/tree/tree_attach_new_nodes.rs b/packages/nextclade/src/tree/tree_attach_new_nodes.rs index dd15b943e..941fc4956 100644 --- a/packages/nextclade/src/tree/tree_attach_new_nodes.rs +++ b/packages/nextclade/src/tree/tree_attach_new_nodes.rs @@ -68,7 +68,7 @@ pub fn create_new_auspice_node( }, node_attrs: TreeNodeAttrs { div: Some(new_divergence), - clade_membership: TreeNodeAttr::new(&result.clade), + clade_membership: result.clade.as_ref().map(|clade| TreeNodeAttr::new(clade)), node_type: Some(TreeNodeAttr::new("New")), region: Some(TreeNodeAttr::new(AUSPICE_UNKNOWN_VALUE)), country: Some(TreeNodeAttr::new(AUSPICE_UNKNOWN_VALUE)), diff --git a/packages/nextclade/src/types/outputs.rs b/packages/nextclade/src/types/outputs.rs index e9863922a..e603e65ae 100644 --- a/packages/nextclade/src/types/outputs.rs +++ b/packages/nextclade/src/types/outputs.rs @@ -70,7 +70,8 @@ pub struct NextcladeOutputs { pub aa_unsequenced_ranges: BTreeMap>, pub pcr_primer_changes: Vec, pub total_pcr_primer_changes: usize, - pub clade: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub clade: Option, pub private_nuc_mutations: PrivateNucMutations, pub private_aa_mutations: BTreeMap, pub warnings: Vec,