Tuesday, November 8, 2011

HL7 HQMF Proof of Concept with hQuery

I've been tweeking the HL7 eMeasures DSTU to support Query Health this week and last to support a declarative, implementation independent, standards-based expression of queries.  There were a couple of things I needed to do to the e-Measure schema in order to make it workable for this effort:
  1. I added the RIM localVariableName attribute to the entries so I could have human readable names instead of just computer managed identifiers within the eMeasure.
  2. I fixed a bug in the Data types Release 1.1 schema released with the DSTU to support use of the expression element in specifying a criteria for an entry.  This makes for much simpler syntax in expressing queries on parameters.
  3. I did one other thing which I've forgotton right now.
The first step was to create a sample query expressed in the eMeasure format.  I used the NQF Diabetes: HbA1c Poor Control Quality Measure (measure 59) found on the Query and Data Model Analysis Page.  I was a little miffed about having to create the EMF from scratch, because it is clear that HTML comes from the eMeasure format, but I couldn't find the original XML for it.

Last week, Mark Hadley had posted the hQuery implementation for this measure, so I used hQuery as my first target implementation.

The JavaScript implementation declares a map() function which is used in the hQuery map/reduce implementation.  It does the following:
  1. Defines value sets
  2. Defines some query parameter variables for the start and end of the measure
  3. Defines matching criteria.
  4. Defines measure functions for the population, numerator, denominator, and exception critiera.
  5. Implements the map() function logic, which is fixed and very short (see below)
Here is the main logic of the map() function.  That never changes so I just emit it using a set of <xsl:text> statements.

  if (population(patient)) {
    emit("population", 1);
    if (denominator(patient)) {
      if (numerator(patient)) {
        emit("denominator", 1);
        emit("numerator", 1);
      } else if (exclusion(patient)) {
        emit("exclusion", 1);
      } else {
        emit("denominator", 1);
      }    
    }
  }


Defining Value Sets
So, my first step was to identify value sets and generate the code for them.  That was pretty easy.  I just searched for every occurrence of @valueSet in the EMF document, and then wrote a bit of XSLT to translate the SVS format XML document I created from the NQF Value Set spreadsheet into a set of JavaScript declarations.  Using SVS to access value sets is one of the suggestions I made last week.


This is the XSLT that does that:
 <xsl:variable name='codeSets' select="document('svs.xml')"/>
 <xsl:for-each select="$codeSets//ValueSet[@id=$valueSets]">
   <xsl:text>    "</xsl:text><xsl:value-of select='@id'/>
   <xsl:text>": {&#xA;</xsl:text> 
   <xsl:variable name="this" select="."/>
   <xsl:for-each select="set:distinct(ConceptList/Concept/@codeSystemName)">
     <xsl:variable name="cs" select="."/>
     <xsl:text>      "</xsl:text><xsl:value-of select='.'/>
     <xsl:text>": [ </xsl:text>
     <xsl:for-each select='$this/ConceptList/Concept[@codeSystemName=$cs]'>
       <xsl:text>"</xsl:text>
       <xsl:value-of select='@code'/>
       <xsl:text>", </xsl:text>
     </xsl:for-each>
     <xsl:text> ], &#xA;</xsl:text>
    </xsl:for-each>
    <xsl:text>      },&#xA;</xsl:text>
  </xsl:for-each>
  <xsl:text>    };&#xA;</xsl:text> 




That gave me text looking like this (with a lot of stuff removed just to show what I did):

  var terms = {
    "2.16.840.1.113883.3.464.1.72": {
      "CPT": [ "83036", "83037",  ], 
      "LOINC": [ "17856-6", "4548-4", "4549-2",  ], 
      "SNOMED-CT": [ "117346004", "165680008", "259689004", "259690008", "313835008", "33601001", "40402000", "408254005", "43396009",  ], 
    },
    "2.16.840.1.113883.3.464.1.98": {
      "ICD-10-CM": [ "E28.2",  ], 
      "ICD-9-CM": [ "256.4",  ], 
      "SNOMED-CT": [ "69878008",  ], 
    },
  };


Define Query Parameter Variables
The next step was to create variables for some of the measure parameters.  I declared the parameters in the EMF file as follows:



  <entry typeCode="COMP">
    <localVariableName>StartDate</localVariableName>
    <observation classCode="OBS" moodCode="EVN">
      <code code="52832-3" codeSystem="2.16.840.1.113883.6.1"/>
      <value xsi:type="TS" value="20100101"/>
    </observation>
  </entry>
  <entry typeCode="COMP">
    <localVariableName>EndDate</localVariableName>
    <observation classCode="OBS" moodCode="EVN">
      <code code="52833-1" codeSystem="2.16.840.1.113883.6.1"/>
      <value xsi:type="TS" value="20101231"/>
    </observation>
  </entry>

These were translated to variable declarations using this bit of XSLT:

  <xsl:for-each 
    select='$DataDeclarationSection//emf:entry[emf:localVariableName != ""]/
            emf:observation[@moodCode="EVN" and not(@isCriterionInd="true")]'>
    <xsl:text>  var </xsl:text><xsl:value-of select="../emf:localVariableName"/>
    <xsl:text> = new </xsl:text>
    <xsl:value-of select="emf:value/@xsi:type"/><xsl:text>(</xsl:text>
    <xsl:choose>
      <xsl:when test="emf:value/@xsi:type='CD'">
        <xsl:text>"</xsl:text>
        <xsl:value-of select='emf:value/@code'/><xsl:text>","</xsl:text>
        <xsl:value-of select='@codeSystem'/><xsl:text>"</xsl:text>
      </xsl:when>
      <xsl:when test="emf:value/@xsi:type='TS'">
        <xsl:text>"</xsl:text>
        <xsl:value-of select='emf:value/@value'/><xsl:text>"</xsl:text>
      </xsl:when>
      <xsl:when test="emf:value/@xsi:type='PQ'">
        <xsl:text>"</xsl:text>
        <xsl:value-of select='emf:value/@value'/><xsl:text>","</xsl:text>
        <xsl:value-of select='emf:value/@unit'/><xsl:text>"</xsl:text>
      </xsl:when>
      <xsl:when test="emf:value/@xsi:type='ST'">
        <xsl:text>"</xsl:text><xsl:value-of select='emf:value'/>
        <xsl:text>"</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>"</xsl:text><xsl:value-of select='emf:value/@value'/>
        <xsl:text>"</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  <xsl:text>);&#xA;</xsl:text>
</xsl:for-each>

That looked like this when it was done:

// Generate named variables
  var StartDate = new TS("20100101");
  var EndDate = new TS("20101231");

Note how I used constructors to create HL7 data types.

Defining Matching Criteria
Next was the data criteria.  Here are a couple of examples of those in the EMF document:

  <entry typeCode="COMP">
    <localVariableName>ageBetween17and64</localVariableName>
    <observation classCode="OBS" moodCode="EVN" isCriterionInd="true">
      <id root="0" extension="ageBetween17and64"/>
      <code code="424144002" codeSystem="2.16.840.1.113883.6.96"
        displayName="Age"/>
      <value xsi:type="IVL_PQ">
        <low value="17" unit="a"/>
        <high value="64" unit="a"/>
      </value>
      <sourceOf typeCode="INST">
        <observation classCode="OBS" moodCode="DEF">
          <id root="0" extension="Demographics"/>
        </observation>
      </sourceOf>
    </observation>
    </entry>
    <entry typeCode="COMP">
      <localVariableName>EDorInpatientEncounter</localVariableName>
      <encounter classCode="ENC" moodCode="EVN" isCriterionInd="true">
        <id root="0" extension="EDorInpatientEncounter"/>
          <code valueSet="2.16.840.1.113883.3.464.1.42"/>
          <effectiveTime>
            <high nullFlavor="DER">
              <expression>EndDate.add(new PQ(-2,"a"))</expression>
            </high>
          </effectiveTime>
          <sourceOf typeCode="INST">
            <observation classCode="OBS" moodCode="DEF">
              <id root="0" extension="Encounter"/>
            </observation>
          </sourceOf>
       </encounter>
     </entry>

There are a couple of things to note here:
  1. Note that I'm using the localVariableName here to give a name to the criteria.  This is useful later.
  2. The second criterion is parameterized using the variables defined previously, and the <expression> element which I added to the DataTypes schema.  I cheated here and used a JavaScript syntax for this example, but I will eventually need an expression syntax that is implementation independent.
  3. Finally, note how each criterion references a model definition as I described a couple of days ago.  This is how I enforce expression of criteria to the S&I Framework CIM Model.
Translating this to JavaScript variables was pretty easy.  Eventually, these should be translated to functions that delay evaluation until they are needed and cache results if they are evaluated again, but that is an optimization that can be done later.  Here is the XSLT:

  <xsl:text>// Create variables for each data criteria&#xA;</xsl:text>
  <xsl:for-each 
    select='$DataDeclarationSection//emf:entry/emf:*[@isCriterionInd="true"]'>
    <xsl:text>&#xA;  var </xsl:text>
    <xsl:value-of select="../emf:localVariableName"/>
    <!-- Generate a name if one isn't given -->
    <xsl:if test="not(../emf:localVariableName)">
      <xsl:value-of select="generate-id(.)"/>
    </xsl:if>
    <xsl:text>= patient.</xsl:text>
    <xsl:value-of select="emf:sourceOf[@typeCode='INST']/*/emf:id/@extension"/>
    <xsl:text>()</xsl:text>
    <xsl:call-template name="criteria">
      <xsl:with-param name="act" select="."/>
    </xsl:call-template>
  </xsl:for-each>

The criteria template (not shown), generates the .match() expression based on code, effectiveTime and value elements expressed in the criterion.  So now I wind up with:
// Create variables for each data criteria
  var ageBetween17and64 = patient.Demographics().match(new CD("424144002","2.16.840.1.113883.6.96), new IVL(new PQ("17","a"),new PQ("64","a")), null);
  var EDorInpatientEncounter = patient.Encounter().match(term["2.16.840.1.113883.3.464.1.42"], new IVL(null, EndDate.add(new PQ(-2,"a"))),null);

Defining Measure Functions
The final step is to generate the population, numerator, denominator and exclusion functions defined using these variables.  This is essentially the same transformation applied to each of the different population groups.  This is split into three components, a declaration template, and an expression processing template that walks the precondition tree, and a template that generates the variable name associated with each precondition.  The declaration template appears below.

<xsl:template name="declareFunction">
  <xsl:param name="name"/>
  <xsl:param name="entries"/>
  <xsl:text>&#xA;  var </xsl:text>
  <xsl:value-of select="$name"/>
  <xsl:text> = function(patient) {&#xA;</xsl:text>
  <xsl:text>    return </xsl:text>
  <xsl:call-template name="processEntries">
      <xsl:with-param name="entries" select="$entries"/>
  </xsl:call-template>
  <xsl:text>;&#xA;  }&#xA;</xsl:text>
</xsl:template>

It's pretty simple, relying on the processEntries template to do most of the heavy lifting.  That appears here:

<xsl:template name="processEntries">
  <xsl:param name="entries"/>
  <xsl:if test="$entries[emf:conjunctionCode/@code='AND' or
                not(emf:conjunctionCode)]">
    <xsl:text>allOf(</xsl:text>
    <xsl:apply-templates 
      select="$entries[emf:conjunctionCode/@code='AND' or
              not(emf:conjunctionCode)]"/>
    <xsl:text>true)</xsl:text>
    <xsl:if test="$entries[emf:conjunctionCode/@code != 'AND']">
      <xsl:text> &amp;&amp; </xsl:text>
    </xsl:if>
  </xsl:if>
  <xsl:if test="$entries/emf:conjunctionCode/@code=&quot;OR&quot;">
    <xsl:text>atLeastOne(</xsl:text>
      <xsl:apply-templates select="$entries[emf:conjunctionCode/@code='OR']"/>
      <xsl:text>false) </xsl:text>
      <xsl:if test="$entries[emf:conjunctionCode/@code = 'XOR']">
        <xsl:text> &amp;&amp; </xsl:text>
      </xsl:if>
  </xsl:if>
  <xsl:if test="$entries/emf:conjunctionCode/@code=&quot;XOR&quot;">
    <xsl:text>onlyOne(</xsl:text>
    <xsl:apply-templates select="$entries[emf:conjunctionCode/@code='OR']"/>
    <xsl:text>false)</xsl:text>
  </xsl:if>
</xsl:template>

All it really does is gather the preconditions into the AND/OR/XOR groups, and generates a call to a function that evaluates to true if the group matches the criteria based on the conjunctionCode.  The criteria are gathered below in this template, which is used recursively 

<xsl:template match="emf:sourceOf[@typeCode='PRCN']">
  <!-- 
    Find the act that was declared in Data Criteria
  -->
  <xsl:variable name="act"
    select="emf:act|emf:observation|emf:encounter|emf:procedure|
           emf:substanceAdministration|emf:supply"/>
  <xsl:variable name="declaration"
    select="$DataDeclarationSection/emf:entry/*[
      $act/emf:id/@root = emf:id/@root and 
      $act/emf:id/@extension = emf:id/@extension]"/>
  <!-- 
    If there was a declaration in the data section, then this is
    not a grouper (which should be represented using organizer?). 
  -->
  <xsl:if test="$declaration">
    <xsl:value-of select="$declaration/../emf:localVariableName"/>
    <!-- Generate a name if one isn't given -->
    <xsl:if test="not($declaration/../emf:localVariableName)">
      <xsl:value-of select="generate-id($declaration)"/>
    </xsl:if>
    <!-- deal with negationInd on sourceOf and actionNegationInd on criteria -->
    <xsl:choose>
      <xsl:when test="$act/../@negationInd = 'true' and 
         $act/@actionNegationInd = 'true'">.length != 0, </xsl:when>
      <xsl:when test="$act/@actionNegationInd = 'true'">.length == 0,</xsl:when>
      <xsl:when test="$act/../@negationInd = 'true'">.length == 0, </xsl:when>
      <xsl:otherwise>.length != 0, </xsl:otherwise>
    </xsl:choose>
  </xsl:if>
  <xsl:if test="$act/emf:sourceOf[@typeCode=&quot;PRCN&quot;]">
    <xsl:call-template name="processEntries">
      <xsl:with-param name="entries"
        select="$act/emf:sourceOf[@typeCode='PRCN']"/>
    </xsl:call-template>
    <xsl:text>, </xsl:text>
  </xsl:if>
</xsl:template>

This just dumps out variable names (in the first part), and deals with parenthetical (nested) sub-expressions in the precondition.

The whole solution, including the sample (partially completed) EMF, the conversion XSLT, and the vocabulary XML document can be found here in one ZIP file.  It's working code.  I think my next proof of concept will look at generating query results using collections of CCD documents for a given patient using XPath with the same EMF as a declarative description.

   Keith

P.S.  One thing I have noted in building  my implementation is that the HTML describing the measure, and the measure as defined by NQF appear to be slightly different.  That is not a Query Health issue, but certainly is something that needs to be addressed.

TBD for the JavaScript Implementation:  Declare constructor and object structures for key HL7 datatypes TS, PQ, IVL, and CD.  Declare logical conjunction functions for allOf(), atLeastOne(), onlyOne().

TBD for EMF: Find an implementation independent, easily parsed (XML) language to write expressions in. Write an implementation guide describing the rules I used to generate the EMF.



Monday, November 7, 2011

Three keys to being a Thought Leader

I've never heard my children (or anyone else's for that matter) say "I want to be a thought leader when I grow up".  No, at first they want to be an astronaut, a president or famous, and later, a doctor, a lawyer, a scientist, a veterinarian  a dancer, a musician, but never "a thought leader".  There are no job titles specific to thought leaders. Very rarely, if ever, will you see it as a required or beneficial skill on a job description.  It will never show up as a check-box on an annual review form.  In fact, thought leadership is rarely "a claim" and much more often by "acclaim".

