nHapiNET / nHapi

nHapi is the .Net port of the original Java project HAPI.
Mozilla Public License 2.0
277 stars 156 forks source link

ORU_R01 GetPATIENT_RESULT() Fails With Exception "PATIENT_RESULT does not exist in the group" #260

Closed michaelerice-github closed 2 years ago

michaelerice-github commented 2 years ago

When calling ORU_R01 message GetPATIENT_RESULT(), an exception occurs with the message "PATIENT_RESULT does not exist in the group". Currently using v26 namespace, but also tried v27 with the same result. Using v23 and calling GetRESPONSE() causes the same exception. Method AddPATIENT_RESULT() also produces the same exception.

Error code: APPLICATION_INTERNAL_ERROR Message: "An unexpected error ocurred"

Inner exception stack trace: at NHapi.Base.Model.AbstractGroup.GetStructure(String name, Int32 rep) at NHapi.Base.Model.AbstractGroup.GetStructure(String name) at NHapi.Model.V26.Message.ORU_R01.GetPATIENT_RESULT()

Let me know if you need additional information.

milkshakeuk commented 2 years ago

hi @michaelerice-github could you provide some example code?

michaelerice-github commented 2 years ago
using NHapi.Base.Parser;
using NHapi.Model.V26.Message;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Globalization;
using System.Windows.Forms;

namespace HL7Sample
{
    public class Form1 : Form
    {
        private IContainer components = null;
        private Button button1;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            var patient = new Patient { FirstName="John", MiddleName = "Q.", LastName = "Public", PatientId = "1234", Gender = "M" };

            var request = ObservationRequest.Create(patient);
        }

