///|
/// Summary statistics for a batch of GenBank records.
pub struct BatchSummary {
record_count : Int
total_bases : Int
total_features : Int
total_cds : Int
total_genes : Int
valid_records : Int
invalid_records : Int
accessions : Array[String]
} derive(Debug, Eq)
///|
/// A reusable collection wrapper for multi-record workflows.
pub struct RecordCollection {
records : Array[Record]
source_name : String
} derive(Debug, Eq)
///|
pub fn parse_record_collection(
text : String,
source_name? : String = "input",
) -> RecordCollection raise GenBankError {
{ records: parse_records(text), source_name }
}
///|
pub fn collection_from_records(
records : Array[Record],
source_name? : String = "memory",
) -> RecordCollection {
{ records, source_name }
}
///|
pub fn RecordCollection::count(self : RecordCollection) -> Int {
self.records.length()
}
///|
pub fn RecordCollection::records(self : RecordCollection) -> Array[Record] {
self.records
}
///|
pub fn RecordCollection::source_name(self : RecordCollection) -> String {
self.source_name
}
///|
pub fn RecordCollection::at(self : RecordCollection, index : Int) -> Record? {
self.records.get(index)
}
///|
pub fn RecordCollection::accessions(self : RecordCollection) -> Array[String] {
self.records.map(fn(record) { record.accession })
}
///|
pub fn RecordCollection::definitions(self : RecordCollection) -> Array[String] {
self.records.map(fn(record) { record.definition })
}
///|
pub fn RecordCollection::total_bases(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) { total + record.origin.length() })
}
///|
pub fn RecordCollection::total_features(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + record.features.length()
})
}
///|
pub fn RecordCollection::total_cds(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + record_cds_count(record)
})
}
///|
pub fn RecordCollection::total_genes(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + record_gene_count(record)
})
}
///|
pub fn RecordCollection::valid_count(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + (if record_is_valid(record) { 1 } else { 0 })
})
}
///|
pub fn RecordCollection::invalid_count(self : RecordCollection) -> Int {
self.count() - self.valid_count()
}
///|
pub fn RecordCollection::summary(self : RecordCollection) -> BatchSummary {
{
record_count: self.count(),
total_bases: self.total_bases(),
total_features: self.total_features(),
total_cds: self.total_cds(),
total_genes: self.total_genes(),
valid_records: self.valid_count(),
invalid_records: self.invalid_count(),
accessions: self.accessions(),
}
}
///|
pub fn RecordCollection::filter_accession(
self : RecordCollection,
prefix : String,
) -> RecordCollection {
{
records: self.records.filter(fn(record) {
record.accession.has_prefix(prefix)
}),
source_name: self.source_name,
}
}
///|
pub fn RecordCollection::filter_definition(
self : RecordCollection,
text : String,
) -> RecordCollection {
let needle = text.to_lower()
{
records: self.records.filter(fn(record) {
record.definition.to_lower().contains(needle)
}),
source_name: self.source_name,
}
}
///|
pub fn RecordCollection::filter_valid(
self : RecordCollection,
) -> RecordCollection {
{
records: self.records.filter(record_is_valid),
source_name: self.source_name,
}
}
///|
pub fn RecordCollection::map_normalized(
self : RecordCollection,
) -> RecordCollection {
{ records: self.records.map(normalize_record), source_name: self.source_name }
}
///|
pub fn RecordCollection::all_feature_keys(
self : RecordCollection,
) -> Array[String] {
let result : Array[String] = []
for record in self.records {
for key in build_feature_index(record).keys() {
if !result.contains(key) {
result.push(key)
}
}
}
result
}
///|
pub fn RecordCollection::records_with_feature(
self : RecordCollection,
key : String,
) -> RecordCollection {
{
records: self.records.filter(fn(record) {
record.features_by_key(key).length() > 0
}),
source_name: self.source_name,
}
}
///|
pub fn RecordCollection::sequence_signatures(
self : RecordCollection,
) -> Array[String] {
self.records.map(fn(record) { sequence_signature(record.origin) })
}
///|
pub fn RecordCollection::reports(self : RecordCollection) -> Array[String] {
self.records.map(record_report)
}
///|
pub fn RecordCollection::validation_reports(
self : RecordCollection,
) -> Array[ValidationReport] {
self.records.map(build_validation_report)
}
///|
pub fn RecordCollection::to_genbank_string(self : RecordCollection) -> String {
self.records.map(fn(record) { record.to_genbank_string() }).join("")
}
///|
pub fn RecordCollection::to_fasta_string(self : RecordCollection) -> String {
let out = StringBuilder()
for record in self.records {
let id = if record.accession == "" {
record.locus.name
} else {
record.accession
}
out.write_string(">" + id + " " + record.definition + "\n")
out.write_string(format_sequence_lines(record.origin, 60))
}
out.to_string()
}
///|
pub fn BatchSummary::to_json_string(self : BatchSummary) -> String {
"{\"record_count\":" +
self.record_count.to_string() +
",\"total_bases\":" +
self.total_bases.to_string() +
",\"total_features\":" +
self.total_features.to_string() +
",\"total_cds\":" +
self.total_cds.to_string() +
",\"total_genes\":" +
self.total_genes.to_string() +
",\"valid_records\":" +
self.valid_records.to_string() +
",\"invalid_records\":" +
self.invalid_records.to_string() +
",\"accessions\":" +
array_json(self.accessions.map(fn(item) { json_string(item) })) +
"}"
}
///|
pub fn BatchSummary::to_text(self : BatchSummary) -> String {
"records: " +
self.record_count.to_string() +
"\n" +
"bases: " +
self.total_bases.to_string() +
"\n" +
"features: " +
self.total_features.to_string() +
"\n" +
"CDS: " +
self.total_cds.to_string() +
"\n" +
"genes: " +
self.total_genes.to_string() +
"\n" +
"valid: " +
self.valid_records.to_string() +
"\n" +
"invalid: " +
self.invalid_records.to_string() +
"\n"
}
///|
pub fn summarize_records(text : String) -> BatchSummary raise GenBankError {
parse_record_collection(text).summary()
}
///|
pub fn collection_json(text : String) -> String raise GenBankError {
summarize_records(text).to_json_string()
}
///|
pub fn collection_fasta(text : String) -> String raise GenBankError {
parse_record_collection(text).to_fasta_string()
}
///|
fn format_sequence_lines(sequence : String, width : Int) -> String {
let out = StringBuilder()
let normalized = normalize_sequence(sequence)
let safe_width = Int::max(1, width)
let mut start = 0
while start < normalized.length() {
let end = Int::min(start + safe_width, normalized.length())
out.write_string(normalized.unsafe_substring(start~, end~))
out.write_char('\n')
start = end
}
out.to_string()
}
///|
pub fn RecordCollection::gc(self : RecordCollection) -> Double {
if self.total_bases() == 0 {
0.0
} else {
self.records.fold(init=0.0, fn(total, record) {
total +
record.sequence_stats().gc_percent * record.origin.length().to_double()
}) /
self.total_bases().to_double()
}
}
///|
pub fn RecordCollection::feature_density(self : RecordCollection) -> Double {
if self.total_bases() == 0 {
0.0
} else {
self.total_features().to_double() / self.total_bases().to_double()
}
}
///|
pub fn RecordCollection::remote_locations(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + record_remote_accession_count(record)
})
}
///|
pub fn RecordCollection::fuzzy_locations(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + record_fuzzy_location_count(record)
})
}
///|
pub fn RecordCollection::between_locations(self : RecordCollection) -> Int {
self.records.fold(init=0, fn(total, record) {
total + record_between_location_count(record)
})
}