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

 

6 Comments

  1. Peter Jordan says:

    Hi Grahame. I’m (finally) having a crack at implementing this and would appreciate clarification on a couple of issues. Firstly, when you note that “servers may return the relationship represented in either direction”, what is passed to the server that indicates the direction to take in any given request? Secondly, if a single SNOMED CT code of “22298006” is posted as an addition, and the source, would you expect the response to contain a ConceptMap Resource including all the concepts that subsume this code, all the way down to the SNOMED CT root concept?

    • Grahame Grieve says:

      nothing is passed to the server about direction. The client just asks the server to add a new concept to the closure table. For the second, you would only expect the response to contain relationships to other concepts already added to the closure table.

  2. Michael Lawley says:

    Having just done a first-pass of implementing $closure in Ontoserver, I’m surprised that the response to the initialisation would be a Parameters resource rather than just an empty ConceptMap.

    It seems a lot cleaner an API if there are only two kinds of result types: ConceptMap on success and OperationOutcome on failure.

    Regarding the special version of “0” – does “resyncing the entire closure table” mean sending the entire contents?

    • Grahame Grieve says:

      * first response – yes, that’s a good point
      * resync – yes, send the entire contents
      * do you want to add support for initialising the closure table by referencing a value set?

  3. Charlie Harp says:

    I am really trying to understand the value of this and why this would be maintained on the server. Is there someplace that has more background and documentation on use cases for this?

Leave a Reply

Your email address will not be published. Required fields are marked *

question razz sad evil exclaim smile redface biggrin surprised eek confused cool lol mad twisted rolleyes wink idea arrow neutral cry mrgreen

*

%d bloggers like this: