Skip to content

Conversation

@ivan-aksamentov
Copy link
Member

@ivan-aksamentov ivan-aksamentov commented Oct 17, 2025

Comment on lines +7 to +16
#[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,
}
Copy link
Member Author

@ivan-aksamentov ivan-aksamentov Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outputs of the metric (also contains a copy of the input config)

Comment on lines +32 to +61
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,
})
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The actual calculation of the metric and it's score is here

Comment on lines +8 to +36
export function formatQCRecombinants<TFunction extends TFunctionInterface>(
t: TFunction,
recombinants?: DeepReadonly<QcResultRecombinants>,
) {
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),
},
)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way result is presented in the tooltip in the web UI is here

Comment on lines +237 to +255
#[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<f64>,
}

impl QcRulesConfigRecombinants {
pub const fn example() -> Self {
Self {
enabled: true,
mutations_threshold: 20,
score_weight: OrderedFloat(100.0),
}
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configurable variables can be adjusted here (maps to fields in qc.recombinants of pathogen.json). They are passed to the function which calculates the metric.

@github-actions
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QC label for recombinant sequences

2 participants