Pipelines for 834 Processing - BizTalk 2013 EDI for Health Care: HIPAA-Compliant 834 (Enrollment) and 837 (Claims) Solutions (2014) (2014)

BizTalk 2013 EDI for Health Care: HIPAA-Compliant 834 (Enrollment) and 837 (Claims) Solutions (2014)

Chapter 7. Pipelines for 834 Processing

In the previous chapter, you looked at receiving an inbound 834 document. In the process described, there were two key areas of functionality that were handled in custom pipeline/pipeline components. The functionality and development of these components are covered in this chapter. Archiving the data, along with EDI validation and disassembly will be covered first (as these map to the first stage of work outlined in Chapter 6 “Solution: Receiving 834 Data.” Splitting and building the metrics will be handled second in this discussion.

image Note There are several ways to handle what is presented in this chapter. The components presented here will work in a production environment, and are solid architecturally.

Pipelines

The naming and structure of the pipeline project was outlined in Chapter 6. There are two pipelines stored in a single project. The project name for this solution is Company.BizTalk.Pipelines.Split834. The pipeline components are split into two projects. Company.BizTalk.PipelineComponents.Split834 and Company.BizTalk.PipelineComponents.EDI.Archive.

Archiving and EDI Validation/Disassembly

The first pipeline – which handles the archiving and EDI specific portions of the processing – is call EDIArchive.btp, and is shown in Figure 7-1. In the Decode stage of this first pipeline, a custom pipeline component has been added (see Listing 7-1 for specifics on this component’s code). In the Disassemble stage, the standard EDI disassembler pipeline component has been added (this is available in the Toolbox, as shown in Figure 7-2).

image

Figure 7-1. The EDIArchive Pipeline

Listing 7-1. Writing the Archive Data in the StreamOnReadEvent Method

// the trading partner ID, used for dynamic looking in the config file, is
// pulled from the original streamed message using a simple substring lookup.
// This is the location in the actual EDI 834 text string
string ID = original834String.Substring(35, 15).Trim();
string dir = System.Configuration.ConfigurationManager.AppSettings[ID];

// the dir path needs to be extended with the file naming pattern you want for // archived data. This can be something as simple as [ID]_[GUID].txt.

using (
FileStream FAS = new FileStream(dir, FileMode.Append,
FileAccess.Write))
{
using (BinaryWriter FBWriter = new BinaryWriter(FAS)
{
FBW.Write(rargs.buffer, 0, rargs.bytesRead);
FBW.Flush();
FBW.Close();
}
}

image

Figure 7-2. EDI Disassembler is Available in the Pipeline Component Toolbox

The archiving of the data takes the exact EDI 834 message that arrived and writes it to a specified file directory in its native EDI format prior to being validated and disassembled in the EDI disassembler component. Chapter 2 showed how to do this from an orchestration Expression shape through a C# method in the helper library. The approach in the pipeline component is similar – grab the stream of data, determine the target archive directory and file name (which is configurable, based on the trading partner), and write it out.

image Note Building a pipeline component from scratch can be a daunting task. Thankfully, there are several third-party tools available that help with this development. One of the most valuable is a project on CodePlex which creates the majority of the core pipeline component code for you (it is available for download at http://btsplcw.codeplex.com).

The core piece of custom functionality in the archive process is the writing of the data to a target directory without impacting the message stream prior to the EDI disassembly and validation. Listing 7-1 shows code that can be used to write the data within the StreamOnReadEvent method of the pipeline component. This is a fairly common approach to archiving data in a pipeline component, and has some limitations. If the data reading terminates mid process (such as in a server reboot scenario) it is possible that only a portion of the message will be archived. You will want to test your solution extensively, and be sensitive to the requirements of your environment. There are several alternatives to this archiving approach, including more robust error proof pipeline components and simple processes such as multiple hop receive/send port solutions.

Splitting and Performing Metrics

The second pipeline works with the data after all of the EDI specific tasks have occurred (validation/party resolution/acknowledgement generation/etc.). At this point, the data is pure XML, and no schemas do any further work. The pipeline receives the data and the metric countsare performed on the full EDI 834 XML. Once these metrics are complete (and the desired information has been written to a database or persisted via some other means) the 834 XML is split into individual 834 XML documents based on the subscriber and its dependents. The pipeline (called Split834.btp is shown in Figure 7-3.

image

Figure 7-3. The Split834 Pipeline

The 834 XML is passed through the disassemble stage where the data can be parsed and split up. There are two key methods in the processing of this data. The first is the Disassemble method, which is part of the core pipeline component framework, and which is shown in Listing 7-2. The second is Create834, the method that parses through the full 834 and dissects it, splitting it into individual 834 messages and sends them to the MessageBox, is shown in Listing 7-3. This method also passes off the original 834 XML to a database where the metric counts are performed (and the data written to a table so that SSRS or other similar process can report on the information).

Listing 7-2. A Customized Disassemble Method

public void Disassemble(IPipelineContext context, IBaseMessage inMsg)
{
Stream originalDataStream = inMsg.BodyPart.GetOriginalDataStream();

Create834(context, inMsg.Context, originalDataStream,inMsg);
}

Listing 7-3. The 834 Splitter

// declare an object of type Queue
private Queue<IBaseMessage> _MessageQueue = new Queue<IBaseMessage>();

private void Create834(IPipelineContext context, IBaseMessageContext
sourceContext, Stream enrollmentStream, IBaseMessage inMsg)
{
IBaseMessage msg = null;

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(enrollmentStream);

// take the full 834 XML document and send it to a stored procedure
// for processing the metrics. See Listing 7-4 for sample SQL for these
// metrics. There are several samples throughout this book on C# code that
// can be used to call a stored procedure.
[... call metrics SQL ...]

string XML = xmlDoc.InnerXml;
string path = null;

XPathNavigator navigator = xmlDoc.CreateNavigator();

XPathNodeIterator xi = navigator.Select(@"/*[local-
name()='X12_00501_834']/*[local-name()='TS834_2000_Loop']/*[local-
name()='INS_MemberLevelDetail'][INS01_MemberIndicator[.='Y']]");
XPathNavigator top, toptMiddle, endMiddle, end;
string messageType =
"http://schemas.microsoft.com/BizTalk/EDI/X12/2006/X12_00501_834";
string systemNamespace = "http://schemas.microsoft.com/BizTalk/2003/system-properties";

while (xi.MoveNext())
{
var xmlDocSplit = new XmlDocument();
enrollmentStream.Position = 0;
xmlDocSplit.Load(enrollmentStream);
XPathNavigator navSplit = xmlDocSplit.CreateNavigator();

top = toptMiddle = endMiddle = end = null;

//Go previous
if (xi.CurrentPosition > 1)
{
// 2000 Loop
path = @"/*[local-name()='X12_00501_834']/*[local-
name()='TS834_2000_Loop'][1]";
top = navSplit.SelectSingleNode(path);
//last 2000
path = String.Format(@"(/*[local-name()='X12_00501_834']/*[local-
name()='TS834_2000_Loop']/*[local-name()='INS_MemberLevelDetail']
[INS01_MemberIndicator[.='Y']]/parent::node())[{0}]/preceding-
sibling::*[1]", xi.CurrentPosition.ToString());
toptMiddle = navSplit.SelectSingleNode(path);
}

//Go next
if (xi.CurrentPosition < xi.Count)
{
//first 2000
path = String.Format(@"(/*[local-name()='X12_00501_834']/*[local-
name()='TS834_2000_Loop']/*[local-name()='INS_MemberLevelDetail']
[INS01_MemberIndicator[.='Y']]/parent::node())[{0}]", (xi.CurrentPosition +
1).ToString());
endMiddle = navSplit.SelectSingleNode(path);
//last 2000
path = "/*[local-name()='X12_00501_834']/*[local-
name()='TS834_2000_Loop'][position()=last()]";
end = navSplit.SelectSingleNode(path);
}

if (top != null)
{
navSplit.MoveTo(top);
navSplit.DeleteRange(toptMiddle);
}
if (endMiddle != null)
{
navSplit.MoveTo(endMiddle);
navSplit.DeleteRange(end);
}

msg = context.GetMessageFactory().CreateMessage();
msg.AddPart("Body", context.GetMessageFactory().CreateMessagePart(), true);
msg.BodyPart.Data = new
MemoryStream(UTF8Encoding.UTF8.GetBytes(xmlDocSplit.OuterXml));

//Copy properties from the original
msg.Context = sourceContext;
msg.Context.Promote("MessageType", systemNamespace, messageType);

// queue the split message for processing
_MessageQueue.Enqueue(msg);
}
}

image Note As mentioned in Chapter 6, inbound 834 data can be split at several levels using the XSD and no other code. The solution in this chapter shows how to split the document using a pipeline component, for some very specific processing requirements, but the data can be split using the schema as discussed at http://msdn.microsoft.com/en-us/library/bb226327.aspx.

SQL Code for Metrics

The metrics that have been referred to in this case include coming up with several counts on the full 834 XML file. These counts include total number of member records, total number of groups, and total number of subscribers. Taking the full XML and passing it to a stored procedure allows for the use of simple XQuery statements that can be used in conjunction with temporary tables to insert data directly into the target tables. Alternatively, C# could be used to do the metrics if you do not have any need to write the data out to a SQL table for reporting.Listing 7-4 gives an example of code to get the mentioned metrics from an 834 XML file.

Listing 7-4. The 834 Splitter

CREATE PROCEDURE [dbo].[GetMetrics]
@XML as nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;

-- replace all ns0: declarations for ease of XQuery queries
SELECT CAST(REPLACE(@XML,'ns0:','')as XML)as sourceXML
INTO #TempXML
DECLARE @totalMemberRecords as int
,@totalSubscriberRecords as int
,@totalGroups as int

-- get a count of distinct groups in the 834
SET @totalGroups =
(
SELECT
COUNT(distinct substring(h.value('REF_SubLoop[1]/REF_MemberPolicyNumber[1]
/REF02_MemberGrouporPolicyNumber[1]','varchar(max)'),3,4)) as [Group]
FROM #TempXML
CROSS APPLY sourceXML.nodes('//X12_00501_834/TS834_2000_Loop')as header(h)
)

SET @totalMemberRecords =
(
SELECT COUNT(h.value('INS_MemberLevelDetail[1]/INS01_MemberIndicator[1]'
,'varchar(max)'))
FROM #TempXML
CROSS APPLY sourceXML.nodes('//X12_00501_834/TS834_2000_Loop')as header(h)
)

SET @totalSubscribers =
(
SELECT COUNT(h.value('INS_MemberLevelDetail[1]/INS01_MemberIndicator[1]'
,'varchar(max)'))
FROM #TempXML
CROSS APPLY sourceXML.nodes('//X12_00501_834/TS834_2000_Loop')as header(h)
WHERE h.value('INS_MemberLevelDetail[1]/INS01_MemberIndicator[1]'
,'varchar(max)') = 'Y'
)

Conclusion

This chapter detailed the development steps for several pipeline and pipeline components that may be useful for inbound 834 solution development. Archiving the original 834 native EDI data is essential – there will be frequent occasions when the need to reference and validate the original data will be required, and it is critical that the data is available in the exact format that the trading partners originally delivered it in. Splitting the data is also extremely valuable in many scenarios, especially when there are orchestration steps that need to take place at the subscriber level. By splitting the data at the pipeline level, and performing all metrics prior to any orchestrations, all looping and complex data handling is eliminated from orchestration development (and, in turn, this simplifies the whole process and streamlines troubleshooting and testing).