The Template

Our starting point is going to be the clinical model. The Discharge template contains the clinical model that we will be building. Click here to download the file set for this template. 

The file set is a zip file that contains the template and the archetypes that fill the template. You need the template editor from Ocean to view this template (you can use a text editor of course, but it would not be very efficient, unless you are a human archetype parser) Visit Ocean Informatics resources page and download the template editor. 

When you click Open Template under File menu, you should see the default location where your templates and archetypes are kept. This location may change based on your operating system and settings, so you should find it out by choosing open template. 

 

In my system, the default location is C:\Users\Public\Documents\My Clinical Models\Sample Set\Templates

Whatever location the file dialog opens, you need to extract the contents of the file set you have downloaded to that location. Archetypes under the archetype directory in the file set should go to relevant archetypes directory. For example  openEHR-EHR-COMPOSITION.discharge.v1.adl should go under archetypes/composition. Follow the same guidance for evaluation, instruction archetypes etc. Look under entry directory to find evaluation, instruction etc. 

Finally, copy the Discharge_report.oet template to templates directory that is used by the template designer.

When you choose open template from the menu again, you should see the new template available to you as follows:

When you open the Discharge report template, you can see the clinical model based on openEHR. If you get any errors about missing archetypes, it means you have not placed the archetypes from the file set in the right directories. Make sure that correct archetypes are under correct directories; take archetype names as hint: EVALUATION goes under entry/evaluation etc.  Here is what your model should look like:

 

Take your time to examine the model. The information panel on the right will give you more specific information about the selected nodes, such as their RM type or path.

It is important to understand that a combination of clinical data instances will be compatible with this template. For example, the diagnosis node selected in the screenshot above has optional occurrences, as you can see from the node properties section on the right.

This means that data that is compatible with this template may not contain any diagnosis. If any field is marked as mandatory through openEHR's relevant rules (see existence and occurrences in ADL specification) then data must contain that field, or it will not be validated by this template.

As mentioned before, template designer supports exporting this template to various formats. Check out the options under the Export section, under File menu. For the moment, we will not be using any of these options, but some of them can be used as alternatives or complimentary approaches to openEHR.NET to create openEHR based clinical data. We will examine these options later. For the moment, let us build a C# RM Composition that would be validated with this template. 

The full source code for this sample project is in the SVN server, so we will only look at clinical snippets from key classes. You can download and open the complete project in Visual Studio to see the details.

Let's take a look at the code now:

 

 private void BuildComposition()
        {

            string archetypeNodeId =  "openEHR-EHR-COMPOSITION.discharge.v1";
            DvText name = new OpenEhr.RM.DataTypes.Text.DvText("Discharge report");
            Archetyped archetypeDetails = GetArchetypeDetails();            
            CodePhrase language = new CodePhrase("en", "ISO_639-1");
            CodePhrase territory = new CodePhrase("AU", "ISO_3166-1");
            DvCodedText category = new DvCodedText("event", "433", "openehr");
            PartyIdentified composer = new PartyIdentified("EhrGateUnit");
            EventContext context = GetContext();
            ContentItem[] contents = getContent();
            _composition = new Composition(name, archetypeNodeId, null, null, archetypeDetails, null, language, territory, category, context, contents, composer);
        }

 

If you look at your template designer screen, you can see that these values are the same with the ones in the template. For example, the Discharge node in the tree has the same path with the archetypeNodeId we have used in C# code. The Discharge node is mandatory, and its class property is Composition. So is the C# object we are creating here.

The Composition constructor takes Composition  fields as parameters due to immutable object pattern explained before. In order to make sure that all aspects of the sample project works, please follow this method in your own code.

A small trick to increase readability of the code is to use GetContext() or getContent() type of calls where you need to provide an instance of an object. Since developers are usually more comfortable in writing code top to bottom (at least I am), creating getter methods and filling them in helps. At least the structure of the code becomes similar to the structure of the tree in the template editor.

The rest of the code basically does the same thing, it creates clinical data using C# implementation of openEHR. Notice that the C# code actually fills in the values which are defined by the archetypes and  the container template. openEHR.NET can not detect that you've provided a value that does not comply with the template unless you try to put this data to OceanEHR repository. However, you can access the template and archetypes via functionality in the openEHR.NET and check for validity yourself. However, in client server programming, client is not usually considered to be a safe location to perform critical checks, and these types of checks are usually done at the server side.

