Advanced Device

Advanced Device

The Advanced HL7 to CDA device converts ORU and MDM HL7 messages to CDA (for Catalyst ingestion). This device has been designed to cover cases where end-users require full control over the CDA-conversion process (as opposed to the standard device, which has limited configuration options).

The advanced HL7 to CDA device is derived from Connexion's built-in Custom Code Device. Please see the documentation for the Custom Code Device for help with general operation and navigation of this device.

Both the standard and advanced versions of this device are built upon a common core which provides a default implementation for ORU Narrative, ORU Lab, and MDM messages. The standard device exposes a simple configuration UI, whereas the advanced version exposes a set of .NET classes (in C#) which can be altered to provide new functionality.

Background: How does the device work?

Both the standard and advanced devices use three components.

  • Templates: These are text files containing the target CDA document with the variable pieces replaced by 'tags' (We use Microsoft's Razor format from ASP.NET). When the device runs, it replaces each 'tag' with the proper value. These documents also support embedded C# and VB statements. The standard device contains two templates: ORU and MDM.

  • Template Contexts: These are classes which specify which values will be injected into the templates. There is one for each type of CDA: ORU Narrative, ORU Lab, and MDM.

  • Content Converters: These are classes which convert OBX-5 to structured content.

When the device runs, it combines each component to produce the CDA document.

Navigation

This device displays C# classes and templates in an items tree. You can modify templates and classes by selecting the corresponding item within this tree. New items can be added by right-clicking each category node and choosing the 'Add ...' option. The device comes pre-configured with three classes and two templates:

  • Conversion Methods (required): Edit this class to return the OIDs, Doc ID, Set ID, and CDA type based on the current HL7 message. You can also specify a template and/or template context.

  • Razor Context (optional): A sample template context where you can configure custom values to be passed to your templates. You can also register custom converters to be used for OBX-5 conversion.

  • Custom OBX5 Converter (optional): A sample OBX-5 converter class which can be used to  supply your own structured content.

  • Templates (required): CDA template files.

Conversion Methods

This class contains functions for specifying the CDA conversion settings on a per-message basis. Each of the following conversion functions must return a non-empty string:

GetRootOid: Typically the root OID will be constant for a given feed.

public override string GetRootOid(IMessageContext context) { return "2.16.840.1.113883.3.21"; }

GetPatientIssuerOid: Return the OID fragment which corresponds to the incoming HL7 message. Typically mapped from PID-3.4.1.

public override string GetPatientIssuerOid(IMessageContext context) { var hl7 = context.GetAsHL7Message(); switch(hl7.PID.PatientIdentifierList_03.First.AssigningAuthority_04.NamespaceID_01.Value.ToUpperInvariant()) { case "HFHS": return "1"; case "EPC": return "2"; default: return "3"; } }

GetExamIssuerOid: Return the OID fragment which corresponds to the incoming HL7 message. Typically mapped from OBR-3.2 (ORU) or TXA-12.2 (MDM).

public override string GetExamIssuerOid(IMessageContext context) { var hl7 = context.GetAsHL7Message(); var sourceVal = GetCdaType(context) == "MDM" ? hl7.Segments.First<TXA>().UniqueDocumentNumber_12.NamespaceID_02.Value.ToUpperInvariant() : hl7.OBR.FillerOrderNumber_03.NamespaceID_02.Value.ToUpperInvariant(); switch(sourceVal.toUpperInvarient()) { case "VAL1": return "1"; default: return "3"; } }

GetDocumentId: Return the document ID for the current HL7 message. In some cases you may wish to generate multiple CDA documents from a single HL7 message, and in this case you will need to use a custom template context class.

public override string GetDocumentId(IMessageContext context) { var hl7 = (HL7Message)context.Message; if(GetCdaType(context) == "ORU") return hl7.OBR.FillerOrderNumber_03.EntityIdentifier_01.Value.ExceptChars(new[] { '(', ')', '{', '}', ' ' }); return hl7.Segments.First<TXA>().UniqueDocumentNumber_12.EntityIdentifier_01.Value.ExceptChars(new [] { '(', ')', '{', '}', ' '}); }

GetSetId: Return the Set ID for the current HL7 message. In some cases you may wish to generate multiple CDA documents from a single HL7 message, and in this case you will need to use a custom template context class.

public override string GetSetId(IMessageContext context) { return GetDocumentId(context); }

GetCdaType: Return the CDA type for the current feed.

public override string GetCdaType(IMessageContext context) { // "ORU" = ORU Narrative // "LAB" = ORU Lab // "MDM" return "ORU"; }

Once you have configured these functions, you need to determine if you will use a custom context or custom template.

Templates

Connexion supports the Microsoft Razor templating engine. Microsoft developed the Razor language circa 2010 for use with ASP.NET MVC and it is widely regarded as an easy and concise template language. In the HL7 to CDA device, templates are CDA documents intertwined with Razor variables and in some cases simple C# or Visual Basic code. When creating templates for use with this device, it is usually easiest to start with a target CDA document and replace the variable pieces with Razor variables.

As diagrammed in the 'Background' section above, the Razor engine requires two components: A template and a template context. The template defines the structure of the resulting CDA document, and the template context class provides the values which will be injected into the template variables. Within the template, variables and single-line code statements start with the @ character. Multi-line code blocks are enclosed by curly braces: @{ ...C#... }. Razor requires that you define the type of the context class which will be used by this template by placing an '@model YourContextType' (lower case 'm') as the first line in the template. A second special variable '@Modal' (upper case 'M') references this context class throughout the rest of the template.

The CDA template has the following items:

  • @model (lower case 'm') declaration in the first line to define the context class type.

  • @using statements to import any types used within your template, including your context class.

  • @Model (upper case 'M') statements to insert values from your context. Example @Model.CurrentDateTime accesses the CurrentDateTime property of your context class. If you are accessing functions, you'll need to append parenthesis. Example: @Model.GetSomeValue().

The template editor provides intellisense for the @Model class when the template is able to compile correctly (ie, @model type and @using statements are correct). If you do not have intellisense, please double-check these values as well as re-referencing any references.

There are lots of Razor tutorials on the web. [External Link: Read more about Razor].

Sample Razor Template
@model HL7ORUCdaBuilder @using MModal.Hl7CdaCatalyst; <?xml version="1.0" encoding="utf-8"?> <ClinicalDocument xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:hl7-org:v3 CDA.ReleaseTwo.CommitteeBallot02.Dec.2003.xsd" xmlns="urn:hl7-org:v3" xmlns:mm="http://mmodal.com/cdaExtensions" xmlns:voc="urn:hl7-org:v3/voc" xmlns:cda="urn:hl7-org:v3" mm:major="2" mm:minor="0"> <typeId extension="PCOCD_HD000040" root="@Model.RootOid.1.3"/> <templateId root="@Model.RootOid.10.20.6"/> <id extension="@Model.DocumentIdExtension" root="@Model.RootOid.1.1"/> <title>@Model.Title</title> <effectiveTime value="@Model.DocumentDateTime"/> <confidentialityCode code="N" codeSystem="@Model.RootOid.5.25"/> <languageCode code="en-US"/> <setId extension="@Model.DocumentSetIdExtension" root="@Model.RootOid.1.1"/> <versionNumber value="1"/> <recordTarget> <patientRole> <id extension="@Model.PatientId" root="@Model.RootOid.@Model.PatientOid"/> <patient> <name> <family>@Model.PatientLastName</family> <given>@Model.PatientFirstName</given> <given>@Model.PatientMiddleName</given> </name> <administrativeGenderCode codeSystem="@Model.RootOid.5.1" code="@Model.PatientGenderCode"/> <birthTime value="@Model.PatientDOB"/> </patient> </patientRole> </recordTarget> ... </ClinicalDocument>

The Razor language supports embedded C# and VB. While you can write complex code blocks directly within the template, we recommend that you only use template-based code for adding conditionality and/or repetition. For example, if you wish to conditionally show a CDA block, you could use the following code:

Razor Conditional
@if(!string.IsNullOrEmpty(@Model.OrderId)) { <inFulfillmentOf> <order> <id extension="@Model.OrderId" root="@Model.RootOid.5"/> <priorityCode/> </order> </inFulfillmentOf> }
Razor Repetition
 @foreach(var location in @Model.Locations) { <location> <healthCareFacility> <id extension="@location.HealthcareFacilityId" root="@Model.RootOid.9"/> <location> <name>@location.HealthcareFacilityName</name> </location> </healthCareFacility> </location> }

Templates also support embedded code blocks. There are two special methods available to you:

  • Raw: Writes out the object (ToString()) without xml encoding.

  • Write: Writes out the object (ToString()) with xml encodeing.

C# Code Block
<component> <structuredBody> <component> <section> <text> @{ ...some C#...   Raw(@Model.StructuredBodyContent); // this will not be encoded Write(@Model.StructuredBodyContent); // this will be encoded ...more C#... } </text> </section> </component> </structuredBody> </component>

Specifying A Template

Within the "Conversion Methods" class, an override is provided to allow you to specify which template to use on a per-message basis. Simply return the name (as it appears in the templates tree) and that template will be used by the Razor engine for the current message.

This is an optional override. If you omit this function, the default logic (as shown below) will be used.

Template Type
public override string GetRazorTemplateName(IMessageContext context) { var hl7 = (HL7Message)context.Message; return hl7.MessageCode == "ORU" ? "Oru" : "Mdm"; }

Template Contexts

Template context classes act as a template data source during CDA construction. As shown above, your template must define the type of context class to be used by adding the '@model ContextType' as the first line in your template. This will instruct the Razor engine to substitute your context class every time the @Model keyword is used. For example, if you use @Model.MyProperty in your template, the Razor engine will replace this with MyContextClass.MyProperty during compilation. This requires that all property and method calls within your template exist in your context class (with the correct casing).

Context Types

The HL7 to CDA device includes a base context type called CdaContextBase. There are several sub-classes: OruLabContext, OruNarrativeContext, and MdmContext. These context classes provide the default implementations for CDA generation and are expected to be used with the default ORU and MDM templates.

The "Razor Context" class is an example of a custom context class which builds upon the CdaContextBase base class to provide custom values to a template. In addition to overriding the base properties and methods, you can add your own properties and methods to be consumed by a template.

HL7 Message Access

The current HL7 message is exposed through the Message property. Although not recommended, you can access the HL7 message directly within your template by using the @Model.Message variable.

Property Override
public override string SourceSystem { get { return Message["MSH-4"]; } }
Add a Property
public string MyNewProperty { get { return "MyVal"; } // access this in your template via @Modal.MyNewProperty }

Context Input/output Validation 

The CdaContextBase context class contains two validation functions:

  • ValidateHL7: Provides a hook for validating the HL7 message prior to conversion to CDA. You can check for the presence of specific required fields and/or values. The base implementation simply checks for the presence of the required segments. Return a string containing validation error information to throw an error, or an empty string if the message is valid.

  • ValidateCda: Provides post-validation for validation of the CDA message. The base implementation loads the CDA into an XDocument and detects the presence of elements and values based on the Catalyst ingestion rules. Return a Tuple<string, string> (a type with containing two strings). The first string should contain validation warning information, and the second should contain validation error information. Return empty strings if the message is valid.

Both functions can be overridden to provide custom validation.

Validation
public override string ValidateHL7() { if(Message["MSH-3"] == "EPC") return "We don't accept messages from EPC. Message invalid";   return string.empty; } public override Tuple<string, string> ValidateCda(string cda) { return new Tuple<string, string>("validation warnings", "validation errors"); }

Specifying A Context

Within the "Conversion Methods" class, an override is provided to allow you to specify which context class to use on a per-message basis. Simply return the type of the context class and it will be used by the Razor engine for the current message.

This is an optional override.

Template Type
public override Type GetRazorContextType(IMessageContext context) { // use a custom razor context (defined in the class Razor Context on the right) return typeof(RazorContext); }

Structured Content Creation (OBX-5)

The HL7 to CDA device uses converters to implement the conversion of OBX-5 to text. Converters inherit from the abstract ConverterBase class and must implement the following functions:

  • GenerateStructuredContent: Returns a string containing the generated content.

  • IsHandlerForContent: Return true if this converter can convert the OBX-5 content.

  • Priority: Returns a number from 1 to 99, which indicates where in a collection of converters this one should be placed. Converters which need to test for a specific content type ({\rtf1..., PDF, base64, etc) should be ordered before (lower number) generic converters. When an HL7 message is received, each converters' IsHandlerForContent function is called and the first to return true will be used to generate the structured content.

Sample Converter
public class FooConverter : ConverterBase { public override string GenerateStructuredContent(HL7Message hl7) { var builder = new StringBuilder(); foreach (var obx in hl7.OBXs) { builder.Append(obx.ObservationValue_05.First.TX_01.Value + "foo"); } return builder.ToString(); } public override bool IsHandlerForContent(HL7Message hl7) { return hl7.OBXs.First().ObservationValue_05.First.TX_01.Value.StartsWith("foo", StringComparison.OrdinalIgnoreCase); } public override int Priority { get { return 5; } } }

The HL7 to CDA device contains a sample OBX-5 RTF converter called "Custom OBX5 Converter". This is a sample only and not production-ready.

Specifying Content Converters

Since template context classes provide structured text to a template via the @Model.StructuredBodyContent property, converters are registered on context classes. 

Converter Registration
public override void RegisterContentConverters() { Converters.Add(new OruTextConverter()); Converters.Add(new CustomOBX5Converter()); ... }

 Converters are ordered via their Priority value, not the order in which they are added to the Converters collection.