go to post Robert Barbiaux · Jun 25 Use the concatenation operator, _ (underscore) in the value of the assign action. Something like : source.{EVN:5.2}_source.{EVN:5.3}
go to post Robert Barbiaux · Mar 22 Error message ERROR #5001: element 'sql' is not allowed for content model '(annotation?,((true,false)|(false,true)|(true)|(false)))'Indicates the <sql> element in the DTL XData block is in an unexpected location in the xml document, and that expected elements in that location are <annotation>, <true> or <false>. Maybe it is directly under <if> ?
go to post Robert Barbiaux · Feb 6 An approach using an operation that, on message : enqueue incoming message in a queue named after patient identifier dequeue and process message from all existing patient queues If PoolSize = 1, it will process 1 message at a time. If PoolSize > 1, multiple jobs will process incoming messages from other operations in sequence for each patient Class dc.interop.PatientMessageOperation Extends Ens.BusinessOperation { Parameter SETTINGS = "DeQueueTimeout,MinProcessTime,MaxProcessTime"; Property DeQueueTimeout As %Integer [ InitialExpression = 1 ]; Property MinProcessTime As %Integer [ InitialExpression = 1 ]; Property MaxProcessTime As %Integer [ InitialExpression = 5 ]; Method HandlePatientMessage(request As test.src.ut.ks.lib.interop.PatientMessage, Output response As %Persistent) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { $$$TOE(sc,..EnqueueRequest(request)) $$$TOE(sc,..ProcessPatientMessages()) } catch (ex) { s sc = ex.AsStatus() } return sc } Method EnqueueRequest(request As PatientMessage) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim queue as Ens.Queue #Dim queueName as %String #Dim s as EnsLib.File.InboundAdapter s sc = $$$OK try { s queueName = ..%ConfigName_".patient."_request.PatientId $$$TOE(sc,##class(Ens.Queue).Create(queueName)) s ..%RequestHeader.TargetQueueName = queueName $$$TOE(sc,##class(Ens.Queue).EnQueue(..%RequestHeader)) s $$$EnsRuntimeAppData(..%ConfigName,"queues",queueName) = 1 } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessPatientMessages() As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { s queueName = "" s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) while queueName '= "" { $$$TOE(sc,..ProcessQueuedMessages(queueName)) s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) } } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessQueuedMessages(queueName As %String) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim header as Ens.MessageHeader #Dim timedOut as %Boolean s sc = $$$OK try { $$$TOE(sc,##class(Ens.Queue).DeQueue(queueName,.header,..DeQueueTimeout,.timedOut,1)) while 'timedOut && $isobject(header) { $$$TOE(sc,..ProcessHeader(header)) $$$TOE(sc,##class(Ens.Queue).DeQueue(queueName,.header,..DeQueueTimeout,.timedOut,1)) } } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessHeader(header As Ens.MessageHeader) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim msg as PatientMessage s sc = $$$OK try { s msg = $classmethod(header.MessageBodyClassName,"%OpenId",header.MessageBodyId) $$$TOE(sc,..ProcessMessage(msg)) } catch (ex) { s sc = ex.AsStatus() } return sc } Method ProcessMessage(msg As PatientMessage) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException #Dim processTime as %Integer s sc = $$$OK try { $$$TRACE("job "_$job_" is processing message for patient "_msg.PatientId_" seq "_msg.Seq) s processTime = $random(..MaxProcessTime-..MinProcessTime)+..MinProcessTime hang processTime $$$TRACE("job "_$job_" processed message for patient "_msg.PatientId_" seq "_msg.Seq_" in "_processTime_" seconds") } catch (ex) { s sc = ex.AsStatus() } return sc } Method OnTearDown() As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { s queueName = "" s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) while queueName '= "" { $$$TOE(sc,##class(Ens.Queue).Delete(queueName,"*")) s queueName = $order($$$EnsRuntimeAppData(..%ConfigName,"queues",queueName)) } } catch (ex) { s sc = ex.AsStatus() } return sc } XData MessageMap { <MessageMap> <MapItem MessageType="ut.ks.lib.interop.PatientMessage"> <Method>HandlePatientMessage</Method> </MapItem> </MessageMap> } } The message class Class ut.ks.lib.interop.PatientMessage Extends Ens.MessageBody { Property PatientId As %Integer; Property Seq As %Integer; Property Payload As %String; Storage Default { <Data name="PatientMessageDefaultData"> <Subscript>"PatientMessage"</Subscript> <Value name="1"> <Value>PatientId</Value> </Value> <Value name="2"> <Value>Seq</Value> </Value> <Value name="3"> <Value>Payload</Value> </Value> </Data> <DefaultData>PatientMessageDefaultData</DefaultData> <Type>%Storage.Persistent</Type> } } Sample test method ClassMethod SendMessages(patientCount As %Integer = 100, messageCount = 1000) As %Status { #Dim sc as %Status #Dim ex as %Exception.AbstractException s sc = $$$OK try { for i = 1:1:messageCount { s msg = ##class(PatientMessage).%New() s msg.PatientId = $random(patientCount)+1 s msg.Seq = $increment(seq(msg.PatientId)) s msg.Payload = "message "_msg.Seq_" for "_msg.PatientId $$$TOE(sc,##class(EnsLib.Testing.Service).SendTestRequest("PatientOperation",msg)) } } catch (ex) { s sc = ex.AsStatus() } return sc }
go to post Robert Barbiaux · Dec 12, 2024 You can use the auxiliary property (aux attribute) of the <subtransform> DTL element to pass the index of the group to process to the subtransform, passing the entire source message as source object.
go to post Robert Barbiaux · Nov 10, 2024 EnsLib.EDI.XML.Document implements Ens.VDoc.Interface, and exposes GetValueAt(propertyPath) method.This method accepts XPath-like property paths such as "/foo/bar" or "/foo/bar/@attr".This also means you can use the curly brace syntax in business rules (classes extending Ens.Rule.Definition).For example, if the context class has a "xmlDocument" property, this expressionxmlDocument.{/foo/bar}will be compiled into xmlDocument.GetValueAt("/foo/bar")
go to post Robert Barbiaux · Jun 12, 2024 Hadn’t used it since Ensemble 2009 ;-), to provide custom html output displayed by the management portal when viewing a message of a class extending Ens.MessageBody, you can override the %GetContentType() and %ShowContents() methods of Ens.Util.MessageBodyMethods in your message class.Here is a small example : Class dc.sample.msg.Message Extends Ens.MessageBody { Property Name As %String [ InitialExpression = "you" ]; // returns MIME content type Method %GetContentType() As %String { return "text/html" } // output content Method %ShowContents() { &html<<p>Hello, #(..Name)#</p>> } Storage Default { <Data name="MessageDefaultData"> <Subscript>"Message"</Subscript> <Value name="1"> <Value>Name</Value> </Value> </Data> <DefaultData>MessageDefaultData</DefaultData> <Type>%Storage.Persistent</Type> } } In the management portal message viewer, the message gets displayed as :
go to post Robert Barbiaux · Jun 5, 2024 What is the class of config item ‘TEST_ROUTER’ ? Maybe not EnsLib.HL7.MsgRouter.RoutingEngine, which exposes the HL7 property.
go to post Robert Barbiaux · May 10, 2024 The curly brace syntax "source.{PID:12}" is not supported in code action as the block is included as is in the class code generated at compile time. Use GetValueAt() to achieve the same effect : Set country = source.GetValueAt("PID:12")
go to post Robert Barbiaux · Apr 11, 2024 You can use $classmethod to invoke any class method, including %New, e.g. set cls = "MyPackage.MyClass" set obj = $classmethod(cls,"%New")
go to post Robert Barbiaux · Feb 24, 2024 Hi,To summarize the documentation : ReplyCodeActions settings of HL7 operations is a comma-separated list of specifiers in the form <code>=<actions>, where <code> is an expression matching error condition(s) and <actions> is a string of one letter action codes. All codes where <actions> consists of only 'W' (for 'log a Warning') will be evaluated, and a warning will be generated for each matching <code>. Other <code> values will be evaluated in left-to-right order, executing the first matching <code> that has a non-warning <actions> value. As noted in the details for the 'W' flag, an error that only triggers 'W' <actions> will be treated as Completed OK. if ReplyCodeActions is empty, a default setting is used. For HL7 operations, it is : :?R=RF,:?E=S,:~=S,:?A=C,:*=S,:I?=W,:T?=C To match the application reject code in the HL7 ACK^O01 message in the example, and suspend all matching messages, use the following specifier, that matches all ACKs with MSA:1 = "AR" and suspend message, while retaining default behavior for other error conditions : :?R=S,:?E=S,:~=S,:?A=C,:*=S,:I?=W,:T?=C
go to post Robert Barbiaux · Feb 22, 2024 As Enrico mentions, HL7 v2 message grammars, segment structures and data type syntaxes are available in IRIS schemas. Complete specifications and semantics are detailed in the normative documents available on HL7.org web site.
go to post Robert Barbiaux · Feb 19, 2024 Yes, ^SPOOL is the simplest way to achieve this. If you need a string rather than a global, you can just get all lines from ^SPOOL, for example : ClassMethod ZWriteToString() As %String { #Dim result as %String #Dim i,lineCount as %Integer kill ^SPOOL($j) open 2:$j use 2 zwrite s result="" s lineCount=$select($data(var):$za-1,1:$za-2) close 2 for i=1:1:lineCount s result=result_^SPOOL($j,i) return result }
go to post Robert Barbiaux · Jan 24, 2024 EnsLib.HL7.Service.HTTPService may suit your needs : it implements reading HL7 message from incoming HTTP request.
go to post Robert Barbiaux · Jan 23, 2024 To clean pending messages and other suspended production data, you can use Ens.Director.CleanProduction() method, as explained in the documentation. Caution though, this will delete current production state, including removing message headers (Ens.MessageHeader instances) from queues.
go to post Robert Barbiaux · Jul 21, 2023 Hi, Using out-of-the-box classes in the EnsLib.HL7 package, you can either completely ignore responses, either process them synchronously. More complex scenarios involving multiple responses (ack/nack) are supported (see Important HL7 Scenarios | Routing HL7 Version 2 Messages in Productions | InterSystems IRIS for Health 2023.1), but I think they do not match your supplier requirements.According to the standard, when sending multiple HL7 v2.x messages over MLLP/TCP, the sender (initiating module) must wait for a response before sending the next message. This is described in "Health Level Seven Implementation Support Guide", Appendix C, section C.6.4, item 5. : It is assumed that an initiating module may connect and perform more than one message transaction before disconnecting, but it may not have more than one outstanding message waiting for a response. In other words, the initiating task must wait for the response to a given message before sending another message.This vendor requirement to receive multiple messages before sending responses is thus non-standard. It would require the sender to keep track of sent messages with a pending response, handle asynchronous replies and retry errored messages.While this is possible by writing custom adapter/operation extending the out-of-the-box classes in EnsLib.HL7 package, it would add a lot of complexity and has IMHO, little added value compared to the usual synchronous implementation.
go to post Robert Barbiaux · Jan 21, 2023 For a simple message transformation flow example, I would go for record map : you get readymade service (EnsLib.RecordMap.Service.FileService) and operation (EnsLib.RecordMap.Operation.FileOperation) in the library to read and write CSV files and a mecanism that generates an appropriate message class based on a declarative definition (see Using the Record Mapper | Developing Productions | InterSystems IRIS Data Platform 2022) So you can focus on DTL and the whole flow can be done from the administration portal, look ma, no code ;-)
go to post Robert Barbiaux · Feb 26, 2022 Hi, Assuming your question is about HL7 (or EDI) message serialization. The DTL is meant for parsing and transforming the message into another one. Serialization occurs when you output the message using the corresponding instance methods. For an instance of the EnsLib.HL7.Message class, methods that output the message such as OutputToFile() are using instance properties to determine what separators to use : .Separators, .SegmentTerminator. Also, business operations (extending EnsLib.HL7.Operation.Standard) expose a setting (Separators) that let you configure what separators to use.