Using ART DECOR for Code Generation

ART DECOR is a framework for capturing medical information requirements, mostly for the exchange of data in healthcare. In a short series of articles we will explore the benefits ART DECOR offers for implementers. (See the ART DECOR Factsheet for a short general introduction, or art-decor.org for detailed documentation on ART DECOR.) ART DECOR captures the data needs of healthcare professionals and patients in datasets and scenarios. ART DECOR does not only help healthcare professionals and IT architects to model medical data, it also offers a wealth of goodies for implementers, some easier to find than others.

Take a look at one of our demos, Measurements by Patient. This page, the ProjectIndex gives access to most resources available for developers. Look for instance at the Vital Sign Result valueset from the Vital Signs Demo. It’s also available in XML and CSV, for easier importing into an application. But let’s look further, to the main transaction from the first demo, the Measurement Message. Most of the data needed for an application is in there: field names, data types, code lists, cardinality, help text (description). This data is also available as XML, and this is an ideal hook for code generation. This XML version of RetrieveTransaction contains even more in the <implementation> element, which contains an XML- or SQL-friendly name (shortName), as well as the HL7 datatype. RetrieveTransaction gathers all data from the dataset, transaction and valueSets, and combines those into a single view.

RT

There’s also detailed documentation for RetrieveTransaction. RetrieveTransaction thus contains almost everything that’s needed for code generation. In the example below, I’ve made a basic HTML code generator. This is just an example, if you wish to use is for real code generation, you will need to adapt it – only some datatypes are supported, the submit action doesn’t actually do anything etc. Later we’ll look at a more full-fledged code generator.

ToHtml

What is done here is just a simple XSL conversion from the RetrieveTransaction output to a HTML page. The XML tab contains the RetrieveTransaction output, the XSL tab the stylesheet which is the actual code generator, and the HTML tab the generated code. The Result tab shows the rendered HTML (it is a .png, since the HTML doesn’t render well in WordPress – here is the HTML Result page as HTML).

Result

Result

HTML

<html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>Measurement message</title>
   </head>
   <body>
      <h1>Measurement message</h1>
      <form name="editor" action="save" method="post">
         <table style="border-style: solid; border-width: 1px;">
            <tbody>
               <tr>
                  <td>
                     <table name="measurement" style="border-style: solid; border-width: 1px;">
                        <tbody>
                           <tr>
                              <h2>Measurement</h2>
                           </tr>
                           <tr>
                              <td>Weight: </td>
                              <td><input name="weight" type="number" step="0.01">kg</td>
                           </tr>
                           <tr>
                              <td>Weight gain: </td>
                              <td><input name="weight_gain" type="checkbox"></td>
                           </tr>
                           <tr>
                              <td>
                                 <table name="weight_gain_data" style="border-style: solid; border-width: 1px;">
                                    <tbody>
                                       <tr>
                                          <h2>Weight gain data</h2>
                                       </tr>
                                       <tr>
                                          <td>Size weight gain: </td>
                                          <td><input name="size_weight_gain" type="number" step="0.01">gram</td>
                                       </tr>
                                       <tr>
                                          <td>Cause weight gain: </td>
                                          <td><input name="cause_weight_gain" type="text"></td>
                                       </tr>
                                    </tbody>
                                 </table>
                              </td>
                           </tr>
                           <tr>
                              <td>Length: </td>
                              <td><input name="length" type="number" step="0.01">m</td>
                           </tr>
                           <tr>
                              <td>Datetime: </td>
                              <td><input name="datetime" type="datetime-local"></td>
                           </tr>
                           <tr>
                              <td>Measured by: </td>
                              <td><select>
                                    <option value="1">Patient</option>
                                    <option value="2">Home care provider</option>
                                    <option value="3">GP</option>
                                    <option value="4">Personal network</option></select></td>
                           </tr>
                           <tr>
                              <td>Comments: </td>
                              <td><input name="comments" type="text"></td>
                           </tr>
                        </tbody>
                     </table>
                  </td>
               </tr>
               <tr>
                  <td>
                     <table name="person" style="border-style: solid; border-width: 1px;">
                        <tbody>
                           <tr>
                              <h2>Person</h2>
                           </tr>
                           <tr>
                              <td>Name: </td>
                              <td><input name="name" type="text"></td>
                           </tr>
                           <tr>
                              <td>BSN: </td>
                              <td><input name="bsn" type="text"></td>
                           </tr>
                           <tr>
                              <td>Date of birth: </td>
                              <td><input name="date_of_birth" type="text"></td>
                           </tr>
                           <tr>
                              <td>Number of children: </td>
                              <td><input name="number_of_children" type="text"></td>
                           </tr>
                        </tbody>
                     </table>
                  </td>
               </tr>
            </tbody>
         </table><input type="submit" value="Save"></form>
   </body>
</html>

XSL

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="dataset">
        <html>
            <head>
                <title>
                    <xsl:value-of select="name"/>
                </title>
            </head>
            <body>
                <h1>
                    <xsl:value-of select="name"/>
                </h1>
                <form name="editor" action="save" method="post">
                    <table style="border-style: solid; border-width: 1px;">
                        <tbody>
                            <xsl:apply-templates/>
                        </tbody>
                    </table>
                    <input type="submit" value="Save"/>
                </form>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="concept[@type='group']">
        <tr>
            <td>
                <table name="{implementation/@shortName}" style="border-style: solid; border-width: 1px;">
                    <tbody>
                        <tr>
                            <h2>
                                <xsl:value-of select="name"/>
                            </h2>
                        </tr>
                        <xsl:apply-templates/>
                    </tbody>
                </table>
            </td>
        </tr>
    </xsl:template>

    <xsl:template match="concept[@type='item']">
        <tr>
            <td><xsl:value-of select="name"/>: </td>
            <td>
                <xsl:if test="valueDomain/@type!='code'">
                    <input name="{implementation/@shortName}">
                    <xsl:choose>
                        <xsl:when test="valueDomain/@type='boolean'"><xsl:attribute name="type">checkbox</xsl:attribute></xsl:when>
                        <xsl:when test="valueDomain/@type='decimal'"><xsl:attribute name="type">number</xsl:attribute></xsl:when>
                        <xsl:when test="valueDomain/@type='datetime'"><xsl:attribute name="type">datetime-local</xsl:attribute></xsl:when>
                        <xsl:when test="valueDomain/@type='quantity'"><xsl:attribute name="type">number</xsl:attribute><xsl:attribute name="step">0.01</xsl:attribute> <xsl:value-of select="valueDomain/property[1]/@unit"/></xsl:when>
                        <xsl:otherwise><xsl:attribute name="type">text</xsl:attribute></xsl:otherwise>
                    </xsl:choose>
                    </input>
                </xsl:if>
                <xsl:if test="valueDomain/@type='code'">
                    <select>
                        <xsl:for-each select="valueDomain/conceptList/concept">
                            <option value="{tokenize(@id, '\.')[last()]}"><xsl:value-of select="name[1]"/></option>
                        </xsl:for-each>
                    </select>
                </xsl:if>
            </td>
        </tr>
    </xsl:template>

    <xsl:template match="@*|node()">
        <xsl:apply-templates select="@*|node()"/>
    </xsl:template>
</xsl:stylesheet>

XML

