Yes, a new schema can do this along with a transformation.
If you have an existing schema, probably best to clone it and then edit the clone to speed things up.
- Log in to post comments
Yes, a new schema can do this along with a transformation.
If you have an existing schema, probably best to clone it and then edit the clone to speed things up.
Take a look at this documentation. It goes in to a lot of detail with some useful diagrams.
*edit*
There is also this useful Mirroring 101 forum post.
Hi Alexandr.
If you are looking to run a task at specific times, you could create a new task which extends %SYS.Task.Definition to then be selectable as an option from the task manager.
For example, I have a folder which I need to periodically delete files older than x days.
To achieve this, I have a class that looks like this:
Class DEV.Schedule.Purge Extends %SYS.Task.Definition{Parameter TaskName = "Purge Sent Folder";Property Directory As %String;Property Daystokeep As %Integer(VALUELIST = ",5,10,15,20,25,30") [ InitialExpression = "30" ];Method OnTask() As %Status{Set tsc = ..PurgeSentFolder(..Directory,..Daystokeep,"txt")Quit tsc}Method PurgeSentFolder(Directory As %String, DaysToKeep As %Integer, Extention As %String) As %Status{// Calculate the oldest date to keep files on or afterset BeforeThisDate = $zdt($h-DaysToKeep_",0",3)// Gather the list of files in the specified directoryset rs=##class(%ResultSet).%New("%File:FileSet")Set ext = "*."_Extentiondo rs.Execute(Directory,ext,"DateModified")// Step through the files in DateModified orderwhile rs.Next() {set DateModified=rs.Get("DateModified")if BeforeThisDate]DateModified {// Delete the fileset Name=rs.Get("Name")do ##class(%File).Delete(Name)}// Stop when we get to files with last modified dates on or after our delete dateif DateModified]BeforeThisDate set tSC = 1}quit tSC}}Then I created a new task in the scheduler, selected the namespace where the new class exists, and then filled in the properties and times I want the task to run.
Hi Andrew.
I don't think the operation to a downstream system would be the appropriate place for adjusting the HL7 content.
Using Transforms within a router will be the best approach for this, and while it might seem a little cumbersome creating a transform per message type you should be able to remove some of the leg work by using sub-transforms.
For example; if you were looking to adjust the datestamp used in an admission date within a PV1, rather than complete the desired transform work in every message transform that contains a PV1, you create a sub-transform for the PV1 segment once and then reference this in your transform for each message type. If you then have additional changes required to the PV1 which is message specific (say, a change for an A01 but not an A02) you can then make this change in the message specific transformation.
As far as transforming datetime in HL7, I have faced my share of faff when it comes to this with suppliers. The best approach from within the transforms is to use the built in ensemble utility function "ConvertDateTime" listed here.
Could you generate a message to your ens.alert (or equivalent) from the BO, and then immediately call ##class(Ens.Director).EnableConfigItem to disable the business process?
I have to say, the documentation is very detailed with regards to mirroring. Here is a link.
I had this exact issue last week, and this is how I got around it. For clarity, I wanted to pass the Dynamic Object from a process to an operation.
I created my dynamic object within the Process, and then used the %ToJSON Method to put the JSON within a GlobalBinaryStream (which can be passed through the request).
In the Operation, I then use the %FromJSON Method of DynamicAbstractObject to then have the Dynamic Object within the operation.
Within the Production under Production Settings, you should currently find a Document button which will produce a report of everything within your production. However, depending on the production size, this could easily be overkill.
2019 brings a new option called "Interface Maps" where you can get a graphical view of message flows, along with the processes, routers, and rules for the individual sections of your production. It's a lot cleaner than using the Documentation generator, but if you're needing to show multiple routes, you're likely to want to go through each one and take a screenshot. I also found that where I have a router with lots of rules/transforms, I need to scroll the screen to see the bottom of the display.
Hi Murillo.
You will need a when to specify when the action should be triggered, and using WHEN 1 is the easiest way of achieving what you need, however I think I see your problem with the ordering.
The reason why your rule only sends to ManageRIS when the ManageRIS item is above the OrderStatus check is because of the RETURN action.
What the RETURN does is stop any actions beyond that point from being processed if the conditions of the WHEN are met. So in your case: When Orderstatus =160, transform and send, and then do not process anything else and move on to the next message.
So what you will want to do is remove the RETURN block to allow the second WHEN in rule two to be run regardless of the outcome of the first WHEN.
Hi Cedric.
I think you should be able to achieve this using the routing rules by doing something like this: (Please ignore my blank condition in the example)
It should complete the first send (which is your transform to A04) and then it should send your source ORM message on the second send.
After working with WRC, I now have an answer.
If the DataSet property points to the MutabaleDateSet property then GetValueAt will return a stream if more than 32k.
If (as in my situation) the DataSet property points to the FixedDateSet property then GetValueAt will return a string of 32648.
The workaround provided by WRC did the trick for me:
Try {
Set setStatus = $$$OK, getStatus = $$$OK
If 'pInput.Modified{
Set setStatus = pInput.SetValueAt(pInput.GetValueAt("DataSet.DocumentTitle",,.getStatus),"DataSet.DocumentTitle")}}
Catch e {
Set setStatus = e.AsStatus()}
If setStatus && getStatus{
Set X = pInput.GetValueAt("DataSet.EncapsulatedDocument",,.tSC)}
However there was an alternative of using the CreateFromDataSetFileStream method of the class EnsLib.DICOM.Document:
set tSC = ##class(EnsLib.DICOM.Document).CreateFromDataSetFileStream(pInput.DataSet.FileStream,pInput.DataSet.TransferSyntax,.dicomDocFromStream)
If tSC Set X = dicomDocFromStream.GetValueAt("DataSet.EncapsulatedDocument",,.tSC)
In both of these options, the next step is to then check tSC to see if X is a stream or string and then work from there.
Assuming you're talking about the online backup function within ensemble/HS/etc - I use a task that will run a purge based on the age of the file, and then runs the backup. The order of the two methods is important if you set your retention period to 0, as you'll end up deleting the backup you just made (I'm neither confirming or denying if this happened to me).
Class Live.Schedule.BackupPurge Extends %SYS.Task.BackupAllDatabases{
Parameter TaskName = "Backup With Purge";
Property Daystokeep As %Integer(VALUELIST = ",0,1,2,3,4,5") [ InitialExpression = "1" ];
Method OnTask() As %Status{
//Call PurgeBackup Method, Return Status
Set tsc = ..PurgeBackups(..Device,..Daystokeep)
Set tsc = ..RunBackup()
Quit tsc
}
Method PurgeBackups(Directory As %String, DaysToKeep As %Integer) As %Status{
// Calculate the oldest date to keep files on or after
set BeforeThisDate = $zdt($h-DaysToKeep_",0",3)
// Gather the list of files in the specified directory
set rs=##class(%ResultSet).%New("%File:FileSet")
do rs.Execute(Directory,"*.cbk","DateModified")
// Step through the files in DateModified order
while rs.Next() {
set DateModified=rs.Get("DateModified")
if BeforeThisDate]DateModified {
// Delete the file
set Name=rs.Get("Name")
do ##class(%File).Delete(Name)
}
// Stop when we get to files with last modified dates on or after our delete date
if DateModified]BeforeThisDate
set tSC = 1
}
quit tSC
}
Method RunBackup() As %Status{
d $zu(5,"%SYS")
Set jobbackup = 0
Set quietflag = 1
Set Device = ..Device
Set tSC = ##class(Backup.General).StartTask("FullAllDatabases", jobbackup, quietflag, Device, "0")
Quit tSC
}
}
The downside to this is you will end up with an extra backup file in your backup location if you run the backup manually as the purge is based on the file age. Not a massive problem unless you're storing in a location with a finite amount of disk space.
The method RemoveSegmentAt returns a status, not the message with the segment removed.
So your HL7 message with the EVN removed is still "newreq" and it is this that you want to pass on.
Hi David.
I raised this with WRC following my recent upgrade to IRIS, and the reason given was that the auto-refresh interferes with a new feature that logs out inactive users.
What you need to do (in a Terminal session) is set a global as follows:
>set ^%SYS("Portal","EnableAutoRefresh")=1
Hi Vandrei.
Have a look at the learning site: https://learning.intersystems.com
Here you will find learning paths for what you want such as Building Your First Application with Caché and Learn Caché for Developers.
There is also classroom training that can be arranged with Intersystems, but it might be good to give yourself exposure with the above before considering such courses.
Hi Marta.
I just took a look at the course to see what was what, and on the lab site generated, there is a link for the terminal on the home page:
This then prompts you to log in with the credentials provided when you first created the lab session, and you end up with an oversized terminal window the size of your browser ![]()
Hi Joao.
I have a task that runs every x mins to check a folder for files older than y mins, and it then sends an alert to Ens.Alert to then trigger an alert to our team. The logic is, if the file has been sat in the folder for more than the defined period, it is effectively "queued".
However, as I went to dig it out, I have realised I made a mistake with subtracting the seconds from the Horolog near midnight...
I'll try clean it up, and then see if it safe to submit here :)
Hi Joao.
Here is the working version of the code I referred to - I'm sure someone here can probably look at it and refactor it into 4 lines, but it does the job ![]()
Class DEV.Monitoring.FolderMonitor Extends %SYS.Task.Definition{Parameter TaskName = "Folder Queue Monitor";Property MonitoredFolder As %String;Property AgeToAlert As %Integer(VALUELIST = ",5,10,15,20,25,30,35,40,45,50,55,60") [ InitialExpression = "30" ];Property SMTPServer As %String;Property SMTPPort As %String [ InitialExpression = "25" ];Property SMTPUsername As %String;Property SMTPPassword As %String;Property Recipient As %String(MAXLEN = 256);Method OnTask() As %Status{Set tsc = ..CheckFileAgeInFolder(..MonitoredFolder,..AgeToAlert,"*.*")Quit tsc}Method CheckFileAgeInFolder(Directory As %String, AgeToAlert As %Integer, Extention As %String) As %Status{set tSC = $$$OK// Calculate the file age that we want to trigger the alert// $h is Horolog. HOROLOG contains a character string that consists of two integer values// separated by a comma. These two integers represent the current local date and time in // Caché storage format. These integers are counters, not user-readable dates and times.// The format is ddddd,sssss//First, take the HOROLOG into a variable so that it isn't different everytime we need to use the HOROLOG.Set pHorolog = $H//Now break down each part of the Horolog into the days and secondsset currentdate = $PIECE(pHorolog,",",1)set currenttime = $PIECE(pHorolog,",",2)// We multiply the AgeToAlert by 60 to convert the AgeToAlert to Seconds so we can use it easily with the HOROLOG.set pAgeToAlertSeconds = AgeToAlert*60// At this point, we want to subtract the age to alert from the curent time in seconds. HOWEVER, we can't go negative without going back a day.// So, to get around this, I have attempted to bodge the checks. If the age to alert in seconds is greater that the // current time, we then go back to before midnight... I dunno, I'm just making this up and hoping it works.If pAgeToAlertSeconds>currenttime {Set currentdate = currentdate-1Set timediff = pAgeToAlertSeconds-currenttimeset adjustedtime = 86400-timediff}Else{set adjustedtime = currenttime - pAgeToAlertSeconds}// We create the value "triggered" with the value of 0 now, so that we can check to see if we need to alert.set triggered = 0// We then build a new Horolog by concatinating the currentdate and adjustedtime values// within the $zdt function. The $zdt function then converts the Horolog to a date/time // which can be used for comparing against the datemodified value of the files being checked.set BeforeThisDate = $zdt(currentdate_","_adjustedtime,3)// Gather the list of files in the specified directoryset rs=##class(%ResultSet).%New("%File:FileSet")do rs.Execute(Directory,Extention,"DateModified")// Step through the files in DateModified order and compare their date modified // with the BeforeThisDate Value to see if any files are old enough to active the triggerwhile rs.Next() {set DateModified=rs.Get("DateModified")if BeforeThisDate]DateModified {// trigger the triggerset triggered = 1}// Stop when we get to files with last modified dates on or after our fileage adjusted date to // avoid checking files that don't need to be checked at this time.if DateModified]BeforeThisDate set tSC = $$$OK}// Evaluate if trigger has been triggeredif triggered = 1 {//Create and send an emailSet newSMTP=##class(%Net.SMTP).%New()Set newSMTP.smtpserver=..SMTPServerSet newSMTP.port=..SMTPPortSet newAuthenticator=##class(%Net.Authenticator).%New()Set newAuthenticator.UserName=..SMTPUsernameSet newAuthenticator.Password=..SMTPPasswordSet newSMTP.authenticator=newAuthenticatorSet newEmailMessage=##class(%Net.MailMessage).%New()Set EmailCount = $LENGTH(..Recipient,",")For i=1:1:EmailCount{Do newEmailMessage.To.Insert($PIECE(..Recipient,",",i))}Set newEmailMessage.From="DoNotReply@imadethisemailupandlackcreativity.co.uk"Set newEmailMessage.Subject="Queue Building in folder:"_..MonitoredFolder#Dim MessageTextSet MessageText = "<html><head><style>th#la {padding-left: 10px; padding-right:20px; padding-bottom:15px; text-align: left;}td#la {padding-left: 10px; padding-right:20px; padding-bottom:5px; text-align: left;}</style></head><body><table border=""0"" cellpadding=""0"" cellspacing=""0"" height=""100%"" width=""100%"" id=""bodyTable""><tr><td align=""center"" valign=""top""><table border=""0"" cellpadding=""20"" cellspacing=""0"" width=""1000"" id=""emailContainer""><tr><td align=""center"" valign=""top""><table border=""0"" cellpadding=""10"" cellspacing=""0"" width=""100%"" id=""emailHeader""><tr><td bgcolor=""#0072CE"" align=""center"" valign=""top"">"// This is the Header of the email.Set MessageText = MessageText _ "<h1 style=""color: #ffffff; ; font-size: 28px;"" >Queue Building in folder "_..MonitoredFolder_"</h1>"// This is the HTML for the start of the body which is built up within a table for structureSet MessageText = MessageText _ "</td></tr></table></td></tr><tr><td align=""center"" valign=""top""><table border=""0"" cellpadding=""20"" cellspacing=""0"" width=""100%"" id=""emailBody""><tr><td align=""left"" valign=""top"">"// This is the Body of the email. These lines can be repeated // as many times as needed to act as the lines of text in the email.Set MessageText = MessageText _ "<p style=""color: #153643; ; font-size: 18px;"">Files are queueing in the folder "_..MonitoredFolder_".</p>"// This line closes off the table secion created aboveSet MessageText = MessageText _ "</td></tr></table></td></tr><tr><td align=""center"" valign=""top""><table border=""0"" cellpadding=""10"" cellspacing=""0"" width=""100%"" id=""emailFooter""><tr><td bgcolor=""#0072CE"" align=""center"" valign=""top"">"// This is the Footer of the emailSet MessageText = MessageText _ "<p style=""color: #ffffff; ; font-size: 12px;"">This email was generated automatically by xyz corp.</p>"// This is the end of the HTML.Set MessageText = MessageText _ "</td></tr></table></td></tr></table></td></tr></table></body></html>"Set newEmailMessage.IsHTML = 1Do newEmailMessage.TextData.Write(MessageText)///Send the EmailSet tSC=newSMTP.Send(newEmailMessage)Quit tSC}else{Quit tSC}}}Hi Vic.
This is what I expected to work as well, but for some reason the messages don't route if I try this method.
For example this works:
But this doesn't:
Even though I'd expect the "HL7.{PIDgrpgrp(1).ORCgrp().OBR:UniversalServiceIdentifier.identifier}" to return something along the lines of "<ABC><XYZ><LMN>" and therefore would be picked up by the contains function.
Ahh, I missed the difference between { and (.
I'll give that a try and let you know how that goes.
*EDIT*
Yep, that did it. Thanks Vic!
Hi Vic.
I hadn't considered that the change in that post would have that effect, as the change was to bring back the ability to set a refresh rate on certain pages - good spot. The Activity Monitor page certainly suggest it should always be refreshing every 60 seconds anyway, so I guess it's another section of the product that had its refresh broken by the changes in 2019... ![]()
I was hoping to avoid reinventing the wheel as the dashboard is otherwise functional, but I guess we can't win them all.
I don't suppose you know if 2020 contains any improvement in this area?
The approach I ended up taking was to write a process that uses the source original file name to determine the destination, and it then sends the file to each operation it needs to using a custom request message which includes the stream + other bits of info I want passed to the operation.
If you're using a service that is using EnsLib.File.InboundAdapter (for example EnsLib.File.PassthroughService) then the syntax you are using should work.
However, the service may need "TargetConfigNames" to be set to a process in your production for it to actively consume a file.
Hey Yone.
There's two issues with your useage of Ens.Util.Time.
The following should work for you:
Set fecha = "2021-08-18T07:44:14.180+0000"
Set nuevaFecha = ##class(Ens.Util.Time).ConvertDateTime(fecha,"%Y-%m-%dT%H:%M:%S.%N%z","%d/%m/%Y")
Write nuevaFechaWhich should output "18/08/2021"
EDIT: And as Alexander points out - the forth argument outputs the status of your calling of the class, which is very useful for debugging these kinda of issues, but you may want to check for the error in your code just in case the source of the date is formatted incorrectly or some other issue occurs.
Ahh, beat me to it ![]()
Hey Oliver.
This Webinar is over on learning.intersystems.com and includes the code sample as an xml that you can import into your environment. The link to the code can be found on this page: https://learning.intersystems.com/course/view.php?id=623
Make sure you give the code a good read before you try to run it ![]()
EDIT: Direct link to the code is here: https://learning.intersystems.com/mod/resource/view.php?id=2388
Hey Kyle.
I would approach this by creating a separate count to what is created by the DTL for for each loops, and then iterate through the list of ID's and only move across the ones you want, while not leaving a blank entry in the target message (and remembering to increment the separate count for each ID you copy to the target message).
I have made an example within a common PID transform:
.png)
This then gave me:
.png)
Don'r worry, this has caught me out too many times ![]()
Your "if" is missing the k1 count in the ().
Should be:
Hey Kyle - it looks like I gave you some bad advice. I have just noticed that you're working with a pre-2.4 HL7 schema, and my examples were all based on HL7 2.4 (even where I corrected myself in the second reply). Also in my corrections, I continued to provide examples for a common PID transform, whereas your screenshot shows you doing the transform at the whole message level.
This should work for you:
.png)
.png)
Sorry about that - hopefully it works for you now ![]()
Hey Yone - there's a few things going on here, but I'll try to explain as best I can.
For starters, calling "request.GetValueAt("5:3.2")" is returning to you the value of segment "3.2" of whatever is row 5 of your inbound message. If in your case this is an OBX, then this is only returning the content of OBX:3.2 (Which is some variation of Observation Identifier Text).
When you are then outputting the entire HL7 message to a string and then running "$PIECE(request.OutputToString(), "OBX", 2)" you are getting every character in the string after the literal text "OBX"
So if we use this fake message as an example:
MSH|blahblahblah
PID|blahblahblah
OBR|blahblahblah
NTE|1|blahblahblah
OBX|1|ST|NA^SODIUM^L||139|mmol/L|136-145|N|||F
Calling "request.GetValueAt("5:3.2")" and then evaluating its length would give you 6, as OBX:3.2 is "SODIUM" in the above. If you then output the above into a string and then checked the length of the output from "$PIECE(request.OutputToString(), "OBX", 2)" you would be evaluating all that is highlighted above.
Now with that being said, it is not a good idea to make assumptions that a specific row within a message will be a set segment type. Certainly in my case, all it would take is for a few NTE segments to be present in a message, and "5:3.2" could easily be part of an OBR or NTE.