Jeffrey Drumm · Dec 24, 2020 go to post

Hi Blake,

Can you provide a bit more detail? If you need to create an individual HL7 message for each repeating element in the XML document, my answer here is probably the easiest way to get there. If there's a "master/detail" relationship within the XML, though, you'll need to handle that in a BPL, and perhaps still "chunk" the XML before handing it off to the BPL if there are multiple elements with master/detail relationships. You'd do that with the XML Object file service mentioned in the link (assuming you're getting these XML documents as local files ... there's also an FTP version of the service).

There are tutorials on BPLs in ISC's Learning library.

Jeffrey Drumm · Oct 25, 2020 go to post

That works, thanks!

So the method is now doing everything I need. In its previous incarnation I was just returning a %String, and although it got me into trouble with messages larger than MAXSTRING, it was a LOT faster ... easily 3x to 5x faster.

Jeffrey Drumm · Oct 25, 2020 go to post

Ok ... so this actually works:

ClassMethod GetHL7Msg(pId As %String) As %Stream.GlobalCharacter [ SqlName = GetMsg, SqlProc ]
{
    Set tHl7 = ##class(EnsLib.HL7.Message).%OpenId(pId,,.tSC)
    Throw:$$$ISERR(tSC) ..GetErr(-400, "HL7 Message with ID "_pId_" Not Found.")
    Set tMsg = ##class(%Stream.GlobalCharacter).%New()
    Set tSC = tHl7.OutputToLibraryStream(.tMsg)
    Do tHl7.%Close()
    If tSC Set tSC=tMsg.%Save()
    Throw:$$$ISERR(tSC) ..GetErr(-400, $system.Status.GetErrorText(tSC))
    Return tMsg."%%OID"
}

I'm concerned that I'm using a persisted object. Does it automatically get killed once I leave the scope of the function (I'm assuming not, see the last paragraph)? Is there a way to force it to temp storage?

It's also very slow, but 1) I'm currently connecting to the database over a VPN connection, and 2) the average message size is around 30KB, with some messages up to 3MB. It took 5 minutes to return 1000 rows.

Looks like ^CacheStream is currently using almost 600MB.

Jeffrey Drumm · Oct 25, 2020 go to post

No errors running the query from $system.SQL.Shell(), but I get the stream OREF rather than the message in the query result:

ID      SourceConfigName        TargetConfigName        Message
191344  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191348  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191352  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191356  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191360  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191364  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191368  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191372  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191376  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191380  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter

ODBC seems to be doing a little magic in the background to return the stream data, but it's not good magic at the moment.

EDIT: Turns out the ODBC wrangler needs a persistent object's "%%OID" rather than an OREF to fetch the stream. See the eventual working method here.

Jeffrey Drumm · Oct 25, 2020 go to post

Hi Eduard,

I'm having a bit of trouble applying your solution to my problem. I have only one method in EnsLib.HL7.Message that seems to populate a %Stream.TmpCharacter data type, and it doesn't allow me to "chunk" the data. In the code I posted, I should be returning a %Stream.TmpCharacter object functionally identical to yours. I think I am, but suspect that the ODBC handler in IRIS isn't finding the stream.

Jeffrey Drumm · Oct 24, 2020 go to post

Also, I should add that although I had said that I wasn't seeing any errors on the client side (aside from not getting the entire result set), that's not exactly true. No errors were displayed in the client application's GUI, but it was logging them to its log file. In examining that log file I found what appears to be a stream-related entry in the connection string: StreamPrefetch=0. I can't find any documentation on it other than it being mentioned (without a description) in the Using .NET and the ADO.NET Managed Provider with Cache section. I'm assuming it takes a number of bytes(?) as an argument, since all the other boolean entries take True/False as values ...

Jeffrey Drumm · Oct 24, 2020 go to post

I'll give that a try, but I'm not optimistic. The data is there; the stream object is getting populated because I can Read() it and return the data as a %String ...

Jeffrey Drumm · Oct 24, 2020 go to post

Hi Robert,

Same result ... a single row and the same error in the IRIS xDBC log:

2020-10-24 01:35:59 [SQLCODE: <-412>:<General stream error>] [Error: <<INVALID OREF>PrepareStream+6^%SYS.SQLStreamSRV>] [Location: <ReadStreamODBC::PrepareStream>] [Client info: <Username: Jeff, Node Name: WIN10X64-VM01, IP Address: 10.208.8.90, Executable Name: HL7Spy.exe, Internal Function: AS>] [%protocol: <59>] $Id: //adhocs-iris/2020.1.0.217.1/HICG_LLC_001/kernel/common/src/aclass.c#1 $ 21256 11

 The method works fine if I define the return type as %String, but only until the query fetches a row with a message larger than MAXSTRING in size. I of course have to Return tMsg.Read() to get the output. When it hits a message that's too large, I get a <MAXSTRING> error in the IRIS xDBC log, but no error is set for the ODBC (actually ADO) client.