<dataset id="2.16.840.1.113883.3.1937.99.62.3.1.1" effectiveDate="2012-05-30T11:32:36" statusCode="draft" versionLabel="" transactionId="2.16.840.1.113883.3.1937.99.62.3.4.2" transactionEffectiveDate="2012-09-05T16:59:35" shortName="measurement_message"><!--
 This is a view of the transaction view of Measurement message containing DECOR specifications for a transaction or dataset in a single, hierarchical view. 
 All inheritances are resolved, for transactions the dataset is filtered for those concepts occurring in the transaction.
 Valuesets are contained within the concept for easy reference. 
 Xpaths are calculated on a best effort basis. The should be considered a starting point for application logic, not an endpoint. -->
    <name language="en-US">Measurement message</name>
    <desc language="en-US">Measurement message test</desc>
    <concept minimumMultiplicity="0" maximumMultiplicity="*" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.1" statusCode="cancelled" effectiveDate="2012-05-30T11:32:36" type="group">
        <name language="en-US">Measurement</name>
        <desc language="en-US">Measurement of body weight on a specific date</desc>
        <implementation shortName="measurement">
            <templateLocation></templateLocation>
        </implementation>
        <concept minimumMultiplicity="1" maximumMultiplicity="1" conformance="M" isMandatory="true" id="2.16.840.1.113883.3.1937.99.62.3.2.3" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
            <name language="en-US">Weight</name>
            <desc language="en-US">Weight measurement</desc>
            <implementation shortName="weight">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="quantity">
                <property unit="kg" minInclude="25" maxInclude="240"></property>
            </valueDomain>
            <terminologyAssociation conceptId="2.16.840.1.113883.3.1937.99.62.3.2.3" code="27113001" codeSystem="2.16.840.1.113883.6.96" displayName="Body weight"></terminologyAssociation>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.13" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
            <name language="en-US">Weight gain</name>
            <desc language="en-US">Is there a weight gain?</desc>
            <implementation shortName="weight_gain">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="boolean"></valueDomain>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" conformance="C" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.18" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="group">
            <name language="en-US">Weight gain data</name>
            <desc language="en-US">Weight gain data</desc>
            <implementation shortName="weight_gain_data">
                <templateLocation></templateLocation>
            </implementation>
            <condition minimumMultiplicity="1" maximumMultiplicity="1" conformance="M" isMandatory="true">Bij gewichtstoename</condition>
            <condition minimumMultiplicity="" maximumMultiplicity="" conformance="NP" isMandatory=""></condition>
            <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.19" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
                <name language="en-US">Size weight gain</name>
                <desc language="en-US"></desc>
                <implementation shortName="size_weight_gain">
                    <templateLocation></templateLocation>
                </implementation>
                <valueDomain type="quantity">
                    <property unit="gram"></property>
                </valueDomain>
            </concept>
            <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.20" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
                <name language="en-US">Cause weight gain</name>
                <desc language="en-US"></desc>
                <implementation shortName="cause_weight_gain">
                    <templateLocation></templateLocation>
                </implementation>
                <valueDomain type="string"></valueDomain>
            </concept>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.15" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
            <name language="en-US">Length</name>
            <desc language="en-US">Body length</desc>
            <implementation shortName="length">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="quantity">
                <property unit="m" minInclude="0" maxInclude="3" fractionDigits="2"></property>
                <property unit="cm" minInclude="0" maxInclude="300" fractionDigits="0!"></property>
            </valueDomain>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.2" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
            <name language="en-US">Datetime</name>
            <desc language="en-US">Date of the measurement</desc>
            <implementation shortName="datetime">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="datetime"></valueDomain>
            <terminologyAssociation conceptId="2.16.840.1.113883.3.1937.99.62.3.2.2" code="DM" codeSystem="2.16.840.1.113883.3.1937.99.62.3.5.2" displayName="Datum meting"></terminologyAssociation>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.5" statusCode="cancelled" effectiveDate="2011-01-28T00:00:00" type="item">
            <name language="en-US">Measured by</name>
            <desc language="en-US">Person who performed the measurement</desc>
            <implementation shortName="measured_by">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="code">
                <conceptList id="2.16.840.1.113883.3.1937.99.62.3.2.5.0">
                    <concept id="2.16.840.1.113883.3.1937.99.62.3.2.5.1">
                        <name language="en-US">Patient</name>
                    </concept>
                    <concept id="2.16.840.1.113883.3.1937.99.62.3.2.5.2">
                        <name language="en-US">Home care provider</name>
                    </concept>
                    <concept id="2.16.840.1.113883.3.1937.99.62.3.2.5.3">
                        <name language="en-US">GP</name>
                    </concept>
                    <concept id="2.16.840.1.113883.3.1937.99.62.3.2.5.4">
                        <name language="en-US">Personal network</name>
                    </concept>
                </conceptList>
            </valueDomain>
            <valueSet id="2.16.840.1.113883.3.1937.99.62.3.11.5" effectiveDate="2012-07-25T15:22:56" name="demo1-meting-door" displayName="demo1-meting-door" statusCode="draft">
                <terminologyAssociation conceptId="2.16.840.1.113883.3.1937.99.62.3.2.5.0" valueSet="demo1-meting-door"></terminologyAssociation>
                <conceptList id="2.16.840.1.113883.3.1937.99.62.3.2.5.0">
                    <concept localId="1" code="P" codeSystem="2.16.840.1.113883.3.1937.99.62.3.5.1" displayName="Patiënt" level="0" type="A">
                        <name language="en-US">Patient</name>
                    </concept>
                    <concept localId="2" code="T" codeSystem="2.16.840.1.113883.3.1937.99.62.3.5.1" displayName="Thuiszorg" level="0" type="S">
                        <name language="en-US">Home care provider</name>
                    </concept>
                    <concept localId="3" code="H" codeSystem="2.16.840.1.113883.3.1937.99.62.3.5.1" displayName="Huisarts" level="0" type="D">
                        <name language="en-US">GP</name>
                    </concept>
                    <concept localId="4" code="M" codeSystem="2.16.840.1.113883.3.1937.99.62.3.5.1" displayName="Family care" level="0" type="L">
                        <name language="en-US">Personal network</name>
                    </concept>
                </conceptList>
            </valueSet>
            <terminologyAssociation conceptId="2.16.840.1.113883.3.1937.99.62.3.2.5" code="MD" codeSystem="2.16.840.1.113883.3.1937.99.62.3.5.2" displayName="Meting door"></terminologyAssociation>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="*" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.17" statusCode="cancelled" effectiveDate="2012-09-09T16:06:28" type="item">
            <name language="en-US">Comments</name>
            <desc language="en-US"></desc>
            <implementation shortName="comments">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="text"></valueDomain>
        </concept>
    </concept>
    <concept minimumMultiplicity="1" maximumMultiplicity="1" conformance="M" isMandatory="true" id="2.16.840.1.113883.3.1937.99.62.3.2.6" statusCode="draft" effectiveDate="2012-05-30T14:18:23" type="group">
        <name language="en-US">Person</name>
        <desc language="en-US">Person</desc>
        <implementation shortName="person">
            <templateLocation></templateLocation>
        </implementation>
        <concept minimumMultiplicity="1" maximumMultiplicity="1" conformance="M" isMandatory="true" id="2.16.840.1.113883.3.1937.99.62.3.2.4" statusCode="draft" effectiveDate="2012-05-30T14:18:23" type="item">
            <name language="en-US">Name</name>
            <desc language="en-US">Name of the person</desc>
            <implementation shortName="name">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="complex"></valueDomain>
        </concept>
        <concept minimumMultiplicity="1" maximumMultiplicity="1" conformance="M" isMandatory="true" id="2.16.840.1.113883.3.1937.99.62.3.2.7" statusCode="draft" effectiveDate="2012-05-30T14:18:23" type="item">
            <name language="en-US">BSN</name>
            <desc language="en-US">Dutch Social Security Number</desc>
            <implementation shortName="bsn">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="identifier"></valueDomain>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.8" statusCode="draft" effectiveDate="2012-05-30T14:18:23" type="item">
            <name language="en-US">Date of birth</name>
            <desc language="en-US">Date of birth of the person</desc>
            <implementation shortName="date_of_birth">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="date"></valueDomain>
        </concept>
        <concept minimumMultiplicity="0" maximumMultiplicity="1" isMandatory="false" id="2.16.840.1.113883.3.1937.99.62.3.2.14" statusCode="deprecated" effectiveDate="2012-05-30T14:18:23" type="item">
            <name language="en-US">Number of children</name>
            <desc language="en-US">Number of children</desc>
            <implementation shortName="number_of_children">
                <templateLocation></templateLocation>
            </implementation>
            <valueDomain type="count"></valueDomain>
        </concept>
    </concept>
