use crate::{Message, Span, Warning};
pub const DCM_STUDY_INSTANCE_UID: &str = "110180";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum OrderField {
PatientId,
Accession,
ProcedureId,
StudyUid,
}
impl OrderField {
pub const ALL: [OrderField; 4] = [
OrderField::PatientId,
OrderField::Accession,
OrderField::ProcedureId,
OrderField::StudyUid,
];
pub fn paths(self) -> &'static [&'static str] {
match self {
OrderField::PatientId => &["PID-3.1"],
OrderField::Accession => &["OBR-18", "IPC-1.1"],
OrderField::ProcedureId => &["OBR-19", "IPC-2.1"],
OrderField::StudyUid => &["IPC-3.1", "ZDS-1.1"],
}
}
#[deprecated(since = "0.2.0", note = "use `paths()`; several paths are consulted")]
pub fn path(self) -> &'static str {
self.paths()[0]
}
pub fn label(self) -> &'static str {
match self {
OrderField::PatientId => "Patient ID",
OrderField::Accession => "Accession number",
OrderField::ProcedureId => "Requested procedure ID",
OrderField::StudyUid => "Study instance UID",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StudyUidSource {
Ipc3,
Zds1,
Obx,
}
impl StudyUidSource {
pub fn path(self) -> &'static str {
match self {
StudyUidSource::Ipc3 => "IPC-3.1",
StudyUidSource::Zds1 => "ZDS-1.1",
StudyUidSource::Obx => "OBX-5 (OBX-3 = 110180^Study Instance UID^DCM)",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Order {
pub patient_id: Option<String>,
pub accession: Option<String>,
pub procedure_id: Option<String>,
pub study_uid: Option<String>,
pub study_uid_source: Option<StudyUidSource>,
pub spans: Vec<(Span, OrderField)>,
pub warnings: Vec<Warning>,
pub sources: Vec<(OrderField, &'static str)>,
}
impl Order {
pub fn extract(msg: &Message) -> Order {
let mut order = Order::default();
for field in OrderField::ALL {
for path in field.paths() {
let Some(span) = msg.get_span(path) else {
continue;
};
let value = msg.decode(span.slice(msg.raw()));
let value = value.trim();
if value.is_empty() {
continue;
}
order.record(field, span, path, value);
if field == OrderField::StudyUid {
order.study_uid_source = Some(match *path {
"IPC-3.1" => StudyUidSource::Ipc3,
_ => StudyUidSource::Zds1,
});
}
break;
}
}
let obx_uids = obx_study_uids(msg);
if order.study_uid.is_none() {
if let Some((span, value)) = obx_uids.first() {
order.record(
OrderField::StudyUid,
*span,
StudyUidSource::Obx.path(),
value,
);
order.study_uid_source = Some(StudyUidSource::Obx);
}
}
if let Some(taken) = order.study_uid.clone() {
let taken_span = order
.spans
.iter()
.find(|(_, f)| *f == OrderField::StudyUid)
.map(|(s, _)| *s)
.unwrap_or_default();
let others = msg
.segments_named("IPC")
.filter_map(|seg| seg.field(3)?.component(1))
.map(|c| (c.span(), msg.decode(c.value()).trim().to_string()))
.chain(obx_uids.iter().cloned())
.filter(|(_, v)| !v.is_empty());
for (span, value) in others {
if value != taken {
order.warnings.push(Warning::ConflictingStudyUid {
first: taken_span,
other: span,
});
break;
}
}
}
order
}
fn record(&mut self, field: OrderField, span: Span, path: &'static str, value: &str) {
self.spans.push((span, field));
self.sources.push((field, path));
let slot = match field {
OrderField::PatientId => &mut self.patient_id,
OrderField::Accession => &mut self.accession,
OrderField::ProcedureId => &mut self.procedure_id,
OrderField::StudyUid => &mut self.study_uid,
};
*slot = Some(value.to_string());
}
pub fn get(&self, field: OrderField) -> Option<&str> {
match field {
OrderField::PatientId => self.patient_id.as_deref(),
OrderField::Accession => self.accession.as_deref(),
OrderField::ProcedureId => self.procedure_id.as_deref(),
OrderField::StudyUid => self.study_uid.as_deref(),
}
}
pub fn source_path(&self, field: OrderField) -> Option<&'static str> {
self.sources
.iter()
.find(|(f, _)| *f == field)
.map(|(_, p)| *p)
}
}
fn obx_study_uids(msg: &Message) -> Vec<(Span, String)> {
msg.segments_named("OBX")
.filter(|seg| {
let Some(id) = seg.field(3) else {
return false;
};
let code = id
.component(1)
.map(|c| c.value().trim().to_string())
.unwrap_or_default();
let text = id
.component(2)
.map(|c| c.value().trim().to_ascii_lowercase())
.unwrap_or_default();
code == DCM_STUDY_INSTANCE_UID || text.contains("study instance uid")
})
.filter_map(|seg| {
let value = seg.field(5)?.component(1)?;
let text = msg.decode(value.value()).trim().to_string();
(!text.is_empty()).then_some((value.span(), text))
})
.collect()
}