Jeffrey Drumm · May 28, 2021 go to post

Here's a method that might get you close to what you want:

ClassMethod GetHostsByAdapter(pProduction As %String, pAdapterName As %String) As %List
{
        Set tPrd = ##class(Ens.Config.Production).%OpenId(pProduction)
        Return:'$ISOBJECT(tPrd) "Production does not exist!"
        Set tItems = tPrd.Items
        Set tItemCnt = tItems.Count()
        Set tHostList = ""
        Set tCnt = 1
        For i=1:1:tItemCnt
        {
            Set tItem = tItems.GetAt(i)
            If $CLASSMETHOD(tItem.ClassName,"%GetParameter","ADAPTER") = pAdapterName
            {
                Set $LIST(tHostList,tCnt) = tItem.Name
                Set tCnt = tCnt + 1
            }
        }
        Return tHostList
}

Call it with:

Set hosts=##class(<classname>).GetHostsByAdapter("<production name>","EnsLib.HTTP.InboundAdapter")

The variable hosts will contain the list (in $LIST form) of business hosts that have the adapter specified as the 2nd argument.

Jeffrey Drumm · May 4, 2021 go to post

I'm posting this as a potential solution. I imagine there are smarter ways of doing this. Hoping someone will jump in and show me the way 😁

I've written a classmethod that accepts 3 arguments: the lower and upper limits of the IDs to select, along with the number of records you'll want to select:

Class Sample.Rand
{
ClassMethod RandList(pMin As %Integer, pMax As %Integer, pLen As %Integer = 10) As %String [ SqlName = List, SqlProc ]
{
        Set tList = ""
        Set tCnt = 1
        For i=1:1:pMax
        {
            Set tNum = $R(pMax + 1)
            If tNum >= pMin
            {
                Set $LIST(tList,tCnt) = tNum
                Set tCnt = tCnt + 1
            }
            Quit:(tCnt > pLen)
        }
    Return tList
}
}

The method can be called as a custom SQL function in a subquery:

SELECT * FROM MyTable WHERE %Id %INLIST (SELECT Sample.List(MIN(%Id),MAX(%Id),10) FROM MyTable)

The query above will select up to 10 rows randomly from table MyTable.

Caveats: This could take a long time to run if your range from minimum to maximum %Id is large. And it's not guaranteed to return the number of rows specified as the 3rd argument (there may be deleted records, or insufficient random values generated before the upper limit for %Ids is reached). Finally, it assumes that %Id is numeric.

EDIT: And as pointed out by @Julius Kavay, @Robert Cemper has a better idea (he usually does 😉)

Jeffrey Drumm · May 2, 2021 go to post

In additional experimentation, I found that there is a quoted-string mechanism:

> Set oM."click".type = "double"

is equivalent to

> Set oM.click.type = "double"

Regardless, it's still not persistent.

Jeffrey Drumm · May 1, 2021 go to post

No, you can't use the quoted string notation (or at least if you can, I haven't figured out how).

It's not persistent either, which is why I followed up to state that it's not really what you're looking for.

Jeffrey Drumm · May 1, 2021 go to post

You should need only to iterate over the result set and populate the repetition of the PV1 field. Note that you reference PV1.7 (attending provider) in your narrative, but your code references PV1.3 (assigned patient location), so adjust the below as needed to identify the proper field:

//create a copy of the request
Set newREQ = pRequest.%ConstructClone()
//now we use the copy for manipulation
Set res=##class(%ResultSet).%New("%DynamicQueryGW:SQLGW")
Set sc=res.Prepare(sqlstring,,conn)
Set sc=res.Execute()
// create a repetition counter
Set cnt=1
While res.Next()
{
    Set RET = res.GetData(2)
    //Modify the segment
    //Reference the correct repetition for the current result set member
    set res = newREQ.SetValueAt(RET,"PV1:3("_cnt_")")
    //Increment the repetition counter
    Set cnt=cnt+1
}
Set sc=res.Close()
Set sc=conn.Disconnect()

The appropriate repetition delimiters as defined within the message schema (in this case "~") are automatically inserted.

Note that the SetValueAt() method has been modified with the assumption that you're using a consistent HL7 schema across all messages and know the correct path to the PV1 field in question. In that case the FindSegment() method should not be necessary.

Jeffrey Drumm · Apr 30, 2021 go to post

Hmm. Upon re-reading your question, I realized this isn't really the answer you were looking for. Others have provided the correct solutions/references.

Jeffrey Drumm · Apr 30, 2021 go to post
ClassMethod TestObj() As %DynamicObject

{
        Set oM = {}
        Set mMode = ["down","up","click"]
        Set iter = mMode.%GetIterator()
        While iter.%GetNext(,.val)
        {
            Do oM.%Set(val,{"id":"","type":""})
        }
        Quit oM
}

USER> set oM = ##class(User.DynObj).TestObj()
USER> write oM.%ToJSON()
{"down":{"id":"","type":""},"up":{"id":"","type":""},"click":{"id":"","type":""}}
USER> zwrite oM.down.id
""
USER> zwrite oM.up.type
""
USER> set oM.click.type = "double"
USER> write oM.click.type
double
USER> write oM.%ToJSON()
{"down":{"id":"","type":""},"up":{"id":"","type":""},"click":{"id":"","type":"double"}}
Jeffrey Drumm · Apr 28, 2021 go to post

