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.

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.

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.

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.

I set up a test scenario based on your criteria. I created a service, HL7 router process and 3 operations:

Service: HL7 MLLP, target HL7 Router, AckMode=Application

Process: HL7 Router, ForceSyncSend: True, ReplyCodeActions: E=F

Target Operation 1: HL7 File Out

Target Operation 2: Custom Operation that returns an HL7 NAK, sets IsError to 1, logs an error code of 5001 with text "Forced NAK", ReplyCodeActions: E=F

Target Operation 3: HL7 File Out

Observations:

  1. Message arrives in router process
  2. Routes to Operation 1, completes successfully
  3. Routes to Operation 2, receives error
  4. Router responds to Service with Error; shuts down ... no message delivered to Operation 3
  5. Service returns NAK to sending application
  6. Message remains queued for Router

Isn't this what you would want to happen?

Seems to work fine if you route the ACK to another Business Process. For the router handling messages from an inbound service:

Response comes in from TestTCPOut; the received ACK is sent to the original service and forwarded to a BP called AckWrangler. That BP runs a routing rule that optionally transforms the ACK and passes it off to another operation called AckTarget:

I'm pretty sure you don't want a newline character anywhere in the target OBX segments, so this should do the trick:

Unfortunately, this does not appear to do the right thing in the DTL Test tool. You can verify it works at the Caché/IRIS prompt, though:

You can, of course, also verify that it works by calling it from a routing rule laugh

Execution details for routing rules are located in Ens.Rule.Log. The available values are ID, ActivityName, ConfigName, CurrentHeaderId, DebugId, EffectiveBegin, EffectiveEnd, ErrorMsg, IsError, Reason, ReturnValue, RuleName, RuleSet, SessionId, and TimeExecuted.

In the SQL facility, you can query Ens_Rule.log:

SELECT ID, ConfigName, CurrentHeaderId, RuleName, RuleSet, SessionId, TimeExecuted FROM Ens_Rule.Log

Assuming the stream contains the normal $C(13) segment delimiters:

Class User.HL7.Stream Extends %RegisteredObject
{

ClassMethod GetCounts(pStream As %Stream.FileCharacter, Output pMsgCount As %Numeric, Output pSegCounts As %ArrayOfDataTypes) As %Status
{
    Do pStream.Rewind()
    Set pStream.LineTerminator = $C(13)
    Set pSegCounts = 0
    While 'pStream.AtEnd
    {
        Set tLine = pStream.ReadLine()
        // Remove leading control characters
        Set tSeg = $ZSTRIP(tLine,"<C")
        // Get the segment name
        Set tSegName = $EXTRACT(tSeg,1,3)
        If tSegName '= ""
        {
            If '$DATA(pSegCounts(tSegName))
            {
                // We have a new subscript
                Set pSegCounts = pSegCounts + 1
                Set pSegCounts(tSegName) = 1
            }
            Else
            {
                Set pSegCounts(tSegName) = pSegCounts(tSegName) + 1
            }
        }
    }
    Set pMsgCount = pSegCounts("MSH")
    Return $$$OK
}

}


Call the classmethod with the stream as follows:

Do ##class(User.HL7.Stream).GetCounts(stream,.Msgs,.Counts)


Counts will be subscripted by the segment names found in the message stream. In the above example, you'll find the occurrence count of FT1 segments in Counts("FT1") and the number of messages will be in Counts("MSH"). The value returned in Msgs is the same as Counts("MSH").

If you have access to Caché terminal, you can run run queries that won't time out:

(the sample below assumes your namespace is "PROD"; just substitute whatever your production's namespace is for that).

USER> zn "PROD"

PROD> d $system.SQL.Shell()

SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter q to quit, ? for help.
PROD>>SELECT COUNT(*) AS AlertCount FROM Ens.MessageHeader WHERE MessageBodyClassName = 'Ens.AlertRequest'

AlertCount
2205

PROD>>Q

PROD>

So ... if you don't have BodiesToo checked, you most likely have lots of orphaned message bodies taking up database space. And KeepIntegrity is probably retaining a lot of message headers (and associated bodies) that you don't care about anymore. There are reasons you would not want to turn KeepIntegrity off in earlier versions of Caché/Ensemble, like pre-2015 releases. If you're on a release more modern than that and you don't need to worry about messages with parent/child relationships (certain batch types, for example), you can probably turn that off.

There are a couple of articles regarding the management of orphaned bodies here on DC. Might be worthwhile to peruse them :)

