Convert your FHIR JSON -> XML and back here. The CDA Book is sometimes listed for Kindle here and it is also SHIPPING from Amazon! See here for Errata.

Wednesday, March 21, 2012

Tricky testing bits in CDA Consolidation

My Grandmother was a fanatic about cleaning.  One day while she was visiting our house, she decided to clean the kitchen.  As she was doing so, she noted that doors in it that hid our heater and air conditioning system were dirty, so she decided they needed cleaning too (the system drew air and therefore lint into the slats).  She dug around beneath the sink and found a brush that would go between the slats, but before she used it, she decided it needed cleaning too.  When I first heard my mom tell this story, I laughed, but today I find myself in a similar situation, in this case, in my own fanaticism about testing and implementation of the CDA Consolidation Guide.

I really want to write a post on moving from the HITSP C32 (version 2.5) to the CDA Consolidation guide.  Before I could do that, I had to make sense of what a Summary Care Record was in the meaningful use regulations.  The next step in my process is to take a valid C32 and migrate it to become a valid CCD 1.1 according to the Consolidation guide.  But, before I can do that, I need to be able to validate that the CCD 1.1 that I create is in fact correct.  I need a Schematron, but since none was delivered with the Consolidated Guide, I have to build one.  I could do it by hand, but I’d much rather automate that process.

Where it gets tricky
So, I’ve been working on a Schematron Generator for the Trifolia Database.  One of the problems that I’ve run across is how to deal with constraints that suggest that a particular data element having certain characteristics exists in the document.  The pattern associated with this goes something like this:
count(dataElement[SHALL(this) and SHOULD(that) and MAY(theOtherThing)] = 1

A simple example of this is CONF:9477 which reads:
1. SHALL contain exactly one [1..1] templateId (CONF:9477) such that it 
   a. SHALL contain exactly one [1..1] @root="2.16.840.1.113883." (CONF:10039)

This translates into:
<sch:assert flag="SHALL" id="CONF-9477-branch"
        test="count(cda:templateId[ @root='2.16.840.1.113883.'])=1">

And that is exactly what I want in that case.  But where it gets challenging is when you have not just SHALL, but also SHOULD and/or MAY constraints associated with the “such that it” clause.  An example of that is in CONF:8662

8. SHOULD contain at least one [1..*] participant (CONF:8662) such that it
a. SHALL contain exactly one [1..1] @typeCode="VRF" Verifier (CodeSystem: HL7ParticipationType 2.16.840.1.113883.5.90) (CONF:8663).
b. SHALL contain exactly one [1..1] templateId (CONF:8664) such that it
i. SHALL contain exactly one [1..1] @root="2.16.840.1.113883." (CONF:10486).
c. SHOULD contain zero or one [0..1] time (CONF:8665).
i. The data type of Observation/participant/time in a verification SHALL be TS (time stamp) (CONF:8666).
d. SHALL contain exactly one [1..1] participantRole (CONF:8825).

In that case, when the SHOULD rules are firing in the context of the Observation (where this constraint appears), what I’m generating right now is:

<sch:assert flag="SHOULD" id="CONF-8662-branch"
 test="count(cda:participant[@typeCode='VRF' and
 and count(cda:time)&lt;=1 and

The parts in bold represent things that must be there, and italic, things that SHOULD be there.
What I need to do with this assertion is break it into two parts.  The first part executes in the context of the observation and only looks for shall requirements:

<sch:assert flag="SHOULD" id="CONF-8662-branch"
 test="count(cda:participant[@typeCode='VRF' and
 and count(cda:participantRole)=1])&lt;=1">

The second part executes in the context of the observation/participant[@typeCode='VRF' and count(cda:templateId[@root='2.16.840.1.113883.'])=1 and count(cda:participantRole)=1] and only looks for SHOULD requirements:
What it would say is:

<sch:assert flag="SHOULD" id="CONF-8662-branch" test="count(cda:time)&lt;=1">

Cleaning out the Lint
What becomes really challenging is reporting on cda:participant elements which are intended to meet the criteria SHALL/SHOULD internal criteria, but don’t for some reason.  From a testing perspective, this is a LINT style check (you did this, is that what you intended).  You have something that might qualify (the participant), and you want to test to see if it does.  These sort of tests prevent errors, but violating the test criteria (not really a constraint) might have been intentional (as in cases where you intentionally write a for loop with an empty body).

For example, in the context of an observation, report on participants that don't meet the shall sub-criteria (see 8.a through 8.d above), in case those participant elements were intended to.

It’s going to take me some more time to think this part through.  I need to get the SHALL/SHOULD/MAY tests working right.  I won't focus a lot of attention on LINT style (did you really mean to do that) checks right now, because I too need to ship.

  -- Keith

P.S.  If you were reading carefully, you probably noted that count(cda:time)&lt;=1 isn't all that useful.  What it needs to do for SHOULD rules is raise the lower bound from 0 to 1, which will result in  count(cda:time)=1 which will do what we want.  I have a fix for that that I'll address when I refactor again.

P.P.S.  The rules around participant should be reconstructed to just focus on the required templateId,

8. SHOULD contain at least one [1..*] participant (CONF:8662) such that it
a. SHALL contain exactly one [1..1] templateId (CONF:8664) such that it
i. SHALL contain exactly one [1..1] @root="2.16.840.1.113883." (CONF:10486).

Then there should be other rules based on that templateId.  That would resolve the problem for THIS case, but there are possibly other cases that look like this that wouldn't be addressed.