Robert Barbiaux · Dec 12, 2024 go to post

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.

Robert Barbiaux · Nov 10, 2024 go to post

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 expression
xmlDocument.{/foo/bar}
will be compiled into 
xmlDocument.GetValueAt("/foo/bar")

Robert Barbiaux · Sep 25, 2024 go to post

For example (in open exchange package ks-iris-lib) :

/// list of data types exposing %GetSerial and %SetSerial
Class ks.lib.collections.ListOfDataTypes Extends %Library.ListOfDataTypes
{

/// serialize object
Method %GetSerial(force As %Integer = 0) As %String
{
    return ##super(force)
}

/// deserialize object
Method %SetSerial(serialized As %String) As %Status
{
    return ##super(.serialized)
}

Storage Custom
{
<Type>%Library.CompleteCustomStorage</Type>
}

}
Robert Barbiaux · Sep 23, 2024 go to post

There is a limit to string length, as explained in the documentation.
To convert a binary stream to a base64 encoded character stream, you have to call the function in a loop using a buffer. Due the way the function works, the buffer length must be a  multiple of 57.
Here a sample implementation, from ks.lib.stream.Utils class in the ks-iris-lib package available on Open Exchange.
 

/// encode stream data into encoded using bufferSize (due the implementation of $system.Encryption.Base64Encode, this must be a multiple of 57)
ClassMethod Base64Encode(stream As %Stream.Object, Output encoded As %Stream.Object, bufferSize As %Integer = 5700) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException

  #Dim len As %Integer
  
  s sc = $$$OK
  try {
    throw:((bufferSize#57)'=0) ##class(%Exception.General).%New("buffer size must be a multiple of 57")
    $$$TOE(sc,stream.Rewind())
    s:'$d(encoded) encoded = ##class(%Stream.TmpCharacter).%New()
    s len=bufferSize
    while 'stream.AtEnd {
      $$$TOE(sc,encoded.Write($system.Encryption.Base64Encode(stream.Read(.len),1)))
      s len = bufferSize
    }
  }
  catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}
Robert Barbiaux · Aug 7, 2024 go to post

 Using the second approach  : edited down to 39 characters 

ClassMethod ascii()
{
 f i=32:1:126 w:$t(ascii+1)'[$c(i) *i
}
Robert Barbiaux · Aug 7, 2024 go to post

Yes Enrico, it is my understanding exactly : if your program code (i.e. the lines in the routine generated by the compiler) contains all printable ASCII characters, it should output an empty string. Of course it sounds silly to devise a very clever way to output an empty string, but after all, golf - from a practical point of view - is a silly sport isn't it ? 🤣

Robert Barbiaux · Aug 7, 2024 go to post

Alas, I'm well behind : 54 characters (according to %Dictionary.MethodDefinition Implementation.Size).
Please, enlighten me 😅

ClassMethod ascii() [ CodeMode = objectgenerator ]
{
 f i=0,83,0,33,29,2,3:1:94,2 d %code.Write($c(i+32))
}
Robert Barbiaux · Jun 12, 2024 go to post

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,&nbsp;#(..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 :

Robert Barbiaux · Jun 5, 2024 go to post

What is the class of config item ‘TEST_ROUTER’ ?

Maybe not EnsLib.HL7.MsgRouter.RoutingEngine, which exposes the HL7 property.

Robert Barbiaux · May 27, 2024 go to post

The message is not valid as per HL7 v2.5 section 4.4.6.

The second ORC...TQ1...OBR is invalid, it should be ORC...OBR...TQ1 : in the ORDER_PRIOR group, ORC is optionnal and must be followed by OBR followed by optional NTE and then TQ1 group.

Keep in mind : IRIS HL7 schemas are based on formal grammars published by HL7.org.

Robert Barbiaux · May 10, 2024 go to post

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")
Robert Barbiaux · Mar 13, 2024 go to post

Size 201 181 181, all unit tests passed (including undefined argument, and additional .Type("abc,de","de,abc") --> Unsorted)
Thanks Eduard, I missed the extraneous quotes (my mind is still not entirely purged of strongly typed languages habits 😅)

ClassMethod Type(a...) As %String
{
 f i=$i(r):1:$g(a){f j=1:1:$l(a(i),","){s l=$l($tr($p(a(i),",",j)," ")),c=$g(c,l),r=$s(l=c:r,r<3*l>c:2,r#2*c>l:3,1:4),c=l} k c} q $p("Constant1Increasing1Decreasing1Unsorted",1,r)
}
Robert Barbiaux · Feb 24, 2024 go to post

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
Robert Barbiaux · Feb 22, 2024 go to post

^SPOOL is not buried 😁, it is well documented, along with %IS and %SPOOL utilities.
It is simple and effective when used to it's intended purpose, that is, spooling text written to the currently in use device.

Robert Barbiaux · Feb 22, 2024 go to post

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.

Robert Barbiaux · Feb 19, 2024 go to post

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
}
Robert Barbiaux · Jan 30, 2024 go to post

If the intent is to wrap the statements in a new variable scope, why not simply extract the block in a private  method ?

Robert Barbiaux · Jan 23, 2024 go to post

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.
 

Robert Barbiaux · Jan 14, 2024 go to post

You can pass the value to the DTL using the transformation auxiliary parameter, as described in the documentation : Working with Rules | Developing Business Rules | InterSystems IRIS for Health 2023.3.
To pass the source configuration name : 

Robert Barbiaux · Sep 18, 2023 go to post

I totally agree.
At the moment I have all routines in a git repo, some files are using suffixes for disambiguation, and will gradually reduce entropy by refactoring and applying clear naming conventions as Evgeny suggest.

my thanks to you both 😀

Robert Barbiaux · Sep 18, 2023 go to post

After enabling case-sensitivity in Windows, it is indeed possible to keep the original storage names.
Unfortunately,  enabling the flag has no impact on the behaviour of Windows versions of git or vscode, they continue to use case-insensitive comparisons for file names. For example, it is impossible to stage / commit changes to the git repository, as file names differing only case are considered as a single file.

Robert Barbiaux · Jul 21, 2023 go to post

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.