</dataset>

As you can see, the actual XSLT to generate the HTML code is pretty small. A real HTML generator would need some more, but still needn’t be large. For a larger example, here is the generated HTML for the epSOS Patient Summary (due to IP issues, some Snomed codes are not included, so corresponding drop-downs will be empty).

In the second installment of this short series, we’ll take a further look at strategies for code generation with ART DECOR.

Informatie-uitwisseling in de zorg: hoe nu verder?

Deze column is eerder verschenen in ICT Zorg Magazine, Jaargang 12, nr. 4, augustus 2011.

De toekomst van informatie-uitwisseling in de zorg is onduidelijk. In maart heeft de Eerste Kamer het landelijke Elektronisch Patiëntendossier (landelijk EPD) verworpen, ten faveure van ontwikkeling van onderop. Een politieke spagaat van jewelste: het parlement wil van bovenaf opleggen dat informatie-uitwisseling in de zorg bottom-up georganiseerd moet worden. Daarnaast is een toename te zien van initiatieven die de ketenzorg willen verbeteren. Te verwachten is met de toenemende specialisatie van ziekenhuizen dat dit alleen maar zal toenemen.

Daarnaast het aantal patiëntportalen dat de patiënt inzage in het medisch dossier bieden, toe. Maar in juni maakte Google, toch niet voor een kleintje vervaard, bekend het eigen patiëntportaal, Google Health, te beëindigen: wegens gebrek aan succes. Hebben patiëntendossiers dan wel een toekomst? Als het Google niet lukt, wie lukt het dan wel? Voorlopig ontbreken de voorwaarden voor succes nog. Continue reading “Informatie-uitwisseling in de zorg: hoe nu verder?”

