Error when ValueSet.compose.include.system refers to a ValueSet

This commit is contained in:
Grahame Grieve 2025-01-09 08:10:12 +11:00
parent f2e48e5dec
commit df72ca604e
6 changed files with 67 additions and 9 deletions

View File

@ -1,11 +1,14 @@
package org.hl7.fhir.r5.terminologies;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.utilities.Utilities;
public class TerminologyUtilities {
@ -24,4 +27,27 @@ public class TerminologyUtilities {
}
return oids;
}
public static List<String> listSystems(ValueSet vs) {
Set<String> res = new HashSet<>();
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
if (inc.hasSystem()) {
if (inc.hasVersion()) {
res.add(inc.getSystem()+"|"+inc.getVersion());
} else {
res.add(inc.getSystem());
}
}
}
for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
if (inc.hasSystem()) {
if (inc.hasVersion()) {
res.add(inc.getSystem()+"|"+inc.getVersion());
} else {
res.add(inc.getSystem());
}
}
}
return Utilities.sorted(res);
}
}

View File

@ -281,8 +281,9 @@ public class ToolingExtensions {
public static final String EXT_SUPPRESS_RESOURCE_TYPE = "http://hl7.org/fhir/tools/StructureDefinition/json-suppress-resourcetype";
public static final String EXT_PROFILE_VIEW_HINT = "http://hl7.org/fhir/tools/StructureDefinition/view-hint";
public static final String EXT_SNAPSHOT_BEHAVIOR = "http://hl7.org/fhir/tools/StructureDefinition/snapshot-behavior";
public static final String EXT_EARLIEST_FHIR_VERSION = "http://hl7.org/fhir/StructureDefinition/earliestAllowedFHIRVersion";
public static final String EXT_LATEST_FHIR_VERSION = "http://hl7.org/fhir/StructureDefinition/latestAllowedFHIRVersion";
public static final String EXT_FHIRVERSION_SPECIFIC_USE = "http://hl7.org/fhir/StructureDefinition/version-specific-use";
public static final String EXT_FHIRVERSION_SPECIFIC_USE_START = "startFhirVersion";
public static final String EXT_FHIRVERSION_SPECIFIC_USE_END = "endFhirVersion";
// specific extension helpers

View File

@ -227,8 +227,12 @@ public class I18nConstants {
public static final String EXTENSION_CONTEXT_UNABLE_TO_FIND_PROFILE = "EXTENSION_CONTEXT_UNABLE_TO_FIND_PROFILE";
public static final String EXTENSION_EXTM_CONTEXT_WRONG = "Extension_EXTM_Context_Wrong";
public static final String EXTENSION_EXTM_CONTEXT_WRONG_XVER = "EXTENSION_EXTM_CONTEXT_WRONG_XVER";
public static final String EXTENSION_EXTM_CONTEXT_WRONG_VER = "Extension_EXTM_Context_Wrong_VER";
public static final String EXTENSION_EXTM_CONTEXT_WRONG_XVER_VER = "EXTENSION_EXTM_CONTEXT_WRONG_XVER_VER";
public static final String EXTENSION_EXTP_CONTEXT_WRONG = "Extension_EXTP_Context_Wrong";
public static final String EXTENSION_EXTP_CONTEXT_WRONG_XVER = "EXTENSION_EXTP_CONTEXT_WRONG_XVER";
public static final String EXTENSION_EXTP_CONTEXT_WRONG_VER = "Extension_EXTP_Context_Wrong_VER";
public static final String EXTENSION_EXTP_CONTEXT_WRONG_XVER_VER = "EXTENSION_EXTP_CONTEXT_WRONG_XVER_VER";
public static final String EXTENSION_EXT_COUNT_MISMATCH = "Extension_EXT_Count_Mismatch";
public static final String EXTENSION_EXT_COUNT_NOTFOUND = "Extension_EXT_Count_NotFound";
public static final String EXTENSION_EXT_FIXED_BANNED = "Extension_EXT_Fixed_Banned";
@ -1167,4 +1171,7 @@ public class I18nConstants {
public static final String SD_DERIVATION_NO_CONCRETE = "SD_DERIVATION_NO_CONCRETE";
public static final String EXTENSION_FHIR_VERSION_EARLIEST = "EXTENSION_FHIR_VERSION_EARLIEST";
public static final String EXTENSION_FHIR_VERSION_LATEST = "EXTENSION_FHIR_VERSION_LATEST";
public static final String VALUESET_INCLUDE_WRONG_VS = "VALUESET_INCLUDE_WRONG_VS";
public static final String VALUESET_INCLUDE_WRONG_VS_HINT = "VALUESET_INCLUDE_WRONG_VS_HINT";
public static final String VALUESET_INCLUDE_WRONG_VS_MANY = "VALUESET_INCLUDE_WRONG_VS_MANY";
}

View File

@ -200,7 +200,9 @@ ERROR_GENERATING_SNAPSHOT = Error generating Snapshot: {0} (this usually arises
EXTENSION_CONTEXT_UNABLE_TO_CHECK_PROFILE = The extension {0} specifies a context of {1} but the validator cannot check whether the profile is valid or not at this time
EXTENSION_CONTEXT_UNABLE_TO_FIND_PROFILE = The extension {0} specifies a context of {1} but the validator cannot find that profile
EXTENSION_EXTM_CONTEXT_WRONG_XVER = The modifier extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions)
EXTENSION_EXTM_CONTEXT_WRONG_XVER_VER = The modifier extension {0} from FHIR version {3} is not allowed to be used at this point (allowed for this version = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions)
EXTENSION_EXTP_CONTEXT_WRONG_XVER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions)
EXTENSION_EXTP_CONTEXT_WRONG_XVER_VER = The extension {0} from FHIR version {3} is not allowed to be used at this point (allowed for this version = {1}; this element is [{2}; this is a warning since contexts may be renamed between FHIR versions)
EXT_VER_URL_IGNORE = Extension URLs don''t have versions. The validator has ignored the version and processed the extension anyway
EXT_VER_URL_MISLEADING = The extension URL contains a ''|'' which makes it look like a versioned URL, but it''s not, and this is confusing for implementers
EXT_VER_URL_NOT_ALLOWED = The extension URL must not contain a version
@ -228,7 +230,9 @@ Error_parsing_XHTML_ = Error parsing XHTML: {0}
Error_reading__from_package__ = Error reading {0} from package {1}#{2}: {3}
Error_validating_code_running_without_terminology_services = Unable to validate code ''{0}'' in system ''{1}'' because the validator is running without terminology services
Extension_EXTM_Context_Wrong = The modifier extension {0} is not allowed to be used at this point (allowed = {1}; this element is {2})
Extension_EXTM_Context_Wrong_VER = The modifier extension {0} is not allowed to be used at this point (allowed for this version = {1}; this element is {2})
Extension_EXTP_Context_Wrong = The extension {0} is not allowed to be used at this point (allowed = {1}; this element is {2})
Extension_EXTP_Context_Wrong_VER = The extension {0} is not allowed to be used at this point (allowed for this version = {1}; this element is {2})
Extension_EXT_Count_Mismatch = Extensions count mismatch: expected {0} but found {1}
Extension_EXT_Count_NotFound = Extension count mismatch: unable to find extension: {0}
Extension_EXT_Fixed_Banned = No extensions allowed, as the specified fixed value doesn''t contain any extensions
@ -1199,4 +1203,7 @@ VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' i
CONCEPTMAP_VS_NOT_A_VS = Reference must be to a ValueSet, but found a {0} instead
SD_DERIVATION_NO_CONCRETE = {0} is labeled as an abstract type, but no concrete descendants were found (check definitions - this is usually an error unless concrete definitions are in some other package)
EXTENSION_FHIR_VERSION_EARLIEST = The definition of the extension ''{0}'' specifies that the earliest version it can be used with is {1} (v{2}), but this context of use is version {3} (v{4})
EXTENSION_FHIR_VERSION_LATEST = The definition of the extension ''{0}'' specifies that the latest version it can be used with is {1} (v{2}), but this context of use is version {3} (v{4})
EXTENSION_FHIR_VERSION_LATEST = The definition of the extension ''{0}'' specifies that the latest version it can be used with is {1} (v{2}), but this context of use is version {3} (v{4})
VALUESET_INCLUDE_WRONG_VS = The system ''{0}'' is actually a value set
VALUESET_INCLUDE_WRONG_VS_HINT = The system ''{0}'' is actually a value set, which itself refers to the system ''{1}'' so that may be what is intended here
VALUESET_INCLUDE_WRONG_VS_MANY = The system ''{0}'' is actually a value set, which itself refers to the systems {1}

View File

@ -18,6 +18,7 @@ import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.TerminologyUtilities;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
@ -303,6 +304,7 @@ public class ValueSetValidator extends BaseValidator {
// can we get it from a terminology server?
cs = context.findTxResource(CodeSystem.class, system, version);
}
boolean validateConcepts = true;
if (cs != null) { // if it's null, we can't analyse this
if (cs.getContent() == null) {
warning(errors, "2024-03-06", IssueType.INVALID, stack, false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_CONTENT : I18nConstants.VALUESET_INCLUDE_CSVER_CONTENT, system, "null", version);
@ -315,15 +317,29 @@ public class ValueSetValidator extends BaseValidator {
hint(errors, "2024-03-06", IssueType.INVALID, stack, false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_CONTENT : I18nConstants.VALUESET_INCLUDE_CSVER_CONTENT, system, cs.getContent().toCode(), version);
break;
case SUPPLEMENT:
validateConcepts = false;
ok = rule(errors, "2024-03-06", IssueType.INVALID, stack, false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_SUPPLEMENT : I18nConstants.VALUESET_INCLUDE_CSVER_SUPPLEMENT, system, cs.getSupplements(), version) && ok;
break;
default:
break;
}
}
}
} else {
ValueSet vs = context.findTxResource(ValueSet.class, system, version);
if (vs != null) {
validateConcepts = false;
List<String> systems = TerminologyUtilities.listSystems(vs);
if (systems.size() == 0) {
ok = rule(errors, "2025-01-09", IssueType.INVALID, stack, false, I18nConstants.VALUESET_INCLUDE_WRONG_VS, system) && ok;
} else if (systems.size() == 1) {
ok = rule(errors, "2025-01-09", IssueType.INVALID, stack, false, I18nConstants.VALUESET_INCLUDE_WRONG_VS_HINT, system, systems.get(0)) && ok;
} else {
ok = rule(errors, "2025-01-09", IssueType.INVALID, stack, false, I18nConstants.VALUESET_INCLUDE_WRONG_VS_MANY, system, CommaSeparatedStringBuilder.join(", ", systems)) && ok;
}
}
}
if (!noTerminologyChecks) {
if (!noTerminologyChecks && validateConcepts) {
boolean systemOk = true;
int cc = 0;
List<VSCodingValidationRequest> batch = new ArrayList<>();
@ -336,7 +352,7 @@ public class ValueSetValidator extends BaseValidator {
for (Element concept : concepts) {
// we treat the first differently because we want to know if the system is worth validating. if it is, then we batch the rest
if (first) {
systemOk = validateValueSetIncludeConcept(errors, concept, stack, stack.push(concept, cc, null, null), system, version, csChecker);
systemOk = validateValueSetIncludeConcept(errors, concept, stack, stack.push(concept, cc, null, null), system, version, csChecker, cs != null);
first = false;
} else if (systemOk) {
batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version, csChecker));
@ -390,7 +406,7 @@ public class ValueSetValidator extends BaseValidator {
}
private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version, CodeSystemChecker slv) {
private boolean validateValueSetIncludeConcept(List<ValidationMessage> errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version, CodeSystemChecker slv, boolean locallyKnownCodeSystem) {
String code = concept.getChildValue("code");
String display = concept.getChildValue("display");
slv.checkConcept(code, display);

View File

@ -2,5 +2,6 @@
"#cs1|null" : null,
"#cs|null" : null,
"http://something|null" : null,
"http://snomed.info/sct|null" : null
"http://snomed.info/sct|null" : null,
"http://hl7.org/fhir/ValueSet/FHIR-version|5.0.0" : null
}