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.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.Requirement;
@ -46,11 +49,14 @@ import org.apache.nifi.stream.io.StreamUtils;
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.Composite;
import ca.uhn.hl7v2.model.Group;
import ca.uhn.hl7v2.model.Message;
import ca.uhn.hl7v2.model.Segment;
import ca.uhn.hl7v2.model.Structure;
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.EncodingCharacters;
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\".")
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()
.name("Character Encoding")
.displayName("Character Encoding")
.description("The Character Encoding that is used to encode the HL7 data")
.required(true)
.expressionLanguageSupported(true)
@ -77,6 +88,46 @@ public class ExtractHL7Attributes extends AbstractProcessor {
.defaultValue("UTF-8")
.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()
.name("success")
.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() {
final List<PropertyDescriptor> properties = new ArrayList<>();
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;
}
@ -110,6 +165,10 @@ public class ExtractHL7Attributes extends AbstractProcessor {
}
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()];
session.read(flowFile, new InputStreamCallback() {
@ -121,89 +180,172 @@ public class ExtractHL7Attributes extends AbstractProcessor {
@SuppressWarnings("resource")
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 String hl7Text = new String(buffer, charset);
final Message message;
try {
message = parser.parse(hl7Text);
final Group group = message.getParent();
final Map<String, String> attributes = new HashMap<>();
extractAttributes(group, attributes);
final Message message = parser.parse(hl7Text);
final Map<String, String> attributes = getAttributes(message, useSegmentNames, parseSegmentFields);
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});
session.transfer(flowFile, REL_SUCCESS);
} catch (final HL7Exception e) {
getLogger().error("Failed to extract attributes from {} due to {}", new Object[]{flowFile, e});
session.transfer(flowFile, REL_FAILURE);
return;
}
session.transfer(flowFile, REL_SUCCESS);
}
private void extractAttributes(final Group group, final Map<String, String> attributes) throws HL7Exception {
extractAttributes(group, attributes, new HashMap<String, Integer>());
}
private void extractAttributes(final Group group, final Map<String, String> attributes, final Map<String, Integer> segmentCounts) throws HL7Exception {
if (group.isEmpty()) {
return;
}
final String[] structureNames = group.getNames();
for (final String structName : structureNames) {
final Structure[] subStructures = group.getAll(structName);
if (group.isGroup(structName)) {
for (final Structure subStructure : subStructures) {
final Group subGroup = (Group) subStructure;
extractAttributes(subGroup, attributes, segmentCounts);
}
} else {
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);
public static Map<String, String> getAttributes(final Group group, final boolean useNames, final boolean parseFields) throws HL7Exception {
final Map<String, String> attributes = new TreeMap<>();
if (!isEmpty(group)) {
for (final Map.Entry<String, Segment> segmentEntry : getAllSegments(group).entrySet()) {
final String segmentKey = segmentEntry.getKey();
final Segment segment = segmentEntry.getValue();
final Map<String, Type> fields = getAllFields(segmentKey, segment, useNames);
for (final Map.Entry<String, Type> fieldEntry : fields.entrySet()) {
final String fieldKey = fieldEntry.getKey();
final Type field = fieldEntry.getValue();
// These maybe should used the escaped values, but that would
// change the existing non-broken behavior of the processor
if (parseFields && (field instanceof Composite) && !isTimestamp(field)) {
for (final Map.Entry<String, Type> componentEntry : getAllComponents(fieldKey, field).entrySet()) {
final String componentKey = componentEntry.getKey();
final Type component = componentEntry.getValue();
final String componentValue = HL7_ESCAPING.unescape(component.encode(), HL7_ENCODING);
if (!StringUtils.isEmpty(componentValue)) {
attributes.put(componentKey, componentValue);
}
}
} else {
segmentNum++;
segmentCounts.put(segmentName, segmentNum);
final String fieldValue = HL7_ESCAPING.unescape(field.encode(), HL7_ENCODING);
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;
}
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 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
public static void setup() {
System.setProperty("org.slf4j.simpleLogger.log.org.apache.nifi", "DEBUG");
@ -38,13 +45,6 @@ public class TestExtractHL7Attributes {
@Test
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<>();
// MSH.1 and MSH.2 could be escaped, but it's not clear which is right
expectedAttributes.put("MSH.1", "|");
@ -82,7 +82,7 @@ public class TestExtractHL7Attributes {
expectedAttributes.put("PID.19", "123456789");
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.assertAllFlowFilesTransferred(ExtractHL7Attributes.REL_SUCCESS, 1);
@ -100,6 +100,7 @@ public class TestExtractHL7Attributes {
int mshSegmentCount = 0;
int obrSegmentCount = 0;
int obxSegmentCount = 0;
int pd1SegmentCount = 0;
int pidSegmentCount = 0;
for (final Map.Entry<String, String> entry : sortedAttrs.entrySet()) {
@ -113,6 +114,9 @@ public class TestExtractHL7Attributes {
} else if (entryKey.startsWith("OBX")) {
obxSegmentCount++;
continue;
} else if (entryKey.startsWith("PD1")) {
pd1SegmentCount++;
continue;
} else if (entryKey.startsWith("PID")) {
pidSegmentCount++;
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 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 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);
}
}