How about the following setup:

  1. In Business Service or first router BP set a global: ^data(mrn, timestamp) = messageId
  2. In your FIFO BP before sending to BO check: 
    • set nexttimestamp=$o(^data(mrn,""),1,nextmessageId)
  3. If nextmessageId equals current messageid that means there's no message in a pipeline for the same patient with earlier timestamp so we can send it out.
    • Kill  ^data(mrn, nexttimestamp) so next message can be processed 
  4. If nextmessageId does not equal current messageid, compare timestamps:
    • If timestamps are equal, send the message anyway and don't kill the subscript - we have more than one message with the same timestamp. If it happens often, value should be a list of ids.
    • If nexttimestamp is earlier than timestamp, it means there are some other messages in a pipeline with the same MRN, sleep for 10 seconds and check again.

Notes:

  1. You'll need to adjust this based on what you want to do if one of the messages errors before being deleted from the ^data global, options:
    • Processing of messages for this patient effectively stops.
    • Add an additional check in (4) - get other message header and check if it's in a final state (completed, errored, etc) - if so clear ^data subscript and continue.
    • Add an additional check in (4) -  if we waited more than X seconds, continue.
  2. This can be wrapped as Custom Functions and called from rules, BPs.
  3. Locks might help with ensuring consistency.

The advantage here is that you can scale to any number of jobs immediately and since you enforce FIFO only at the end, most of the processing can be parallelized.

Here's how to do it (sample code to transfer files over iris connection):

/// Get IRIS connection object
/// set iris = ##class().GetIRIS()
ClassMethod GetIRIS() As %Net.DB.Iris
{
    Set host = "host"
    Set port = 1972
    Set ns = "%SYS"
    Set user = "_SYSTEM"
    Set pass = "***"
    Set connection = ##class(%Net.DB.DataSource).CreateConnection(host, port, ns, user, pass)
    Set iris = connection.CreateIris()
    Quit iris
}

/// Transfer one file from sourceFile to targetFile on iris connection.
ClassMethod Transfer(iris As %Net.DB.Iris, sourceFile As %String, targetFile As %String) As %Status
{
    Set sc = $$$OK
    Try {
        Set stream = ##class(%Stream.FileBinary).%New()
        Do stream.LinkToFile(sourceFile)
        Set var = "%stream"
        Do iris.ClassMethodVoid($classname(), "InitStream", var, targetFile)
        While 'stream.AtEnd {
            Set chunk = stream.Read($$$MaxStringLength-1000)
            Do iris.ClassMethodVoid($classname(), "WriteStream", var, chunk)
        }
        Do iris.ClassMethodVoid($classname(), "SaveStream", var, ##class(%File).Attributes(sourceFile))
    }
    Catch ex{
        Do ##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in Transfer:" _ ex.DisplayString())
        Throw ex

    }

    Quit sc
}

/// Initialize stream for subsequent write requests and place it in var.
/// var must be a global variable (start form %) 
/// file is created or truncated if already exists
ClassMethod InitStream(var As %String, file As %String)
{
    Try {
        Do ##class(%File).Truncate(file)
        Set stream = ##class(%Stream.FileBinary).%New()
        Do stream.LinkToFile(file)
        Set @var = stream
    } Catch ex{
        Do ##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in InitStream: " _ ex.DisplayString())
        Throw ex
    }
}

/// Wrile string into a stream initialized by InitStream 
ClassMethod WriteStream(var As %String, string As %String)
{
    Try {
        Do $method(@var, "Write", string)
    } Catch ex{
        Do ##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in WriteStream: " _ ex.DisplayString())
        Throw ex
    }
}

/// Save stream initialized by InitStream. 
/// Optionally sets file attributes.
ClassMethod SaveStream(var As %String, attributes As %String = "")
{
    Try {
        Set sc = $method(@var, "%Save")
        Set file = $property(@var, "Id")
        Kill @var
        Do:attributes'="" ##class(%File).SetAttributes(file, attributes)
    } catch ex {
        Do ##class(%SYS.System).WriteToConsoleLog("SuperServer Copy failure in Savetream: " _ ex.DisplayString())
        Throw ex
    }
}

Use ClassMethodValue to get a scalar value back. Use json for complex type transfer as objects are not supported. There are other methods corresponding to APIs in other languages.

Also please note that this class (technically only the callee *Stream methods but save yourself a headache and just copy the entire class) needs to be present on both nodes.

Finally, remember that callee methods must produce no stdout/stderr writes, since the io is bound to the iris connection itself and it cannot disambiguate stdout.

Create subscript level mappings for a correct database.

%ALL global mapping is higher priority than namespace mapping: if %ALL has ^a global mapped to db A and your namespace has ^a mapped to db B, global ^a from db A would be used when you access it from your namespace.

