Wednesday, December 11, 2013

Fix HL7 ORU_R01_ORDER_OBSERVATION in NHapi

This week, I worked on a feature requiring parsing of HL7 lab test results messages (ORU_R01).
Some messages did not parse correctly, and trusting the NHapi library http://sourceforge.net/projects/nhapi/,
I've spent some time tinkering around the problem, namely being unable to parse multiple observations (OBX segment). In the API, OBSERVATION was for a single instance property.
Finding no ways further, I've downloaded the source and took a look inside. The code is structured fairly good and it was easy to compare with other HL7 API documentation, especially http://hl7api.sourceforge.net/v251/apidocs/ca/uhn/hl7v2/model/v251/group/ORU_R01_ORDER_OBSERVATION.html.

There were two issues:
- ORU_R01_OBSERVATION has ORU_R01_OBSERVATION group incorrectly defined as required, non-repeating. It should be optional, repeating. Similarly,
- ORU_R01_SPECIMEN group is defined as required, non-repeating, but should be optional, repeating as well.

Here is a simple code change in the group constructor:

///<summary>
/// Creates new ORU_R01_ORDER_OBSERVATION Group.
///</summary>
public ORU_R01_ORDER_OBSERVATION(IGroup parent, IModelClassFactory factory) : base(parent, factory)
{
  try {
    this.add(typeof(ORC), false, false);
    this.add(typeof(OBR, true, false);
    this.add(typeof(NTE), false, true);
    this.add(typeof(ORU_R01_TIMING_QTY), true, false<);
    this.add(typeof(CTD), false, false);
    this.add(typeof(ORU_R01_OBSERVATION), false, true); //TODO: make ORU_R01_OBSERVATION optional, repeating - was true, false
    this.add(typeof(FT1), false, true);
    this.add(typeof(CTI), false, true);
    this.add(typeof(ORU_R01_SPECIMEN), false, true); //TODO: make ORU_R01_SPECIMEN optional, repeating - was true,false
  }
  catch(HL7Exception e)
  {
    HapiLogFactory.GetHapiLog(GetType()).Error("Unexpected error creating ORU_R01_ORDER_OBSERVATION - this is probably a bug in the source code generator.", e);
  }
}

Also, single instance properties needed change to repetition access methods as follows:

//TODO: replace OBSERVATION instance accessor with collection
//  ///<summary>
//  /// Returns ORU_R01_OBSERVATION(a Group object) - creates it if necessary
//    ///</summary>
//    public ORU_R01_OBSERVATION OBSERVATION { 
//       get{
//       ORU_R01_OBSERVATION ret = null;
//       try {
//          ret = (ORU_R01_OBSERVATION)this.GetStructure("OBSERVATION");
//       } catch(HL7Exception e) {
//          HapiLogFactory.GetHapiLog(GetType()).Error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
//          throw new System.Exception("An unexpected error ocurred",e);
//       }
//       return ret;
//    }
//    }
    ///<summary>
    ///  Returns  first repetition of ORU_R01_OBSERVATION (a Group object) - creates it if necessary 
    ///</summary> 
    public ORU_R01_OBSERVATION GetOBSERVATION ()
    {
      ORU_R01_OBSERVATION ret = null;
        try 
        {
            ret = (ORU_R01_OBSERVATION)this.GetStructure("OBSERVATION");
        }
        catch (HL7Exception e)
        {
            HapiLogFactory.GetHapiLog(GetType()).Error("Unexpected error accessing data - this is probably a bug in the source code generator." , e);
            throw new System.Exception("An unexpected error ocurred", e);
        }
        return ret;
    }
 
     ///<summary> 
     ///  Returns a specific repetition of OUL_R21_OBSERVATION 
     ///   * (a Group object) - creates it if necessary 
     ///   throws HL7Exception if the repetition requested is more than one  
     ///       greater than the number of existing repetitions. 
     ///</summary> 
    public ORU_R01_OBSERVATION GetOBSERVATION(int rep)
    {
        return (ORU_R01_OBSERVATION)this.GetStructure("OBSERVATION", rep);
    }
 
     /**  
      * Returns the number of existing repetitions of OUL_R21_OBSERVATION  
      */ 
    public int OBSERVATIONRepetitionsUsed
    {
        get 
        {
            int reps = -1;
            try 
            {
                reps = this.GetAll("OBSERVATION").Length;
            }
            catch (HL7Exception e)
            {
                string message = "Unexpected error accessing data - this is probably a bug in the source code generator.";
                HapiLogFactory.GetHapiLog(GetType ()).Error(message, e);
                throw new System.Exception(message);
            }
            return reps;
        }
    }

and
//TODO: replace SPECIMEN instance accessor with collection
//    ///<summary>
//    /// Returns ORU_R01_SPECIMEN (a Group object) - creates it if necessary
//    ///</summary>
//    public ORU_R01_SPECIMEN SPECIMEN { 
//    get {
//       ORU_R01_SPECIMEN ret = null;
//       try {
//          ret = (ORU_R01_SPECIMEN)this.GetStructure("SPECIMEN");
//       } catch(HL7Exception e) {
//          HapiLogFactory.GetHapiLog(GetType()).Error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
//          throw new System.Exception("An unexpected error ocurred",e);
//       }
//       return ret;
//      }
//    }
    ///<summary>
    /// Returns  first repetition of ORU_R01_SPECIMEN (a Group object) - creates it if necessary
    ///</summary>
    public ORU_R01_SPECIMEN GetSPECIMEN()
    {
        ORU_R01_SPECIMEN ret = null;
        try
        {
            ret = (ORU_R01_SPECIMEN)this.GetStructure("SPECIMEN");
        }
        catch (HL7Exception e)
        {
            HapiLogFactory.GetHapiLog(GetType()).Error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
            throw new System.Exception("An unexpected error ocurred", e);
        }
        return ret;
    }
 
    ///<summary>
    ///Returns a specific repetition of ORU_R01_SPECIMEN
    /// * (a Group object) - creates it if necessary
    /// throws HL7Exception if the repetition requested is more than one 
    ///     greater than the number of existing repetitions.
    ///</summary>
    public ORU_R01_SPECIMEN GetSPECIMEN(int rep)
    {
        return (ORU_R01_SPECIMEN)this.GetStructure("SPECIMEN", rep);
    }
 
    /** 
     * Returns the number of existing repetitions of ORU_R01_SPECIMEN 
     */
    public int SPECIMENRepetitionsUsed
    {
        get
        {
            int reps = -1;
            try
            {
                reps = this.GetAll("SPECIMEN").Length;
            }
            catch (HL7Exception e)
            {
                string message = "Unexpected error accessing data - this is probably a bug in the source code generator.";
                HapiLogFactory.GetHapiLog(GetType()).Error(message, e);
                throw new System.Exception(message);
            }
            return reps;
        }
    }


Offered to contribute the change back to https://sourceforge.net/projects/nhapi/

Wednesday, October 2, 2013

Setting INSTALLFOLDER in Custom Action

Yesterday, I've looked at changing INSTALLFOLDER in a custom action for a WIX 3.7 installer project.

As installer technology is not my usual daily thing, the Web was the first resource. I've looked at a few of the related posts, where most of them advising that the action should be invoked after CostInitialize step or similar. This might be useful for other properties, but not for INSTALLFOLDER, as the installer apparently used the default value and installed the product in the default location.

After trying a few spots in the InstallExecuteSequence with the same result, I've ultimately logged the install run, which showed that the custom action was invoked too late, i.e. after target locations for the components were already set based on the default INSTALLFOLDER.

After looking at Suggested InstallExecuteSequence, on the MSDN library site, I've decided to execute it at the earliest available location i.e. before LaunchConditions. And it worked!

Below is the snippet of the code from the .wxs file

<CustomAction Id="SetINSTALLFOLDER" BinaryKey="CustomActions" DllEntry="SetINSTALLFOLDER" Execute="immediate" Return="check"  />
<InstallExecuteSequence>
 <Custom Action="SetINSTALLFOLDER" Before="LaunchConditions">NOT Installed<Custom>
</InstallExecuteSequence>

and here is a snippet of the code in the custom action:

public class CustomActions
{
  [CustomAction]
  public static ActionResult SetINSTALLFOLDER(Session session)
  {
    try
    {
      var installFolder = ... // defined here
      if (!string.IsNullOrEmpty(installFolder))
      {
        session["INSTALLFOLDER"] = installFolder; 
        session.Log("SetINSTALLFOLDER: set to '{0}'", session["INSTALLFOLDER"]);
      }
      return ActionResult.Success;
    }
    catch (Exception ex)
    {
      session.Log("SetINSTALLFOLDER exception: {0}", ex.Message);
      return ActionResult.Failure;
    }
  }
}

The thing that I don't like is that it also overrides user overrides via msiexec command line arguments, but it wasn't an issue this time...