use crate::backtranslate::{Codon, CodonTable};
use crate::hgvs::location::AminoAcid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Consequence {
TranscriptAblation,
SpliceAcceptorVariant,
SpliceDonorVariant,
StopGained,
FrameshiftVariant,
StopLost,
StartLost,
MissenseVariant,
InframeInsertion,
InframeDeletion,
ProteinAlteringVariant,
SpliceRegionVariant,
SynonymousVariant,
StartRetainedVariant,
StopRetainedVariant,
FivePrimeUtrVariant,
ThreePrimeUtrVariant,
IntronVariant,
CodingSequenceVariant,
}
impl Consequence {
pub fn so_term(&self) -> &'static str {
match self {
Consequence::TranscriptAblation => "transcript_ablation",
Consequence::SpliceAcceptorVariant => "splice_acceptor_variant",
Consequence::SpliceDonorVariant => "splice_donor_variant",
Consequence::StopGained => "stop_gained",
Consequence::FrameshiftVariant => "frameshift_variant",
Consequence::StopLost => "stop_lost",
Consequence::StartLost => "start_lost",
Consequence::MissenseVariant => "missense_variant",
Consequence::InframeInsertion => "inframe_insertion",
Consequence::InframeDeletion => "inframe_deletion",
Consequence::ProteinAlteringVariant => "protein_altering_variant",
Consequence::SpliceRegionVariant => "splice_region_variant",
Consequence::SynonymousVariant => "synonymous_variant",
Consequence::StartRetainedVariant => "start_retained_variant",
Consequence::StopRetainedVariant => "stop_retained_variant",
Consequence::FivePrimeUtrVariant => "5_prime_UTR_variant",
Consequence::ThreePrimeUtrVariant => "3_prime_UTR_variant",
Consequence::IntronVariant => "intron_variant",
Consequence::CodingSequenceVariant => "coding_sequence_variant",
}
}
pub fn so_id(&self) -> &'static str {
match self {
Consequence::TranscriptAblation => "SO:0001893",
Consequence::SpliceAcceptorVariant => "SO:0001574",
Consequence::SpliceDonorVariant => "SO:0001575",
Consequence::StopGained => "SO:0001587",
Consequence::FrameshiftVariant => "SO:0001589",
Consequence::StopLost => "SO:0001578",
Consequence::StartLost => "SO:0002012",
Consequence::MissenseVariant => "SO:0001583",
Consequence::InframeInsertion => "SO:0001821",
Consequence::InframeDeletion => "SO:0001822",
Consequence::ProteinAlteringVariant => "SO:0001818",
Consequence::SpliceRegionVariant => "SO:0001630",
Consequence::SynonymousVariant => "SO:0001819",
Consequence::StartRetainedVariant => "SO:0002019",
Consequence::StopRetainedVariant => "SO:0001567",
Consequence::FivePrimeUtrVariant => "SO:0001623",
Consequence::ThreePrimeUtrVariant => "SO:0001624",
Consequence::IntronVariant => "SO:0001627",
Consequence::CodingSequenceVariant => "SO:0001580",
}
}
pub fn impact(&self) -> Impact {
match self {
Consequence::TranscriptAblation
| Consequence::SpliceAcceptorVariant
| Consequence::SpliceDonorVariant
| Consequence::StopGained
| Consequence::FrameshiftVariant
| Consequence::StopLost
| Consequence::StartLost => Impact::High,
Consequence::MissenseVariant
| Consequence::InframeInsertion
| Consequence::InframeDeletion
| Consequence::ProteinAlteringVariant => Impact::Moderate,
Consequence::SpliceRegionVariant
| Consequence::SynonymousVariant
| Consequence::StartRetainedVariant
| Consequence::StopRetainedVariant => Impact::Low,
Consequence::FivePrimeUtrVariant
| Consequence::ThreePrimeUtrVariant
| Consequence::IntronVariant
| Consequence::CodingSequenceVariant => Impact::Modifier,
}
}
pub fn description(&self) -> &'static str {
match self {
Consequence::TranscriptAblation => "Complete transcript deletion",
Consequence::SpliceAcceptorVariant => "Variant in splice acceptor site (2bp)",
Consequence::SpliceDonorVariant => "Variant in splice donor site (2bp)",
Consequence::StopGained => "Premature stop codon introduced",
Consequence::FrameshiftVariant => "Frameshift causing protein truncation",
Consequence::StopLost => "Stop codon changed to amino acid",
Consequence::StartLost => "Start codon changed",
Consequence::MissenseVariant => "Amino acid substitution",
Consequence::InframeInsertion => "In-frame amino acid insertion",
Consequence::InframeDeletion => "In-frame amino acid deletion",
Consequence::ProteinAlteringVariant => "Protein-altering variant",
Consequence::SpliceRegionVariant => "Variant in splice region (3-8bp)",
Consequence::SynonymousVariant => "Silent change (same amino acid)",
Consequence::StartRetainedVariant => "Start codon preserved",
Consequence::StopRetainedVariant => "Stop codon preserved",
Consequence::FivePrimeUtrVariant => "Variant in 5' UTR",
Consequence::ThreePrimeUtrVariant => "Variant in 3' UTR",
Consequence::IntronVariant => "Variant in intron",
Consequence::CodingSequenceVariant => "Variant in coding sequence",
}
}
}
impl std::fmt::Display for Consequence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.so_term())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Impact {
Modifier,
Low,
Moderate,
High,
}
impl Impact {
pub fn as_str(&self) -> &'static str {
match self {
Impact::High => "HIGH",
Impact::Moderate => "MODERATE",
Impact::Low => "LOW",
Impact::Modifier => "MODIFIER",
}
}
}
impl std::fmt::Display for Impact {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct AminoAcidChange {
pub position: u64,
pub ref_aa: AminoAcid,
pub alt_aa: AminoAcid,
pub ref_codon: Option<Codon>,
pub alt_codon: Option<Codon>,
}
#[derive(Debug, Clone)]
pub struct ProteinEffect {
pub consequences: Vec<Consequence>,
pub impact: Impact,
pub amino_acid_change: Option<AminoAcidChange>,
pub intronic_offset: Option<i64>,
}
impl ProteinEffect {
pub fn most_severe(&self) -> Option<&Consequence> {
self.consequences.iter().max_by_key(|c| c.impact())
}
pub fn is_high_impact(&self) -> bool {
self.impact == Impact::High
}
pub fn is_protein_altering(&self) -> bool {
self.consequences.iter().any(|c| {
matches!(
c,
Consequence::MissenseVariant
| Consequence::StopGained
| Consequence::StopLost
| Consequence::StartLost
| Consequence::FrameshiftVariant
| Consequence::InframeInsertion
| Consequence::InframeDeletion
| Consequence::ProteinAlteringVariant
)
})
}
}
#[derive(Debug, Clone)]
pub struct EffectPredictor {
codon_table: CodonTable,
}
impl EffectPredictor {
pub fn new() -> Self {
Self {
codon_table: CodonTable::standard(),
}
}
pub fn classify_amino_acid_change(
&self,
ref_aa: &AminoAcid,
alt_aa: &AminoAcid,
position: u64,
) -> ProteinEffect {
let consequence = if ref_aa == alt_aa {
Consequence::SynonymousVariant
} else if *alt_aa == AminoAcid::Ter {
Consequence::StopGained
} else if *ref_aa == AminoAcid::Ter {
Consequence::StopLost
} else if *ref_aa == AminoAcid::Met && position == 1 {
Consequence::StartLost
} else {
Consequence::MissenseVariant
};
ProteinEffect {
consequences: vec![consequence],
impact: consequence.impact(),
amino_acid_change: Some(AminoAcidChange {
position,
ref_aa: *ref_aa,
alt_aa: *alt_aa,
ref_codon: None,
alt_codon: None,
}),
intronic_offset: None,
}
}
pub fn classify_indel(&self, ref_len: usize, alt_len: usize) -> ProteinEffect {
let net_change = alt_len as i64 - ref_len as i64;
let consequence = if net_change % 3 == 0 {
if net_change > 0 {
Consequence::InframeInsertion
} else if net_change < 0 {
Consequence::InframeDeletion
} else {
Consequence::CodingSequenceVariant
}
} else {
Consequence::FrameshiftVariant
};
ProteinEffect {
consequences: vec![consequence],
impact: consequence.impact(),
amino_acid_change: None,
intronic_offset: None,
}
}
pub fn classify_splice_variant(&self, offset: i64) -> ProteinEffect {
let abs_offset = offset.abs();
let consequence = if abs_offset <= 2 {
if offset > 0 {
Consequence::SpliceDonorVariant
} else {
Consequence::SpliceAcceptorVariant
}
} else if abs_offset <= 8 {
Consequence::SpliceRegionVariant
} else {
Consequence::IntronVariant
};
ProteinEffect {
consequences: vec![consequence],
impact: consequence.impact(),
amino_acid_change: None,
intronic_offset: Some(offset),
}
}
pub fn classify_utr_variant(&self, is_5_prime: bool) -> ProteinEffect {
let consequence = if is_5_prime {
Consequence::FivePrimeUtrVariant
} else {
Consequence::ThreePrimeUtrVariant
};
ProteinEffect {
consequences: vec![consequence],
impact: consequence.impact(),
amino_acid_change: None,
intronic_offset: None,
}
}
pub fn codon_table(&self) -> &CodonTable {
&self.codon_table
}
}
impl Default for EffectPredictor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NmdPrediction {
Triggered {
distance_to_junction: u64,
confidence: NmdConfidence,
},
NotTriggered {
reason: NmdNotTriggeredReason,
},
Uncertain {
reason: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NmdConfidence {
High,
Medium,
Low,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NmdNotTriggeredReason {
PtcInLastExon,
PtcNearLastJunction {
distance: u64,
},
SingleExonTranscript,
NoPtc,
PtcAtOrAfterNormalStop,
}
#[derive(Debug, Clone)]
pub struct TranscriptForNmd {
pub exon_boundaries: Vec<(u64, u64)>,
pub normal_stop_position: u64,
pub cds_length: u64,
}
#[derive(Debug, Clone)]
pub struct NmdPredictor {
pub threshold_low: u64,
pub threshold_high: u64,
}
impl NmdPredictor {
pub fn new() -> Self {
Self {
threshold_low: 50,
threshold_high: 55,
}
}
pub fn with_thresholds(threshold_low: u64, threshold_high: u64) -> Self {
Self {
threshold_low,
threshold_high,
}
}
pub fn predict(&self, ptc_position: u64, transcript: &TranscriptForNmd) -> NmdPrediction {
if transcript.exon_boundaries.len() <= 1 {
return NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::SingleExonTranscript,
};
}
if ptc_position >= transcript.normal_stop_position {
return NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::PtcAtOrAfterNormalStop,
};
}
let last_junction = transcript.exon_boundaries.last().map(|(start, _)| *start);
match last_junction {
Some(junction_pos) => {
if ptc_position >= junction_pos {
return NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::PtcInLastExon,
};
}
let distance = junction_pos.saturating_sub(ptc_position);
if distance > self.threshold_high {
NmdPrediction::Triggered {
distance_to_junction: distance,
confidence: NmdConfidence::High,
}
} else if distance >= self.threshold_low {
NmdPrediction::Triggered {
distance_to_junction: distance,
confidence: NmdConfidence::Medium,
}
} else {
NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::PtcNearLastJunction { distance },
}
}
}
None => NmdPrediction::Uncertain {
reason: "Could not determine last exon-exon junction position".to_string(),
},
}
}
pub fn predict_for_frameshift(
&self,
frameshift_position: u64,
new_stop_position: Option<u64>,
transcript: &TranscriptForNmd,
) -> NmdPrediction {
match new_stop_position {
Some(stop_pos) => self.predict(stop_pos, transcript),
None => {
NmdPrediction::Uncertain {
reason: format!(
"Frameshift at position {}; new stop codon position unknown",
frameshift_position
),
}
}
}
}
}
impl Default for NmdPredictor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KozakStrength {
Strong,
Adequate,
Weak,
Unknown,
}
#[derive(Debug, Clone)]
pub struct KozakAnalysis {
pub original_strength: KozakStrength,
pub variant_strength: Option<KozakStrength>,
pub affects_kozak: bool,
pub damages_initiation: bool,
pub positions_affected: Vec<i8>,
pub interpretation: KozakInterpretation,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KozakInterpretation {
NoEffect,
ReducedEfficiency,
LikelyAbolished,
Uncertain,
}
#[derive(Debug, Clone)]
pub struct KozakAnalyzer {
_consensus: Vec<(i8, Vec<char>)>,
}
impl KozakAnalyzer {
pub fn new() -> Self {
Self {
_consensus: vec![
(-3, vec!['A', 'G']), (4, vec!['G']), ],
}
}
pub fn affects_kozak_region(&self, variant_start: i64, variant_end: i64) -> bool {
let kozak_start: i64 = -6;
let kozak_end: i64 = 4;
variant_start <= kozak_end && variant_end >= kozak_start
}
pub fn analyze_strength(&self, sequence: &str) -> KozakStrength {
if sequence.len() < 10 {
return KozakStrength::Unknown;
}
let chars: Vec<char> = sequence.chars().collect();
let pos_minus3 = chars.get(3).map(|c| c.to_ascii_uppercase());
let pos_plus4 = chars.get(9).map(|c| c.to_ascii_uppercase());
let has_purine_minus3 = pos_minus3.is_some_and(|c| c == 'A' || c == 'G');
let has_g_plus4 = pos_plus4 == Some('G');
match (has_purine_minus3, has_g_plus4) {
(true, true) => KozakStrength::Strong,
(true, false) | (false, true) => KozakStrength::Adequate,
(false, false) => KozakStrength::Weak,
}
}
pub fn analyze_variant(
&self,
variant_start: i64,
variant_end: i64,
original_sequence: Option<&str>,
variant_sequence: Option<&str>,
) -> KozakAnalysis {
let affects_kozak = self.affects_kozak_region(variant_start, variant_end);
let original_strength = original_sequence
.map(|s| self.analyze_strength(s))
.unwrap_or(KozakStrength::Unknown);
let variant_strength = variant_sequence.map(|s| self.analyze_strength(s));
let mut positions_affected = Vec::new();
for pos in -6..=4i8 {
if (pos as i64) >= variant_start && (pos as i64) <= variant_end {
positions_affected.push(pos);
}
}
let damages_initiation = if affects_kozak {
let affects_critical =
positions_affected.contains(&-3) || positions_affected.contains(&4);
let strength_decreased = matches!(
(&original_strength, &variant_strength),
(KozakStrength::Strong, Some(KozakStrength::Adequate))
| (KozakStrength::Strong, Some(KozakStrength::Weak))
| (KozakStrength::Adequate, Some(KozakStrength::Weak))
);
affects_critical || strength_decreased
} else {
false
};
let interpretation = if !affects_kozak {
KozakInterpretation::NoEffect
} else if damages_initiation {
match variant_strength {
Some(KozakStrength::Weak) => KozakInterpretation::LikelyAbolished,
Some(KozakStrength::Adequate) => KozakInterpretation::ReducedEfficiency,
Some(KozakStrength::Strong) => KozakInterpretation::NoEffect,
_ => KozakInterpretation::Uncertain,
}
} else {
KozakInterpretation::Uncertain
};
KozakAnalysis {
original_strength,
variant_strength,
affects_kozak,
damages_initiation,
positions_affected,
interpretation,
}
}
}
impl Default for KozakAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InversionValidation {
Valid,
SequenceMismatch {
expected: String,
provided: String,
},
LengthMismatch {
range_length: usize,
sequence_length: usize,
},
CannotValidate {
reason: String,
},
}
pub fn validate_inversion(
reference_sequence: Option<&str>,
provided_sequence: Option<&str>,
start: u64,
end: u64,
) -> InversionValidation {
let expected_length = (end - start + 1) as usize;
if let Some(provided) = provided_sequence {
if provided.len() != expected_length {
return InversionValidation::LengthMismatch {
range_length: expected_length,
sequence_length: provided.len(),
};
}
}
match (reference_sequence, provided_sequence) {
(Some(reference), Some(provided)) => {
let expected_revcomp = reverse_complement(reference);
if provided.to_uppercase() == expected_revcomp.to_uppercase() {
InversionValidation::Valid
} else {
InversionValidation::SequenceMismatch {
expected: expected_revcomp,
provided: provided.to_string(),
}
}
}
(Some(_reference), None) => {
InversionValidation::Valid
}
(None, Some(_)) => InversionValidation::CannotValidate {
reason: "Reference sequence not available to validate inversion".to_string(),
},
(None, None) => InversionValidation::Valid, }
}
fn reverse_complement(sequence: &str) -> String {
sequence
.chars()
.rev()
.map(|c| match c.to_ascii_uppercase() {
'A' => 'T',
'T' => 'A',
'C' => 'G',
'G' => 'C',
'N' => 'N',
other => other,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nmd_triggered_high_confidence() {
let predictor = NmdPredictor::new();
let transcript = TranscriptForNmd {
exon_boundaries: vec![(1, 100), (101, 200), (201, 300)],
normal_stop_position: 300,
cds_length: 300,
};
let result = predictor.predict(100, &transcript);
match result {
NmdPrediction::Triggered {
distance_to_junction,
confidence,
} => {
assert_eq!(distance_to_junction, 101);
assert_eq!(confidence, NmdConfidence::High);
}
_ => panic!("Expected NMD to be triggered with high confidence"),
}
}
#[test]
fn test_nmd_triggered_medium_confidence() {
let predictor = NmdPredictor::new();
let transcript = TranscriptForNmd {
exon_boundaries: vec![(1, 100), (101, 200), (201, 300)],
normal_stop_position: 300,
cds_length: 300,
};
let result = predictor.predict(148, &transcript);
match result {
NmdPrediction::Triggered {
distance_to_junction,
confidence,
} => {
assert_eq!(distance_to_junction, 53);
assert_eq!(confidence, NmdConfidence::Medium);
}
_ => panic!("Expected NMD to be triggered with medium confidence"),
}
}
#[test]
fn test_nmd_not_triggered_near_junction() {
let predictor = NmdPredictor::new();
let transcript = TranscriptForNmd {
exon_boundaries: vec![(1, 100), (101, 200), (201, 300)],
normal_stop_position: 300,
cds_length: 300,
};
let result = predictor.predict(180, &transcript);
match result {
NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::PtcNearLastJunction { distance },
} => {
assert_eq!(distance, 21);
}
_ => panic!("Expected NMD not to be triggered due to proximity to junction"),
}
}
#[test]
fn test_nmd_not_triggered_last_exon() {
let predictor = NmdPredictor::new();
let transcript = TranscriptForNmd {
exon_boundaries: vec![(1, 100), (101, 200), (201, 300)],
normal_stop_position: 300,
cds_length: 300,
};
let result = predictor.predict(250, &transcript);
assert_eq!(
result,
NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::PtcInLastExon
}
);
}
#[test]
fn test_nmd_single_exon_transcript() {
let predictor = NmdPredictor::new();
let transcript = TranscriptForNmd {
exon_boundaries: vec![(1, 300)],
normal_stop_position: 300,
cds_length: 300,
};
let result = predictor.predict(100, &transcript);
assert_eq!(
result,
NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::SingleExonTranscript
}
);
}
#[test]
fn test_nmd_ptc_after_normal_stop() {
let predictor = NmdPredictor::new();
let transcript = TranscriptForNmd {
exon_boundaries: vec![(1, 100), (101, 200), (201, 300)],
normal_stop_position: 300,
cds_length: 300,
};
let result = predictor.predict(350, &transcript);
assert_eq!(
result,
NmdPrediction::NotTriggered {
reason: NmdNotTriggeredReason::PtcAtOrAfterNormalStop
}
);
}
#[test]
fn test_kozak_strong() {
let analyzer = KozakAnalyzer::new();
let strength = analyzer.analyze_strength("GCCACCATGG");
assert_eq!(strength, KozakStrength::Strong);
}
#[test]
fn test_kozak_adequate_purine_only() {
let analyzer = KozakAnalyzer::new();
let strength = analyzer.analyze_strength("GCCACCATGT");
assert_eq!(strength, KozakStrength::Adequate);
}
#[test]
fn test_kozak_adequate_g_plus4_only() {
let analyzer = KozakAnalyzer::new();
let strength = analyzer.analyze_strength("GCCTCCATGG");
assert_eq!(strength, KozakStrength::Adequate);
}
#[test]
fn test_kozak_weak() {
let analyzer = KozakAnalyzer::new();
let strength = analyzer.analyze_strength("GCCTCCATGT");
assert_eq!(strength, KozakStrength::Weak);
}
#[test]
fn test_kozak_affects_region() {
let analyzer = KozakAnalyzer::new();
assert!(analyzer.affects_kozak_region(-5, -5));
assert!(analyzer.affects_kozak_region(3, 3));
assert!(analyzer.affects_kozak_region(-3, 4));
assert!(!analyzer.affects_kozak_region(-10, -10));
assert!(!analyzer.affects_kozak_region(10, 10));
}
#[test]
fn test_kozak_variant_analysis() {
let analyzer = KozakAnalyzer::new();
let analysis = analyzer.analyze_variant(
-5,
-1,
Some("GCCACCATGG"), Some("GCCATATGG"), );
assert!(analysis.affects_kozak);
assert_eq!(analysis.original_strength, KozakStrength::Strong);
}
#[test]
fn test_inversion_valid() {
let result = validate_inversion(Some("ATGC"), Some("GCAT"), 1, 4);
assert_eq!(result, InversionValidation::Valid);
}
#[test]
fn test_inversion_sequence_mismatch() {
let result = validate_inversion(Some("ATGC"), Some("AAAA"), 1, 4);
match result {
InversionValidation::SequenceMismatch { expected, provided } => {
assert_eq!(expected, "GCAT");
assert_eq!(provided, "AAAA");
}
_ => panic!("Expected sequence mismatch"),
}
}
#[test]
fn test_inversion_length_mismatch() {
let result = validate_inversion(Some("ATGC"), Some("GC"), 1, 4);
match result {
InversionValidation::LengthMismatch {
range_length,
sequence_length,
} => {
assert_eq!(range_length, 4);
assert_eq!(sequence_length, 2);
}
_ => panic!("Expected length mismatch"),
}
}
#[test]
fn test_inversion_no_sequence_provided() {
let result = validate_inversion(Some("ATGC"), None, 1, 4);
assert_eq!(result, InversionValidation::Valid);
}
#[test]
fn test_reverse_complement() {
assert_eq!(reverse_complement("ATGC"), "GCAT");
assert_eq!(reverse_complement("AAAA"), "TTTT");
assert_eq!(reverse_complement("GCGC"), "GCGC");
assert_eq!(reverse_complement("A"), "T");
}
#[test]
fn test_consequence_impact() {
assert_eq!(Consequence::StopGained.impact(), Impact::High);
assert_eq!(Consequence::MissenseVariant.impact(), Impact::Moderate);
assert_eq!(Consequence::SynonymousVariant.impact(), Impact::Low);
assert_eq!(Consequence::IntronVariant.impact(), Impact::Modifier);
}
#[test]
fn test_consequence_so_terms() {
assert_eq!(Consequence::MissenseVariant.so_term(), "missense_variant");
assert_eq!(Consequence::MissenseVariant.so_id(), "SO:0001583");
}
#[test]
fn test_classify_missense() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_amino_acid_change(&AminoAcid::Val, &AminoAcid::Glu, 600);
assert_eq!(effect.consequences[0], Consequence::MissenseVariant);
assert_eq!(effect.impact, Impact::Moderate);
}
#[test]
fn test_classify_synonymous() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_amino_acid_change(&AminoAcid::Val, &AminoAcid::Val, 100);
assert_eq!(effect.consequences[0], Consequence::SynonymousVariant);
assert_eq!(effect.impact, Impact::Low);
}
#[test]
fn test_classify_stop_gained() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_amino_acid_change(&AminoAcid::Gln, &AminoAcid::Ter, 100);
assert_eq!(effect.consequences[0], Consequence::StopGained);
assert_eq!(effect.impact, Impact::High);
}
#[test]
fn test_classify_stop_lost() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_amino_acid_change(&AminoAcid::Ter, &AminoAcid::Gln, 500);
assert_eq!(effect.consequences[0], Consequence::StopLost);
assert_eq!(effect.impact, Impact::High);
}
#[test]
fn test_classify_start_lost() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_amino_acid_change(&AminoAcid::Met, &AminoAcid::Val, 1);
assert_eq!(effect.consequences[0], Consequence::StartLost);
assert_eq!(effect.impact, Impact::High);
}
#[test]
fn test_classify_frameshift() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_indel(1, 0);
assert_eq!(effect.consequences[0], Consequence::FrameshiftVariant);
assert_eq!(effect.impact, Impact::High);
let effect = predictor.classify_indel(0, 2);
assert_eq!(effect.consequences[0], Consequence::FrameshiftVariant);
}
#[test]
fn test_classify_inframe() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_indel(3, 0);
assert_eq!(effect.consequences[0], Consequence::InframeDeletion);
assert_eq!(effect.impact, Impact::Moderate);
let effect = predictor.classify_indel(0, 6);
assert_eq!(effect.consequences[0], Consequence::InframeInsertion);
}
#[test]
fn test_classify_splice_donor() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_splice_variant(1);
assert_eq!(effect.consequences[0], Consequence::SpliceDonorVariant);
assert_eq!(effect.impact, Impact::High);
}
#[test]
fn test_classify_splice_acceptor() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_splice_variant(-2);
assert_eq!(effect.consequences[0], Consequence::SpliceAcceptorVariant);
assert_eq!(effect.impact, Impact::High);
}
#[test]
fn test_classify_splice_region() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_splice_variant(5);
assert_eq!(effect.consequences[0], Consequence::SpliceRegionVariant);
assert_eq!(effect.impact, Impact::Low);
}
#[test]
fn test_classify_intron() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_splice_variant(50);
assert_eq!(effect.consequences[0], Consequence::IntronVariant);
assert_eq!(effect.impact, Impact::Modifier);
}
#[test]
fn test_classify_utr() {
let predictor = EffectPredictor::new();
let effect = predictor.classify_utr_variant(true);
assert_eq!(effect.consequences[0], Consequence::FivePrimeUtrVariant);
let effect = predictor.classify_utr_variant(false);
assert_eq!(effect.consequences[0], Consequence::ThreePrimeUtrVariant);
}
#[test]
fn test_protein_effect_methods() {
let effect = ProteinEffect {
consequences: vec![Consequence::StopGained, Consequence::SpliceRegionVariant],
impact: Impact::High,
amino_acid_change: None,
intronic_offset: None,
};
assert_eq!(effect.most_severe(), Some(&Consequence::StopGained));
assert!(effect.is_high_impact());
assert!(effect.is_protein_altering());
}
#[test]
fn test_impact_ordering() {
assert!(Impact::High > Impact::Moderate);
assert!(Impact::Moderate > Impact::Low);
assert!(Impact::Low > Impact::Modifier);
}
}