Monthly Archives: December 2015

Improved Documentation of the $closure operation for #FHIR Connectathon 11

One of the stated goals of FHIR Connectathon 11 in Orlando in a few weeks time is for the terminology services stream participants to test out the $closure operation.

The $closure operation is an important one because it enables terminology consumers to close the loop – to integrate terminological based reasoning into their internal search capabilities, without having to split the search across multiple technologies – which is a real challenge. For additional documentation about the closure table process, see http://hl7.org/fhir/terminology-service.html#closure

However, up to now, the terminology connectathons have focused on other parts of the terminology services, and $closure hasn’t been tested – or even implemented.

In preparation for the Connectathon, I’ve implemented the $closure operation. Here’s some additional API documentation for the $closure operation, which will be the basis for the connectathon. Note that all this documentation is in JSON, but XML should work the same for servers that support XML.

Note: this is additional API documentation for connectathon implementers. Before using the specific documentation below, you should be familiar with the $closure operation concepts and description.

To help connectathon participants, I’ve published both a client and server that implement this operation. The server is at http://fhir2.healthintersections.com.au/open, and the client is in my ValueSet editor (Tools…Closure Manager). Note that the value set editor is still rough – I haven’t got the rest of it up to speed for DSTU2 quite, but the closure table part works.

Initialising a Closure Table

Before it can be used, a closure table has to be initialised. To initialise a closure table, POST the following to http://fhir2.healthintersections.com.au/open/ConceptMap/$closure:

{ 
  "resourceType" : "Parameters", 
   "parameter" : [{ 
     "name" : "name", 
     "valueString" : "eaab9a21-db9a-45e0-a400-0075eb19f795" 
  }]
 }

A successful response from the server looks like this:

{
 "resourceType": "Parameters",
 "parameter": [
    {
      "name": "outcome",
      "valueBoolean": true
    }
  ]
}

If there is an error – usually involving the closure name, the server returns a HTTP status 400 with an operation outcome:

{
  "resourceType": "OperationOutcome",
  "text": {
    "status": "generated",
    "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>invalid closure name \"invalid-id!\":</p></div>"
  },
  "issue": [
    {
      "severity": "error",
      "details": {
        "text" : "invalid closure name \"invalid-id!\""
      }
    }
  ]
}

Note: As a matter of policy, my server requires valid allows UUIDs for closure names unless you log in using SMART on FHIR first, in which case you can use any valid id value for the closure name, but the closure is unique to the login behind the OAuth unless it’s a UUID. Other servers may choose other policies.

Adding to a closure Table

When the consumer (client) encounters a new code, it POST the following to http://fhir2.healthintersections.com.au/open/ConceptMap/$closure:

{
  "resourceType" : "Parameters",
  "parameter" : [{
    "name" : "name",
    "valueString" : "eaab9a21-db9a-45e0-a400-0075eb19f795"
  }, {
    "name" : "concept",
    "valueCoding" : {
       "system" : "http://snomed.info/sct",
       "code" : "22298006",
       "display" : "Myocardial infarction"
    }
  }]
}

Note that this example only includes one concept, but more than one is allowed:

{
  "resourceType" : "Parameters",
  "parameter" : [{
    "name" : "name",
    "valueString" : "eaab9a21-db9a-45e0-a400-0075eb19f795"
  }, {
    "name" : "concept",
    "valueCoding" : {
       "system" : "http://snomed.info/sct",
       "code" : "22298006",
       "display" : "Myocardial infarction"
    }
  }, {
    "name" : "concept",
    "valueCoding" : {
       "system" : "http://snomed.info/sct",
       "code" : "128599005",
       "display" : "Structural disorder of heart"
    }
  }]
}

The response varies depending on the conditions on the server. Possible responses:

If the closure table has not been initialised:

Return a 404 Not Found with

