///|
/// A review label assigned to an alignment pair.
pub enum ReviewLabel {
Accept
NeedsReview
Reject
} derive(Debug, Eq, ToJson)
///|
/// A pair plus the evidence used to make a review decision.
pub struct ReviewItem {
document_id : String
pair_index : Int
label : ReviewLabel
confidence : Double
reason : String
anchors : Int
} derive(Debug, ToJson)
///|
/// An ordered human-review queue.
pub struct ReviewQueue {
items : Array[ReviewItem]
accepted : Int
needs_review : Int
rejected : Int
} derive(Debug, ToJson)
///|
fn review_label(confidence : Double, pair : AlignmentPair) -> ReviewLabel {
if pair.source_end <= pair.source_start ||
pair.target_end <= pair.target_start {
Reject
} else if confidence >= 0.72 && pair.score < 2.0 {
Accept
} else {
NeedsReview
}
}
///|
fn review_reason(
label : ReviewLabel,
pair : AlignmentPair,
anchor_count : Int,
) -> String {
match label {
Accept =>
if anchor_count > 0 {
"stable length and shared anchor"
} else {
"stable length ratio"
}
NeedsReview =>
if pair.move_kind != "1-1" {
"merged sentence boundary"
} else {
"low confidence score"
}
Reject => "empty or invalid alignment span"
}
}
///|
/// Build an ordered review queue from one document result.
pub fn make_review_queue(result : CorpusResult) -> ReviewQueue {
let items = []
let mut accepted = 0
let mut needs_review = 0
let mut rejected = 0
for index, pair in result.report.pairs {
let confidence = 1.0 / (1.0 + pair.score)
let anchors = 0
let label = review_label(confidence, pair)
match label {
Accept => accepted += 1
NeedsReview => needs_review += 1
Reject => rejected += 1
}
items.push({
document_id: result.id,
pair_index: index,
label,
confidence,
reason: review_reason(label, pair, anchors),
anchors,
})
}
{ items, accepted, needs_review, rejected }
}
///|
/// Build queues for a complete batch.
pub fn make_review_queues(results : Array[CorpusResult]) -> Array[ReviewQueue] {
let queues = []
for result in results {
queues.push(make_review_queue(result))
}
queues
}
///|
/// Return only items which should be shown to a human.
pub fn pending_review_items(queue : ReviewQueue) -> Array[ReviewItem] {
let pending = []
for item in queue.items {
if item.label == NeedsReview {
pending.push(item)
}
}
pending
}
///|
/// Render a review queue as TSV for spreadsheet workflows.
pub fn review_queue_to_tsv(queue : ReviewQueue) -> String {
let out = StringBuilder()
out.write_string("document_id\tpair_index\tlabel\tconfidence\treason\n")
for item in queue.items {
let label = match item.label {
Accept => "accept"
NeedsReview => "needs_review"
Reject => "reject"
}
out.write_string(
"\{item.document_id}\t\{item.pair_index}\t\{label}\t\{item.confidence}\t\{item.reason}\n",
)
}
out.to_string()
}
///|
/// Count review labels across a batch.
pub fn review_label_counts(queues : Array[ReviewQueue]) -> Map[String, Int] {
let counts : Map[String, Int] = Map([])
for queue in queues {
counts["accept"] = counts.get_or_default("accept", 0) + queue.accepted
counts["needs_review"] = counts.get_or_default("needs_review", 0) +
queue.needs_review
counts["reject"] = counts.get_or_default("reject", 0) + queue.rejected
}
counts
}
///|
/// Return a stable summary sentence for a review dashboard.
pub fn review_summary(queue : ReviewQueue) -> String {
"accepted=\{queue.accepted};needs_review=\{queue.needs_review};rejected=\{queue.rejected}"
}