///|
/// Aggregate result for CDS translation validation in a single record.
pub struct TranslationAuditSummary {
accession : String
cds_count : Int
checked_count : Int
passed_count : Int
failed_count : Int
missing_translation_count : Int
total_nucleotide_bases : Int
} derive(Debug, Eq)
///|
/// Summarize every CDS, including entries that do not provide `/translation`.
pub fn translation_audit(record : Record) -> TranslationAuditSummary {
let checks = record.check_cds_translations()
let cds = record.features.filter(fn(feature) { feature.key == "CDS" })
let checked = checks.length()
let passed = checks.fold(init=0, fn(total, item) {
total + (if item.ok { 1 } else { 0 })
})
let bases = cds.fold(init=0, fn(total, feature) {
total + record.sequence_for(feature).length()
})
{
accession: record.accession,
cds_count: cds.length(),
checked_count: checked,
passed_count: passed,
failed_count: checked - passed,
missing_translation_count: cds.length() - checked,
total_nucleotide_bases: bases,
}
}
///|
/// Return false when any supplied translation disagrees with extracted DNA.
pub fn translations_are_consistent(record : Record) -> Bool {
translation_audit(record).failed_count == 0
}
///|
/// Return the checked CDS findings as stable JSON objects.
pub fn translation_checks_json(record : Record) -> String {
"[" +
record
.check_cds_translations()
.map(fn(item) {
"{\"feature_index\":" +
item.feature_index.to_string() +
",\"location\":" +
json_string(item.location) +
",\"expected\":" +
json_string(item.expected) +
",\"actual\":" +
json_string(item.actual) +
",\"ok\":" +
item.ok.to_string() +
"}"
})
.join(",") +
"]"
}
///|
/// Render one row per CDS for human review and spreadsheet import.
pub fn TranslationAuditSummary::to_tsv(
self : TranslationAuditSummary,
) -> String {
"accession\tcds_count\tchecked\tpassed\tfailed\tmissing\tnucleotide_bases\n" +
self.accession +
"\t" +
self.cds_count.to_string() +
"\t" +
self.checked_count.to_string() +
"\t" +
self.passed_count.to_string() +
"\t" +
self.failed_count.to_string() +
"\t" +
self.missing_translation_count.to_string() +
"\t" +
self.total_nucleotide_bases.to_string() +
"\n"
}
///|
pub fn TranslationAuditSummary::to_json_string(
self : TranslationAuditSummary,
) -> String {
"{\"accession\":" +
json_string(self.accession) +
",\"cds_count\":" +
self.cds_count.to_string() +
",\"checked_count\":" +
self.checked_count.to_string() +
",\"passed_count\":" +
self.passed_count.to_string() +
",\"failed_count\":" +
self.failed_count.to_string() +
",\"missing_translation_count\":" +
self.missing_translation_count.to_string() +
",\"total_nucleotide_bases\":" +
self.total_nucleotide_bases.to_string() +
"}"
}
///|
/// Compute the proportion of supplied translations that passed validation.
pub fn translation_pass_rate(record : Record) -> Double {
let summary = translation_audit(record)
if summary.checked_count == 0 {
100.0
} else {
summary.passed_count.to_double() * 100.0 / summary.checked_count.to_double()
}
}
///|
/// Summarize translation checks across a multi-record batch.
pub fn batch_translation_audits(
records : Array[Record],
) -> Array[TranslationAuditSummary] {
records.map(translation_audit)
}
///|
/// Count all CDS features with a supplied translation in a batch.
pub fn batch_checked_translation_count(records : Array[Record]) -> Int {
batch_translation_audits(records).fold(init=0, fn(total, item) {
total + item.checked_count
})
}
///|
/// Count all failed CDS translation checks in a batch.
pub fn batch_failed_translation_count(records : Array[Record]) -> Int {
batch_translation_audits(records).fold(init=0, fn(total, item) {
total + item.failed_count
})
}
///|
/// Render only failed translation checks, making review output concise.
pub fn failed_translation_checks_tsv(record : Record) -> String {
let out = StringBuilder()
out.write_string("feature_index\tlocation\texpected\tactual\n")
for check in record.check_cds_translations() {
if !check.ok {
out.write_string(check.feature_index.to_string())
out.write_char('\t')
out.write_string(check.location)
out.write_char('\t')
out.write_string(check.expected)
out.write_char('\t')
out.write_string(check.actual)
out.write_char('\n')
}
}
out.to_string()
}
///|
/// Return a compact batch status string for monitoring jobs.
pub fn batch_translation_status(records : Array[Record]) -> String {
let checked = batch_checked_translation_count(records)
let failed = batch_failed_translation_count(records)
let status = if failed == 0 { "ok" } else { "failed" }
"status=" +
status +
" records=" +
records.length().to_string() +
" checked=" +
checked.to_string() +
" failed=" +
failed.to_string()
}
///|
/// Select records whose CDS annotations are fully self-consistent.
pub fn records_with_consistent_translations(
records : Array[Record],
) -> Array[Record] {
records.filter(translations_are_consistent)
}
///|
/// Return the total amount of nucleotide material represented by CDS checks.
pub fn batch_coding_bases(records : Array[Record]) -> Int {
batch_translation_audits(records).fold(init=0, fn(total, item) {
total + item.total_nucleotide_bases
})
}