use crate::store::{ConcreteValue, DisplayStore, NumericStore};
use anyhow::Result;
use serde::Serialize;
const DEFINED: u8 = 2;
const FSN: u64 = 900000000000003001;
const PREFERRED: u64 = 900000000000548007;
#[derive(Serialize)]
pub struct Concept {
pub code: String,
pub display: Option<String>,
}
#[derive(Serialize)]
pub struct Description {
pub id: String,
pub term: String,
pub kind: Concept,
pub active: bool,
pub language: String,
pub preferred_in: Vec<Concept>,
pub acceptable_in: Vec<Concept>,
}
#[derive(Serialize)]
#[serde(tag = "type", content = "value", rename_all = "lowercase")]
pub enum Value {
Number(String),
Text(String),
Boolean(bool),
}
impl From<&ConcreteValue> for Value {
fn from(value: &ConcreteValue) -> Self {
match value {
ConcreteValue::Number(n) => Value::Number(n.clone()),
ConcreteValue::Text(t) => Value::Text(t.clone()),
ConcreteValue::Boolean(b) => Value::Boolean(*b),
}
}
}
#[derive(Serialize)]
pub struct Relationship {
#[serde(rename = "type")]
pub kind: Concept,
#[serde(skip_serializing_if = "Option::is_none")]
pub target: Option<Concept>,
#[serde(skip_serializing_if = "Option::is_none")]
pub value: Option<Value>,
}
#[derive(Serialize)]
pub struct Group {
pub group: u32,
pub attributes: Vec<Relationship>,
}
#[derive(Serialize)]
pub struct Detail {
pub code: String,
pub active: bool,
pub defined: bool,
pub module: Concept,
pub effective_time: u32,
pub fsn: Option<String>,
pub display: Option<String>,
pub parents: Vec<Concept>,
pub children: Vec<Concept>,
pub groups: Vec<Group>,
pub descriptions: Vec<Description>,
pub refsets: Vec<Concept>,
}
pub fn describe(
store: &NumericStore,
displays: &DisplayStore,
sctid: u64,
) -> Result<Option<Detail>> {
let Some(ordinal) = store.ordinal(sctid) else {
return Ok(None);
};
let label = |ordinal: u32| -> Result<Concept> {
Ok(Concept {
code: store.ids[ordinal as usize].to_string(),
display: displays.get(ordinal)?,
})
};
let mut parents = Vec::new();
for &parent in store.parents.get(ordinal) {
parents.push(label(parent)?);
}
let mut children = Vec::new();
for &child in store.children.get(ordinal) {
children.push(label(child)?);
}
let mut groups: Vec<Group> = Vec::new();
let push =
|groups: &mut Vec<Group>, group: u32, attribute: Relationship| match groups.last_mut() {
Some(last) if last.group == group => last.attributes.push(attribute),
_ => groups.push(Group {
group,
attributes: vec![attribute],
}),
};
for attribute in store.attributes.get(ordinal) {
let relationship = Relationship {
kind: label(attribute.kind)?,
target: Some(label(attribute.value)?),
value: None,
};
push(&mut groups, attribute.group, relationship);
}
for attribute in store.concrete.get(ordinal) {
let value = store
.concrete_values
.get(attribute.value as usize)
.map(Value::from);
let relationship = Relationship {
kind: label(attribute.kind)?,
target: None,
value,
};
push(&mut groups, attribute.group, relationship);
}
groups.sort_by_key(|g| g.group);
let mut fsn = None;
let mut descriptions = Vec::new();
if let Some(rows) = store.descriptions.concept_rows(ordinal)? {
for row in rows {
let kind_ordinal = row.kind;
let kind = store.ids[kind_ordinal as usize];
let term = row.term;
let active = row.active;
if kind == FSN && active && fsn.is_none() {
fsn = Some(term.clone());
}
let (mut preferred_in, mut acceptable_in) = (Vec::new(), Vec::new());
for (refset, acceptability) in row.dialects {
let target = if store.ids[acceptability as usize] == PREFERRED {
&mut preferred_in
} else {
&mut acceptable_in
};
target.push(label(refset)?);
}
let language = row.language;
descriptions.push(Description {
id: row.id.to_string(),
term,
kind: label(kind_ordinal)?,
active,
language: String::from_utf8_lossy(&language).into_owned(),
preferred_in,
acceptable_in,
});
}
descriptions.sort_by(|a, b| {
b.active
.cmp(&a.active)
.then_with(|| {
(a.kind.code != FSN.to_string()).cmp(&(b.kind.code != FSN.to_string()))
})
.then_with(|| a.term.cmp(&b.term))
});
}
let mut refsets = Vec::new();
if let Some(membership) = &store.membership {
for (position, &refset) in membership.refsets.iter().enumerate() {
if membership.get(position).binary_search(&ordinal).is_ok() {
refsets.push(label(refset)?);
}
}
}
let flags = store.flags[ordinal as usize];
Ok(Some(Detail {
code: sctid.to_string(),
active: flags & 1 != 0,
defined: flags & DEFINED != 0,
module: label(store.modules[ordinal as usize])?,
effective_time: store.effective_times[ordinal as usize],
fsn,
display: displays.get(ordinal)?,
parents,
children,
groups,
descriptions,
refsets,
}))
}