To clarify, are you looking for a method to be used in a Business Process to obtain the path and name of a file created by a Business Operation?

Jeffrey Drumm · Apr 24, 2021 go to post

For me, this was a fundamental revelation when I started working with Caché/ObjectScript. Where the $#&! is the sort function/method? I had naively assumed that arrays were simply associative with no intrinsic ordering, like Perl hashes.

When the light dawned ... 🤦‍♀️

Jeffrey Drumm · Apr 1, 2021 go to post

Do you have ^EnsPortal("DisableInactivityTimeout","Portal")=1 in each namespace that's running a production? It's not mapped from another namespace.

Jeffrey Drumm · Apr 1, 2021 go to post

I'm pretty sure you want the value to be 0 for the second option:

set ^%SYS("Portal","EnableAutoRefresh")=0
Jeffrey Drumm · Apr 1, 2021 go to post

Have you tried %K(Server) as the output format prefix?

HICG>set tm="20210401072630+0000"
HICG>w ##class(Ens.Util.FunctionSet).ConvertDateTime(tm,"%q%z","%K(Server)%q%z")
20210401032630-0400

This of course assumes your server is in your locale/time zone.

Jeffrey Drumm · Mar 30, 2021 go to post

This might get you closer to what you want, perhaps called from OnProcessMessage() in the service:

ClassMethod QueueGetOldest(pQueueName As %String, Output pStatus As %Status) As %String
{
    If ##class(Ens.Queue).GetCount(pQueueName) > 0
    {
        Set tStmt = ##class(%SQL.Statement).%New()                                          
        set qSC = tStmt.%PrepareClassQuery("Ens.Queue","EnumerateItem")
        Set tRS = tStmt.%Execute(pQueueName,"")                        
        Do tRS.%Next()                                                
        Set tHdrid = tRS.%Get("MessageId")
        Set tMsghdr = ##class(Ens.MessageHeader).%OpenId(tHdrid)
        Set pStatus = $$$OK
        Return tMsghdr.TimeCreated
    }
    Set pStatus = $$$ERROR($$$GeneralError,"Not found")
    Return ""
}

It returns the time created of the oldest entry in the queue, or the empty string if the queue is empty or doesn't exist.

You could create a variant that would accept a duration argument and return true/false if the duration between the current time and the time of the oldest entry exceeds that.

Jeffrey Drumm · Mar 24, 2021 go to post

Hi Scott,

You can get the repetition count for a field with the GetValueAt() method:

Set tRepCount = msg.GetValueAt("OBX(3):5(*)")

You can then iterate over the repetitions in a FOR loop:

For tRep = 1:1:tRepCount
{
    If msg.GetValueAt("OBX(3):5("_tRep_")") = tMatchVal
    {
        // Do stuff
    }
}
Jeffrey Drumm · Mar 24, 2021 go to post

Messages for a Business Service would be queued in an external system if the service is unable to process them faster than they're being generated.

Are you referring to the time-in-queue for messages received by the service before they're acted upon by a Business Process or Operation?

Jeffrey Drumm · Feb 24, 2021 go to post

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?

Jeffrey Drumm · Feb 18, 2021 go to post

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.

Jeffrey Drumm · Feb 11, 2021 go to post

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.

Jeffrey Drumm · Feb 8, 2021 go to post
02/08/21-09:17:52:754 (8272) 2 [Generic.Event] Too many Cores (12) for InterSystems IRIS Community License.

The Community Edition license is invalid for the number of cores your Windows 10 host has. It supports a maximum of 8 cores, and you appear to have 12.

Jeffrey Drumm · Feb 8, 2021 go to post

You'll need to install the CSP gateway component on the Apache server, but not IRIS itself.

Jeffrey Drumm · Feb 8, 2021 go to post

As is 

USER> w $system.Util.InstallDirectory()_"lib"

There's also a LibPath entry in IRIS.cpf, but I'm not sure it's the same. But you can get it:

%SYS> do ##class(Config.config).Get(.props)
%SYS> write props("LibPath")

Unfortunately on the IRIS server I'm working with it's empty ...

Jeffrey Drumm · Jan 20, 2021 go to post

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 ...

Jeffrey Drumm · Jan 20, 2021 go to post

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.

Jeffrey Drumm · Jan 15, 2021 go to post

Nevermind.  Ugh. Been a rough week.

HL7.{PID.7} Contains CurrentDateTime("YYYYMMDD")

Close, but not quite:

Jeffrey Drumm · Jan 15, 2021 go to post

This question is more than 2 1/2 years old now, but I guess I missed it when it was posted. Regardless, the issue is that "?" is the match operator that "Matches" represents, and is not part of the pattern itself. Your match pattern should not include the "?" character.

Jeffrey Drumm · Jan 14, 2021 go to post

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"

Jeffrey Drumm · Dec 24, 2020 go to post

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?