feat: seeded transcription-error injection + Recall@k

This commit is contained in:
2026-05-18 18:26:43 +02:00
parent 079ecea2a3
commit 377fc84875
2 changed files with 56 additions and 1 deletions
+39 -1
View File
@@ -1 +1,39 @@
// implemented in a later task
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
/// Inject realistic transcription errors deterministically (seeded):
/// char drop, adjacent swap, umlaut flattening, digit transposition.
pub fn inject_errors(text: &str, rate: f64, seed: u64) -> String {
let mut rng = StdRng::seed_from_u64(seed);
let mut chars: Vec<char> = text.chars().collect();
let n = chars.len();
let mut i = 0;
while i < chars.len() {
if rng.gen::<f64>() < rate {
match rng.gen_range(0..4) {
0 => { chars.remove(i); continue; } // drop
1 => { if i + 1 < chars.len() { chars.swap(i, i + 1); } } // swap
2 => { // flatten umlaut
chars[i] = match chars[i] {
'ä' => 'a', 'ö' => 'o', 'ü' => 'u', 'ß' => 's',
other => other,
};
}
_ => { // digit transpose
if chars[i].is_ascii_digit() && i + 1 < chars.len()
&& chars[i + 1].is_ascii_digit() {
chars.swap(i, i + 1);
}
}
}
}
i += 1;
}
let _ = n;
chars.into_iter().collect()
}
/// 1.0 if `expected` appears in the first k predictions, else 0.0.
pub fn recall_at_k(predicted: &[String], expected: &str, k: usize) -> f64 {
if predicted.iter().take(k).any(|p| p == expected) { 1.0 } else { 0.0 }
}
+17
View File
@@ -0,0 +1,17 @@
use alpha_id::eval::{inject_errors, recall_at_k};
#[test]
fn injection_is_deterministic_for_seed() {
let a = inject_errors("Diabetes mellitus Typ 2", 0.3, 42);
let b = inject_errors("Diabetes mellitus Typ 2", 0.3, 42);
assert_eq!(a, b);
assert_ne!(a, "Diabetes mellitus Typ 2"); // some corruption at rate 0.3
}
#[test]
fn recall_counts_hit_within_k() {
let predicted = vec!["E11.9".to_string(), "I10.90".to_string(), "J45.0".to_string()];
assert_eq!(recall_at_k(&predicted, "I10.90", 5), 1.0);
assert_eq!(recall_at_k(&predicted, "I10.90", 1), 0.0);
assert_eq!(recall_at_k(&predicted, "Z99.9", 5), 0.0);
}