go to post Jeffrey Drumm · May 5 Are you dealing with multiple DocTypes and Categories of messages going to Epic, or are all messages the same schema? I know you're not crazy about a custom operation, but if you take a look at the source for EnsLib.HL7.Operation.TCPOperation, you'll see that it would be dead simple to copy/extend it, add a check for the population of the Ordering Provider field, and the logic to populate it with a default if it's empty.
go to post Jeffrey Drumm · May 5 From within your task, you can obtain the task's classname with $CLASSNAME() and query the %SYS.Task table using the TaskClass of your task to fetch the OutputFilename column. If you want to use that file under your direct control, you can set "Open output file when task is running" to "yes," enter the full path of the filename, then set the previous setting back to "no." The filename will remain in the table. If you're calling the same class under multiple Task schedules or with different configurations, the schedule values and settings are also available to help you refine your selection. Settings are stored in $LIST() format. EDIT: You can also define a Filename property in the task class, assuming it's a custom class. It will show up as a configurable field in the task editor. That way you don't have to deal with the OutputFilename settings.
go to post Jeffrey Drumm · May 4 Usually, OBX segments are either defined as repeating segments or members of repeating segment groups. The syntax you'll use will vary depending on the HL7 Document Category and Structure you're using in your DTL. In HL7 2.5.1, the OBX segment itself is non-repeating, but is part of a repeating group (OBXgrp) inside another repeating group (ORCgrp) inside yet another repeating group (PIDgrpgrp). You first need to get the count of the total number of OBX segments, which you can do by supplying the "*" character in the iteration argument to the source path's OBXgrp(). Add 1 to that, and you have your iteration for the new OBX segment. Use that value as the iteration for the new OBX segment and populate the fields as needed, as in the example below: The above assumes that the OBX segments are the last segments in the message. If they're not and the message requires another OBX at the very end, it's a bit more complicated ... you'd create a new segment object, populate it, then use the AppendSegment method to slap it on the end of the target:
go to post Jeffrey Drumm · Apr 17 There really is no list of "Standard" settings. You can use whatever section name you desire and it will appear in the list of settings for the business host in the production. Most adapters provide the settings Basic, Connection, and Additional. However if the setting you're creating doesn't fit any of those categories, you can create your own with the "MySetting:MyCategory" format. The documentation for creating business host settings can be found here. EDIT: After reviewing the documentation, I discovered that there are a set of predefined categories and they're listed here.
go to post Jeffrey Drumm · Apr 5 Are you using VS Code? With the Intersystems plugins installed, IRIS Terminal Lite is available on version 2025.1. Unless the Linux system admin has set session timeouts for ssh, you shouldn't be timing out. The default settings (for Redhat and Ubuntu, at least) do not time you out.
go to post Jeffrey Drumm · Mar 27 Glad you got it working. I feel I should mention that I specifically told you to just that 2 days ago ... quoted here: There are a few issues with this code, but the biggest one is that you've created a clone and should be modifying the clone (pOutput) exclusively. However, you're still calling SetValueAt() against a segment pulled from pRequest. The above notwithstanding, you're still sending the original unchanged message object (pRequest) to the target operation with the SendRequestAsync() call. Fundamentally, you should: Create a clone of the request object pRequest (i.e. pOutput) Modify the clone, which is a mutable copy of the original message Send the modified clone (pOutput) to the target operation
go to post Jeffrey Drumm · Mar 26 You're cloning the source message inside the While loop that is iterating over segments within the message. That's not doing anything useful. Clone the message, then iterate over its OBX segments, making whatever changes are necessary. Before sending it with SendRequestAsync(), do a pOutput.%Save().This snippet below is completely untested and there are other enhancements I would make to it, but I'm not going to be available for a while, so here's something to play with. It's a cleaned up version of the code snippet you provided above. Note that there is not a single call to GetSegmentAt() ... it's an unnecessary extra step. // Message Subtype Set tMessageSubType = pRequest.GetValueAt("ORCgrp(1).ORC:1") // // Check if OBR:19 contains "Implant Usage (PSAS)" OR "Issued in Clinic (PSAS)" Set tOrderType = pRequest.GetValueAt("ORCgrp(1).OBRuniongrp.OBRunion.OBR:19(1).1") If ((tOrderType["Implant Usage (PSAS)") || (tOrderType["Issued in Clinic (PSAS)")) { // First loop: Count occurrences of "Item Number:" Set i = 1 Set tItemNumberCount = 0 While (pRequest.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX")'="") { Set tOBXText = pRequest.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX.5", .tStatus) // If (tOBXText [ "Item Number:") { Set tItemNumberCount = tItemNumberCount + 1 } // If (tOBXText[ "Total Cost:") { Set tTotalCostSegment = i //$$$LOGINFO(tTotalCostSegment_" ------ "tTotalCostSegment check Total Cost") } Set i = i + 1 } // Second loop: Modify OBX segments if necessary Set i = 1 Set tItemNumberProcessed = 0 Set pOutput = pRequest.%ConstructClone(1) // moved here because %ConstructClone() need only be done at the message object level. While (pOutput.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX")'="") { Set tOBXText = pOutput.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX.5", .tStatus) // using pOutput instead of pRequest If (tOBXText [ "Item Number:") { Set tItemNumberProcessed = tItemNumberProcessed + 1 } If (tItemNumberCount > 1) && (tOBXText [ "REASON FOR REQUEST:") { set tOBXKey = i Set tOBXText=$p(tOBXText,":",2,5) /// OBX 5 text parsed to replace in OBX 5 later // Start replacement process Do pOutput.SetValueAt(tOBXText,"ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX.5") $$$LOGINFO(tOBXText_" ---- OBX 5 parsed to be saved in new OBX 5") /// works Set tOBXTextChanged = pOutput.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX.5") $$$LOGINFO(i_tOBXTextChanged_" --- i --- tOBXTextChanged, Is OBX 5 changed???") } Set i = i + 1 } } Do pOutput.%Save()
go to post Jeffrey Drumm · Mar 26 %ConstructClone() need only be done at the message object level. And I'm not sure why you're extracting segments to update them; you can use SetValueAt() against the message object instead a segment object ... For example this code: Set tOBXSegmentpOutput = pOutput.GetSegmentAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX", .tStatus) Do tOBXSegmentpOutput.SetValueAt(tOBXText, 5) Is functionally identical to: Do pOutput.SetValueAt(tOBXText,"ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX.5") That should help simplify the code. You shouldn't need to do a "deep clone" for this, so the "1" argument to %ConstructClone() is optional.
go to post Jeffrey Drumm · Mar 25 Somewhere between the %ContstructClone() call and the SetValueAt() call, you'll be using the GetSegmentAt() method of the clone to get the segment object into tOBXSegmentpOutput. The syntax of the SetValueAt() call should have the string to store as the first argument rather than the pOutput message object. tOBXSegmentpOutput is a reference to the segment in the clone message. It should not need to be explicitly saved, and I'm pretty sure SaveData() doesn't do what you think it does anyway.
go to post Jeffrey Drumm · Mar 25 There are a few issues with this code, but the biggest one is that you've created a clone and should be modifying the clone (pOutput) exclusively. However, you're still calling SetValueAt() against a segment pulled from pRequest. The above notwithstanding, you're still sending the original unchanged message object (pRequest) to the target operation with the SendRequestAsync() call. Fundamentally, you should: Create a clone of the request object pRequest (i.e. pOutput) Modify the clone, which is a mutable copy of the original message Send the modified clone (pOutput) to the target operation
go to post Jeffrey Drumm · Mar 25 The code sample you've provided above is pulling the OBX segment from pRequest, not the clone. pRequest is immutable so you can't make changes to it. You should be using the pOutput object for your SetValueAt() calls, and since it's a clone you can use it for the GetValueAt() calls as well. I provided pretty much everything you need to do this via a DTL in your other post on this subject. If you're worried about keeping things simple for ongoing support, the DTL solution is likely the better option.
go to post Jeffrey Drumm · Mar 20 You can do this by creating a collection object in a code block, and iterate over the comma-separated string to populate it: Set tArray = ##class(%ArrayOfDataTypes).%New() For i = 1:1:$LENGTH(LookupVal,",") { Do tArray.SetAt($PIECE(LookupVal,",",i),i) } You can now iterate over the collection variable in the for each action:
go to post Jeffrey Drumm · Mar 15 Here's how I would approach this using a DTL ... Create a counter variable to hold the number of occurrences of "Item Number:" Use a for each action to iterate over the OBX segments In the for each, create an if action that checks for the presence of "Item Number:" in OBX:5 (using ..Contains()) If the action is true, increment the counter variable Example: On completion of the for each, you should have a value that indicates the presence of more than one "Item Number." Next, update the OBXs: Create another counter variable to track occurrences of "Item Number" to be used for comparison Add another for each action to perform the modifications to the OBX segments Use the $REPLACE() or ..Replace() functions to replace "REASON FOR REQUEST:" in each OBX:5 with an empty string Add an if action to check the OBX:5 field for "Item Number:"; increment the new counter variable if found Add an if action to evaluate the new counter variable against the original; if it's less than the original, add a line break (not actual $C(13)/$C(10) characters, but escaped representation such as \.br\ or \X0D\\X0A\; likely vendor-dependent) Example:
go to post Jeffrey Drumm · Mar 13 While you can create multiple productions in a single namespace, you can only run one at a time. There's really no good reason to have more than one production in a namespace ... and with each production in its own namespace, they can be running concurrently.
go to post Jeffrey Drumm · Mar 7 Inserting characters that are defined as HL7 delimiters to fields is likely to be problematic. It would be very helpful if you would provide an example of the source message and the desired result.
go to post Jeffrey Drumm · Feb 25 Something like this should do the trick: ClassMethod GetConnectionStatus(pItemName As %String, ByRef pStatus As %String, ByRef pState As %String) As %Status [ Language = objectscript ] { Set tStatement = ##class(%SQL.Statement).%New() Set tSC = tStatement.%PrepareClassQuery("Ens.Util.Statistics","EnumerateJobStatus") Return:$$$ISERR(tSC) tSC Set tRS = tStatement.%Execute(pItemName) If tRS.%SQLCODE = 0 { Do tRS.%Next() Set pStatus = tRS.%Get("Status") Set pState = tRS.%Get("AdapterState") Return $$$OK } Return $$$ERROR(5001,"Status not Found") } Call it with the status and state variables passed by reference: Set sc=##class(My.Class).GetConnectionStatus("T_SPM_SIU",.status,.state)
go to post Jeffrey Drumm · Feb 15 The class Ens.MessageHeader has a classmethod Purge() that deletes message headers and bodies based on the number of days to keep along with a few other criteria; those are used in an SQL query to select the set of messages to purge. That query along with the associated purge code should work as an example to see what's involved in carefully removing messages without collateral damage ... search table indices need to be maintained, for example. There's also the Ens.Util.MessagePurge task definition that is used for scheduled purges, it has much of the same code but offers a multi-threaded purge feature that leverages the work queue. @Enrico.Parisi's solution moves the actual purge of the messages off to the scheduled Ens.Util.MessagePurge task for messages that exceed the DaysToKeep limit, as long as that is configured to delete bodies too. This is likely the safer solution and requires significantly less effort 😉
go to post Jeffrey Drumm · Feb 10 Does the operation have a job number in the Jobs tab on the right side of the Production page? That should correspond to the task's process ID in Windows. You should be able to terminate it with Task Manager or the taskkill command.
go to post Jeffrey Drumm · Jan 30 I'm pretty sure the SegType property (deprecated, should be using DocType) should be set to just "2.7:OBX".
go to post Jeffrey Drumm · Jan 3 Since there are no ROL segments in the source message, your foreach never executes. Just check for the existence of the ROL segment in the source, and if it doesn't exist, populate any field in a ROL segment in the target: