Skip to content

SD's expressing constraints

chrisgrenz edited this page Mar 4, 2016 · 10 revisions

[Some intro text]

  • Stuff about three types of ElementDefinitions
    • Root (or first)
    • Slice intro
    • Normal ElementDefinition

TODO: Split the description into two: 1) merging a differential to a base 2) what are correct constraints on a given base.

TODO: Merge this text: http://hl7.org/fhir/profiling.html#resources

Differential and Snapshot representations

Structure Definitions may contain a differential statement, a snapshot statement or both.

Differential statements describe only the differences that they make relative to another structure definition (which is most often the base FHIR resource or data type). For example, a profile may make a single element mandatory (cardinality 1..1). In the case of a differential structure, it will contain a single element with the path of the element being made mandatory, and a cardinality statement. Nothing else is stated - all the rest of the structural information is implied (note: this means that a differential profile can be sparse and only mention the elements that are changed, without having to list the full structure).

Note that a differential can choose not to constrain elements. Doing so means that the profile will be more flexible in terms of compatibility with other profiles, but will require more work to support from implementing systems. Alternatively, a profile can constrain all optional elements to be not present (max cardinality = 0) - this closes the content, which makes implementation easier, but the reduces its usefulness.

Conceptually, we can define a merge operation that takes the base and applies the changes in the differential to generate the snapshot:

snapshot = merge(core definition,differential)

If a StructureDefinitions puts further constraints on another StructureDefinition, this process will repeat:

snapshot = merge(merge(merge(core definition,diff1),diff2),diff3)

In order to save tools from needing to support this (repeated) operation (which is computationally intensive - and impossible if the base structure is not available), a StructureDefinition can also carry a "snapshot" - a fully calculated form of the structure that is not dependent on any other structure.

StructureDefinitions can contain both a differential and a snapshot view. In fact, this is the most useful form - the differential form serves the authoring process, while the snapshot serves the implementation tooling. StructureDefinition resources used in operational systems should always have the snapshot view populated.

The next table lays out the details of how a differential is applied to the base to generate a snapshot representation (note that all elements are optional in a differential):

