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:
+25
-18
@@ -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) {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<_>>());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user