go to post Julian Matthews · Mar 19, 2021 I believe that the HL7 Router which you are using to send to the operation should have a configuration item for setting the response target (called ResponseTargetConfigNames). So the route the message would take is:
go to post Julian Matthews · Feb 23, 2021 Hey Scott. If you were open to having a Service in your production which is what your function sends its two variables (and the service then passes it onto your Operation) you could have something like this: ClassMethod SendPage(PagerNumber As %String, Message As %String) As %Status { //The String passed to Ens.Director must match a service name within the active production set tsc = ##class(Ens.Director).CreateBusinessService("Pager From Function Service",.tService) if ($IsObject(tService)) { set input = ##class(osuwmc.Page.DataStructures.Page).%New() set input.PagerNumber = PagerNumber Set input.Message = Message set tsc = tService.ProcessInput(input) Quit tsc } else { Quit 0 } } and then you have a custom service that looks a little like this: Class osuwmc.Services.PageService Extends Ens.BusinessService { Property TargetConfigName As Ens.DataType.ConfigName; Parameter SETTINGS = "TargetConfigName"; Method OnProcessInput(pRequest As osuwmc.Page.DataStructures.Page) As %Status { set tsc=..SendRequestAsync(..TargetConfigName, pRequest) Quit tsc } } Then when you add the service to your production (remembering to match it to the name declared in the service code), you can select your target operation as a config item, and when the function is triggered it should go Function -->Service-->Operation. Edit: my Service Class example had an error in the SETTINGS parameter, I have corrected it.
go to post Julian Matthews · Feb 23, 2021 Hey Scott. I think you can achieve this by setting the second parameter to a comma delimted list of the request names, and then pass each value afterwards (in the same order you have the names). For example: Method Sample(pReq As osuwmc.Page.DataStructures.Page, Output pResp As %Net.HttpResponse) As %Status { Set FormItems = "PNo,PMsg" set tSC = ..Adapter.Post(.tResponse,FormItems,pReq.PagerNumber,pReq.Message) if ('tSC) { $$$LOGERROR(tSC) } quit tSC }
go to post Julian Matthews · Feb 18, 2021 Hey Mufsi, I had this happen to me and according to WRC this is a known issue fixed in 2020.1.1. It is caused by an attempt to get an exclusive lock on a specific node for the mirrored database (which is read only) where the task is being scheduled. There is an alternative workaround to the steps you took by using the ^TASKMGR interface from the %SYS namespace in a Terminal session as it doesn't try to perform any write operations on the read-only mirror databases.
go to post Julian Matthews · Feb 16, 2021 Just to expand on Davids response - the File Outbound adapter will create a new file per message assuming the filename settings are configured so that the filenames it produces are unique per message. If you were to set the Filename property to a specific value rather than use the timestamp specifiers (so for example set it so that the filename is output.txt) then each message should write the data at the end of the file, giving you a single file with all of the entries.
go to post Julian Matthews · Jan 27, 2021 You won't be able to do this using the built in viewer, however you can query the sql tables directly and then interrogate the results using your preferred method. For example, I had a spike in activity on the 20th of Dec which made a disk fill a lot more than usual, but the purges meant I couldn't just check the messages as they had since been deleted. So I ran the following in the SQL option in the management portal: SELECT * FROM Ens_Activity_Data.Days Where TimeSlot = '2020-12-20 00:00:00' I then used the print option to export to CSV to then use a simple Pivot table to work through each Host name to see what had a dramatically higher number of messages to what I would usually expect. (I actually exported a few days worth of data to compare between days, but hopefully you're getting the idea) You could always explore using Grafana to produce a nice visual representation of the data that you've surfaced using a method like this.
go to post Julian Matthews · Nov 24, 2020 Hey Ahmad. If this is all happening in the one production, then you will have an inbound service for each port. You could setup a router with a specific rule for each destination, and then use the rule constraint to be restricted on the source service. That way, if you point all three services at the one router, it will only use the rule within the router that has the service listed as a constraint. For example: I hope that helps!
go to post Julian Matthews · Nov 11, 2020 Then in that case, I believe so. My expectation is that setting the AckMode of your service to "Application" should send your operation the HL7 message and then wait for the ACK your Operation gets to then use as an ACK to the sending application.
go to post Julian Matthews · Nov 4, 2020 So if your HisEmrRouter is running with 10 jobs, which then sends to ADTRoutingRule that has half the number of jobs available, then I can see that this would introduce some form of a bottleneck. It's worth considering the impact of increasing your app pool beyond 1 when working with healthcare data, and the details of which are noted here: https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls... It also mentioned not having the poolsize exceed the number of CPU (I assume it means CPU cores) , however this has been contested in the past by other users as seen by the comments of Eduard here: https://community.intersystems.com/post/ensemble-introduction-pool-size-... If FIFO is not required for your use case, I would at the very least try setting the poolsize to the same value.
go to post Julian Matthews · Oct 6, 2020 Hey Yone. It doesn't look like There was an ORU^R30 in the standard until HL7 2.5, so that would explain why there isn't a schema. Depending on your source, you might want to look at how the source system thinks it is providing you a v2.3 ORU^R30 as it's possible they're using the 2.5 schema and then incorrectly calling it 2.3 in the header. If that is the case, you could create your own schema based off of the 2.5 ORU^R30 to match what you're receiving.
go to post Julian Matthews · Oct 4, 2020 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|blahblahblahPID|blahblahblahOBR|blahblahblahNTE|1|blahblahblahOBX|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.
go to post Julian Matthews · Sep 24, 2020 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: Sorry about that - hopefully it works for you now
go to post Julian Matthews · Aug 20, 2020 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
go to post Julian Matthews · Aug 18, 2020 Hey Yone. There's two issues with your useage of Ens.Util.Time. Your spec in is missing the milliseconds and the timezone offset You have a % before the T which seperates the date and 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 nuevaFecha Which 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.
go to post Julian Matthews · Jul 23, 2020 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.
go to post Julian Matthews · Jul 13, 2020 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.
go to post Julian Matthews · Feb 5, 2020 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 seconds set 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-1 Set timediff = pAgeToAlertSeconds-currenttime set 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 directory set 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 trigger while rs.Next() { set DateModified=rs.Get("DateModified") if BeforeThisDate]DateModified { // trigger the trigger set 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 triggered if triggered = 1 { //Create and send an email Set newSMTP=##class(%Net.SMTP).%New() Set newSMTP.smtpserver=..SMTPServer Set newSMTP.port=..SMTPPort Set newAuthenticator=##class(%Net.Authenticator).%New() Set newAuthenticator.UserName=..SMTPUsername Set newAuthenticator.Password=..SMTPPassword Set newSMTP.authenticator=newAuthenticator Set 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 MessageText Set 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 structure Set 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 above Set 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 email Set 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 = 1 Do newEmailMessage.TextData.Write(MessageText) ///Send the Email Set tSC=newSMTP.Send(newEmailMessage) Quit tSC } else{ Quit tSC } } }
go to post Julian Matthews · Dec 30, 2019 All the documentation I have read advises against recompiling like that: "You could also use the $SYSTEM.OBJ.CompileAllNamespaces() method to recompile all namespaces, but you should not do this when upgrading a HealthShare installation. You should not recompile a namespace used for HealthShare." Were any errors returned when you ran this? It might be worth checking with WRC to see if there's anything they can advise on?
go to post Julian Matthews · Sep 6, 2019 Doesn't answer your question, but I use Winmerge with some tweaks to the settings to make the highlighting a little nicer:http://forums.winmerge.org/viewtopic.php?f=4&t=1040If I need to compare 1000s of messages from two sets of transforms, I then point their outbound to a single fileout (or since I upgraded to 2019.1, using the export) and then compare the two files.
go to post Julian Matthews · Aug 29, 2019 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