use std::collections::{BTreeMap, BTreeSet};
use crate::id::{ConceptId, ModuleId};
use crate::refset::ModuleDependencyMember;
use crate::time::EffectiveTime;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum EditionError {
#[error("no active module dependency members")]
NoDependencies,
#[error("module dependency member {member} names {target}, which is not a concept identifier")]
MalformedTarget {
member: String,
target: String,
},
#[error("several root modules and none carries the release date {release}: {roots:?}")]
AmbiguousRoot {
release: String,
roots: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Edition {
pub module: ModuleId,
pub sibling_roots: Vec<ModuleId>,
pub effective_time: EffectiveTime,
pub modules: BTreeMap<ModuleId, EffectiveTime>,
}
impl Edition {
pub fn identify(
members: &[ModuleDependencyMember],
release: EffectiveTime,
) -> Result<Self, EditionError> {
let active: Vec<&ModuleDependencyMember> =
members.iter().filter(|m| m.member.active).collect();
if active.is_empty() {
return Err(EditionError::NoDependencies);
}
let mut modules: BTreeMap<ModuleId, EffectiveTime> = BTreeMap::new();
let mut depended_on: BTreeSet<ModuleId> = BTreeSet::new();
for member in &active {
let source = member.member.module_id;
modules
.entry(source)
.and_modify(|t| *t = (*t).max(member.source_effective_time))
.or_insert(member.source_effective_time);
let target = ConceptId::try_from(member.member.referenced_component_id)
.map(ModuleId::from)
.map_err(|_| EditionError::MalformedTarget {
member: member.member.id.to_string(),
target: member.member.referenced_component_id.to_string(),
})?;
depended_on.insert(target);
modules
.entry(target)
.and_modify(|t| *t = (*t).max(member.target_effective_time))
.or_insert(member.target_effective_time);
}
let roots: Vec<ModuleId> = modules
.keys()
.filter(|m| !depended_on.contains(*m))
.copied()
.collect();
let module = match roots.as_slice() {
[only] => *only,
many => *many
.iter()
.find(|m| modules.get(*m) == Some(&release))
.ok_or_else(|| EditionError::AmbiguousRoot {
release: release.compact(),
roots: many.iter().map(ToString::to_string).collect(),
})?,
};
let effective_time = modules.get(&module).copied().unwrap_or(release);
let sibling_roots = roots.into_iter().filter(|m| *m != module).collect();
Ok(Self {
module,
sibling_roots,
effective_time,
modules,
})
}
#[must_use]
pub fn edition_uri(&self) -> String {
format!("http://snomed.info/sct/{}", self.module)
}
#[must_use]
pub fn version_uri(&self) -> String {
format!(
"{}/version/{}",
self.edition_uri(),
self.effective_time.compact()
)
}
}