Hi Michael,

To summarize, HealthConnect is built on top of Ensemble. If you are getting HealthConnect, you must also have Ensemble. 

Here is the main difference: Ensemble in an Interface Engine that is used primarily to receive data in most common formats and transform the data before re-sending transformed data to another destination. Ensemble supports FTP File Transfer, SQL Transfer (with a connection to another DB), HTTP Transfer and TCP Transfer. As part of TCP Transfer there is also support for HL7 Standard. The outbound support is the same as for inbound. As part of Ensemble there are Transformation Languages (BPL, DTL) that allow you to transform your data.

HealthConnect has all of that but because it is specialized, in addition to the above transfer modes, it also implements the IHE IT Infrastructure Technical Framework (if you search for it you will get its documentation on the IHE web site). In order to implement the framework, in addition to the Interface Engine, you have to have a set of Configuration Databases (Registries). This is what HealthConnect provides. In a nutshell, with HealthConnect, you can process and transform not only HL7 messages but also Continuity of Care Documents (CCDs) - XML-based documents that are typically delivered via SOAP messages with a specialized SOAP Body (IHE Transaction) and a payload/attachment (the CCD document itself which is typically b64-encoded). HealthConnect also has a native intermediate XML format (SDA) and it provides ready-made (and customizable) XSLT transforms that can transform the CCD to and from this native format.

I hope this helps.

Hi Kurro,

1) The best way to debug SOAP issues is to enable SOAP Logging on the client side.

On your Cache server, use Terminal to zn to the namespace from which the request originates and type:

NAMESPACE> set ^ISCSOAP("LogFile")="C:\temp\SOAP.log"

NAMESPACE> set ^ISCSOAP("Log")="ios"

and then retry your GET/POST. You should see messages in the file above.

To turn off logging, type:

NAMESPACE> set ^ISCSOAP("Log")=""

2) If you try this you will likely see the same error in the SOAP log. So the most likely issue is the ContentType property of your HttpRequest object.

In your Business Operation, your probably have code like this somewhere:

Set tHttpRequest = ##class(%Net.HttpRequest).%New()

Set tHttpRequest.ContentType = "text/xml" // is this property set?

Here is the message from the class on its default behavior:

Sets/gets the 'Content-Type:' entity header field in the HTTP request. If it
/// is not specified and there is an <PROPERTY>EntityBody</PROPERTY> then it default
/// to 'text/html'.<p>

...

Property ContentType As %String [ Calculated ];
 

So if you used HttpRequest.EntityBody for your payload, you probably need to set ContentType explicitly.

I hope this helps.

Hi Ahmad,

Please see my summary below:

From the Management point of view, Option 2 is much preferred - less configuration, and as a result, less maintenance (also less work for your EHR Participants). One variable is Consent and the legal framework under which you are operating. Can doctors from the same Facility Group see patients belonging to different branches? How is Consent collected at these branches (does the medical staff collect consent per branch or is it understood that the information can be shared with doctors within the Facility Group)? Option 2 is only even possible because MRNs are the same within the Facility Group.

That said, there is one additional consideration which may end up a deal-breaker. If an ADT message for a patient in Facility Group A Branch 1 is processed, and then another ADT message (different ADT event) for the same patient is processed in Facility Group A Branch 2, unless the Medical Record is truly shared on the EMR side, you may have order issues. For example, ADT^A04 (register) is at Branch 1 and then the patient is discharged (A03), then patient goes to Branch B, there is an ADT^A04 (new register) and the patient is admitted and transferred to Inpatient (A06). So far, everything is OK because the patient cannot be in two branches at the same time but if there is a glitch and one of the Branches stops transmitting for a while (unlikely but possible) the order of events may get scrambled.

I hope this helps.

This use case looks good for the ISC product "HealthConnect"... it is designed to accept some data, transform it and output new data. It includes built-in XSLT Transforms that can transform CCD Documents into SDA (XML format for ISC Data Model) and back to CCD.

