Marc Mundt · May 13, 2025 go to post

Many systems that send HL7 over HTTP use very simple requests -- they just send the raw HTML as the HTTP request body. There's no SOAP layer on top. This is the approach that EnsLib.HL7.Operation.HTTPOperation uses.

HTTP docs say that 415 could indicate that the content-type header is specifying a content type that the receiving system doesn't like. Assuming the recipient doesn't actually need SOAP, if you can find out what content type they are looking for, you can change this in the Business Operation settings under Additional Settings/ContentType.

If the receiving system really does need a SOAP call you can look at EnsLib.HL7.Operation.SOAPOperation. I haven't seen this in use so I don't know the details but it might be worth a try.

Marc Mundt · Apr 28, 2025 go to post

For using this from a DTL you can put the query logic in a custom function which takes the identifier from the MFN and returns the facility.

Marc Mundt · Apr 28, 2025 go to post

Here's how you can iterate through the results:

set query = "SELECT Facility FROM FROM osuwmc_EnterpriseDirDB.RelationshipMedCtrID WHERE OSUmedcenterID = ?"SET rset = ##class(%SQL.Statement).%New()
 SET qStatus = rset.%Prepare(query)
 SET rset = rset.%Execute($Get(ID))
 
 while (rset.%Next()) {
    // Check Facility value in each rowset facility=rset.%Get("Facility")
    if (facility = "SOMEVALUE") {
        //...
    }
 }
Marc Mundt · Feb 27, 2025 go to post

As Enrico mentioned, the rule logic is stored as XML in a rule class, so you can't query the logic directly via SQL.

You can find the names of all rule classes using SQL:
SELECT ID FROM %Dictionary.ClassDefinition where Super='Ens.Rule.Definition'

To then view the rule logic you would need to open a class and view the "RuleDefinition" XData block:

Class ORU.RouterRoutingRule Extends Ens.Rule.Definition
{

Parameter RuleAssistClass = "EnsLib.HL7.MsgRouter.RuleAssist";

XData RuleDefinition [ XMLNamespace = "http://www.intersystems.com/rule" ]
{
<ruleDefinition alias="" context="EnsLib.HL7.MsgRouter.RoutingEngine" production="ADTPKG.FoundationProduction">
<ruleSet name="" effectiveBegin="" effectiveEnd="">
<rule name="">
<when condition="1" comment="">
<send transform="Demo.ORUTransformation" target="ORU.Out"></send>
<send transform="Demo.ORUTransformation" target="ORU.Two"></send>
</when>
</rule>
</ruleSet>
</ruleDefinition>
}

}
Marc Mundt · Feb 21, 2025 go to post

Just to clarify a bit:

Are you talking about, for example, HL7 routing rules that are used with the standard business process EnsLib.HL7.MsgRouter.RoutingEngine?

What information do you want to retrieve for the rules?

Marc Mundt · Jan 31, 2025 go to post

We tracked this down to a core VS Code setting that filters out certain subdirectory names. Removing the entry in this list for "CVS" solved the problem.

Marc Mundt · Jan 31, 2025 go to post

No errors are appearing in the OUTPUT tab.

The package contains only custom classes.

Not sure if this is the right config -- let me know if not. I'm on Linux and this is ".config/Code/Workspaces/1731519734832/workspace.json" The problematic item is irishealth:CVSCCDA.

{
        "folders": [
                {
                        "name": "irishealth:AUTOMATION",
                        "uri": "isfs://irishealth:automation/"
                },
                {
                        "name": "irishealth:FHIRCONVERSION",
                        "uri": "isfs://irishealth:fhirconversion/"
                },
                {
                        "name": "irishealth:%SYS",
                        "uri": "isfs://irishealth:%sys/"
                },
                {
                        "name": "irishealth:CVSCCDA",
                        "uri": "isfs://irishealth:cvsccda/"
                }
        ]
}

Marc Mundt · Dec 5, 2024 go to post

I haven't tested this, but this is what I'm suggesting. New part is colored blue.

XData BPL [ XMLNamespace = "http://www.intersystems.com/bpl" ]
{
<process language='objectscript' request='CUH.Mess.DCIQ.JsonIn' response='CUH.Mess.DCIQ.JsonOut' height='3400' width='2000' >
<context>
<property name='JsonObjectIn' type='CUH.Mess.DCIQ.JsonInObj' instantiate='1' />
<property name='HL7QRY' type='EnsLib.HL7.Message' instantiate='0' />
<property name='HL7ADR' type='EnsLib.HL7.Message' instantiate='0' />
<property name='JsonObjectOut' type='CUH.Mess.DCIQ.JsonOutObj' instantiate='1' />
<property name='RequestNumber' type='%String' initialexpression='""' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
<property name='IsInteger' type='%Boolean' instantiate='0' />
<property name='ProcError' type='%String' initialexpression='""' instantiate='0' >
<annotation><![CDATA[Error message to be sent back in the Response.]]></annotation>
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
<property name='ValidOriginUrl' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='100' />
</parameters>
</property>
<property name='ValidContactType' type='%String' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
<property name='ValidIDNumberType' type='%String' initialexpression='""' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
<property name='ValidNHSCodeType' type='%String' initialexpression='""' instantiate='0' >
<parameters>
<parameter name='MAXLEN'  value='50' />
</parameters>
</property>
</context>
<sequence xend='200' yend='3500' >
<scope xpos='200' ypos='250' xend='200' yend='2850' >
<annotation><![CDATA[Handles all errors]]></annotation>
<code name='Initialize JSON request' xpos='200' ypos='350' >
<![CDATA[ set context.JsonObjectIn = ##class(CUH.Mess.DCIQ.JsonInObj).%New()
 do context.JsonObjectIn.%JSONImport(request.bodyJson)]]>
</code>
<if name='Is "data" an array?' condition='context.JsonObjectIn.data.%IsA("%DynamicArray")' xpos='200' ypos='450' xend='200' yend='700' >
<true>
<assign name="Make &quot;data&quot; a %DynamicObject" property="context.JsonObjectIn.data" value="context.JsonObjectIn.data.%Pop()" action="set" xpos='335' ypos='600' />
</true>
</if>
<assign name="Valid URL" property="context.ValidOriginUrl" value="##class(Ens.Util.FunctionSet).Lookup(&quot;CUH.DCIQToEpic.OriginUrl&quot;,$NAMESPACE,&quot;&quot;,3)" action="set" languageOverride="" xpos='200' ypos='800' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<assign name="Valid ContactType" property="context.ValidContactType" value="##class(Ens.Util.FunctionSet).Lookup(&quot;CUH.DCIQToEpic.ContactType&quot;,$NAMESPACE,&quot;&quot;,3)" action="set" languageOverride="" xpos='200' ypos='900' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<assign name="Valid IDNumberType" property="context.ValidIDNumberType" value="##class(Ens.Util.FunctionSet).Lookup(&quot;CUH.DCIQToEpic.IDNumberType&quot;,$NAMESPACE,&quot;&quot;,3)" action="set" languageOverride="" xpos='200' ypos='1000' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<assign name="Valid NHSCodeType" property="context.ValidNHSCodeType" value="##class(Ens.Util.FunctionSet).Lookup(&quot;CUH.DCIQToEpic.NHSCodeType&quot;,$NAMESPACE,&quot;&quot;,3)" action="set" languageOverride="" xpos='200' ypos='1100' >
<annotation><![CDATA[Environment specific]]></annotation>
</assign>
<if name='Check OriginUrl' condition='context.JsonObjectIn.meta."origin_url"=context.ValidOriginUrl' xpos='200' ypos='1200' xend='200' yend='2600' >
<true>
<if name='Check Contact Type' condition='(context.JsonObjectIn.data."0"."con_type"=context.ValidContactType)||(context.JsonObjectIn.data."con_type"=context.ValidContactType)' xpos='470' ypos='1350' xend='470' yend='2500' >
<true>
<if name='Check idNumber Type' condition='(context.JsonObjectIn.data.idNumbers.GetAt(1).type)=context.ValidIDNumberType' xpos='740' ypos='1500' xend='740' yend='2400' >
<true>
<assign name="Get Request Number" property="context.RequestNumber" value="context.JsonObjectIn.data.idNumbers.GetAt(1).number" action="set" languageOverride="" xpos='1010' ypos='1650' />
<code name='Integer pattern' xpos='1010' ypos='1750' >
<![CDATA[ set context.IsInteger = ((context.RequestNumber)?.N)]]>
</code>
<if name='Integer?' condition='context.IsInteger' xpos='1010' ypos='1850' xend='1010' yend='2300' >
<annotation><![CDATA[Checks if Requested Number sent is an Integer]]></annotation>
<true>
<transform name='Create HL7 query' class='CUH.Tran.GetDCIQJsonPatientToEpicQRYQ011' source='context.JsonObjectIn' target='context.HL7QRY' xpos='1280' ypos='2000' />
<call name='Send to MPIQueryHandler' target='MPI Query Handler' async='0' xpos='1280' ypos='2100' >
<request type='EnsLib.HL7.Message' >
<assign property="callrequest" value="context.HL7QRY" action="set" languageOverride="" />
</request>
<response type='EnsLib.HL7.Message' >
<assign property="context.HL7ADR" value="callresponse" action="set" languageOverride="" />
</response>
</call>
<transform name='Converts HL7 to JSON' class='CUH.Tran.EpicADRA19ToDCIQJsonPatient' source='context.HL7ADR' target='context.JsonObjectOut' xpos='1280' ypos='2200' />
</true>
<false>
<assign name="Error No Number" property="context.ProcError" value="&quot;MRN needs to be a positive numeric value&quot;" action="set" languageOverride="" xpos='1010' ypos='2000' />
</false>
</if>
</true>
<false>
<assign name="Error idNumber Type" property="context.ProcError" value="&quot;Invalid idNumber Type&quot;" action="set" languageOverride="" xpos='740' ypos='1650' />
</false>
</if>
</true>
<false>
<assign name="Error Contact Type" property="context.ProcError" value="&quot;Invalid Patient Contact Type&quot;" action="set" languageOverride="" xpos='470' ypos='1500' />
</false>
</if>
</true>
<false>
<assign name="Error Origin Url" property="context.ProcError" value="&quot;Origin URL not authorized!&quot;" action="set" languageOverride="" xpos='200' ypos='1350' />
</false>
</if>
<faulthandlers>
<catchall xpos='200' ypos='2700' xend='200' yend='350' >
<assign property="context.ProcError" value="&quot;Error in the Json/HL7 conversion/transformation block&quot;" action="set" languageOverride="" xpos='200' ypos='250' />
</catchall>
</faulthandlers>
</scope>
<assign name="Assign Error to Response" property="context.JsonObjectOut.errors" value="context.ProcError" action="set" languageOverride="" xpos='200' ypos='2950' />
<if condition='context.JsonObjectOut.errors&apos;=""' xpos='200' ypos='3050' xend='200' yend='3400' >
<annotation><![CDATA[Check if any errors]]></annotation>
<true>
<trace name='Log the Error' value='context.JsonObjectOut.errors' xpos='470' ypos='3200' />
<assign name="Returns {}" property="response.bodyJson" value="&quot;{}&quot;" action="set" languageOverride="" xpos='470' ypos='3300' />
</true>
<false>
<code name='Returns Valid Response' xpos='200' ypos='3200' >
<![CDATA[ set temp = ##class(CUH.Mess.DCIQ.JsonOut).%New()
 do context.JsonObjectOut.%JSONExportToString(.temp)
 set response.bodyJson = temp]]>
</code>
</false>
</if>
</sequence>
</process>
}
Marc Mundt · Dec 5, 2024 go to post