“Luister niet naar de gebruiker”

(Oorspronkelijk verschenen in Computable 30-5-2011: Luister niet naar de gebruiker)

Regelmatig hoor je het weer. Automatiseerders zijn techneuten. Ze kijken niet wat de gebruiker wil. Ze komen met technische oplossingen waar niemand om vraagt, en daarom mislukken al die projecten. Om een IT project tot een succes te maken, moet je niet van de techniek uitgaan, maar eerst eens gaan vragen wat de gebruiker nu eigenlijk wil.

Het oude, vertrouwde model. We gaan de gebruiker vragen wat die wil, en schrijven dat heel precies op, in User Requirements, of Eisen en Wensen of zoiets. Dan laten we daar een partij informatie-analisten op los, die dat informatie-analyseren, en zo nog een paar stappen verder komen we met een functioneel ontwerp, en dat laten we ondertekenen door de gebruiker met diens eigen bloed, zwerend op al wat voorhanden is, dat dit toch écht (maar dan ook écht) is wat hij of zij wil. En dat bouwen we dan (wat altijd veel en veel duurder is dan verwacht, en veel en veel langer duurt, want waar in dit verhaal komt nu voor wat de techniek wel kan en niet kan, en wat makkelijk gemaakt kan worden en wat niet). Continue reading ““Luister niet naar de gebruiker””

Het EPD, standaarden en marktwerking

Nu de Eerste Kamer naar verwachting de Wet op het EPD afwijst, is het de vraag hoe het verder moet. De Eerste Kamer ziet liever eerst uitwisseling van medische gegevens in de regio, en tegelijkertijd een rol voor de overheid bij het opstellen van standaarden en normen en het houden van toezicht. Beter het EPD eerst op te bouwen van onderop – en tegelijkertijd vragen om sturing van de overheid. Het lijkt een tegenstelling. En deels is het dat ook.

Is het niet beter wanneer de overheid zich even terugtrekt uit de sturende rol? Wanneer lokale initiatieven de kans krijgen te bloeien – of te mislukken,  zonder last van die vervelende pottenkijkers van de ministerie? Echt innovatie ontstaat zelden vanuit regulering en de moloch van de ambtenarij. Is het niet beter eerst te zien wat werkt, en later als overheid te proberen die succesvolle initiatieven verder te verbreiden? Laat de markt haar werk doen – het zou VVD-minister Schippers en VVD-opponente Dupuis als liberalen uit het hart gegrepen moeten zijn.

Of toch niet? Continue reading “Het EPD, standaarden en marktwerking”