If you have ISC HealthConnect, you can do the following:

1) Use one of the CCDA-to-SDA transforms to create an SDA XML Document

2) Use  TransformIntoObject() method of  HS.Util.XSLTTransformer to get an SDA Object with Properties

3) Change whatever you need and save a new SDA Object

4) Use  TransformFromObject() of the same class to get a new SDA Document

5) Use one of the SDA-to-CCDA XSLT Transforms to create a new CCDA Document.

All of this is basically out of the box... you just need to find a way to get your document into the system. If you just use FTP you can either use Ensemble FTP Service or write a script to transfer the files onto the file system where an Ensemble FileService would pick them up. 

You probably would need to create a Business Operation that would invoke the Transforms and manipulate your data; after that you can pass your output to an FTPOperation that will send the files out to your recipients.

If you don;t have HealthConnect, I would recommend getting it - XSLT is very fast compared to reading the XML directly into Objects. ISC basically solved this problem for you (for extra money of course...) If it is not possible, you may need to write your XML to Obj conversion... I don;t think there is anything like that in Ensemble...

Hi Frances,

You are saying that you are getting an Order message. Is it an ORM message or an ORU message (the latter typically contains OBX segments and is of type Observation/Result). 

In general, you would probably have to do something of the following:

1) During message processing, concatenate the content of every OBX:5 field into a single variable. In order to avoid a "Long String" problem in Cache, I would open a GlobalCharacterStream or FileCharacterStream and keep writing each new OBX:5 field to Stream as you loop over the segments.

2) In order to convert anything into PDF, you would need an external Rendering Engine. There is one I found on Open Exchange:

https://openexchange.intersystems.com/package/iris-pdf-generator

Also, Cache provides Apache FOP PDF Engine.  Here is some documentation on how to run it:

https://xmlgraphics.apache.org/fop/

https://xmlgraphics.apache.org/fop/2.5/running.html

The issue is, how to invoke the rendering engine from your ObjectScript code. This thread might be helpful:

https://community.intersystems.com/post/how-create-pdf-file-html

You can then write your PDF to a file.

3) If you wrote your PDF to file, you can use the following article on how to embed it into the HL7 Message:

https://community.intersystems.com/post/ensemble-how-embed-pdf-file-hl7-...

This is a complex project; but the tools listed here should help you.

Hi Blakely,

I assume you already have code that goes over your XML and extracts data into the HL7 fields.

So then you get to the Par_Location element, do something like this:

Set tPar_Location_Text = M071|M074|...

For i=1:1:$LENGTH(tPar_Location_Text) {

    Set word = $PIECE(tPar_Location_Text, "|", i)

    Do createSegment("IVT", i, word, field3, ...)

}

Note I assume you have a routine that creates the segments...

I hope this helps

Vitaly

There is an EnsLib.XML.TCPService which extends EnsLib.TCP.PassthroughService but the only new thing it does it adds processing for XML SearchTableClass. There is also EnsLib.TCP.CountedXMLInboundAdapter (which inherits from Ens.Util.XML.Reader)

You can try to extend EnsLib.TCP.PassthroughService by overriding its OnProcessInput method and bind it to EnsLib.TCP.CountedXMLInboundAdapter (you may want to explore it further to see whether there are any useful methods there that might help you extract your XML).

In any case, a Business Service simply reads messages from input (File, TCP,  HTTP etc..) into an object. So, you also could send this object to a Business Process which will be processing your XML (it would likely need to extend %XML.Adaptor).

When you talk about writing data into SQL Server do you mean simply storing data into a Cache table? Or do you actually need to write to a real SQL Server? In the latter case, you would need a Business Operation that would bind perhaps to EnsLib.SQL.OutboundAdapter and will call your external database with the data extracted from your XML.

Hi Anna,

This error points to an Authentication issue. I see only two areas where the problem could be:

1) Incorrect username/pw (unlikely as you probably already checked it)