Once you build a Composition, you have an in memory object that contains clinical data. So what can you do with it? Here are a few options:

  • You can run some clinical logic code on it, such as checking for critical values and triggering another action in response
  • You can persist the data in the object to OceanEHR repository, using web services.
  • You can persist data to XML. 

XML Serialization

The last option, persisting data to XML, can provide interesting opportunities. The following code is all it takes to save code as XML:

 

 public void SaveToDisk(string pTargetPath)
        {
            using (XmlWriter writer = XmlWriter.Create(pTargetPath))
            {
                System.Xml.Serialization.XmlSerializer serializer =
                                new System.Xml.Serialization.XmlSerializer(
                                    typeof(Composition),
                                    new System.Xml.Serialization.XmlAttributeOverrides(),
                                    new Type[] { },
                                    new System.Xml.Serialization.XmlRootAttribute("composition"),
                                   OpenEhr.Serialisation.RmXmlSerializer.OpenEhrNamespace);

                serializer.Serialize(writer, _composition);
            }
        }

 

The XML output is valid XML according to published openEHR XSD. The following diagram is an overview of the process:

 

This jump to XML space actually corresponds to a very common industry setup, where there is an XSD and various tools and software consume the XML that is compatible with the XSD. Even if a clinical system does not use any industrial strength openEHR repository, such as OceanEHR, it is still possible to use Archetype Editor, Template Designer and openEHR.NET code base to follow model driven development principles.  This approach has the advantage of using openEHR RM and two level modelling as the clinical knowledge building method, and the output is XML, which is usable across almost all modern technologies.

The second benefit is being able to exchange data with other systems which follow the same practice, even if they're not using C# implementation of openEHR. The openEHR java reference implementation code that was developed by Rong Chen actually supports emitting XML that is again compatible with published openEHR XSD. This means the following process is possible:

There are other options in the XML space that are provide by the template designer, but they'll be discussed in a future addition to this tutorial.  For the moment, let's take a look at another possibility that is based on the XML output; using XML web services to talk to OceanEHR

Calling web services with EhrGateWS Client

SOAP (Simple Object Access Protocol) web services use WSDL on top of XSD to exchange data. Almost all major software technologies provide support for generating proxy classes from WSDL documents. Once given a WSDL document, the tooling generates source code in C#/Java/Phyton/C++ etc and these generated classes allow easy access to remote server functionality over HTTP, by sending and receiving XML content.

However, XSD is not rich enough to describe all aspects of openEHR specification, for example methods for navigating across fields of clinical documents can not be defined in XSD, such as GetParent() or GetNodeAt() etc.. Therefore, if one wants to use OceanEHR web services or any other WSDL based service, two sets of classes will be created. For example for Composition, there would be two C# Composition classes, one is from openEHR.NET, the RM implementation, and the other from the Visual Studio tooling that would use WSDL as input. The web service proxy Composition would have only data related methods, and openEHR.NET Composition class would have both data and other methods. Web service proxy would have the capability to talk to a web service, and to handle HTTP calls, errors etc.

So they would be similar classes, but they would not be the same. The interesting thing is, both Composition classes would have the capability to read and write XML based on the same schema!

Ideally, we'd like to have full RM implementation, with a capability to talk to web services so that we can put data into a repository and read it back.  The immediate hack that one can think of is to write a thin layer over HTTP, and send the XML output from openEHR.NET Composition in this envelope. This is indeed how web services work, but error handling, asynchronous calls etc would be tedious work. The EhrGateWS Client project that is released with oenEHR.NET connects two sets of classes. The end result is being able to send data to OceanEHR from full RM implementation, without having to deal with proxy generation etc. See the following snippets, taken from various methods of the sample project:

 

//code for setting up class fields...
service = new EhrServiceProxy(url);
            sessionTicket = service.LoginSession(userId, password);
            committer = new PartyIdentified("EhrServiceTests");
            subject = NewPartyRef();
            ehrId = service.Create(sessionTicket, committer, subject);

//send existing Composition instance to server
String compositionId = service.CommitComposition(sessionTicket, ehrId, committer, null, AuditChangeType.creation, VersionLifecycleState.complete,null, _composition);

//close session on the server
service.CloseSession(sessionTicket);

 

The particulars of calls is not important, they are the specifics of OceanEHR web service, but as you can see, simply creating a service object and putting the Composition instance into it is enough to send it to server.

EhrGateWS Client project uses web service proxy classes at the background to manage communication. The following diagram explains the architecture we've described above:

 

So why do we put data to OceanEHR? Other than persisting it at a central location of course. The answer to this question is the set of services provided by OceanEHR. The most important one that is worth mentioning at this point is AQL

