///|
/// A compact, deterministic annotation summary for dashboards and reports.
pub struct AnnotationSummary {
feature_count : Int
cds_count : Int
gene_count : Int
source_count : Int
base_count : Int
gc_percent : Double
feature_keys : Array[String]
qualifier_names : Array[String]
} derive(Debug, Eq)
///|
pub fn location_span(location : Location) -> Int {
let mut maximum = 0
for range in location.ranges {
if range.end > maximum {
maximum = range.end
}
}
maximum
}
///|
pub fn location_minimum(location : Location) -> Int {
let mut minimum = 2147483647
for range in location.ranges {
if range.start < minimum {
minimum = range.start
}
}
if minimum == 2147483647 {
0
} else {
minimum
}
}
///|
pub fn location_length(location : Location) -> Int {
location.ranges.fold(init=0, fn(total, range) {
total + (if range.between { 0 } else { range.end - range.start + 1 })
})
}
///|
pub fn location_contains(location : Location, position : Int) -> Bool {
location.ranges.any(fn(range) {
range.start <= position && position <= range.end
})
}
///|
/// Emit each coordinate in biological order, reversing complemented segments.
pub fn location_coordinates(location : Location) -> Array[Int] {
let coordinates : Array[Int] = []
for segment in location.segments {
if segment.reverse {
let mut coordinate = segment.range.end
while coordinate >= segment.range.start {
coordinates.push(coordinate)
coordinate -= 1
}
} else {
for coordinate in segment.range.start..<=segment.range.end {
coordinates.push(coordinate)
}
}
}
coordinates
}
///|
pub fn location_is_fuzzy(location : Location) -> Bool {
location.ranges.any(fn(range) { range.partial_start || range.partial_end })
}
///|
pub fn location_is_remote(location : Location) -> Bool {
location.segments.any(fn(segment) { segment.remote is Some(_) })
}
///|
pub fn render_location(location : Location) -> String {
location.raw
}
///|
pub fn feature_overlaps_any(
features : Array[Feature],
start : Int,
end : Int,
) -> Array[Feature] {
features.filter(fn(feature) { feature.overlaps(start, end) })
}
///|
pub fn feature_qualifier_values(
features : Array[Feature],
qualifier_name : String,
) -> Array[String] {
let values : Array[String] = []
for feature in features {
match feature.qualifier(qualifier_name) {
Some(value) => values.push(value)
None => ()
}
}
values
}
///|
pub fn annotation_summary(record : Record) -> AnnotationSummary {
let keys : Array[String] = []
let qualifier_names : Array[String] = []
let mut cds_count = 0
let mut gene_count = 0
let mut source_count = 0
for feature in record.features {
keys.push(feature.key)
if feature.key == "CDS" {
cds_count += 1
}
if feature.key == "gene" {
gene_count += 1
}
if feature.key == "source" {
source_count += 1
}
for qualifier in feature.qualifiers {
if !qualifier_names.contains(qualifier.name) {
qualifier_names.push(qualifier.name)
}
}
}
let stats = record.sequence_stats()
{
feature_count: record.features.length(),
cds_count,
gene_count,
source_count,
base_count: stats.length,
gc_percent: stats.gc_percent,
feature_keys: keys,
qualifier_names,
}
}
///|
pub fn AnnotationSummary::to_json_string(self : AnnotationSummary) -> String {
"{\"feature_count\":" +
self.feature_count.to_string() +
",\"cds_count\":" +
self.cds_count.to_string() +
",\"gene_count\":" +
self.gene_count.to_string() +
",\"source_count\":" +
self.source_count.to_string() +
",\"base_count\":" +
self.base_count.to_string() +
",\"gc_percent\":" +
self.gc_percent.to_string() +
",\"feature_keys\":" +
array_json(self.feature_keys.map(fn(item) { json_string(item) })) +
",\"qualifier_names\":" +
array_json(self.qualifier_names.map(fn(item) { json_string(item) })) +
"}"
}