2) Mismatched Certificate

I assume you created an SSL Configuration in System Administration on the Server that is sending the email. Did you add any Certificate file to that configuration? If you did, I would check the Common Name (CN) - is it set to "smtp.gmail.com"?

I found a thread in which a setting in the Postfix configuration on the local server would cause a substitution of the CommonName with the Fully Qualified Domain Name obtained during DNS lookup - which was different from "smtp.gmail.com" and resulted in a similar error:

https://www.experts-exchange.com/questions/21813187/530-5-7-0-Must-issue...

Hi Rob,

This discussion I had with Mary George a couple of weeks ago might help:

https://community.intersystems.com/post/hl7-message-json-input-file

Here the issue is accepting HL7 messages encoded as JSON; you are trying to the reverse.

Basically, first, you would have to save your HL7 data to the correct EnsLib.HL7.Message object (in this case the message type is ADT). This Object has several useful Properties and Methods that would allow you to get the number of Segments in the message and also get a segment Object by index. You can loop over your segments and extract the data from each segment. 

Once you have the data in the Properties of the object, there are two ways to continue:

1) If you were on IRIS, your custom object could also extend %JSON.Adaptor. The data from the message that you stored in Properties could then be exported to a JSON string by calling yourObject.%JSONExport(). However, this option is not available in Cache product line.

2) The Cache product line has support for Dynamic Objects. These are JSON-like objects that cannot be passed in methods or in Ensemble messages directly - you would need to serialize them into a String or a Character Stream (but they would appear as JSON).

Here is the link to the documentation on JSON:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls...

(Because this is for IRIS is also includes a chapter on JSON Adaptors).

For example, in the same loop where you process your message, you could create a Dynamic Object for each Segment Field and use fieldObject.%Set("PID:"_i, valueOfPID:i) where i is the field number.

At the very end, where you have your complex Message Object you would 

Do msgDynamicObject.%ToJSON() - that will serialize the object to JSON String which you can then assign to a Property of an HttpRequest and post it to the Saleforce API. (I would create a separate Business Operation for that and use EnsLib.HTTP.OutboundAdapter). You would then need to process their JSON Response by doing

Set responseObject = {}.%FromJSON(HttpResponse.Data)

I hope this helps. Unfortunately, there is no easy way to do this.

I think Dmitriy is right. You would probably need to create an intermediate Object Model - a class or classes that model your XML Structure and another class that models your JSON Structure. The XML model extends %XML.Adaptor and imports your XML using tools like ImportXMLFromStream etc.., while the JSON model extends %JSON.Adaptor. Then you would step through your XML model, copying your XML properties to the properties of your JSON-aware class. 

Looking at the code, I see that  EnsLib.RecordMap.Service.FileService extends EnsLib.RecordMap.Service.Standard. You can try to create a custom Business Service (for example, EnsLib.RecordMap.Service.StreamService) extending the same parent class and override the OnProcessInput method to process your stream.

One issue you might have is, the FileService is bound to EnsLib.File.InboundAdapter that does a lot of work in the service. There are other types of out-of-the-box adapters - like EnsLib.HTTP.InboundAdapter which you could use here but your code would be customized.

Please note that this might not be officially supported by ISC.

Hi Mary,

I just went through a similar exercise, although in my case, I was importing JSON Data directly into SDA.

I can think of two ways of going about this:

The first one is what you are doing - that is, extracting JSON data using %Get(), %GetNext(), etc... and save the data to the Properties of the correct EnsLib.HL7.Message object (based on the message type). This is the most straightforward way but I found that it has drawbacks. The biggest one is that you have to initialize every variable that is being saved to the object Property. The reason is, you usually wouldn't know whether a certain field that you expect is populated or not (unless you have a way to check this by running the JSON files through some kind of schema first). So if you try to store something that you expect but does not exist in this particular JSON snippet, your processing would stop. Everything that is being set to a Property should be initialized (usually to ""). 

