The tilde character ("~") has special meaning in HL7; it is normally used as the field repetition character. If it is being included in a field value as a literal character, it is often converted to an "escape sequence" so that it can be delivered to a downstream system intact rather than interpreted as a delimiter; the escape sequence for the field repetition character is \R\.

Is your intended use of the ~ character to function as a repetition delimiter, or is it actually a verbatim part of a field value?

Are you inserting these values into an EnsLib.HL7.Message object? If yes, the standard way of doing this is to first make sure the SPM field you're working with is defined as repeating in the message schema (DocType), then iterate through your list of field values to be inserted.

Assuming your message object is tMsg:

Do tMsg.SetValueAt(Modifier1,"SPM:9(1)")
Do tMsg.SetValueAt(Modifier2,"SPM:9(2)")
Do tMsg.SetValueAt(Modifier3,"SPM:9(3)")

This will automatically use the repetition separator to delimit the values in SPM field 9. Note that the path supplied for the SPM segment/field will vary based on your specific schema definition.

If it's an ObjectScript process, you would use ..SendRequestSync() or ..SendRequestAsync(), with newrequest as the request argument and the name of the downstream process or operation as the target argument. Whether you would send it asynchronously or synchronously would depend on your needs. For healthcare integration, synchronous is generally required as it maintains FIFO.

You can't modify an inbound message from a service as it will have the immutable property set; that's why you have to clone it to make changes.

What class did you use for the inbound service?

I created a small batch of HL7 messages using nothing but newlines (newline is \n ... carriage return is actually \r). I consumed that file using a service based on EnsLib.HL7.Service.FileService and they were processed correctly.

If you used one of the HL7 service classes, what did you select for Framing?

Finally, is it possible that the messages you're testing with contain an actual backslash (\) character followed by the letter 'n' separating the segments? I've seen this happen before, believe it or not ...

I'm assuming you're working with Ensemble (or Interoperability as it's known in the most recent IRIS-based versions) ...

If you create a class that extends Ens.Rule.FunctionSet, not only will you be able to call its methods/functions from the DTL editor, they will appear as selectable functions in the drop-down list.

Your syntax for the match argument is wrong. You need quantifiers for the literal strings: 1P4N1":F"5N1"R"1P. This is not obvious from the documentation ... I only discovered it through experimentation.

It appears as though the expression editor expects the pattern to be a quoted string, so you'll probably need to follow the syntax for quoting strings that contain quote characters: "1P4N1"":F""5N1""R""1P"

I'm thinking that the value you provided for ElementName  in the service's configuration is incorrect. Your class definition also needs to match the structure of the repeating element, not the entire XML document.

Here's an example XML structure:

<Persons>
    <Person>
        <LastName>Drumm</LastName>
        <FirstName>Jeff</FirstName>
        <FavoriteColor>Red</FavoriteColor>
    </Person>
    <Person>
        <LastName>Herlick</LastName>
        <FirstName>Blakely</FirstName>
        <FavoriteColor>Teal</FavoriteColor>
    </Person>
</Persons>

The classname you'd create would be something like User.Custom.Person, with properties FirstName, LastName and FavoriteColor.

In the service's ElementName field, you'd enter Person. When the file is read, each Person element from the XML would end up in a separate message. You can then filter in the routing rule by using the variables Document.FirstName, Document.LastName, etc. and transform it in the DTL by selecting the Persistent Class User.Custom.Person as the source and your HL7 schema as the target.

Make sense?

Hi Blake,

Can you provide a bit more detail? If you need to create an individual HL7 message for each repeating element in the XML document, my answer here is probably the easiest way to get there. If there's a "master/detail" relationship within the XML, though, you'll need to handle that in a BPL, and perhaps still "chunk" the XML before handing it off to the BPL if there are multiple elements with master/detail relationships. You'd do that with the XML Object file service mentioned in the link (assuming you're getting these XML documents as local files ... there's also an FTP version of the service).

There are tutorials on BPLs in ISC's Learning library.

Ok ... so this actually works:

ClassMethod GetHL7Msg(pId As %String) As %Stream.GlobalCharacter [ SqlName = GetMsg, SqlProc ]
{
    Set tHl7 = ##class(EnsLib.HL7.Message).%OpenId(pId,,.tSC)
    Throw:$$$ISERR(tSC) ..GetErr(-400, "HL7 Message with ID "_pId_" Not Found.")
    Set tMsg = ##class(%Stream.GlobalCharacter).%New()
    Set tSC = tHl7.OutputToLibraryStream(.tMsg)
    Do tHl7.%Close()
    If tSC Set tSC=tMsg.%Save()
    Throw:$$$ISERR(tSC) ..GetErr(-400, $system.Status.GetErrorText(tSC))
    Return tMsg."%%OID"
}

I'm concerned that I'm using a persisted object. Does it automatically get killed once I leave the scope of the function (I'm assuming not, see the last paragraph)? Is there a way to force it to temp storage?

It's also very slow, but 1) I'm currently connecting to the database over a VPN connection, and 2) the average message size is around 30KB, with some messages up to 3MB. It took 5 minutes to return 1000 rows.

Looks like ^CacheStream is currently using almost 600MB.

No errors running the query from $system.SQL.Shell(), but I get the stream OREF rather than the message in the query result:

ID      SourceConfigName        TargetConfigName        Message
191344  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191348  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191352  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191356  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191360  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191364  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191368  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191372  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191376  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191380  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter

ODBC seems to be doing a little magic in the background to return the stream data, but it's not good magic at the moment.

EDIT: Turns out the ODBC wrangler needs a persistent object's "%%OID" rather than an OREF to fetch the stream. See the eventual working method here.