What do thought leaders do?  Obviously, they have interesting thoughts that cause others to think, most often, differently than they have thought before.  Have you ever seen thinking on a job description?  But it isn't even necessary that they be the first to have the thought (Xerox was working on usability long before Apple).  Just that they communicate it in ways that others can understand and think about it, or implement it in ways ways that are successful. Most weren't successful on their first try, or with there first attempt to communicate their thoughts.  They kept trying over and over (Edison is reported to have tried thousands of times before he came up with the right wire for the light bulb.).

I don't know many thought leaders that set out to be "thought leaders".  Some of them got there accidentally.  They weren't always happy accidents, some of them were very sad.  Sometimes they just had a really good idea and kept working at it, sometimes for many years before they were acknowledged.

There are three things common to all of them though:

  1. Think
  2. Communicate
  3. Persevere

Just a thought.

   Keith


Thursday, November 3, 2011

IHE Eye Care Technical Framework Supplement Published for Public Comment

And this appeared in my inbox this morning...


IHE Community,


IHE Eye Care Technical Framework Supplement Published for Public Comment

The IHE Eye Care Technical Committee has published the following supplement to the IHE Eye Care Technical Framework for Public Comment in the period from November 1, 2011 to November 30, 2011:

·       Routine Eye Exam (REE)

