Written by

Question Gary M Lusso · Mar 22, 2025

HL7 OBX 5 modification resolved

I need to make changes to  OBX 5 which shows as immutable

I have tried ConstructClone, ThrowOnError, and Streams but I can't get the syntax correct

Example

OBX|1|TX|2000.02^REASON FOR REQUEST^AS4|142|REASON FOR REQUEST:      Total Cost:           0.00||||||O
                                        ^^^^ remove "REASON FOR REQUEST"                                                                            ^^ add cr/lf so down stream reports can be formatted more easily

I have the code done to parse out the "REASON FOR REQUEST" but I need to make the OBX 5 show the change.

Before:

OBX|1|TX|2000.02^REASON FOR REQUEST^AS4|142|REASON FOR REQUEST:      Total Cost:           0.00||||||O
OBX|1|TX|2000.02^REASON FOR REQUEST^AS4|143|Special Instructions: ||||||O
OBX|2|CE|^PROVISIONAL DIAGNOSIS|1|R52^Pain, unspecified^I10|O|||||||||||test|\0x0A\0x0D\

After

OBX|1|TX|2000.02^REASON FOR REQUEST^AS4|142|      Total Cost:           0.00||||||O

blank line added
OBX|1|TX|2000.02^REASON FOR REQUEST^AS4|143|Special Instructions: ||||||O
OBX|2|CE|^PROVISIONAL DIAGNOSIS|1|R52^Pain, unspecified^I10|O|||||||||||test|\0x0A\0x0D\

FYI

Set pRequest = ##class(EnsLib.HL7.Message).%OpenId(284)
Set pOutput = pRequest.%ConstructClone()
zw pOutput
pOutput=14@EnsLib.HL7.Message ; <OREF>

+----------------- general information ---------------
| oref value: 14
| class name: EnsLib.HL7.Message

| reference count: 2
+----------------- attribute values ------------------
| %ClonedId = 284
| %Concurrency = 1 <Set>
|%maps("runtimeIndex") = 149
|%maps("runtimeIndex",0) = ""
| AutoBuildMap = 0
| BuildMapStatus = ""
| CacheSegsGotten = 1
| DocType = "2.3:ORM_O01" <Set>
| DocTypeCategory = 2.3
| DocTypeName = "ORM_O01"
| Envelope = ""
| IsMutable = 1 <Set>     <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

I tried concantonating cr/lf.

The do statement doesn't work

 If (tItemNumberCount > 1) && (tTotalCostSegment > 0) {

                Set tOBXSegment = pRequest.GetSegmentAt("ORCgrp(1).OBRuniongrp.OBXgrp("_tTotalCostSegment_").OBX", .tStatus)

                Set tOBXText = tOBXSegment.GetValueAt(5)_"\X0D\\X0A\"  // Append escaped CR/LF representation

                Do tOBXSegment.SetValueAt(tOBXText, 5)     <<<<<<<<<<<<<<<<<<<<<<<<<<

            }

RESOLVED

I had to change the object for the send message

