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?

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

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′

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?

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?

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?

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

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

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

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?

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");
}
  • Page 1 of 2
  • 1
  • 2