hl7_3/rim.rs
1//! The Reference Information Model (RIM): the six backbone classes every
2//! HL7 v3 domain payload is built from.
3//!
4//! Where HL7 v2 names segments and fields per message type, HL7 v3 names
5//! six classes once and reuses them everywhere:
6//!
7//! - [`Act`] — something that happened, is happening, or is intended to
8//! happen: an observation, a procedure, an administration of a
9//! substance. `moodCode` is what makes this one class cover both "this
10//! was done" (`EVN`) and "please do this" (`RQO`) — an intent and its
11//! eventual fulfillment are the same kind of thing at different moods,
12//! not two different classes.
13//! - [`Entity`] — a physical thing: a person, an organization, a device,
14//! a place. `determinerCode` distinguishes one specific instance
15//! (`INSTANCE`) from a kind of thing in general (`KIND`).
16//! - [`Role`] — a competency one [`Entity`] has with respect to another:
17//! *this* person as *patient*, *that* organization as *provider*. The
18//! same [`Entity`] plays different [`Role`]s in different messages.
19//! - [`Participation`] — links an [`Act`] to the [`Role`] that took part
20//! in it, and how (`AUT` author, `PRF` performer, `SBJ` subject, ...).
21//! - [`ActRelationship`] — links two [`Act`]s (`COMP` component, `RSON`
22//! reason, `PERT` pertains to, ...), which is how a single act becomes
23//! a document made of sections made of entries.
24//! - [`RoleLink`] — links two [`Role`]s, less common than the other five.
25//!
26//! Every attribute below is read straight off the matching XML element or
27//! attribute by [`crate::message::parse`]'s domain-payload walk, or can be
28//! read directly from a [`hl7_2_xml_lite_helper::Element`] with the
29//! `from_element` methods here. Nothing here validates that a `classCode`
30//! or `moodCode` is one of the values its vocabulary domain actually
31//! allows — see `spec/index.md` §6 for why that is future work, not a
32//! missing check.
33
34use crate::vocabulary::{Cd, Ii};
35use hl7_2_xml_lite_helper::Element;
36
37/// Something that happened, is happening, or is intended to happen.
38#[derive(Debug, Clone, PartialEq, Eq, Default)]
39pub struct Act {
40 /// What kind of act this is (`ActClass`: `OBS` observation, `PROC`
41 /// procedure, `SBADM` substance administration, ...).
42 pub class_code: String,
43 /// Where the act sits between intent and fact (`ActMood`: `EVN` it
44 /// happened, `INT` it is intended, `RQO` it is requested, ...).
45 pub mood_code: String,
46 /// This act's own identifiers.
47 pub id: Vec<Ii>,
48 /// What the act is, more specifically than `classCode` alone says.
49 pub code: Option<Cd>,
50 /// Where the act stands (`ActStatus`: `active`, `completed`,
51 /// `cancelled`, ...).
52 pub status_code: Option<Cd>,
53 /// When the act happened, happens, or is meant to happen. Carried as
54 /// the raw text HL7 v3's `TS`/`IVL<TS>` types serialize to — see
55 /// `spec/index.md` §1 for why this crate doesn't parse it further yet.
56 pub effective_time: Option<String>,
57 /// Free text describing the act, when the message carries one
58 /// alongside or instead of `code`.
59 pub text: Option<String>,
60}
61
62/// A physical thing: a person, organization, device, or place.
63#[derive(Debug, Clone, PartialEq, Eq, Default)]
64pub struct Entity {
65 /// What kind of thing this is (`EntityClass`: `PSN` person, `ORG`
66 /// organization, `DEV` device, `PLC` place, ...).
67 pub class_code: String,
68 /// Whether this element names one specific entity (`INSTANCE`) or a
69 /// kind of entity in general (`KIND`).
70 pub determiner_code: Option<String>,
71 /// This entity's own identifiers.
72 pub id: Vec<Ii>,
73 /// What kind of entity this is, more specifically than `classCode`
74 /// alone says (a device's model, an organization's type).
75 pub code: Option<Cd>,
76 /// A name for this entity, when it has one as free text (a person's or
77 /// organization's name is usually structured in real messages; this
78 /// crate reads it as text — see `spec/index.md` §1).
79 pub name: Option<String>,
80}
81
82/// A competency one [`Entity`] has with respect to another: this person as
83/// patient, that organization as provider.
84#[derive(Debug, Clone, PartialEq, Eq, Default)]
85pub struct Role {
86 /// What kind of role this is (`RoleClass`: `PAT` patient, `PROV`
87 /// provider, `ASSIGNED`, ...).
88 pub class_code: String,
89 /// This role's own identifiers — distinct from the identifiers of the
90 /// [`Entity`] playing it.
91 pub id: Vec<Ii>,
92 /// What the role is, more specifically than `classCode` alone says.
93 pub code: Option<Cd>,
94 /// Where the role stands (`RoleStatus`: `active`, `terminated`, ...).
95 pub status_code: Option<Cd>,
96 /// When this role applies, as raw text (see [`Act::effective_time`]).
97 pub effective_time: Option<String>,
98}
99
100/// Links an [`Act`] to the [`Role`] that took part in it, and how.
101#[derive(Debug, Clone, PartialEq, Eq, Default)]
102pub struct Participation {
103 /// How the role took part (`ParticipationType`: `AUT` author, `PRF`
104 /// performer, `SBJ` subject, `LOC` location, ...).
105 pub type_code: String,
106 /// When this participation applies, as raw text.
107 pub time: Option<String>,
108 /// A finer-grained statement of the role's function in this act, when
109 /// `typeCode` alone doesn't say enough.
110 pub function_code: Option<Cd>,
111}
112
113/// Links two [`Act`]s — the mechanism a single top-level act becomes a
114/// document built of sections built of entries.
115#[derive(Debug, Clone, PartialEq, Eq, Default)]
116pub struct ActRelationship {
117 /// How the two acts relate (`ActRelationshipType`: `COMP` component,
118 /// `RSON` reason, `PERT` pertains to, ...).
119 pub type_code: String,
120 /// Whether the relationship reads in the stated direction (`false`,
121 /// the default) or reversed (`true`).
122 pub inversion_ind: Option<bool>,
123}
124
125/// Links two [`Role`]s. Less common in practice than the other five
126/// backbone classes.
127#[derive(Debug, Clone, PartialEq, Eq, Default)]
128pub struct RoleLink {
129 /// How the two roles relate (`RoleLinkType`).
130 pub type_code: String,
131}
132
133impl Act {
134 /// Read an [`Act`] from its XML element: `classCode`/`moodCode`
135 /// attributes, an `id` child per identifier, a `code` child, a
136 /// `statusCode` child, an `effectiveTime` child's `value` attribute,
137 /// and a `text` child's text.
138 #[must_use]
139 pub fn from_element(element: &Element) -> Act {
140 Act {
141 class_code: element
142 .attribute("classCode")
143 .unwrap_or_default()
144 .to_string(),
145 mood_code: element
146 .attribute("moodCode")
147 .unwrap_or_default()
148 .to_string(),
149 id: element
150 .children_named("id")
151 .filter_map(Ii::from_element)
152 .collect(),
153 code: element.child("code").and_then(Cd::from_element),
154 status_code: element.child("statusCode").and_then(Cd::from_element),
155 effective_time: element
156 .child("effectiveTime")
157 .and_then(|time| time.attribute("value"))
158 .map(str::to_string),
159 text: element
160 .child("text")
161 .and_then(Element::text_opt)
162 .map(str::to_string),
163 }
164 }
165}
166
167impl Entity {
168 /// Read an [`Entity`] from its XML element, the same way
169 /// [`Act::from_element`] reads an `Act`.
170 #[must_use]
171 pub fn from_element(element: &Element) -> Entity {
172 Entity {
173 class_code: element
174 .attribute("classCode")
175 .unwrap_or_default()
176 .to_string(),
177 determiner_code: element.attribute("determinerCode").map(str::to_string),
178 id: element
179 .children_named("id")
180 .filter_map(Ii::from_element)
181 .collect(),
182 code: element.child("code").and_then(Cd::from_element),
183 name: element
184 .child("name")
185 .and_then(Element::text_opt)
186 .map(str::to_string),
187 }
188 }
189}
190
191impl Role {
192 /// Read a [`Role`] from its XML element.
193 #[must_use]
194 pub fn from_element(element: &Element) -> Role {
195 Role {
196 class_code: element
197 .attribute("classCode")
198 .unwrap_or_default()
199 .to_string(),
200 id: element
201 .children_named("id")
202 .filter_map(Ii::from_element)
203 .collect(),
204 code: element.child("code").and_then(Cd::from_element),
205 status_code: element.child("statusCode").and_then(Cd::from_element),
206 effective_time: element
207 .child("effectiveTime")
208 .and_then(|time| time.attribute("value"))
209 .map(str::to_string),
210 }
211 }
212}
213
214impl Participation {
215 /// Read a [`Participation`] from its XML element.
216 #[must_use]
217 pub fn from_element(element: &Element) -> Participation {
218 Participation {
219 type_code: element
220 .attribute("typeCode")
221 .unwrap_or_default()
222 .to_string(),
223 time: element
224 .child("time")
225 .and_then(|time| time.attribute("value"))
226 .map(str::to_string),
227 function_code: element.child("functionCode").and_then(Cd::from_element),
228 }
229 }
230}
231
232impl ActRelationship {
233 /// Read an [`ActRelationship`] from its XML element.
234 #[must_use]
235 pub fn from_element(element: &Element) -> ActRelationship {
236 ActRelationship {
237 type_code: element
238 .attribute("typeCode")
239 .unwrap_or_default()
240 .to_string(),
241 inversion_ind: element
242 .attribute("inversionInd")
243 .map(|value| value == "true"),
244 }
245 }
246}
247
248impl RoleLink {
249 /// Read a [`RoleLink`] from its XML element.
250 #[must_use]
251 pub fn from_element(element: &Element) -> RoleLink {
252 RoleLink {
253 type_code: element
254 .attribute("typeCode")
255 .unwrap_or_default()
256 .to_string(),
257 }
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn act_reads_class_mood_id_code_status_and_time() {
267 let element = hl7_2_xml_lite_helper::parse(
268 r#"<observation classCode="OBS" moodCode="EVN">
269 <id root="2.16.840.1.113883.19.5" extension="1"/>
270 <code code="8302-2" codeSystem="2.16.840.1.113883.6.1" displayName="Height"/>
271 <statusCode code="completed"/>
272 <effectiveTime value="20260101"/>
273 </observation>"#,
274 )
275 .unwrap();
276 let act = Act::from_element(&element);
277 assert_eq!(act.class_code, "OBS");
278 assert_eq!(act.mood_code, "EVN");
279 assert_eq!(act.id.len(), 1);
280 assert_eq!(act.id[0].extension.as_deref(), Some("1"));
281 assert_eq!(act.code.unwrap().code, "8302-2");
282 assert_eq!(act.status_code.unwrap().code, "completed");
283 assert_eq!(act.effective_time.as_deref(), Some("20260101"));
284 }
285
286 #[test]
287 fn act_with_no_optional_children_still_reads() {
288 let element =
289 hl7_2_xml_lite_helper::parse(r#"<act classCode="ACT" moodCode="EVN"/>"#).unwrap();
290 let act = Act::from_element(&element);
291 assert_eq!(act.id, Vec::new());
292 assert_eq!(act.code, None);
293 assert_eq!(act.status_code, None);
294 assert_eq!(act.effective_time, None);
295 assert_eq!(act.text, None);
296 }
297
298 #[test]
299 fn entity_reads_class_determiner_and_name() {
300 let element = hl7_2_xml_lite_helper::parse(
301 r#"<representedOrganization classCode="ORG" determinerCode="INSTANCE">
302 <id root="2.16.840.1.113883.19.5"/>
303 <name>Acme Clinic</name>
304 </representedOrganization>"#,
305 )
306 .unwrap();
307 let entity = Entity::from_element(&element);
308 assert_eq!(entity.class_code, "ORG");
309 assert_eq!(entity.determiner_code.as_deref(), Some("INSTANCE"));
310 assert_eq!(entity.name.as_deref(), Some("Acme Clinic"));
311 }
312
313 #[test]
314 fn role_reads_class_and_status() {
315 let element = hl7_2_xml_lite_helper::parse(
316 r#"<patient classCode="PAT">
317 <id root="2.16.840.1.113883.19.5" extension="12345"/>
318 <statusCode code="active"/>
319 </patient>"#,
320 )
321 .unwrap();
322 let role = Role::from_element(&element);
323 assert_eq!(role.class_code, "PAT");
324 assert_eq!(role.id[0].extension.as_deref(), Some("12345"));
325 assert_eq!(role.status_code.unwrap().code, "active");
326 }
327
328 #[test]
329 fn participation_reads_type_and_function() {
330 let element = hl7_2_xml_lite_helper::parse(
331 r#"<author typeCode="AUT"><time value="20260101"/></author>"#,
332 )
333 .unwrap();
334 let participation = Participation::from_element(&element);
335 assert_eq!(participation.type_code, "AUT");
336 assert_eq!(participation.time.as_deref(), Some("20260101"));
337 }
338
339 #[test]
340 fn act_relationship_reads_type_and_inversion() {
341 let element =
342 hl7_2_xml_lite_helper::parse(r#"<component typeCode="COMP" inversionInd="true"/>"#)
343 .unwrap();
344 let relationship = ActRelationship::from_element(&element);
345 assert_eq!(relationship.type_code, "COMP");
346 assert_eq!(relationship.inversion_ind, Some(true));
347 }
348
349 #[test]
350 fn role_link_reads_type() {
351 let element = hl7_2_xml_lite_helper::parse(r#"<roleLink typeCode="REPL"/>"#).unwrap();
352 assert_eq!(RoleLink::from_element(&element).type_code, "REPL");
353 }
354}