{
  "resourceType": "OperationOutcome",
  "text": {
    "status": "generated",
    "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>invalid closure name \"eaab9a21-db9a-45e0-a400-0075eb19f795\":</p></div>"
  },
  "issue": [
    {
      "severity": "error",
      "details": {
        "text" : "invalid closure name \"eaab9a21-db9a-45e0-a400-0075eb19f795\""
      }
    }
  ]
}

If the closure table needs to be reinitialised:

Return a 422 Unprocessable Entity with

{
  "resourceType": "OperationOutcome",
  "text": {
    "status": "generated",
    "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p>closure \" eaab9a21-db9a-45e0-a400-0075eb19f795\" must be reinitialised</p></div>"
   },   
   "issue": [{
       "severity": "error",
       "details": {
         "text" : "closure \" eaab9a21-db9a-45e0-a400-0075eb19f795\" must be reinitialised"
       }
     }
   ]
}

The server should only send this when it’s underlying terminology conditions have been changed (e.g. a new version of SNOMED CT has been loaded). When a client gets this, it’s only choice is to initialise the closure table, and process all the codes in the closure table again (the assumption here is that the system has some external source of ‘all the codes’ so it can rebuild the table again).

If the concept(s) submitted are processed ok, but there’s no new concepts, or no new entries in the table, return a 200 OK with :

{
    "resourceType": "ConceptMap",
    "id": "42dc941b-7ff9-4859-a4cb-9592007a26e5",
    "version": "1",
    "name": "Updates for Closure Table eaab9a21-db9a-45e0-a400-0075eb19f795",
    "status": "active",
    "experimental": true,
    "date": "2015-12-20T23:12:55Z"
}

Note that in the $closure operation, the response never explicitly states that a code is subsumed by itself. Clients should assume that this is implicit.

If there’s new entries in the closure table:

The server returns a 200 ok with:

{
    "resourceType": "ConceptMap",
    "id": "b87db127-9996-4d0c-bda9-a278d7a24a69",
    "version": "2",
    "name": "Updates for Closure Table eaab9a21-db9a-45e0-a400-0075eb19f795",
    "status": "active",
    "experimental": true,
    "date": "2015-12-20T23:16:24Z",
    "element": [{
        "codeSystem": "http://snomed.info/sct",
        "code": "22298006",
        "target": [{
           "codeSystem": "http://snomed.info/sct",
           "code": "128599005",
           "equivalence": "subsumes"
        }]
    }]
}

Note: it’s important to get this the right way around. From the spec: The equivalence is read from target to source (e.g. the target is ‘wider’ than the source). So in this case, 128599005 (Structural disorder of heart) subsumes 22298006 (Myocardial infarction).

Notes:

  • The server can return multiple elements, each with 1 or more targets
  • servers may return the relationship represented in either direction.

Re-running a closure operation

The way that the closure operation functions, it’s possible for a client to lose a response from the server before it is committed to safe storage (or the client may not have particularly safe storage). For this reason, when a client is starting up, it should check that there has been no missing operations. It can do this by passing the last version it is sure it processed in the request:

{ 
  "resourceType" : "Parameters", 
   "parameter" : [{ 
     "name" : "name", 
     "valueString" : "eaab9a21-db9a-45e0-a400-0075eb19f795" 
  }, {
     "name" : "version", 
     "valueString" : "3" 
  }]
 }

That’s a request to return all the additions to the closure table since version 3. The server returns its latest version in the concept map, along with anything added to the closure table since version 3 (not including version 3)

Notes:

  • The client can pass a concept or version, but not both
  • These examples (and my server) use a serially incrementing sequential integer, but this is not required, and clients should not assume that there is any meaning or order in the version. Just recall the last version, and treat it as a magic value. There is, however, one special value: ‘0’. Passing a last version of 0 should be understood as resyncing the entire closure table

 

#FHIR and mapping approaches

