I'm thinking to increase the pool parameter, but I'm not sure if it's a good idea.

If you are not concerned about the order of which you are processing the inbound requests, then upping the pool size to the number of parallel jobs you're looking to run with should do what you need 

However, you may need to then also apply this logic to related components that the Process interacts with, otherwise you will end up just moving the bottleneck to another component.

Alternatively, if it fits your use case, you could use the Actor Pool for your production components and then increase it to a point where you see the bottleneck drop off.

Paolo has provided the link to the documentation on Pools, which has some info on considerations for the use of the two different types of Pool.

Hey Christine.

If I'm reading your question and subsequent replies correctly, you're trying to take the value of PV1:7.1, and then use that in a SQL query. The answer has been given by Ashok when you put their replies together, but hopefully putting it all into a single response will make things easier to follow.

If this is the case, then you will want to do the following:

Step 1: Set a variable to the value of PV1:7.1:

Step 2: Add a code block, and use this to run your sql query:

Step 3: Do what you need to with the value of ID - for the sake of this response, I'm just setting the value of PV1:7.2 to the ID returned from the query that inserted into the variable "Output":



It's worth knowing that, when working with Embedded SQL, prefixing a variable with a colon is how you can pass variables in and out of the Embedded SQL section of code. However it's a bit clearer when working directly with ObjectScript vs a DTL.

For example, if we had the following table:

ID COL_A COL_B
1 ABC 123
2 DEF 234

We could have the following in ObjectScript:

    Set X = "" // X is null
    Set Y = "ABC"
    &SQL(
        SELECT COL_B
        into :X
        From TestTable
        WHERE COL_A = :Y
    )
    WRITE X //X is 123

The input is a string, so the max length will be your system max (which should be 3,641,144).

Assuming you're trying to retrieve the stream from a HL7 message, you will probably want to use the built in method GetFieldStreamBase64

So you could try something like:

Set tStream = ##class(%Stream.TmpBinary).%New()
Set tSC = pHL7.GetFieldStreamBase64(.tStream,"OBX:5")

And then your decoded file would be in the temp stream.

(You may need to tweak this slightly depending on how you intend to then use the stream, and the correct property path of the Base64 within the HL7 message)

This is a rather subjective based on the skill level of the intended audience.

You could add a comment to the ClassMethod to provide context to what is being done and why. For example:

/// This ClassMethod takes a delimited String from System-X that consists of sets of Questions and Answers. 
/// The Sets are delimited by a pipe "|" and then the questions and answers are delimeted by a colon ":"
/// The response from this ClassMethod is a %Library.DynamicArray object containing the questions and answers
ClassMethod createResponse(data As %String(MAXLEN="")) As %Library.DynamicArray
{
    ;1.- Questions splitted by "|"
    Set listQuestions 			= $LISTFROMSTRING(data, "|")
    Set items 			= []
    Set questionNumber 	= 0
    ;2.- Iterate
    For i=1:1:$LISTLENGTH(listQuestions) {
        Set questionAnswer = $LISTGET(listQuestions, i)
        ;3.- Update variables
        Set questionNumber 	= questionNumber + 1
        Set question 		= $PIECE(questionAnswer, ":", 1)
        Set answer 		    = $ZSTRIP($PIECE(questionAnswer, ":", 2), "<W") //Get rid of initial whitespace
        ;4.- Generate item
        Set item 			= 									
        {
        "definition": ("question "_(questionNumber)),
        "text": (question),
        "answer": 
        [
            {
                "valueString": (answer)
            }
        ]
        }
        Do items.%Push(item)
    }	
    Quit items
}

Or you could go one step further and be more descriptive with your comment at each action within your code. So, instead of:

;2.- Iterate

You could write something like:

;2.- Iterate through the list of Questions and Answers

If your intended audience is not familiar with ObjectScript, then you may want to introduce them to features in stages. For example, you could use $ZSTRIP on both the question and answer in your For loop, but only nest it for the answer and use comments to describe it all. Something like:

