Jeffrey Drumm · Aug 27, 2023 go to post

I've often had missing images and other graphical anomalies unless I included these additional extensions for CPSFileTypes:

        CSPFileTypes csp cls zen cxw js png jpg css svg gif

Those extensions would represent static files ...

Jeffrey Drumm · Aug 25, 2023 go to post

Are you attempting to do this in a routing rule foreach or a DTL?

You're much better off working with a Business Process/BPL for this sort of thing. You would:

  • Create a context variable for the FT1 segment counter (ex. FT1counter)
  • use that as the key in a Foreach action, with request.{FT1()} as the property.
  • In the loop:
    • Add a Transform action to use a DTL to map the required PID/PV1/etc. fields to the record map, specifying the current FT1 segment values using source.{FT1(context.FT1counter):Fieldname} to select the current segment's fields.
    • Add a Call action for invoking the operation in the same loop, and you're done.
  • Save, add it to your production, and point the service at it.
Jeffrey Drumm · Aug 21, 2023 go to post

As part of the encryption negotiation process, there's an exchange of supported cypher suites between the client and server. If there's no match, no connection can be established. No need to force a specific cypher site; all available should be presented by the client during connection negotiation.

If upgrading to a current version of HealthShare/Health Connect is not an option, you could script the transfers outside of the production (batch/powershell/Python/Perl script running under Windows' Scheduler or called from ObjectScript in a Scheduled Task via $ZF(-100) ) and then use a File service/operation to pick them up for processing or drop them off for delivery.

Jeffrey Drumm · Aug 20, 2023 go to post

There have been updates to openssh within the last few years that retired older, less secure cypher suites. It's possible that 2017.2 may be old enough to be incompatible with newer versions of the ssh (which sftp relies upon) libraries.

Check with the vendor/customer at the other end of the connection to see if they've made recent changes to their version of ssh.

Jeffrey Drumm · Aug 18, 2023 go to post

export flags the variable to be available in child processes, but only affects the current account in an interactive shell.

jeff@ourawai03:~/tmp$ a="hey"
jeff@ourawai03:~/tmp$ echo $a
hey
jeff@ourawai03:~/tmp$ bash
jeff@ourawai03:~/tmp$ echo $a

jeff@ourawai03:~/tmp$ exit
exit
jeff@ourawai03:~/tmp$ export a="hey"
jeff@ourawai03:~/tmp$ bash
jeff@ourawai03:~/tmp$ echo $a
hey
Jeffrey Drumm · Aug 18, 2023 go to post

Well that might not have been quite correct ...

jeff@ourawai03:~$ export TEST="test"
jeff@ourawai03:~$ iris session ih
Node: ourawai03, Instance: IH

HICG>w $system.Util.GetEnviron("TEST")
test
HICG>
Jeffrey Drumm · Aug 18, 2023 go to post

Most likely because IRIS is running as user irisusr (or similar) rather than root.

Jeffrey Drumm · Aug 2, 2023 go to post

As others have mentioned, Size can be something of a "squishy" terrm depending on a variety of factors.

There's no single property that will work for all message classes; you do need to know the class and make sure it has a "Size" (or in the case of  EnsLib.HL7.Message) "FullSize" property. And you can certainly add it as a search criteria in the Message Viewer:

Jeffrey Drumm · Jul 30, 2023 go to post

I would expect such narrative/descriptive information to be included with the result in associated NTE (note) segments, since the reference range field is non-repeating text, maximum 60 characters in length (through HL7 2.6). The Reference Range field itself may have a directive to see the ranges in the NTE segment(s).

Jeffrey Drumm · Jul 24, 2023 go to post

I had previously posted a comment with a solution here, but received a request to turn it into a question/article. You can find it at this link.

Jeffrey Drumm · Jul 20, 2023 go to post

Also, a bit of friendly advice: Please refrain from wrapping your text with formatting that breaks word-wrap. It makes posts difficult to read on mobile devices, and can be problematic in desktop browsers too. I've removed the <pre> wrapper from your post and re-flowed the text to make it more accessible.

Thanks!

Jeffrey Drumm · Jul 20, 2023 go to post

I've worked with a number of integration solutions over the last few decades and not a single one of them would work with this vendor's perverse interpretation of HL7 communications out-of-the-box.

Health Connect / HealthShare can accommodate pretty much anything if you're willing to dip your toes into ObjectScript and write custom services and operations. But the OOB classes won't handle the vendor's requirements without subclassing/extending.

Acknowledgements

There are certainly variances in the types of acknowledgements received and even cases where acknowledgements are received out-of-band (Sunrise Clinical Manager, I'm looking at you). I've even seen implementations where multiple HL7 messages are sent, streamed in a batch, with envelope characters wrapped around the whole batch rather than the individual messages. In that case no ACK was expected. I wrote a custom service to handle that one.

Fundamentally, Health Connect is a request/response messaging system and violating that design presents some interesting challenges. Receiving the ACKs back asynchronously over the same connection is theoretically possible, but I wouldn't encourage the vendor to work that way ... every single future customer will be problematic for them.

Message Control IDs

I've never seen an engine that increments the message control ID automatically, although it's something that can be coded/customized in multiple solutions. The message control ID is defined by the sending application, not the middleware; setting the MSH:10 in the engine is usually an extraordinary measure.

HL7.org defines it as such:

This field contains a number or other identifier that uniquely identifies the message.

If the same message was re-sent with a different MSH:10 value, it would not be uniquely identified. In my opinion, at least.

Jeffrey Drumm · Jul 11, 2023 go to post

Depends on your platform (although that may have changed since the last time I configured an LDAP setup).

With Caché/IRIS running on *nix variants, the only option is STARTTLS, which is encrypted but uses the "standard" port 389. With Windows, I believe "LDAP over SSL" (aka LDAPS) is also an option, on port 686 by default.

Both will require that whatever certificate is served is valid for the load balancer. This is usually accomplished via a certificate Subject Alternative Name value.

Jeffrey Drumm · Jul 7, 2023 go to post

There's really no functional difference for your purpose between a Caché cube and an IRIS cube. Configure your Caché hosts in the ServerManager app just as you would with a Caché 2017 cube tray icon. The only difference is the logo 😉

Jeffrey Drumm · Jul 6, 2023 go to post

Are there child elements in Analysis()? For minOccurs="0" to be valid for Analysis(), all sub-members must be set to minOccurs="0" as well.

Not an XML expert here, but that's how I understand it.

Jeffrey Drumm · Jul 6, 2023 go to post

As I mentioned in my original post, my code was very bare-bones. While it supports authentication, it does not have any encryption features enabled.

You may also need to enable TLS/SSL and possibly STARTTLS in %Net.SMTP. It's also likely that you'll have to specify an alternate port; the default is 25.

Updated to support TLS/STARTTLS and alternate port:

Class User.Mail Extends %RegisteredObject
{

Property MailServer As %String [ InitialExpression = "hostname.domainname" ];
Property FromAddress As %String [ InitialExpression = "fromaddress@domainname" ];
Property EmailCreds As %String [ InitialExpression = "SMTPServer" ];
// May have to change value to 587 or 465 if STARTTLS required
Property SMTPPort As %Integer [ InitialExpression = 25 ];
// The TLS/SSL client configuration will need to be added in
// Management Console: System Administration | Security
Property SSLConfig As %String [ InitialExpression = "TLSClient"];
// Set to 0 if STARTTLS is not necessary
Property UseSTARTTLS As %Boolean [ IniitalExpression = 1 ];
Method Send(pToAddress As %String, pSubject As %String, pBody As %String = "") As %Status
{
    Set tEmail = ##class(%Net.SMTP).%New()
    Set tEmail.port = ..SMTPPort
    Set tEmail.SSLConfiguration = ..SSLConfig
    // STARTTLS: 1 for yes, 0 or omit the following line for no
    Set tEmail.UseSTARTTLS = ..UseSTARTTLS
    If pEmailCreds '= ""
    {
        #dim tCred As Ens.Config.Credentials
        Set tSC = ##class(Ens.Config.Credentials).GetCredentialsObj(.tCred,$CLASSNAME(),"Ens.Config.Credentials",..EmailCreds)
        Return:$$$ISERR(tSC) tSC
        Set tAuth = ##class(%Net.Authenticator).%New()
        Set tAuth.UserName = tCred.Username
        Set tAuth.Password = tCred.PasswordGet()
        Set tEmail.authenticator = tAuth
    }
    Set tEmail.smtpserver = ..MailServer
    Set tEmail.timezone="LOCAL"
    Set tMsg = ##class(%Net.MailMessage).%New()
    Set tMsg.From = ..FromAddress
    Do tMsg.To.Insert(pToAddress)
    Set tMsg.Subject = pSubject
    Set tMsg.Charset = "utf-8"
    Do tMsg.TextData.Write(pBody)
    Return tEmail.Send(tMsg)
}

}

Link to the %Net.SMTP documentation

If you're still having issues authenticating, you will need to reach out to your SMTP provider.

Jeffrey Drumm · Jul 5, 2023 go to post

When you say "SMTP Key," are you referring to a User ID and Password? If yes, then create a Credentials entry in the production's namespace via the Management Console's Interoperability | Configure | Credentials menu item and supply the name of the credentials entry as the Credentials property in the User.Mail class.

If there's some other form of authentication required, I'm not sure what to tell you; %Net.SMTP supports only user id/password as far as I know.

The organization doesn't have an Exchange server or GSuite domain? Both can function as SMTP relays, and work with the InterSystems %Net.SMTP class.

Jeffrey Drumm · Jul 5, 2023 go to post

Did you create an XML schema from an xsd? If yes, is the "Required" attribute set to No for the elements you don't want populated?

Jeffrey Drumm · Jul 4, 2023 go to post

Not with this code, no. I interpreted your original request literally, assuming that you wanted to send the email directly from within a custom service.

If you wish to have an Email operation in the production that handles the delivery of messages from routing rules, that's a bit more work to create, but in the end more versatile and easier to support.

If you don't care about having an operation, you could create a class that extends Ens.Rule.FunctionSet that wraps the original class in such a  way that you can send email from a rule:
 

Class User.Util.FunctionSet Extends Ens.Rule.FunctionSet
{
ClassMethod SendEmail(pToAddr As %String, pSubject As %String, pMessageBody As %String) As %Status
{
	Set tMail=##class(User.Mail).%New()
	Set tSC = tMail.Send(pToAddr, pSubject, pMessageBody)
}
}

You could then call it from a rule (the assign simply lets you call it and optionally do something with the returned status):

Jeffrey Drumm · Jul 4, 2023 go to post

Do you actually want the logic to check for the specific fields in the service itself? A Business Process with a rule can do this, and would be a bit more "analyst friendly."

Here's a very bare-bones method to send email from within a custom Business Service:

Class User.Mail Extends %RegisteredObject
{

Property MailServer As %String [ InitialExpression = "hostname@domainname" ];

Property FromAddress As %String [ InitialExpression = "fromaddress@domainname" ];

Property EmailCreds As %String [ InitialExpression = "SMTPServer" ];

Method Send(pToAddress As %String, pSubject As %String, pBody As %String = "") As %Status
{
    Set tEmail = ##class(%Net.SMTP).%New()
    If pEmailCreds '= ""
    {
        #dim tCred As Ens.Config.Credentials
        Set tSC = ##class(Ens.Config.Credentials).GetCredentialsObj(.tCred,$CLASSNAME(),"Ens.Config.Credentials",..EmailCreds)
        Return:$$$ISERR(tSC) tSC
        Set tAuth = ##class(%Net.Authenticator).%New()
        Set tAuth.UserName = tCred.Username
        Set tAuth.Password = tCred.PasswordGet()
        Set tEmail.authenticator = tAuth
    }
    Set tEmail.smtpserver = ..MailServer
    Set tEmail.timezone="LOCAL"
    Set tMsg = ##class(%Net.MailMessage).%New()
    Set tMsg.From = ..FromAddress
    Do tMsg.To.Insert(pToAddress)
    Set tMsg.Subject = pSubject
    Set tMsg.Charset = "utf-8"
    Do tMsg.TextData.Write(pBody)
    Return tEmail.Send(tMsg)
}

}

You can set the properties in the class to represent default values for your organization. Otherwise, you would call it like so:

	Set tMail=##class(User.Mail).%New()
	Set tMail.MailServer = "smtpserver.mydomain.com"
	Set tMail.FromAddress = "healthshare@mydomain.com"
	Set tMail.EmailCreds = "SMTPServer"
	Set tSC = tMail.Send("user@mydomain.com","This Thing Happened","And Here's what it is")

You will need to obtain access to an SMTP relay host; HealthShare does not supply one. There will also be adjustments required for the class if encryption is required, and if you need to supply credentials for authentication to the SMTP server, you will need to create a Credentials entry in the Management Console and supply its name for the EmailCreds property.

Jeffrey Drumm · Jul 3, 2023 go to post

Is there more to this than just disabling the web application? That simply causes the "Open" button in the Business Rules List or Business Process to display an authentication page, which does not allow one to log on. Until the new rules editor is sorted, it would be nice not  to require extra steps each time a rule needs editing.

EDIT: Oops ... disabled the wrong service (/api/interop-editors). Disabling the right service (/ui/interop/rule-editor) does the job.

Jeffrey Drumm · Jul 3, 2023 go to post

Do you need a local installation of the database itself on these machines or just the client tools (system tray launcher, Studio, terminal, etc.). If the latter, you can install the latest version of the client kit rather than 2017. The tools are backwards compatible to at least 2015, and likely earlier.

Jeffrey Drumm · Jun 20, 2023 go to post

The RecordMap services have options for handling errors:

I'm not sure whether they do actual datatype validation on fields in the RecordMap (wouldn't make much sense to let you specify datatypes without supporting validation though).

But if your extra field didn't trigger some sort of warning or error in the Event Log 🤷‍♂️

Jeffrey Drumm · Jun 1, 2023 go to post

@Eduard Lebedyuk is correct (yeah, he's always correct 😁), you can't use a variable for the target in a business rule.

You can do this in a custom BP (COS or Python) or a BPL-based BP, though. The BPL <call> action specifically supports a context variable as a destination:

The variable would be assigned the name of the BH to send to prior to invoking the call.

Jeffrey Drumm · May 31, 2023 go to post

It's not clear from your post whether you're using any of the healthcare-related variants of the InterSystems suite.

If you're using HealthShare, Health Connect or IRIS for Health, support is included for receiving HL7 messages and storing them via multiple mechanisms. TCP/IP MLLP, HTTP, and file based services are all supported natively.

When configuring a Business Service to receive HL7 messages via TCP/IP MLLP, you would select the EnsLib.HL7.Service.TCPService class and configure the Message Schema Category to "2.5" or "2.5.1" depending on your specific HL7 version. You would also need to configure the port on which to receive the messages, the target Business Process or Operation to act on them, and possibly a few other settings.

Messages received through that service will then be inserted into the Interoperability message store, regardless of HL7 message type; message headers are created to provide tracking/status information and the messages are databased.

If your need is to index and retrieve those messages based on content (patient name, account number, gender, etc), there are additional steps to fetch the specific data elements needed from the messages themselves and populate associated fields in a database structure that addresses those needs. That's something you would need to design; you would populate it using interoperability production components tailored to your filtering requirements and database design.