HL7Spy.Core class level documentation: HL7Spy.Core.chm
(after downloading, you will need to right-click on the file, go to Properties, and “Unblock” it.)

How can I remove RXA-12, not just delete the value, but remove it?

January 1, 2018

Question: How can I remove RXA-12, not just delete the value, but remove it so that RXA-13 becomes the new RXA-12?

To do this you can use the simple function below. They way it works is to go through all RXA segments and calls the RemoveAt(12) on the segment fields. This will remove the field causing all fields after RXA-12 to be offset by one.

public override void Run()
{
   HL7Message message = GetParsedMessage();
   foreach(HL7Segment segment in message.GetSegments("RXA"))
   {
     segment.Fields.RemoveAt(12);
   }
    
   this.SaveMessage(message,"RXA-12 Removed");
}

Find Last A08/Visit not discharged A03

July 26, 2017

Question: How do I find the last A08/Visit not discharged.

This code searches through a message set, pulling the last A08 per visit/encounter. If the visit gets an A03 discharge message, then the visit is excluded from the final data set.

The final data set loads in a new tab and has only the last A08 for non-discharged encounters. If you want to include the A04s, see below for lines to comment out.

Note: that some of the A08 messages could still be for discharged patients. The PV1-45 discharge date/time may contain a value, but the original message set not have contained a corresponding A03.

Thank you to Scott Hall for contributing this code.

string _fileName = @"c:\_test.txt";
Dictionary<string,Encounter> _data = new Dictionary<string,Encounter>(10000);
Dictionary<string,Encounter> _discharged = new Dictionary<string,Encounter>(10000);

public class Encounter
{
   public Encounter(string encounterId)
   {
    EncounterId = encounterId;
   }

   public IMessageData Message;
   public string EncounterId;
}

public override void Run()
{
  // Get an HL7 Message in parsed format
  HL7Message message = GetParsedMessage();
  MSH msh = message.Segments.First<MSH>();
  if(msh==null || (msh.MessageType_09.TriggerEvent_02.Value!="A08" && msh.MessageType_09.TriggerEvent_02.Value!="A03"))
    return; // not an ADT^A08 message
  PID pid = message.Segments.First<PID>();
  if(pid==null)
    return; // no pid segment

  PV1 pv1 = message.Segments.First<PV1>();
  if(pv1==null)
    return; // no PV1 segment for encounter number
  string encounterId = pv1.VisitNumber_19.ToString(); //pid.PatientIdentifierList_03.First.ToString();
  Encounter CSN;

  // handle A03 Discharges
  if(msh.MessageType_09.TriggerEvent_02.Value=="A03")
  {
    if(!_discharged.TryGetValue(encounterId,out CSN)) // is patient already in the discharged dictionary?
    {
      _discharged.Add(encounterId,CSN);
    }

    // If you want A04 messages included for discharged patients, then comment out these two lines.
    // Otherwise, your dataset will be A08 messages for patients w/o an A04.
    // If you comment these lines, you might also want to change the tab name in the SaveMessage at the end.

    _data.Remove(encounterId);
    return;
  }

  // For A08 patient updates, check if the patient is already discharged.
  if(msh.MessageType_09.TriggerEvent_02.Value=="A08")
  {
    if(_discharged.TryGetValue(encounterId,out CSN)) // is patient in the discharged dictionary?
    {
      return;
    }
  }

  if(!_data.TryGetValue(encounterId,out CSN)) //CSN not found in dictionary?
  {
     CSN = new Encounter(encounterId);
     _data.Add(encounterId,CSN);
  } 

  CSN.Message = Message;
}

// Called once after the last message has been processed.
// It is a good place to perform cleanup and to report information.
// Always called from the UI thread.
public override void OnFinish()
{
   SaveMessage(_data.Values.Select(p=>p.Message),"Last A08/Visit not discharged A03");
}

How do I pull all OBX-5s where OBX-3.1=’SS003′

December 19, 2016

Question:

I am working on a syndromic surveillance feed for the CDC and hope you can help me with an HL7 SQL question. The messages contain multiple OBX lines. In those lines, I need to pull: 

data in OBX-5
from the segment
 where OBX-3.1 = ‘SS003’

The order and number of OBX segments varies, so I can’t always specify a specific OBX segment.
Is there a way to do this in HL7 Spy? I’m including a set of OBX segments below as an example.

