- Log in to post comments
Hi Mike,
Could you use an XLST to parse the XML and pass the value fields by reference into COS variables?
I've done something similar on a smaller scale for some IHE transactions (Albeit in HealthShare), but I've used XLST in IRIS as well.
I can't get to the Caché 2018.1 docs to check.
- Log in to post comments
Hi @Kathy Ward
Are you trying to transform from HL7 2.x to HL7 2.x?
If that's the case you should be able to map the OBX()-5 from target to source.
Regards
Stuart
- Log in to post comments
The use of sub transforms is always subjective subject.
Go what feels best for you if your having many lines for a specific section / function then a sub-transform is suitable.
- Log in to post comments
Your getting that error as your class is named %BI.Adaptor.
Create a custom name and extend %BI.Adaptor
- Log in to post comments
The key (k1) is missing from your targetObj:
<subtransform class='ADT.DTL.SDAToPatientIdentifierInfo' targetObj='target.PatientIdentifierInfo.()' sourceObj='source.{Patient.Extension.PatientIdentifiers(k1)}' />
In your master transform is this housed within a foreach loop and k1 defined?
- Log in to post comments
Thank you @Antonio Garcia Martinez for raising this, as I've just learnt something important and @Marc Mundt for pointing me at the right place!
- Log in to post comments
Thank you @Peter Steiwer" href="javascript:" title="Copy to clipboard">@Peter Steiwer that nailed it swizzling .found.
- Log in to post comments
Thanks for the advice @Vitaly Furman, I used square bracket syntax as I was iterating over an array.
I did try my code using {} brackets and my code still isn't ending the nested IF loops upon finding the field and value that match.
Regards
Stuart
- Log in to post comments
Source JSON:
{
"patient": [
{
"age": 51,
"name": {
"forename": "Bill",
"surname": "Brighthouse"
}
},
{
"age": 2,
"name": {
"forename": "Dickie",
"surname": "Bird"
}
}
]
}
- Log in to post comments
Hi Joey,
@Arun Tiriveedhi is correct that you need to use square bracket syntax, as this returns a comma delimited list.
I had a case where I had a whitelist of OBR-4.1 values for filtering result messages to a particular system.
@Stephen Renshaw made a great suggestion to create a function to loop through the list and query the lookup table here: https://community.intersystems.com/post/using-lookup-or-exists-repeating-fields-ensemble-business-rule
Regards
Stuart
- Log in to post comments
Thanks David, your suggestion and some advice via the WRC enabled me to connect over FTPS and collect the text reports from the 3rd party.
- Log in to post comments
Thank you Stephen, I used your advice on square brackets and your method as a template to write my own.
Thank you for your guidance.
Stuart
- Log in to post comments
I've tried testing in the console using the %Net.FTP Command.
I ran the following (server name substituted):
do ftpcall.Connect("sftp.ldh.nhs.uk",username,pwd,"990")
I did a zwrite and found the connection was sucessful and on:
I got the following for the Return message:
write !, ftpcall.ReturnMessage
Features:
MDTM
REST STREAM
SIZE
MLST type*;size*;modify*;
MLSD
AUTH SSL
AUTH TLS
PROT
PBSZ
UTF8
CLNT
MFMT
EPSV
EPRT
End
This says it supports PROT protocol, but I am not sure from the %Net.FTP documentation how to set this feature.
- Log in to post comments
Hi Kevin,
I had gone down your route in the end.
I had been using the FindSegment method from the EnsLib.HL7.Message, which uses the 2nd input variable, however I was having issues implementing the key byref.
Creating my own counter using the same as you suggested with
"set obxCounter=source.GetValueAt("PIDgrpgrp("_(1)_").ORCgrp("_(1)_").OBXgrp("_("*")_")") " and then using the full HL7 segment structure to retrieve the fields removed all woes.
Thank you Vic for your help too, I think my use of the FindSegment method was causing my issues.
- Log in to post comments
Thanks Vic,
The incoming messages have these set already:
| DocTypeCategory | 2.4 | |
| DocTypeSecondary | ORU_R01 | |
| DocTypeName |
ORU_R01 |
pMsg is set to
Stuart
- Log in to post comments
Thanks Vic,
I fixed the mistake, but am still having issues.
I tried set obxcnt = pMSg.GetValueAt("OBX(*)"), but the counter doesn't get populated.
- Log in to post comments
Hi Edward,
I've posted an excerpt of the code as I want to save the classmethod for another post.
Memory/speed shouldn't be an issue as the size of the lists should be <30 elements.
Thanks
Stuart
- Log in to post comments
Excerpt of Code as Promised:
set list = "ABC~DEF~GHI~JKL~MNO~[~PQR~]"
set SegmentList = $LISTFROMSTRING(list,"~")
Set SegmentCount = $LISTLENGTH(SegmentList)
FOR i = 1:1:SegmentCount{
IF ($LISTGET(SegmentList,i,i)= "[") {
set $LIST(SegmentList,i,i)=""
set OptionalSegment = "Y"
}
ELSEIF ($LISTGET(SegmentList,i,i)= "]") {
set $LIST(SegmentList,i,i)=""
}
ELSEIF ($LISTGET(SegmentList,i,i)= "") {
QUIT
}
Write !, "Current Segment: "_$LISTGET(SegmentList,i,i)
Set SegmentFields = $LISTFROMSTRING(object.SegmentSubStructure,"~")
Set SegFieldCount = $LISTLENGTH(SegmentFields)- Log in to post comments
Superb, thank you both for your advice. @Anastasia Dyubaylo has already made contact.
Have a good weekend.
Stuart
- Log in to post comments
Hi Evgeny Shvarov ,
Please can you assist.
The reason for the change is that my existing email address is with my current employer.
I am leaving my organisation and will lose the access to this address with it.
I do not wish to lose my content or at least the ownership of it.
Thanks in advance.
Kind Regards
Stuart
- Log in to post comments
I am also having issues changing my email address for the developer community.
I don't have access to the WRC either.
Please could you advise on how to proceed.
- Log in to post comments
Thanks Jeffrey.
I've spoken to our Caché ODBA and he concurs there are orphaned records in the Ens.Message* globals.
We're on 2017.1 currently, but have been live since Caché 2012, which may explain todays issues.
Looking at the purge tasks we have records with no timestamp, which would escape the purging process.
Our Caché ODBA is going to open a WRC to confirm the best course of action for a one off purge of these Ens.Message* globals.
Thank you so much for your advice as it has helped immensely identifying this issue.
- Log in to post comments
Hi Jeffrey,
SELECT COUNT(ID) & SELECT COUNT (*) are timing out via the System>SQL pages.
I had a look at our purge settings and we have "BodiesToo" unchecked and "KeepIntegrity" checked.
The next way forward would be to investigate the effect of purging with the "BodiesToo" and see if this reduces the EnsAlertRequest global size.
- Log in to post comments
Please find my method below which is used to drive a Business Operation. It's activated by a schedule on a daily basis at 6am:
Method OnInit(Output pResponse As Ens.Response) As %Status
{
//set the DST variable
set DST = $SYSTEM.Util.IsDST()
// set pre-DST edited timestamps for the email header
set emailSummaryStartDAT = $ZDATE($H-1,3)_" "_(..SummaryStartTime)
set emailSummaryFinishDAT = $ZDATE($H,3)_" "_(..SummaryFinishTime)
// ..SummaryStartTime DST handling IF DST =1 adjust SummaryStartTime & SummaryFinishTime Property's H-1
IF DST = 1 {
set ..SummaryStartTime = ($P(..SummaryStartTime,":",1)-1)_":"_$P(..SummaryStartTime,":",2,3)
set ..SummaryFinishTime = ($P(..SummaryFinishTime,":",1)-1)_":"_$P(..SummaryFinishTime,":",2,3)
$$$LOGINFO("Daylight Savings is being applied to timestamps")
}
// IF the hour part of timestamp is <10 then add a leading 0 to make a full UTC timestamp
IF ($P(..SummaryStartTime,":",1)<10){
set ..SummaryStartTime = "0"_..SummaryStartTime
}
IF ($P(..SummaryFinishTime,":",1)<10){
set ..SummaryFinishTime = "0"_..SummaryFinishTime
}
//Set Search Start Date/Time of yesterday with SummaryStartTime
set SummaryStartDAT = $ZDATE($H-1,3)_" "_(..SummaryStartTime)
//Set Search Finish Date/Time for today with SummaryFinishtime
set SummaryFinishDAT = $ZDATE($H,3)_" "_(..SummaryFinishTime) //Obtain the ID of the first and last message in the search timeframe
&sql(SELECT MAX(ID),MIN(ID),COUNT(ID)INTO :LastID,:FirstID,:ErrorCount FROM Ens.AlertRequest WHERE AlertTime BETWEEN :SummaryStartDAT AND :SummaryFinishDAT) /// Log Ens.AlertRequest ID range and the no. of errors
$$$LOGINFO("AlertRequest ID's identified between: "_FirstID_" & "_LastID_" No. of Errors is: "_ErrorCount)
//build the email subject line
Set Subject = "Automatic alert from AIE: "_$NAMESPACE_" Email Alert Summary between:"_emailSummaryStartDAT_" & "_emailSummaryFinishDAT
//build the email Body
Set emailBody = "This is a summary of alerts generated by the CUH Application Integration Engine (AIE)."
set emailBody = emailBody_"<br><br>"
set emailBody = emailBody_"This email covers alerts between: "_emailSummaryStartDAT_" & "_emailSummaryFinishDAT
set emailBody = emailBody_"<br><br>"
Set emailBody = emailBody_"Namespace: "_$NAMESPACE
Set emailBody = emailBody_"<br><br>"
set emailBody = emailBody_"No of errors is: "_ErrorCount
Set emailBody = emailBody_"<br><br>"
// Check if any errors have been identified
//If errors identified create table
IF ErrorCount '= 0 {
// set table header
set errortbl = "<head><style>table, th, td { border: 0px solid black;}</style></head><table style='width:100%'><tr><th>Alert ID</th><th>AlertText</th><th>AlertTime</th><th>SourceConfigName</th> </tr>"
//Populate the error table using sql cursor query to loop through the SQL resultset.
&sql(DECLARE C1 CURSOR FOR SELECT ID, AlertText, AlertTime, SourceConfigName INTO :ID, :AlertText, :AlertTime, :SourceConfigName FROM Ens.AlertRequest WHERE AlertTime BETWEEN :SummaryStartDAT AND :SummaryFinishDAT ORDER BY SourceConfigName, AlertTime ASC)
&sql(OPEN C1)
IF SQLCODE '= 0 {
$$$LOGERROR("SQL Error Code: "_SQLCODE)
}
QUIT:(SQLCODE'=0)
&sql(FETCH C1)
//Loop through Alert fields from the time period specified.
While SQLCODE = 0 {
IF DST = 1 {
set AlertHour = $P(AlertTime," ",2)
set $P(AlertHour,":",1) = ($P(AlertHour,":",1)+1)
// Check if updated time is before 10:00am if so a "0" prefix needs adding
IF ($P(AlertHour,":",1)<10){
set $P(AlertHour,":",1) = "0"_$P(AlertHour,":",1)
}
set AlertTime = ($P(AlertTime," ",1)_" "_AlertHour)
}
Set errortbl = errortbl_"<tr> <td>"_ID_"</td> <td>"_AlertText_"</td> <td>"_AlertTime_"</td>"
set errortbl = errortbl_"<td>"_SourceConfigName_"</td></tr>"
&sql(FETCH C1)
}
&sql(CLOSE C1)
//End of HTML Table
set errortbl = errortbl_"</table>"
Set errortbl = errortbl_"<br>"
set emailBody = emailBody_errortbl}
//handling for no errors. No table is created and comment below written
ELSE {set emailBody = emailBody_"No errors have been identified between: "_emailSummaryStartDAT_" & "_emailSummaryFinishDAT_"<br><br>"}
// Set Email Footer
set emailBody = emailBody_"Do NOT reply to this email."
Set emailBody = emailBody_"<br>"
Set emailBody = emailBody_"<br>Application Integration Engine (AIE) | eHospital | "
Set emailBody = emailBody_"<br>Cambridge University Hospitals NHS Foundation Trust | Addenbrookes Hospital | Box 117 |"
Set emailBody = emailBody_"<br>"
Set emailBody = emailBody_"This email is confidential, see www.cuh.org.uk/email_disclaimer.html"
// Build new mail structure.
$$$LOGINFO("Attempting to send Alert Summary email")
IF (..From = "") {
SET From = "noreply@addenbrookes.nhs.uk"
}
IF (..To = "") {
$$$LOGWARNING("Warning - No email provided => No email sent")
}
ELSE {
SET mail = ##class(%Net.MailMessage).%New()
// Define sender.
SET mail.From = ..From
// Define recipients.
// Converts String into list of string.
DO mail.To.InsertList($lfs(..To,";"))
// Define subject.
SET mail.Subject = Subject
//Char set to "utf-8" to allow for html manipulation
SET mail.Charset = "utf-8"
SET mail.ContentType = "text/html"
SET mail.IsHTML = 1
// Define body.
DO mail.TextData.WriteLine(emailBody)
// Check if high priority is needed.
set Priority = 1
IF Priority = 1 {
DO mail.Headers.SetAt(1,"X-Priority")
DO mail.Headers.SetAt("High","X-MSMail-Priority")
DO mail.Headers.SetAt("High","Importance")
}
// Build the smtp server to send the email.
SET server=##class(%Net.SMTP).%New()
SET server.smtpserver=..SMTPServer
SET server.port=..SMTPPort
SET status=server.Send(mail)
If $$$ISERR(status) {
QUIT $$$ISERR(status)
}
$$$LOGINFO("Alert Summary Email has been sent")
}
QUIT $$$OK
} }- Log in to post comments
Have you tried applying a schedule to it, so it's only active when you want to send out the critical alerts summary.
- Log in to post comments
We are currently implementing something similar to Jeff's code above.
We're trying to reduce the amount of alert spam
For the out of hours alerts, I have them sent to a custom BO (business operation) where we send a summary in a single email.
This way you don't lose what's alerted overnight and take action if necessary.
- Log in to post comments
The object of part 2 is to compare the strings from the same input as part 1 and identify which 2 strings differ by 1 character in the same position.
Use the links above to find the exercise.
My code goes:
ClassMethod Part2SQL()
{
//initialising varibles
set rowcount = ""
set line = ""
// Get the highest row number in the table WDA
&sql(SELECT TOP 1 ID INTO :rowcount FROM AOC2018.Day2 ORDER BY ID DESC)
Write !, "Row count is: "_rowcount
// Loop through each row in the table
FOR i=1:1:rowcount{
// initialise looping variables
set mismatchcount = "0"
set complinelength = ""
set compline = ""
set compchar = ""
// run SQL query to look at last number for row loop.
&sql(SELECT WholeLine INTO :line FROM AOC2018.Day2 WHERE ID = :i)
set linelength = $LENGTH(line)
set complinelength = linelength
//set currrentrow = ##class(AOC2018.Day2).%OpenId(i)
FOR comprow = i+1:1:rowcount{
set mismatchchar1 = ""
set mismatchchar2 = ""
set mismatchpos = ""
//IF comprow = rowcount+1 {QUIT}
&sql(SELECT WholeLine INTO :compline FROM AOC2018.Day2 WHERE ID = :comprow)
IF compline = "" {QUIT}
set mismatchcount = "0"
For char=1:1:linelength{
/// get current character being looked for
set currentchar = $EXTRACT(line,char,char)
set compchar = $EXTRACT(compline,char,char)
IF compchar '= currentchar{
set mismatchcount = $INCREMENT(mismatchcount)
set mismatchchar1 = currentchar
set mismatchchar2 = compchar
set mismatchpos = char
}
QUIT:(mismatchcount >1)
}
QUIT:(mismatchcount =1)
}
QUIT:(mismatchcount =1)
}
write !, "The lines with only one mismatch are:"
write !, "1st line: "_line_" On row: "_i
write !, "2nd Line: "_compline_" On row: "_comprow
write !, "The char in the mismatch in line: "_i_". The char is: "_mismatchchar1
write !, "The char in the mismatch in line: "_comprow_". The char is: "_mismatchchar2
write !, "With character position: "_mismatchpos
set answerfirsthalf = $EXTRACT(line,1,mismatchpos-1)
set answersecondhalf = $EXTRACT(line,mismatchpos+1,linelength)
write !, "The matching text is: "_answerfirsthalf_answersecondhalf
}
- Log in to post comments
Aaron Wassall, beautiful lean code!
- Log in to post comments
AOC 2018 Day1 Part 2
As promised in the post above, I have posted part 2 in the comments.
The summary of part 2 is to find the first frequency that repeats twice.
In this published method rather than looping through a string, I looped through the first set of sequence changes (990) via a sql query.
This version was much quicker than parsing the string of frequency changes through each loop. I added a kill global to the first method, so I could use this to generate the first loop of values before testing/debugging this script.
In hindsight I didn't need to do this and could do something inside this method.
Let me know what you think:
ClassMethod FreqPart2(Input As %String) { //This needs to be run after ImportFreqPI method as expects Global to be populated // set previous record counter to 0 set prevrecord = 0 //Find the last record to append to in AOC2018.Day1 global &sql(SELECT TOP 1 ID INTO :prevrecord FROM AOC2018.Day1 ORDER BY ID DESC) //Set Prev frequency to 0 then get value via SQL query set prevfreq = 0 &sql(SELECT CurrentFrequency INTO :prevfreq FROM AOC2018.Day1 WHERE ID = :prevrecord) write prevfreq set end = 990 PILOOP // Loop through list FOR i=1:1:end{ //create new object to save to Global set newrecord = ##class(AOC2018.Day1).%New() // Set Freqency counter to 0, so when used later is only value from current loop set FreqCount = 0 //set freqcount variable to newrecord.Freqcount so can be saved correctly . set FreqCount = newrecord.FreqCount //set current value in list &sql(SELECT FrequencyChange INTO :insertitem FROM AOC2018.Day1 WHERE ID = :i ) //write list value //write insertitem //set Frequency change value = to insertitem variable set newrecord.FrequencyChange = insertitem //set current frequency by adding prevfreq to newrecord.FrequencyChange set newrecord.CurrentFrequency = prevfreq + newrecord.FrequencyChange //set prev freq variable to newrecord.CurrentFrequency // so can be used to calculate newrecord.CurrentFrequency in next loop set prevfreq = newrecord.CurrentFrequency //save record set status = newrecord.%Save() // Check if there is any entry in the global already for the frequency and input into FreqCount variable &sql(SELECT COUNT (CurrentFrequency) INTO :FreqCount FROM AOC2018.Day1 WHERE CurrentFrequency = :newrecord.CurrentFrequency) //write FreqCount set newrecord.FreqCount = FreqCount set status = newrecord.%Save() //write !,"Frequency Change Is:"_insertitem_" Current Frequency is:"_newrecord.FrequencyChange //write !,"Frequency counter IS: "_FreqCount //write status IF FreqCount > 1 { write "First repeat = "_newrecord.CurrentFrequency QUIT} set prevrecord = prevrecord+1 } IF FreqCount < 2 { write !, "Relooping" GOTO PILOOP } return status }
- Log in to post comments