mirror of https://github.com/apache/nifi.git
NIFI-2090 This closes #669. Added options for segment names, parse fields in ExtractHL7Attributes
This commit is contained in:
parent
4a4d60e6af
commit
4179ce6644
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue