NIFI-2090 This closes #669. Added options for segment names, parse fields in ExtractHL7Attributes

This commit is contained in:
Joey Frazee 2016-06-22 23:44:40 -05:00 committed by joewitt
parent 4a4d60e6af
commit 4179ce6644
2 changed files with 411 additions and 72 deletions

View File

@ -20,11 +20,14 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.nifi.annotation.behavior.InputRequirement; import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.annotation.behavior.InputRequirement.Requirement; import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
@ -46,11 +49,14 @@ import org.apache.nifi.stream.io.StreamUtils;
import ca.uhn.hl7v2.DefaultHapiContext; import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HL7Exception; import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.HapiContext; import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Composite;
import ca.uhn.hl7v2.model.Group; import ca.uhn.hl7v2.model.Group;
import ca.uhn.hl7v2.model.Message; import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.Segment; import ca.uhn.hl7v2.model.Segment;
import ca.uhn.hl7v2.model.Structure; import ca.uhn.hl7v2.model.Structure;
import ca.uhn.hl7v2.model.Type; import ca.uhn.hl7v2.model.Type;
import ca.uhn.hl7v2.model.Visitable;
import ca.uhn.hl7v2.parser.CanonicalModelClassFactory;
import ca.uhn.hl7v2.parser.DefaultEscaping; import ca.uhn.hl7v2.parser.DefaultEscaping;
import ca.uhn.hl7v2.parser.EncodingCharacters; import ca.uhn.hl7v2.parser.EncodingCharacters;
import ca.uhn.hl7v2.parser.Escaping; import ca.uhn.hl7v2.parser.Escaping;
@ -68,8 +74,13 @@ import ca.uhn.hl7v2.validation.impl.ValidationContextFactory;
+ "a value of \"2.1\" and an attribute named \"OBX_11.3\" with a value of \"93000^CPT4\".") + "a value of \"2.1\" and an attribute named \"OBX_11.3\" with a value of \"93000^CPT4\".")
public class ExtractHL7Attributes extends AbstractProcessor { public class ExtractHL7Attributes extends AbstractProcessor {
private static final EncodingCharacters HL7_ENCODING = EncodingCharacters.defaultInstance();
private static final Escaping HL7_ESCAPING = new DefaultEscaping();
public static final PropertyDescriptor CHARACTER_SET = new PropertyDescriptor.Builder() public static final PropertyDescriptor CHARACTER_SET = new PropertyDescriptor.Builder()
.name("Character Encoding") .name("Character Encoding")
.displayName("Character Encoding")
.description("The Character Encoding that is used to encode the HL7 data") .description("The Character Encoding that is used to encode the HL7 data")
.required(true) .required(true)
.expressionLanguageSupported(true) .expressionLanguageSupported(true)
@ -77,6 +88,46 @@ public class ExtractHL7Attributes extends AbstractProcessor {
.defaultValue("UTF-8") .defaultValue("UTF-8")
.build(); .build();
public static final PropertyDescriptor USE_SEGMENT_NAMES = new PropertyDescriptor.Builder()
.name("use-segment-names")
.displayName("Use Segment Names")
.description("Whether or not to use HL7 segment names in attributes")
.required(true)
.allowableValues("true", "false")
.defaultValue("false")
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
.build();
public static final PropertyDescriptor PARSE_SEGMENT_FIELDS = new PropertyDescriptor.Builder()
.name("parse-segment-fields")
.displayName("Parse Segment Fields")
.description("Whether or not to parse HL7 segment fields into attributes")
.required(true)
.allowableValues("true", "false")
.defaultValue("false")
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
.build();
public static final PropertyDescriptor SKIP_VALIDATION = new PropertyDescriptor.Builder()
.name("skip-validation")
.displayName("Skip Validation")
.description("Whether or not to validate HL7 message values")
.required(true)
.allowableValues("true", "false")
.defaultValue("true")
.addValidator(StandardValidators.BOOLEAN_VALIDATOR)
.build();
public static final PropertyDescriptor HL7_INPUT_VERSION = new PropertyDescriptor.Builder()
.name("hl7-input-version")
.displayName("HL7 Input Version")
.description("The HL7 version to use for parsing and validation")
.required(true)
.allowableValues("autodetect", "2.2", "2.3", "2.3.1", "2.4", "2.5", "2.5.1", "2.6")
.defaultValue("autodetect")
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
public static final Relationship REL_SUCCESS = new Relationship.Builder() public static final Relationship REL_SUCCESS = new Relationship.Builder()
.name("success") .name("success")
.description("A FlowFile is routed to this relationship if it is properly parsed as HL7 and its attributes extracted") .description("A FlowFile is routed to this relationship if it is properly parsed as HL7 and its attributes extracted")
@ -91,6 +142,10 @@ public class ExtractHL7Attributes extends AbstractProcessor {
protected List<PropertyDescriptor> getSupportedPropertyDescriptors() { protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
final List<PropertyDescriptor> properties = new ArrayList<>(); final List<PropertyDescriptor> properties = new ArrayList<>();
properties.add(CHARACTER_SET); properties.add(CHARACTER_SET);
properties.add(USE_SEGMENT_NAMES);
properties.add(PARSE_SEGMENT_FIELDS);
properties.add(SKIP_VALIDATION);
properties.add(HL7_INPUT_VERSION);
return properties; return properties;
} }
@ -110,6 +165,10 @@ public class ExtractHL7Attributes extends AbstractProcessor {
} }
final Charset charset = Charset.forName(context.getProperty(CHARACTER_SET).evaluateAttributeExpressions(flowFile).getValue()); final Charset charset = Charset.forName(context.getProperty(CHARACTER_SET).evaluateAttributeExpressions(flowFile).getValue());
final Boolean useSegmentNames = context.getProperty(USE_SEGMENT_NAMES).asBoolean();
final Boolean parseSegmentFields = context.getProperty(PARSE_SEGMENT_FIELDS).asBoolean();
final Boolean skipValidation = context.getProperty(SKIP_VALIDATION).asBoolean();
final String inputVersion = context.getProperty(HL7_INPUT_VERSION).getValue();
final byte[] buffer = new byte[(int) flowFile.getSize()]; final byte[] buffer = new byte[(int) flowFile.getSize()];
session.read(flowFile, new InputStreamCallback() { session.read(flowFile, new InputStreamCallback() {
@ -121,89 +180,172 @@ public class ExtractHL7Attributes extends AbstractProcessor {
@SuppressWarnings("resource") @SuppressWarnings("resource")
final HapiContext hapiContext = new DefaultHapiContext(); final HapiContext hapiContext = new DefaultHapiContext();
hapiContext.setValidationContext((ValidationContext) ValidationContextFactory.noValidation()); if (!inputVersion.equals("autodetect")) {
hapiContext.setModelClassFactory(new CanonicalModelClassFactory(inputVersion));
}
if (skipValidation) {
hapiContext.setValidationContext((ValidationContext) ValidationContextFactory.noValidation());
}
final PipeParser parser = hapiContext.getPipeParser(); final PipeParser parser = hapiContext.getPipeParser();
final String hl7Text = new String(buffer, charset); final String hl7Text = new String(buffer, charset);
final Message message;
try { try {
message = parser.parse(hl7Text); final Message message = parser.parse(hl7Text);
final Group group = message.getParent(); final Map<String, String> attributes = getAttributes(message, useSegmentNames, parseSegmentFields);
final Map<String, String> attributes = new HashMap<>();
extractAttributes(group, attributes);
flowFile = session.putAllAttributes(flowFile, attributes); flowFile = session.putAllAttributes(flowFile, attributes);
getLogger().info("Successfully extracted {} attributes for {}; routing to success", new Object[]{attributes.size(), flowFile});
getLogger().debug("Added the following attributes for {}: {}", new Object[]{flowFile, attributes}); getLogger().debug("Added the following attributes for {}: {}", new Object[]{flowFile, attributes});
session.transfer(flowFile, REL_SUCCESS);
} catch (final HL7Exception e) { } catch (final HL7Exception e) {
getLogger().error("Failed to extract attributes from {} due to {}", new Object[]{flowFile, e}); getLogger().error("Failed to extract attributes from {} due to {}", new Object[]{flowFile, e});
session.transfer(flowFile, REL_FAILURE); session.transfer(flowFile, REL_FAILURE);
return; return;
} }
session.transfer(flowFile, REL_SUCCESS);
} }
private void extractAttributes(final Group group, final Map<String, String> attributes) throws HL7Exception { public static Map<String, String> getAttributes(final Group group, final boolean useNames, final boolean parseFields) throws HL7Exception {
extractAttributes(group, attributes, new HashMap<String, Integer>()); final Map<String, String> attributes = new TreeMap<>();
} if (!isEmpty(group)) {
for (final Map.Entry<String, Segment> segmentEntry : getAllSegments(group).entrySet()) {
private void extractAttributes(final Group group, final Map<String, String> attributes, final Map<String, Integer> segmentCounts) throws HL7Exception { final String segmentKey = segmentEntry.getKey();
if (group.isEmpty()) { final Segment segment = segmentEntry.getValue();
return; final Map<String, Type> fields = getAllFields(segmentKey, segment, useNames);
} for (final Map.Entry<String, Type> fieldEntry : fields.entrySet()) {
final String fieldKey = fieldEntry.getKey();
final String[] structureNames = group.getNames(); final Type field = fieldEntry.getValue();
for (final String structName : structureNames) { // These maybe should used the escaped values, but that would
final Structure[] subStructures = group.getAll(structName); // change the existing non-broken behavior of the processor
if (parseFields && (field instanceof Composite) && !isTimestamp(field)) {
if (group.isGroup(structName)) { for (final Map.Entry<String, Type> componentEntry : getAllComponents(fieldKey, field).entrySet()) {
for (final Structure subStructure : subStructures) { final String componentKey = componentEntry.getKey();
final Group subGroup = (Group) subStructure; final Type component = componentEntry.getValue();
extractAttributes(subGroup, attributes, segmentCounts); final String componentValue = HL7_ESCAPING.unescape(component.encode(), HL7_ENCODING);
} if (!StringUtils.isEmpty(componentValue)) {
} else { attributes.put(componentKey, componentValue);
for (final Structure structure : subStructures) { }
final Segment segment = (Segment) structure; }
final String segmentName = segment.getName();
Integer segmentNum = segmentCounts.get(segmentName);
if (segmentNum == null) {
segmentNum = 1;
segmentCounts.put(segmentName, 1);
} else { } else {
segmentNum++; final String fieldValue = HL7_ESCAPING.unescape(field.encode(), HL7_ENCODING);
segmentCounts.put(segmentName, segmentNum); if (!StringUtils.isEmpty(fieldValue)) {
attributes.put(fieldKey, fieldValue);
}
} }
final boolean segmentRepeating = segment.getParent().isRepeating(segment.getName());
final boolean parentRepeating = (segment.getParent().getParent() != segment.getParent() && segment.getParent().getParent().isRepeating(segment.getParent().getName()));
final boolean useSegmentIndex = segmentRepeating || parentRepeating;
final Map<String, String> attributeMap = getAttributes(segment, useSegmentIndex ? segmentNum : null);
attributes.putAll(attributeMap);
} }
} }
} }
}
private Map<String, String> getAttributes(final Segment segment, final Integer segmentNum) throws HL7Exception {
final Map<String, String> attributes = new HashMap<>();
final EncodingCharacters encoding = EncodingCharacters.defaultInstance();
final Escaping escaping = new DefaultEscaping();
for (int i = 1; i <= segment.numFields(); i++) {
final String fieldName = segment.getName() + (segmentNum == null ? "" : "_" + segmentNum) + "." + i;
final Type[] types = segment.getField(i);
for (final Type type : types) {
// This maybe should used the escaped values, but that would
// change the existing non-broken behavior of the processor
final String escapedTypeValue = type.encode();
final String unescapedTypeValue = escaping.unescape(escapedTypeValue, encoding);
attributes.put(fieldName, unescapedTypeValue);
}
}
return attributes; return attributes;
} }
private static Map<String, Segment> getAllSegments(final Group group) throws HL7Exception {
final Map<String, Segment> segments = new TreeMap<>();
addSegments(group, segments);
return segments;
}
private static void addSegments(final Group group, final Map<String, Segment> segments) throws HL7Exception {
if (!isEmpty(group)) {
for (final String name : group.getNames()) {
for (final Structure structure : group.getAll(name)) {
if (group.isGroup(name) && structure instanceof Group) {
addSegments((Group) structure, segments);
} else if (structure instanceof Segment) {
addSegments((Segment) structure, segments);
}
}
}
}
}
private static void addSegments(final Segment segment, final Map<String, Segment> segments) throws HL7Exception {
if (!isEmpty(segment)) {
final StringBuilder sb = new StringBuilder().append(segment.getName());
if (isRepeating(segment)) {
final Type field = segment.getField(1, 0);
if (!isEmpty(field)) {
final String fieldValue = field.encode();
final int segmentIndex = StringUtils.isEmpty(fieldValue) ? 1 : Integer.parseInt(fieldValue);
sb.append("_").append(segmentIndex);
}
}
final String segmentKey = sb.toString();
segments.put(segmentKey, segment);
}
}
private static Map<String, Type> getAllFields(final String segmentKey, final Segment segment, final boolean useNames) throws HL7Exception {
final Map<String, Type> fields = new TreeMap<>();
final String[] segmentNames = segment.getNames();
for (int i = 1; i <= segment.numFields(); i++) {
final Type field = segment.getField(i, 0);
if (!isEmpty(field)) {
final String fieldName;
if (useNames) {
fieldName = WordUtils.capitalize(segmentNames[i-1]).replaceAll("\\W+", "");
} else {
fieldName = String.valueOf(i);
}
final String fieldKey = new StringBuilder()
.append(segmentKey)
.append(".")
.append(fieldName)
.toString();
fields.put(fieldKey, field);
}
}
return fields;
}
private static Map<String, Type> getAllComponents(final String fieldKey, final Type field) throws HL7Exception {
final Map<String, Type> components = new TreeMap<>();
if (!isEmpty(field) && (field instanceof Composite)) {
final Type[] types = ((Composite) field).getComponents();
for (int i = 0; i < types.length; i++) {
final Type type = types[i];
if (!isEmpty(type)) {
String fieldName = field.getName();
if (fieldName.equals("CM_MSG")) {
fieldName = "CM";
}
final String typeKey = new StringBuilder()
.append(fieldKey)
.append(".")
.append(fieldName)
.append(".")
.append(i+1)
.toString();
components.put(typeKey, type);
}
}
}
return components;
}
private static boolean isTimestamp(final Type field) throws HL7Exception {
if (isEmpty(field)) {
return false;
}
final String fieldName = field.getName();
return (fieldName.equals("TS") || fieldName.equals("DT") || fieldName.equals("TM"));
}
private static boolean isRepeating(final Segment segment) throws HL7Exception {
if (isEmpty(segment)) {
return false;
}
final Group parent = segment.getParent();
final Group grandparent = parent.getParent();
if (parent == grandparent) {
return false;
}
return grandparent.isRepeating(parent.getName());
}
private static boolean isEmpty(final Visitable visitable) throws HL7Exception {
return (visitable == null || visitable.isEmpty());
}
} }

View File

@ -31,6 +31,13 @@ import java.util.TreeMap;
public class TestExtractHL7Attributes { public class TestExtractHL7Attributes {
public static final String TEST_INPUT_RECORD =
"MSH|^~\\&|XXXXXXXX||HealthProvider||||ORU^R01|Q1111111111111111111|P|2.3|\r\n" +
"PID|||12345^^^XYZ^MR||SMITH^JOHN||19700100|M||||||||||111111111111|123456789|\r\n" +
"PD1||||1234567890^LAST^FIRST^M^^^^^NPI|\r\n" +
"OBR|1|341856649^HNAM_ORDERID|000000000000000000|648088^Basic Metabolic Panel|||20150101000000|||||||||1620^Johnson^Corey^A||||||20150101000000|||F|||||||||||20150101000000|\r\n" +
"OBX|1|NM|GLU^Glucose Lvl|59|mg/dL|65-99^65^99|L|||F|||20150102000000|\r\n";
@BeforeClass @BeforeClass
public static void setup() { public static void setup() {
System.setProperty("org.slf4j.simpleLogger.log.org.apache.nifi", "DEBUG"); System.setProperty("org.slf4j.simpleLogger.log.org.apache.nifi", "DEBUG");
@ -38,13 +45,6 @@ public class TestExtractHL7Attributes {
@Test @Test
public void testExtract() throws IOException { public void testExtract() throws IOException {
final String inputRecord =
"MSH|^~\\&|XXXXXXXX||HealthProvider||||ORU^R01|Q1111111111111111111|P|2.3|\r\n" +
"PID|||12345^^^XYZ^MR||SMITH^JOHN||19700100|M||||||||||111111111111|123456789|\r\n" +
"PD1||||1234567890^LAST^FIRST^M^^^^^NPI|\r\n" +
"OBR|1|341856649^HNAM_ORDERID|000000000000000000|648088^Basic Metabolic Panel|||20150101000000|||||||||1620^Johnson^Corey^A||||||20150101000000|||F|||||||||||20150101000000|\r\n" +
"OBX|1|NM|GLU^Glucose Lvl|59|mg/dL|65-99^65^99|L|||F|||20150102000000|\r\n";
final SortedMap<String, String> expectedAttributes = new TreeMap<>(); final SortedMap<String, String> expectedAttributes = new TreeMap<>();
// MSH.1 and MSH.2 could be escaped, but it's not clear which is right // MSH.1 and MSH.2 could be escaped, but it's not clear which is right
expectedAttributes.put("MSH.1", "|"); expectedAttributes.put("MSH.1", "|");
@ -82,7 +82,7 @@ public class TestExtractHL7Attributes {
expectedAttributes.put("PID.19", "123456789"); expectedAttributes.put("PID.19", "123456789");
final TestRunner runner = TestRunners.newTestRunner(ExtractHL7Attributes.class); final TestRunner runner = TestRunners.newTestRunner(ExtractHL7Attributes.class);
runner.enqueue(inputRecord.getBytes(StandardCharsets.UTF_8)); runner.enqueue(TEST_INPUT_RECORD.getBytes(StandardCharsets.UTF_8));
runner.run(); runner.run();
runner.assertAllFlowFilesTransferred(ExtractHL7Attributes.REL_SUCCESS, 1); runner.assertAllFlowFilesTransferred(ExtractHL7Attributes.REL_SUCCESS, 1);
@ -100,6 +100,7 @@ public class TestExtractHL7Attributes {
int mshSegmentCount = 0; int mshSegmentCount = 0;
int obrSegmentCount = 0; int obrSegmentCount = 0;
int obxSegmentCount = 0; int obxSegmentCount = 0;
int pd1SegmentCount = 0;
int pidSegmentCount = 0; int pidSegmentCount = 0;
for (final Map.Entry<String, String> entry : sortedAttrs.entrySet()) { for (final Map.Entry<String, String> entry : sortedAttrs.entrySet()) {
@ -113,6 +114,9 @@ public class TestExtractHL7Attributes {
} else if (entryKey.startsWith("OBX")) { } else if (entryKey.startsWith("OBX")) {
obxSegmentCount++; obxSegmentCount++;
continue; continue;
} else if (entryKey.startsWith("PD1")) {
pd1SegmentCount++;
continue;
} else if (entryKey.startsWith("PID")) { } else if (entryKey.startsWith("PID")) {
pidSegmentCount++; pidSegmentCount++;
continue; continue;
@ -122,6 +126,199 @@ public class TestExtractHL7Attributes {
Assert.assertEquals("Did not have the proper number of MSH segments", 8, mshSegmentCount); Assert.assertEquals("Did not have the proper number of MSH segments", 8, mshSegmentCount);
Assert.assertEquals("Did not have the proper number of OBR segments", 9, obrSegmentCount); Assert.assertEquals("Did not have the proper number of OBR segments", 9, obrSegmentCount);
Assert.assertEquals("Did not have the proper number of OBX segments", 9, obxSegmentCount); Assert.assertEquals("Did not have the proper number of OBX segments", 9, obxSegmentCount);
Assert.assertEquals("Did not have the proper number of PD1 segments", 1, pd1SegmentCount);
Assert.assertEquals("Did not have the proper number of PID segments", 6, pidSegmentCount); Assert.assertEquals("Did not have the proper number of PID segments", 6, pidSegmentCount);
} }
@Test
public void testExtractWithSegmentNames() throws IOException {
final SortedMap<String, String> expectedAttributes = new TreeMap<>();
expectedAttributes.put("MSH.FieldSeparator", "|");
expectedAttributes.put("MSH.EncodingCharacters", "^~\\&");
expectedAttributes.put("MSH.SendingApplication", "XXXXXXXX");
expectedAttributes.put("MSH.ReceivingApplication", "HealthProvider");
expectedAttributes.put("MSH.MessageType", "ORU^R01");
expectedAttributes.put("MSH.MessageControlID", "Q1111111111111111111");
expectedAttributes.put("MSH.ProcessingID", "P");
expectedAttributes.put("MSH.VersionID", "2.3");
expectedAttributes.put("OBR_1.SetIDObservationRequest", "1");
expectedAttributes.put("OBR_1.PlacerOrderNumber", "341856649^HNAM_ORDERID");
expectedAttributes.put("OBR_1.FillerOrderNumber", "000000000000000000");
expectedAttributes.put("OBR_1.UniversalServiceIdentifier", "648088^Basic Metabolic Panel");
expectedAttributes.put("OBR_1.ObservationDateTime", "20150101000000");
expectedAttributes.put("OBR_1.OrderingProvider", "1620^Johnson^Corey^A");
expectedAttributes.put("OBR_1.ResultsRptStatusChngDateTime", "20150101000000");
expectedAttributes.put("OBR_1.ResultStatus", "F");
expectedAttributes.put("OBR_1.ScheduledDateTime", "20150101000000");
expectedAttributes.put("OBX_1.SetIDOBX", "1");
expectedAttributes.put("OBX_1.ValueType", "NM");
expectedAttributes.put("OBX_1.ObservationIdentifier", "GLU^Glucose Lvl");
expectedAttributes.put("OBX_1.ObservationSubID", "59");
expectedAttributes.put("OBX_1.ObservationValue", "mg/dL");
expectedAttributes.put("OBX_1.Units", "65-99^65^99");
expectedAttributes.put("OBX_1.ReferencesRange", "L");
expectedAttributes.put("OBX_1.NatureOfAbnormalTest", "F");
expectedAttributes.put("OBX_1.UserDefinedAccessChecks", "20150102000000");
expectedAttributes.put("PD1.PatientPrimaryCareProviderNameIDNo", "1234567890^LAST^FIRST^M^^^^^NPI");
expectedAttributes.put("PID.PatientIDInternalID", "12345^^^XYZ^MR");
expectedAttributes.put("PID.PatientName", "SMITH^JOHN");
expectedAttributes.put("PID.DateOfBirth", "19700100");
expectedAttributes.put("PID.Sex", "M");
expectedAttributes.put("PID.PatientAccountNumber", "111111111111");
expectedAttributes.put("PID.SSNNumberPatient", "123456789");
final TestRunner runner = TestRunners.newTestRunner(ExtractHL7Attributes.class);
runner.setProperty(ExtractHL7Attributes.USE_SEGMENT_NAMES, "true");
runner.enqueue(TEST_INPUT_RECORD.getBytes(StandardCharsets.UTF_8));
runner.run();
runner.assertAllFlowFilesTransferred(ExtractHL7Attributes.REL_SUCCESS, 1);
final MockFlowFile out = runner.getFlowFilesForRelationship(ExtractHL7Attributes.REL_SUCCESS).get(0);
final SortedMap<String, String> sortedAttrs = new TreeMap<>(out.getAttributes());
for (final Map.Entry<String, String> entry : expectedAttributes.entrySet()) {
final String key = entry.getKey();
final String expected = entry.getValue();
final String actual = sortedAttrs.get(key);
Assert.assertEquals(key + " segment values do not match", expected, actual);
}
int mshSegmentCount = 0;
int obrSegmentCount = 0;
int obxSegmentCount = 0;
int pd1SegmentCount = 0;
int pidSegmentCount = 0;
for (final Map.Entry<String, String> entry : sortedAttrs.entrySet()) {
final String entryKey = entry.getKey();
if (entryKey.startsWith("MSH")) {
mshSegmentCount++;
continue;
} else if (entryKey.startsWith("OBR")) {
obrSegmentCount++;
continue;
} else if (entryKey.startsWith("OBX")) {
obxSegmentCount++;
continue;
} else if (entryKey.startsWith("PD1")) {
pd1SegmentCount++;
continue;
} else if (entryKey.startsWith("PID")) {
pidSegmentCount++;
continue;
}
}
Assert.assertEquals("Did not have the proper number of MSH segments", 8, mshSegmentCount);
Assert.assertEquals("Did not have the proper number of OBR segments", 9, obrSegmentCount);
Assert.assertEquals("Did not have the proper number of OBX segments", 9, obxSegmentCount);
Assert.assertEquals("Did not have the proper number of PD1 segments", 1, pd1SegmentCount);
Assert.assertEquals("Did not have the proper number of PID segments", 6, pidSegmentCount);
}
@Test
public void testExtractWithSegmentNamesAndFields() throws IOException {
final SortedMap<String, String> expectedAttributes = new TreeMap<>();
expectedAttributes.put("MSH.FieldSeparator", "|");
expectedAttributes.put("MSH.EncodingCharacters", "^~\\&");
expectedAttributes.put("MSH.SendingApplication.HD.1", "XXXXXXXX");
expectedAttributes.put("MSH.ReceivingApplication.HD.1", "HealthProvider");
expectedAttributes.put("MSH.MessageType.CM.1", "ORU");
expectedAttributes.put("MSH.MessageType.CM.2", "R01");
expectedAttributes.put("MSH.MessageControlID", "Q1111111111111111111");
expectedAttributes.put("MSH.ProcessingID.PT.1", "P");
expectedAttributes.put("MSH.VersionID", "2.3");
expectedAttributes.put("OBR_1.SetIDObservationRequest", "1");
expectedAttributes.put("OBR_1.PlacerOrderNumber.EI.1", "341856649");
expectedAttributes.put("OBR_1.PlacerOrderNumber.EI.2", "HNAM_ORDERID");
expectedAttributes.put("OBR_1.FillerOrderNumber.EI.1", "000000000000000000");
expectedAttributes.put("OBR_1.UniversalServiceIdentifier.CE.1", "648088");
expectedAttributes.put("OBR_1.UniversalServiceIdentifier.CE.2", "Basic Metabolic Panel");
expectedAttributes.put("OBR_1.ObservationDateTime", "20150101000000");
expectedAttributes.put("OBR_1.OrderingProvider.XCN.1", "1620");
expectedAttributes.put("OBR_1.OrderingProvider.XCN.2", "Johnson");
expectedAttributes.put("OBR_1.OrderingProvider.XCN.3", "Corey");
expectedAttributes.put("OBR_1.OrderingProvider.XCN.4", "A");
expectedAttributes.put("OBR_1.ResultsRptStatusChngDateTime", "20150101000000");
expectedAttributes.put("OBR_1.ResultStatus", "F");
expectedAttributes.put("OBR_1.ScheduledDateTime", "20150101000000");
expectedAttributes.put("OBX_1.SetIDOBX", "1");
expectedAttributes.put("OBX_1.ValueType", "NM");
expectedAttributes.put("OBX_1.ObservationIdentifier.CE.1", "GLU");
expectedAttributes.put("OBX_1.ObservationIdentifier.CE.2", "Glucose Lvl");
expectedAttributes.put("OBX_1.ObservationSubID", "59");
expectedAttributes.put("OBX_1.ObservationValue", "mg/dL");
expectedAttributes.put("OBX_1.Units.CE.1", "65-99");
expectedAttributes.put("OBX_1.Units.CE.3", "65");
expectedAttributes.put("OBX_1.Units.CE.3", "99");
expectedAttributes.put("OBX_1.ReferencesRange", "L");
expectedAttributes.put("OBX_1.NatureOfAbnormalTest", "F");
expectedAttributes.put("OBX_1.UserDefinedAccessChecks", "20150102000000");
expectedAttributes.put("PD1.PatientPrimaryCareProviderNameIDNo.XCN.1", "1234567890");
expectedAttributes.put("PD1.PatientPrimaryCareProviderNameIDNo.XCN.2", "LAST");
expectedAttributes.put("PD1.PatientPrimaryCareProviderNameIDNo.XCN.3", "FIRST");
expectedAttributes.put("PD1.PatientPrimaryCareProviderNameIDNo.XCN.4", "M");
expectedAttributes.put("PD1.PatientPrimaryCareProviderNameIDNo.XCN.9", "NPI");
expectedAttributes.put("PID.PatientIDInternalID.CX.1", "12345");
expectedAttributes.put("PID.PatientIDInternalID.CX.4", "XYZ");
expectedAttributes.put("PID.PatientIDInternalID.CX.5", "MR");
expectedAttributes.put("PID.PatientName.XPN.1", "SMITH");
expectedAttributes.put("PID.PatientName.XPN.2", "JOHN");
expectedAttributes.put("PID.DateOfBirth", "19700100");
expectedAttributes.put("PID.Sex", "M");
expectedAttributes.put("PID.PatientAccountNumber.CX.1", "111111111111");
expectedAttributes.put("PID.SSNNumberPatient", "123456789");
final TestRunner runner = TestRunners.newTestRunner(ExtractHL7Attributes.class);
runner.setProperty(ExtractHL7Attributes.USE_SEGMENT_NAMES, "true");
runner.setProperty(ExtractHL7Attributes.PARSE_SEGMENT_FIELDS, "true");
runner.enqueue(TEST_INPUT_RECORD.getBytes(StandardCharsets.UTF_8));
runner.run();
runner.assertAllFlowFilesTransferred(ExtractHL7Attributes.REL_SUCCESS, 1);
final MockFlowFile out = runner.getFlowFilesForRelationship(ExtractHL7Attributes.REL_SUCCESS).get(0);
final SortedMap<String, String> sortedAttrs = new TreeMap<>(out.getAttributes());
for (final Map.Entry<String, String> entry : expectedAttributes.entrySet()) {
final String key = entry.getKey();
final String expected = entry.getValue();
final String actual = sortedAttrs.get(key);
Assert.assertEquals(key + " segment values do not match", expected, actual);
}
int mshSegmentCount = 0;
int obrSegmentCount = 0;
int obxSegmentCount = 0;
int pd1SegmentCount = 0;
int pidSegmentCount = 0;
for (final Map.Entry<String, String> entry : sortedAttrs.entrySet()) {
final String entryKey = entry.getKey();
if (entryKey.startsWith("MSH")) {
mshSegmentCount++;
continue;
} else if (entryKey.startsWith("OBR")) {
obrSegmentCount++;
continue;
} else if (entryKey.startsWith("OBX")) {
obxSegmentCount++;
continue;
} else if (entryKey.startsWith("PD1")) {
pd1SegmentCount++;
continue;
} else if (entryKey.startsWith("PID")) {
pidSegmentCount++;
continue;
}
}
Assert.assertEquals("Did not have the proper number of MSH segments", 9, mshSegmentCount);
Assert.assertEquals("Did not have the proper number of OBR segments", 14, obrSegmentCount);
Assert.assertEquals("Did not have the proper number of OBX segments", 12, obxSegmentCount);
Assert.assertEquals("Did not have the proper number of PD1 segments", 5, pd1SegmentCount);
Assert.assertEquals("Did not have the proper number of PID segments", 9, pidSegmentCount);
}
} }