diff --git a/packages/nextclade-schemas/input-pathogen-json.schema.json b/packages/nextclade-schemas/input-pathogen-json.schema.json index 7f4404f64..9adb8f3f7 100644 --- a/packages/nextclade-schemas/input-pathogen-json.schema.json +++ b/packages/nextclade-schemas/input-pathogen-json.schema.json @@ -83,6 +83,11 @@ } ], "scoreWeight": 75.0 + }, + "recombinants": { + "enabled": true, + "mutationsThreshold": 20, + "scoreWeight": 100.0 } }, "phenotypeData": [ @@ -444,6 +449,11 @@ } ], "scoreWeight": 75.0 + }, + "recombinants": { + "enabled": true, + "mutationsThreshold": 20, + "scoreWeight": 100.0 } } ], @@ -524,6 +534,18 @@ "$ref": "#/definitions/QcRulesConfigStopCodons" } ] + }, + "recombinants": { + "default": { + "enabled": false, + "mutationsThreshold": 0, + "scoreWeight": 0.0 + }, + "allOf": [ + { + "$ref": "#/definitions/QcRulesConfigRecombinants" + } + ] } } }, @@ -820,6 +842,34 @@ } } }, + "QcRulesConfigRecombinants": { + "description": "Configuration for QC rule \"recombinants\"", + "examples": [ + { + "enabled": true, + "mutationsThreshold": 20, + "scoreWeight": 100.0 + } + ], + "type": "object", + "properties": { + "enabled": { + "default": false, + "type": "boolean" + }, + "mutationsThreshold": { + "default": 0, + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "scoreWeight": { + "default": 0.0, + "type": "number", + "format": "double" + } + } + }, "NextcladeGeneralParamsOptional": { "type": "object", "properties": { diff --git a/packages/nextclade-schemas/input-pathogen-json.schema.yaml b/packages/nextclade-schemas/input-pathogen-json.schema.yaml index 2cd7049c3..671c4147d 100644 --- a/packages/nextclade-schemas/input-pathogen-json.schema.yaml +++ b/packages/nextclade-schemas/input-pathogen-json.schema.yaml @@ -60,6 +60,10 @@ examples: - cdsName: ORF3a codon: 238 scoreWeight: 75.0 + recombinants: + enabled: true + mutationsThreshold: 20 + scoreWeight: 100.0 phenotypeData: - name: receptor_binding nameFriendly: Receptor Binding @@ -276,6 +280,10 @@ definitions: - cdsName: ORF3a codon: 238 scoreWeight: 75.0 + recombinants: + enabled: true + mutationsThreshold: 20 + scoreWeight: 100.0 type: object properties: missingData: @@ -324,6 +332,13 @@ definitions: scoreWeight: 75.0 allOf: - $ref: '#/definitions/QcRulesConfigStopCodons' + recombinants: + default: + enabled: false + mutationsThreshold: 0 + scoreWeight: 0.0 + allOf: + - $ref: '#/definitions/QcRulesConfigRecombinants' QcRulesConfigMissingData: description: Configuration for QC rule "missing data" examples: @@ -529,6 +544,26 @@ definitions: type: integer format: uint minimum: 0.0 + QcRulesConfigRecombinants: + description: Configuration for QC rule "recombinants" + examples: + - enabled: true + mutationsThreshold: 20 + scoreWeight: 100.0 + type: object + properties: + enabled: + default: false + type: boolean + mutationsThreshold: + default: 0 + type: integer + format: uint + minimum: 0.0 + scoreWeight: + default: 0.0 + type: number + format: double NextcladeGeneralParamsOptional: type: object properties: diff --git a/packages/nextclade-schemas/nextclade-auspice-extensions.schema.json b/packages/nextclade-schemas/nextclade-auspice-extensions.schema.json index d9acd558e..17f9d21e3 100644 --- a/packages/nextclade-schemas/nextclade-auspice-extensions.schema.json +++ b/packages/nextclade-schemas/nextclade-auspice-extensions.schema.json @@ -362,6 +362,11 @@ } ], "scoreWeight": 75.0 + }, + "recombinants": { + "enabled": true, + "mutationsThreshold": 20, + "scoreWeight": 100.0 } }, "phenotypeData": [ @@ -723,6 +728,11 @@ } ], "scoreWeight": 75.0 + }, + "recombinants": { + "enabled": true, + "mutationsThreshold": 20, + "scoreWeight": 100.0 } } ], @@ -803,6 +813,18 @@ "$ref": "#/definitions/QcRulesConfigStopCodons" } ] + }, + "recombinants": { + "default": { + "enabled": false, + "mutationsThreshold": 0, + "scoreWeight": 0.0 + }, + "allOf": [ + { + "$ref": "#/definitions/QcRulesConfigRecombinants" + } + ] } } }, @@ -1079,6 +1101,34 @@ } } }, + "QcRulesConfigRecombinants": { + "description": "Configuration for QC rule \"recombinants\"", + "examples": [ + { + "enabled": true, + "mutationsThreshold": 20, + "scoreWeight": 100.0 + } + ], + "type": "object", + "properties": { + "enabled": { + "default": false, + "type": "boolean" + }, + "mutationsThreshold": { + "default": 0, + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "scoreWeight": { + "default": 0.0, + "type": "number", + "format": "double" + } + } + }, "NextcladeGeneralParamsOptional": { "type": "object", "properties": { diff --git a/packages/nextclade-schemas/nextclade-auspice-extensions.schema.yaml b/packages/nextclade-schemas/nextclade-auspice-extensions.schema.yaml index 5706a03bf..abf5e1c9e 100644 --- a/packages/nextclade-schemas/nextclade-auspice-extensions.schema.yaml +++ b/packages/nextclade-schemas/nextclade-auspice-extensions.schema.yaml @@ -237,6 +237,10 @@ definitions: - cdsName: ORF3a codon: 238 scoreWeight: 75.0 + recombinants: + enabled: true + mutationsThreshold: 20 + scoreWeight: 100.0 phenotypeData: - name: receptor_binding nameFriendly: Receptor Binding @@ -452,6 +456,10 @@ definitions: - cdsName: ORF3a codon: 238 scoreWeight: 75.0 + recombinants: + enabled: true + mutationsThreshold: 20 + scoreWeight: 100.0 type: object properties: missingData: @@ -500,6 +508,13 @@ definitions: scoreWeight: 75.0 allOf: - $ref: '#/definitions/QcRulesConfigStopCodons' + recombinants: + default: + enabled: false + mutationsThreshold: 0 + scoreWeight: 0.0 + allOf: + - $ref: '#/definitions/QcRulesConfigRecombinants' QcRulesConfigMissingData: description: Configuration for QC rule "missing data" examples: @@ -688,6 +703,26 @@ definitions: type: integer format: uint minimum: 0.0 + QcRulesConfigRecombinants: + description: Configuration for QC rule "recombinants" + examples: + - enabled: true + mutationsThreshold: 20 + scoreWeight: 100.0 + type: object + properties: + enabled: + default: false + type: boolean + mutationsThreshold: + default: 0 + type: integer + format: uint + minimum: 0.0 + scoreWeight: + default: 0.0 + type: number + format: double NextcladeGeneralParamsOptional: type: object properties: diff --git a/packages/nextclade-schemas/output-json.schema.json b/packages/nextclade-schemas/output-json.schema.json index ab2907548..b89653168 100644 --- a/packages/nextclade-schemas/output-json.schema.json +++ b/packages/nextclade-schemas/output-json.schema.json @@ -1699,6 +1699,16 @@ } ] }, + "recombinants": { + "anyOf": [ + { + "$ref": "#/definitions/QcResultRecombinants" + }, + { + "type": "null" + } + ] + }, "overallScore": { "type": "number", "format": "double" @@ -1988,6 +1998,46 @@ } } }, + "QcResultRecombinants": { + "type": "object", + "required": [ + "excessMutations", + "mutationsThreshold", + "score", + "status", + "totalPrivateMutations", + "totalReversionSubstitutions" + ], + "properties": { + "score": { + "type": "number", + "format": "double" + }, + "status": { + "$ref": "#/definitions/QcStatus" + }, + "totalPrivateMutations": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "totalReversionSubstitutions": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "mutationsThreshold": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "excessMutations": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + }, "PhenotypeValue": { "description": "Result for a single phenotype value", "type": "object", diff --git a/packages/nextclade-schemas/output-json.schema.yaml b/packages/nextclade-schemas/output-json.schema.yaml index 3d4d584e5..d1b11ac6a 100644 --- a/packages/nextclade-schemas/output-json.schema.yaml +++ b/packages/nextclade-schemas/output-json.schema.yaml @@ -1177,6 +1177,10 @@ definitions: anyOf: - $ref: '#/definitions/QcResultStopCodons' - type: 'null' + recombinants: + anyOf: + - $ref: '#/definitions/QcResultRecombinants' + - type: 'null' overallScore: type: number format: double @@ -1390,6 +1394,37 @@ definitions: type: integer format: uint minimum: 0.0 + QcResultRecombinants: + type: object + required: + - excessMutations + - mutationsThreshold + - score + - status + - totalPrivateMutations + - totalReversionSubstitutions + properties: + score: + type: number + format: double + status: + $ref: '#/definitions/QcStatus' + totalPrivateMutations: + type: integer + format: uint + minimum: 0.0 + totalReversionSubstitutions: + type: integer + format: uint + minimum: 0.0 + mutationsThreshold: + type: integer + format: uint + minimum: 0.0 + excessMutations: + type: integer + format: uint + minimum: 0.0 PhenotypeValue: description: Result for a single phenotype value type: object diff --git a/packages/nextclade-schemas/output-ndjson.schema.json b/packages/nextclade-schemas/output-ndjson.schema.json index 6c9ecb263..44b6ad858 100644 --- a/packages/nextclade-schemas/output-ndjson.schema.json +++ b/packages/nextclade-schemas/output-ndjson.schema.json @@ -1590,6 +1590,16 @@ } ] }, + "recombinants": { + "anyOf": [ + { + "$ref": "#/definitions/QcResultRecombinants" + }, + { + "type": "null" + } + ] + }, "overallScore": { "type": "number", "format": "double" @@ -1879,6 +1889,46 @@ } } }, + "QcResultRecombinants": { + "type": "object", + "required": [ + "excessMutations", + "mutationsThreshold", + "score", + "status", + "totalPrivateMutations", + "totalReversionSubstitutions" + ], + "properties": { + "score": { + "type": "number", + "format": "double" + }, + "status": { + "$ref": "#/definitions/QcStatus" + }, + "totalPrivateMutations": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "totalReversionSubstitutions": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "mutationsThreshold": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + }, + "excessMutations": { + "type": "integer", + "format": "uint", + "minimum": 0.0 + } + } + }, "PhenotypeValue": { "description": "Result for a single phenotype value", "type": "object", diff --git a/packages/nextclade-schemas/output-ndjson.schema.yaml b/packages/nextclade-schemas/output-ndjson.schema.yaml index 3b0c355c6..5a7728bb4 100644 --- a/packages/nextclade-schemas/output-ndjson.schema.yaml +++ b/packages/nextclade-schemas/output-ndjson.schema.yaml @@ -1100,6 +1100,10 @@ definitions: anyOf: - $ref: '#/definitions/QcResultStopCodons' - type: 'null' + recombinants: + anyOf: + - $ref: '#/definitions/QcResultRecombinants' + - type: 'null' overallScore: type: number format: double @@ -1313,6 +1317,37 @@ definitions: type: integer format: uint minimum: 0.0 + QcResultRecombinants: + type: object + required: + - excessMutations + - mutationsThreshold + - score + - status + - totalPrivateMutations + - totalReversionSubstitutions + properties: + score: + type: number + format: double + status: + $ref: '#/definitions/QcStatus' + totalPrivateMutations: + type: integer + format: uint + minimum: 0.0 + totalReversionSubstitutions: + type: integer + format: uint + minimum: 0.0 + mutationsThreshold: + type: integer + format: uint + minimum: 0.0 + excessMutations: + type: integer + format: uint + minimum: 0.0 PhenotypeValue: description: Result for a single phenotype value type: object diff --git a/packages/nextclade-web/src/components/Results/ColumnQCStatus.tsx b/packages/nextclade-web/src/components/Results/ColumnQCStatus.tsx index f3de71d97..5de947fdc 100644 --- a/packages/nextclade-web/src/components/Results/ColumnQCStatus.tsx +++ b/packages/nextclade-web/src/components/Results/ColumnQCStatus.tsx @@ -17,7 +17,7 @@ export function ColumnQCStatus({ analysisResult }: ColumnQCStatusProps) { const onMouseLeave = useCallback(() => setShowTooltip(false), []) const { index, seqName, qc } = analysisResult - const { missingData, privateMutations, mixedSites, snpClusters, frameShifts, stopCodons } = qc + const { missingData, privateMutations, mixedSites, snpClusters, frameShifts, stopCodons, recombinants } = qc const id = getSafeId('qc-label', { index, seqName }) @@ -28,6 +28,7 @@ export function ColumnQCStatus({ analysisResult }: ColumnQCStatusProps) { { value: snpClusters, name: 'C' }, { value: frameShifts, name: 'F' }, { value: stopCodons, name: 'S' }, + { value: recombinants, name: 'R' }, ].filter((value) => notUndefined(value)) const icons = rules.map(({ name, value }, i) => { diff --git a/packages/nextclade-web/src/components/Results/ListOfQcIsuues.tsx b/packages/nextclade-web/src/components/Results/ListOfQcIsuues.tsx index f57befadc..0cc6c3bda 100644 --- a/packages/nextclade-web/src/components/Results/ListOfQcIsuues.tsx +++ b/packages/nextclade-web/src/components/Results/ListOfQcIsuues.tsx @@ -12,6 +12,7 @@ import { formatQCMissingData } from 'src/helpers/formatQCMissingData' import { formatQCMixedSites } from 'src/helpers/formatQCMixedSites' import { formatQCFrameShifts } from 'src/helpers/formatQCFrameShifts' import { formatQCStopCodons } from 'src/helpers/formatQCStopCodons' +import { formatQCRecombinants } from 'src/helpers/formatQCRecombinants' import { Circle, CircleProps } from 'src/components/Results/Circle' export const QcList = styled.ul` @@ -52,6 +53,7 @@ export function ListOfQcIssues({ qc }: ListOfQcIssuesProps) { missingData, frameShifts, stopCodons, + recombinants, } = qc const rules = [ @@ -61,6 +63,7 @@ export function ListOfQcIssues({ qc }: ListOfQcIssuesProps) { { name: t('Mutation Clusters'), shortName: 'C', value: snpClusters, message: formatQCSNPClusters(t, snpClusters) }, // prettier-ignore { name: t('Frame shifts'), shortName: 'F', value: frameShifts, message: formatQCFrameShifts(t, frameShifts) }, // prettier-ignore { name: t('Stop codons'), shortName: 'S', value: stopCodons, message: formatQCStopCodons(t, stopCodons) }, // prettier-ignore + { name: t('Recombinants'), shortName: 'R', value: recombinants, message: formatQCRecombinants(t, recombinants) }, // prettier-ignore ].filter((value) => notUndefined(value)) const issues = rules.map(({ name, shortName, value, message }) => { diff --git a/packages/nextclade-web/src/components/Results/ResultsTableStyle.tsx b/packages/nextclade-web/src/components/Results/ResultsTableStyle.tsx index 5ae36e1cc..d7f482804 100644 --- a/packages/nextclade-web/src/components/Results/ResultsTableStyle.tsx +++ b/packages/nextclade-web/src/components/Results/ResultsTableStyle.tsx @@ -17,7 +17,7 @@ export const COLUMN_WIDTHS = { rowIndex: 45, id: 45, seqName: 250, - qc: 130, + qc: 150, clade: 110, coverage: 50, mut: 50, diff --git a/packages/nextclade-web/src/helpers/formatQCRecombinants.ts b/packages/nextclade-web/src/helpers/formatQCRecombinants.ts new file mode 100644 index 000000000..9a62d7e62 --- /dev/null +++ b/packages/nextclade-web/src/helpers/formatQCRecombinants.ts @@ -0,0 +1,36 @@ +import { round } from 'lodash' + +import type { DeepReadonly } from 'ts-essentials' + +import type { QcResultRecombinants } from 'src/types' +import type { TFunctionInterface } from 'src/helpers/TFunctionInterface' + +export function formatQCRecombinants( + t: TFunction, + recombinants?: DeepReadonly, +) { + if (!recombinants || recombinants.status === 'good') { + return undefined + } + + const { score, totalPrivateMutations, totalReversionSubstitutions, mutationsThreshold, excessMutations, status } = + recombinants + + const totalMutations = totalPrivateMutations + totalReversionSubstitutions + + let message = t('Potentially recombinant sequence detected') + if (status === 'bad') { + message = t('Likely recombinant sequence detected') + } + + return t( + '{{message}}. Seen {{totalMutations}} private mutations and reversions ({{excessMutations}} above threshold of {{mutationsThreshold}}). QC score: {{score}}', + { + message, + totalMutations, + excessMutations, + mutationsThreshold, + score: round(score), + }, + ) +} diff --git a/packages/nextclade/src/qc/mod.rs b/packages/nextclade/src/qc/mod.rs index 27371ba15..06b5f6589 100644 --- a/packages/nextclade/src/qc/mod.rs +++ b/packages/nextclade/src/qc/mod.rs @@ -3,6 +3,7 @@ pub mod qc_rule_frame_shifts; pub mod qc_rule_missing_data; pub mod qc_rule_mixed_sites; pub mod qc_rule_private_mutations; +pub mod qc_rule_recombinants; pub mod qc_rule_snp_clusters; pub mod qc_rule_stop_codons; pub mod qc_run; diff --git a/packages/nextclade/src/qc/qc_config.rs b/packages/nextclade/src/qc/qc_config.rs index 08f563bb9..1b6b322b9 100644 --- a/packages/nextclade/src/qc/qc_config.rs +++ b/packages/nextclade/src/qc/qc_config.rs @@ -233,6 +233,27 @@ impl QcRulesConfigStopCodons { } } +/// Configuration for QC rule "recombinants" +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, schemars::JsonSchema, Validate)] +#[serde(rename_all = "camelCase")] +#[serde(default)] +#[schemars(example = "QcRulesConfigRecombinants::example")] +pub struct QcRulesConfigRecombinants { + pub enabled: bool, + pub mutations_threshold: usize, + pub score_weight: OrderedFloat, +} + +impl QcRulesConfigRecombinants { + pub const fn example() -> Self { + Self { + enabled: true, + mutations_threshold: 20, + score_weight: OrderedFloat(100.0), + } + } +} + /// Configuration for QC rules #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, schemars::JsonSchema, Validate)] #[serde(rename_all = "camelCase")] @@ -245,6 +266,7 @@ pub struct QcConfig { pub snp_clusters: QcRulesConfigSnpClusters, pub frame_shifts: QcRulesConfigFrameShifts, pub stop_codons: QcRulesConfigStopCodons, + pub recombinants: QcRulesConfigRecombinants, } impl FromStr for QcConfig { @@ -264,6 +286,7 @@ impl QcConfig { snp_clusters: QcRulesConfigSnpClusters::example(), frame_shifts: QcRulesConfigFrameShifts::example(), stop_codons: QcRulesConfigStopCodons::example(), + recombinants: QcRulesConfigRecombinants::example(), } } diff --git a/packages/nextclade/src/qc/qc_rule_recombinants.rs b/packages/nextclade/src/qc/qc_rule_recombinants.rs new file mode 100644 index 000000000..c7466ae35 --- /dev/null +++ b/packages/nextclade/src/qc/qc_rule_recombinants.rs @@ -0,0 +1,72 @@ +use crate::analyze::find_private_nuc_mutations::PrivateNucMutations; +use crate::qc::qc_config::QcRulesConfigRecombinants; +use crate::qc::qc_rule_snp_clusters::QcResultSnpClusters; +use crate::qc::qc_run::{QcRule, QcStatus}; +use num::traits::clamp_min; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize, schemars::JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct QcResultRecombinants { + pub score: f64, + pub status: QcStatus, + pub total_private_mutations: usize, + pub total_reversion_substitutions: usize, + pub mutations_threshold: usize, + pub excess_mutations: usize, +} + +impl QcRule for QcResultRecombinants { + fn score(&self) -> f64 { + self.score + } +} + +pub fn rule_recombinants( + private_nuc_mutations: &PrivateNucMutations, + snp_clusters: Option<&QcResultSnpClusters>, + config: &QcRulesConfigRecombinants, +) -> Option { + if !config.enabled { + return None; + } + + let clustered_snps = snp_clusters.map(|sc| sc.clustered_snps.as_slice()).unwrap_or_default(); + + // TODO: use clustered_snps here + dbg!(&clustered_snps); + + let total_private_mutations = private_nuc_mutations.total_private_substitutions; + let total_reversion_substitutions = private_nuc_mutations.total_reversion_substitutions; + + // Calculate the total mutations relevant for recombinant detection + // This includes all private substitutions and reversions, as both are indicators of recombination + let total_mutations_for_recombination = total_private_mutations + total_reversion_substitutions; + + let excess_mutations = if total_mutations_for_recombination > config.mutations_threshold { + total_mutations_for_recombination - config.mutations_threshold + } else { + 0 + }; + + // Calculate score based on excess mutations beyond threshold + let score = if total_mutations_for_recombination > config.mutations_threshold { + clamp_min( + excess_mutations as f64 * *config.score_weight / config.mutations_threshold as f64, + 0.0, + ) + } else { + 0.0 + }; + + let status = QcStatus::from_score(score); + + Some(QcResultRecombinants { + score, + status, + total_private_mutations, + total_reversion_substitutions, + mutations_threshold: config.mutations_threshold, + excess_mutations, + }) +} diff --git a/packages/nextclade/src/qc/qc_run.rs b/packages/nextclade/src/qc/qc_run.rs index e9d91e03c..ad71e8ce7 100644 --- a/packages/nextclade/src/qc/qc_run.rs +++ b/packages/nextclade/src/qc/qc_run.rs @@ -5,6 +5,7 @@ use crate::qc::qc_rule_frame_shifts::{rule_frame_shifts, QcResultFrameShifts}; use crate::qc::qc_rule_missing_data::{rule_missing_data, QcResultMissingData}; use crate::qc::qc_rule_mixed_sites::{rule_mixed_sites, QcResultMixedSites}; use crate::qc::qc_rule_private_mutations::{rule_private_mutations, QcResultPrivateMutations}; +use crate::qc::qc_rule_recombinants::{rule_recombinants, QcResultRecombinants}; use crate::qc::qc_rule_snp_clusters::{rule_snp_clusters, QcResultSnpClusters}; use crate::qc::qc_rule_stop_codons::{rule_stop_codons, QcResultStopCodons}; use crate::translate::frame_shifts_translate::FrameShift; @@ -54,6 +55,7 @@ pub struct QcResult { pub snp_clusters: Option, pub frame_shifts: Option, pub stop_codons: Option, + pub recombinants: Option, pub overall_score: f64, pub overall_status: QcStatus, } @@ -70,13 +72,17 @@ pub fn qc_run( frame_shifts: &[FrameShift], config: &QcConfig, ) -> QcResult { + let snp_clusters = rule_snp_clusters(private_nuc_mutations, &config.snp_clusters); + let recombinants = rule_recombinants(private_nuc_mutations, snp_clusters.as_ref(), &config.recombinants); + let mut result = QcResult { missing_data: rule_missing_data(total_missing, &config.missing_data), mixed_sites: rule_mixed_sites(nucleotide_composition, &config.mixed_sites), private_mutations: rule_private_mutations(private_nuc_mutations, &config.private_mutations), - snp_clusters: rule_snp_clusters(private_nuc_mutations, &config.snp_clusters), + snp_clusters, frame_shifts: rule_frame_shifts(frame_shifts, &config.frame_shifts), stop_codons: rule_stop_codons(translation, &config.stop_codons), + recombinants, overall_score: 0.0, overall_status: QcStatus::Good, }; @@ -87,6 +93,7 @@ pub fn qc_run( result.overall_score += add_score(&result.snp_clusters); result.overall_score += add_score(&result.frame_shifts); result.overall_score += add_score(&result.stop_codons); + result.overall_score += add_score(&result.recombinants); result.overall_status = QcStatus::from_score(result.overall_score);