OBX|1|NM|11289-6^BODY TEMPERATURE:TEMP:ENCTRFIRST:PATIENT:QN^LN||97.4|degF^FARENHEIT^UCUM|||||F
OBX|2|NM|59408-5^OXYGEN SATURATION:MFR:PT:BLDA:QN:PULSE OXIMETRY^LN||97|%^PERCENT^UCUM|||||F
OBX|3|CWE|8661-1^CHIEF COMPLAINT:FIND:PT:PATIENT:NOM:REPORTED^LN||||||||F
OBX|4|CWE|8661-1^CHIEF COMPLAINT:FIND:PT:PATIENT:NOM:REPORTED^LN||||||||F
OBX|5|NM|21612-7^AGE TIME PATIENT REPORTED^LN||60|a^YEAR^UCUM|||||F
OBX|6|CWE|SS003^FACILITY / VISIT TYPE^PHINQUESTION||225XN1300X||||||F
OBX|7|TX|44833-2^DIAGNOSIS.PRELIMINARY:IMP:PT:PATIENT:NOM:^LN||per ems pt with increased confusion. pt also presents with ulcers to bilateral feet||||||F
OBX|8|AD|SS002^TREATING FACILITY LOCATION^PHINQUESTION||701 10TH STREET SE||||||F

Answer:

The best way to accomplish this is to write a small amount of Custom Code. In the example below, all OBX-5 values where OBX-3.1 = ‘SS003’ will be placed in a file called obx5s.csv. Once all the messages have been processed the file will automatically be opened in excel, if it is installed on the computer.

private StreamWriter _writer;
private string _fileName = Path.Combine(Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop),"ob5s.csv");

public override void OnStart()
{
  _writer = new StreamWriter(_fileName,false);
}

// Called once or many times based on RunTypeRequested
public override void Run()
{
  // Get an HL7 Message in parsed format
  HL7Message message = GetParsedMessage();
  foreach(var obx in message.OBXs)
  {
    if(obx[3,1,1] == "SS003")
    _writer.WriteLine("{0}", obx[5]);
   }
}
public override void OnFinish()
{
   if(_writer!=null)
   {
     _writer.Close();
     _writer = null;
     System.Diagnostics.Process.Start(_fileName);
   }
}

How do I export fields from repeated segments?

September 15, 2015

Question:

I am trying to pull data from multiple HL7 messages using the SQL query: SELECT *, PID-5.1 as ‘B Last Name’, PID-5.2 as ‘B First Name’, OBR-4.1 as ‘Order Number’, OBR-4.2 as ‘Order’ WHERE MSH-11 != ‘T’

The issue I’m having is messages with multiple OBRs. How can I get data all the data from the SQL statement and the data from each OBR on each message?

Answer: 

Unfortunately you cannot use the HL7 SQL tool for this. This tool returns at most 1 row per message. You would have to write some custom code to do this.

The code in the example below will create a file on the desktop called out.csv which will be a comma separated list of the fields

string _fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),"out.csv");
StreamWriter _fileWriter;

public override void OnStart() 
{
   _fileWriter = new StreamWriter(_fileName,false);
}

public override void Run() 
{   
  // Get an HL7 Message in parsed format
  HL7Message message = GetParsedMessage();
  
  // skip any test messages
  if(message.MSH[10]=="T")
    return;
  
  PID pid = message.Segments.First<PID>();
  if(pid == null)
    return;
  
  // Get the first PID segment
  foreach(OBR obr in message.Segments.OfType<OBR>())
  {
     _fileWriter.Write(pid[5,1,1]);
     _fileWriter.Write(", ");
     _fileWriter.Write(pid[5,1,2]);
     _fileWriter.Write(", ");
     _fileWriter.Write(obr.Segment.Path.Repeat);
     _fileWriter.Write(", ");
     _fileWriter.Write(obr[2,1,1]);
     _fileWriter.Write(", ");
     _fileWriter.Write(obr[2,1,2]);
     _fileWriter.WriteLine();
  }
  
}

public override void OnFinish() 
{
  if(_fileWriter != null)
  {    
    _fileWriter.Close(); // close the file
    _fileWriter = null;
    // launch notepad to display the file.
    System.Diagnostics.Process.Start("Notepad",_fileName);
  }
}

How do I copy the value in HL7 field OBX[1]-14.1 to HL7 field OBR[1]-7.1?

September 10, 2015

Question:

I have a file of hundreds of HL7 messages and would like to copy the value in HL7 field OBX[1]-14.1 to HL7 field OBR[1]-7.1.  Basically, I want to overwrite the contents of field OBR-7 with the contents of field OBX-14 on every message.

Answer: 

The following code will loop through all the OBR segments, and set OBR-17 to the value of the first OBX segment following the current OBR segment.

public override void Run() 
{
  HL7Message message = GetParsedMessage();
  foreach(OBR obr in message.Segments.OfType<OBR>())
  {
    OBX obx = obr.Segment.GetRelative<OBX>(1); // get the next OBX
    if(obx != null)
      obr[17] = obx[14];
  }
  
  SaveMessage(message.ToString(), "Result");
 }

How do I find all A14 messages that do not have a matching A05?

June 11, 2013

Question:

Thanks for making such a great product. 🙂 Question, I have two tabs open with ADT messages. I need to do a query to see what patients are in the first tab and are not in the second. There is not select Join type function supported. My first tab is A04’s and the second is A14’s. Need to know which A04’s don’t have my patient in the A14’s list.

I send a quick registration (A14) to a system that returns a A05 (or it should).  I need to know when an A05 is not returned for an A14.  The field I am actually matching on is PV1-50 (we put special IDs there).

Answer:

Thank you for being a supportive customer.
The only way I can think of doing this is to merge the two tabs into a single tab and write a Custom Code function to perform the analysis. See my solution below.

private string _fileName = @"C:\temp\results.txt";
private class TrackedItem
{
   public int Index {get;set;}
   public string AlternateVisitNumber {get;set;}
   public string MessageControlId {get;set;}
}

Dictionary<string,TrackedItem> _a14s = new Dictionary<string,TrackedItem>(StringComparer.CurrentCultureIgnoreCase);
Dictionary<string,TrackedItem> _a05s = new Dictionary<string,TrackedItem>(StringComparer.CurrentCultureIgnoreCase);

public override void Run()
{
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  var pv1 = message.GetFirstSegment<PV1>();
  if(pv1 == null)
    return; // no PV1 segment

  Dictionary<string,TrackedItem> collection=null;
  if(message.TriggerEvent=="A14")
  {
  collection = _a14s;
  } else if(message.TriggerEvent=="A05")
  {
    collection = _a05s;
  }

  if(collection == null)
    return; // not an A05, or A04 message

  TrackedItem item=null;
  if(!collection.TryGetValue(pv1.AlternateVisitID_50.IDNumber_01.Value, out item))
  {
    // remember information about the message
   item = new TrackedItem
          {
            Index = MessageIndex,
            AlternateVisitNumber = pv1.AlternateVisitID_50.IDNumber_01.Value,
            MessageControlId = message.MSH.MessageControlID_10.Value,
          };
    collection.Add(pv1.AlternateVisitID_50.IDNumber_01.Value, item);
  }
}

// this is called at the very end
public override void OnFinish()
{
  using(var stream = new System.IO.StreamWriter(_fileName))
  {
    stream.WriteLine("A14s without a matching A05");
    ExportDiff(stream, _a14s, _a05s);
    stream.WriteLine("A05s without a matching A14");
    ExportDiff(stream, _a05s, _a14s);
  }
  try{
    System.Diagnostics.Process.Start("Notepad",_fileName);
  } catch{}
 }

// show the difference between two collections
private void ExportDiff(StreamWriter writer, Dictionary<string,TrackedItem> collection1, Dictionary<string,TrackedItem> collection2)
{
  foreach(TrackedItem item in collection1.Values.OrderBy(i=>i.Index))
  {
    if(!collection2.ContainsKey(item.AlternateVisitNumber))
    {
      writer.Write(item.Index.ToString());
      writer.Write(" ");
      writer.Write(item.AlternateVisitNumber);
      writer.Write(" ");
      writer.WriteLine(item.MessageControlId);
   }
 }
}

How to handle Z-Segments in Custom Code

November 28, 2012

Question:
How can we handle Z-Segments in HL7Spy’s Custom Code Tool?

Answer:
The following code demonstrates how use Z-Segments. We start by adding a couple of ZF1 segments (1), then we set a few fields (2), and finally print the fields out using the Log function.

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  // (1) Append a couple of ZF1 segments
  message.Append(new Segment("ZF1")); // Append ZF1
  message.Append(new Segment("ZF1")); // Append a second ZF1
  string today=DateTime.Today.ToString("yyyyMMdd");
  int count=0;
  foreach(Segment zf1 in message.GetSegments("ZF1"))
  {
    ++count;
    //(2) set ZF1-3.1, and ZF1-3.2 with information from the MSH 
    zf1[3,1,1] = message.MSH[3,1,1] + "-V" + today;
    zf1[3,1,2] = message.MSH[9,1,2] + "-TEST";
    zf1[6] = count.ToString();
  }
  
  // (3) Log the information out using the Hl7Message class
  string firstZF1_6 = message["ZF1-6"];
  string secondZF1_6 = message["ZF1[2]-6"];
  Log(Severity.Informational,string.Format("ZF1-6={0}", firstZF1_6));
  Log(Severity.Informational,string.Format("ZF1[2]-6={0}",secondZF1_6));
  
  // save the message out to a new tab
  SaveMessage(message, "Modified Messages");  
}

Find last known allergies for each patient

October 7, 2012

Question:
How can we use HL7Spy to find the last set of known allergies for each patient in a set of messages?

Answer:
The best way is to write a custom function that remembers the last set of allergies for each patient. Then in the OnFinished function we print out all patients with their last set of known allergies.

string _fileName = @"c:\temp\_test.txt";
Dictionary<string,Patient> _data = new Dictionary<string,Patient>(10000);

public class Patient
{
   public Patient(string patientId) 
   { 
    Allergies = new HashSet<string>();
    PatientId = patientId;
   }
   public HashSet<string> Allergies;
   public string PatientId;
}

public override void Run() { 
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  MSH msh = message.Segments.First<MSH>();
  if(msh==null || msh.MessageType_09.TriggerEvent_02.Value!="A08")
    return; // not an ADT^A08 message
  
  PID pid = message.Segments.First<PID>();
  if(pid==null)
    return; // no pid segment
  
  List<AL1> patientAllergies = message.Segments.OfType<AL1>();
  if(patientAllergies.Count==0)
    return; // no allergies
  
  string patientId = pid.PatientIdentifierList_03.First.ToString();  
  
  Patient pat;
  if(!_data.TryGetValue(patientId,out pat))
  {
     pat = new Patient(patientId); 
     _data.Add(patientId,pat);
  }
  
  pat.Allergies.Clear();
  
  // loop through all AL1 segments
  foreach(AL1 al1 in patientAllergies)
  {
     pat.Allergies.Add(al1.AllergenTypeCode_02.ToString());
   }
}
// Called once after the last message has been processed.
// It is a good place to perform cleanup and to report information.
// Always called from the UI thread.
public override void OnFinish() 
{
  // to figure out the distribution of all allergies
  Dictionary<string, int> allergies = new Dictionary<string,int>(1000);
  
  // write the data out to a file
  using(System.IO.StreamWriter wr = new System.IO.StreamWriter(_fileName))
  {   
    wr.WriteLine("Patient ID vs Allergy for {0} patient samples",_data.Count);
    wr.WriteLine();
   
    foreach(Patient p in _data.Values.OrderByDescending(p=>p.Allergies.Count))
    {
        wr.Write(p.PatientId);
        wr.Write(',');
        wr.Write(p.Allergies.Count); 
        foreach(string s in p.Allergies)
        {
           wr.Write(',');
           wr.Write(s);         
           // calculate the total number of allergy types for the given set of patients
           int frequency=0;
           if(!allergies.TryGetValue(s,out frequency))
           {
              allergies.Add(s,1);
           } else {
              allergies[s]=++frequency;
           }
        }
        wr.WriteLine();
    }
    
    wr.WriteLine();
    wr.WriteLine();
    wr.WriteLine("Allergy distribution for {0} patient samples.",_data.Count);
    wr.WriteLine("Number of distinct allergies is {0}.",allergies.Count);    
    wr.WriteLine();
    
    foreach(KeyValuePair<string,int> d in allergies)
    {
      wr.Write(d.Key);
      wr.Write(',');
      wr.Write(d.Value);
      wr.WriteLine();
    }       
  }
// launch notepad to display the file.
System.Diagnostics.Process.Start("Notepad",_fileName);
 }

Segments where OBX-15 differ from each other

October 7, 2012

Question:
Dear HL7Spy,
A user has a request – am not able to find function within HL7 to execute.

Find all the segments(i.e. OBX 15:1) within a message where content is different from each other. For example: If first OBX 15:1 is ABC, then find all other OBX within same message with content other than ABC.

Not sure if HL7Spy has that capability but thought I’d check.

Answer:
Sure, no problem, you can write a custom function to display all messages where this is true in a few lines of code.

To add this code, go to the Custom Code tool, select the “+” to add a new custom function, and cut and paste this code over-top of the default code that is displayed.

To run the code, load up a set of messages into a tab and click “Run”. What you should see is a new tab created that will contain any messages where the OBX-15 value differs between segments.

 public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  string obx15 = null;
  foreach(OBX obx in message.Segments.OfType<OBX>())
  {
    if(obx15==null)
    {
      obx15 = obx.ProducerSID_15.Value;
    }
    else if(obx15 != obx.ProducerSID_15.Value)
    {
      SaveMessage(message,"Different OBX-15");
      break;  
    }
  } 
} 

Find the last ADT^A08 message for each patient in the message collection?

October 6, 2012

Question:
How can I find the last ADT^A08 message for every patient in a collection of messages?