Jeffrey Drumm · Oct 23, 2020 go to post

Hi Robert,

Yes, I get the exact same error whether I use %Stream.GlobalCharacter or %Stream.TmpCharacter.

Jeffrey Drumm · Oct 14, 2020 go to post

Hi Nora! Long time laugh

Something like this should do the trick, assuming the file to be appended to is named "spoo.txt" and the file from which you're appending is named "fleem.txt":

    Set tOut = ##class(%File).%New()
    Set tIn = ##class(%File).%New()
    Set tSC = 1
    Set tOut.Name = "spoo.txt"
    Set tIn.Name = "fleem.txt"
    Set sc = tOut.Open("WA")
    Set:$$$ISERR(sc) tSC=$$$ADDSC(tSC,sc)
    Set sc = tIn.Open()
    Set:$$$ISERR(sc) tSC=$$$ADDSC(tSC,sc)
    Quit:$$$ISERR(tSC) tSC
    While 'tIn.AtEnd
    {
        Set sc=tOut.Write(tIn.Read())
        Return:$$$ISERR(sc) sc
    }
    Do tIn.Close()
    Do tOut.Close()
    Return $$$OK
Jeffrey Drumm · Sep 8, 2020 go to post

I'm not finding any significant differences in the supporting classes between HS and I4H 2020.1. You might want to open an incident with the WRC to see why you're successful in I4H and not HS.

I'm also thinking that you might be better off developing your NACK logic in a BPL, though. Routing Rules aren't really designed for this sort of thing.

Jeffrey Drumm · Sep 3, 2020 go to post

I guess that depends on what you want to do if you get an invalid date. Here's a method you can add to the class I referenced above:

ClassMethod IsValidBirthDate(pDate As %String) As %Boolean
{
    Try {
            Set tHDate = $ZDATEH(pDate)
    } Catch ex {
        Return 0
    }
    Return 1
}

And you'd use it something like this:

Jeffrey Drumm · Sep 3, 2020 go to post

Here's a method that, once created and compiled, will appear in your dropdown list of functions in the rule editor:

Class User.Util.DateTime Extends Ens.Rule.FunctionSet
{

ClassMethod DaysPrior(pDays As %Integer) As %String
{
    Return $ZDATE($H - pDays,8)
}

}

You'd use it in your rule like this:

Caveat: The birthdate is assumed to be valid and 8 characters in length. If there's a possibility that you would get an invalid or missing date, the ">" comparison will not be valid.

Jeffrey Drumm · Aug 27, 2020 go to post

If you know that there will always be 8 characters at the position of the 2nd "*" character, you should be able to use this pattern instead:

TEST_PID*_NDCA_Drug_Utilization_????????_UD.txt
 

Jeffrey Drumm · Aug 17, 2020 go to post

I'm pretty sure that System Default Settings would only solve this issue if you were already using it to "default" the original value for those interfaces. Had you done that, making the update would be a very simple change of one entry in SDS.

@Craig Regester's solution is probably the easiest to implement quickly, but it might also be an opportunity to move to an SDS-based configuration.

Jeffrey Drumm · Jul 1, 2020 go to post

Thank you for the clarification. I'm in the middle of a Cache/Ensemble on SUSE -> IRIS for Health on RHEL migration  and would prefer to be at the latest available release prior to go-live. But I guess I am, per the release schedule.

Jeffrey Drumm · Jul 1, 2020 go to post

Hi Stefan,

I'm not seeing the 2020.2 distributions listed for the full kits on the WRC site. Do you know when they will be available?

Jeffrey Drumm · Jun 30, 2020 go to post

Would you like us to post feature requests here, on GitHub, or not at all? laugh

For example, I want to be able to go back and edit a previously entered line before execution ...

Jeffrey Drumm · Jun 26, 2020 go to post

Did you try GetHostSettingValue()?  I'm working with 2018.1.2, but I'd be surprised if the API changed.

Jeffrey Drumm · Jun 26, 2020 go to post

GetAdapterSettingValue() may give you what you want. The Type parameter isn't needed.

Set archiveFilePath=##class(Ens.Director).GetAdapterSettingValue("ReadPDFFileService","ArchivePath",.status)
 

There's a corresponding GetHostSettingValue() method for non-adapter settings that works similarly.

Both seem to supply the System Default Setting when the setting is unvalued in the production.

Jeffrey Drumm · Jun 21, 2020 go to post

Hi Dmitriy,

UPDATE: Also posted as an issue to GitHub

Here's the layout in VS Code's Explorer:

The code-workspace file from the Workspace top-level directory:

Note that I've tried both manually creating the file and adding directories to the workspace using the VS Code menus. The only difference I can see in the result is that the json name value appears in the explorer tree rather than the actual directory name.

Hers's a .vscode\settings.json file from one of the directories referenced in the code-workspace file shown above:

The oddest symptom is that when I change just one of the settings.json files, all directories change to those value in the OBJECTSCRIPT:EXPLORER view (only the file I've actually edited and saved has the changes in it, though):

Thanks for looking into this.

PS.

Just in case it matters:

Version: 1.46.1 (user setup)
Commit: cd9ea6488829f560dc949a8b2fb789f3cdc05f5d
Date: 2020-06-17T21:13:20.174Z
Electron: 7.3.1
Chrome: 78.0.3904.130
Node.js: 12.8.1
V8: 7.8.279.23-electron.0
OS: Windows_NT x64 10.0.18362

Jeffrey Drumm · Jun 21, 2020 go to post

Hi Dmitriy,

I've created the .code-workspace file up as you've described, with the "main" name/path and the "part" names/paths pointing to their respective subdirectories. Each subdirectory has it's own .vscode/settings.json file. However, when I go into Explorer, all of the workspace directories show connections to the same server, not the distinct ones defined for each workspace subdirectory.

There are no objectscript.* values defined in the main .code-workspace file or the .vscode/settings.json file in the root of the main workspace directory.

I'm obviously missing something, but I've tried a variety of options and I'm stuck.

Thanks!

Jeffrey Drumm · Jun 2, 2020 go to post

The schema doesn't seem to match the structure of the message you've supplied; the OBRuniongrp group should be a repeating group and indicate such with parentheses (i.e. OBRuiniongrp()). Assuming that's fixed in the schema, you should be able to get at the fields in question in repeating OBR segments with something like:

Set tStudy = 0
// Get count of OBR segments
Set tOBRCnt = request.GetValueAt("ORCgrp(1).OBRuniongrp(*)")
// Loop through OBRs and evaluate field contents
For tIter = 1:1:tOBRCnt
{
   If request.GetValueAt("ORCgrp(1).OBRuniongrp("_tIter_").OBRunion.OBR:UniversalServiceIdentifier.Identifier") = "match_value"
   {
      Set tStudy = 1
   }
}
If tStudy
{
   ...insert action to take here...
}

The above may need to be modified to use context variables if it's being used in a BPL.

Jeffrey Drumm · Apr 20, 2020 go to post

Rather than:

s target.{ORCgrp(k1).OBRgrp(k2).OBXgrp(k3).NTE(iNteCnt)} sTmp

Try:

d target.SetValueAt(iNteCnt, "ORCgrp("_k1_").OBRgrp("_k2_").OBXgrp("_k3_").NTE("_iNteCnt_"):1")
d target.SetValueAt(sTmp, "ORCgrp("_k1_").OBRgrp("_k2_").OBXgrp("_k3_").NTE("_iNteCnt_"):3")

Assuming you want the value in the 3rd field of the NTE. You should also change the value stored in sNTE to:

source.{ORCgrp(k1).OBRgrp(k2).OBXgrp(k3).NTE(1):3}

If there's only one NTE segment in each OBXgrp of the the inbound message.

Jeffrey Drumm · Apr 15, 2020 go to post

Building an interface in Ensemble would require essentially emulating the printing protocol used by Meditech (lpr/lpd?). While that might be fun, I don't know that it's the best use of your time smiley

Depending on your OS platform, it might be possible to route Meditech's printer output to files. This would be done by configuring a custom printer definition on either the Ensemble host or a host that Ensemble has file (ftp/sftp/cifs/etc.) access to. For Unix/Linux, this isn't terribly hard to do with lpd, and I imagine it can be done with CUPS as well.

I know Windows can function as an lpd server, but I'm not sure how you would get its output routed to a file.

Once the printed output is in file form, you could then create a file service in Ensemble to pick up and process it.

Jeffrey Drumm · Apr 13, 2020 go to post

InterSystems documentation states that this method should never be used in a live, deployed environment. It will remove all messages from queues, along with other state management data.

Link

Jeffrey Drumm · Apr 3, 2020 go to post

The documentation is available both in the DTL editor when you use the Exists() method in the Expression Editor for a rule:

And in the Class Reference for Ens.Rule.FunctionSet -- the Exists() Method:

The Class reference is clearer about the table argument being a quoted string.

Jeffrey Drumm · Mar 4, 2020 go to post

Hi Brendan,

Well, I just tested it with WinSQL and the queries that never returned results previously are now working. They're also working in the application I had originally tested with, so ... not sure what the cause was.

I was previously using IRIS for Health CE 2019.1 as the target environment but am now using a "commercial" IRIS 2019.1 license.