// Retrieve the question from the delimited entry
Set tQuestion = $PIECE(questionAnswer, ":", 1)

// Strip any whitespace from the start of the question
Set question = $ZSTRIP(tQuestion, "<W")

// It is also possible to nest functions, so below we will retrieve the answer and remove the whitespace in a single line.
Set answer = $ZSTRIP($PIECE(questionAnswer, ":", 2), "<W")

Hey Guillaume.

Funnily enough - it's one of your github repos where I located the demo I'm trying use as a jumping off point (but from https://github.com/grongierisc/InstallEnsDemoHealth/blob/master/src/CLS/Demo/DICOM/Process/WorkList.cls)

Basically, I'm stuck trying to work out if I should scrap the wakeup calls etc, and just call the external data when I get a C-FIND-RQ message and then call "CreateIntermediateFindResponse" for each result set entry, or if it's necessary to use the wakeup calls and somehow hold the result set in context and move to the next result set entry on each Ens.AlarmResponse received.

ETA: The approach taken was to use the initial message as a trigger to call off to an external db, and write the results into a local table, and then use the Ens.AlarmResponse as the trigger to grab the top entry from the local table and return this to the calling system. This then allows for a cancel to come in and interrupt the process (the cancel will trigger a deletion of the appropriate rows in the local table)

Hey Kurro.

I'm not sure of a built in function for this, but if you wanted to have your own:

Class Demo.FunctionSets.Example
{

ClassMethod Format(InputString As %String, Params... As %String) As %String
{
	Set OutputString = InputString
	For i = 1 : 1 : $GET(Params, 0){
		Set OutputString = $Replace(OutputString,"{"_i_"}",Params(i))
	}
	
	Quit OutputString
}

}

And then:

Write ##Class(Demo.FunctionSets.example).Format("My name is {1} and I'm {2} years","Kurro","18")
My name is Kurro and I'm 18 years

Hey Yuri.

The users are held within the SQL table "Security.Users" in the %SYS namespace, so you could use embedded sql to return the information, however as you're unlikely to be executing your code directly from the %SYS namespace, I'd suggest creating a function that you pass the email address, and it returns the username.

Something like:

Class Demo.Utils.General.Users
{

ClassMethod UserFromEmail(Email As %String, Output Username As %String) As %Status
{
	//Initially set this to null, as we want to return it empty when we get no results
	Set Username = ""
	//Hold the Namespace within a variable so we can use the variable to set the namespace back once the SQL has been run.
	Set CurrNamespace = $NAMESPACE
	//Change NameSpace to %SYS
	Set $NAMESPACE = "%SYS"
	//Run query to get the Username based on the email address - note the use of the UPPER function to remove issues with case sensitivity
	&SQL(
	Select ID into :Username
	FROM Security.Users
	WHERE UPPER(EmailAddress) = UPPER(:Email)
	)
	
	//Set namespace back to the namespace the function was run from
	Set $NAMESPACE = CurrNamespace
	
	//Evaluate SQLCODE for result
	//Less than 0 is an error.
	If SQLCODE <0{
		WRITE "SQLCODE="_$SYSTEM.SQL.Functions.SQLCODE(SQLCODE)
		QUIT 0
		}
	
	//Greater than 0 can really only mean Code 100, which is no results found.
	If SQLCODE > 0 {
		QUIT 1 //No Result Found
		}
	Else {
		QUIT 1 //Result Found
		}
}

}
DEMO> WRITE Class(Demo.Utils.General.Users).UserFromEmail("YuriMarx@ACME.XYZ",.Output)
1

DEMO> WRITE Output
YMARX

This is by no means perfect as I have thrown it together for the example - please forgive the messy if/else's! smiley

There's a few "gotchas" when it comes to Character Encoding. But the key thing in you case is understanding the character encoding being used by the receiving system. This should be something specified in the specification of the receiving system, but many times it's not.

If I had to guess, it's most likely that the receiving system is using UTF-8 simply because latin1/ISO-8859-1 encodes the pound symbol as hex "A3" whereas UTF-8 encodes to  "C2 A3". As there's no solitary "A3" in UTF-8, there's nothing to print, which is why you get the ? instead. I'm sure there's other character sets where this can happen, but I would start there.

Hey Andy.

When you're copying the router from your production, it will continue to reference the same rules class in the settings as per:

After you have copied the Router, you will want to hit the magnifying glass and then use the "Save As" option in the Rule Editor. After you have done this, you can then go back to your Production and then change the rule assigned to your Router via the drop down to select the new rule.

Just make sure you create a new alias for your Rule on the "general" tab on the rule page. 

Hey William.

I'm pretty sure you just need to query the table Ens.MessageHeader.

This should give you the process by way of the column SourceConfigName, and the status of the discarded messages.

For example:

SELECT *
FROM Ens.MessageHeader
WHERE SourceConfigName = 'ProcessNameHere' AND Status = 'Discarded'

You may want to consider including a time range depending on the size of the underlying database.

I ended up extending EnsLib.HL7.Operation.TCPOperation and overwriting the OnGetReplyAction method.

From there, I coped the default methods content but prepended it with a check that does the following:

  • Check pResponse is an object
  • Loop through the HL7 message in hunt of an ERR segment
  • Checks value of ERR:3.9 against a lookup table

If any of the above fail, the response message is passed to the original ReplyCodeAction code logic, otherwise it quits with the result from the Lookup Table.

The use of the Lookup Table then makes adding/amending error actions accessible to the wider team rather than burying it within the ObjectScript, and having the failsafe of reverting to the original ReplyCodeAction logic keeps the operation from receiving an unexpected error and breaking as it has the robustness of the original method.

Hey Patty.

If you just simply need the empty NTE to be added in using the DTL, you can set the first field to an empty string to force it to appear.

For example:

Will give this:

Note that my example is simply hardcoding the first OBX repetition of every first repeating field with no care for the content. You will likely need to do a for each where you evaluate if the source NTE:1 has a value, and then only set to an empty string if there is no content in the source.

So upon further review, it seems that the first ACK is being generated by the Operation, and the second one is the body of the HTTP Response.

Basically, the operation will attempt to parse the http response into a HL7 message, and if that doesn't happen, it will then "generate" an ack and write the http response data at the end of the generated ack.

In my case, although there is a HL7 message in the response, it's not being parsed for some reason, so the code moves onto generating its own ack, followed by the http response body, which is the second ack I was seeing.

I'm now replicating the HTTP operation and attempting to pin down exactly where it's falling down, and failing that I will likely reach out to WRC as it seems to be an issue deeper than I can dive.

Your Process is most likely using ..SendRequestAsync() to send to the Operation and has "pResponseRequired" set to 1 (or not set at all, so it's using the default value of 1).

There's nothing inherently wrong with this, but if you just want to send to the Operation and not worry about the response going back to your process, you could change the "pResponseRequired" flag to 0 in your call. So it would look a little like this:

Set tSC = ..SendRequestAsync("TargetOperationName",ObjToSend,0)

However you may wish to consider if this approach is appropriate to your setup, or if you would be better off using "SendRequestSync()" and dealing with the response synchronously. 

To parse the json, the below is a starting point for taking the content of the stream into a dynamic object, and then saving the value into its own variable.

Set DynamicObject=[].%FromJSON(pRequest.Stream)
Set Name = DynamicObject.name
Set DOB = DynamicObject.DOB
Set SSN = DynamicObject.SSN

You could then store these wherever you need to. If your SQL table is external, then you could have your Operation using the SQL Outbound Adapter to then write these in your external DB.

ETA: If you then need to pick out the values within the content of name (which I assume has come from a HL7 message) you could use $PIECE to pick out the data from the delimited string you're receiving.