FHIR conformance and cardinality
As of v0.02, FHIR has the following fields that describe the rules around whether an element can or must be present or not:
- Conformance: Mandatory | Required | Conditional | Optional | Prohibited.
- condition field - must be present if Conformance is conditional
The interplay between these fields creates some interesting dynamics, and there's things you want to say that you can't, things you can say that don't make sense, and things that aren't clear. This page considers how to improve this.
Note that there's three separate issues at play here: how resources and profiles are authored, how they are presented, and how they are properly understood. Ideally, the last two should be the same, but not all presentation formats are capable of that. XML schema, for instance, knows only cardinality.
For authoring a resource, the only allowed min cardinality is 0 or 1, and the only allowed max cardinality is 1 or *. Other values are not allowed (build fails). This is equivalent to
- must you have an element present
- are you allowed to have more than one element present (i.e. implementations use some kind of list for the element)
In a profile, you can specify any combination of cardinalities, as long as min <= max, and min >= 0.
Cardinality is authored in the spreadsheets by entering the M..N values directly into the column entitled "Cardinality"
Conformance is edited in the spreadsheet by entering a code into the "Conformance" column. The following codes are allowed:
- Unstated - this element doesn't need to have a conformance flag on it. This means that the element is there fore purely structural reasons, has no inherent meaning, and is optional
- Mandatory - if this element is present, it may not have a dataAbsentReason on it (it's quite common to have min cardinality = 0, but to be mandatory)
- Conditional - the element has a minimum cardinality of 0. If the condition is true, then it must be present (or at least one must be present), and it cannot have a dataAbsentReason on it
- Optional - if the element is present, it may have a dataAbsentReason on it
- Prohibited - (only in a profile) if the element is present, it must have a dataAbsentReason on it. (when would you use this? -when the min cardinality of the content you are profiling is 1, and it is optional, and you don't want to use it. This is an edge case. - May be appropriate to remove prohibited?)
If the conformance type is conditional, then this is a human (english only in resources themselves) expression of the rules associated with the conditionality.
Enter your problems with this content here.
- should deprecate prohibited? the case it represents is really very edge case
- do you ever want conditional to only refer to the cardinality, and not the dataAbsentReason as well?
- mandatory has an overlaid meaning. Perhaps better names would be: Unstated | NoAbsent | MaybeAbsent | MustBeAbsent?
- conditional is different - it applies to the cardinality as well as the dataAbsentReason. Should drop it, and be more explicit?
- related problem: is there mileage in being explicit about co-occurent constraints and model choice directly?
- gg: no? So far, the cases in resources haven't often been as simple as (a xor b) - they've been more complicated.
- gg: todo - gather a list of observed cases for conditional so far
- I don't think we should allow "conformance unstated". For one thing, the behavior will be different in resource definitions vs. profiles. In profiles, when you leave something out, it remains the same. In profiles, it would become optional. I'd also question the situation where we should have something that's structural with no meaning in a resource. Can you give examples?
- GG: the root element of a data type - it doesn't have it's own conformance. The root element of resources - they're mandatory?
- LM: I think root element of a datatype or a resource is determined by the context of use. It's not a characteristic of the thing itself. I.e. Conformance (and cardinality) have no meaning for root elements.
- Conditional is useful as a flag. It's important to know that there are elements that may have tighter conformance expectations than they first appear. However, it's orthogonal with the other cardinalities. An element could have a base conformance of "Required" or even "Mandatory" and still have a conditional flag on it. Therefore, suggest adding this as a separate flag on the element. Ideally we could derive this by parsing the xPaths, but for now, manual maintenance is most appropriate.
- Conditions should be melded into our general idea of Invariants. There's no difference between them. The things you'd want as a condition are a subset of the invariants and we don't want to provide two different ways to say the same thing.
- GG: I'm not sure that I agree. It's different perspectives, different frames of reference
- LM: You could have a condition that fixed a value (and in doing so makes it minimum cardinality 1, mandatory). You could have all sorts of different co-occurrence constraints (and, or, xor, nand). And the focus of most of those constraints is not one element. It's a collection of elements. Just make the whole procss generic.
- Conditions as only text is not terribly useful. Conditions should be capture formally so they can be tested as much as possible, though obviously some conditions may not be testable in a given formal language and some authors may not be capable of using the chosen language. So capture constraints as XPath + human readable text, where the text is a positive assertion of the condition. That will allow us to create Schematron (and maybe eventually Schema 1.1) rules.
- Conditions/Invariants should be captured on the element in whose context they apply. This is generally the common parent of all nodes influenced by the constraint. So if you have a constraint that at least one of Foo and Bar must be present, the constraint lives on the parent of Foo and Bar, not on the Foo or Bar elements themselves (though the Conditional flag would of course be set on Foo and Bar)
- We should think about what the rules are for how deep conditions can be asserted. I think conditions should only be allowed to hold within your current resource, not across profiles. However, I can see some situations where constraint across resources might be necessary. Perhaps require that if you want to enforce a constraint on another resource, it's done by asserting a profile on that resource, not reaching inside the resource? That simplifies maintenance. It also avoids worrying about setting the Conditional flag on content where the condition is declared in a different resource from the element.
- GG: yes, that's how it's done.
- I think attempting to model out a subset of formal constraints has limited value. We support choices of types within an element. Choices of types across elements gets very ugly very quickly and also places constraints on order of appearance. I think just using constraints is more appropriate.
- In v3, we've always asserted that isMandatory="true" implies that conformance="Required" and minimumCardinality >= 1. You're breaking that here. You're asserting that "dataAbsentReason" can be constrained away in circumstances where minimumCardinality is still 0. I'd like to assert that it's actually orthogonal. There are elements where dataAbsentReason is unnecessary overhead. And the cardinality and conformance expectations for those elements could be pretty much anything.
- GG: yes, that's right. I used mandatory in that orthogonal sense
- LM: So if it's orthogonal, split it.
- The name is poor. First, it doesn't have the same semantic as "mandatory" in v3, which will confuse people. Second, most people new to HL7 mentally interpret mandatory as the same as required, because that's what it means in standard english. Suggest being explicit and saying something like "darProhibited". Obviously this will require someone to look at the definition to understand that "dar" is the abbreviation for "dataAbsentReason", but that's a quick thing. And once done, it's intuitive.
- GG: agree about confusion
- There are situations where a given implementer *cannot* accept data they weren't expecting. Perhaps there's a regulatory need to share and expose whatever they're given or something. So the solution is to reject instances that contain data they don't support. We need to have a way to expose this behavior in FHIR, even if it's problematic from an interoperability perspective. (See FHIR Conformance Levels Issue Page)
- GG: there already is a way, in an application conformance statement - an element that does not conform to the profile is unknown, and they can declare whether they accept or reject it
- LM: And how do you eliminate an element that's in a resource? You set max cardinality to 0 or you mark it as prohibited.
- Prohibited should mean "I'm going to raise an error if you send me the element at all". Schema equivalent to maxOccurs="0"
- GG: but this is *not* equivalent. It's orthoginal, as you stated above.
- LM: I can't see any circumstance where Prohibited (as you've defined it) makes sense. I can see situations where you might fix a value to a particular dataAbsentReason, but not where you'd say "any dataAbsentReason is fine, but don't send real data".
- LM: BTW, how do we "fix" a dataAbsentReason?
- This qualifies in the 80% because every implementer needs to know how to handle this situation, even if (hopefully) very few implementers declare profiles that use it.
- V2 and V3 also have the concept of "Required for Sender" and "Required for Receiver". These are used when creating a resource definition or a profile intended to define conformance from both a sending and a receiving perspective. In some cases you want to say "You're non-conformant if you aren't capable of sending this element, but it's ok for receivers to ignore"; or "It's ok if you don't support sending this element, but receivers must be capable of processing it if sent".
- overlap with must-support and must-understand
- These are definitely edge cases, but again they probably fall in the 80% because implementers need to know what to do if they encounter these concepts in the wild.
Grahame: All right, what if we do this:
- get rid of conformance field
- get rid of the conditionality field
- keep cardinality (Constrained to 0..0, 0..1, 0..*, 1..1, 1..* in resource definitions)
- add a condition applies flag, which references one of more conditions that apply
- add a dataAbsentReason allowed flag, true and false
- more work is needed on controlling dataAbsentReason. Rather than (or maybe in addition to) just turning it on and off, there's a need to constrain the value set. GG: deferred for later
- also, there's must understand, and must support (do we need sender and receiver or just one). - and what does must support mean? Do we need further details of what must be supported? (for proposed definitions of these, see FHIR Profile Page)
- LM +1
- GG: +1
- JD: +1 - with the caveat that we have to clearly define what understand means and what support means. There is an email thread I participated on a year ago that showed the confusion that still exists in v3 around those terms.
- EK: +1
- RI: +1
Issue closed: Change made to FHIR source - Grahame - 1st June.