From 8cfcbf389e21619623fe84cc72d5fd021be415fe Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sat, 21 Sep 2024 00:35:01 -0400 Subject: [PATCH] Fix various issues rendering and validating additional bindings with usage contexts --- .../special40_50/ElementDefinition40_50.java | 13 +- .../conformance/profile/ProfileUtilities.java | 2 +- .../renderers/AdditionalBindingsRenderer.java | 25 +- .../hl7/fhir/r5/renderers/DataRenderer.java | 21 +- .../fhir/utilities/i18n/I18nConstants.java | 7 +- .../utilities/i18n/RenderingI18nContext.java | 1 + .../src/main/resources/Messages.properties | 7 +- .../resources/rendering-phrases.properties | 3 +- .../instance/InstanceValidator.java | 272 ++++++++++++++---- .../type/StructureDefinitionValidator.java | 117 +++++++- ...nitionadditional-bindings-profile-cs.cache | 42 +++ ...ystemSDOHCC-CodeSystemTemporaryCodes.cache | 83 ++++++ 12 files changed, 519 insertions(+), 74 deletions(-) create mode 100644 org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/testStructureDefinitionadditional-bindings-profile-cs.cache create mode 100644 org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ussdoh-clinicalcareCodeSystemSDOHCC-CodeSystemTemporaryCodes.cache diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/datatypes40_50/special40_50/ElementDefinition40_50.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/datatypes40_50/special40_50/ElementDefinition40_50.java index f51e9f55f..8eb57d554 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/datatypes40_50/special40_50/ElementDefinition40_50.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/conv40_50/datatypes40_50/special40_50/ElementDefinition40_50.java @@ -25,8 +25,10 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; import org.hl7.fhir.r5.model.UsageContext; +import org.hl7.fhir.r5.utils.ToolingExtensions; public class ElementDefinition40_50 { + public static org.hl7.fhir.r5.model.ElementDefinition convertElementDefinition(org.hl7.fhir.r4.model.ElementDefinition src) throws FHIRException { if (src == null) return null; org.hl7.fhir.r5.model.ElementDefinition tgt = new org.hl7.fhir.r5.model.ElementDefinition(); @@ -616,7 +618,7 @@ public class ElementDefinition40_50 { if (src == null) return null; org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent tgt = new org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent(); ConversionContext40_50.INSTANCE.getVersionConvertor_40_50().copyElement(src, tgt, - "http://hl7.org/fhir/5.0/StructureDefinition/extension-ElementDefinition.binding.additional"); + "http://hl7.org/fhir/5.0/StructureDefinition/extension-ElementDefinition.binding.additional", ToolingExtensions.EXT_BINDING_ADDITIONAL); if (src.hasStrength()) tgt.setStrengthElement(Enumerations40_50.convertBindingStrength(src.getStrengthElement())); if (src.hasDescription()) tgt.setDescriptionElement(String40_50.convertStringToMarkdown(src.getDescriptionElement())); if (src.hasValueSet()) tgt.setValueSetElement(Canonical40_50.convertCanonical(src.getValueSetElement())); @@ -624,6 +626,9 @@ public class ElementDefinition40_50 { for (org.hl7.fhir.r4.model.Extension ext : src.getExtensionsByUrl("http://hl7.org/fhir/5.0/StructureDefinition/extension-ElementDefinition.binding.additional")) { tgt.addAdditional(convertAdditional(ext)); } + for (org.hl7.fhir.r4.model.Extension ext : src.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { + tgt.addAdditional(convertAdditional(ext)); + } return tgt; } @@ -652,12 +657,12 @@ public class ElementDefinition40_50 { return tgt; } - private static Extension convertAdditional(ElementDefinitionBindingAdditionalComponent src) { + private static org.hl7.fhir.r4.model.Extension convertAdditional(ElementDefinitionBindingAdditionalComponent src) { if (src == null) return null; - Extension tgt = new Extension(); + org.hl7.fhir.r4.model.Extension tgt = new Extension(ToolingExtensions.EXT_BINDING_ADDITIONAL); ConversionContext40_50.INSTANCE.getVersionConvertor_40_50().copyElement(src, tgt); if (src.hasPurpose()) { - tgt.addExtension(new Extension("purpose", new CodeType(src.getPurposeElement().primitiveValue()))); + tgt.addExtension(new Extension("purpose", new org.hl7.fhir.r4.model.CodeType(src.getPurposeElement().primitiveValue()))); } if (src.hasValueSet()) { tgt.addExtension(new Extension("valueSet", Canonical40_50.convertCanonical(src.getValueSetElement()))); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java index bc8d243a2..e8f3441b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/profile/ProfileUtilities.java @@ -2923,7 +2923,7 @@ public class ProfileUtilities { private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) { for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) { - if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose()) { + if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose() && !ab.hasUsage()) { return t; } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java index a80990c4c..519489942 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/AdditionalBindingsRenderer.java @@ -36,7 +36,7 @@ public class AdditionalBindingsRenderer { private String valueSet; private String doco; private String docoShort; - private UsageContext usage; + private List usages = new ArrayList(); private boolean any = false; private boolean isUnchanged = false; private boolean matched = false; @@ -72,7 +72,7 @@ public class AdditionalBindingsRenderer { isUnchanged = isUnchanged && ((valueSet==null && compare.valueSet==null) || valueSet.equals(compare.valueSet)); isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco)); isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort)); - isUnchanged = isUnchanged && ((usage==null && compare.usage==null) || usage.equals(compare.usage)); + isUnchanged = isUnchanged && ((usages==null && compare.usages==null) || usages.equals(compare.usages)); return isUnchanged; } } @@ -174,8 +174,12 @@ public class AdditionalBindingsRenderer { abr.purpose = ext.getExtensionString("purpose"); abr.valueSet = ext.getExtensionString("valueSet"); abr.doco = ext.getExtensionString("documentation"); - abr.docoShort = ext.getExtensionString("shortDoco"); - abr.usage = (ext.hasExtension("usage")) && ext.getExtensionByUrl("usage").hasValueUsageContext() ? ext.getExtensionByUrl("usage").getValueUsageContext() : null; + abr.docoShort = ext.getExtensionString("shortDoco"); + for (Extension x : ext.getExtensionsByUrl("usage")) { + if (x.hasValueUsageContext()) { + abr.usages.add(x.getValueUsageContext()); + } + } abr.any = "any".equals(ext.getExtensionString("scope")); abr.isUnchanged = ext.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS); return abr; @@ -187,7 +191,7 @@ public class AdditionalBindingsRenderer { abr.valueSet = ab.getValueSet(); abr.doco = ab.getDocumentation(); abr.docoShort = ab.getShortDoco(); - abr.usage = ab.hasUsage() ? ab.getUsageFirstRep() : null; + abr.usages.addAll(ab.getUsage()); abr.any = ab.getAny(); abr.isUnchanged = ab.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS); return abr; @@ -220,7 +224,7 @@ public class AdditionalBindingsRenderer { boolean any = false; for (AdditionalBindingDetail binding : bindings) { doco = doco || binding.getDoco(fullDoco)!=null || (binding.compare!=null && binding.compare.getDoco(fullDoco)!=null); - usage = usage || binding.usage != null || (binding.compare!=null && binding.compare.usage!=null); + usage = usage || !binding.usages.isEmpty() || (binding.compare!=null && !binding.compare.usages.isEmpty()); any = any || binding.any || (binding.compare!=null && binding.compare.any); } @@ -283,9 +287,12 @@ public class AdditionalBindingsRenderer { renderPurpose(purpose, binding.compare.purpose); } if (usage) { - if (binding.usage != null) { - // TODO: This isn't rendered at all yet. Ideally, we want it to render with comparison... - new DataRenderer(context).renderBase(new RenderingStatus(), tr.td(), binding.usage); + if (!binding.usages.isEmpty()) { + XhtmlNode td = tr.td(); + for (UsageContext uc : binding.usages) { + td.sep(", "); + new DataRenderer(context).renderBase(new RenderingStatus(), td, uc); + } } else { tr.td(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index ffb441b6d..4edaebc1f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -1301,7 +1301,11 @@ public class DataRenderer extends Renderer implements CodeResolver { } } - protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { + protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { + renderCoding(status, x, c, true); + } + + protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c, boolean details) { String s = ""; if (c.has("display")) s = context.getTranslated(c.child("display")); @@ -1311,10 +1315,13 @@ public class DataRenderer extends Renderer implements CodeResolver { if (Utilities.noString(s)) s = c.primitiveValue("code"); - if (context.isTechnicalMode()) { - x.addText(s+" "+context.formatPhrase(RenderingContext.DATA_REND_DETAILS_STATED, displaySystem(c.primitiveValue("system")), c.primitiveValue("code"), " = '", lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")), c.primitiveValue("display"), "')")); - } else - x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); + if (context.isTechnicalMode() && details) { + String d = c.primitiveValue("display") == null ? lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")): c.primitiveValue("display"); + d = context.formatPhrase(d == null || d.equals(c.primitiveValue("code")) ? RenderingContext.DATA_REND_DETAILS_STATED_ND : RenderingContext.DATA_REND_DETAILS_STATED, displaySystem(c.primitiveValue("system")), c.primitiveValue("code"), d); + x.addText(s+" "+d); + } else { + x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); + } } public String displayCodeableConcept(ResourceWrapper cc) { @@ -1860,8 +1867,8 @@ public class DataRenderer extends Renderer implements CodeResolver { } public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { - renderCoding(status, x, u.child("code")); - x.tx(": "); + renderCoding(status, x, u.child("code"), false); + x.tx(" = "); renderDataType(status, x, u.child("value")); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index e8af42a88..9925a2acb 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -18,7 +18,9 @@ public class I18nConstants { public static final String BAD_FILE_PATH_ERROR = "Bad_file_path_error"; public static final String BASE_PROFILE__HAS_NO_TYPE = "Base_profile__has_no_type"; public static final String BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___ = "Base__Derived_profiles_have_different_types____vs___"; - public static final String BINDING_ADDITIONAL = "BINDING_ADDITIONAL"; + public static final String BINDING_ADDITIONAL_D = "BINDING_ADDITIONAL_D"; + public static final String BINDING_ADDITIONAL_UC = "BINDING_ADDITIONAL_UC"; + public static final String BINDING_ADDITIONAL_USAGE = "BINDING_ADDITIONAL_USAGE"; public static final String BINDING_MAX = "BINDING_MAX"; public static final String BUNDLE_BUNDLE_ENTRY_CANONICAL = "Bundle_BUNDLE_Entry_Canonical"; public static final String BUNDLE_BUNDLE_ENTRY_DOCUMENT = "Bundle_BUNDLE_Entry_Document"; @@ -1111,4 +1113,7 @@ public class I18nConstants { public static final String SD_BASE_EXPERIMENTAL = "SD_BASE_EXPERIMENTAL"; public static final String SD_ED_EXPERIMENTAL_BINDING = "SD_ED_EXPERIMENTAL_BINDING"; public static final String VALIDATION_NO_EXPERIMENTAL_CONTENT = "VALIDATION_NO_EXPERIMENTAL_CONTENT"; + public static final String SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN = "SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN"; + public static final String SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT = "SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT"; + public static final String SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE = "SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE"; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java index c3b1be7da..9a2b52a5a 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/RenderingI18nContext.java @@ -214,6 +214,7 @@ public class RenderingI18nContext extends I18nBase { public static final String DATA_REND_DATA = "DATA_REND_DATA"; public static final String DATA_REND_DETAILS = "DATA_REND_DETAILS"; public static final String DATA_REND_DETAILS_STATED = "DATA_REND_DETAILS_STATED"; + public static final String DATA_REND_DETAILS_STATED_ND = "DATA_REND_DETAILS_STATED_ND"; public static final String DATA_REND_DICOM = "DATA_REND_DICOM"; public static final String DATA_REND_DIM = "DATA_REND_DIM"; public static final String DATA_REND_DURATION = "DATA_REND_DURATION"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 35811c33e..f5b5c2782 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -13,7 +13,9 @@ Attempt_to_a_slice_an_element_that_does_not_repeat__from__in_ = Attempt to a sli Attempt_to_replace_element_name_for_a_nonchoice_type=Attempt to replace element name for a non-choice type Attempt_to_use_Terminology_server_when_no_Terminology_server_is_available = Attempt to use Terminology server when no Terminology server is available Attempt_to_use_a_snapshot_on_profile__as__before_it_is_generated = Attempt to use a snapshot on profile ''{0}'' as {1} before it is generated -BINDING_ADDITIONAL = {0} specified in an additional binding +BINDING_ADDITIONAL_D = {0} specified in an additional binding +BINDING_ADDITIONAL_UC = {0} specified in an additional binding which applies because {1} +BINDING_ADDITIONAL_USAGE = {0} = {1} BINDING_MAX = {0} specified in the max binding BUNDLE_BUNDLE_ENTRY_FOUND_MULTIPLE = Found {0} matches for ''{1}'' in the bundle ({2}) BUNDLE_BUNDLE_ENTRY_FOUND_MULTIPLE_FRAGMENT = Found {0} matches for fragment {2} in resource ''{1}'' in the bundle ({3}) @@ -1143,3 +1145,6 @@ NO_VALID_DISPLAY_AT_ALL = Cannot validate display Name ''{0}'' for {1}#{2}: No d SD_BASE_EXPERIMENTAL = The definition builds on ''{0}'' which is experimental, but this definition is not labeled as experimental SD_ED_EXPERIMENTAL_BINDING = The definition for the element ''{0}'' binds to the value set ''{1}'' which is experimental, but this structure is not labeled as experimental VALIDATION_NO_EXPERIMENTAL_CONTENT = Experimental content is not allowed in this context +SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN = The Usage Context {0}#{1} is not recognised and may not be correct +SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT = The Usage Context {0}#{1} is a reference to an element that does not exist +SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE = The Usage Context value must be of type {1} not {0} diff --git a/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties b/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties index 0147ecbe4..39aa5cab8 100644 --- a/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties +++ b/org.hl7.fhir.utilities/src/main/resources/rendering-phrases.properties @@ -207,7 +207,8 @@ DATA_REND_COND = Condition DATA_REND_COUNT = Count {0} DATA_REND_DATA = Data: {0} DATA_REND_DETAILS = (Details: {0} code -DATA_REND_DETAILS_STATED = (Details: {0} code {1} {2} {3} '', stated as '' {4} {5} +DATA_REND_DETAILS_STATED = (Details: {0} code {1} = ''{2}'') +DATA_REND_DETAILS_STATED_ND = (Details: {0} code {1}) DATA_REND_DICOM = DICOM DATA_REND_DIM = Dimensions: {0} DATA_REND_DURATION = Duration {0} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 5c191c7c9..03d4e1cdd 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -217,6 +217,7 @@ import org.hl7.fhir.validation.instance.type.ValueSetValidator; import org.hl7.fhir.validation.instance.utils.*; import org.w3c.dom.Document; + /** * Thinking of using this in a java program? Don't! * You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine @@ -1356,19 +1357,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat BindingStrength strength = binding.getStrength(); Extension maxVS = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET); - checkDisp = validateBindingCodeableConcept(errors, path, element, profile, stack, bh, checkDisp, checked, cc, vsRef, valueset, strength, maxVS, true); + checkDisp = validateBindingCodeableConcept(errors, path, element, profile, stack, bh, checkDisp, checked, cc, vsRef, valueset, strength, maxVS, true, null); // } else if (binding.hasValueSet()) { // hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); } else if (!noBindingMsgSuppressed) { hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); } for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) { - if (isTestableBinding(ab) && isInScope(ab)) { + StringBuilder b = new StringBuilder(); + if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) { String vsRef = ab.getValueSet(); ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile); BindingStrength strength = convertPurposeToStrength(ab.getPurpose()); - checkDisp = validateBindingCodeableConcept(errors, path, element, profile, stack, bh, checkDisp, checked, cc, vsRef, valueset, strength, null, false) && checkDisp; + checkDisp = validateBindingCodeableConcept(errors, path, element, profile, stack, bh, checkDisp, checked, cc, vsRef, valueset, strength, null, false, b.toString()) && checkDisp; } } } catch (CheckCodeOnServerException e) { @@ -1394,25 +1396,186 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return checkDisp; } - private boolean isInScope(ElementDefinitionBindingAdditionalComponent ab) { + private boolean isInScope(ElementDefinitionBindingAdditionalComponent ab, StructureDefinition profile, Element resource, StringBuilder b) { + if (ab.getUsage().isEmpty()) { + return true; + } + boolean ok = true; for (UsageContext usage : ab.getUsage()) { - if (isInScope(usage)) { + if (!isInScope(usage, profile, resource, b)) { + ok = false; + } + } + return ok; + } + + private boolean isInScope(UsageContext usage, StructureDefinition profile, Element resource, StringBuilder b) { + if (isKnownUsage(usage)) { + return true; + } + if (usage.getCode().hasSystem() && (usage.getCode().getSystem().equals(profile.getUrl()) || usage.getCode().getSystem().equals(profile.getVersionedUrl()))) { + // if it's not a defined usage from external sources, it might match something in the data content + List items = findDataValue(resource, usage.getCode().getCode()); + if (matchesUsage(items, usage.getValue())) { + b.append(context.formatMessage(I18nConstants.BINDING_ADDITIONAL_USAGE, displayCoding(usage.getCode()), display(usage.getValue()))); return true; } } - return ab.getUsage().isEmpty(); + return false; } - private boolean isInScope(UsageContext usage) { - if (isKnownUsage(usage)) { + private String displayCoding(Coding value) { + return value.getCode(); + } + + private String displayCodeableConcept(CodeableConcept value) { + for (Coding c : value.getCoding()) { + String s = displayCoding(c); + if (s != null) { + return s; + } + } + return value.getText(); + } + + private String display(DataType value) { + switch (value.fhirType()) { + case "Coding" : return displayCoding((Coding) value); + case "CodeableConcept" : return displayCodeableConcept((CodeableConcept) value); + } + return value.fhirType(); + } + + private boolean matchesUsage(List items, DataType value) { + for (Element item : items) { + if (matchesUsage(item, value)) { + return true; + } + } + return false; + } + + + + private String display(List items) { + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (Element item : items) { + display(b, item); + } + return b.toString(); + } + + private void display(CommaSeparatedStringBuilder b, Element item) { + if (item.isPrimitive()) { + b.append(item.primitiveValue()); + } else if (item.fhirType().equals("CodeableConcept")) { + for (Element c : item.getChildren("coding")) { + b.append(c.getNamedChildValue("code")); + } + } else { + b.append(item.toString()); + } + + } + + private boolean matchesUsage(Element item, DataType value) { + switch (value.fhirType()) { + case "CodeableConcept": return matchesUsageCodeableConcept(item, (CodeableConcept) value); + case "Quantity": return false; + case "Range": return false; + case "Reference": return false; + default: return false; + } + } + + private boolean matchesUsageCodeableConcept(Element item, CodeableConcept value) { + switch (item.fhirType()) { + case "CodeableConcept": return matchesUsageCodeableConceptCodeableConcept(item, value); + case "Coding": return matchesUsageCodeableConceptCoding(item, value); + default: return false; + } + } + + private boolean matchesUsageCodeableConceptCoding(Element item, CodeableConcept value) { + String system = item.getNamedChildValue("system"); + String version = item.getNamedChildValue("version"); + String code = item.getNamedChildValue("code"); + for (Coding c : value.getCoding()) { + if (system == null || !system.equals(c.getSystem())) { + return false; + } + if (code == null || !code.equals(c.getCode())) { + return false; + } + if (c.hasVersion()) { + if (version == null || !version.equals(c.getVersion())) { + return false; + } + } return true; } return false; } + private boolean matchesUsageCodeableConceptCodeableConcept(Element item, CodeableConcept value) { + for (Element code : item.getChildren("coding")) { + if (matchesUsageCodeableConceptCoding(code, value)) { + return true; + } + } + return false; + } + + private List findDataValue(Element resource, String code) { + List items = new ArrayList(); + if (resource != null) { + findDataValues(items, resource, code); + } + return items; + } + + private void findDataValues(List items, Element element, String path) { + if (element.getPath() == null) { + return; + } + if (pathMatches(element.getPath(), path)) { + items.add(element); + } else if (element.hasChildren() && path.startsWith(element.getPath())) { + for (Element child : element.getChildren()) { + findDataValues(items, child, path); + } + } + } + + private boolean pathMatches(String actualPath, String pathSpec) { + String[] ap = actualPath.split("\\."); + String[] ps = pathSpec.split("\\."); + if (ap.length != ps.length) { + return false; + } + for (int i = 0; i < ap.length; i++) { + if (!pathSegmentMatches(ap[i], ps[i])) { + return false; + } + } + return true; + } + + private boolean pathSegmentMatches(String ap, String ps) { + if (ps.contains("[")) { + return ap.equals(ps); + } else { + if (ap.contains("[")) { + ap = ap.substring(0, ap.indexOf("[")); + } + return ap.equals(ps); + } + } + private BindingStrength convertPurposeToStrength(AdditionalBindingPurposeVS purpose) { switch (purpose) { case MAXIMUM: return BindingStrength.REQUIRED; + case EXTENSIBLE: return BindingStrength.EXTENSIBLE; case PREFERRED: return BindingStrength.PREFERRED; case REQUIRED: return BindingStrength.REQUIRED; default: return null; @@ -1424,7 +1587,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private boolean validateBindingCodeableConcept(List errors, String path, Element element, StructureDefinition profile, NodeStack stack, BooleanHolder bh, boolean checkDisp, BooleanHolder checked, - CodeableConcept cc, String vsRef, ValueSet valueset, BindingStrength strength, Extension maxVS, boolean base) throws CheckCodeOnServerException { + CodeableConcept cc, String vsRef, ValueSet valueset, BindingStrength strength, Extension maxVS, boolean base, String usageNote) throws CheckCodeOnServerException { if (valueset == null) { CodeSystem cs = context.fetchCodeSystem(vsRef); if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(vsRef))) { @@ -1436,12 +1599,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat BindingContext bc = base ? BindingContext.BASE : BindingContext.ADDITIONAL; if (!cc.hasCoding()) { if (strength == BindingStrength.REQUIRED) - bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeReference(vsRef, valueset, bc))); + bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeReference(vsRef, valueset, bc, usageNote))); else if (strength == BindingStrength.EXTENSIBLE) { if (maxVS != null) bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringFromExtension(maxVS)), valueset.getVersionedUrl())); else if (!noExtensibleWarnings) { - warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(vsRef, valueset, bc)); + warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(vsRef, valueset, bc, usageNote)); } } } else { @@ -1493,15 +1656,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // we've already handled the warnings / errors about this, and set the status correctly. We don't need to do anything more? } else { if (strength == BindingStrength.REQUIRED) { - bh.see(txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(vsRef, valueset, bc), ccSummary(cc))); + bh.see(txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(vsRef, valueset, bc, usageNote), ccSummary(cc))); } else if (strength == BindingStrength.EXTENSIBLE) { if (maxVS != null) bh.see(checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(maxVS), cc, stack)); if (!noExtensibleWarnings) - txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(vsRef, valueset, bc), ccSummary(cc)); + txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(vsRef, valueset, bc, usageNote), ccSummary(cc)); } else if (strength == BindingStrength.PREFERRED) { if (baseOnly) { - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeReference(vsRef, valueset, bc), ccSummary(cc)); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeReference(vsRef, valueset, bc, usageNote), ccSummary(cc)); } } } @@ -1648,7 +1811,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat BindingStrength strength = binding.getStrength(); Extension vsMax = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET); - validateBindingCodeableConcept(errors, path, element, profile, stack, ok, false, new BooleanHolder(), cc, vsRef, valueset, strength, vsMax, true); + validateBindingCodeableConcept(errors, path, element, profile, stack, ok, false, new BooleanHolder(), cc, vsRef, valueset, strength, vsMax, true, null); // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding. if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) { @@ -1660,11 +1823,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); } for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) { - if (isTestableBinding(ab) && isInScope(ab)) { + StringBuilder b = new StringBuilder(); + if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) { String vsRef = ab.getValueSet(); ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile); BindingStrength strength = convertPurposeToStrength(ab.getPurpose()); - validateBindingCodeableConcept(errors, path, element, profile, stack, ok, false, new BooleanHolder(), cc, vsRef, valueset, strength, null, false); + validateBindingCodeableConcept(errors, path, element, profile, stack, ok, false, new BooleanHolder(), cc, vsRef, valueset, strength, null, false, b.toString()); } } } @@ -1698,18 +1862,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat BindingStrength strength = binding.getStrength(); Extension vsMax = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET); - ok = validateBindingTerminologyCoding(errors, path, element, profile, stack, ok, c, code, system, display, vsRef, valueset, strength, vsMax, true); + ok = validateBindingTerminologyCoding(errors, path, element, profile, stack, ok, c, code, system, display, vsRef, valueset, strength, vsMax, true, null); } else if (binding.hasValueSet()) { hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); } else if (!inCodeableConcept && !noBindingMsgSuppressed) { hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path); } for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) { - if (isTestableBinding(ab) && isInScope(ab)) { + StringBuilder b = new StringBuilder(); + if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) { String vsRef = ab.getValueSet(); ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile); BindingStrength strength = convertPurposeToStrength(ab.getPurpose()); - ok = validateBindingTerminologyCoding(errors, path, element, profile, stack, ok, c, code, system, display, vsRef, valueset, strength, null, true) && ok; + ok = validateBindingTerminologyCoding(errors, path, element, profile, stack, ok, c, code, system, display, vsRef, valueset, strength, null, true, b.toString()) && ok; } } } @@ -1728,7 +1893,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean validateBindingTerminologyCoding(List errors, String path, Element element, StructureDefinition profile, NodeStack stack, boolean ok, Coding c, String code, String system, String display, - String vsRef, ValueSet valueset, BindingStrength strength, Extension vsMax, boolean base) { + String vsRef, ValueSet valueset, BindingStrength strength, Extension vsMax, boolean base, String usageNote) { if (valueset == null) { CodeSystem cs = context.fetchCodeSystem(vsRef); if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(vsRef))) { @@ -1754,27 +1919,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER, system+"#"+code); else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) { if (strength == BindingStrength.REQUIRED) - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(vsRef, valueset, bc), vr.getMessage(), system+"#"+code); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(vsRef, valueset, bc, usageNote), vr.getMessage(), system+"#"+code); else if (strength == BindingStrength.EXTENSIBLE) { if (vsMax != null) checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack); else if (!noExtensibleWarnings) - txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(vsRef, valueset, bc)); + txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(vsRef, valueset, bc, usageNote)); } else if (strength == BindingStrength.PREFERRED) { if (baseOnly) { - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(vsRef, valueset, bc)); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(vsRef, valueset, bc, usageNote)); } } } else if (strength == BindingStrength.REQUIRED) - ok= txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_4, describeReference(vsRef, valueset, bc), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code) && ok; + ok= txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_4, describeReference(vsRef, valueset, bc, usageNote), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code) && ok; else if (strength == BindingStrength.EXTENSIBLE) { if (vsMax != null) ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack) && ok; else - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_5, describeReference(vsRef, valueset, bc), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_5, describeReference(vsRef, valueset, bc, usageNote), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); } else if (strength == BindingStrength.PREFERRED) { if (baseOnly) { - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_6, describeReference(vsRef, valueset, bc), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_6, describeReference(vsRef, valueset, bc, usageNote), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code); } } } else if (vr != null && vr.getMessage() != null){ @@ -1896,9 +2061,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+cc.toString()); if (!vr.isOk()) { if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_7, describeReference(maxVSUrl, valueset, BindingContext.MAXVS), vr.getMessage()); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_7, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), vr.getMessage()); else - ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset, BindingContext.MAXVS), ccSummary(cc)) && ok; + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), ccSummary(cc)) && ok; } } catch (CheckCodeOnServerException e) { if (STACK_TRACE) e.getCause().printStackTrace(); @@ -1936,9 +2101,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'"); if (!vr.isOk()) { if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset, BindingContext.MAXVS), vr.getMessage(), c.getSystem()+"#"+c.getCode()); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), vr.getMessage(), c.getSystem()+"#"+c.getCode()); else - ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_10, describeReference(maxVSUrl, valueset, BindingContext.MAXVS), c.getSystem(), c.getCode()) && ok; + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_10, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), c.getSystem(), c.getCode()) && ok; } } catch (Exception e) { if (STACK_TRACE) e.printStackTrace(); @@ -1966,9 +2131,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.tx(t, "vc "+value); if (!vr.isOk()) { if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) - txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset, BindingContext.BASE), vr.getMessage(), value); + txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset, BindingContext.BASE, null), vr.getMessage(), value); else { - ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_11, describeReference(maxVSUrl, valueset, BindingContext.BASE), vr.getMessage()) && ok; + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_11, describeReference(maxVSUrl, valueset, BindingContext.BASE, null), vr.getMessage()) && ok; } } } catch (Exception e) { @@ -2029,7 +2194,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat BindingStrength strength = binding.getStrength(); Extension vsMax = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET); - ok = validateBindingCodedElement(errors, path, element, profile, stack, theCode, theSystem, ok, checked, c, vsRef, valueset, strength, vsMax, true); + ok = validateBindingCodedElement(errors, path, element, profile, stack, theCode, theSystem, ok, checked, c, vsRef, valueset, strength, vsMax, true, null); // } else if (binding.hasValueSet()) { // hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK); @@ -2038,12 +2203,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) { - if (isTestableBinding(ab) && isInScope(ab)) { + StringBuilder b = new StringBuilder(); + if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) { String vsRef = ab.getValueSet(); ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile); BindingStrength strength = convertPurposeToStrength(ab.getPurpose()); - ok = validateBindingCodedElement(errors, path, element, profile, stack, theCode, theSystem, ok, checked, c, vsRef, valueset, strength, null, false) && ok; + ok = validateBindingCodedElement(errors, path, element, profile, stack, theCode, theSystem, ok, checked, c, vsRef, valueset, strength, null, false, b.toString()) && ok; } } } catch (Exception e) { @@ -2068,9 +2234,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return ok; } + private Element getResource(NodeStack stack) { + if (stack.getElement().isResource()) { + return stack.getElement(); + } + if (stack.getParent() == null) { + return null; + } + return getResource(stack.getParent()); + } + private boolean validateBindingCodedElement(List errors, String path, Element element, StructureDefinition profile, NodeStack stack, String theCode, String theSystem, boolean ok, BooleanHolder checked, - Coding c, String vsRef, ValueSet valueset, BindingStrength strength, Extension vsMax, boolean base) { + Coding c, String vsRef, ValueSet valueset, BindingStrength strength, Extension vsMax, boolean base, String usageNote) { if (valueset == null) { CodeSystem cs = context.fetchCodeSystem(vsRef); if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(vsRef))) { @@ -2098,28 +2274,28 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER, theSystem+"#"+theCode); else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) { if (strength == BindingStrength.REQUIRED) - ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(vsRef, valueset, bc), vr.getMessage(), theSystem+"#"+theCode) && ok; + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(vsRef, valueset, bc, usageNote), vr.getMessage(), theSystem+"#"+theCode) && ok; else if (strength == BindingStrength.EXTENSIBLE) { if (vsMax != null) checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack); else if (!noExtensibleWarnings) - txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(vsRef, valueset, bc), theSystem+"#"+theCode); + txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(vsRef, valueset, bc, usageNote), theSystem+"#"+theCode); } else if (strength == BindingStrength.PREFERRED) { if (baseOnly) { - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(vsRef, valueset, bc), theSystem+"#"+theCode); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(vsRef, valueset, bc, usageNote), theSystem+"#"+theCode); } } } else if (strength == BindingStrength.REQUIRED) - ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_12, describeReference(vsRef, valueset, bc), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode) && ok; + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_12, describeReference(vsRef, valueset, bc, usageNote), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode) && ok; else if (strength == BindingStrength.EXTENSIBLE) { if (vsMax != null) ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack) && ok; else if (!noExtensibleWarnings) { - txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_13, describeReference(vsRef, valueset, bc), getErrorMessage(vr.getMessage()), c.getSystem()+"#"+c.getCode()); + txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_13, describeReference(vsRef, valueset, bc, usageNote), getErrorMessage(vr.getMessage()), c.getSystem()+"#"+c.getCode()); } } else if (strength == BindingStrength.PREFERRED) { if (baseOnly) { - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(vsRef, valueset, bc), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(vsRef, valueset, bc, usageNote), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode); } } } else if (vr != null && vr.getMessage() != null) { @@ -3563,15 +3739,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } else if (binding.getStrength() == BindingStrength.REQUIRED) { - ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE), getErrorMessage(vr.getMessage())) && ok; + ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE, null), getErrorMessage(vr.getMessage())) && ok; } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) { if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), value, stack) && ok; else if (!noExtensibleWarnings && !isOkExtension(value, vs)) - txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE), getErrorMessage(vr.getMessage())); + txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE, null), getErrorMessage(vr.getMessage())); } else if (binding.getStrength() == BindingStrength.PREFERRED) { if (baseOnly) { - txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE), getErrorMessage(vr.getMessage())); + txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE, null), getErrorMessage(vr.getMessage())); } } } else if (vr != null && vr.getMessage() != null){ @@ -4279,7 +4455,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return false; } - private String describeReference(String reference, CanonicalResource target, BindingContext ctxt) { + private String describeReference(String reference, CanonicalResource target, BindingContext ctxt, String usageNote) { if (reference == null && target == null) return "null"; String res = null; @@ -4297,7 +4473,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } switch (ctxt) { - case ADDITIONAL: return context.formatMessage(I18nConstants.BINDING_ADDITIONAL, res); + case ADDITIONAL: return context.formatMessage(Utilities.noString(usageNote) ? I18nConstants.BINDING_ADDITIONAL_D : I18nConstants.BINDING_ADDITIONAL_UC, res, usageNote); case MAXVS: return context.formatMessage(I18nConstants.BINDING_MAX, res); default: return res; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 0df6dd68f..db777b0c3 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -525,7 +525,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), characteristics.contains("can-bind") , I18nConstants.SD_ILLEGAL_CHARACTERISTICS, "Binding", typeCodes) && ok; } Element binding = element.getNamedChild("binding", false); - ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path, experimental) && ok; + ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path, experimental, sd) && ok; } else { // this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back // String bt = boundType(typeCodes); @@ -989,7 +989,7 @@ public class StructureDefinitionValidator extends BaseValidator { return null; } - private boolean validateBinding(List errors, Element binding, NodeStack stack, Set typeCodes, boolean snapshot, String path, boolean experimental) { + private boolean validateBinding(List errors, Element binding, NodeStack stack, Set typeCodes, boolean snapshot, String path, boolean experimental, StructureDefinition profile) { boolean ok = true; if (bindableType(typeCodes) == null) { ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot, I18nConstants.SD_ED_BIND_NO_BINDABLE, path, typeCodes.toString()) && ok; @@ -1019,9 +1019,122 @@ public class StructureDefinitionValidator extends BaseValidator { } } } + if (binding.hasChildren("additional")) { + int i = 0; + for (Element ab : binding.getChildren("additional")) { + ok = validateAdditionalBinding(errors, ab, stack.push(ab, i, null, null), snapshot, path, experimental) && ok; + i++; + } + } + if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) { + int i = 0; + for (Element ab : binding.getChildren("extension")) { + String url = ab.getNamedChildValue("url"); + if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(url)) { + ok = validateAdditionalBindingExtension(errors, ab, stack.push(ab, i, null, null), snapshot, path, experimental, profile) && ok; + } + i++; + } + } + return ok; + } + + private boolean validateAdditionalBinding(List errors, Element binding, NodeStack stack, boolean snapshot, String path, boolean experimental) { + boolean ok = true; + + if (binding.hasChild("valueSet", false)) { + Element valueSet = binding.getNamedChild("valueSet", false); + String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference", false); + if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) { + Resource vs = context.fetchResource(Resource.class, ref); + + // just because we can't resolve it directly doesn't mean that terminology server can't. Check with it + + if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) { + if (vs != null) { + if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType())) { + ValueSet vsr = (ValueSet) vs; + warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), !vsr.getExperimental() || experimental, I18nConstants.SD_ED_EXPERIMENTAL_BINDING, path, ref); + } else { + ok = false; + } + } + } + } + } + if (binding.hasChildren("usage")) { + for (Element usage : binding.getChildren("usage")) { + warning(errors, "2024-09-20", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, "test"); + } + } return ok; } + private boolean validateAdditionalBindingExtension(List errors, Element binding, NodeStack stack, boolean snapshot, String path, boolean experimental, StructureDefinition profile) { + boolean ok = true; + + if (binding.hasExtension("valueSet")) { + Element valueSet = binding.getExtension("valueSet"); + Element vv = valueSet.getNamedChild("value"); + String ref = vv.hasPrimitiveValue() ? vv.primitiveValue() : vv.getNamedChildValue("reference", false); + if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) { + Resource vs = context.fetchResource(Resource.class, ref); + + // just because we can't resolve it directly doesn't mean that terminology server can't. Check with it + + if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) { + if (vs != null) { + if (rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType())) { + ValueSet vsr = (ValueSet) vs; + warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), !vsr.getExperimental() || experimental, I18nConstants.SD_ED_EXPERIMENTAL_BINDING, path, ref); + } else { + ok = false; + } + } + } + } + } + if (binding.hasExtension("usage")) { + int i = 0; + for (Element usage : binding.getChildren("extension")) { + String url = usage.getNamedChildValue("url"); + if ("usage".equals(url)) { + Element uv = usage.getNamedChild("value"); + ok = validateAdditionalBindingUsage(errors, uv, stack.push(uv, -1, null, null), path, profile) && ok; + } + i++; + } + } + return ok; + } + + private boolean validateAdditionalBindingUsage(List errors, Element usage, NodeStack stack, String path, StructureDefinition profile) { + boolean ok = true; + Element cc = usage.getNamedChild("code"); + if (cc != null) { + String system = cc.getNamedChildValue("system"); + String code = cc.getNamedChildValue("code"); + if (system != null && system.equals(profile.getUrl())) { + ElementDefinition ed = profile.getDifferential().getElementByPath(code); + if (ed == null) { + ed = profile.getSnapshot().getElementByPath(code); + } + if (ed == null) { + ok = false; + rule(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT, system, code); + } else { + if (usage.hasChild("value")) { + String t = usage.getNamedChild("value").fhirType(); + ok = rule(errors, "2024-09-20", IssueType.BUSINESSRULE, stack.getLiteralPath(), "CodeableConcept".equals(t), I18nConstants.SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE, t, "CodeableConcept") && ok; + } + } + } else { + warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN, system, code); + } + } + return ok; + } + private Set getListofBindableTypes(Set types) { Set res = new HashSet<>(); for (String s : types) { diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/testStructureDefinitionadditional-bindings-profile-cs.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/testStructureDefinitionadditional-bindings-profile-cs.cache new file mode 100644 index 000000000..a194229ef --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/testStructureDefinitionadditional-bindings-profile-cs.cache @@ -0,0 +1,42 @@ +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://hl7.org/fhir/test/StructureDefinition/additional-bindings-profile-cs", + "code" : "digital-access" +}, "valueSet" :null, "langs":"en-US", "useServer":"true", "useClient":"false", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "code" : "digital-access", + "system" : "http://hl7.org/fhir/test/StructureDefinition/additional-bindings-profile-cs", + "severity" : "error", + "error" : "A definition for CodeSystem 'http://hl7.org/fhir/test/StructureDefinition/additional-bindings-profile-cs' could not be found, so the code cannot be validated", + "class" : "CODESYSTEM_UNSUPPORTED", + "server" : "http://tx-dev.fhir.org/r4", + "unknown-systems" : "http://hl7.org/fhir/test/StructureDefinition/additional-bindings-profile-cs", + "issues" : { + "resourceType" : "OperationOutcome", + "issue" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server", + "valueUrl" : "http://tx-dev.fhir.org/r4" + }], + "severity" : "error", + "code" : "not-found", + "details" : { + "coding" : [{ + "system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code" : "not-found" + }], + "text" : "A definition for CodeSystem 'http://hl7.org/fhir/test/StructureDefinition/additional-bindings-profile-cs' could not be found, so the code cannot be validated" + }, + "location" : ["Coding.system"], + "expression" : ["Coding.system"] + }] +} + +} +------------------------------------------------------------------------------------- diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ussdoh-clinicalcareCodeSystemSDOHCC-CodeSystemTemporaryCodes.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ussdoh-clinicalcareCodeSystemSDOHCC-CodeSystemTemporaryCodes.cache new file mode 100644 index 000000000..ffc75d0de --- /dev/null +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/ussdoh-clinicalcareCodeSystemSDOHCC-CodeSystemTemporaryCodes.cache @@ -0,0 +1,83 @@ +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes", + "code" : "digital-access-x" +}, "valueSet" :null, "langs":"en-US", "useServer":"true", "useClient":"false", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "code" : "digital-access-x", + "system" : "http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes", + "severity" : "error", + "error" : "A definition for CodeSystem 'http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes' could not be found, so the code cannot be validated", + "class" : "CODESYSTEM_UNSUPPORTED", + "server" : "http://tx-dev.fhir.org/r4", + "unknown-systems" : "http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes", + "issues" : { + "resourceType" : "OperationOutcome", + "issue" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server", + "valueUrl" : "http://tx-dev.fhir.org/r4" + }], + "severity" : "error", + "code" : "not-found", + "details" : { + "coding" : [{ + "system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code" : "not-found" + }], + "text" : "A definition for CodeSystem 'http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes' could not be found, so the code cannot be validated" + }, + "location" : ["Coding.system"], + "expression" : ["Coding.system"] + }] +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes", + "code" : "digital-access" +}, "valueSet" :null, "langs":"en-US", "useServer":"true", "useClient":"false", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": { + "resourceType" : "Parameters", + "parameter" : [{ + "name" : "profile-url", + "valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891" + }] +}}#### +v: { + "code" : "digital-access", + "system" : "http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes", + "severity" : "error", + "error" : "A definition for CodeSystem 'http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes' could not be found, so the code cannot be validated", + "class" : "CODESYSTEM_UNSUPPORTED", + "server" : "http://tx-dev.fhir.org/r4", + "unknown-systems" : "http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes", + "issues" : { + "resourceType" : "OperationOutcome", + "issue" : [{ + "extension" : [{ + "url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server", + "valueUrl" : "http://tx-dev.fhir.org/r4" + }], + "severity" : "error", + "code" : "not-found", + "details" : { + "coding" : [{ + "system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", + "code" : "not-found" + }], + "text" : "A definition for CodeSystem 'http://hl7.org/fhir/us/sdoh-clinicalcare/CodeSystem/SDOHCC-CodeSystemTemporaryCodes' could not be found, so the code cannot be validated" + }, + "location" : ["Coding.system"], + "expression" : ["Coding.system"] + }] +} + +} +-------------------------------------------------------------------------------------