Monday, December 5, 2011

Visualizing the Criteria in an HQMF Document

One of my take-aways at the recent Query Health Face to Face was to show how well or poorly the transformations I had developed would work against HQMF documents generated by the Model Authoring Tool (MAT).  I started my analysis and got stuck because I couldn't really see the logic flow of the criteria generated by MAT. I decided I would build a little visualization tool.  Previously, I had generated a visualization of IHE Profile and Actor relationships using GrafViz and and XSLT.  I figured that I could use the same techniques here.

This is an interesting side project because it could also be used to visualize relationship in other HL7 Version 3 and CDA Release 2.0 or Release 3.0 instances.

The first template matches the root of the HQMF document and creates a directed graph named "G".  Then it delves into the Population Criteria Section (section[code/@code='57026-7'] and describes the content of it.  I could use the same thing over other sections to delve into those by simply removing the predicate or changing it's contents to focus on a different section.


  <xsl:output method="text" indent="no"/>
  <xsl:template match="/">
    <xsl:text>digraph G {&#xA;</xsl:text>
    <xsl:apply-templates select="//hqmf:section[hqmf:code/@code='57026-7']"/>
    <xsl:text>}&#xA;</xsl:text>
  </xsl:template>

Since I'm just generating a grafviz input file, I set the output method to text at the top of the stylesheet.  The next template generates the root node representing the section:


  <xsl:template match="hqmf:section">
    <xsl:call-template name="getNodeName"/>
    <xsl:text> [ label="</xsl:text>
    <xsl:value-of select="hqmf:code/@displayName"/>
    <xsl:text>\n</xsl:text>
    <xsl:value-of select="hqmf:title"/>
    <xsl:text>" ];&#xA;</xsl:text>
    <xsl:apply-templates select="hqmf:entry"/>
  </xsl:template>

It calls upon a template to get the name of the section from it's content.  Then it assigns a label to the node based on the displayName in the code, and the title of the section.

The getNodeName template is reused in several places to generate an internal name for a node.  This was done because HQMF uses references to acts based on the <id> element, and I wanted only one node in the graph for each of the items referencing the same node.

  <xsl:template name="getNodeName">
    <xsl:choose>
      <xsl:when test="hqmf:id">
        <xsl:text>"</xsl:text>
        <xsl:value-of select="translate(hqmf:id/@root,'-','_')"/>
        <xsl:if test="hqmf:id/@extension">
          <xsl:text>:</xsl:text>
          <xsl:value-of select="hqmf:id/@extension"/>
        </xsl:if>
        <xsl:text>"</xsl:text>
      </xsl:when>
      <xsl:otherwise><xsl:value-of select="generate-id()"/></xsl:otherwise>
    </xsl:choose>
  </xsl:template>

So, if there is an <id> in a class, it is used as the internal name for it, otherwise, I just generate an identifier from it using the generate-id() XPath function.


Entries and their relationships to other acts are handled using the same template, since an entry is just another  act relationship from a section to some other act.


  <xsl:template match="hqmf:entry|hqmf:sourceOf">
    <xsl:variable name="act" 
      select="hqmf:act|hqmf:observation|hqmf:procedure|hqmf:encounter|
              hqmf:substanceAdministration|hqmf:supply"/>
    <xsl:variable name="target">
      <xsl:for-each select="$act">
        <xsl:call-template name="getNodeName"/>
      </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="source">
      <xsl:for-each select="..">
        <xsl:call-template name="getNodeName"/>
      </xsl:for-each>
    </xsl:variable>
    <xsl:apply-templates select="$act"/>
    <xsl:text>  </xsl:text>
    <xsl:choose>
      <xsl:when test="@inversionInd='true'">
        <xsl:value-of select="$target"/>
        <xsl:text> -&gt; </xsl:text>
        <xsl:value-of select="$source"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$source"/>
        <xsl:text> -&gt; </xsl:text>
        <xsl:value-of select="$target"/>
      </xsl:otherwise>
    </xsl:choose>
    <xsl:text> [ label=</xsl:text>
      <xsl:text>"</xsl:text>
      <xsl:if test='hqmf:conjunctionCode'>
        <xsl:value-of select="hqmf:conjunctionCode/@code"/>
        <xsl:text> </xsl:text>
      </xsl:if>
      <xsl:value-of select="@typeCode"/> 
      <xsl:if test="hqmf:subsetCode">
        <xsl:text> (</xsl:text>
        <xsl:value-of select="hqmf:subsetCode/@code"/>
        <xsl:text>)</xsl:text>
      </xsl:if>
      <xsl:text>"</xsl:text>
    <xsl:text> ]&#xA;</xsl:text>
  </xsl:template>

This template finds the act that is the target of the relationship.  Then it gets the name of the target and source acts (the source is the parent of the relationship element).  Then it outputs the necessary definition for the child node by applying the template for acts (see the line in bold above).  Finally it generates either:

 source-act-name -> target-act-name
OR
 target-act-name -> source-act-name

depending on whether the @inversionInd attribute is true or not.  Then it generates a label describing the relationship.  A more evolved version of the stylesheet would generate more human readable labels, but this was sufficient for starters.

The next-to-last step was to process each act, and recurse over the act relationships.


  <xsl:template match="hqmf:act|hqmf:observation|hqmf:procedure|hqmf:encounter|
    hqmf:substanceAdministration|hqmf:supply">
    <xsl:text>  </xsl:text>
    <xsl:call-template name="getNodeName"/>
    <xsl:text> [ label="</xsl:text>
    <xsl:value-of select="local-name()"/>
    <xsl:if test="hqmf:code">
      <xsl:text>\n</xsl:text><xsl:value-of select="hqmf:code/@displayName"/>
    </xsl:if>
    <xsl:if test="hqmf:title">
      <xsl:text>\n</xsl:text>
      <xsl:value-of select="hqmf:title"/>
    </xsl:if>
    <xsl:text>", tooltip="</xsl:text>
    <xsl:apply-templates 
      select="hqmf:*[not(self::hqmf:sourceOf)]" mode='tooltip'/>
    <xsl:text>"]; &#xA;</xsl:text>  
    <xsl:apply-templates select="hqmf:sourceOf"/>  
  </xsl:template>

This step generates a node for the act, naming it from the element name and code and title (when present).  Then it applies the previously described template to any child relationships in the last line.

Finally, I needed to generate a description of the criteria.  For now, I just copied the XML inside the act into a tooltip using the line in bold above and a pair of other templates.  These templates are run in a different mode so they don't overlap with other processing.  All they do for now is copy the XML into a tooltip.  The first one processes elements and generates an appropriate opening tag.  It calls the second (see the line in bold) to generate the attributes.


<xsl:template match="*" mode="tooltip">
    <xsl:param name="indent" select="0"/>
    <xsl:variable name="newline">&amp;#xA;</xsl:variable>
    <xsl:value-of select="substring('          ',1,$indent)"/>
    <xsl:text>&lt;</xsl:text>
    <xsl:value-of select="local-name()"/>
    <xsl:apply-templates select="@*" mode="tooltip"/>
    <xsl:if test="count(*)=0 or normalize-space(text()) = ''">/</xsl:if>
    <xsl:text>&gt;</xsl:text>
    <xsl:value-of select="$newline"/>
    <xsl:if test="normalize-space(text())!=''">
      <xsl:value-of select="normalize-space(text())"/>
    </xsl:if>
    <xsl:if test="count(*)!=0">
      <xsl:apply-templates select="*" mode="tooltip">
        <xsl:with-param name="indent" select="$indent+1"/>
      </xsl:apply-templates>
      <xsl:value-of select="substring('          ',1,$indent)"/>
    </xsl:if>
    <xsl:if test="count(*)&gt;0 or normalize-space(text()) != ''">
      <xsl:text>&lt;/</xsl:text>
      <xsl:value-of select="local-name()"/>
      <xsl:text>&gt;</xsl:text>
      <xsl:value-of select="$newline"/>
    </xsl:if>
  </xsl:template>
  
  <xsl:template match="@*" mode="tooltip">
    <xsl:text> </xsl:text>
    <xsl:value-of select="local-name()"/>
    <xsl:text>='</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>'</xsl:text>
  </xsl:template>



Then I can run grafviz over the output to generate an SVG which I can display in my browser.  Unfortunately, I cannot upload the SVG to Blogger and I have to fix a few other things before I can upload it elsewhere, so here is a rather large scrollable PNG version.  So, now I can visualize the criteria, the next question I have is what to do about it.



For comparison, here is the same measure in the way that I implemented it (also as a PNG, but much smaller and about one quarter as complicated).

What I need to do now is figure out how much of the complexity in the first image is unnecessary.

0 comments:

Post a Comment