refactor: rename Mode variants C/A to Lexical/Hybrid (closes #2)
The retrieval-mode enum used opaque single letters `Mode { C, A }`;
neither the identifier nor the operator-facing `--mode a` token
conveyed what the mode does. Rename to self-describing names that map
directly onto the glossary definitions:
Mode::C -> Mode::Lexical (BM25 + tag filter, offline, no IONOS)
Mode::A -> Mode::Hybrid (lexical u semantic -> RRF -> rerank)
Decisions taken (the issue's open questions, resolved):
- CLI canonical tokens are now `lexical` / `hybrid`; the default is
`lexical`. The legacy `c` / `a` tokens stay accepted as
case-insensitive input aliases (zero cost; protects hand-typed
invocations). A unit test pins the alias contract.
- The diagnostics `mode` string (Debug-derived) now emits
"Lexical"/"Hybrid". Verified safe: no downstream consumer parses it.
doctate is the future integrator and embeds the library (the `Mode`
enum), not the JSON string (design spec §103).
- The reserved, out-of-scope generative tier (spec's "Mode B") keeps
conceptual room: a future `Mode::Generative` does not collide with
the retrieval-strategy names Lexical/Hybrid.
Surface: enum + FromStr, CLI defaults and `eval --mode both`
expansion, the private `collect_mode_c`/`collect_mode_a` methods (now
`collect_lexical`/`collect_hybrid`) and their comments, the two
pipeline mode test files (renamed), the eval CLI test, the glossary
(old letter forms demoted to Avoid lists), and the spec CLI synopsis.
closes #2
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+10
-10
@@ -20,13 +20,13 @@ The BfArM Alpha-ID dataset mapping free-text diagnosis phrases to ICD-10-GM code
|
|||||||
**Avoid:** segment, unit, Einheit, Formulierung
|
**Avoid:** segment, unit, Einheit, Formulierung
|
||||||
One caller-extracted diagnostic phrase (one input line) — the unit that is matched and ranked. In source code this concept is carried by the identifier `segment` (e.g. `split_segments`, `source_segments`), which is retained as a code term only.
|
One caller-extracted diagnostic phrase (one input line) — the unit that is matched and ranked. In source code this concept is carried by the identifier `segment` (e.g. `split_segments`, `source_segments`), which is retained as a code term only.
|
||||||
|
|
||||||
### Mode C
|
### Mode Lexical
|
||||||
**Avoid:** Modus C
|
**Avoid:** Mode C, Modus C, mode c
|
||||||
The lexical-baseline retrieval mode: BM25 over the corpus plus tag filtering, with no IONOS calls. Also the degrade target for Mode A.
|
The lexical-baseline retrieval mode: BM25 over the corpus plus tag filtering, with no IONOS calls. Also the degrade target for Mode Hybrid.
|
||||||
|
|
||||||
### Mode A
|
### Mode Hybrid
|
||||||
**Avoid:** Modus A, semantic mode
|
**Avoid:** Mode A, Modus A, mode a, semantic mode
|
||||||
The hybrid retrieval mode: lexical and semantic (IONOS embedding) pools fused via RRF, then cross-encoder reranked. Degrades to Mode C on any IONOS failure.
|
The hybrid retrieval mode: lexical and semantic (IONOS embedding) pools fused via RRF, then cross-encoder reranked. Degrades to Mode Lexical on any IONOS failure.
|
||||||
|
|
||||||
### billable
|
### billable
|
||||||
**Avoid:** abrechenbar, Abrechenbarkeit
|
**Avoid:** abrechenbar, Abrechenbarkeit
|
||||||
@@ -42,11 +42,11 @@ The ClaML process of generating terminal sub-digit codes (e.g. `E11.90`) by appl
|
|||||||
|
|
||||||
### RRF
|
### RRF
|
||||||
**Avoid:** —
|
**Avoid:** —
|
||||||
Reciprocal Rank Fusion: the rank-based method that merges the lexical and semantic candidate lists into one fused pool in Mode A.
|
Reciprocal Rank Fusion: the rank-based method that merges the lexical and semantic candidate lists into one fused pool in Mode Hybrid.
|
||||||
|
|
||||||
### rerank
|
### rerank
|
||||||
**Avoid:** —
|
**Avoid:** —
|
||||||
The Mode A cross-encoder step that rescores the fused candidate pool against the query phrase, using the IONOS reranker model.
|
The Mode Hybrid cross-encoder step that rescores the fused candidate pool against the query phrase, using the IONOS reranker model.
|
||||||
|
|
||||||
### corpus
|
### corpus
|
||||||
**Avoid:** Korpus, match corpus
|
**Avoid:** Korpus, match corpus
|
||||||
@@ -58,11 +58,11 @@ The per-phrase, content-hash-keyed persistent store of corpus embeddings under `
|
|||||||
|
|
||||||
### vector index
|
### vector index
|
||||||
**Avoid:** ANN index
|
**Avoid:** ANN index
|
||||||
The in-RAM matrix of corpus embeddings searched by exact cosine similarity for the semantic pool. Built only when every corpus entry has a cached embedding; otherwise Mode A degrades.
|
The in-RAM matrix of corpus embeddings searched by exact cosine similarity for the semantic pool. Built only when every corpus entry has a cached embedding; otherwise Mode Hybrid degrades.
|
||||||
|
|
||||||
### degrade
|
### degrade
|
||||||
**Avoid:** —
|
**Avoid:** —
|
||||||
Mode A falling back to Mode C when any IONOS step fails (embed, rerank, or a missing vector index); the request is still answered and `degraded` is set in diagnostics. Distinct from the 5-char-parent lookup fallback in `meta_lookup`.
|
Mode Hybrid falling back to Mode Lexical when any IONOS step fails (embed, rerank, or a missing vector index); the request is still answered and `degraded` is set in diagnostics. Distinct from the 5-char-parent lookup fallback in `meta_lookup`.
|
||||||
|
|
||||||
### tag filter
|
### tag filter
|
||||||
**Avoid:** —
|
**Avoid:** —
|
||||||
|
|||||||
@@ -197,9 +197,9 @@ alpha-id index build [--sample N | --full] [--confirm]
|
|||||||
# Korpus+ClaML parsen, bge-m3-Embeddings,
|
# Korpus+ClaML parsen, bge-m3-Embeddings,
|
||||||
# Lexik-+Vektorindex persistieren (resumierbar)
|
# Lexik-+Vektorindex persistieren (resumierbar)
|
||||||
alpha-id suggest [FILE|-] # Formulierungs-Liste (eine pro Zeile) → Top-10
|
alpha-id suggest [FILE|-] # Formulierungs-Liste (eine pro Zeile) → Top-10
|
||||||
--mode c|a --top 10 --billable-only --valid-only
|
--mode lexical|hybrid --top 10 --billable-only --valid-only
|
||||||
--chapters I,J --exclude-exotic --json|--table
|
--chapters I,J --exclude-exotic --json|--table
|
||||||
alpha-id eval --mode c|a --cases N --seed S [--gold PATH]
|
alpha-id eval --mode lexical|hybrid --cases N --seed S [--gold PATH]
|
||||||
```
|
```
|
||||||
|
|
||||||
- Config-TOML: IONOS-Endpoint/Modellnamen/Pool-Größen/Pfad-Defaults, per Flags überschreibbar.
|
- Config-TOML: IONOS-Endpoint/Modellnamen/Pool-Größen/Pfad-Defaults, per Flags überschreibbar.
|
||||||
|
|||||||
+3
-3
@@ -19,7 +19,7 @@ enum Cmd {
|
|||||||
/// Suggest ICD codes for a dictation (FILE or - for stdin)
|
/// Suggest ICD codes for a dictation (FILE or - for stdin)
|
||||||
Suggest {
|
Suggest {
|
||||||
input: String,
|
input: String,
|
||||||
#[arg(long, default_value = "c")] mode: String,
|
#[arg(long, default_value = "lexical")] mode: String,
|
||||||
#[arg(long, default_value_t = 10)] top: usize,
|
#[arg(long, default_value_t = 10)] top: usize,
|
||||||
#[arg(long)] billable_only: bool,
|
#[arg(long)] billable_only: bool,
|
||||||
#[arg(long)] valid_only: bool,
|
#[arg(long)] valid_only: bool,
|
||||||
@@ -29,7 +29,7 @@ enum Cmd {
|
|||||||
},
|
},
|
||||||
/// Run the eval harness
|
/// Run the eval harness
|
||||||
Eval {
|
Eval {
|
||||||
#[arg(long, default_value = "c")] mode: String,
|
#[arg(long, default_value = "lexical")] mode: String,
|
||||||
#[arg(long, default_value_t = 200)] cases: usize,
|
#[arg(long, default_value_t = 200)] cases: usize,
|
||||||
#[arg(long, default_value_t = 42)] seed: u64,
|
#[arg(long, default_value_t = 42)] seed: u64,
|
||||||
},
|
},
|
||||||
@@ -152,7 +152,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
Cmd::Eval { mode, cases, seed } => {
|
Cmd::Eval { mode, cases, seed } => {
|
||||||
let modes: Vec<Mode> = if mode == "both" {
|
let modes: Vec<Mode> = if mode == "both" {
|
||||||
vec![Mode::C, Mode::A]
|
vec![Mode::Lexical, Mode::Hybrid]
|
||||||
} else {
|
} else {
|
||||||
vec![mode.parse().expect("mode")]
|
vec![mode.parse().expect("mode")]
|
||||||
};
|
};
|
||||||
|
|||||||
+3
-3
@@ -78,14 +78,14 @@ impl Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Mode { C, A }
|
pub enum Mode { Lexical, Hybrid }
|
||||||
|
|
||||||
impl FromStr for Mode {
|
impl FromStr for Mode {
|
||||||
type Err = String;
|
type Err = String;
|
||||||
fn from_str(s: &str) -> Result<Self, String> {
|
fn from_str(s: &str) -> Result<Self, String> {
|
||||||
match s.to_ascii_lowercase().as_str() {
|
match s.to_ascii_lowercase().as_str() {
|
||||||
"c" => Ok(Mode::C),
|
"lexical" | "c" => Ok(Mode::Lexical),
|
||||||
"a" => Ok(Mode::A),
|
"hybrid" | "a" => Ok(Mode::Hybrid),
|
||||||
other => Err(format!("unknown mode: {other}")),
|
other => Err(format!("unknown mode: {other}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -38,8 +38,8 @@ impl Pipeline {
|
|||||||
m
|
m
|
||||||
};
|
};
|
||||||
// Build the semantic index only if EVERY corpus entry already has a
|
// Build the semantic index only if EVERY corpus entry already has a
|
||||||
// cached embedding; otherwise leave it `None` so Mode A degrades to C
|
// cached embedding; otherwise leave it `None` so Mode Hybrid degrades to
|
||||||
// rather than mispairing partial vectors.
|
// Lexical rather than mispairing partial vectors.
|
||||||
let vector = match EmbeddingStore::open(&cfg.index_dir, &cfg.embed_model) {
|
let vector = match EmbeddingStore::open(&cfg.index_dir, &cfg.embed_model) {
|
||||||
Ok(store) if entries.iter().all(|e| store.has(&e.text)) => {
|
Ok(store) if entries.iter().all(|e| store.has(&e.text)) => {
|
||||||
let vecs: Option<Vec<Vec<f32>>> =
|
let vecs: Option<Vec<Vec<f32>>> =
|
||||||
@@ -108,9 +108,9 @@ impl Pipeline {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mode C candidate collection: lexical pool per segment. This is the
|
/// Mode Lexical candidate collection: lexical pool per segment. This is the
|
||||||
/// exact original behavior; it is also the degrade fallback for Mode A.
|
/// exact original behavior; it is also the degrade fallback for Mode Hybrid.
|
||||||
fn collect_mode_c(&self, segments: &[String]) -> Vec<Candidate> {
|
fn collect_lexical(&self, segments: &[String]) -> Vec<Candidate> {
|
||||||
let mut candidates: Vec<Candidate> = Vec::new();
|
let mut candidates: Vec<Candidate> = Vec::new();
|
||||||
for (si, seg) in segments.iter().enumerate() {
|
for (si, seg) in segments.iter().enumerate() {
|
||||||
let lex = self.lexical.search(seg, self.pool_size).unwrap_or_default();
|
let lex = self.lexical.search(seg, self.pool_size).unwrap_or_default();
|
||||||
@@ -131,11 +131,11 @@ impl Pipeline {
|
|||||||
candidates
|
candidates
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mode A candidate collection: per segment, fuse a lexical pool with a
|
/// Mode Hybrid candidate collection: per segment, fuse a lexical pool with a
|
||||||
/// semantic pool (IONOS embedding + in-RAM vector search) via RRF, then
|
/// semantic pool (IONOS embedding + in-RAM vector search) via RRF, then
|
||||||
/// cross-encoder rerank the fused pool. ANY error (IONOS unreachable, no
|
/// cross-encoder rerank the fused pool. ANY error (IONOS unreachable, no
|
||||||
/// vector index, embed/rerank failure) propagates so `suggest` degrades.
|
/// vector index, embed/rerank failure) propagates so `suggest` degrades.
|
||||||
fn collect_mode_a(&self, segments: &[String], calls: &mut usize)
|
fn collect_hybrid(&self, segments: &[String], calls: &mut usize)
|
||||||
-> Result<Vec<Candidate>, AppError> {
|
-> Result<Vec<Candidate>, AppError> {
|
||||||
let client = IonosClient::new(&self.cfg.ionos_base_url, &self.cfg.token_path)?;
|
let client = IonosClient::new(&self.cfg.ionos_base_url, &self.cfg.token_path)?;
|
||||||
let vector = self.vector.as_ref()
|
let vector = self.vector.as_ref()
|
||||||
@@ -199,14 +199,14 @@ impl Pipeline {
|
|||||||
let mut degraded = false;
|
let mut degraded = false;
|
||||||
|
|
||||||
let candidates: Vec<Candidate> = match mode {
|
let candidates: Vec<Candidate> = match mode {
|
||||||
Mode::C => self.collect_mode_c(&segments),
|
Mode::Lexical => self.collect_lexical(&segments),
|
||||||
Mode::A => match self.collect_mode_a(&segments, &mut ionos_calls) {
|
Mode::Hybrid => match self.collect_hybrid(&segments, &mut ionos_calls) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Total degradation: any failure → Mode C fallback,
|
// Total degradation: any failure → Mode Lexical fallback,
|
||||||
// request still answered, never panics.
|
// request still answered, never panics.
|
||||||
degraded = true;
|
degraded = true;
|
||||||
self.collect_mode_c(&segments)
|
self.collect_lexical(&segments)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,3 +12,21 @@ fn cli_suggest_outputs_json_with_codes() {
|
|||||||
assert!(s.contains("\"icd_code\""));
|
assert!(s.contains("\"icd_code\""));
|
||||||
assert!(s.contains("E11"));
|
assert!(s.contains("E11"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The renamed canonical CLI token `--mode lexical` is accepted and the
|
||||||
|
/// diagnostics line reports the new variant name `Lexical` (not the legacy `C`),
|
||||||
|
/// confirming the enum rename reaches observable CLI output.
|
||||||
|
#[test]
|
||||||
|
fn cli_suggest_accepts_canonical_lexical_token() {
|
||||||
|
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
|
||||||
|
.args(["suggest", "--mode", "lexical", "-"])
|
||||||
|
.arg("--config").arg("config/default.toml")
|
||||||
|
.env("ALPHA_ID_STDIN", "Diabetes mellitus Typ 2")
|
||||||
|
.output().unwrap();
|
||||||
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
|
assert!(stdout.contains("E11"));
|
||||||
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
|
assert!(stderr.contains("mode Lexical"),
|
||||||
|
"diagnostics should report canonical mode name; stderr: {stderr}");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eval_mode_c_runs_and_reports_billable_recall() {
|
fn eval_lexical_runs_and_reports_billable_recall() {
|
||||||
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
|
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
|
||||||
.args(["eval", "--mode", "c", "--cases", "30"])
|
.args(["eval", "--mode", "lexical", "--cases", "30"])
|
||||||
.arg("--config").arg("config/default.toml")
|
.arg("--config").arg("config/default.toml")
|
||||||
.output().unwrap();
|
.output().unwrap();
|
||||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
@@ -15,7 +15,7 @@ fn eval_mode_c_runs_and_reports_billable_recall() {
|
|||||||
fn eval_is_deterministic_same_seed() {
|
fn eval_is_deterministic_same_seed() {
|
||||||
let run = || {
|
let run = || {
|
||||||
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
|
let out = Command::new(env!("CARGO_BIN_EXE_alpha-id"))
|
||||||
.args(["eval", "--mode", "c", "--cases", "40", "--seed", "42"])
|
.args(["eval", "--mode", "lexical", "--cases", "40", "--seed", "42"])
|
||||||
.arg("--config").arg("config/default.toml")
|
.arg("--config").arg("config/default.toml")
|
||||||
.output().unwrap();
|
.output().unwrap();
|
||||||
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
assert!(out.status.success(), "stderr: {}", String::from_utf8_lossy(&out.stderr));
|
||||||
|
|||||||
+11
-2
@@ -15,6 +15,15 @@ fn filter_default_accepts_everything() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mode_parses_from_str() {
|
fn mode_parses_from_str() {
|
||||||
assert_eq!("a".parse::<Mode>().unwrap(), Mode::A);
|
assert_eq!("lexical".parse::<Mode>().unwrap(), Mode::Lexical);
|
||||||
assert_eq!("c".parse::<Mode>().unwrap(), Mode::C);
|
assert_eq!("hybrid".parse::<Mode>().unwrap(), Mode::Hybrid);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mode_letter_aliases_still_parse_to_new_variants() {
|
||||||
|
// The legacy `c`/`a` CLI tokens remain accepted aliases (case-insensitive)
|
||||||
|
// for the renamed canonical `lexical`/`hybrid` modes.
|
||||||
|
assert_eq!("c".parse::<Mode>().unwrap(), Mode::Lexical);
|
||||||
|
assert_eq!("a".parse::<Mode>().unwrap(), Mode::Hybrid);
|
||||||
|
assert_eq!("A".parse::<Mode>().unwrap(), Mode::Hybrid);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ use alpha_id::pipeline::Pipeline;
|
|||||||
use alpha_id::model::{Config, Filter, Mode};
|
use alpha_id::model::{Config, Filter, Mode};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mode_a_degrades_to_c_when_ionos_unreachable() {
|
fn hybrid_degrades_to_lexical_when_ionos_unreachable() {
|
||||||
let mut cfg = Config::load("config/default.toml").unwrap();
|
let mut cfg = Config::load("config/default.toml").unwrap();
|
||||||
cfg.ionos_base_url = "http://127.0.0.1:1".into(); // refused
|
cfg.ionos_base_url = "http://127.0.0.1:1".into(); // refused
|
||||||
let p = Pipeline::load(&cfg).unwrap();
|
let p = Pipeline::load(&cfg).unwrap();
|
||||||
let res = p.suggest("Diabetes mellitus Typ 2", Mode::A, &Filter::default(), 10);
|
let res = p.suggest("Diabetes mellitus Typ 2", Mode::Hybrid, &Filter::default(), 10);
|
||||||
assert!(res.diagnostics.degraded); // fell back
|
assert!(res.diagnostics.degraded); // fell back
|
||||||
assert!(!res.suggestions.is_empty()); // still answered
|
assert!(!res.suggestions.is_empty()); // still answered
|
||||||
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11")));
|
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11")));
|
||||||
@@ -6,23 +6,23 @@ fn cfg() -> Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn mode_c_end_to_end_real_data() {
|
fn lexical_end_to_end_real_data() {
|
||||||
let p = Pipeline::load(&cfg()).unwrap();
|
let p = Pipeline::load(&cfg()).unwrap();
|
||||||
let dictation = "Patient mit Diabetes mellitus Typ 2\n\nArterielle Hypertonie";
|
let dictation = "Patient mit Diabetes mellitus Typ 2\n\nArterielle Hypertonie";
|
||||||
let res = p.suggest(dictation, Mode::C, &Filter::default(), 10);
|
let res = p.suggest(dictation, Mode::Lexical, &Filter::default(), 10);
|
||||||
assert!(!res.suggestions.is_empty());
|
assert!(!res.suggestions.is_empty());
|
||||||
assert!(res.suggestions.len() <= 10);
|
assert!(res.suggestions.len() <= 10);
|
||||||
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11")));
|
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11")));
|
||||||
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("I10")));
|
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("I10")));
|
||||||
assert_eq!(res.diagnostics.segment_count, 2);
|
assert_eq!(res.diagnostics.segment_count, 2);
|
||||||
assert_eq!(res.diagnostics.mode, "C");
|
assert_eq!(res.diagnostics.mode, "Lexical");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn billable_only_filter_excludes_three_digit_v_codes() {
|
fn billable_only_filter_excludes_three_digit_v_codes() {
|
||||||
let p = Pipeline::load(&cfg()).unwrap();
|
let p = Pipeline::load(&cfg()).unwrap();
|
||||||
let f = Filter { billable_only: true, valid_only: true, chapters: None, exclude_exotic: false };
|
let f = Filter { billable_only: true, valid_only: true, chapters: None, exclude_exotic: false };
|
||||||
let res = p.suggest("Cholera", Mode::C, &f, 10);
|
let res = p.suggest("Cholera", Mode::Lexical, &f, 10);
|
||||||
assert!(res.suggestions.iter().all(|s| s.tags.billable && s.tags.valid));
|
assert!(res.suggestions.iter().all(|s| s.tags.billable && s.tags.valid));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ fn billable_only_filter_excludes_three_digit_v_codes() {
|
|||||||
fn billable_only_keeps_diabetes_5th_digit_codes() {
|
fn billable_only_keeps_diabetes_5th_digit_codes() {
|
||||||
let p = Pipeline::load(&cfg()).unwrap();
|
let p = Pipeline::load(&cfg()).unwrap();
|
||||||
let f = Filter { billable_only: true, valid_only: true, chapters: None, exclude_exotic: false };
|
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);
|
let res = p.suggest("Diabetes mellitus Typ 2", Mode::Lexical, &f, 10);
|
||||||
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11") && s.tags.billable),
|
assert!(res.suggestions.iter().any(|s| s.icd_code.starts_with("E11") && s.tags.billable),
|
||||||
"billable E11.x must survive --billable-only; got {:?}",
|
"billable E11.x must survive --billable-only; got {:?}",
|
||||||
res.suggestions.iter().map(|s| (&s.icd_code, s.tags.billable)).collect::<Vec<_>>());
|
res.suggestions.iter().map(|s| (&s.icd_code, s.tags.billable)).collect::<Vec<_>>());
|
||||||
Reference in New Issue
Block a user