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
Comments
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)
checking. OBX 5 shows it is immutable
%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)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.
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
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.
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.
NA
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)
I'm using $$$LOGINFO to check the SetValueAt() value
In the code you posted that's not true for all SetValueAt().
Does any call returns an error? If so, what's the error?
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
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()
I really appreciate the help. I wish there were freelancers that I could pay to help.
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.
NA
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) ?
%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.
NA
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()sadly ,, same results
.png)
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 i 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 i = 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 i = i + 1
}
// Second loop: Modify OBX segments if necessary
Set i = 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 i = i + 1
}
}
//Do pOutput.%Save()
Set tSave2 = pOutput.%Save()
$$$LOGINFO(tSave2_" ---- did it save")
//
The OBX 5 changes just aren't getting saved
I also tried this outside the loop; Do pOutput.%Save()
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
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
And in every method you call that returns a status code, check it!😊