The document is available for download at http://www.ihe.net/Technical_Framework/public_comment.cfm.  Comments submitted by November 30, 2011 will be considered by the Eye Care Technical Committee in developing the trial implementation version of the supplement.  Comments should be submitted at http://www.ihe.net/eyecare/eyecarecomments.cfm 

HIE Leader Roundtable on Governance and Building Stakeholder Trust

This showed up in my inbox yesterday...

The Office of the National Coordinator for Health Information Technology (ONC) is a cooperative agreement partner of the National eHealth Collaborative (NeHC).
On Thursday, November 3, 2011 at 1 p.m. ET, NeHC University will continue its Health Information Exchange (HIE) Leader Roundtable series with a session on governance and building stakeholder trust. NeHC will welcome three of its HIE Leaders as well as representatives from the ONC Office of Policy and Planning and the State HIE program to discuss the challenges and strategies around building successful governance models and fostering effective and ongoing stakeholder collaboration.
We invite you to join this NeHC University HIE Leader Roundtable. Register today!
Questions? Email NeHC University at university@nationalehealth.org.




When the XML Sucks

One of the complaints about HL7 is that their XML sucks.  One of the complaints about ebXML RIM specifications is that their XML sucks.  One of the points of GreenCDA is to get to less suckful XML in the content.  There are a lot of other XML schemas that "suck".

The evaluation of "Sucks" is a subjective measure, and is often an assessment made through the eyes of developers who have not yet drunk a particular flavor of kool-aid, be it HL7 RIM, ebXML RIM, whatever.  For most problems, the requirements imposed by model oriented XML schemas are complex enough that in fact, the XML does suck for the average developer.  Few real world problems are truly simple.  Simple XML can probably solve 80% of the problem, but then we get into the dicey parts, and well, that's where solutions (and XML) gets complicated.

Hey, even HTML sucks, but we've learned to live with it.

A long time ago, in a place far-far-away, I used to write code in a programming language that almost but not-quite sucked.  But it had one very important feature, and that was a pre-processor that could write amazingly complex expressions in simple, easy to use terms.  It occurs to me that we could do the same with XML today, we just need to define the preprocessing mechanism.  Let's say that I wanted to take this:


<component>
  <section>
    <code code="57026-7" codeSystem="2.16.840.1.113883.6.1"/>
    <title>Population Criteria Section</title>
    <text>
      This section describes the Initial Patient Population, 
      Numerator, Denominator, Denominator Exceptions, and
      Measure Populations</text>
    <entry>
      <observation classCode='OBS' moodCode='EVN.CRT'>
        <id root="0"/>
        <code code="ASSERTION"
          codeSystem="2.16.840.1.113883.5.4"/>

        <value xsi:type="CD" code="IPP"
          codeSystem="2.16.840.1.113883.5.1063"/>

        <sourceOf typeCode="PRCN">
          <observation classCode="OBS" moodCode="EVN.CRT">
            ...
          </observation>
        </sourceOf>
      </observation>
    </entry>
  </section>
</component>

And turn it into this:


<PopulationCritieriaSection>
  <title>Population Criteria Section</title>
  <text>
    This section describes the Initial Patient Population, 
    Numerator, Denominator, Denominator Exceptions, and
    Measure Populations</text>
  <InitialPatientPopulation>
    <id root="0"/>
    <precondition>
      <observationCriteria>
       ...
      </observationCriteria>
    </precondition>
  </InitialPatientPopulation>
</PopulationCriteriaSection>

This is a pretty simple transformation of hard-to-read XML into something that is pretty readable.  How could I do this in a way that was "standards-compliant".  The keyword here is transformation.  I need some way to tell the receiver that it needs to transform the XML before using it.  Hey, there's even a standard for that!

What if, at the top of the document, I put in this little piece of XML:

<?xml:stylesheet href='http://myxmlisbetter.com/yxs.xsl' ?>

And then, if I included in that stylesheet, the following:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  version="1.0">
  <xsl:template match="/">
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match='PopulationCritieriaSection'>
    <component>
      <section>
        <code code="57026-7" 
          codeSystem="2.16.840.1.113883.6.1"/>
        <xsl:apply-templates/>
      </section>
    </component>
  </xsl:template>
  <xsl:template match='InitialPatientPopulation'>
    <observation classCode='OBS' moodCode='EVN.CRT'>
      <xsl:copy-of select='id'/>
        <code code="ASSERTION"
          codeSystem="2.16.840.1.113883.5.4"/>
        <value xsi:type="CD" code="IPP"
          codeSystem="2.16.840.1.113883.5.1063"/>
        <xsl:apply-templates/>
    </observation>
  </xsl:template>
  <xsl:template match='precondition'>
    <sourceOf typeCode='PRCN'>
      <xsl:apply-templates/>
    </sourceOf>
  </xsl:template>
  <xsl:template match='observationCriteria'>
    <observation classCode="OBS" moodCode="EVN.CRT">
      <xsl:apply-templates/>
    </observation>
  </xsl:template>
  <xsl:template match="*">
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>


If your application didn't understand my better XML, but did understand the geeky original, it could simply regenerate the original XML by applying the transformation (and it does work, I tested it).  BUT, if it did understand the "Better" XML, it could just process that directly.  OK, so this is pretty much the way that "GreenCDA" works today, except that it doesn't put an XML stylesheet processing instruction right up front in the "green" document.

To go a little bit further, let's say I wanted to reuse some content over and over again.  In C (or later derivatives), I would say
#include "stdio.h"

In XML, I can say (again using a standard):
<xi:include href='mystuff.xml'/>


So, if you have to deal with XML that sucks, you can fix it up using this technique (at least before you have to release for production).  There are a bunch of challenges in using this technique that I haven't addressed.

  1. Governance:  Who gets to define transformations that are allowed to be used in an exchange.
  2. Retrieval and security issues: The transformation resources becomes referenced as part of the communication, but it's specification as a URL seems to imply that separate retrieval is required (this isn't necessarily the case, but I digress).  The real issue is that if you've gone to the trouble to set up a secure transport, and then throw this into the mix, it messes with things. BTW: There is a way to include the XSLT inline in the original XML using HL7 V3 extensions and XML Fragment identifiers. 
  3. Overkill:  For most purposes, XSLT is probably a little bit too powerful.  It certainly does the job, but there is probably  a better way to represent the mapping for 80% of the cases. (see above on solutions getting tricky after that first 80%).
Anyway, this was an interesting digression, and gives me an excuse to use simpler XML as I start working through Query Health examples.  If I do it right, I can always transform them back to compliant implementations, and who knows, maybe the improvements I come up with could be used to improve the standard.  After all, HL7 is revamping HQMF, which is great timing for Query Health.

Wednesday, November 2, 2011

Putting together pieces for QueryHealth

In Query Health, mostly, we are counting things, summing them, averaging them, computing a standard deviation, correlating them, et cetera.  These are aggregate statistical functions.  After we’ve counted things, we can do other sorts of statistical measurements on them.
What are the things we counting:
  • Patients
  • Orders for Interventions or Diagnostic Tests
  • Encounters/Visits
  • Findings
    • History
    • Diagnoses/Problems/Symptoms
    • Measurements (Lab Results, Vital Signs)
  • Interventions
    • Medications
    • Immunizations
    • Procedures

The S&I Framework CIM provides one mechanism to access to these various components, but doesn’t specify a query language, and doesn’t have a formalism (yet) that defines it, or allows EHR system access based on it.  The HL7 HQMF provides a mechanism to query a model, but its base model is the HL7 RIM.  That model is very broad, and means that there are a number of different possible ways to reference different components.  A restricted and simpler model would be much better, especially in the context of Query Health.  If there were some way to put the two of these components together, it could make real world implementations much easier, and would allow QH to take advantage existing work.

The CIM can be represented by a set of HL7 acts, associations, and entities from the RIM, described in definition mood and identified with CIM model component names.  Such a representation could be used for Query Health based queries.  For completeness, we could create acts in definition mood that define the components of the S&I Framework CIM.

One of the things that I like about the HQMF specification is the Data Criteria section.  But I don’t like the way the data criteria is referenced (by ID) in the document.  That’s because the ID is computer friendly, not human friendly.  One way to make that a little better is to use OID+extension, and have one OID that references a particular model, and an extension that references a component in that model.  That way, you’d only have to remember one OID, and could use the model names to access different parts.
Then, to create a data criterion (and presuming the model was available at a URL), we would do the following:

<xi:include href='http://wiki.siframework.org/file/view/QHM.xml'
  xmlns:xi='http://www.w3.org/2001/XInclude' />
<act… moodCode='EVN.CRT'>
  <id root='criteria-oid'
    extension='criteria-descriptive-name'/>
   … query details …
  <sourceOf typeCode='INST'>
    <id root='…' extension='name-of-component-in-CIM'/>

  </sourceOf>
</act>

You can see where I put in an <xi:include> element that referenced a QH/CIM model that contained the appropriate model definitions in XML (See the W3C XInclude standard). The point of that is that each HQMF document doesn’t need to duplicate that effort, but can simply reference it.  It becomes a “standard model” for QH queries, and systems which understand that model can readily understand the data criteria and transform it into a way to access the information.


That last item (and we should require it to be either first or last in the list of act relationships just to make it easy to find) would indicate which CIM model element was being constrained.
Note: If you have an existing HQMF Measure definition, provided you’ve followed some best practices, “fixing it up” to work with Query Health can be done in a couple of easy steps.  At the top of the Data Criteria section, add that <xml:include> element.  Within each element in Data Criteria, add the reference to the model instance that is being queried (that last <sourceOf> element).  So long as you’ve followed the best practices in creating a measure definition this shouldn’t require a great deal of work.  That is of course, true only when we’ve figured out what those best practices are.
What’s next is defining what is to be measured.  HQMF specifies how to define the Initial Patient Population, the Numerator, the Denominator, Denominator Exclusions, and a Measure Population.  If you are computing a normal quality measure, you would define the Initial Patient Population, the Numerator and Denominator, and possibly Denominator Exclusions.  What would typically be reported for the measure would be the Numerator and the Denominator counts, but you could also include the Exclusions and Initial Patient Population counts.

If you are defining some other sort of measurement, you’d define the Initial Patient Population and the Measure Population.  What would typically be reported would be the Measure Population, but again, you could also include the Initial Patient Population count.

HQMF doesn't actually say how the measure is reported.  We'd have to establish some best practices around this.  I think it's pretty clear that Numerator, Denominator and Measure Populations need to be reported.  I would also assume that if you define a Denominator Exception, that those values would also be reported.  The real question is whether the Initial Patient Population needs to be reported or not.  Given that anything specified in all the other defined sections is reported, I'd just go with saying that the Initial Patient Population must also be reported in the output.

The next step in the process is to take the HQMF query and turn it into something that a system could execute.  This involves a few steps, and is something I’m still working out…

What I already know is that mapping data criteria and measures back to the EHR data model will be needed.  If you know how to map your EHR data model to the S&I Framework CIM, this can be accomplished.  Since that CIM is built on CCD and similar encounter documentation models, and certified EHRs can already map their data models to the CCD, I’m pretty sure this is feasible.  Another important step that is mapping from EHR used vocabulary to the CIM and/or Measure specific vocabulary.  Ideally, we’d like to avoid things like multiple vocabularies for the same data elements (e.g., ICD-9, ICD-10 and SNOMED CT for problems), but that’s not something Query Health can control (although it could influence things).

Tuesday, November 1, 2011

Value Sets and QueryHealth

One of the challenges in Query Health is that a particular measure may reference a number of coded values.  These are known in HL7 parlance as "Value Sets".  For example, the TCNY Quality Measures (pdf) mentioned on the Query Health "Query and Data Analysis" page references a number of codes (which they call data elements).

Here is an example from it:

Eligible
encounters
(CPT codes)
99201; 99202; 99203; 99204; 99205; 99211; 99212; 99213; 99214; 99215; 99241; 99242;
99243; 99244; 99245; 99354; 99355; 99385; 99386; 99387; 99395; 99396; 99397; 99401;
99402; 99403; 99404; 99406; 99407

OK, so we have this list of codes that we need to access.  There are a couple of different ways to encode this value set in the query.  

In HQMF this could be written as separate criteria on encounters composed with "OR" between them.

<sourceOf typeCode="PRCN">
  <conjunctionCode code="OR"/>
  <encounter>
    ...
    <code code='99201' codeSystem='2.16.840.1.113883.6.12'/>
  </encounter>
</sourceOf>
<sourceOf typeCode="PRCN">

  <conjunctionCode code="OR"/>
  <encounter>
    ...
    <code code='99202' codeSystem='2.16.840.1.113883.6.12'/>
  </encounter>
</sourceOf>

That's not terribly pretty, or reusable (except by copy and paste).  HQMF does talk about how to specify a value set, but it is knarly.  Essentially you use a code from some code system that identifies the value set (even though every other HL7 V3 message knows that value sets are identified through OIDs).  I won't even show that, if you want an example, look at HQMF.

The next way to approach it is by using the valueSet attribute in the code.  The reason that HQMF doesn't suggest this is because someone in M&M thinks that is a bad idea.  We'll be revisiting this in the next revision of HQMF in a project that is currently being scoped by HL7 Structured Documents.  Here is how I would propose representing the value set.

<sourceOf typeCode="PRCN">
  <encounter>
    ...
    <code valueSet='2.16.840.1.113883.19.1091'/>
  </encounter>
