///|
/// Validate every CDS feature that provides a `/translation` qualifier.
pub fn Record::check_cds_translations(self : Record) -> Array[TranslationCheck] {
let checks : Array[TranslationCheck] = []
for index, feature in self.features {
if feature.key == "CDS" {
match feature.qualifier("translation") {
Some(expected) => {
let dna = self.sequence_for(feature)
let actual = translate_dna(dna)
checks.push({
feature_index: index,
location: feature.location.raw,
expected,
actual,
ok: expected == actual,
})
}
None => ()
}
}
}
checks
}
///|
/// Extract and orient feature sequence from the parsed ORIGIN.
pub fn Record::sequence_for(self : Record, feature : Feature) -> String {
let pieces : Array[String] = []
for segment in feature.location.segments {
if segment.remote is Some(_) {
continue
}
let range = segment.range
let start = Int::max(0, range.start - 1)
let end = Int::min(self.origin.length(), range.end)
if start < end {
let piece = self.origin.unsafe_substring(start~, end~)
pieces.push(
if segment.reverse {
reverse_complement(piece)
} else {
piece
},
)
}
}
pieces.join("")
}
///|
pub fn translate_dna(seq : String) -> String {
let upper = seq.to_upper().replace_all(old="U", new="T")
let amino : Array[String] = []
for i = 0; i + 2 < upper.length(); i = i + 3 {
let codon = upper.unsafe_substring(start=i, end=i + 3)
amino.push(codon_to_amino(codon))
}
amino.join("")
}
///|
pub fn reverse_complement(seq : String) -> String {
let out = StringBuilder()
for ch in seq.rev_iter() {
out.write_char(complement_base(ch))
}
out.to_string()
}
///|
fn complement_base(ch : Char) -> Char {
match ch {
'A' | 'a' => 'T'
'C' | 'c' => 'G'
'G' | 'g' => 'C'
'T' | 't' | 'U' | 'u' => 'A'
'R' | 'r' => 'Y'
'Y' | 'y' => 'R'
'S' | 's' => 'S'
'W' | 'w' => 'W'
'K' | 'k' => 'M'
'M' | 'm' => 'K'
'B' | 'b' => 'V'
'V' | 'v' => 'B'
'D' | 'd' => 'H'
'H' | 'h' => 'D'
'N' | 'n' => 'N'
_ => 'N'
}
}
///|
fn codon_to_amino(codon : String) -> String {
match codon {
"TTT" | "TTC" => "F"
"TTA" | "TTG" | "CTT" | "CTC" | "CTA" | "CTG" => "L"
"ATT" | "ATC" | "ATA" => "I"
"ATG" => "M"
"GTT" | "GTC" | "GTA" | "GTG" => "V"
"TCT" | "TCC" | "TCA" | "TCG" | "AGT" | "AGC" => "S"
"CCT" | "CCC" | "CCA" | "CCG" => "P"
"ACT" | "ACC" | "ACA" | "ACG" => "T"
"GCT" | "GCC" | "GCA" | "GCG" => "A"
"TAT" | "TAC" => "Y"
"TAA" | "TAG" | "TGA" => "*"
"CAT" | "CAC" => "H"
"CAA" | "CAG" => "Q"
"AAT" | "AAC" => "N"
"AAA" | "AAG" => "K"
"GAT" | "GAC" => "D"
"GAA" | "GAG" => "E"
"TGT" | "TGC" => "C"
"TGG" => "W"
"CGT" | "CGC" | "CGA" | "CGG" | "AGA" | "AGG" => "R"
"GGT" | "GGC" | "GGA" | "GGG" => "G"
_ => "X"
}
}