Across the board, many FHIR implementers have the same challenge: mapping between some other format, and a set of FHIR resources. The other format might be a CIMI model, an openEHR model, a CCDA document, or a series of other things. At the HL7 Orlando meeting in the second week of January 2016, we’ll be holding a BOF (Birds of a Feather) on Tuesday evening. In this meeting, we’ll be:

  • seeing some mapping tool demos
  • considering how to flesh out an interoperable format for mappings, and create an eco-system around that
  • gathering a set of requirements to drive the format./eco-system
  • identifying significant applications where exchanging mappings will be useful (e.g. CCDA…)

One problem when it comes to mappings is that there’s a wide variety of things that are called ‘mappings’. I find it useful to classify mappings into 4 categories:

  • Skeletal. There is just enough information to indicate to readers where the different models align structurally. Anything else is left to the reader’s imagination
  • Notional. The mappings are done at the class and attribute level (or equivalent), and indicate that the definitions of the elements establish that these are about the same thing. However the types of the attributes might not match up, and there might be all sorts of special conditions and assumptions that need to be true for the mappings to be valid
  • Conceptual. The mappings are done at the class and attribute level, down to the level of primitive data types. However not all the value domains of all attributes are fully mapped, and there may be special cases not accounted for
  • Executional. The mappings account for the full value domain of all the source attributes, and all the special cases are handled. The mapping notation is precise enough that all the construction issues such as when to create new instances of repeating elements is fully described. These mappings can be fed into conversion engines that are then capable of transforming instances correctly from one format to another

The mapping notations become more complex as you move through this classification; and it becomes less easy to read their intent. In fact, the full executable mapping cases is proper 3GL code, and it’s not unusual for implementers to maintain both executional mappings in code, and notional (usually) mappings as documentation of the code. But fully executional mappings are the most useful for real world use

FHIR’s existing mappings (on the mappings tab throughout the specification) are mostly Notional or Conceptual, though there’s a smattering of skeletal and executional ones as well. We’re going to ramp that up in the FHIR specification itself, and in a raft of implementation guides of various forms. As we work through our use cases, I suspect we’ll find a need for a mix of these levels of mappings, and we need to keep this classification in mind – unless someone comes up with (or knows) a better one.

 

Terminology Services Connectathon in Australia

This is out today:

Adoption and use of clinical terminology in Australia has received a major boost with the signing of a licensing agreement between the CSIRO and the National E-Health Transition Authority (NEHTA) to grant users within Australia free access to a comprehensive suite of tools to support browsing, authoring, mapping, maintaining, and querying terminology.

These tools will be invaluable for implementers of clinical terminology to move towards unified clinical coding and improved patient safety.

  • NEHTA’s LINGO™ enables users to author local extensions to SNOMED CT-AU using the same robust browser-based authoring tool used by the National Clinical Terminology Service.
  • CSIRO’s Ontoserver is a terminology server that provides a sophisticated means of querying, searching, filtering and ranking SNOMED CT-AU and other standard clinical terminologies including an application programming interface (API) that allows for quick and easy way for implementers to add SNOMED CT based data capture fields to their system.
  • CSIRO’s Snapper is backed by Ontoserver and enables users to create local data sub-sets and maps.

“This licensing agreement between NEHTA and CSIRO enables both the private and public health sectors in Australia to access these tools to support the use and maintenance of terminology products. This will significantly improve the implementation and management of clinical data for enhanced patient outcomes,” said NEHTA CEO Peter Fleming.

The licensing agreement and national implementation will enable NEHTA to establish a fully syndicated terminology service providing national support for the re-use of locally-built reference sets, simple portal-based access to terminology products, and simplified maintenance processes to cascade SNOMED CT-AU updates into other products and support improved vendor testing processes.

I think that this is a great step forward for healthcare applications in Australia; laying down a solid terminology infrastructure is a real opportunity for us to improve healthcare applications around the country, though it will take some time for the application providers to figure out how to use it well, and then to start to make use of the powerful possibilities it offers.

The press release goes on to say:

NEHTA invites all interested to participate in a series of three Connectathons, with the first scheduled for February 2016.

