Julian Matthews · Apr 10, 2025 go to post

Hey Colin.

I had a few minutes before a meeting so wanted to try recreate on IRIS instead of Cache and happened to have not yet restarted VSCode so didn't have the new extension versions installed.

I was able to run the example you tried in the old extension versions and observed no errors in the output for the language server, ran my updates, and then lost the ability to select the Language Server from the Output selection dropdown:

I would have liked to been able to completely recreate this in the new version, or at least give some indication of where the fault may be. But not being able to see the Output window for the language server like in your screenshot stops me in my tracks 😅

Julian Matthews · Mar 28, 2025 go to post

Hey Gary.

I know that this has been mentioned before, but actual line breaks in the HL7 like this will likely causes issues with the parsing of the HL7 message in your receiving system.

I believe your goal should either be (see line 35):

(but maybe swapping out "/.br/" with "\X0D\\X0A\" depending on the receiving system per Jeffrey Drumm's comment here)

Or (see line 21)

Julian Matthews · Mar 25, 2025 go to post

Good spot - I got so distracted by the %ConstructClone not being called with deep=1 that I didn't even notice that they were then still working from their pRequest variable.

Julian Matthews · Mar 25, 2025 go to post

Also, from a HL7 perspective, trying to add line breaks between your first and second OBX lines may not translate as you're expecting. Any receiving system trying to parse the HL7 will either ignore the break or will see it as malformed HL7.

Assuming that whatever system is receiving the HL7 is producing an output from the OBX:5 values, then you'll want to try adding in a new line and leave the OBX.5 blank, or if your receiving system supports line breaks, then this would usually be with adding a "/.br/" to the end of the OBX.5 in your first OBX (some systems may expect a different value to represent a line break, so your milage may vary).

Finally, as a general point, I would personally move this into a DTL. If there are actions going on in an ObjectScript Business Process leading up to the need to manipulate the message, then you can call the DTL from within your ObjectScript rather easily.

Julian Matthews · Mar 25, 2025 go to post

%ConstructClone() has a property of "deep" that is defaulted to 0, and it controls whether or not the new copy is a complete copy of the source, or if it merely copies the top level of the source object and then creates references to any sub objects contained within the source object. See more here.

My guess (without testing) is that the EnsLib.HL7.Message at a top level is being cloned with your code, but deep being false means that it's then still referencing the original EnsLib.HL7.Segment objects contained within the source EnsLib.HL7.Message object.

In your case, try changing to:

Set pOutput = pRequest.%ConstructClone(1)
Julian Matthews · Mar 13, 2025 go to post

Hey John.

I know it's not a direct answer to what you're looking to do, but we ended up writing a nodeJS app that acts an API that we pass HTML to and it returns a PDF, and we then have an Operation we call this API with.

Julian Matthews · Feb 26, 2025 go to post

Hey Thembelani.

You have a few options you can go for depending on what formats you are attempting to convert from.

You could look at something like using pandoc via the command line (as in, building an operation that does the command line calling, and then creating a set of request/response classes to call from a Process in your IRIS instance), or with with Python becoming more popular in IRIS, you could look what packages are available for doing what you're looking to do.

You could also look at libreoffice from the command line, but I found that headless libreoffice was a fair bit slower when running on Windows vs linux, so you may not get a lot of use with this approach depending on what you're running on.

Julian Matthews · Feb 25, 2025 go to post

What environment did you run the installer on?

If Windows, did you have IIS enabled before running the installer?

I had some oddities when attempting to install a version that no longer includes the PWS (including a few self inflicted), and found that the best method was to:

  1. Enable IIS
  2. Run the installer (making sure to select the option to "Configure local IIS web server for this instance" when the installer prompts)
  3. check the default site in IIS to make sure it's got all the "sites" required (api,csp,etc).
  4. Restart the "Default web site" in IIS
  5. Navigate to http://hostname/csp/bin/Systems/Module.cxw per ludwig's reply below, logging in with the password you set during install along with the username "CSPSystem"
  6. Attempt the test via "Test Server Connection"
    1. If that fails, go to "Server Access" select the instance and edit server, and make sure that the config has the Username/password set there as well before retesting.
Julian Matthews · Feb 11, 2025 go to post

2025 edit:

For instances where VSS can be utilised, the above scripts are no longer required and instead this entire setup can be replaced with the following steps:

  1. Set  EnableVSSBackup = 1 in iris.cpf or via the management portal
  2. Enable the vSphere snapshot option "Quiesce guest file system"

This is based on information found in the documentation here which towards the bottom reads:

InterSystems IRIS supports the Volume Shadow Copy Service (VSS) on Windows by acting as a writer on behalf of its databases. Copies of InterSystems IRIS databases included in a VSS shadow are physically consistent, although not logically consistent with respect to transactions, and therefore may be restored individually. To ensure the transactional integrity of these restored databases, journal files should also be restored. Only databases that are mounted at the time of VSS shadow creation are included in the VSS shadow.

The VSS writer for InterSystems IRIS can be started only by an administrator.

On Windows systems, EnableVSSBackup parameter in the iris.cpf file is set to 1 (enabled) by default. At InterSystems IRIS startup, the message “InterSystems IRIS VSS Writer started” is written to the messages log. When you create a VSS shadow copy, InterSystems IRIS automatically calls Backup.General.ExternalFreeze()Opens in a new tab and Backup.General.ExternalThaw()Opens in a new tab, as indicated by messages in the messages log.

IMPORTANT:

If you use VSS, make to use vSphere snapshot option Quiesce guest file system. This option invokes calls the VSS callbacks, which will freeze the database before the snapshot and thaw the database after the snapshot. The messages log will show VSS Writer: OnFreeze and VSS Writer: OnThaw.

In contrast, without this option, vSphere performs only a memory snapshot and the messages log does not contain these messages.

Julian Matthews · Feb 4, 2025 go to post

This is a great repo - thanks for sharing!

Looking through what you have here, it's certainly given me the inspiration to move away from a single class and instead breaking these out into a class per type.

Julian Matthews · Jan 31, 2025 go to post

There are a few options you could go for here. One would be to have your DTL go from your HL7 Message to Ens.StreamContainer, and then use a code block in your DTL to create and write the stream to store in the stream container.

Something like:

Set stream = ##class(%Stream.GlobalBinary).%New()
    Do stream.Write(yourTextString)
    set streamContainer=##class(Ens.StreamContainer).%New(stream)

(Above is untested, and you may need to set other values within the container.)

You can then use the operation class EnsLib.File.PassthroughOperation to then write the file out to your desired location.

Julian Matthews · Jan 30, 2025 go to post

Hi Alexander.

Sorry for being pedantic, but you're missing the ending quotation mark when setting the value of 'sql'

SET variable = 2000SET sql = "SELECT Column FROM Table WHERE ID = ?"DO##class(%SQL.Statement).%ExecDirect(, sql, variable)
Julian Matthews · Jan 29, 2025 go to post

I have faced similar in the past, and I found the easiest way to manage this was to add a router at an early stage of the process to act as a pre-processor which makes the initial change once. Something like:

Julian Matthews · Jan 9, 2025 go to post

Yes, I believe so.

The general principle is that the message is evaluated to see if it is eligible for being sent before the transform begins.

Julian Matthews · Jan 8, 2025 go to post

Hey Yuhong.

This would need to be achieved via the a rule as something like this:

If PV1:7.9 is then not equal to the value, then it will not hit the transform and will not then get sent to the target.

Julian Matthews · Dec 12, 2024 go to post

Hey Enrico.

The value of KeepIntegrity is set to True. I don't believe we have ever run the purge without including message bodies, but it's a non-production environment so stranger things have happened.

However, I did also just try running ##class(EnsLib.HL7.Message).Purge() in our production environment with the settings matching the retention period there, and it ran for a period, freed up around 5gb and then failed over to the other mirror as running the method consumed all of the ram on the box 😅
This namespace has been around for a long time (started life at around caché/ensemble 2014) and has likely accumulated a lot of baggage. I had a similar problem when trying to run Ensemble Orphaned Messages Purge Routine 

Julian Matthews · Dec 12, 2024 go to post

Hey David.

Your solution has caught my attention. What led you to this?

I have just run this against a non-production namespace that had a 30 day retention period and was sat at around 6GB. Running this command with the "pDaysToKeep" parameter set to 30 days has managed to free up 5GB and deleted 92158 messages, where I would have expected to have not deleted anything given the retention period for the purge task and the running of this method were the same.

Julian Matthews · Dec 5, 2024 go to post

My approach would be to make use of the OAuth 2.0 Client configuration via the Management Portal.

You can configure the Issuer Endpoint here, as well as add the details of the Client, Secret, etc.

To then make use of this configuration within an Operation, you can then do something like this:

Method AuthoriseMe(Output AccessToken As%String) As%Status
{
 //Set basic parametersSet tSC = $$$OKSet myscopes = "profile"Set clientName = ..ClientSet AccessToken = ""//Check to see if client is already authenticatedSet isAuth=##class(%SYS.OAuth2.AccessToken).IsAuthorized(clientName,,myscopes,.accessToken,.idtoken,.responseProperties,.error)

 //If we're not authorised already, we need to authorise ourselves.If isAuth=0{
   //Not Authenticated - authenticate client//Quit on error is used here as, if we're unable to get the token $$$QuitOnError(##class(%SYS.OAuth2.Authorization).GetAccessTokenClient(clientName,myscopes,,.error))
   $$$QuitOnError(##class(%SYS.OAuth2.AccessToken).IsAuthorized(clientName,,myscopes,.accessToken,.idtoken,.responseProperties,.error))
   }
 
 Set AccessToken = accessToken
 Quit tSC
}

Where ..Client is in the code snippet, the value of this will need to match the name of the client as configured in the management portal.

Julian Matthews · Nov 27, 2024 go to post

Hey Brad.

Apologies, I'm not entirely sure why I typed unicode in full upper-case when that's not present in the helper dialog or the drop down.

How confident are you that what you're receiving is actually unicode?

The adapter by default will look at what's in MSH:18 and will only use the selection in the adapter setting if this is blank in the message.

Firstly, try setting this to "!latin1" (without the quotes) to force it to operate as latin1 as per the support info for DefCharEncoding:

Putting ! before the encoding name will force the use of the named encoding and will ignore any value found in MSH:18.

If that fails, I'd then cycle through the other options starting with "!utf-8" and then one of the variants of Unicode available when using the drop down

Be careful - there are some overlaps when it comes to come encodings where things look fine until certain symbols come into play, at which point you end up with some interesting outputs. 

Julian Matthews · Nov 26, 2024 go to post

Hey Brad.

The adapter has two sets of options here which can lead to confusion. We first have the charset for the adapter for the File adapter elements, and then the Default Char Encoding for the HL7 adapter elements.

As a starting point, I would try changing the Charset setting to Binary, and then setting the DefCharEncoding to UNICODE to match what is in your header.

Julian Matthews · Nov 15, 2024 go to post

So to do what you're trying to do in your DTL, add in a code block and paste in the following:

Set CHUNKSIZE = 2097144Set outputStream=##class(%Stream.TmpCharacter).%New()
  Do source.EncodedPdf.Rewind()
  While ('source.EncodedPdf.AtEnd) {
    Set tReadLen=CHUNKSIZE
    Set tChunk=source.EncodedPdf.Read(.tReadLen)
    Do outputStream.Write($SYSTEM.Encryption.Base64Encode(tChunk,1))
  }
  Do outputStream.Rewind()
  Set Status = target.StoreFieldStreamRaw(outputStream,"OBXgrp(1).OBX:5.5")
  )

