Improve HealthVault query efficiency with final transforms…

A while back I wrote a post about using the built-in HealthVault transforms to get a different view on your data. That post discusses the mtt, stt, and form transforms.

There’s another transform option in HealthVault that gives you the option to transform the response that comes back. It is best explained through a scenario. Most of this discussions is going to be at the raw xml request/response level. Some

Getting the instance ids of all Weight items in a record

If you want to do a full synchronization of weight values in a HealthVault record (and please read the guidance here before you decide to do so), you will need to get the instance ids of all the items. This simplest way to do this is just to ask for all the weight items, which will result in the following response:

<response>
  <status>
    <code>0</code>
  </status>
  <wc:info xmlns:wc="urn:com.microsoft.wc.methods.response.GetThings3">
    <group>
        <thing>
        <thing-id version-stamp="8853d460-3c79-4284-aa12-08cb9730d151">645cae6f-942d-40bb-bc36-5529b6f9f46a</thing-id>
        <type-id name="Weight Measurement">3d34d87e-7fc1-4153-800f-f56592cb0d17</type-id>
        <thing-state>Active</thing-state>
        <flags>0</flags>
        <eff-date>2011-10-26T10:14:01.17</eff-date>
        <data-xml>
          <weight>
            <when>
              <date>
                <y>2011</y>
                <m>10</m>
                <d>26</d>
              </date>
              <time>
                <h>10</h>
                <m>14</m>
                <s>1</s>
                <f>170</f>
              </time>
            </when>
            <value>
              <kg>79.4010889292196</kg>
              <display units="lbs">175</display>
            </value>
          </weight>
        </data-xml>
      </thing>
    </group>
  </wc:info>
</response>

Where the <thing/> element is repeated for each weight item.

That’s a lot of data (almost 2K) just to get the single instance guid. The first thing we can do is change our request so that the response only contains the key information. This is done by telling the platform not to return you any full items, but just return them as partial items. You do that by setting the max-full attribute to “0”. If you are working with the SDK, you can set the MaxFullItemsReturnedPerRequest property and use GetMatchingItemsRaw().

If you do this, you will get a response that looks like this:

<response>
  <status>
    <code>0</code>
  </status>
  <wc:info xmlns:wc="urn:com.microsoft.wc.methods.response.GetThings3">
    <group>
      <unprocessed-thing-key-info>
        <thing-id version-stamp="8853d460-3c79-4284-aa12-08cb9730d151">645cae6f-942d-40bb-bc36-5529b6f9f46a</thing-id>
        <type-id name="Weight Measurement">3d34d87e-7fc1-4153-800f-f56592cb0d17</type-id>
        <eff-date>2011-10-26T10:14:01.17</eff-date>
      </unprocessed-thing-key-info>
    </group>
  </wc:info>
</response>

That takes us down to about 500 bytes, which is much smaller. But there’s still a lot of extraneous information there. What we really need is a way to get back only the information that we care about. That something is a HealthVault final xsl.

A final-xsl is an application-supplied transform that is run on the response right before it is sent back to the client. In the .NET sdk, the GetTransformedItems method is used. In the pure xml world, it is done by adding a <final-xsl> element to the request after the <country> element and before the <msg-time> element. The xsl is represented as a string in the request, so it must be appropriately escaped in the request xml.

Here’s a transform I wrote to process the unprocessed items response:

<xsl:stylesheet version="2.0" xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:fo="https://www.w3.org/1999/XSL/Format" xmlns:xs="https://www.w3.org/2001/XMLSchema" xmlns:fn="https://www.w3.org/2005/xpath-functions" xmlns:wc="urn:com.microsoft.wc.methods.response.GetThings3" exclude-result-prefixes="fn fo wc xs">
  <xsl:output omit-xml-declaration="yes"/>
  <xsl:template match="/response">
    <response>
      <xsl:copy-of select="status"/>
      <xsl:copy-of select="error"/>
      <wc:info>
        <xsl:for-each select="wc:info/group">
          <group>
            <xsl:for-each select="unprocessed-thing-key-info">
              <id>
                <xsl:value-of select="thing-id"/>
              </id>
            </xsl:for-each>
          </group>
        </xsl:for-each>
      </wc:info>
    </response>
  </xsl:template>
</xsl:stylesheet>

Which results in the following output:

<response>
  <status>
    <code>0</code>
  </status>
  <wc:info xmlns:wc="urn:com.microsoft.wc.methods.response.GetThings3">
    <group>
      <id>645cae6f-942d-40bb-bc36-5529b6f9f46a</id>
    </group>
  </wc:info>
</response>

That takes us down to about 250 bytes, but still preserves the basic form of the response; the mobile libraries will still be able to parse it successfully. If the application is willing to do more parsing, we can simply further to:

<xsl:stylesheet version="2.0" xmlns:xsl="https://www.w3.org/1999/XSL/Transform" xmlns:fo="https://www.w3.org/1999/XSL/Format" xmlns:xs="https://www.w3.org/2001/XMLSchema" xmlns:fn="https://www.w3.org/2005/xpath-functions" xmlns:wc="urn:com.microsoft.wc.methods.response.GetThings3" exclude-result-prefixes="fn fo wc xs">
  <xsl:output omit-xml-declaration="yes"/>
  <xsl:template match="/response">
        <xsl:for-each select="wc:info/group">
            <xsl:for-each select="unprocessed-thing-key-info">
              <id>
                <xsl:value-of select="thing-id"/>
              </id>
            </xsl:for-each>
        </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

which yields:

<group>
  <id>645cae6f-942d-40bb-bc36-5529b6f9f46a</id>
</group>

which takes us to 66 easy-to-parse bytes.

Final XSL parameters

The following set of parameters are passed to all final-xsl transforms:

  • currentDateTimeUtc - the date and time just before the transform started executing
  • requestingApplicationName - the name of the application that made the request to HealthVault.
  • countryCode - the ISO 3166 country code from the request.
  • languageCode - the ISO 639-1 language code from the request.
  • personName - the name of the person making the request.
  • recordName - if the request identified a HealthVault record to be used, this parameter contains the name of that record.