        private void InitializeComponent()
        {
            button1 = new Button();
            SuspendLayout();

            button1.Location = new Point(12, 12);
            button1.Name = "button1";
            button1.Size = new Size(75, 23);
            button1.TabIndex = 0;
            button1.Text = "button1";
            button1.UseVisualStyleBackColor = true;
            button1.Click += new EventHandler(button1_Click);

            AutoScaleDimensions = new SizeF(7F, 15F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(800, 450);
            Controls.Add(button1);
            Name = "Form1";
            Text = "Form1";
            ResumeLayout(false);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    }

    public class Constants
    {
        public const string AcceptAcknowledgmentType = "AL";
        public const string ApplicationAcknowledgmentType = "NE";
        public const string EncodingCharacters = "^~\\&";
        public const string FieldSeparator = "|";
        public const string LongDateTimeWithSecondsFormat = "yyyyMMddHHmmss";
        public const string ProcessingId = "P";
        public const string ReceivingApplication = "EMR";
        public const string ReceivingFacility = "HIS";
        public const string SendingApplication = "Device";
        public const string SendingFacility = "Elsewhere";
    }

    public class Patient
    {
        public string Gender { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string PatientId { get; set; }
    }

    public class ObservationRequest : ORU_R01
    {
        public const string MessageCode = "ORU";
        public const string TriggerEvent = "R01";
        public const string VersionId = "2.6";

        internal static ObservationRequest Create(Patient patient)
        {
            var dateTimeOfMessage = DateTime.UtcNow;
            var retval = new ObservationRequest();

            CreateMSHSegment(retval, dateTimeOfMessage);
            CreatePIDSegment(retval, patient);

            // TODO: CreatePV1Segment
            // TODO: CreateOBRSegment
            // TODO: CreateOBXSegments

            return retval;
        }

        private static void CreateMSHSegment(ORU_R01 message, DateTime dateTimeOfMessage)
        {
            var segment = message.MSH;

            segment.FieldSeparator.Value = Constants.FieldSeparator;
            segment.EncodingCharacters.Value = Constants.EncodingCharacters;
            segment.SendingApplication.NamespaceID.Value = Constants.SendingApplication;
            segment.SendingFacility.NamespaceID.Value = Constants.SendingFacility;
            segment.ReceivingApplication.NamespaceID.Value = Constants.ReceivingApplication;
            segment.ReceivingFacility.NamespaceID.Value = Constants.ReceivingFacility;
            segment.DateTimeOfMessage.Value = dateTimeOfMessage.ToString(Constants.LongDateTimeWithSecondsFormat, CultureInfo.InvariantCulture);
            segment.DateTimeOfMessage.Offset = 0;
            segment.MessageControlID.Value = dateTimeOfMessage.ToString(Constants.LongDateTimeWithSecondsFormat, CultureInfo.InvariantCulture);
            segment.MessageType.MessageCode.Value = MessageCode;
            segment.MessageType.TriggerEvent.Value = TriggerEvent;
            segment.MessageType.MessageStructure.Value = string.Join('_', MessageCode, TriggerEvent);
            segment.VersionID.VersionID.Value = VersionId;
            segment.ProcessingID.ProcessingID.Value = Constants.ProcessingId;
            segment.AcceptAcknowledgmentType.Value = Constants.AcceptAcknowledgmentType;
            segment.ApplicationAcknowledgmentType.Value = Constants.ApplicationAcknowledgmentType;
        }

        private static void CreatePIDSegment(ObservationRequest message, Patient patient)
        {
            var patientResult = message.GetPATIENT_RESULT(); // exception occurs here
        }

        public override string ToString()
        {
            return new PipeParser().Encode(this);
        }
    }
}
jakubsuchybio commented 2 years ago

That is weird. We use that part of code too and it's working fine with us. This is our usage in our HL7Message26Writer.cs

        private void Write_ORU_R01(MewORU_R01 mewORU_R01, HL7ProcessingID processingID)
        {
            var message = new ORU_R01();
            InitQuickStart(message, HL7MessageCode.ORU, mewORU_R01.EventType, processingID);

            var msh = message.MSH;
            FillMSH(msh, mewORU_R01);

            var patientResult = message.GetPATIENT_RESULT();
            if (_settings.HL7_v2_AddPatientAndPatientVisit)
            {
                var pid = patientResult.PATIENT.PID;
                FillPID(pid, mewORU_R01);

                if (mewORU_R01.AttendingDoctor != null && mewORU_R01.ReferringDoctor != null && mewORU_R01.ConsultingDoctor != null)
                {
                    var pv1 = patientResult.PATIENT.VISIT.PV1;
                    FillPV1(pv1, mewORU_R01);
                }
            }
            // ... other stuff
        }

        public void InitQuickStart(IMessage message, HL7MessageCode messageCode, HL7EventType eventType, HL7ProcessingID processingID)
        {
            var msh = (MSH)message.GetStructure("MSH");
            msh.FieldSeparator.Value = "|";
            msh.EncodingCharacters.Value = @"^~\&";
            msh.DateTimeOfMessage.SetLongDateWithFractionOfSecondAndLocalTimeZoneOffset(DateTime.Now);
            msh.MessageControlID.Value = MessageControlIDGenerator.GenerateID();
            msh.MessageType.MessageCode.Value = messageCode.ToHL7Value();
            msh.MessageType.TriggerEvent.Value = eventType.ToHL7Value();
            msh.ProcessingID.ProcessingID.Value = processingID.ToHL7Value();
        }

        private void FillMSH(MSH msh, IMewMSH mewMessage)
        {
            // do odchozi zpravy vlozime nase generovane MessageControlID
            msh.MessageControlID.Value = MessageControlIDGenerator.GenerateID();

            var sendingApplication = msh.SendingApplication;
            if (!string.IsNullOrEmpty(mewMessage.SendingApplicationNamespaceID))
                sendingApplication.NamespaceID.Value = mewMessage.SendingApplicationNamespaceID;
            if (!string.IsNullOrEmpty(mewMessage.SendingApplicationUniversalID))
                sendingApplication.UniversalID.Value = mewMessage.SendingApplicationUniversalID;

            var sendingFacility = msh.SendingFacility;
            if (!string.IsNullOrEmpty(mewMessage.SendingFacilityNamespaceID))
                sendingFacility.NamespaceID.Value = mewMessage.SendingFacilityNamespaceID;
            if (!string.IsNullOrEmpty(mewMessage.SendingFacilityUniversalID))
                sendingFacility.UniversalID.Value = mewMessage.SendingFacilityUniversalID;

            var receivingApplication = msh.ReceivingApplication;
            if (!string.IsNullOrEmpty(mewMessage.ReceivingApplicationNamespaceID))
                receivingApplication.NamespaceID.Value = mewMessage.ReceivingApplicationNamespaceID;
            if (!string.IsNullOrEmpty(mewMessage.ReceivingApplicationUniversalID))
                receivingApplication.UniversalID.Value = mewMessage.ReceivingApplicationUniversalID;

            var receivingFacility = msh.ReceivingFacility;
            if (!string.IsNullOrEmpty(mewMessage.ReceivingFacilityNamespaceID))
                receivingFacility.NamespaceID.Value = mewMessage.ReceivingFacilityNamespaceID;
            if (!string.IsNullOrEmpty(mewMessage.ReceivingFacilityUniversalID))
                receivingFacility.UniversalID.Value = mewMessage.ReceivingFacilityUniversalID;

            // ... other stuff
        }
michaelerice-github commented 2 years ago

Jakob, I am using nhapi version 3.1.0. What version are you using?

jakubsuchybio commented 2 years ago
<PackageReference Include="nHapi" Version="3.0.4.871" />

We have a little bit modified cached version for our company purposes. But it is basically 3.0.4

milkshakeuk commented 2 years ago

@michaelerice-github have you tried changing the type of the first argument in the CreatePIDSegment from ObservationRequest to ORU_R01 ?

private static void CreatePIDSegment(ORU_R01 message, Patient patient)
{
    var patientResult = message.GetPATIENT_RESULT(); // exception occurs here
}
michaelerice-github commented 2 years ago

@michaelerice-github have you tried changing the type of the first argument in the CreatePIDSegment from ObservationRequest to ORU_R01 ?

private static void CreatePIDSegment(ORU_R01 message, Patient patient)
{
    var patientResult = message.GetPATIENT_RESULT(); // exception occurs here
}

I just tried that - no success.

milkshakeuk commented 2 years ago

@michaelerice-github Ok, so this is throwing an exception because nhapi doesn't understand what is ObservationRequest is, it only knows about the message types in the library, now you can extend the classes but notice how the extended class has the same name as the original nhapi class only the namespace is different (this gets around the issue described below.

you can also register your own hl7 object models so that nhapi does understand them.

When you new up a nhapi message model it adds the structures of the hl7 message type to a collection internally to the class, it also removes prefixes of class names whilst doing so, in the case of ORU_R01 the class which holds the PATIENT_RESULT segment is actually called ORU_R01_PATIENT_RESULT but that normally gets added as PATIENT_RESULT because it strips the name of the message from the beginning, it can't do that with your class because it doesn't match and so it can't locate the segment to return it since its asking for PATIENT_RESULT but in your case the prefix wasnt stripped so its called ORU_R01_PATIENT_RESULT.

...
// remove message name prefix from group names for compatibility with getters ...
if (typeof(IGroup).IsAssignableFrom(c) && !typeof(IMessage).IsAssignableFrom(c))
{
    var messageName = Message.GetStructureName();
    if (name.StartsWith(messageName, StringComparison.Ordinal) && name.Length > messageName.Length)
    {
        name = name.Substring(messageName.Length + 1);
    }
}

to fix this in your case you can override GetStructureName like so:

public override string GetStructureName()
{
    return "ORU_R01";
}

but you should also add the following constructors otherwise you might not be able to parse hl7 into ObservationRequest.

/// <summary>
/// Creates a new ORU_R01 Group with custom IModelClassFactory.
/// </summary>
public ObservationRequest(IModelClassFactory factory)
    : base(factory)
{
}

/// <summary>
/// Creates a new ORU_R01 Group with DefaultModelClassFactory.
/// </summary>
public ObservationRequest()
    : base(new DefaultModelClassFactory())
{
}

That should fix your issue, let me know if you have any further questions.

michaelerice-github commented 2 years ago

I changed the code to create a new ORU_R01 instead of the ObservationRequest object and it works.

Thanks!

milkshakeuk commented 2 years ago
<PackageReference Include="nHapi" Version="3.0.4.871" />

We have a little bit modified cached version for our company purposes. But it is basically 3.0.4

@jakubsuchybio What modifications have you made?

jakubsuchybio commented 2 years ago

@milkshakeuk before you took over the administration of this project, it was kinda dead and we found a few bugs, which we fixed ourselves in our forked cache and created pull requests here. They are all already merged and fixed. So after upgrading to "latest" at a time, only changes to our fork are readme and custom version. Also I forgot to do one PR about a little bug in powershell build, will create here tommorow :).

milkshakeuk commented 2 years ago

@jakubsuchybio you know you can add custom models without editing the source code 😊

michaelerice-github commented 2 years ago

Overriding the structure name and adding the constructors was the changes that were necessary to make the code work. Thanks for taking the time to respond in such a quick manner!

From: Jake Aitchison @.> Sent: Friday, January 7, 2022 11:06 To: nHapiNET/nHapi @.> Cc: Michael Rice @.>; Mention @.> Subject: Re: [nHapiNET/nHapi] ORU_R01 GetPATIENT_RESULT() Fails With Exception "PATIENT_RESULT does not exist in the group" (Issue #260)

External Sender

Ok, so this is throwing an exception because nhapi doesn't understand what is ObservationRequest is, it only knows about the message types in the library, now you can extend the classeshttps://saravanansubramanian.com/hl72xnhapiparsemessage/#step-2-of-3---create-a-specialized-class-by-extending-the-adt-a01-message-class but notice how the extended class has the same name as the original nhapi class only the namespace is different (this gets around the issue described below.

you can also register your own hl7 object modelshttps://github.com/nHapiNET/nHapi/wiki/Configuration-Options#custom-hl7-object-models so that nhapi does understand them.

When you new up a nhapi message model it adds the structures of the hl7 message type to a collection internally to the class, it also removes prefixes of class names whilst doing so, in the case of ORU_R01 the class which holds the PATIENT_RESULT segment is actually called ORU_R01_PATIENT_RESULT but that normally gets added as PATIENT_RESULT because it strips the name of the message from the beginning, it can't do that with your class because it doesn't match and so it can't locate the segment to return it since its asking for PATIENT_RESULT but in your case the prefix wasnt stripped so its called ORU_R01_PATIENT_RESULT.

...

// remove message name prefix from group names for compatibility with getters ...

if (typeof(IGroup).IsAssignableFrom(c) && !typeof(IMessage).IsAssignableFrom(c))

{

var messageName = Message.GetStructureName();

if (name.StartsWith(messageName, StringComparison.Ordinal) && name.Length > messageName.Length)

{

    name = name.Substring(messageName.Length + 1);

}

}

to fix this in your case you can override GetStructureName like so:

public override string GetStructureName()

{

return "ORU_R01";

}

but you should also add the following constructors otherwise you might not be able to parse hl7 into ObservationRequest.

///

/// Creates a new ORU_R01 Group with custom IModelClassFactory.

///

public ObservationRequest(IModelClassFactory factory)

: base(factory)

{

}

///

/// Creates a new ORU_R01 Group with DefaultModelClassFactory.

///

public ObservationRequest()

: base(new DefaultModelClassFactory())

{

}

That should fix your issue, let me know if you have any further questions.

— Reply to this email directly, view it on GitHubhttps://github.com/nHapiNET/nHapi/issues/260#issuecomment-1007528683, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AQUUFMDMAOMLJCRJHQJYSGTUU4FO7ANCNFSM5LNGHD3Q. Triage notifications on the go with GitHub Mobile for iOShttps://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Androidhttps://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub. You are receiving this because you were mentioned.Message ID: @.**@.>>

CONFIDENTIAL NOTICE: If you are not the intended recipient of this message, you are not authorized to intercept, read, print, retain, copy, forward, or disseminate this communication. This communication may contain information that is proprietary, attorney/client privileged, attorney work product, confidential or otherwise legally exempt from disclosure. If you have received this message in error, please notify the sender immediately either by phone or by return e-mail, and destroy all copies of this message, electronic, paper, or otherwise.