Here’s some additional provisional details about the first connectathon:

  • It’s planned to be in Brisbane Feb 10/11
  • It’ll be held in association with HL7 Australia, and in addition to the CSIRO Ontoserver, the HL7 Australia Terminology Server will be part of the connectathon. Other terminology services may also be represented
  • Attendance is open to any software development team that produces healthcare applications that run in Australia (ISVs, jurisdictions, etc)
  • The technical focus will be the ValueSet and Concept Map resources, and the Value Set Expansion, Validation, and Translation operations
  • I don’t think there’ll be any charge for attending the connectathon

The connectathons are a key opportunity for vendors – large and small – to learn

  • what terminology services can do
  • what deployable terminology service solutions exist, including open source ones
  • why making use of them will be a key strategic requirement to make their customers happy and keep up with the market

Note that the connectathon details are still subject to change.

#FHIR and Postel’s Robustness Principle

An important principle in interoperability is Postel’s Robustness Principle:

Be conservative in what you send, be liberal in what you accept

There’s been quite a bit of discussion recently in various implementation forums about robustness of FHIR interfaces, and I think there’s a few things to say about how to develop robust FHIR principles.

Firstly, the FHIR core team endorses Postel’s principle – the pathway to robust interoperability is to be careful to be conformant in what you send, and to be as accepting as possible in what you receive. However, in practice, it’s not necessarily easy to see how to implement like that.

There’s also some circumstances where this isn’t what you should do. As an example, when I started writing my reference server, I followed Postel’s law, and accepted whatever I could accept. However this fostered non-conformant implementations, so on the behest of the community, I’ve been gradually tightening up the rigor with which my server enforces correctness on the clients. For example, my server validates all submitted resources using the formal FHIR validator. Somewhat unfortunately, the main effect that has had is that implementers use one of the other servers, since their client works against that server. This’ll get worse when I tighten up on validating content type codes in the lead in to the Orlando Connectathon. Generally, if an implementation is used as a reference implementation, it should insist that trading partners get it right, or else all implementations will be forced to be as generous as the reference implementation.

But let’s assume you wanted to follow Postel’s law. What would that mean in practice, using a FHIR RESTful interface?

Reading/Writing Resources

If you’re creating a resource, then you can start by ensuring that your XML or JSON is well formed. It’s pretty much impossible for a receiver to process improperly formed XML or JSON (or it’s at least very expensive), but experience shows that many implementers can’t even do that (or here), and I’ve seen this a lot. So for a start, never use string handling routines to build your resources – eventually, you’ll produce something invalid. Always always use a DOM or a writer API.

Beyond this:

  • Ensure that all mandatory elements are present
  • Ensure that the correct cardinalities apply
  • Ensure that you use the right value sets
  • Always use UTF-8
  • etc

In fact, in general, if you are writing a resource, you should ensure that it passes the validator (methods for validation), including checking against the applicable profiles (whether they are explicit – stated in Resource.meta.profile – or implicit – from the conformance statement or other contextual clues).

If you’re reading a resource, then:

  • Only check the content of the elements that you have to use
  • Accept non-UTF-8 encoding
  • Only check for modifier extensions on the elements you actually use, and don’t check for other extensions (only look for extensions you know)
  • accept invalid codes for Coding/CodeableConcept data types (further discussion below)

However, there’s not that much you can be graceful about with the content; generally, if you have to use it, it has to be right.

Using the RESTful API

In practice, when using the API, clients should ensure that they:

  • use the correct mime types for content-type and accept, and they always specify a mime type (never leave it to the server)
  • construct the URL correctly, and all the escapable characters are properly escaped
  • they use the correct case for the URL
  • they look for ‘xml’ or ‘json’ in the return content-type, and parse correctly without insisting on the correct mime type
  • they can handle redirects and continue headers correctly

Servers should:

  • accept any mime types that have ‘xml’ or ‘json’ in them
  • only check headers they have to
  • accept URLs where not all the characters are escaped correctly (in practice, ‘ ‘, =, ?, and + have to be escaped, but other characters sometimes aren’t escaped by client libraries)
  • always return the correct FHIR mime types for XML or JSON
  • always return the correct CORS headers
  • ignore case in the URL as much as possible
  • only issue redirects when they really have to

