///|
/// Quality summary for a collection of parsed GenBank records.
pub struct BatchQualityReport {
record_count : Int
valid_count : Int
invalid_count : Int
total_bases : Int
mean_length : Double
mean_gc : Double
duplicate_accessions : Array[String]
records_with_remote_locations : Int
records_with_fuzzy_locations : Int
records_with_translation_errors : Int
} derive(Debug, Eq)
///|
/// Build a batch report without mutating or reparsing records.
pub fn batch_quality(records : Array[Record]) -> BatchQualityReport {
let accessions : Array[String] = []
let duplicates : Array[String] = []
let mut total_bases = 0
let mut total_gc = 0.0
let mut valid = 0
let mut remote_records = 0
let mut fuzzy_records = 0
let mut translation_errors = 0
for record in records {
if accessions.contains(record.accession) && record.accession != "" {
if !duplicates.contains(record.accession) {
duplicates.push(record.accession)
}
} else {
accessions.push(record.accession)
}
let stats = record.sequence_stats()
total_bases += stats.length
total_gc += stats.gc_percent * stats.length.to_double()
if record_is_valid(record) {
valid += 1
}
if record_remote_accession_count(record) > 0 {
remote_records += 1
}
if record_fuzzy_location_count(record) > 0 {
fuzzy_records += 1
}
translation_errors += record
.check_cds_translations()
.fold(init=0, fn(total, item) { total + (if item.ok { 0 } else { 1 }) })
}
{
record_count: records.length(),
valid_count: valid,
invalid_count: records.length() - valid,
total_bases,
mean_length: if records.length() == 0 {
0.0
} else {
total_bases.to_double() / records.length().to_double()
},
mean_gc: if total_bases == 0 {
0.0
} else {
total_gc / total_bases.to_double()
},
duplicate_accessions: duplicates,
records_with_remote_locations: remote_records,
records_with_fuzzy_locations: fuzzy_records,
records_with_translation_errors: translation_errors,
}
}
///|
/// Parse and assess a multi-record stream in one operation.
pub fn batch_quality_from_text(
text : String,
) -> BatchQualityReport raise GenBankError {
batch_quality(parse_records(text))
}
///|
/// Return records that pass structural validation and CDS translation checks.
pub fn clean_records(records : Array[Record]) -> Array[Record] {
records.filter(fn(record) {
record_is_valid(record) &&
record.check_cds_translations().all(fn(item) { item.ok })
})
}
///|
/// Return a stable report line useful for a CLI summary.
pub fn BatchQualityReport::to_text(self : BatchQualityReport) -> String {
"records=" +
self.record_count.to_string() +
" valid=" +
self.valid_count.to_string() +
" invalid=" +
self.invalid_count.to_string() +
" bases=" +
self.total_bases.to_string() +
" mean_length=" +
self.mean_length.to_string() +
" mean_gc=" +
self.mean_gc.to_string() +
" duplicate_accessions=" +
self.duplicate_accessions.length().to_string() +
" remote_records=" +
self.records_with_remote_locations.to_string() +
" fuzzy_records=" +
self.records_with_fuzzy_locations.to_string() +
" translation_errors=" +
self.records_with_translation_errors.to_string()
}
///|
pub fn BatchQualityReport::to_json_string(self : BatchQualityReport) -> String {
"{\"record_count\":" +
self.record_count.to_string() +
",\"valid_count\":" +
self.valid_count.to_string() +
",\"invalid_count\":" +
self.invalid_count.to_string() +
",\"total_bases\":" +
self.total_bases.to_string() +
",\"mean_length\":" +
self.mean_length.to_string() +
",\"mean_gc\":" +
self.mean_gc.to_string() +
",\"duplicate_accessions\":[" +
self.duplicate_accessions.map(json_string).join(",") +
"],\"records_with_remote_locations\":" +
self.records_with_remote_locations.to_string() +
",\"records_with_fuzzy_locations\":" +
self.records_with_fuzzy_locations.to_string() +
",\"records_with_translation_errors\":" +
self.records_with_translation_errors.to_string() +
"}"
}