use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OntologySystem {
SNOMED,
RxNorm,
LOINC,
ICD11,
Unstructured,
}
impl fmt::Display for OntologySystem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OntologySystem::SNOMED => write!(f, "SNOMED"),
OntologySystem::RxNorm => write!(f, "RxNorm"),
OntologySystem::LOINC => write!(f, "LOINC"),
OntologySystem::ICD11 => write!(f, "ICD11"),
OntologySystem::Unstructured => write!(f, "Unstructured"),
}
}
}
#[derive(Debug, Clone)]
pub struct Atom {
pub system: OntologySystem,
pub code: String,
pub preferred_term: String,
pub version: String,
}
impl PartialEq for Atom {
fn eq(&self, other: &Self) -> bool {
self.system == other.system && self.code == other.code && self.version == other.version
}
}
impl Eq for Atom {}
impl std::hash::Hash for Atom {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.system.hash(state);
self.code.hash(state);
self.version.hash(state);
}
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{} ({})", self.system, self.code, self.preferred_term)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OntologyError {
CodeNotFound {
code: String,
system: OntologySystem,
},
NetworkError {
system: OntologySystem,
description: String,
},
InvalidCodeFormat { code: String },
OfflineSnapshotUnavailable {
system: OntologySystem,
description: String,
},
}
impl fmt::Display for OntologyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OntologyError::CodeNotFound { code, system } => {
write!(f, "Code not found: {} in {}", code, system)
}
OntologyError::NetworkError {
system,
description,
} => {
write!(f, "Network error resolving {}: {}", system, description)
}
OntologyError::InvalidCodeFormat { code } => {
write!(f, "Invalid code format: {}", code)
}
OntologyError::OfflineSnapshotUnavailable {
system,
description,
} => {
write!(
f,
"Offline snapshot unavailable for {}: {}",
system, description
)
}
}
}
}
impl std::error::Error for OntologyError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheMode {
Online,
Offline,
CacheOnly,
}
#[async_trait::async_trait]
pub trait OntologyAdapter: Send + Sync {
async fn resolve_atom(&self, code: &str) -> Result<Atom, OntologyError>;
fn validate_compatibility(&self, atom1: &Atom, atom2: &Atom) -> bool {
atom1.system == atom2.system && atom1.code == atom2.code && atom1.version == atom2.version
}
fn ontology_version(&self) -> &str;
fn cache_mode(&self) -> CacheMode;
}
pub struct SNOMEDAdapter {
cache: std::sync::Arc<tokio::sync::Mutex<lru::LruCache<String, Atom>>>,
snapshot: std::sync::Arc<std::collections::HashMap<String, Atom>>,
version: String,
mode: CacheMode,
}
impl SNOMEDAdapter {
pub fn new(
snapshot: std::collections::HashMap<String, Atom>,
cache_size: usize,
version: impl Into<String>,
mode: CacheMode,
) -> Self {
let cache_size = if cache_size == 0 { 1024 } else { cache_size };
SNOMEDAdapter {
cache: std::sync::Arc::new(tokio::sync::Mutex::new(lru::LruCache::new(
std::num::NonZeroUsize::new(cache_size).unwrap(),
))),
snapshot: std::sync::Arc::new(snapshot),
version: version.into(),
mode,
}
}
#[cfg(test)]
pub fn test_adapter() -> Self {
let mut snapshot = std::collections::HashMap::new();
snapshot.insert(
"67822003".to_string(),
Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
},
);
snapshot.insert(
"3723001".to_string(),
Atom {
system: OntologySystem::SNOMED,
code: "3723001".to_string(),
preferred_term: "Acute respiratory distress syndrome".to_string(),
version: "2026-01-31".to_string(),
},
);
snapshot.insert(
"29303001".to_string(),
Atom {
system: OntologySystem::SNOMED,
code: "29303001".to_string(),
preferred_term: "Respiratory distress".to_string(),
version: "2026-01-31".to_string(),
},
);
SNOMEDAdapter::new(snapshot, 100, "2026-01-31", CacheMode::CacheOnly)
}
}
#[async_trait::async_trait]
impl OntologyAdapter for SNOMEDAdapter {
async fn resolve_atom(&self, code: &str) -> Result<Atom, OntologyError> {
{
let mut cache = self.cache.lock().await;
if let Some(atom) = cache.get(code) {
return Ok(atom.clone());
}
}
if let Some(atom) = self.snapshot.get(code).cloned() {
let mut cache = self.cache.lock().await;
cache.put(code.to_string(), atom.clone());
return Ok(atom);
}
Err(OntologyError::CodeNotFound {
code: code.to_string(),
system: OntologySystem::SNOMED,
})
}
fn ontology_version(&self) -> &str {
&self.version
}
fn cache_mode(&self) -> CacheMode {
self.mode
}
}
pub struct RxNormAdapter {
cache: std::sync::Arc<tokio::sync::Mutex<lru::LruCache<String, Atom>>>,
snapshot: std::sync::Arc<std::collections::HashMap<String, Atom>>,
version: String,
mode: CacheMode,
}
impl RxNormAdapter {
pub fn new(
snapshot: std::collections::HashMap<String, Atom>,
cache_size: usize,
version: impl Into<String>,
mode: CacheMode,
) -> Self {
let cache_size = if cache_size == 0 { 1024 } else { cache_size };
RxNormAdapter {
cache: std::sync::Arc::new(tokio::sync::Mutex::new(lru::LruCache::new(
std::num::NonZeroUsize::new(cache_size).unwrap(),
))),
snapshot: std::sync::Arc::new(snapshot),
version: version.into(),
mode,
}
}
#[cfg(test)]
pub fn test_adapter() -> Self {
let mut snapshot = std::collections::HashMap::new();
snapshot.insert(
"855288".to_string(),
Atom {
system: OntologySystem::RxNorm,
code: "855288".to_string(),
preferred_term: "Lisinopril 10 MG Oral Tablet".to_string(),
version: "2026-01-06".to_string(),
},
);
snapshot.insert(
"312547".to_string(),
Atom {
system: OntologySystem::RxNorm,
code: "312547".to_string(),
preferred_term: "Metformin 500 MG Oral Tablet".to_string(),
version: "2026-01-06".to_string(),
},
);
snapshot.insert(
"1049612".to_string(),
Atom {
system: OntologySystem::RxNorm,
code: "1049612".to_string(),
preferred_term: "Levothyroxine 50 MCG Oral Tablet".to_string(),
version: "2026-01-06".to_string(),
},
);
RxNormAdapter::new(snapshot, 100, "2026-01-06", CacheMode::CacheOnly)
}
}
#[async_trait::async_trait]
impl OntologyAdapter for RxNormAdapter {
async fn resolve_atom(&self, code: &str) -> Result<Atom, OntologyError> {
{
let mut cache = self.cache.lock().await;
if let Some(atom) = cache.get(code) {
return Ok(atom.clone());
}
}
if let Some(atom) = self.snapshot.get(code).cloned() {
let mut cache = self.cache.lock().await;
cache.put(code.to_string(), atom.clone());
return Ok(atom);
}
Err(OntologyError::CodeNotFound {
code: code.to_string(),
system: OntologySystem::RxNorm,
})
}
fn ontology_version(&self) -> &str {
&self.version
}
fn cache_mode(&self) -> CacheMode {
self.mode
}
}
pub struct LoincAdapter {
cache: std::sync::Arc<tokio::sync::Mutex<lru::LruCache<String, Atom>>>,
snapshot: std::sync::Arc<std::collections::HashMap<String, Atom>>,
version: String,
mode: CacheMode,
}
impl LoincAdapter {
pub fn new(
snapshot: std::collections::HashMap<String, Atom>,
cache_size: usize,
version: impl Into<String>,
mode: CacheMode,
) -> Self {
let cache_size = if cache_size == 0 { 1024 } else { cache_size };
LoincAdapter {
cache: std::sync::Arc::new(tokio::sync::Mutex::new(lru::LruCache::new(
std::num::NonZeroUsize::new(cache_size).unwrap(),
))),
snapshot: std::sync::Arc::new(snapshot),
version: version.into(),
mode,
}
}
#[cfg(test)]
pub fn test_adapter() -> Self {
let mut snapshot = std::collections::HashMap::new();
snapshot.insert(
"2160-0".to_string(),
Atom {
system: OntologySystem::LOINC,
code: "2160-0".to_string(),
preferred_term: "Creatinine [Mass/volume] in Serum or Plasma".to_string(),
version: "2.76".to_string(),
},
);
snapshot.insert(
"3024-7".to_string(),
Atom {
system: OntologySystem::LOINC,
code: "3024-7".to_string(),
preferred_term: "Urea nitrogen [Mass/volume] in Serum or Plasma".to_string(),
version: "2.76".to_string(),
},
);
snapshot.insert(
"8867-4".to_string(),
Atom {
system: OntologySystem::LOINC,
code: "8867-4".to_string(),
preferred_term: "Heart rate".to_string(),
version: "2.76".to_string(),
},
);
LoincAdapter::new(snapshot, 100, "2.76", CacheMode::CacheOnly)
}
}
#[async_trait::async_trait]
impl OntologyAdapter for LoincAdapter {
async fn resolve_atom(&self, code: &str) -> Result<Atom, OntologyError> {
{
let mut cache = self.cache.lock().await;
if let Some(atom) = cache.get(code) {
return Ok(atom.clone());
}
}
if let Some(atom) = self.snapshot.get(code).cloned() {
let mut cache = self.cache.lock().await;
cache.put(code.to_string(), atom.clone());
return Ok(atom);
}
Err(OntologyError::CodeNotFound {
code: code.to_string(),
system: OntologySystem::LOINC,
})
}
fn ontology_version(&self) -> &str {
&self.version
}
fn cache_mode(&self) -> CacheMode {
self.mode
}
}
pub struct Icd11Adapter {
cache: std::sync::Arc<tokio::sync::Mutex<lru::LruCache<String, Atom>>>,
snapshot: std::sync::Arc<std::collections::HashMap<String, Atom>>,
version: String,
mode: CacheMode,
}
impl Icd11Adapter {
pub fn new(
snapshot: std::collections::HashMap<String, Atom>,
cache_size: usize,
version: impl Into<String>,
mode: CacheMode,
) -> Self {
let cache_size = if cache_size == 0 { 1024 } else { cache_size };
Icd11Adapter {
cache: std::sync::Arc::new(tokio::sync::Mutex::new(lru::LruCache::new(
std::num::NonZeroUsize::new(cache_size).unwrap(),
))),
snapshot: std::sync::Arc::new(snapshot),
version: version.into(),
mode,
}
}
#[cfg(test)]
pub fn test_adapter() -> Self {
let mut snapshot = std::collections::HashMap::new();
snapshot.insert(
"BA47".to_string(),
Atom {
system: OntologySystem::ICD11,
code: "BA47".to_string(),
preferred_term: "Essential (primary) hypertension".to_string(),
version: "2024-11".to_string(),
},
);
snapshot.insert(
"BB81.1".to_string(),
Atom {
system: OntologySystem::ICD11,
code: "BB81.1".to_string(),
preferred_term: "Acute kidney injury".to_string(),
version: "2024-11".to_string(),
},
);
snapshot.insert(
"BA80.31".to_string(),
Atom {
system: OntologySystem::ICD11,
code: "BA80.31".to_string(),
preferred_term: "Type 2 diabetes mellitus".to_string(),
version: "2024-11".to_string(),
},
);
Icd11Adapter::new(snapshot, 100, "2024-11", CacheMode::CacheOnly)
}
}
#[async_trait::async_trait]
impl OntologyAdapter for Icd11Adapter {
async fn resolve_atom(&self, code: &str) -> Result<Atom, OntologyError> {
{
let mut cache = self.cache.lock().await;
if let Some(atom) = cache.get(code) {
return Ok(atom.clone());
}
}
if let Some(atom) = self.snapshot.get(code).cloned() {
let mut cache = self.cache.lock().await;
cache.put(code.to_string(), atom.clone());
return Ok(atom);
}
Err(OntologyError::CodeNotFound {
code: code.to_string(),
system: OntologySystem::ICD11,
})
}
fn ontology_version(&self) -> &str {
&self.version
}
fn cache_mode(&self) -> CacheMode {
self.mode
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ontology_system_display() {
assert_eq!(OntologySystem::SNOMED.to_string(), "SNOMED");
assert_eq!(OntologySystem::RxNorm.to_string(), "RxNorm");
assert_eq!(OntologySystem::LOINC.to_string(), "LOINC");
assert_eq!(OntologySystem::ICD11.to_string(), "ICD11");
assert_eq!(OntologySystem::Unstructured.to_string(), "Unstructured");
}
#[test]
fn test_atom_creation() {
let atom = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
assert_eq!(atom.system, OntologySystem::SNOMED);
assert_eq!(atom.code, "67822003");
assert_eq!(atom.preferred_term, "Hypoxemia");
assert_eq!(atom.version, "2026-01-31");
}
#[test]
fn test_atom_compatibility_same() {
let atom1 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
let atom2 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
let adapter = MockAdapter;
assert!(adapter.validate_compatibility(&atom1, &atom2));
}
#[test]
fn test_atom_compatibility_different_version() {
let atom1 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
let atom2 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2025-01-31".to_string(),
};
let adapter = MockAdapter;
assert!(!adapter.validate_compatibility(&atom1, &atom2));
}
#[test]
fn test_atom_compatibility_different_code() {
let atom1 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
let atom2 = Atom {
system: OntologySystem::SNOMED,
code: "67822004".to_string(), preferred_term: "Different concept".to_string(),
version: "2026-01-31".to_string(),
};
let adapter = MockAdapter;
assert!(!adapter.validate_compatibility(&atom1, &atom2));
}
#[test]
fn test_ontology_error_display() {
let err = OntologyError::CodeNotFound {
code: "67822003".to_string(),
system: OntologySystem::SNOMED,
};
assert_eq!(err.to_string(), "Code not found: 67822003 in SNOMED");
}
struct MockAdapter;
#[async_trait::async_trait]
impl OntologyAdapter for MockAdapter {
async fn resolve_atom(&self, _code: &str) -> Result<Atom, OntologyError> {
Err(OntologyError::CodeNotFound {
code: "mock".to_string(),
system: OntologySystem::SNOMED,
})
}
fn ontology_version(&self) -> &str {
"1.0.0"
}
fn cache_mode(&self) -> CacheMode {
CacheMode::CacheOnly
}
}
#[tokio::test]
async fn test_snomed_adapter_resolve_success() {
let adapter = SNOMEDAdapter::test_adapter();
let atom = adapter.resolve_atom("67822003").await;
assert!(atom.is_ok());
let atom = atom.unwrap();
assert_eq!(atom.code, "67822003");
assert_eq!(atom.preferred_term, "Hypoxemia");
assert_eq!(atom.system, OntologySystem::SNOMED);
}
#[tokio::test]
async fn test_snomed_adapter_resolve_not_found() {
let adapter = SNOMEDAdapter::test_adapter();
let result = adapter.resolve_atom("999999999").await;
assert!(result.is_err());
match result {
Err(OntologyError::CodeNotFound { code, system }) => {
assert_eq!(code, "999999999");
assert_eq!(system, OntologySystem::SNOMED);
}
_ => panic!("Expected CodeNotFound error"),
}
}
#[tokio::test]
async fn test_snomed_adapter_cache_behavior() {
let adapter = SNOMEDAdapter::test_adapter();
let atom1 = adapter.resolve_atom("67822003").await.unwrap();
let atom2 = adapter.resolve_atom("67822003").await.unwrap();
assert_eq!(atom1, atom2);
assert_eq!(atom1.code, "67822003");
}
#[tokio::test]
async fn test_snomed_adapter_version() {
let adapter = SNOMEDAdapter::test_adapter();
assert_eq!(adapter.ontology_version(), "2026-01-31");
}
#[tokio::test]
async fn test_snomed_adapter_cache_mode() {
let adapter = SNOMEDAdapter::test_adapter();
assert_eq!(adapter.cache_mode(), CacheMode::CacheOnly);
}
#[tokio::test]
async fn test_snomed_adapter_multiple_codes() {
let adapter = SNOMEDAdapter::test_adapter();
let hypoxemia = adapter.resolve_atom("67822003").await.unwrap();
assert_eq!(hypoxemia.preferred_term, "Hypoxemia");
let ards = adapter.resolve_atom("3723001").await.unwrap();
assert_eq!(ards.preferred_term, "Acute respiratory distress syndrome");
let resp_distress = adapter.resolve_atom("29303001").await.unwrap();
assert_eq!(resp_distress.preferred_term, "Respiratory distress");
assert_eq!(hypoxemia.version, "2026-01-31");
assert_eq!(ards.version, "2026-01-31");
assert_eq!(resp_distress.version, "2026-01-31");
}
#[test]
fn test_snomed_adapter_compatibility() {
let adapter = SNOMEDAdapter::test_adapter();
let atom1 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
let atom2 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
assert!(adapter.validate_compatibility(&atom1, &atom2));
}
#[tokio::test]
async fn test_rxnorm_adapter_resolve_success() {
let adapter = RxNormAdapter::test_adapter();
let atom = adapter.resolve_atom("855288").await;
assert!(atom.is_ok());
let atom = atom.unwrap();
assert_eq!(atom.code, "855288");
assert_eq!(atom.preferred_term, "Lisinopril 10 MG Oral Tablet");
assert_eq!(atom.system, OntologySystem::RxNorm);
}
#[tokio::test]
async fn test_rxnorm_adapter_resolve_not_found() {
let adapter = RxNormAdapter::test_adapter();
let result = adapter.resolve_atom("999999999").await;
assert!(result.is_err());
match result {
Err(OntologyError::CodeNotFound { code, system }) => {
assert_eq!(code, "999999999");
assert_eq!(system, OntologySystem::RxNorm);
}
_ => panic!("Expected CodeNotFound error"),
}
}
#[tokio::test]
async fn test_rxnorm_adapter_cache_behavior() {
let adapter = RxNormAdapter::test_adapter();
let atom1 = adapter.resolve_atom("855288").await.unwrap();
let atom2 = adapter.resolve_atom("855288").await.unwrap();
assert_eq!(atom1, atom2);
assert_eq!(atom1.code, "855288");
}
#[tokio::test]
async fn test_rxnorm_adapter_version() {
let adapter = RxNormAdapter::test_adapter();
assert_eq!(adapter.ontology_version(), "2026-01-06");
}
#[tokio::test]
async fn test_rxnorm_adapter_cache_mode() {
let adapter = RxNormAdapter::test_adapter();
assert_eq!(adapter.cache_mode(), CacheMode::CacheOnly);
}
#[tokio::test]
async fn test_rxnorm_adapter_multiple_codes() {
let adapter = RxNormAdapter::test_adapter();
let lisinopril = adapter.resolve_atom("855288").await.unwrap();
assert_eq!(lisinopril.preferred_term, "Lisinopril 10 MG Oral Tablet");
let metformin = adapter.resolve_atom("312547").await.unwrap();
assert_eq!(metformin.preferred_term, "Metformin 500 MG Oral Tablet");
let levothyroxine = adapter.resolve_atom("1049612").await.unwrap();
assert_eq!(
levothyroxine.preferred_term,
"Levothyroxine 50 MCG Oral Tablet"
);
assert_eq!(lisinopril.version, "2026-01-06");
assert_eq!(metformin.version, "2026-01-06");
assert_eq!(levothyroxine.version, "2026-01-06");
}
#[test]
fn test_rxnorm_adapter_compatibility() {
let adapter = RxNormAdapter::test_adapter();
let atom1 = Atom {
system: OntologySystem::RxNorm,
code: "855288".to_string(),
preferred_term: "Lisinopril 10 MG Oral Tablet".to_string(),
version: "2026-01-06".to_string(),
};
let atom2 = Atom {
system: OntologySystem::RxNorm,
code: "855288".to_string(),
preferred_term: "Lisinopril 10 MG Oral Tablet".to_string(),
version: "2026-01-06".to_string(),
};
assert!(adapter.validate_compatibility(&atom1, &atom2));
}
#[tokio::test]
async fn test_loinc_adapter_resolve_success() {
let adapter = LoincAdapter::test_adapter();
let atom = adapter.resolve_atom("2160-0").await;
assert!(atom.is_ok());
let atom = atom.unwrap();
assert_eq!(atom.code, "2160-0");
assert_eq!(
atom.preferred_term,
"Creatinine [Mass/volume] in Serum or Plasma"
);
assert_eq!(atom.system, OntologySystem::LOINC);
}
#[tokio::test]
async fn test_loinc_adapter_resolve_not_found() {
let adapter = LoincAdapter::test_adapter();
let result = adapter.resolve_atom("9999-9").await;
assert!(result.is_err());
match result {
Err(OntologyError::CodeNotFound { code, system }) => {
assert_eq!(code, "9999-9");
assert_eq!(system, OntologySystem::LOINC);
}
_ => panic!("Expected CodeNotFound error"),
}
}
#[tokio::test]
async fn test_loinc_adapter_cache_behavior() {
let adapter = LoincAdapter::test_adapter();
let atom1 = adapter.resolve_atom("2160-0").await.unwrap();
let atom2 = adapter.resolve_atom("2160-0").await.unwrap();
assert_eq!(atom1, atom2);
assert_eq!(atom1.code, "2160-0");
}
#[tokio::test]
async fn test_loinc_adapter_version() {
let adapter = LoincAdapter::test_adapter();
assert_eq!(adapter.ontology_version(), "2.76");
}
#[tokio::test]
async fn test_loinc_adapter_cache_mode() {
let adapter = LoincAdapter::test_adapter();
assert_eq!(adapter.cache_mode(), CacheMode::CacheOnly);
}
#[tokio::test]
async fn test_loinc_adapter_multiple_codes() {
let adapter = LoincAdapter::test_adapter();
let creatinine = adapter.resolve_atom("2160-0").await.unwrap();
assert_eq!(
creatinine.preferred_term,
"Creatinine [Mass/volume] in Serum or Plasma"
);
let urea = adapter.resolve_atom("3024-7").await.unwrap();
assert_eq!(
urea.preferred_term,
"Urea nitrogen [Mass/volume] in Serum or Plasma"
);
let heart_rate = adapter.resolve_atom("8867-4").await.unwrap();
assert_eq!(heart_rate.preferred_term, "Heart rate");
assert_eq!(creatinine.version, "2.76");
assert_eq!(urea.version, "2.76");
assert_eq!(heart_rate.version, "2.76");
}
#[test]
fn test_loinc_adapter_compatibility() {
let adapter = LoincAdapter::test_adapter();
let atom1 = Atom {
system: OntologySystem::LOINC,
code: "2160-0".to_string(),
preferred_term: "Creatinine [Mass/volume] in Serum or Plasma".to_string(),
version: "2.76".to_string(),
};
let atom2 = Atom {
system: OntologySystem::LOINC,
code: "2160-0".to_string(),
preferred_term: "Creatinine [Mass/volume] in Serum or Plasma".to_string(),
version: "2.76".to_string(),
};
assert!(adapter.validate_compatibility(&atom1, &atom2));
}
#[tokio::test]
async fn test_icd11_adapter_resolve_success() {
let adapter = Icd11Adapter::test_adapter();
let atom = adapter.resolve_atom("BA47").await;
assert!(atom.is_ok());
let atom = atom.unwrap();
assert_eq!(atom.code, "BA47");
assert_eq!(atom.preferred_term, "Essential (primary) hypertension");
assert_eq!(atom.system, OntologySystem::ICD11);
}
#[tokio::test]
async fn test_icd11_adapter_resolve_not_found() {
let adapter = Icd11Adapter::test_adapter();
let result = adapter.resolve_atom("ZZ99").await;
assert!(result.is_err());
match result {
Err(OntologyError::CodeNotFound { code, system }) => {
assert_eq!(code, "ZZ99");
assert_eq!(system, OntologySystem::ICD11);
}
_ => panic!("Expected CodeNotFound error"),
}
}
#[tokio::test]
async fn test_icd11_adapter_cache_behavior() {
let adapter = Icd11Adapter::test_adapter();
let atom1 = adapter.resolve_atom("BA47").await.unwrap();
let atom2 = adapter.resolve_atom("BA47").await.unwrap();
assert_eq!(atom1, atom2);
assert_eq!(atom1.code, "BA47");
}
#[tokio::test]
async fn test_icd11_adapter_version() {
let adapter = Icd11Adapter::test_adapter();
assert_eq!(adapter.ontology_version(), "2024-11");
}
#[tokio::test]
async fn test_icd11_adapter_cache_mode() {
let adapter = Icd11Adapter::test_adapter();
assert_eq!(adapter.cache_mode(), CacheMode::CacheOnly);
}
#[tokio::test]
async fn test_icd11_adapter_multiple_codes() {
let adapter = Icd11Adapter::test_adapter();
let hypertension = adapter.resolve_atom("BA47").await.unwrap();
assert_eq!(
hypertension.preferred_term,
"Essential (primary) hypertension"
);
let aki = adapter.resolve_atom("BB81.1").await.unwrap();
assert_eq!(aki.preferred_term, "Acute kidney injury");
let diabetes = adapter.resolve_atom("BA80.31").await.unwrap();
assert_eq!(diabetes.preferred_term, "Type 2 diabetes mellitus");
assert_eq!(hypertension.version, "2024-11");
assert_eq!(aki.version, "2024-11");
assert_eq!(diabetes.version, "2024-11");
}
#[test]
fn test_icd11_adapter_compatibility() {
let adapter = Icd11Adapter::test_adapter();
let atom1 = Atom {
system: OntologySystem::ICD11,
code: "BA47".to_string(),
preferred_term: "Essential (primary) hypertension".to_string(),
version: "2024-11".to_string(),
};
let atom2 = Atom {
system: OntologySystem::ICD11,
code: "BA47".to_string(),
preferred_term: "Essential (primary) hypertension".to_string(),
version: "2024-11".to_string(),
};
assert!(adapter.validate_compatibility(&atom1, &atom2));
}
#[test]
fn test_atom_identity_excludes_preferred_term() {
let atom_hypoxemia_label1 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2026-01-31".to_string(),
};
let atom_hypoxemia_label2 = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Low blood oxygen level".to_string(),
version: "2026-01-31".to_string(),
};
assert_eq!(atom_hypoxemia_label1, atom_hypoxemia_label2);
let mut set = std::collections::HashSet::new();
set.insert(atom_hypoxemia_label1.clone());
assert!(set.contains(&atom_hypoxemia_label2));
let atom_different_version = Atom {
system: OntologySystem::SNOMED,
code: "67822003".to_string(),
preferred_term: "Hypoxemia".to_string(),
version: "2025-01-31".to_string(),
};
assert_ne!(atom_hypoxemia_label1, atom_different_version);
}
}