Note: the full multi-dimensional grid of request/response mime types, and the _format header is long and complex, so we’ve not specified the entire thing. As a consequence, outside of these recommendations above, there’s dangerous waters to be encountered.

HTTP Parameters

One area that’s proven controversial in practice is how to handle HTTP parameters. With regard to search, the FHIR specification is quite specific: a server SHALL ignore HTTP parameter that it does not understand. This is because there may be reasons that a client has to add a parameter to the request because of requirements imposed by HTTP agents that intercept the request before it hit’s the FHIR server (this may be clients, proxies, or filters or security agents running on the server itself). In the search API, a server specifically tells a client which parameters it processed in the search results (Bundle.links, where rel = ‘self’), but this doesn’t happen in other GET requests (read, vread, conformance).

For robustness, then, a client should:

  • Only use parameters defined in the specification or in the servers conformance statement (if possible)
  • check search results to confirm which ones were processed (if it matters)

A server should:

  • ignore parameters it doesn’t recognise
  • return HTTP errors where parameters it does recognise are inapplicable or have invalid content, or where it cannot conform to the requested behaviour

ValueSet Variance

The things above really deal with syntactical variance. Postel’s Principle is relatively easy to apply in this way. It’s much harder to apply when the underlying business process vary. Typical examples include:

  • exchanging data between 2 business process that use different fields (e.g. they care about different things)
  • exchanging data between 2 business processes that use text/structured data differently (e.g. structured dosage vs a single text ‘dosage instructions’ field)
  •  exchanging data between systems that use different value sets

To grapple with these issues, I’m going to work with the last example; it’s the easiest to understand and apply, though the basic principles apply to the others as well. In this case, we have 2 applications exchanging data between them, and they support different sets of codes. There’s a few different possibilities:

  • A sends B a code it doesn’t know
  • A sends B a code for something which is different to the one B uses
  • Either of those 3 cases, but B edits the record, and returns it to A

The way the CodeableConcept data type works is intimately linked to practical resolutions to these common cases. In order to support these cases, it has a text representation, and 0 or more Codings:

In HL7’s experience, Postel’s Principle, as applied to the exchange of coded information, says that

  • The source of the information should provide text, and all the codes they know
  • The text should be a full representation of the concept for a human reader
  • It is understood that the codings may represent the concept with variable levels of completeness e.g. the Concept might be ‘severe headache’, but the coding omits ‘severe’ and just represents ‘headache’

Note: there’s a wide variety of workflows that lead to the choice of a concept, and the process for selecting the text and the multiple codings varies accordingly. Since the subtle details of the process are not represented, the most important criteria for the correct choice of text is ‘does a receiver needs to know how the data was collected to understand the text’

  • a receiver of information should retain the text, and all the provided codes
  • When displaying the information to a user, the text is always what should be shown, and the formal codings may be shown additionally (e.g. in a hint, or a secondary data widget)
  • Decision support may choose one of the codes, but the user should always have a path back to view the text when (e.g.) approving decision support recommendations
  • When sending information on, a receiver should always send the original text and codes, even if it adds additional codes of it’s own
  • When a user or process changes the code to another value, all the existing codes should be replaced, and the text should be updated

Note: this implies that there’s a process difference between ‘adding another code for the same concept’ and ‘changing the concept’ and this change should be reflected in the information level APIs and surfaced in the workflow explicitly. But if there’s no difference…

  • if a system receives an update to a coded element (from UI or another system) that contains a different text, and codings, but at least one of the codings is the same, then this should be interpreted as ‘update the existing concept’. The text should be replaced and the codings merged

Many, if not most, systems, do not follow this advice, and these often have workflow consequences. Note, though, that we’re not saying that this is the only way to manage this; more specific workflows are appropriate where more specific trading partnership details can be agreed. But the rules above are a great place to start from, and to use in the general case.

Beyond this general advice, specific advice can be provided for particular contexts. Here, for instance, is a set of recommendations for RxNorm:

  1. Don’t throw away codes (as suggested above). The system originating data needs to expose RxNorm codes, but has good reason to include any “real” underlying codes it used, if different (e.g. FDB). And downstream proxies, CDRs, EWDs, interface engines, etc. shouldn’t remove codes. FHIR has a way to indicate which was the “primary” code corresponding to what a clinician saw.
  2. Senders should expose valid RxNorm codes at the level of SCD, SBD, GPCK, or BPCK prescribables, not ingredients or dose forms. Namely, these codes should appear in RxNorm and mean the thing you want to say. It’s possible they may not be in the current “prescribable” list at the time you generate a data payload (e.g. for historical records), but active prescriptions should be. Furthermore, the conservative practice is to always use an up-to-date release of RxNorm. (And by RxNorm’s design, any codes that were once valid should be present in all future versions of RxNorm, even if obsolete.) These codes might not be marked “primary” in FHIR’s sense of the word
  3. Recipients should use the most recent version of RxNorm available, and should look through all codings in a received medication to find an RxNorm SCD, SBD, GPCK, or BPCK. If you find such a code and don’t “understand” it, that’s a client-internal issue and it should be escalated/handled locally. If you *don’t* find such a code, that’s a potential data quality issue; clients should log a warning and use any code they understand, or display the text value(s) to a user.

There’s a whole art form around version management of terminologies. I’ll take that up in a later post.

Dealing with People

One last comment about Postel’s principle: Interoperability is all about the people, and the same principle applies. If you want to interoperate, you need to get on with people, and that means that you need to use Postel’s principle:

Be generous with what other people say, be disciplined with what you say

A community of people who want to interoperate with others – I’d like to be part of that. But no, I already am! The FHIR community has been very good at this over the last few years.

 

p.s. this post dealt with the RESTful interface, but the basic principles apply in other contexts of use as well.

 

A Path expression language for #FHIR

Since the beginning of working with FHIR, we’ve had a number of contexts in the specification where we’ve need to refer to a particular element within a resource, or make rules on the properties of the elements in a resource. Over time, that list has grown, and within the specification we now have the following places for this:

  • search parameter paths – used to define what contents the parameter refers to
  • invariants in ElementDefinition, used to apply co-occurance and other rules to the contents
  • slicing discriminator – used to indicate what element(s) define uniqueness
  • error message locations in OperationOutcome

In addition, Smart on FHIR’s cds-hooks has a template approach which really needs something like this, and we have an identified need for patch which needs something like this. In addition, implementations may find other uses for this as well – I know I have several places where I need to refer to content in a resource dynamically. One really important context is a formal mapping specifications

Up to now, we’ve used a mix of XPath and informal dotted notations. Feedback from implementers is that this is a barrier to adoption, and we need a better approach. XPath is a problem because:

  • XPath is XML specific
  • We’ve used XPath2 features in our XPaths, and XPath2 is very poorly supported to the point
  • XPath is overkill for the simply use cases, and too hard for the complex use cases

As for JSON, there’s a raft of variations around “XPath for JSON”, but none of them seem to be on any standards path. And what we really need is a single language that works for both XML and JSON, and is easy to use for simple cases, and powerful enough for the difficult cases. It become clear, working with implementers after the publication of DSTU2, that it was time to address this. And it was clear that we need to define our own language for this (this is contentious – surely, people say, there is already something out there for this, so we waited for 3 years before acting on this, but eventually we couldn’t wait).

So we’ve defined a little expression language for these purposes. Several of us have implemented processing engines for it, to validate the approach. It’s not part of the formal specification right now, but it’s certainly something we expect to make part of it.

Useful Links:

Note: we’ve not formalised any of this yet – including the name “FHIRPath”. Discussion and suggestions on names, syntaxes, and approaches is all welcome