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;  
    }
  } 
} 

How do I remove PR1 segments?

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");
 }

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

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);
}

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

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

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

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

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);
}