fix: drop 3-char-root meta fallback; rely on expanded ClaML map (Goal 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 18:15:21 +02:00
parent b53c4bfb71
commit 5305fb251e
2 changed files with 36 additions and 19 deletions
+26 -19
View File
@@ -23,32 +23,39 @@ impl Pipeline {
Ok(Self { entries, meta, lexical, pool_size: cfg.pool_size })
}
/// Look up ICD metadata with fallback: exact code → strip trailing '0' after
/// the decimal (e.g. "E11.90" → "E11.9") → 3-char root (e.g. "E11").
/// The corpus uses 5- and 6-char codes; ClaML uses 3-, 5-, and 6-char codes.
/// Look up ICD metadata: exact code first, then — only for 6-char dotted codes
/// of the form `Lxx.yz` — the 5-char parent `Lxx.y`.
///
/// The ClaML map (populated by `claml::load`) now contains synthesized
/// 5th-digit entries (e.g. `E11.90`, `I10.91`) with correct `Para295 = "P"`,
/// so the exact hit succeeds for the vast majority of corpus codes. The
/// 5-char-parent fallback covers any residual gap where a 6-char code is not
/// explicitly present but its 5-char parent is.
///
/// No 3-char-root fallback is applied: that level caused ~1 193 codes to
/// inherit the parent's `Para295 = "V"` and be wrongly dropped under
/// `--billable-only` (Goal 1 regression). Codes absent from ClaML entirely
/// yield `None` (accepted, documented out-of-scope gap).
fn meta_lookup<'a>(&'a self, code: &str) -> Option<&'a IcdMeta> {
if let Some(m) = self.meta.get(code) {
return Some(m);
}
// Try stripping a trailing '0' from the last position after the dot
// e.g. "E11.90" → "E11.9", "I10.90" → "I10.9", "G40.00" → "G40.0"
if code.len() >= 5 {
if let Some(dot_pos) = code.find('.') {
let after_dot = &code[dot_pos + 1..];
if after_dot.ends_with('0') && after_dot.len() > 1 {
let trimmed = &code[..code.len() - 1];
if let Some(m) = self.meta.get(trimmed) {
return Some(m);
}
// 5-char-parent fallback only for 6-char dotted codes: Lxx.yz → Lxx.y
// Pattern: one letter, two digits, dot, two digits (total 6 chars).
if code.len() == 6 {
let bytes = code.as_bytes();
if bytes[0].is_ascii_alphabetic()
&& bytes[1].is_ascii_digit()
&& bytes[2].is_ascii_digit()
&& bytes[3] == b'.'
&& bytes[4].is_ascii_alphanumeric()
&& bytes[5].is_ascii_alphanumeric()
{
if let Some(m) = self.meta.get(&code[..5]) {
return Some(m);
}
}
}
// Fallback to 3-char root (e.g. "E11")
if code.len() > 3 {
if let Some(m) = self.meta.get(&code[..3]) {
return Some(m);
}
}
None
}
+10
View File
@@ -25,3 +25,13 @@ fn billable_only_filter_excludes_three_digit_v_codes() {
let res = p.suggest("Cholera", Mode::C, &f, 10);
assert!(res.suggestions.iter().all(|s| s.tags.billable && s.tags.valid));
}
#[test]
fn billable_only_keeps_diabetes_5th_digit_codes() {
let p = Pipeline::load(&cfg()).unwrap();
let f = Filter { billable_only: true, valid_only: true, chapters: None, exclude_exotic: false };
let res = p.suggest("Diabetes mellitus Typ 2", Mode::C, &f, 10);
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11") && s.tags.billable),
"billable E11.x must survive --billable-only; got {:?}",
res.suggestions.iter().map(|s| (&s.icd_code, s.tags.billable)).collect::<Vec<_>>());
}