Answer:
The best way to do this is to create a dictionary based on the patient Medical Record Number (PID-3). For each message receive. Look up the patient in the dictionary, if there is no entry, add one. Otherwise, replace the existing entry’s message with the new version. See the code below.

string _fileName = @"c:\_test.txt";
Dictionary<string,Patient> _data = new Dictionary<string,Patient>(10000);

public class Patient
{
   public Patient(string patientId)
   {
    PatientId = patientId;
   }

   public IMessageData Message;
   public string PatientId;
}

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  MSH msh = message.GetFirstSegment<MSH>();
  if(msh==null || msh.MessageType_09.TriggerEvent_02.Value!="A08")
    return; // not an ADT^A08 message

  PID pid = message.GetFirstSegment<PID>();
  if(pid==null)
    return; // no pid segment

  string patientId = pid.PatientIdentifierList_03.First.Value; 
  Patient pat;
  if(!_data.TryGetValue(patientId,out pat))
  {
     pat = new Patient(patientId);
     _data.Add(patientId,pat);
  }

  pat.Message = Message;
}

// Called once after the last message has been processed.
// It is a good place to perform cleanup and to report information.
// Always called from the UI thread.
public override void OnFinish()
{
   SaveMessage(_data.Values.Select(p=>p.Message),"Last A08");
}

How do I remove PR1 segments?

September 13, 2012

Question:
Let’s say that I have a particular interface that is shipping me bad PR1 transactions, which is killing my interface. The vendor on the other side can’t make a change to his interface just yet, and I want to drop all those bad PR1s and let the vendor catch them up for me at a later date when he’s cleaned his data up. Is there a function that deals with including or deleting particular segments?

Answer:
If you create a new Custom Function and paste this code in, load up some messages with PR1 segments, and press “Run”, the PR1 segments will get stripped out.

 public override void Run()
 {
    // Get an HL7 Message in parsed format
    Hl7Message message = GetParsedMessage();
    // remove any segment with name PR1"
    message.RemoveAll(s=>s.SegmentName=="PR1");
    // save the message into an new tab called No PR1s
    SaveMessage(message, "NO PR1s");
 }

Subset of messages for which there is more than one MRN per account number

August 10, 2012

Question:
How can we get the subset of messages for which there is more than one MRN associated with an account number?

Answer:
The best way to do this is to use a dictionary and track each message received based on the account number. At the end of processing all messages, check to see if any account has more than 1 MRN in its list. See the code below.

 // Called once or many times based on RunTypeRequested
 public class Tracker
 {
   private readonly List<string> m_medicalRecordNumbers = new List<string>(1);
   public string AccountNumber {get;set;}
   public List<string> MedicalRecordNumbers { get{return m_medicalRecordNumbers;} }
 }
 
 private readonly Dictionary<string,Tracker> m_items = new Dictionary<string,Tracker>(StringComparer.OrdinalIgnoreCase);  
 
 // Called once before the first message is processed.
 // It is a good place to initialize objects.
 // Always called from the UI thread.
 public override void OnStart()
 {
   m_items.Clear();
 }
   
 public override void Run()
 {
   // Get an HL7 Message in parsed format
   Hl7Message message = GetParsedMessage();
   PID pid = message.GetFirstSegment<PID>();
   if(pid==null)
     return; // no pid segment

   var accountNumber = pid.PatientAccountNumber_18.Value;
   if(string.IsNullOrEmpty(accountNumber))
     return; // no account number
   
   var mrn = pid.PatientIdentifierList_03.First.Value;
   
   Tracker tracker;
   if(!m_items.TryGetValue(accountNumber, out tracker))
   {
     tracker = new Tracker{AccountNumber = accountNumber};
     m_items.Add(accountNumber, tracker);
   }
   
   if(!tracker.MedicalRecordNumbers.Any(m=>string.Compare(m, mrn, true)==0))
     tracker.MedicalRecordNumbers.Add(mrn);
 }
 
 // Called once after the last message has been processed.
 // It is a good place to perform cleanup and to report information.
 // Always called from the UI thread.
public override void OnFinish()
{
 string path = Path.GetTempFileName();
 using(var stream = new System.IO.StreamWriter(path))
 {
   List<Tracker> itemsWithMultipleMrns = m_items.Values.Where(i=>i.MedicalRecordNumbers.Count > 1).ToList();
   if(itemsWithMultipleMrns.Count == 0)
   {
     stream.WriteLine("No Account numbers were found with duplicate MRNs");
   } else
   {      
     foreach(Tracker item in itemsWithMultipleMrns)
     {
       stream.Write(item.AccountNumber);
       foreach(var mrn in item.MedicalRecordNumbers)
       {
         stream.Write(" ,");
         stream.Write(mrn);
       }
       stream.WriteLine();      
     }
   }
   // show the results in notepad
   try
   {
     System.Diagnostics.Process.Start("Notepad", path);
   } catch(Exception ex)
   {
     this.Log(Severity.Error, string.Format("Could not open file '{0}' with notepad",path));
   }
 }
} 

Capture EVN-1, PID-3, PV1-2, FT1-2 to file

June 11, 2012

Question:
How can I capture a list of the following fields: EVN-1, PID-3, PV1-2, FT1-2 in a comma delimited list?
Answer:
Custom code is the best approach if you are expecting repeated FT1 segments. The following code will create a file in the c:\temp directory containing the specified list, and will display in Notepad upon completion.

string _fileName = @"c:\temp\_test.txt";
StreamWriter _file;

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  // Get the first FT1 segment
  EVN evn = message.Segments.OfType<EVN>().FirstOrDefault();
  PID pid = message.Segments.OfType<PID>().FirstOrDefault();
  PV1 pv1 = message.Segments.OfType<PV1>().FirstOrDefault();
  foreach(FT1 ft1 in message.Segments.OfType<FT1>())
  {
    _file.Write("{0},",evn != null ? evn.EventTypeCode_01.Value : null);
    _file.Write("{0},",pid != null ? pid.PatientIdentifierList_03.First.Value : null);
    _file.Write("{0},",pv1 != null ? pv1.PatientClass_02.Value : null);
    _file.Write("{0},",ft1 != null ? ft1.TransactionID_02.Value : null);
    _file.WriteLine();
  }
}

public override void OnStart()
{
  // create the file to be written out
  _file = new StreamWriter(_fileName,false);
  _file.WriteLine("EVN_EVENT_TYPE, PID_MRN, PV1_PATIENTCLAS", "FT1_TRANS_ID");
}

// Called once after the last message has been processed.
// It is a good place to perform cleanup and to report information.
// Always called from the UI thread.
public override void OnFinish() {
  // sort the data
  _file.Close();
  // launch notepad to display the file.
  System.Diagnostics.Process.Start("Notepad",_fileName);
}

Messages with duplicate OBR-3.1 to a new tab

June 11, 2012

Question:
I’m looking the way to get “DISTINCT” OBR-3.1 value messages. How can I do this in custom code. I want to show any messages that have duplicate OBR-3.1 values to a new tab. Please note we have multiple OBR segments per message.

Answer:
The following code should do the trick.

private class TrackedItem
{
   public List<IMessageData> Messages=new List<IMessageData>();
   public int Index {get;set;}
}

Dictionary<string,TrackedItem> _knownValues = new Dictionary<string,TrackedItem>(StringComparer.CurrentCultureIgnoreCase);

public override void OnStart() {
  _knownValues.Clear();
}

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  OBR obr = message.Segments.First<OBR>();
  if(obr != null && !string.IsNullOrEmpty(obr.FillerOrderNumber_03.Value))
  {
    string obrIdentifier = obr.FillerOrderNumber_03.Value;
    TrackedItem item;
    if(!_knownValues.TryGetValue(obrIdentifier,out item))
    {
       item = new TrackedItem();
       _knownValues.Add(obrIdentifier,item);
    } else
    {
       Log(Severity.Informational,string.Format("duplicates found at index",MessageIndex));
       if(item.Index==0)
        item.Index = MessageIndex;
    }
    item.Messages.Add(Message);
  }
}

public override void OnFinish() {
  var list = _knownValues.Values.Where(i=>i.Messages.Count>1).ToList();
  Log(Severity.Informational,string.Format("{0} duplicates found",list.Count));
  foreach(TrackedItem item in list)
  {
    SaveMessage(item.Messages,"Duplicates");
  }
}

How do I create test data from a set of messages?

June 6, 2012

Question:
I would like to book some of you and or your teams time to develop or enhance HL7spy custom codes or product for my team. We would like the custom code to do the following:

  1. Take HL7 transactions (loaded in the Query Results tab)
  2. Append “-” + OBR-2.1 … to OBR-3.1 + “-” + some variable, ‘date yyyymmdd’
  3. Append “-” + some variable like “TEST” to OBR-3.2
  4. Delete the contents of OBR-2

Answer:
No need to hire us. This is pretty easy to do. There are two ways of accomplishing this: using type safe classes, and using field position specifications. Type safe classes provide the benefit of automatically documenting the code.

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  string today=DateTime.Today.ToString("yyyyMMdd");
  // loop through all the OBR segments in the message
  foreach(OBR obr in message.GetSegments<OBR>())
  {
    //(2) Append "-" + OBR-2.1 ... to OBR-3.1 + "-" + some variable, 'date yyyymmdd'
    obr.FillerOrderNumber_03.EntityIdentifier_01.Value = obr.PlacerOrderNumber_02.EntityIdentifier_01.Value + "-V" + today; 
    //(3) Append "-" + some variable like "TEST" to OBR-3.2
    obr.FillerOrderNumber_03.NamespaceID_02.Value = obr.FillerOrderNumber_03.NamespaceID_02.Value + "-TEST";
    //(4) Delete the contents of OBR-2
    obr.PlacerOrderNumber_02.Value = "";
    // save the message out to a new tab
    SaveMessage(message, "Modified Messages");
  }
}
public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  string today=DateTime.Today.ToString("yyyyMMdd");
  foreach(OBR obr in message.GetSegments<OBR>())
  {
    //(2) Append "-" + OBR-2.1 ... to OBR-3.1 + "-" + some variable, 'date yyyymmdd'
    obr[3,1,1] = obr[2,1,1] + "-V" + today;
    //(3) Append "-" + some variable like "TEST" to OBR-3.2
    obr[3,1,2] = obr[3,1,2] + "-TEST";
    //(4) Delete the contents of OBR-2
    obr[2] = "";
    // save the message out to a new tab
    SaveMessage(message, "Modified Messages");
  }
}

Find all unique OBX-3/OBR-3 values

May 22, 2012

Question:
Hi, I am using HL7Spy HL7 SQL for the first time and am trying to pull the values stored in obx-3.1.1 for repeating obx segments but it seems to be only pulling the first value. I thought that this query would accomplish what I needed but I do not believe it is.

SELECT OBx[*]-3.1.1,OBx[*]-3.2.1 WHERE OBx[*]-3.1.1 IS NOT NULL

I would also like to find all unique OBR-4 values too.

Answer:

The HL7 SQL function will only return one result per message, so it probably not what you are looking for.

It would be quite easy to write a Custom Function to do what you are looking for. Is your goal to get a list of OBX-3.1, OBX-3.2 pairs with perhaps a count of how many times each pair occurred? If so, here is some code that will do just that.

private class TrackedItem
{
   public string Key {get; set;}
   public string Value {get;set;}
   public int Counter {get;set;}
}

Dictionary<string,TrackedItem> _knownValues = new Dictionary<string,TrackedItem>(StringComparer.CurrentCultureIgnoreCase);


public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  foreach(OBX obx in message.Segments.OfType<OBX>())
  {  
    string key = obx.ObservationIdentifier_03.Identifier_01.Value;   
    TrackedItem item;
    if(!_knownValues.TryGetValue(key,out item))
    {
       item = new TrackedItem {Key=key, Value=obx.ObservationIdentifier_03.Text_02.Value};
       _knownValues.Add(key,item);
    }
    item.Counter = item.Counter + 1;
  }
}

private string _fileName = @"C:\temp\_test.txt";
public override void OnFinish() {
  using(var stream = new System.IO.StreamWriter(_fileName))
  {
    stream.WriteLine("OBX-3.1, OBX-3.2, Count");
    foreach(TrackedItem item in _knownValues.Values.OrderByDescending(i=>i.Counter))
    {
      stream.Write(item.Key);    
      stream.Write("\t");
      stream.Write(item.Value);
      stream.Write("\t");
      stream.WriteLine(item.Counter.ToString());
    }
  }
 
  try{
    System.Diagnostics.Process.Start("Notepad",_fileName);
  } catch{}
}

And, a variation on the same theme for OBR-4

private class TrackedItem
{
   public string Key {get; set;}
   public string Value {get;set;}
   public int Counter {get;set;}
}

Dictionary<string,TrackedItem> _knownValues = new Dictionary<string,TrackedItem>(StringComparer.CurrentCultureIgnoreCase);

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  foreach(OBR obr in message.Segments.OfType<OBR>())
  {  
    string key = obr.universalServiceIdentifier_04.Identifier_01.Value;   
    TrackedItem item;
    if(!_knownValues.TryGetValue(key,out item))
    {
       item = new TrackedItem {Key=key, Value=obr.UniversalServiceIdentifier_04.Text_02.Value};
       _knownValues.Add(key,item);
    }
    item.Counter = item.Counter + 1;
  }
}

private string _fileName = @"C:\temp\_test.txt";
public override void OnFinish() {
  using(var stream = new System.IO.StreamWriter(_fileName))
  {
    stream.WriteLine("OBR-4.1, OBR-4.2, Count");
    foreach(TrackedItem item in _knownValues.Values.OrderByDescending(i=>i.Counter))
    {
      stream.Write(item.Key);    
      stream.Write("\t");
      stream.Write(item.Value);
      stream.Write("\t");
      stream.WriteLine(item.Counter.ToString());
    }
  }

  try{
    System.Diagnostics.Process.Start("Notepad",_fileName);
  } catch{}
}

Find all Unique OBR-4 – OBX-3 pairs in a message stream

May 11, 2012

Question:
Here is some custom code that you wrote for as a while back. The code exports a row for every unique OBR 4 and OBX 3 pair, and a count of the occurrences. This report is used by our lab mappers during the mapping process for a new laboratory. The lab mappers are requesting that the report contain additional information. Below is what they are requesting, with the more important item on top:

Include a full message with each OBR 4 – OBX 3 pair. It could be the 1st message for this pair, the last message for this pair, or any other message for this pair. Which message isn’t as import as actually having a message that contains the OBR 4 – OBX 3 pair. They plan to visually review this message as they are doing the mapping to resolve any issues with method, units, reference range, etc.

Include a list of OBR 4 terms only without any OBX information. They plan to use this for mapping the OBR 4 term, which we do for radiology and text reports.

Answer:
The following code will create a

private class TrackedItem
{
   public string Value {get;set;}
   public int Counter {get;set;}
}

Dictionary<string,TrackedItem> _knownValues = new Dictionary<string,TrackedItem>(StringComparer.CurrentCultureIgnoreCase);
private string _fileName = @"C:\temp\data.hl7";

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
  foreach(OBX obx in message.GetSegments<OBX>())
  {
    string obrIdentifier = message["OBR-4.1.1"];
    string obxIdentifier = obx.ObservationIdentifier_03.Identifier_01.Value;
    string key = (obrIdentifier + "-" + obxIdentifier).Trim();
    TrackedItem item;
    if(key=="-")
      continue;
    
    if(!_knownValues.TryGetValue(key,out item))
    {
       item = new TrackedItem();
       item.Value = string.Format("{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}",message["OBR-4.1"],message["OBR-4.2"],message["OBR-4.3"],obx.ObservationIdentifier_03.Identifier_01, obx.ObservationIdentifier_03.Text_02, obx.ObservationIdentifier_03.NameOfCodingSystem_03,obx.Units_06,obx.ReferencesRange_07);
       _knownValues.Add(key,item);
    }
    item.Counter = item.Counter + 1;
  }
}

public override void OnFinish() {
  using(var stream = new System.IO.StreamWriter(_fileName))
  {
    foreach(TrackedItem item in _knownValues.Values.OrderByDescending(i=>i.Counter))
    {
      stream.Write(item.Counter.ToString());
      stream.Write("|");
      stream.WriteLine(item.Value);
    }
  }
  
  try{
   // System.Diagnostics.Process.Start("Notepad",_fileName);
  } catch{}
}

Find all CPT codes for each patient

April 9, 2012

Question:
What I want to do is for each of the three accounts contained in PID-18, show all PR1-3 fields for all PR1 segments. Typically, these are sequential segments, e.g., PR1|1…, PR1|2…, etc. The purpose is to generate a list of all procedure and CPT codes for each of the patients.

Answer:
please try the following code:

make sure you can access the c:\temp directory, that is where the file is being stored.

To narrow the search, use HL7 SQL to filter for the patient accounts you are interested in, into a separate tab (SELECT INTO).

or, modify the _patientAccount list below to include the patients you want included, and uncomment the code with the // (1) “Uncomment me” comment

string _fileName = @"c:\temp\_test.txt";
private StreamWriter _writer;

// patient Identifiers
private string[] _patientAccounts = new string[]{"952-11-3088", "H000759100", "H000759092", "H000759118"};

public override void OnStart() {
  _writer = new StreamWriter(_fileName);
}

public override void Run() {
  // Get an HL7 Message in parsed format
  Hl7Message message = GetParsedMessage();
 
  PID pid = message.Segments.First<PID>();
  if(pid==null)
    return; // no pid segment
 
  // (1) Uncomment me
  //if(!_patientAccounts.Contains(pid[18,1,1]))
  //  return;
   
  foreach(var pr1 in message.Segments.OfType<PR1>())
  {
    if(pr1[3]!="")
    {
    _writer.WriteLine("{0},{1},{2},{3},{4},{5}",this.MessageIndex, pid[18], message.MSH[7], message.MSH[10], pid[5], pr1[3]);
    }
  }
}

// Called once after the last message has been processed.
// It is a good place to perform cleanup and to report information.
// Always called from the UI thread.
public override void OnFinish()
{
  if(_writer!=null)
  {
    _writer.Close();
    _writer = null;
  }
  // launch notepad to display the file.
  System.Diagnostics.Process.Start("Notepad",_fileName);
}