ElseIf (tMessageType="ORM") {
            //$$$ThrowOnError(..SendRequestAsync(..VistaTarget, pRequest, 1, "OutputCernerToVistaORM")) // can't use object, pRequest, created  for the HL7 message
            // 03272025 Must make a new command that uses the clone object instead of the HL7 message object which is immutable
            $$$ThrowOnError(..SendRequestAsync(..VistaTarget, pOutput, 1, "OutputCernerToVistaORM")) // Must use the object, pOutput,  created by the clone

Product version: IRIS 2021.2

Comments

Enrico Parisi · Mar 22, 2025

If the SetValueAt() method is not working I guess it's also returning an error, my suggestion is to check the returned error, so:

Set sc=tOBXSegment.SetValueAt(tOBXText, 5)
Do $system.OBJ.DisplayError(sc)

0
Gary M Lusso  Mar 23, 2025 to Enrico Parisi

checking. OBX 5 shows it is immutable

0
Julian Matthews · Mar 25, 2025

%ConstructClone() has a property of "deep" that is defaulted to 0, and it controls whether or not the new copy is a complete copy of the source, or if it merely copies the top level of the source object and then creates references to any sub objects contained within the source object. See more here.

My guess (without testing) is that the EnsLib.HL7.Message at a top level is being cloned with your code, but deep being false means that it's then still referencing the original EnsLib.HL7.Segment objects contained within the source EnsLib.HL7.Message object.

In your case, try changing to:

Set pOutput = pRequest.%ConstructClone(1)
0
Julian Matthews  Mar 25, 2025 to Julian Matthews

Also, from a HL7 perspective, trying to add line breaks between your first and second OBX lines may not translate as you're expecting. Any receiving system trying to parse the HL7 will either ignore the break or will see it as malformed HL7.

Assuming that whatever system is receiving the HL7 is producing an output from the OBX:5 values, then you'll want to try adding in a new line and leave the OBX.5 blank, or if your receiving system supports line breaks, then this would usually be with adding a "/.br/" to the end of the OBX.5 in your first OBX (some systems may expect a different value to represent a line break, so your milage may vary).

Finally, as a general point, I would personally move this into a DTL. If there are actions going on in an ObjectScript Business Process leading up to the need to manipulate the message, then you can call the DTL from within your ObjectScript rather easily.

0
Gary M Lusso  Mar 25, 2025 to Julian Matthews

Thanks. I'm rusty using DTL. I have to research that more but I agree.

I have the parsing and setting the OBX 5 segment working but it isn't saving the changes to the message

0
Jeffrey Drumm · Mar 25, 2025

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.

0
Julian Matthews  Mar 25, 2025 to Jeffrey Drumm

Good spot - I got so distracted by the %ConstructClone not being called with deep=1 that I didn't even notice that they were then still working from their pRequest variable.

0
Enrico Parisi  Mar 25, 2025 to Gary M Lusso

You keep not checking the status returned by SetValueAt() calls, that would probably gives clues/hints on the problem.

I suggest to change all calls with:
Set tSC=xxxx.SetValueAt(...)
$$$LOGSTATUS(tSC)
 

0
Gary M Lusso  Mar 25, 2025 to Enrico Parisi

I'm using $$$LOGINFO to check the SetValueAt() value

0
Enrico Parisi  Mar 25, 2025 to Gary M Lusso

In the code you posted that's not true for all SetValueAt().

Does any call returns an error? If so, what's the error?

0
Jeffrey Drumm  Mar 25, 2025 to Gary M Lusso

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:

  1. Create a clone of the request object pRequest (i.e. pOutput)
  2. Modify the clone, which is a mutable copy of the original message
  3. Send the modified clone (pOutput) to the target operation
0
Gary M Lusso  Mar 25, 2025 to Jeffrey Drumm

It shows pRequest and pOutput to see what the differenece is,.I  will commetn out pRequest

I think I am usng pOutput from the clone?

Set pOutput = pRequest.%ConstructClone(1)

Set tSC=tOBXSegmentpOutput.SetValueAt(pOutput, 5)

does this save the chnage?

Set tSC = tOBXSegmentpOutput.SaveData()

0
Gary M Lusso  Mar 25, 2025 to Gary M Lusso

I really appreciate the help. I wish there were freelancers that I could pay to help.

0
Jeffrey Drumm  Mar 25, 2025 to Gary M Lusso

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.

0
Gary M Lusso  Mar 26, 2025 to Jeffrey Drumm

Does the %ConstructClone()/GetSegmentAt()/SaveValeAt have to be done on every OBX segment  object as it goes through the loop?

When do you put a number in ConstructClone(1) ?

0
Jeffrey Drumm  Mar 26, 2025 to Gary M Lusso

%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.

0
Jeffrey Drumm  Mar 26, 2025 to Gary M Lusso

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 SubtypeSet 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 = 1Set tItemNumberCount = 0While (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 necessarySet i = 1Set tItemNumberProcessed = 0Set 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 pRequestIf (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 processDo pOutput.SetValueAt(tOBXText,"ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX.5")
                $$$LOGINFO(tOBXText_" ---- OBX 5 parsed to be saved in new OBX 5") /// worksSet 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()
0
Gary M Lusso  Mar 26, 2025 to Jeffrey Drumm

sadly  ,, same results

0
Gary M Lusso  Mar 27, 2025 to Gary M Lusso

I moved the $ConstructClone outside the loop but chnages are getting made still

Is there a way to check the contents of the clone?

Code:

Method FromCerner(pRequest As EnsLib.HL7.Message) As %Status
{
    #dim tStatus As %Status = $$$OK
    #dim eException As %Exception.AbstractException
    #dim tOBXSegment As EnsLib.HL7.Segment
    #dim tOBXText As %String
    #dim tItemNumberCount As %Integer = 0
    #dim tItemNumberProcessed As %Integer = 0
    #dim tTotalCostSegment As %Integer = 0
    #dim tOrderType As %String
    #dim As %Interger = 0
    #dim tOBXKey As %Integer = 0
    #dim tOBXTextChanged As %String
    //
    // Set the message
    Set tMessageType = pRequest.GetValueAt("MSH:9.1")
    
    Try {
        // Message Subtype
       // Message Subtype
    Set tMessageSubType = pRequest.GetValueAt("ORCgrp(1).ORC:1")
    //$$$LOGINFO(tMessageSubType_"----- tMessageSubType - = NW -- ")
    //
    // 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")
    //$$$LOGINFO(tOrderType_" ---- tOrderType Ordder Type = Implant Usage (PSAS) for first interation")
    If ((tOrderType["Implant Usage (PSAS)") || (tOrderType["Issued in Clinic (PSAS)")) {
        // First loop: Count occurrences of "Item Number:"
        Set = 1
        Set tItemNumberCount = 0
   Set pOutput = pRequest.%ConstructClone(1) // moved here because %ConstructClone() need only be done at the message object level.
   $$$LOGINFO(pOutput_" ---- pOutput to see if clone is created****")
        While (pOutput.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX")'="") {
            Set tOBXText = pOutput.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX:5", .tStatus)
            $$$LOGINFO(tOBXText_"**** "_i_" ---- tOBXText OBX 5 text and i value") // working
            //
            If (tOBXText [ "Item Number:") {
                Set tItemNumberCount = tItemNumberCount + 1
            }
            //
            If (tOBXText[ "Total Cost:") {
                Set tTotalCostSegment = i
                //$$$LOGINFO(tTotalCostSegment_" ------ tTotalCostSegment check Total Cost")
            }
            Set = + 1
        }
        // Second loop: Modify OBX segments if necessary
        Set = 1
  //Set pOutput = pRequest.%ConstructClone(1) //%ConstructClone() need only be done at the message object level.
        Set tItemNumberProcessed = 0
        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
            //$$$LOGINFO(tOBXText_" ---- tOBXText get OBX 5 text")
            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","set") // was a typo
                //$$$LOGINFO(tOBXText_" ---- OBX 5 parsed to be saved in new OBX 5")
                Set tOBXTextChanged = pOutput.GetValueAt("ORCgrp(1).OBRuniongrp.OBXgrp("_i_").OBX:5") // typo
                //$$$LOGINFO(i_" **** "_tOBXTextChanged_" --- i --- tOBXTextChanged, Is OBX 5 changed???") // OBX 5 is changed but not saved
            }
            Set = + 1
        }
    }
            //Do pOutput.%Save()
            Set tSave2 = pOutput.%Save()
            
            $$$LOGINFO(tSave2_" ---- did it save")
            //

The OBX 5 changes just aren't getting saved

0
Gary M Lusso  Mar 27, 2025 to Gary M Lusso

I also tried this outside the loop; Do pOutput.%Save()

0
Gary M Lusso  Mar 27, 2025 to Jeffrey Drumm

You won't believe the solution !!

I needed to change the send instructions with the clone object, pOutput, and NOT the HL7 message object, pRequest.

Example:

ElseIf (tMessageType="ORM") {
            //$$$ThrowOnError(..SendRequestAsync(..VistaTarget, pRequest, 1, "OutputCernerToVistaORM"))
            //  03272025 Must make a new command that uses the clone object instead of the HL7 message object which is immutable
            $$$ThrowOnError(..SendRequestAsync(..VistaTarget, pOutput, 1, "OutputCernerToVistaORM")) // Must use the object for the clone

This is resolved.

Now, I need to add the CR/LF

0
Jeffrey Drumm  Mar 27, 2025 to Gary M Lusso

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:

  1. Create a clone of the request object pRequest (i.e. pOutput)
  2. Modify the clone, which is a mutable copy of the original message
  3. Send the modified clone (pOutput) to the target operation
0
Enrico Parisi  Mar 27, 2025 to Jeffrey Drumm

And in every method you call that returns a status code, check it!😊

0