Because you are on IRIS, you have another option. IRIS now supports JSON Adaptors (not sure if your particular version supports it). You could create a Data Model: a separate Object for each HL7 message, which would all extend %JSON.Adaptor. This is rather like working with %XML.Adaptor: you should be able to read JSON messages directly without using Dynamic Objects. The advantage would be that before storing the data in the HL7 Message Object, you would store it in an intermediate object. I see the following advantages:

1) If you need to make a change because message structure changes, you would make a change only to the particular object, not the whole Business Process/Operation that would be extracting all of your JSON files.

2) It is easier to troubleshoot and maintain for the same reason.

3) It is easier to scale for different Participants (if they have differing JSON objects)

4) These objects would be %Persistent so you could pass them to Methods and in Ensemble messages.

Because you are dealing with JSON objects that mimic HL7 messages, this method may not give you more advantages. HL7 messages are discrete so if you create a Business Service for each message you could create a business process for each one as well, making it easier to troubleshoot. 

I hope this helps.

Hi George,

I would start by reading the following:

https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=EF...

This explains how the FTP Inbound Adapter works and how to create a Business Service with the ADAPTER parameter. As an example, take a look at EnsLib.FTP.PassthroughService  class.

An Ensemble FTP Service is essentially a poller - it would connect to the remote FTP server and pull files from a remote folder that is specified in the File Path property on the Business Service.

We had to deal with this issue once. The class is EnsLib.HL7.SearchTable which extends Ens.VDoc.SearchTable. You can extend EnsLib.HL7.SearchTable and add a new PropName "MSHSendingFacility" to the SearchSpec XData block:

<Item DocType=""  PropName="MSHSendingFacility>[MSH:3]</Item>

Or you can create a parallel class that extends %Persistent and Ens.VDoc.SearchTable directly and copy the XData block from EnsLib.HL7.SearchTable, adding the line above to the block.

Hi Ahmad,

I would start with the following:

Generating Alerts:

cedocs.intersystems.com/latest/csp/docbook/Doc.View.cls?KEY=EGDV_prog#EGDV_prog_alerts_defining

Monitoring Alerts:

https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?K...

There is more documentation about Alerts and and ISC Training (it is part of Study Materials for Certified Health Connect Expert Certification). 

Implementing alerts might let you sort the errors you are getting in the Event Log and decide which errors would need an alert that can then be sent to your Participant.

Hi Ahmad,

Your added code seems correct. Except for the where you add the remainder to the newline... $PIECE(line"|", 8) gets the content of the 8th column. remainder begins with the content of the 9th column, without any separator between them. So maybe $PIECE(line"|", 8)_"^"_remainder or $PIECE(line"|", 8)_"|"_remainder if you want to keep the original separator between 8th and 9th columns.

Also I assume you have Set tLineNumber = 0 before the while loop. Then, set tLineNumber tLineNumber + 1 makes sense at the beginning of the loop (I usually initialize at 1 and would have the increment at the very end of the loop).

Also, SetPatientContact takes "line" by reference... I assume there is a reason for that... usually strings in COS can be passed by value... unless you are going to change it inside SetPatientContact; then it makes sense.

Hi Victor,

I assume that you need Dynamic Objects in order to create some JSON Output (so you can call %ToJSON() on the object to create a JSON string). So in my opinion, the complexity of your JSON object will drive these decisions - for example, how deeply nested will your JSON need to be?

From some familiarity with TextReader, it records every XML Element into a Property. So in theory, each Property can have its corresponding Dynamic Object which will be a Name/Value pair: Name = Name of Property; Value = Value of Property (i,e. XML Text Node)

It is hard to say more without seeing your XML structure or your JSON Spec. You may find the following links helpful:

https://community.intersystems.com/post/convert-xml-json

https://community.intersystems.com/post/size-limitation-tojson-large-stream

https://community.intersystems.com/post/how-could-we-include-json-object...