use super::helpers::{
build_base_result, compute_cdna_position_exonic, compute_cdna_position_for_cds,
fetch_cds_sequence, fetch_ref_codon, finalize_consequences,
};
use super::{Consequence, ConsequenceResult};
use crate::codon::{
format_amino_acids_indel, format_codons_indel, reverse_complement,
translate_codon_for_transcript, translate_sequence,
};
use crate::error::VarEffectError;
use crate::fasta::FastaReader;
use crate::hgvs_c;
use crate::locate::{
IndelLocation, IndelRegion, LocateIndex, format_exon_number, format_intron_number, locate_indel,
};
use crate::types::{Strand, TranscriptModel, TranscriptTier};
pub fn annotate_deletion(
chrom: &str,
start: u64,
end: u64,
_deleted_bases: &[u8],
transcript: &TranscriptModel,
locate_index: &LocateIndex,
fasta: &FastaReader,
) -> Result<ConsequenceResult, VarEffectError> {
let location = locate_indel(chrom, start, end, transcript, locate_index)?;
let shift = if !location.crosses_exon_boundary {
crate::normalize::compute_3prime_shift_deletion(chrom, start, end, transcript, fasta)?
} else {
0
};
let (hgvs_start, hgvs_end) = match transcript.strand {
Strand::Plus => (start + shift, end + shift),
Strand::Minus => (start - shift, end - shift),
};
let hgvs = hgvs_c::format_deletion_hgvs(chrom, hgvs_start, hgvs_end, transcript, locate_index)?;
if location.overlaps_splice_canonical {
let mut result = build_splice_indel_result(transcript, &location, start, end)?;
result.hgvs_c = hgvs;
return Ok(result);
}
if location.crosses_exon_boundary {
let mut result = super::complex::annotate_boundary_spanning_deletion(
chrom,
start,
end,
transcript,
locate_index,
fasta,
&location,
)?;
result.hgvs_c = hgvs;
return Ok(result);
}
if matches!(location.region, IndelRegion::BoundarySpanning) {
let mut result = super::complex::annotate_boundary_spanning_deletion(
chrom,
start,
end,
transcript,
locate_index,
fasta,
&location,
)?;
result.hgvs_c = hgvs;
return Ok(result);
}
let mut result = match &location.region {
IndelRegion::Cds {
cds_offset_start,
cds_offset_end,
} => {
let del_cds_len = cds_offset_end - cds_offset_start;
let exon_index = location.exon_index.unwrap_or(0);
if !del_cds_len.is_multiple_of(3) {
annotate_cds_frameshift(
chrom,
*cds_offset_start,
transcript,
locate_index,
fasta,
exon_index,
location.overlaps_splice_region,
*cds_offset_end,
None,
None,
0,
)
} else {
annotate_cds_inframe_deletion(
chrom,
*cds_offset_start,
*cds_offset_end,
transcript,
locate_index,
fasta,
exon_index,
location.overlaps_splice_region,
)
}
}
_ => build_noncds_indel_result(transcript, &location, start, end),
}?;
result.hgvs_c = hgvs;
Ok(result)
}
pub fn annotate_insertion(
chrom: &str,
pos: u64,
inserted_bases: &[u8],
transcript: &TranscriptModel,
locate_index: &LocateIndex,
fasta: &FastaReader,
) -> Result<ConsequenceResult, VarEffectError> {
let location = locate_indel(chrom, pos, pos, transcript, locate_index)?;
let shift = if !location.crosses_exon_boundary {
crate::normalize::compute_3prime_shift_insertion(
chrom,
pos,
inserted_bases,
transcript,
fasta,
)?
} else {
0
};
let shifted_pos = match transcript.strand {
Strand::Plus => pos + shift,
Strand::Minus => pos - shift,
};
let hgvs = hgvs_c::format_insertion_hgvs(
shifted_pos,
inserted_bases,
chrom,
transcript,
locate_index,
fasta,
)?;
if location.overlaps_splice_canonical {
let mut result = build_splice_indel_result(transcript, &location, pos, pos)?;
result.hgvs_c = hgvs;
return Ok(result);
}
if location.crosses_exon_boundary {
let mut consequences = vec![Consequence::IntronVariant];
if location.overlaps_splice_region {
consequences.push(Consequence::SpliceRegionVariant);
}
let impact = finalize_consequences(&mut consequences);
let mut result = build_base_result(transcript, consequences);
result.impact = impact;
if let Some(intron_idx) = location.intron_index {
result.intron = Some(format_intron_number(intron_idx, transcript.exon_count));
}
if let Some(exon_idx) = location.exon_index {
result.exon = Some(format_exon_number(exon_idx, transcript.exon_count));
}
result.hgvs_c = hgvs;
return Ok(result);
}
let mut result = match &location.region {
IndelRegion::Cds {
cds_offset_start, ..
} => {
let ins_len = inserted_bases.len() as u32;
let exon_index = location.exon_index.unwrap_or(0);
if !ins_len.is_multiple_of(3) {
annotate_cds_frameshift(
chrom,
*cds_offset_start,
transcript,
locate_index,
fasta,
exon_index,
location.overlaps_splice_region,
*cds_offset_start,
Some(inserted_bases),
None,
0, )
} else {
annotate_cds_inframe_insertion(
chrom,
*cds_offset_start,
inserted_bases,
transcript,
locate_index,
fasta,
exon_index,
location.overlaps_splice_region,
shift as u32,
)
}
}
_ => build_noncds_indel_result(transcript, &location, pos, pos),
}?;
result.hgvs_c = hgvs;
Ok(result)
}
#[allow(clippy::too_many_arguments)]
pub(super) fn annotate_cds_frameshift(
chrom: &str,
cds_offset_start: u32,
transcript: &TranscriptModel,
index: &LocateIndex,
fasta: &FastaReader,
exon_index: u16,
is_splice_region: bool,
cds_offset_end: u32,
inserted_bases: Option<&[u8]>,
delins_alt: Option<&[u8]>,
dna_shift: u32,
) -> Result<ConsequenceResult, VarEffectError> {
let is_mito = transcript.chrom == "chrM";
let is_insertion = cds_offset_end == cds_offset_start;
let codon_number = if is_insertion && cds_offset_start > 0 && cds_offset_start.is_multiple_of(3)
{
cds_offset_start / 3
} else {
cds_offset_start / 3 + 1
};
let overlaps_start_codon = codon_number == 1;
let start_lost = if overlaps_start_codon {
if cds_offset_start == 0 && cds_offset_end >= 3 {
true
} else if is_insertion && cds_offset_start < 3 {
true
} else if !is_insertion && cds_offset_start < 3 {
let ref_codon = fetch_ref_codon(0, chrom, transcript, index, fasta)?;
let del_start = cds_offset_start as usize;
let del_end = (cds_offset_end.min(3)) as usize;
let mut remaining: Vec<u8> = Vec::with_capacity(3);
remaining.extend_from_slice(&ref_codon[..del_start]);
remaining.extend_from_slice(&ref_codon[del_end..]);
remaining.len() < 3
|| translate_codon_for_transcript(
remaining[..3]
.try_into()
.expect("length >= 3 guaranteed by short-circuit above"),
is_mito,
) != b'M'
} else {
false
}
} else {
false
};
let predicts_nmd = !start_lost && super::nmd::predicts_nmd(cds_offset_start + 1, index);
let mut consequences = vec![Consequence::FrameshiftVariant];
if start_lost {
consequences.push(Consequence::StartLost);
}
if is_splice_region {
consequences.push(Consequence::SpliceRegionVariant);
}
let codon_start = (cds_offset_start / 3) * 3;
let coding_inserted_for_hgvs: Vec<u8> = if let Some(bases) = inserted_bases {
match transcript.strand {
Strand::Plus => bases.to_vec(),
Strand::Minus => reverse_complement(bases),
}
} else if let Some(alt_bases) = delins_alt {
match transcript.strand {
Strand::Plus => alt_bases.to_vec(),
Strand::Minus => reverse_complement(alt_bases),
}
} else {
Vec::new() };
let (codons, amino_acids, protein_end) = if is_insertion {
let ref_codon = fetch_ref_codon(codon_start, chrom, transcript, index, fasta)?;
let ref_aa = translate_codon_for_transcript(&ref_codon, is_mito);
let codons = inserted_bases.map(|bases| {
let coding_bases = match transcript.strand {
Strand::Plus => bases.to_vec(),
Strand::Minus => reverse_complement(bases),
};
let ins_str: String = coding_bases.iter().map(|&b| b as char).collect();
format!("-/{ins_str}")
});
let aa_char = if ref_aa == b'X' { 'X' } else { ref_aa as char };
(codons, format!("{aa_char}/X"), codon_number)
} else {
let codon_end = ((cds_offset_end - 1) / 3 + 1) * 3;
let ref_seq = fetch_cds_sequence(codon_start, codon_end, chrom, transcript, index, fasta)?;
let local_del_start = (cds_offset_start - codon_start) as usize;
let local_del_end = (cds_offset_end - codon_start) as usize;
let replacement_len = delins_alt.map_or(0, |b| b.len());
let mut alt_seq =
Vec::with_capacity(ref_seq.len() - (local_del_end - local_del_start) + replacement_len);
alt_seq.extend_from_slice(&ref_seq[..local_del_start]);
if let Some(alt_bases) = delins_alt {
let coding_alt = match transcript.strand {
Strand::Plus => alt_bases.to_vec(),
Strand::Minus => reverse_complement(alt_bases),
};
alt_seq.extend_from_slice(&coding_alt);
}
alt_seq.extend_from_slice(&ref_seq[local_del_end..]);
let ref_aas = translate_sequence(&ref_seq, is_mito)?;
let translate_len = alt_seq.len() - (alt_seq.len() % 3);
let mut alt_aas = translate_sequence(&alt_seq[..translate_len], is_mito)?;
alt_aas.push(b'X');
let codons_str = format_codons_indel(&ref_seq, &alt_seq, local_del_start, local_del_end);
let aa_str = format_amino_acids_indel(&ref_aas, &alt_aas);
(Some(codons_str), aa_str, codon_end / 3)
};
let impact = finalize_consequences(&mut consequences);
let (cds_pos, cds_pos_end, cdna_position, cdna_end) = if is_insertion {
let cdna_after = compute_cdna_position_for_cds(cds_offset_start, index);
let cdna_before = if cds_offset_start > 0 {
compute_cdna_position_for_cds(cds_offset_start - 1, index)
} else {
cdna_after
};
(
cds_offset_start,
cds_offset_start + 1,
cdna_before.min(cdna_after),
cdna_before.max(cdna_after),
)
} else {
let cdna_start = compute_cdna_position_for_cds(cds_offset_start, index);
let last_offset = cds_offset_end - 1;
let cdna_end = compute_cdna_position_for_cds(last_offset, index);
(cds_offset_start + 1, last_offset + 1, cdna_start, cdna_end)
};
let hgvs_p = if start_lost {
Some("p.Met1?".to_string())
} else {
crate::hgvs_p::format_hgvs_p_frameshift(
cds_offset_start,
cds_offset_end,
&coding_inserted_for_hgvs,
chrom,
transcript,
index,
fasta,
dna_shift,
)?
};
Ok(ConsequenceResult {
transcript: transcript.accession.clone(),
gene_symbol: transcript.gene_symbol.clone(),
protein_accession: transcript.protein_accession.clone(),
consequences,
impact,
protein_start: Some(codon_number),
protein_end: Some(protein_end),
codons,
amino_acids: Some(amino_acids),
exon: Some(format_exon_number(exon_index, transcript.exon_count)),
intron: None,
cds_position: Some(cds_pos),
cds_position_end: Some(cds_pos_end),
cdna_position: Some(cdna_position),
cdna_position_end: Some(cdna_end),
strand: transcript.strand,
biotype: transcript.biotype.clone(),
is_mane_select: transcript.tier == TranscriptTier::ManeSelect,
is_mane_plus_clinical: transcript.tier == TranscriptTier::ManePlusClinical,
is_refseq_select: transcript.tier == TranscriptTier::RefSeqSelect,
hgvs_c: None,
hgvs_p,
predicts_nmd,
})
}
#[allow(clippy::too_many_arguments)]
fn annotate_cds_inframe_deletion(
chrom: &str,
cds_offset_start: u32,
cds_offset_end: u32,
transcript: &TranscriptModel,
index: &LocateIndex,
fasta: &FastaReader,
exon_index: u16,
is_splice_region: bool,
) -> Result<ConsequenceResult, VarEffectError> {
let is_mito = transcript.chrom == "chrM";
let total_cds = index.total_cds_length();
let codon_start = (cds_offset_start / 3) * 3;
let codon_end = ((cds_offset_end - 1) / 3 + 1) * 3;
let ref_seq = fetch_cds_sequence(codon_start, codon_end, chrom, transcript, index, fasta)?;
let local_del_start = (cds_offset_start - codon_start) as usize;
let local_del_end = (cds_offset_end - codon_start) as usize;
let mut alt_seq = Vec::with_capacity(ref_seq.len() - (local_del_end - local_del_start));
alt_seq.extend_from_slice(&ref_seq[..local_del_start]);
alt_seq.extend_from_slice(&ref_seq[local_del_end..]);
let ref_aas = translate_sequence(&ref_seq, is_mito)?;
let alt_aas = translate_sequence(&alt_seq, is_mito)?;
let first_codon = codon_start / 3;
let mut consequences = Vec::new();
if first_codon == 0
&& ((cds_offset_start == 0 && cds_offset_end >= 3) || alt_aas.first() != Some(&b'M'))
{
consequences.push(Consequence::StartLost);
}
if alt_aas.contains(&b'*') {
consequences.push(Consequence::StopGained);
}
if ref_aas.contains(&b'*') && !alt_aas.contains(&b'*') {
consequences.push(Consequence::StopLost);
}
consequences.push(Consequence::InframeDeletion);
if is_splice_region {
consequences.push(Consequence::SpliceRegionVariant);
}
let impact = finalize_consequences(&mut consequences);
let protein_start = first_codon + 1;
let protein_end = codon_end / 3;
let cdna_start = compute_cdna_position_for_cds(cds_offset_start, index);
let cdna_end = compute_cdna_position_for_cds(cds_offset_end - 1, index);
let ext_codon_end = (codon_end + 90).min(total_cds);
let (ref_aas_ext, alt_aas_ext) = if ext_codon_end > codon_end {
let ext_seq =
fetch_cds_sequence(codon_end, ext_codon_end, chrom, transcript, index, fasta)?;
let ext_aas = translate_sequence(&ext_seq, is_mito)?;
let mut r = ref_aas.clone();
r.extend_from_slice(&ext_aas);
let mut a = alt_aas.clone();
a.extend_from_slice(&ext_aas);
(r, a)
} else {
(ref_aas.clone(), alt_aas.clone())
};
let mut hgvs_p = crate::hgvs_p::format_hgvs_p_inframe_del(
&ref_aas_ext,
&alt_aas_ext,
protein_start,
&consequences,
);
if hgvs_p.is_none() && consequences.contains(&Consequence::StopLost) {
let stop_protein_pos = ref_aas
.iter()
.position(|&a| a == b'*')
.map(|i| protein_start + i as u32)
.unwrap_or(protein_end);
hgvs_p = Some(crate::hgvs_p::format_hgvs_p_del_extension(
stop_protein_pos,
chrom,
transcript,
fasta,
)?);
}
let predicts_nmd = consequences.contains(&Consequence::StopGained)
&& super::nmd::predicts_nmd(cds_offset_start + 1, index);
Ok(ConsequenceResult {
transcript: transcript.accession.clone(),
gene_symbol: transcript.gene_symbol.clone(),
protein_accession: transcript.protein_accession.clone(),
consequences,
impact,
protein_start: Some(protein_start),
protein_end: Some(protein_end),
codons: Some(format_codons_indel(
&ref_seq,
&alt_seq,
local_del_start,
local_del_end,
)),
amino_acids: Some(format_amino_acids_indel(&ref_aas, &alt_aas)),
exon: Some(format_exon_number(exon_index, transcript.exon_count)),
intron: None,
cds_position: Some(cds_offset_start + 1),
cds_position_end: Some(cds_offset_end),
cdna_position: Some(cdna_start),
cdna_position_end: Some(cdna_end),
strand: transcript.strand,
biotype: transcript.biotype.clone(),
is_mane_select: transcript.tier == TranscriptTier::ManeSelect,
is_mane_plus_clinical: transcript.tier == TranscriptTier::ManePlusClinical,
is_refseq_select: transcript.tier == TranscriptTier::RefSeqSelect,
hgvs_c: None,
hgvs_p,
predicts_nmd,
})
}
#[allow(clippy::too_many_arguments)]
fn annotate_cds_inframe_insertion(
chrom: &str,
cds_offset: u32,
inserted_bases: &[u8],
transcript: &TranscriptModel,
index: &LocateIndex,
fasta: &FastaReader,
exon_index: u16,
is_splice_region: bool,
dna_shift: u32,
) -> Result<ConsequenceResult, VarEffectError> {
let is_mito = transcript.chrom == "chrM";
let at_codon_boundary = cds_offset.is_multiple_of(3) && cds_offset >= 3;
let codon_start = if at_codon_boundary {
(cds_offset / 3 - 1) * 3
} else {
(cds_offset / 3) * 3
};
let codon_end = codon_start + if at_codon_boundary { 6 } else { 3 };
let codon_end = codon_end.min(index.total_cds_length());
let ref_seq = fetch_cds_sequence(codon_start, codon_end, chrom, transcript, index, fasta)?;
let local_ins_pos = (cds_offset - codon_start) as usize;
let coding_inserted = match transcript.strand {
Strand::Plus => inserted_bases.to_vec(),
Strand::Minus => reverse_complement(inserted_bases),
};
let mut alt_seq = Vec::with_capacity(ref_seq.len() + coding_inserted.len());
alt_seq.extend_from_slice(&ref_seq[..local_ins_pos]);
alt_seq.extend_from_slice(&coding_inserted);
alt_seq.extend_from_slice(&ref_seq[local_ins_pos..]);
let ref_aas = translate_sequence(&ref_seq, is_mito)?;
let alt_aas = translate_sequence(&alt_seq, is_mito)?;
let first_codon = codon_start / 3;
let mut consequences = Vec::new();
if first_codon == 0 && alt_aas.first() != Some(&b'M') {
consequences.push(Consequence::StartLost);
}
if alt_aas.contains(&b'*') && !ref_aas.contains(&b'*') {
consequences.push(Consequence::StopGained);
}
if ref_aas.contains(&b'*') && !alt_aas.contains(&b'*') {
consequences.push(Consequence::StopLost);
}
if !consequences
.iter()
.any(|c| matches!(c, Consequence::StartLost))
{
consequences.push(Consequence::InframeInsertion);
}
if is_splice_region {
consequences.push(Consequence::SpliceRegionVariant);
}
let impact = finalize_consequences(&mut consequences);
let protein_pos = first_codon + 1;
let cdna_after = compute_cdna_position_for_cds(cds_offset, index);
let cdna_before = if cds_offset > 0 {
compute_cdna_position_for_cds(cds_offset - 1, index)
} else {
cdna_after
};
let right_flanking_aa = if codon_end < index.total_cds_length() {
let next_codon = fetch_ref_codon(codon_end, chrom, transcript, index, fasta)?;
translate_codon_for_transcript(&next_codon, is_mito)
} else {
b'*' };
let shifted_cds = cds_offset.saturating_add(dna_shift);
let hgvs_p = if dna_shift > 0 && shifted_cds < index.total_cds_length() {
let ins_aa_count = (coding_inserted.len() / 3) as u32;
let upstream_codons = ins_aa_count + 1; let s_codon_start = (shifted_cds / 3).saturating_sub(upstream_codons) * 3;
let s_codon_end = ((shifted_cds / 3 + 1) * 3).min(index.total_cds_length());
let s_ref =
fetch_cds_sequence(s_codon_start, s_codon_end, chrom, transcript, index, fasta)?;
let s_local = (shifted_cds - s_codon_start) as usize;
let mut s_alt = Vec::with_capacity(s_ref.len() + coding_inserted.len());
s_alt.extend_from_slice(&s_ref[..s_local]);
s_alt.extend_from_slice(&coding_inserted);
s_alt.extend_from_slice(&s_ref[s_local..]);
let s_ref_aas = translate_sequence(&s_ref, is_mito)?;
let s_alt_aas = translate_sequence(&s_alt, is_mito)?;
let s_protein_pos = s_codon_start / 3 + 1;
let s_right_flank = if s_codon_end < index.total_cds_length() {
let nc = fetch_ref_codon(s_codon_end, chrom, transcript, index, fasta)?;
translate_codon_for_transcript(&nc, is_mito)
} else {
b'*'
};
crate::hgvs_p::format_hgvs_p_inframe_ins(
&s_ref_aas,
&s_alt_aas,
s_protein_pos,
&consequences,
s_right_flank,
)
} else {
let ins_aa_count = (coding_inserted.len() / 3) as u32;
let upstream_codons = ins_aa_count + 1;
let aligned_cds = cds_offset.div_ceil(3) * 3;
let aligned_cds = aligned_cds.min(index.total_cds_length());
let aligned_dup = if aligned_cds != cds_offset {
let a_start = (aligned_cds / 3).saturating_sub(upstream_codons) * 3;
let a_end = ((aligned_cds / 3 + 1) * 3).min(index.total_cds_length());
let a_ref = fetch_cds_sequence(a_start, a_end, chrom, transcript, index, fasta)?;
let a_local = (aligned_cds - a_start) as usize;
let mut a_alt = Vec::with_capacity(a_ref.len() + coding_inserted.len());
a_alt.extend_from_slice(&a_ref[..a_local]);
a_alt.extend_from_slice(&coding_inserted);
a_alt.extend_from_slice(&a_ref[a_local..]);
let a_ref_aas = translate_sequence(&a_ref, is_mito)?;
let a_alt_aas = translate_sequence(&a_alt, is_mito)?;
let a_pos = a_start / 3 + 1;
let a_flank = if a_end < index.total_cds_length() {
let nc = fetch_ref_codon(a_end, chrom, transcript, index, fasta)?;
translate_codon_for_transcript(&nc, is_mito)
} else {
b'*'
};
let r = crate::hgvs_p::format_hgvs_p_inframe_ins(
&a_ref_aas,
&a_alt_aas,
a_pos,
&consequences,
a_flank,
);
match &r {
Some(s) if s.contains("dup") => Some(r),
_ => None,
}
} else {
None
};
if let Some(dup_result) = aligned_dup {
dup_result
} else {
crate::hgvs_p::format_hgvs_p_inframe_ins(
&ref_aas,
&alt_aas,
protein_pos,
&consequences,
right_flanking_aa,
)
}
};
let predicts_nmd = consequences.contains(&Consequence::StopGained)
&& super::nmd::predicts_nmd(cds_offset + 1, index);
Ok(ConsequenceResult {
transcript: transcript.accession.clone(),
gene_symbol: transcript.gene_symbol.clone(),
protein_accession: transcript.protein_accession.clone(),
consequences,
impact,
protein_start: Some(protein_pos),
protein_end: Some(protein_pos),
codons: Some(format_codons_indel(
&ref_seq,
&alt_seq,
local_ins_pos,
local_ins_pos,
)),
amino_acids: Some(format_amino_acids_indel(&ref_aas, &alt_aas)),
exon: Some(format_exon_number(exon_index, transcript.exon_count)),
intron: None,
cds_position: Some(cds_offset),
cds_position_end: Some(cds_offset + 1),
cdna_position: Some(cdna_before.min(cdna_after)),
cdna_position_end: Some(cdna_before.max(cdna_after)),
strand: transcript.strand,
biotype: transcript.biotype.clone(),
is_mane_select: transcript.tier == TranscriptTier::ManeSelect,
is_mane_plus_clinical: transcript.tier == TranscriptTier::ManePlusClinical,
is_refseq_select: transcript.tier == TranscriptTier::RefSeqSelect,
hgvs_c: None,
hgvs_p,
predicts_nmd,
})
}
pub(super) fn build_splice_indel_result(
transcript: &TranscriptModel,
location: &IndelLocation,
var_start: u64,
var_end: u64,
) -> Result<ConsequenceResult, VarEffectError> {
let detail = location.splice_detail.as_ref().ok_or_else(|| {
VarEffectError::Malformed(format!(
"{}: build_splice_indel_result called without splice_detail",
transcript.accession,
))
})?;
let mut consequences = Vec::with_capacity(4);
if detail.overlaps_donor {
consequences.push(Consequence::SpliceDonorVariant);
}
if detail.overlaps_acceptor {
consequences.push(Consequence::SpliceAcceptorVariant);
}
if consequences.is_empty() {
return Err(VarEffectError::Malformed(format!(
"{}: splice canonical flag set but detail reports neither donor nor acceptor",
transcript.accession,
)));
}
let has_intron =
matches!(location.region, IndelRegion::Intron) || location.crosses_exon_boundary;
if has_intron {
consequences.push(Consequence::IntronVariant);
}
if let (Some(cds_start), Some(cds_end)) =
(transcript.cds_genomic_start, transcript.cds_genomic_end)
&& var_start < cds_end
&& var_end > cds_start
{
consequences.push(Consequence::CodingSequenceVariant);
}
if location.overlaps_splice_region {
consequences.push(Consequence::SpliceRegionVariant);
}
let impact = finalize_consequences(&mut consequences);
let mut result = build_base_result(transcript, consequences);
result.impact = impact;
if let Some(&idx) = detail
.donor_intron_indices
.first()
.or(detail.acceptor_intron_indices.first())
{
result.intron = Some(format_intron_number(idx, transcript.exon_count));
} else if let Some(intron_idx) = location.intron_index {
result.intron = Some(format_intron_number(intron_idx, transcript.exon_count));
}
if let Some(exon_idx) = location.exon_index {
result.exon = Some(format_exon_number(exon_idx, transcript.exon_count));
}
Ok(result)
}
pub(super) fn build_noncds_indel_result(
transcript: &TranscriptModel,
location: &IndelLocation,
start: u64,
end: u64,
) -> Result<ConsequenceResult, VarEffectError> {
let mut consequences = match &location.region {
IndelRegion::FivePrimeUtr => vec![Consequence::FivePrimeUtrVariant],
IndelRegion::ThreePrimeUtr => vec![Consequence::ThreePrimeUtrVariant],
IndelRegion::Intron => vec![Consequence::IntronVariant],
IndelRegion::NonCodingExon => vec![Consequence::NonCodingTranscriptExonVariant],
IndelRegion::Upstream => vec![Consequence::UpstreamGeneVariant],
IndelRegion::Downstream => vec![Consequence::DownstreamGeneVariant],
_ => vec![Consequence::CodingSequenceVariant],
};
if location.overlaps_splice_region {
consequences.push(Consequence::SpliceRegionVariant);
}
let mut result = build_base_result(transcript, consequences);
if let Some(exon_idx) = location.exon_index {
result.exon = Some(format_exon_number(exon_idx, transcript.exon_count));
let is_insertion = end == start;
if is_insertion {
let cdna_at = compute_cdna_position_exonic(start, transcript);
let cdna_adj = if start > 0 {
compute_cdna_position_exonic(start - 1, transcript)
} else {
cdna_at
};
match (cdna_at, cdna_adj) {
(Some(a), Some(b)) => {
result.cdna_position = Some(a.min(b));
result.cdna_position_end = Some(a.max(b));
}
(v @ Some(_), None) | (None, v @ Some(_)) => {
result.cdna_position = v;
result.cdna_position_end = v;
}
(None, None) => {}
}
} else {
result.cdna_position = compute_cdna_position_exonic(start, transcript);
result.cdna_position_end = compute_cdna_position_exonic(end - 1, transcript);
}
}
if let Some(intron_idx) = location.intron_index {
result.intron = Some(format_intron_number(intron_idx, transcript.exon_count));
}
Ok(result)
}