Archetype Query Language (AQL) and Web Services

(TBC)

 

There is another opportunity for the developers when they have a template, and that is using the Template Data Schema.

Using Template Data Schema and Template Data Documents for XML based development

One of the options  provided by the template designer, for exporting templates is the Template Data Schema (TDS).

What makes TDS a promising option for software development, is its very simple nature: it is just an XML schema, which has references to other published openEHR schemas.

For every template, the template designer is capable of exporting a TDS created for that template. Once you have the TDS, you are actually in the XML space, and what makes TDS interesting is the amount of tooling and frameworks that has been developed on top of XML. 

Once you have a TDS, you can start creating TDDs (Template Data Document). A TDD is just an XML file that is valid according to the TDS. So every TDD is a container for clinical information, but it is based on openEHR's XML representation.

This presents a very convenient balance for openEHR involvement. On one hand, archetype editor and template designer allow one to develop models for clinical software, using openEHR RM, and XML outputs allow software developers to develop software with their usual tools of the trade. The software that is being developed does not have to persist data to an openEHR backend, but since it has originated from the TDS, with some care, it can stay in a form that can be pushed to an openEHR backend in the future.

Let's list a set of use cases which may be possible based on the TDS export feature of template designer

  • Integrating existing systems to openEHR based systems
  • Exchanging data accross systems
  • Applying XML transformations to leverage existing tools and technologies.

This diagram provides an overview of what I've just described:

An overview of these options would help see the opportunities clearer:

If there is an existing system which is producing and processing clinical data, the TDS + TDD is a great container to talk to an openEHR based system. HL7 messages, CDA documents and custom clinical model serializations are all candidates for TDD based communication to openEHR backends and other openEHR based systems. If you can put existing data into TDD, and read it back from TDD, it means you can talk to openEHR.

Even though you're not talking to an openEHR based system, TDS + TDD makes a very convenient container for exchanging data accross sytems. Imagine you're developing an iPhone or Android application for home care. You need to send the blood pressure to a web portal, which may be accessed by the GP, either via web or via his GP system.  In this case, rather than developing a content protocol, you can simply use the blood pressure archetype, create a template, trim bits of the archetype that you're not interested in, and export it to a TDS. You now have a schema which you can use to create XML files. You can send those files to web portal, or to GP system, and you have gained a lot of time, since you've used both the available clinical model, and common technology (XML)

What you can do with XML is not limited with data exchange of course. A common point of interest is to create user interfaces based on the TDS and other transformations that can be applied to TDD, such as using XSLT to display data to users.

Take a look at http://gastros.codeplex.com/ to see an actual clinical system that is making use of the XML capabilities, and you may want to take a peek at the source code while you're there. That is a valuable project which leverages the openEHR.NET code base.

One thing that GastroOS does is that it uses Operational Templates, and that is the last bit of XML related export from the template designer that we'll be talking about.

Operational Template Exports from the Template Designer

An operational template, is an XML file that is generated by the template designer. It is one of the export options for the template, and what it does is actually to flatten the template, cleaning up fields which are disabled, or hidden, and produce a simpler hierarchy, preserving all the contents. The file extension is opt, but it is actually an XML file, which is based on an XML schema (Template.xsd)

A practical method for seeing the difference between a TDS and an OPT is to open both files in a text editor.  The opt file uses AOM constructs to define the template. Take a look at the following snippet:

 

<children xsi:type="C_COMPLEX_OBJECT">
                <rm_type_name>ELEMENT</rm_type_name>
                <occurrences>
                  <lower_included>true</lower_included>
                  <upper_included>true</upper_included>
                  <lower_unbounded>false</lower_unbounded>
                  <upper_unbounded>false</upper_unbounded>
                  <lower>0</lower>
                  <upper>1</upper>
                </occurrences>
                <node_id>at0002</node_id>
                <attributes xsi:type="C_SINGLE_ATTRIBUTE">
                  <rm_attribute_name>value</rm_attribute_name>
                  <existence>
                    <lower_included>true</lower_included>
                    <upper_included>true</upper_included>
                    <lower_unbounded>false</lower_unbounded>
                    <upper_unbounded>false</upper_unbounded>
                    <lower>0</lower>
                    <upper>1</upper>
                  </existence>
                  <children xsi:type="C_COMPLEX_OBJECT">
                    <rm_type_name>DV_TEXT</rm_type_name>
                    <occurrences>
                      <lower_included>true</lower_included>
                      <upper_included>true</upper_included>
                      <lower_unbounded>false</lower_unbounded>
                      <upper_unbounded>false</upper_unbounded>
                      <lower>1</lower>
                      <upper>1</upper>
                    </occurrences>
                    <node_id />
                  </children>
                </attributes>
              </children>

 

 