I've done something similar with COS, but in my case it was the ORC group that was repeating:

Class HICG.Process.MultiORC Extends Ens.BusinessProcess [ ClassType = persistent ]
{
Property TargetConfigNames As %String(MAXLEN = 1000);
Parameter SETTINGS = "TargetConfigNames:Basic:selector?multiSelect=1&context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId}";
Method OnRequest(pRequest As EnsLib.HL7.Message, Output pResponse As Ens.Response) As %Status
{
    Set tORCcnt = pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp(*)")
    For i=1:1:tORCcnt
    {
        Set tMsg = ##class(EnsLib.HL7.Message).%New()
        Set tMsg.DocType = pRequest.DocType
        // Keep MSH:10 unique
        Set tMsgCtrl = pRequest.GetValueAt("MSH:10")_"."_i
        Do tMsg.SetValueAt(pRequest.GetValueAt("MSH"),"MSH")
        Do tMsg.SetValueAt(tMsgCtrl,"MSH:10")
        Do tMsg.SetValueAt(pRequest.GetValueAt("PIDgrpgrp(1).PIDgrp.PID"),"PIDgrpgrp(1).PIDgrp.PID")
        Do tMsg.SetValueAt(pRequest.GetValueAt("PIDgrpgrp(1).PIDgrp.PV1grp.PV1"),"PIDgrpgrp(1).PIDgrp.PV1grp.PV1")
        Do tMsg.SetValueAt(pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp("_i_").ORC"),"PIDgrpgrp(1).ORCgrp(1).ORC")
        Do tMsg.SetValueAt(pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp("_i_").OBR"),"PIDgrpgrp(1).ORCgrp(1).OBR")
        Set tOBXcnt = pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp("_i_").OBXgrp(*)")
        For j=1:1:tOBXcnt
        {
            Do tMsg.SetValueAt(pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp("_i_").OBXgrp("_j_").OBX"),"PIDgrpgrp(1).ORCgrp(1).OBXgrp("_j_").OBX")
        }
        Set tSC = tMsg.%Save()
        $$$TRACE(tSC)
        For iTarget=1:1:$L(..TargetConfigNames, ",")
        {
            Set tTarget=$ZStrip($P(..TargetConfigNames,",",iTarget),"<>W")
            Continue:""=tTarget
            Set tSC1 = ..SendRequestAsync(tTarget,tMsg)
            Set:$$$ISERR(tSC1) tSC=$$$ADDSC(tSC,tSC1)
        }
    }
    Return tSC
}

The audit log can provide information on who changed a production configuration, such as adding or modifying a business host configuration. It does not contain information on who modified or created a routing rule, DTL, BPL or other class associated with running a production.

For the latter you should consider implementing a Source/Version control system that controls how those components progress from development through QA to Prod and tracks who made those changes. Deltanji from George James Software is a strong contender in that area.

Did you select EnsLib.HL7.Service.SOAPService as your service's class? If yes, then I know the following XML format works:

<?xml version="1.0" encoding="UTF-8" ?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:s='http://www.w3.org/2001/XMLSchema'>

      <SOAP-ENV:Body>

            <Send xmlns="http://tempuri.org">

                  <Input>

                        <![CDATA[MSH|^~\&|... rest of hl7 message here, with <CR><LF> at end

]]>

                  </Input>

            </Send>

      </SOAP-ENV:Body>

</SOAP-ENV:Envelope>

I'm assuming you don't have "Act on Validation Error" enabled in the router's settings  ... in which case the message will be set to Completed status even though it failed validation.

If you enable "Act on Validation Error" its status will be set to Error; you'll then be able to select for that status in the Message Viewer and resend. Note that with this setting enabled, you may want to set "Reply Code Actions" to do something other than the default, such as sending the messages to the Suspended Messages facility.

Another option would be to specify a Bad Message Handler; this forwards failed validations to a business host where other actions can be taken such as alerting or logging to a file. You'll also have the option of resubmitting those messages to their original destination by specifying a new target in the Message Viewer's Resend facility.