Yours is almost doing the same thing but, as Enrico points out with your code sample, you have the "Set tSC = tStream.Write($C(10))" line adding in the line breaks whereas my example has this excluded.

Separately, as alluded to by Scott, when adding the base 64 encoded PDF stream to the HL7, you'll want to use the StoreFieldStreamRaw method for the HL7. Trying to do a traditional set with a .Read() risks the input being truncated.

Julian Matthews · Nov 15, 2024 go to post

Hey Smythe.

Your Base64 has line breaks, so is breaking onto a new line which is then being read as a new line in the HL7.

Depending on what method you are using to convert the PDF to Base 64, you should have a setting to not use line breaks.

Julian Matthews · Oct 22, 2024 go to post

It's a bodge, but can you create a new namespace with the same name, delete the task, and then delete the namespace again?

Julian Matthews · Oct 21, 2024 go to post

Hey Anthony.

Depending on your version of Iris, I would recommend swapping out your use of %GlobalCharacterStream with %Stream.GlobalCharacter as the former is depreciated. Additionally, I would recommend swapping them out for their temp couterparts so you're not inadvertently creating loads of orphaned global streams, especially where you're dealing with files of this size.

Julian Matthews · Oct 18, 2024 go to post

It's a wild shot in the dark, but looking here: https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=ESQL_adapter_methods_creating#ESQL_transactions

has a try/catch where the catch has the following:

catch err{
    if (err.%ClassName(1)="common.err.exception") && ($$$ISERR(err.status)) {
      set tSC = err.status
    }
    else {
      set tSC = $system.Status.Error(err.Code,err.Name,err.Location,err.InnerException)
  }

If you try to recreate this, does the code you're looking for appear in either err.Code,err.Name,err.Location, or err.InnerException?

Julian Matthews · Sep 24, 2024 go to post

I thought that this would be a case of the Tilde being a special character for your target document due to its common use in HL7 for repeating fields. However, I ran a test to see what I got when trying this.

I created a transform for a PV1 segment, and attempted to set the value of PV1:1 to the output of the replace function and the input string contained a few commas:

I then ran this, and got this result:

Not only did it successfully replace the commas with tildes, but the virtual document now see's it as a repeating segment (even though the field is not repeating in it's specification).

I know this doesn't directly help you, but wanted to share my results in case it helped lead you to finding a solution. (for ref, this is from version 2022.1 of Iris For Health)