And the following snippet is from the TDS, for the same section of the template:

 

<xs:element name="Encounter_ID" minOccurs="0">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="name">
                      <xs:complexType>
                        <xs:sequence>
                          <xs:element name="value" type="xs:string" default="Encounter ID" />
                          <xs:element name="mappings" type="oe:TERM_MAPPING" minOccurs="0" maxOccurs="unbounded" />
                          <xs:element name="defining_code" type="oe:CODE_PHRASE" minOccurs="0" />
                        </xs:sequence>
                      </xs:complexType>
                    </xs:element>
                    <xs:choice>
                      <xs:element name="value" type="oe:DV_TEXT" />
                      <xs:element name="null_flavour" type="oe:DV_CODED_TEXT" />
                    </xs:choice>
                  </xs:sequence>
                  <xs:attribute fixed="at0002" name="archetype_node_id" type="oe:archetypeNodeId" />
                  <xs:attribute fixed="ELEMENT" name="type" />
                  <xs:attribute name="valueType" fixed="DV_TEXT" />
                </xs:complexType>
              </xs:element>
...

 

As you can see, the opt is describing structure and types from the openEHR specification directly, that is it is talking about DV_TEXT from the openEHR specification. The TDS on the other hand is referring to oe:DV_TEXT which is a type defined in the XML schema of openEHR. So TDS is an artefact that stays fully in the XML type system, but opt is an XML artefact that describites something in the openEHR specification level, without assumption of any particular implementation. From an implementation perspective, opt is a more abstract artefact. A more pragmatic description of the difference would be as follows:

If you want to create a composition based on the opt, you'd have to create RM objects in C#/Java/Phyton etc, as defined by the opt, and fill the values of those objects.

If you want to create a composition based on the TDS, you'd have to create XML as defined by the TDS, and fill the values in the XML document.

The XML can be bound to programming language classes of course, but it is still a reflection of the XML type system. If you are given a TDS only, (along with referenced XSDs), you can create XML based clinical data, without an implementation of the RM in your programming language of choice .If you are given an opt, you have to have an implementation of openEHR to use it.

Once you see the diffence between these outputs from the template designer, you can start using openEHR.NET to make use of them in various use cases.

 

Back to openEHR.NET , and its XML capabilities.

We've seen before that openEHR.NET can persist and read XML that is compatible with the published openEHR schemas. This means you can read/write RM and AM. After explaining the opt output, we now have a process that includes the following key components:

  • Templates
  • Operational templates
  • openEHR.NET functionality
  • XML output

There is also the option of sending data to an openEHR backend, such as the OceanEHR, which is possible with openEHR.NET, but we'll focus on what is immediately available out of the box here.

With the components listed above, it is possible to build an openEHR based system, and use XML to connect it to a larger ecosystem. GastroOS is a good example worth looking at for a demonstration of the idea. 

 


<children xsi:type="C_COMPLEX_OBJECT">
                <rm_type_name>ELEMENT</rm_type_name>
                <occurrences>
                  <lower_included>true</lower_included>
                  <upper_included>true</upper_included>
                  <lower_unbounded>false</lower_unbounded>
                  <upper_unbounded>false</upper_unbounded>
                  <lower>0</lower>
                  <upper>1</upper>
                </occurrences>
                <node_id>at0002</node_id>
                <attributes xsi:type="C_SINGLE_ATTRIBUTE">
                  <rm_attribute_name>value</rm_attribute_name>
                  <existence>
                    <lower_included>true</lower_included>
                    <upper_included>true</upper_included>
                    <lower_unbounded>false</lower_unbounded>
                    <upper_unbounded>false</upper_unbounded>
                    <lower>0</lower>
                    <upper>1</upper>
                  </existence>
                  <children xsi:type="C_COMPLEX_OBJECT">
                    <rm_type_name>DV_TEXT</rm_type_name>
                    <occurrences>
                      <lower_included>true</lower_included>
                      <upper_included>true</upper_included>
                      <lower_unbounded>false</lower_unbounded>
                      <upper_unbounded>false</upper_unbounded>
                      <lower>1</lower>
                      <upper>1</upper>
                    </occurrences>
                    <node_id />
                  </children>
                </attributes>
              </children>

Last edited Mar 22, 2012 at 12:04 PM by serefarikan, version 14

Comments

No comments yet.