Hi, I also help support an NHS trust using Ensemble, and it also has ever-growing PDF files in messages. We have our incoming PDFs as external file streams and it helps, though you have to bear in mind that the files are not going to be part of the cache backup for Disaster Recovery, etc. (Not sure about mirroring. I'd assume they don't get mirrored either as the contents are not in the journal.)

As yet, we don't have as big a problem as you - less messages and we only keep 92 days - but that is just as well as the PDF files are converted to base64 encoded in HL7 v2 messages, so they then do take up space in the database, and the journal, and the backup, which has resulted in the need to expand the disk space recently. I can recommend keeping Ensemble on a virtual server with disk expansion on demand.

I tend to think the problem is not going to go away whatever you do. I assume, like us, the PDFs come from 3rd party applications and they are always going to be producing  ever more and prettier documents as time goes by. So I recommend looking at more disk. :-)  / Mike


I won't claim this is an answer, because it's not quite the same and people may object to the structure, but here is one solution that is used quite a lot in code I look after. Basically, a subroutine is called and then tests are done and a Quit is used to drop out when a match is found. Often used for validation, something like this that returns a result in the zER variable:

V1 ; Validate ORGC
   S ORG=zORG WC2^hZUTV zER'="" Q
   I IPACC<9,'$D(^hIW(WAID)) zER="No details set up for ward" Q
   D WARDON^hILO1 LOCK zER="Ward in use" Q


Apologies for the old-fashioned code! However, you can see each test can be quite complex and using lots of variables, but it is easy to understand as long as you expect the structure to work that way.

This is very similar to the "clean code" solution of making the whole thing into a function that returns a value:

ClassMethod Main(val1 As %String, val2 As %String)
  write ..MyOutput(val1,val2)

ClassMethod MyOutput(val1 As %String, val2 As %String) As %String
  if val1 = 1 return "case 1"
  if val1 = 2, val2="*" return "case 2"
  return "default match"

Of course there are probably as many answers as there are Cache programmers!  :-)

Yes, we have something like that, except we use the letter "q" as the prefix, and we follow it by the programmer's initials so that it becomes a "personal" set that is left alone in all namespaces, dev-test and live. We also extend this to rule to globals and things inside the application like functions, screens, tasks, etc. The in-house configuration management system we use ignores them so they are left untouched. It's a useful convention.

(We might have used "z" like you, but it was already taken for "utility/library" stuff.)

Anyone know why we ended up with this strange behaviour? Why doesn't COS store 2 and "2" in the same way in lists? The rest of the programming environment is based around them being the same - (2="2") - as everything is a string until used otherwise. It may use "an optimized binary representation", but surely that's not really an excuse. Just curious.


I'm not sure what your "persistent objects" contain, but if the repeating data is in the form of strings or streams then perhaps you could put them into a XPATH document object ( %XML.XPATH.Document) and use the evaluator in that? I support a system that has a message with a transient property to hold the document created, so it's only done once (per processing), and a method that builds that property if needed and then calls the  EvaluateExpression method in that property for an expression supplied as a parameter. This is used in transformations to extract data to post into HL7 v2 messages, so the same calls should work in rules as well.

Of course the XPATH expressions may be no easier to define than your exporting and importing methods. I certainly struggled with them and in the end had to build lots of special inputs that added the huge long list of nested items that usually went at the front of each expression, resulting in calls like source.Pull("Baby","ep2:id/@extension") where "Baby" added a prefix of over 160 characters to find the baby section of the Mother's full document before tunneling down to the hospital number. If you already have a sub-section of the full document then you may not need as much.


Hi. We had a site upgrade from 2012.2.5 to 2017.1.0 last year, and it included a mirror. We had very few code changes needed - just an issue with it failing to save objects inside a Business Process where we had used some "unusual" structures. The upgrade itself went smoothly. The only issue was afterwards when the next backup was a "full" one instead of the scheduled "partial", using more space than expected. Our Production was much smaller than yours, with only about 120 items, and it is hard to say how much effort went into pre-release testing as it was "fitted in" around other work by a team of people. Maybe a couple of man  months?

To be honest, it all depends on how much custom or unusual code you have, and how much testing the customer wants. We upgraded a development namespace and re-ran test messages through all the important paths and compared the result before and after upgrade. Plus some connection testing to cover all the "types" we used: ftp, web service, HL7, etc.  In our case the testers included people from the user side, so they could decide when they were happy with it.

InterSystems were very helpful. We raised a call a few months before and they gave advice on testing and desk checked our detailed plan of the upgrade itself, including how to do the mirror.

Good luck.

I won't embarrass myself by listing the MUMPS code from 1991 that does this on our application, but I will comment that you need to work out how many birthdays have gone by so it must compare month and day values once the basic year subtraction has been done. It gets quite complicated. (Might also like to look at whether you need more than just a "years old", and also need months or days for very low values.)


I have used the  %XML.Writer class to create a document, but only for a fairly simple one that was destined for a SOAP outbound call. The SDA is tricky, so I would imagine using HealthShare (Ensemble) would be much easier. (I have used Ensemble classes like EnsLib.EDI.XML.Document that can be added to a message, etc. and used as the target for transformations once you have an appropriate document definition loaded in. Reduces the coding required, though not entirely as repeating groups are an issue.)


I'm just thinking that maybe we need more information as to why this is needed before recommending anything. If the network connection is good enough for mirroring, then why not just map the classes to a central repository? Perhaps all that is needed are security settings to prevent updates from the "slave" systems. Perhaps there is no network connection, in which case mirroring or shadowing is not possible, and what is needed is a good way to automate export/import to OS files.


Amir's answer with option 2 is what we did. The XML we sent had to be converted to allow it to be sent, so our code looked a bit like this:

Method ImportEpisode(pRequest As EnsLib.EDI.XML.Document, Output pResponse As Ens.Response) As %Status
 ; Use format 8 bit regardless of cache default (else Base64Encode gives ILLEGAL VALUE error)
 Set sendingXML = pRequest.OutputToString("C(utf-8)",.tSC)
  If $$$ISERR(tSC) Quit tSC
  $$$TRACE("Sending: "_sendingXML)
  Set sending = $system.Encryption.Base64Encode(sendingXML)
  Set tSC = ..Adapter.InvokeMethod("ImportEpisode",.result,sending,{plus some other id parameters})
  If $$$ISERR(tSC) Quit tSC
 Set resultXML = $system.Encryption.Base64Decode(result)


I hope this is useful to you.