</sourceOf>

The problem with this approach is that you've only referenced the value set, and the system that needs to process the query must then figure out how to dereference it.

There are at least two different ways to handle that.  One is to use a terminology service as defined by the HL7 Common Terminology Service specification.  For what Query Health needs, this is like swatting a fly with a sledgehammer.  The IHE Sharing Value Sets (SVS) profile is a more appropriate tool for swatting this fly.  This profile supports both SOAP and HTTP bindings (there are great uses for the HTTP binding in Schematron).


This is an example URL to retrieve a value set:
https://example.com/RetrieveValueSet?id=1.2.840.10008.6.1.308

And here is a sample of what it would return:

<RetrieveValueSetResponse xmlns="urn:ihe:iti:svs:2008" cacheExpirationHint="2008-08-15T00:00:00-05:00">
  <ValueSet id="1.2.840.10008.6.1.308" 
    displayName="Common Anatomic Regions Context ID 4031" version="20061023">
    <ConceptList xml:lang="en-US">
      <Concept code="T-D4000" displayName="Abdomen"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="R-FAB57" displayName="Abdomen and Pelvis"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-15420" displayName="Acromioclavicular joint"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-15750" displayName="Ankle joint"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-280A0" displayName="Apex of Lung"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-D8200" displayName="Arm"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-60610" displayName="Bile Duct"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-74000" displayName="Bladder"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-04000" displayName="Breast"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-26000" displayName="Bronchus"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-12770" displayName="Calcaneus"
        codeSystem="2.16.840.1.113883.6.5"/>
      <Concept code="T-11501" displayName="Cervical spine"
        codeSystem="2.16.840.1.113883.6.5"/>
      </ConceptList>
  </ValueSet>
</RetrieveValueSetResponse>


Now, there are some really nice features of using SVS for value sets.  If you want to put this value set into a SQL table, you can simply transform the result using XSLT:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    xmlns:svs="urn:ihe:iti:svs:2008"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text"/>
    <xsl:template match="/">
        <xsl:apply-templates select=".//svs:ConceptList"/>
    </xsl:template>
    <xsl:template match="svs:ConceptList">
        <xsl:text>INSERT INTO VALUESETMEMBERS (VALUESETID, CODE, CODESYSTEMID, DISPLAYNAME)&#x0A;</xsl:text>
        <xsl:apply-templates select="svs:Concept"></xsl:apply-templates>
    </xsl:template>
    <xsl:template match="svs:Concept">
        <xsl:text>VALUES ('</xsl:text>
        <xsl:value-of select="../../@id"/>
        <xsl:text>', '</xsl:text>
        <xsl:value-of select="@code"/>
        <xsl:text>', '</xsl:text>
        <xsl:value-of select="@codeSystem"/>
        <xsl:text>', '</xsl:text>
        <xsl:value-of select="@displayName"/>
        <xsl:text>')&#x0A;</xsl:text>
    </xsl:template>
</xsl:stylesheet>


Another easy to write stylesheet would create JSON objects containing the members of the value set (I'll leave that as an exercise for the reader.

You can also access the value-set using the XPath document function:

document("https://example.com/RetrieveValueSet?id=1.2.840.10008.6.1.308")

The value of this expression is an XML Document from which you can do other selections.  It's a pretty handy trick for validation testing (which is why ITI put it in).  If you assume that $myValueSet is an XPath variable containing that document, and $code is another variable containing a code value, then $myValueSet//svs:Concept[@code=$code] is true if the value in code matches one of the codes in the document describing the value set [Note: I omitted but would in a production environment also test for the appropriate codeSystem match].

So, for dealing with value sets in Query Health, if we wind up with an XML representation of the query, I would suggest that they use the IHE SVS mechanism for referencing the value sets. Value sets are reusable resources.  There are a lot of different quality measures out there that would benefit from using similar definitions for ED visit, patient with a diagnosis of diabetes, HbA1c result, et cetera.

My first recommendation is to consider using the IHE SVS schema to represent a value set, and the HTTP binding to retrieve them for use in the query.  The reason for doing so is that it supports any of the previously mentioned mechanisms for representing the query, independently of the technology or standards that the query uses.  If queries are expressed in JavaScript, the SVS profile can generate objects that can be used to access the content in JSON format, if in SQL, the same, if in XPath, the same, and if in XML using HQMF, they use the same HL7 V3 data types to represent the content.  That sounds like a win/win/win/win solution.

     Keith