The field definitions are properties of the *.Record class, so you could perform a property query against %Dictionary.Properties using the *.Record class as the parent.

SELECT Name
FROM %Dictionary.PropertyDefinition
WHERE parent = 'OSUMC.RecordMap.Patient.Record' AND Name >='A' AND Name <= 'z'
ORDER BY SequenceNumber ASC

That would get you the field names in the same order as the data and exclude any percent properties.

Sorry Scott, nothing so straightforward as that 😉

When you create a RecordMap, you usually create up to 3 classes, depending on whether or not you're using a Batch Class.

So you'll have something like:

  • OSUMC.RecordMap.Patient (the "template")
  • OSUMC.RecordMap.Patient.Record (the actual record class)
  • OSUMC.RecordMap.Patient.Batch (if you're using batch class)

If the RecordMap is the source object in your DTL, it should be an instance of OSUMC.RecordMap.Patient.Record and will be the first argument in the method below.

You'll need to create an instance of OSUMC.RecordMap.Patient with %New(), and pass it as the second argument.

Class HICG.Util.RecordMap [ Abstract ]
{
ClassMethod GetRecordAsString(pRec As %Persistent, pTmpl As %RegisteredObject) As %String
{
    Set tStream = ##class(%Stream.TmpCharacter).%New()
    Set tIOStream = ##class(%IO.MetaCharacterStream).%New(tStream)
    Set tSC = pTmpl.PutObject(tIOStream,pRec)
    If $$$ISOK(tSC)
    {
        Do tStream.Rewind()
        Return tStream.Read(tStream.Size)
    }
    // Empty string if PutObject fails *shrug*
    Return ""
}
}

In the DTL:

The value in tRecStr should be the formatted record.

Or you could write a custom Business Process to do it. Here's an example with inadequate error processing (😉) that should give you some ideas:

/// Business Process to Modify the MSH:7 field
Class HICG.Sample.SetMSHDate Extends Ens.BusinessProcess [ ClassType = persistent ]
{
/// Downstream processes or operations to send messages to
Property TargetConfigNames As %String(MAXLEN = 1000);

Parameter SETTINGS = "TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";

/// Clone, modify and send the message downstream
Method OnRequest(pRequest As Ens.Request, Output pResponse As Ens.Response) As %Status
{
  Set tClone = pRequest.%ConstructClone()
  Set tCurDtTm = ##class(Ens.Rule.FunctionSet).CurrentDateTime("%Y%m%d%H%M%S")
  Do tClone.SetValueAt(tCurDtTm,"MSH:7")
  Do tClone.%Save()
  For i=1:1:$LENGTH(..TargetConfigNames,",") 
  {
    Set tSC = ..SendRequestAsync($PIECE(..TargetConfigNames,",",i),tClone,0)
    Return:$$$ISERR(tSC) tSC
  }
  Return $$$OK
}

/// Return an array of connections for drawing lines on the config diagram
ClassMethod OnGetConnections(Output pArray As %String, pItem As Ens.Config.Item)
{
    Do ##super(.pArray,pItem)
    If pItem.GetModifiedSetting("TargetConfigNames",.tValue) {
        For i=1:1:$LENGTH(tValue,",") { Set tOne=$ZSTRIP($P(tValue,",",i),"<>W")  Continue:""=tOne  Set pArray(tOne)="" }
    }
}
}

The reason you need to clone the inbound message is that Start-of-Session messages are immutable. You must clone them, modify the clone and send it.

The code block action in a DTL is for writing arbitrary ObjectScript, not Javascript. It's commonly used for for data manipulation that can't be satisfied by the methods available in the FunctionSet; for example, extracting and decoding a base64-encoded PDF from an OBX:5.5 field and writing it to a file. It can also be used to interact with globals to maintain state between invocations of the DTL, or perform a database lookup, or even write values to the default device that will display in the Test tool. Very useful for debugging.

I would not recommend using it for operations that could potentially block. There's no built-in mechanism for setting a timeout so use a BPL for those cases.

I wrote a quick classmethod in my custom FunctionSet class to test your observation and found that I can use the full mnemonic property path name, for example:

ClassMethod GetControlID(pMsg As EnsLib.HL7.Message) As %String
{
    // Also works with "MSH:10"
    Return pMsg.GetValueAt("MSH:MessageControlID")
}

Example from a rule (I used Document, but HL7 also works):

And the resulting trace from the Visual Trace:

I'm thinking that your inbound messages might not have the DocCategory (ex. "2.3.1") and DocName (ex. "ADT_A01") properties set ... ?

Looking through the HL7 2.5 OML_O21 structure as supplied by InterSystems, you'll find that there's a nested PIDgrpgrp() under ORCgrp().OBRgrp() that has a subordinate ORCgrp(). It looks like the parse is attempting to match on the required OBR segment in the nested PIDgrpgrp().ORCgrp().

You have a couple of options ... both of which require a custom schema to match your message. The first is to make the OBR segment in the PIDGrpgrp().ORCgrp() optional; the second is to remove the PIDgrpgrp() grouping entirely in the custom schema.

EDIT: The first option doesn't work since the ORC matches on the optional ORC segment in the nested PIDgrpgrp.ORCgrp(), which makes it attempt to match on the required PIDgrpgrp.ORCgrp().OBXgrp().

The NTE segment is commonly used for plain-text "notes" containing descriptive data that doesn't conform to the structured format used for most other data in HL7 messages. It could certainly be used for the storage of base64-encoded binary data, and the likely candidate for that would be the NTE:3 Comment field.

If the base64 content is less than ~3.5MB in size, it can be assigned directly to that field in a DTL or using the SetValueAt() method of the message object (Note: the HL7 specification does not define a maximum length for the Comment field, but there are considerations for IRIS storage that need to be handled appropriately). If larger than 3.5MB, it must be stored using the StoreFieldStreamRaw() method of the EnsLib.HL7.Message object (there is also a StoreFieldStreamBase64() method but it's not applicable if your data is already in base64 format). If you must use the StoreFieldStreamRaw() method, note that it must be the last method called against the NTE segment as it makes the segment immutable.

The recipient of the ORL_O22 message must be informed of this atypical use of the NTE segment so that it can be handled appropriately.

You can use the "*" shorthand for obtaining a count of the number of repeating segments (or fields, or groups) in a when clause. Anything greater than 0 evaluates to "true," so adding a Not() around the expression negates that:

Note: The original reply had Document.DocTypeName rather than Document.Name as the first part of the condition expression. The DocTypeName property refers to the message structure, not the actual message trigger event. That's in the Name property.

If the content of the field that contains the base64-encoded PDF is greater than the maximum string size in IRIS, it will be stored as a stream.

I've written a custom function that will extract the full content of a message containing such a value; it was originally created to support fetching large HL7 messages in HL7 Spy. You can download it here: http://www.hl7spy.com/downloads/HICG_HL7.xml. I've been asked to add it to the OEX repository, but I just haven't gotten around to that yet ...

I'm not sure how the SQL Editor in the management console will handle a query that uses that function, though. It will likely cause some interesting behavior 😉

The specific SQL function in that class is HICG.GetBigMsg(<messagebodyid>).

I"m assuming you're talking about HL7 here ...

InterSystems does a bit of magic in the background to display the Message Body fields in the message viewer, and it's not immediately available to you. However, I've written a custom SQL function that fetches the field at the supplied path (you'll need supply the path as its defined by the Schema/DocType of the message).

Example:

SELECT TOP 100 head.ID As ID,
        {fn RIGHT(%EXTERNAL(head.TimeCreated),999 )} As TimeCreated,
        head.SessionId As Session,
        head.Status As Status,
        head.SourceConfigName As Source,
        head.TargetConfigName As Target,
        HICG.GetMsg(head.MessageBodyId,'MSH:9') As MessageType
    FROM
        %IGNOREINDEX Ens.MessageHeader.SessionId Ens.MessageHeader head
    WHERE
        head.SessionId = head.%ID
        AND head.MessageBodyClassname = 'EnsLib.HL7.Message'
        AND head.SourceConfigName = 'SourceBusinessHostName'
    ORDER BY
        head.ID Desc

You can download the class here: http://www.hl7spy.com/downloads/HICG_HL7.xml

I don't know of any other attributes, and increasing logging may turn up something but hasn't proved useful in my experience.

If your servers are reporting occasional disconnect errors, that's not necessarily a network issue, or even something to worry about. The mirror members may report disconnections due to things like snapshots/backups where one or more hosts are momentarily suspended by the hypervisor in a virtualized environment, or updates to the OS that might stop/start services as part of the update process. It's important to remember, though, that the arbiter is designed to operate optimally in a low-latency, same network, same data center scenario. If your mirrored hosts are spread across a WAN, you're asking for errors.