Right before this statement:

<assign name="Valid URL" property="context.ValidOriginUrl" value="##class(Ens.Util.FunctionSet).Lookup(&quot;CUH.DCIQToEpic.OriginUrl&quot;,$NAMESPACE,&quot;&quot;,3)" action="set" xpos='200' ypos='450' >
Marc Mundt · Dec 5, 2024 go to post

One thing you could do:
At the top of your BPL, add an <if> checking if "data" is an array or an object:

if context.JsonObjectIn.data.%IsA("%DynamicArray")

If it is, then use an assign to set context.JsonObjectIn.data to the last element of the array (%Pop() returns the last element of an array):

set context.JsonObjectIn.data = context.JsonObjectIn.data.%Pop()

After doing this, the rest of the actions should work the same for either input.

Note that this assumes that when "data" is an array that the array only has one item in it. If it can have multiple items then you'll need to add logic to choose the correct item.

Marc Mundt · Dec 2, 2024 go to post

Can you give some more information about what's happening? Is it returning an error? Does the JSON string look right in the trace log?

Marc Mundt · Nov 21, 2024 go to post

In the message trace for the whole process, what is the value of DocType in the Body tab?

Marc Mundt · Nov 21, 2024 go to post

Check the settings on your inbound HL7 service and make sure that Message Schema Category is set to your custom schema (ADT_ALL).

 

Marc Mundt · Nov 20, 2024 go to post

And in the Developer Tools "Network" tab, do any of the requests return HTTP codes other than 200?

Marc Mundt · Nov 20, 2024 go to post

In Developer Tools, can you try the "Disable Cache" option and then reload the page?

This screenshot is from Firefox, but Chrome also has this option in the Network tab.

Marc Mundt · Nov 20, 2024 go to post

Yeah, so something like this:

for colnum=1:1:snapshot.GetColumnCount() {
    write snapshot.GetColumnName(colnum),"=",snapshot.GetData(colnum),!
}
Marc Mundt · Nov 20, 2024 go to post

This is just how the message viewer displays it. But is the message complete when the business operation writes it to the file?

Marc Mundt · Jul 5, 2024 go to post

Good question. In OAuth the response will include the expiration time, so that's what I was expecting here.

If the response doesn't include an expiration time does the vendor's documentation say anything about if tokens can be used for more than 1 request and if so how long it's valid for? If you know it's supposed to be valid for 60 minutes then you could just log the time when you requested the token and use that to track it.

Marc Mundt · Jul 5, 2024 go to post

There could be non-OAuth token-based authentication methods. I expect the %SYS.OAuth stuff won't work for something non-OAuth.

What does the payload look like that you have to send in order to get a token? We can see if it looks like OAuth or not.

Marc Mundt · Jul 5, 2024 go to post

OnInit() only gets called when starting the operation.

If your tokens have an expiration time then you need to check if the token is expired on each call and fetch a new one if needed. You can just store the token and expiration time in properties and check them on each call.

Marc Mundt · Jul 5, 2024 go to post

I would start by checking if some other process is already using port 5000 (or whatever port JavaGateway is trying to run on). It would also be good to check the JavaGateway

Since you're on Linux you can check this with netstat:

netstat --listening --numeric-ports