But, subscript mapping is higher priority than global mapping: if %ALL has ^a global mapped to db A and your namespace has ^a(1) mapped to db B, global ^a(1) from db B would be used when you access ^a(1) from your namespace.

There are two formats for LUT:

Old one:

<?xml version="1.0" encoding="UTF-8"?>
<Export generator="IRIS" version="26" zv="IRIS for UNIX (Red Hat Enterprise Linux 7 for x86-64) 2022.1 (Build 209U)" ts="2024-03-03 06:05:36">
    <Document name="LUT_NAME.LUT">
        <lookupTable>
            <entry table="LUT_NAME" key="KEY">VALUE</entry>
            <entry table="LUT_NAME" key="KEY2">VALUE2</entry>
        </lookupTable>
    </Document>
</Export>

New one:

<?xml version="1.0"?>
<lookupTable>
    <entry table="LUT_NAME" key="KEY">VALUE</entry>
    <entry table="LUT_NAME" key="KEY2">NALUE2</entry>
</lookupTable>

Looks like you're importing old format using new importer. Here's the code to import both versions:

ClassMethod ImportLUT(dir)
{
	#include %occErrors
	write "Lookup Tables import from: " _ dir
	set rs = ##class(%File).FileSetFunc(dir, "*.xml;*.XML;*.lut;*.LUT")
	while rs.%Next() {
		set tablePath = rs.Get("Name")
		write "Importing: " _ tablePath,!
		// table is the full path, the last part (denoted by *) is the actual file name
		set tablePathNoExtension = $PIECE(tablePath, "/", *)
		// asking for $PIECE with just delimiter asks for the first part, thus ignore anything after the .
		set tablePathNoExtension = $PIECE(tablePathNoExtension, ".")
		write "Importing Lookup Table in " _ tablePathNoExtension,!
		// lookup table should be named the file name (without extension)
		//do ##class(Ens.Util.LookupTable).%ClearTable(tablePathNoExtension)
		
		// Try the new import first.
		set sc = ..ImportLUTFile(tablePath)
			
		// If we got an error, try legacy import
		if $$$ISERR(sc) {
			write "New import failed. Trying legacy import",!
			set sc=##class(Ens.Util.LookupTable).%Import(tablePath)
			if $$$ISOK(sc) {
				write "Import successful",!
			}
		}
		
		// Error remains unfixed. Fail.
		if $$$ISERR(sc) {
			write "Lookup Table import failure: ", $System.Status.GetErrorText(sc),!
			do $system.Process.Terminate(, 1)
		}
	}
}

/// Adapted from EnsPortal.LookupSettings:Import
/// Import lookup tables from file <var>Filename</var>
ClassMethod ImportLUTFile(Filename As %String) As %String
{
	Set tRS = ##class(%ResultSet).%New("%RoutineMgr:ImportItemList")
	Set tSC = tRS.Execute(Filename)
	Quit:$$$ISERR(tSC) tSC
	Set tSC = $$$OK
	Kill Select
	For  {
		Quit:'tRS.Next(.tSC)
		Set Name = tRS.Get("Name")
		If $E(Name,*-3,*)=".LUT" {
			Lock +^Ens.LookupTable(Name):2
			If '$T Set tSC = $$$ERROR($$$LockFailedToAcquireRead,$Name(^Ens.LookupTable(Name))) Quit
			Set Select($E(Name,1,*-4)) = ""
		}
	}
	Quit:$$$ISERR(tSC) tSC
	Quit:'$D(Select) $$$ERROR($$$GeneralError,"This file does not contain any lookup tables")
	Set tSC = $system.OBJ.Load(Filename,"-d", .Err, .Loaded, 0)
	Set Name = ""
	For  {
		Set Name = $O(Select(Name))
		Quit:Name=""
		Lock -^Ens.LookupTable(Name)
	}
	Quit tSC
}

You need to iterate on value:

 // extract json content from the request:
 set dynRequestJsonPayload = {}.%FromJSON(%request.Content)
 #dim JsonIterator As %Iterator.AbstractIterator
 set JsonIterator = dynRequestJsonPayload.%GetIterator()
 
 // iterate on json structure:
 if dynRequestJsonPayload '= "" {
    while JsonIterator.%GetNext(.key, .value, .NodeType) { 
      if NodeType = "string" {
        do GlobalTrace("NodeType: " _ NodeType _ "; key: " _ key _ "; value: " _ value)
      } elseif NodeType = "array" {
         // i want to iterate on this array...
         // the following line throws the exeception "ERREUR #5002: Erreur ObjectScript: &lt;INVALID OREF&gt;traitementUFI+34^common.REST.1"
         set JsonIteratorSecondary = value.%GetIterator()
      } else {
	      // Do something
      }

    }
}