-
Notifications
You must be signed in to change notification settings - Fork 0
SD's expressing constraints
[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
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.
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 ??
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.
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)
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.
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 aBackboneElement
, 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
andmax
) 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', themax
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)