representation Prohibited. This element cannot be changed in a constraint.
name TODO ?Can override a name, introduce a new name for each slice?
label Overrides the value in base.
code Codes in the differential are added to the base (no doubles - btw what's the equality check here?)†.
slicing Discussed in the section on slicing below
short Overrides the value in base
definition Overrides the value in the base, unless the text in the differential starts with three periods (`...`), in which case the text in the differential is added to the text in the base.
comments Overrides the value in the base, unless the text in the differential starts with three periods (`...`), in which case the text in the differential is added to the text in the base.
requirements Overrides the value in the base, unless the text in the differential starts with three periods (`...`), in which case the text in the differential is added to the text in the base.
alias Aliases in the differential will be added to the base (no doubles)†.
min Overrides the value in base, but it must be equal to or higher than the value in base.
max Overrides the value in base, but it must be equal to or lower than than the value in base.
base Prohibited. In the snapshot, this will be a copy of the `base` element in the snapshot representation of the base StructureDefinition. See below for more information about `base`.
type Replaces the value in base. But see the section on merging types below
nameReference Prohibited. This element cannot be changed in a constraint."
defaultValue[x] Prohibited. This element cannot be changed in a constraint.
meaningWhenMissing Prohibited. This element cannot be changed in a constraint.
fixed Overrides the value in base.
pattern Overrides the value in base.
example Overrides the value in base.
minValue[x] Overrides the value in base, can only be used on ordered types and must be larger than the minValue in base (if any)
maxValue[x] Overrides the value in base, can only be used on ordered types and and must be smaller than the minValue in base (if any)
maxLength Overrides the value in base, but must be equal or smaller than the value in the base.
condition TODO
constraint Constraints in the differential will be added to the base†.
mustSupport Overrides the value in base.
isModifier Prohibited (only allowed in extension definitions)
isSummary Overrides the value in base.
binding Binding can only be present for coded elements, string, and uri. See below for the section on merging bindings.
mapping Mappings in the differential will be added to base, when the base has no mapping for the given mapping target (ElementDefinition.mapping.identity)†

† Note that this means the differential cannot remove information from this element, only add to it.

Element Scenarios

To determine the merged element definition, the snapshot generator must determine the element to be "refined" or merged with the local differential definition.

Elements have one or more of the following dispositions:

  • New element - no base. Applies only in HL7 defined StructureDefinitions (profiles may not introduce new elements). Refines the type of the element.
  • Constrained element - matching path/name in a base SD. An element may refine a non-sliced or sliced element from the base. Refines the matching element from the base.
  • New slice - a newly defined named slice. Refines the matching path from the base SD (?? not the local unsliced element ??).
  • Re-slice - a newly defined slice of an existing slice. The "parent" slice may be local or in the base SD. Refines the parent slice??
  • Type profiled element - an element of any of the above dispositions that include a type/profile definition. In this case, the element refines ??

More on base

ElementDefinition.base contains information about the core definition of the element, provided to make it unncessary for tools to trace the deviation of the element through the derived and related profile back to the original base definition. When processing a differential into a snapshot, this data is copied from the base definition, so that it is copied from the core into each successive derived StructureDefinition.

Using this information tools can determine whether an element is repeating, even if the cardinality is constrained to 0..1 or 1..1 (this is still relevant in the serialization for example). The same holds when choice elements have their type choices constrained down to a single type, the path in the base will still end in '[x]' to signify this.

Merging the type element

Both the base and the differential can have one or more type elements. The list of types in the differential will replace the ones in the base, but must be a correct subset of those in the base (otherwise stated, they must be as least as specific as)
The (list of) type element(s) in the differential have to be a subset of the type(s) specified in the base. Determining whether this is the case is described using the following pseudo code, which involves both type.code and type.profile (if any), represented by the pair type(code,profile):

Given a list of types in the differential, verify for each type that it is a valid constraint for at least one type in the base.

Valid type constraints are:

  • type(Resource) may be constrained to type(R), where R is any (abstract) resource type.

  • type(DomainResource) may be constrained to type(R), where R is a domain resource (Patient, Procedure, etc).

  • type(Element) may be constrained to type(T), where T is any primitive or unconstrained complex datatype (so all types except Age, Money etc).

  • type(T;p) is a valid type constraint for a type(T)

  • type(T;p) is a valid type constraint for a type(T;p)

  • type(T;p') is a valid type constraint for a type(T,p) if StructureDefinition p' is a valid constraint on p.

  • type(T;p',q') is a valid type constraint for a type(T,p) if StructureDefinition p' is a valid constraint on p and also StructureDefinition q' is a valid constraint on p.

  • In general: for each profile p in the base type list definition, there must exist at least one valid constrained profile p' in the derived type list definition.

Notes:

  • Determining whether a StructureDefinition p is a constraint on another StructureDefinition q could done either by verifying that p is in the chain of ancestors of q as expressed by base, or we could compute whether the constraints in p are equal to or stricter than the constraints in q. The specification is currently unclear which of the two approaches is desired.
  • These rules also implicitly cover handling Reference types, where Reference(Any) (which is in fact a type(Reference;http://fhir.org/StructureDefinition/Resource) can be constrained to, say, Reference type(Reference;http://fhir.org/StructureDefinition/Patient) and type(Reference,p,q) can be constrained to type(Reference,p)

Merging the binding element

Binding strength (binding.strength) in a differential has to be equal or stronger than the binding strength in the base. These are the binding strengths, in order of strength:

required > extensible > preferred > example.

The reference to the valueset (binding.valueSet[x] changes) can also be changed, according to the following rules:

  • If the strength is required, the new valueset must be a subset of the base's valueset
  • If the strength is extensible, the new valueset need not be a subset (although it may not contain codes for concepts that are already in base - but this cannot be determined algorithmically)
  • For other strengths, there are no rules.

Handling slices

Slices are described in the specification on the Profiling page. Things to note from this description are:

  • Slices are initated by a "slicing entry". (...) It also contains the unconstrained definition of the element that is sliced, potentially including children of the unconstrained element, if there are any
  • Slicing can be used in any element that repeats (cardinality has a max > 1), or any element with a choice of types.

In a given instance of a StructureDefinition this looks like this (in snapshot form):

<element>   <!-- slicing entry -->
	<path value="DiagnosticReport.result"/>
	<slicing>
  		<discriminator value="reference.code"/>
  		<ordered value="true"/>
		<rules value="closed"/>
	</slicing>
	<!-- some elements left out -->
	<min value="0"/>
	<max value="*"/>
	<base>
  		<path value="DiagnosticReport.result"/>
  		<min value="0"/>
  		<max value="*"/>
	</base>
	<type>
		<code value="Reference"/>
		<profile value="http://hl7.org/fhir/StructureDefinition/Observation"/>
	</type>
	<!-- other elements from the original definition -->
</element>
<element>
	<path value="DiagnosticReport.result"/>
	<name value="Cholesterol"/>
	<short value="Cholesterol Result"/>
	<!-- more elements -->
	<min value="1"/>
	<max value="1"/>
	<base>
  		<path value="DiagnosticReport.result"/>
  		<min value="0"/>
  		<max value="*"/>
	</base>
	<type>
  		<code value="Reference"/>
  		<profile value="http://hl7.org/fhir/StructureDefinition/cholesterol"/>
	</type>
	<!-- other elements from the original definition -->
</element>

Notes:

  • The slicing entry has an additional element slicing, but otherwise is the same as the element that is being sliced in the base, so this contains the "original" definitions. If the sliced element is a BackboneElement, the sliced element contains child elements, and these will appear before the first slice and after the slice entry.
  • Both slicing entry and slices contain a base element with the original, unconstrained cardinality from the element in the FHIR core definition for the type of resource.
  • The cardinality (min and max) of slice entry may be narrowed to reflect the cardinality constraints in the slices. E.g. if you have three slices with a max cardinality of '1', the max in the slicing entry could be set to '3' (but may be left at the original value).

A differential may chose to skip the slicing entry, and put the slicing element on the first slice instead:

<element>  <!-- first slice, with no preceding slicing entry -->
	<path value="DiagnosticReport.result"/>
	<name value="Cholesterol"/>
	<slicing>
	  <discriminator value="reference.code"/>
	  <ordered value="true"/>
	  <rules value="closed"/>
	</slicing>
	<short value="Cholesterol Result"/>
	<definition value="Reference to Cholesterol Result."/>
	<min value="1"/>
	<max value="1"/>
	<type>
	  <code value="Reference"/>
	  <profile value="http://hl7.org/fhir/StructureDefinition/cholesterol"/>
	</type>
	<!-- etcerera -->
</element>

In fact, when slicing the extension element of a Resource, even the slicing element may be left out, which defaults to:

<slicing>
	<discriminator value="url"/>
	<ordered value="false"/>
	<rules value="open"/>
</slicing>
 <element>
            <path value="Observation.valueQuantity.id"/>
            <representation value="xmlAttr"/>
            <short value="xml:id (or equivalent in JSON)"/>
            <definition value="unique id for the element within a resource (for internal references)."/>
            <min value="0"/>
            <max value="1"/>
            <base>
             ** <path value="Quantity.id"/>**   <!-- Quantity.id, not the path within Observation! -->
              <min value="0"/>
              <max value="1"/>
            </base>
            <type>

Discriminator @type, @Profile, type@profile??

Differential need not repeat

Function of on root element in differential?

valueXXXX may only appear once (for each type? really once?)

What is extension structuredefinition-display-hint?

Why not expand valueRange to a full type-slice in the snaphot? Because you can still do it even if you do not implement slicing?

Differentials on constrained primitives must be rooted in the base primitive, differentials on constraints on Quantity must be rooted in Quantity.

May diffs be sparse trees?

Equivalence between short-hand type slice and real type slice (also for comparing valid constraints)