ValueSetValidation improvements
This commit is contained in:
parent
33a1814d4e
commit
9e8a010c13
|
@ -562,9 +562,28 @@ public class Coding extends DataType implements IBaseCoding, ICompositeType, ICo
|
|||
base = base + "#"+getCode();
|
||||
if (hasDisplay())
|
||||
base = base+": '"+getDisplay()+"'";
|
||||
return base;
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
public static Coding fromLiteral(String value) {
|
||||
String sv = value.contains("#") ? value.substring(0, value.indexOf("#")) : value;
|
||||
String cp = value.contains("#") ? value.substring(value.indexOf("#")+1) : null;
|
||||
|
||||
String system = sv.contains("|") ? sv.substring(0, sv.indexOf("|")) : sv;
|
||||
String version = sv.contains("|") ? sv.substring(sv.indexOf("|")+1) : null;
|
||||
|
||||
String code = cp != null && cp.contains("'") ? cp.substring(0, cp.indexOf("'")) : cp;
|
||||
String display = cp != null && cp.contains("'") ? cp.substring(cp.indexOf("'")+1) : null;
|
||||
if (display != null) {
|
||||
display = display.trim();
|
||||
display = display.substring(0, display.length() -1);
|
||||
}
|
||||
if ((system == null || !Utilities.isAbsoluteUrl(system)) && code == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new Coding(system, version, code, display);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches(Coding other) {
|
||||
return other.hasCode() && this.hasCode() && other.hasSystem() && this.hasSystem() && this.getCode().equals(other.getCode()) && this.getSystem().equals(other.getSystem()) ;
|
||||
|
|
|
@ -1056,6 +1056,8 @@ public class CodeSystemUtilities extends TerminologyUtilities {
|
|||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isExemptFromMultipleVersionChecking(String url) {
|
||||
return Utilities.existsInList(url, "http://snomed.info/sct", "http://loinc.org");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package org.hl7.fhir.utilities;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
|
@ -171,4 +174,12 @@ public class CommaSeparatedStringBuilder {
|
|||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public static String join(String sep, EnumSet<? extends Enum> set) {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(sep);
|
||||
for (Enum e : set) {
|
||||
b.append(e.toString());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
}
|
|
@ -1075,7 +1075,7 @@ public class I18nConstants {
|
|||
public static final String TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS = "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS";
|
||||
public static final String CODESYSTEM_PROPERTY_DUPLICATE_URI = "CODESYSTEM_PROPERTY_DUPLICATE_URI";
|
||||
public static final String CODESYSTEM_PROPERTY_BAD_HL7_URI = "CODESYSTEM_PROPERTY_BAD_HL7_URI";
|
||||
public static final String CODESYSTEM_PROPERTY_SYNONYM_DEPRECATED = "CODESYSTEM_PROPERTY_SYNONYM_DEPRECATED";
|
||||
public static final String CODESYSTEM_PROPERTY_SYNONYM_CHECK = "CODESYSTEM_PROPERTY_SYNONYM_CHECK";
|
||||
public static final String CODESYSTEM_PROPERTY_DUPLICATE_CODE = "CODESYSTEM_PROPERTY_DUPLICATE_CODE";
|
||||
public static final String CODESYSTEM_PROPERTY_URI_CODE_MISMATCH = "CODESYSTEM_PROPERTY_URI_CODE_MISMATCH";
|
||||
public static final String CODESYSTEM_PROPERTY_URI_TYPE_MISMATCH = "CODESYSTEM_PROPERTY_URI_TYPE_MISMATCH";
|
||||
|
@ -1087,6 +1087,17 @@ public class I18nConstants {
|
|||
public static final String CODESYSTEM_PROPERTY_WRONG_TYPE = "CODESYSTEM_PROPERTY_WRONG_TYPE";
|
||||
public static final String CODESYSTEM_DESIGNATION_DISP_CLASH_NO_LANG = "CODESYSTEM_DESIGNATION_DISP_CLASH_NO_LANG";
|
||||
public static final String CODESYSTEM_DESIGNATION_DISP_CLASH_LANG = "CODESYSTEM_DESIGNATION_DISP_CLASH_LANG";
|
||||
public static final String VALUESET_UNKNOWN_FILTER_PROPERTY_NO_CS = "VALUESET_UNKNOWN_FILTER_PROPERTY_NO_CS";
|
||||
public static final String VALUESET_UNKNOWN_FILTER_PROPERTY = "VALUESET_UNKNOWN_FILTER_PROPERTY";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_BOOLEAN = "VALUESET_BAD_FILTER_VALUE_BOOLEAN";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_CODE = "VALUESET_BAD_FILTER_VALUE_CODE";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_DATETIME = "VALUESET_BAD_FILTER_VALUE_DATETIME";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_DECIMAL = "VALUESET_BAD_FILTER_VALUE_DECIMAL";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_INTEGER = "VALUESET_BAD_FILTER_VALUE_INTEGER";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_VALID_CODE = "VALUESET_BAD_FILTER_VALUE_VALID_CODE";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_CODED = "VALUESET_BAD_FILTER_VALUE_CODED";
|
||||
public static final String VALUESET_BAD_FILTER_VALUE_CODED_INVALID = "VALUESET_BAD_FILTER_VALUE_CODED_INVALID";
|
||||
public static final String VALUESET_BAD_FILTER_OP = "VALUESET_BAD_FILTER_OP";
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1133,15 +1133,26 @@ TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS = There are multipl
|
|||
ABSTRACT_CODE_NOT_ALLOWED = Code ''{0}#{1}'' is abstract, and not allowed in this context
|
||||
CODESYSTEM_PROPERTY_DUPLICATE_URI = A property is already defined with the URI ''{0}''
|
||||
CODESYSTEM_PROPERTY_BAD_HL7_URI = Unknown CodeSystem Property ''{0}''. If you are creating your own property, do not create it in the HL7 namespace
|
||||
CODESYSTEM_PROPERTY_SYNONYM_DEPRECATED = The 'synonym' property is deprecated; just create duplicate concepts
|
||||
CODESYSTEM_PROPERTY_SYNONYM_CHECK = The synonym ''{0}'' is not also defined in the code system. The Synonym property should only used to declare equivalence to other existing codes
|
||||
CODESYSTEM_PROPERTY_DUPLICATE_CODE = A property is already defined with the code ''{0}''
|
||||
CODESYSTEM_PROPERTY_URI_CODE_MISMATCH = The URI ''{0}'' is normally assigned the code ''{1}''. Using the code ''{2}'' will usually create confusion in ValueSet filters etc
|
||||
CODESYSTEM_PROPERTY_URI_TYPE_MISMATCH = Wrong type ''{2}'': The URI ''{0}'' identifies a property that has the type ''{1}''
|
||||
CODESYSTEM_PROPERTY_UNKNOWN_CODE = This property has only (''{0}'') a code and no URI, so it has no clearly defined meaning in the terminology ecosystem
|
||||
CODESYSTEM_PROPERTY_KNOWN_CODE_SUGGESTIVE = This property has only the standard code (''{0}'') but not the standard URI ''{1}'', so it has no clearly defined meaning in the terminology ecosystem
|
||||
CODESYSTEM_PROPERTY_CODE_TYPE_MISMATCH = Wrong type ''{2}'': The code ''{0}'' identifies a property that has the type ''{1}''
|
||||
CODESYSTEM_PROPERTY_UNDEFINED = The property ''{0}'' has no definition. Many terminology tools won't know what to do with it
|
||||
CODESYSTEM_PROPERTY_UNDEFINED = The property ''{0}'' has no definition in CodeSystem.property. Many terminology tools won''t know what to do with it
|
||||
CODESYSTEM_PROPERTY_NO_VALUE = The property ''{0}'' has no value, and cannot be understoof
|
||||
CODESYSTEM_PROPERTY_WRONG_TYPE = The property ''{0}'' has the invalid type ''{1}'', when it is defined to have the type ''{2}''
|
||||
CODESYSTEM_DESIGNATION_DISP_CLASH_NO_LANG = The designation ''{0}'' has no use and no language, so is not differentiated from the base display (''{1}'')
|
||||
CODESYSTEM_DESIGNATION_DISP_CLASH_LANG = The designation ''{0}'' has no use and is in the same language (''{2}''), so is not differentiated from the base display (''{1}'')
|
||||
VALUESET_UNKNOWN_FILTER_PROPERTY = The property ''{0}'' is not known for the system ''{1}'', so may not be understood by the terminology ecosystem. Known properties for this system: {2}
|
||||
VALUESET_UNKNOWN_FILTER_PROPERTY_NO_CS = No definition can be found for the system {1}, and the property ''{0}'' is not a generally known property, so the property might not be valid, or understood by the terminology ecosystem. In case it's useful, the list of generally known properties for all systems is {2}
|
||||
VALUESET_BAD_FILTER_VALUE_BOOLEAN = The value for a filter based on property ''{0}'' must be either ''true'' or ''false'', not ''{1}''
|
||||
VALUESET_BAD_FILTER_VALUE_CODE = The value for a filter based on property ''{0}'' must be a valid code, not ''{1}''
|
||||
VALUESET_BAD_FILTER_VALUE_DATETIME = The value for a filter based on property ''{0}'' must be a valid date(/time), not ''{1}''
|
||||
VALUESET_BAD_FILTER_VALUE_DECIMAL = The value for a filter based on property ''{0}'' must be a decimal value, not ''{1}''
|
||||
VALUESET_BAD_FILTER_VALUE_INTEGER = The value for a filter based on property ''{0}'' must be integer value, not ''{1}''
|
||||
VALUESET_BAD_FILTER_VALUE_VALID_CODE = The value for a filter based on property ''{0}'' must be a valid code from the system ''{2}'', and ''{1}'' is not ({3})
|
||||
VALUESET_BAD_FILTER_VALUE_CODED = The value for a filter based on property ''{0}'' must be in the format system(|version)#code, not ''{1}''
|
||||
VALUESET_BAD_FILTER_VALUE_CODED_INVALID = The value for a filter based on property ''{0}'' is ''{1}'' which is not a valid code ({2})
|
||||
VALUESET_BAD_FILTER_OP = The operation ''{0}'' is not allowed for property ''{1}''. Allowed ops: {2}
|
||||
|
|
|
@ -1241,7 +1241,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
|
|||
Set<String> res = new HashSet<>();
|
||||
for (Resource r : context.fetchResourcesByUrl(Resource.class, url)) {
|
||||
if (r instanceof CanonicalResource) {
|
||||
res.add(((CanonicalResource) r).getVersion());
|
||||
CanonicalResource cr = (CanonicalResource) r;
|
||||
res.add(cr.hasVersion() ? cr.getVersion() : "{{unversioned}}");
|
||||
}
|
||||
}
|
||||
if (fetcher != null) {
|
||||
|
|
|
@ -5724,6 +5724,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
String pub = element.getNamedChildValue("publisher", false);
|
||||
Base wgT = element.getExtensionValue(ToolingExtensions.EXT_WORKGROUP);
|
||||
String wg = wgT == null ? null : wgT.primitiveValue();
|
||||
String url = element.getNamedChildValue("url");
|
||||
|
||||
if (contained && wg == null) {
|
||||
boolean ok = true;
|
||||
|
@ -5750,7 +5751,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
List<String> urls = new ArrayList<>();
|
||||
for (Element c : element.getChildren("contact")) {
|
||||
for (Element t : c.getChildren("telecom")) {
|
||||
|
@ -5760,7 +5760,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
|||
}
|
||||
}
|
||||
|
||||
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null, I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) {
|
||||
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null && !url.contains("http://hl7.org/fhir/sid"), I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) {
|
||||
HL7WorkGroup wgd = HL7WorkGroups.find(wg);
|
||||
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wgd != null, I18nConstants.VALIDATION_HL7_WG_UNKNOWN, wg)) {
|
||||
String rpub = "HL7 International / "+wgd.getName();
|
||||
|
|
|
@ -187,6 +187,11 @@ public class CodeSystemValidator extends BaseValidator {
|
|||
for (Element concept : concepts) {
|
||||
ok = checkConcept(errors, cs, stack.push(concept, i, null, null), "true".equals(caseSensitive), hierarchyMeaning, csB, concept, codes, properties) && ok;
|
||||
i++;
|
||||
}
|
||||
i = 0;
|
||||
for (Element concept : concepts) {
|
||||
ok = checkConceptProps(errors, cs, stack.push(concept, i, null, null), "true".equals(caseSensitive), hierarchyMeaning, csB, concept, codes, properties) && ok;
|
||||
i++;
|
||||
}
|
||||
|
||||
return ok;
|
||||
|
@ -250,7 +255,6 @@ public class CodeSystemValidator extends BaseValidator {
|
|||
ok = false;
|
||||
rule(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), false, I18nConstants.CODESYSTEM_PROPERTY_BAD_HL7_URI, uri);
|
||||
}
|
||||
warning(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), ukp != KnownProperty.Synonym, I18nConstants.CODESYSTEM_PROPERTY_SYNONYM_DEPRECATED);
|
||||
}
|
||||
}
|
||||
if (code != null) {
|
||||
|
@ -336,19 +340,8 @@ public class CodeSystemValidator extends BaseValidator {
|
|||
}
|
||||
}
|
||||
|
||||
// todo: check that all the properties are defined.
|
||||
// check that all the defined properties have values
|
||||
// check the designations have values, and the use/language don't conflict
|
||||
|
||||
List<Element> propertyElements = concept.getChildrenByName("property");
|
||||
int i = 0;
|
||||
for (Element propertyElement : propertyElements) {
|
||||
ok = checkPropertyValue(errors, cs, stack.push(propertyElement, i, null, null), propertyElement, properties) && ok;
|
||||
i++;
|
||||
}
|
||||
|
||||
List<Element> designations = concept.getChildrenByName("designation");
|
||||
i = 0;
|
||||
int i = 0;
|
||||
for (Element designation : designations) {
|
||||
ok = checkDesignation(errors, cs, stack.push(designation, i, null, null), concept, designation) && ok;
|
||||
i++;
|
||||
|
@ -362,6 +355,25 @@ public class CodeSystemValidator extends BaseValidator {
|
|||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean checkConceptProps(List<ValidationMessage> errors, Element cs, NodeStack stack, boolean caseSensitive, String hierarchyMeaning, CodeSystem csB, Element concept, Set<String> codes, Map<String, PropertyDef> properties) {
|
||||
boolean ok = true;
|
||||
|
||||
List<Element> propertyElements = concept.getChildrenByName("property");
|
||||
int i = 0;
|
||||
for (Element propertyElement : propertyElements) {
|
||||
ok = checkPropertyValue(errors, cs, stack.push(propertyElement, i, null, null), propertyElement, properties, codes) && ok;
|
||||
i++;
|
||||
}
|
||||
|
||||
List<Element> concepts = concept.getChildrenByName("concept");
|
||||
i = 0;
|
||||
for (Element child : concepts) {
|
||||
ok = checkConceptProps(errors, cs, stack.push(concept, i, null, null), caseSensitive, hierarchyMeaning, csB, child, codes, properties) && ok;
|
||||
i++;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean checkDesignation(List<ValidationMessage> errors, Element cs, NodeStack stack, Element concept, Element designation) {
|
||||
boolean ok = true;
|
||||
|
@ -391,7 +403,7 @@ public class CodeSystemValidator extends BaseValidator {
|
|||
return ok;
|
||||
}
|
||||
|
||||
private boolean checkPropertyValue(List<ValidationMessage> errors, Element cs, NodeStack stack, Element property, Map<String, PropertyDef> properties) {
|
||||
private boolean checkPropertyValue(List<ValidationMessage> errors, Element cs, NodeStack stack, Element property, Map<String, PropertyDef> properties, Set<String> codes) {
|
||||
boolean ok = true;
|
||||
|
||||
String code = property.getNamedChildValue("code");
|
||||
|
@ -405,6 +417,10 @@ public class CodeSystemValidator extends BaseValidator {
|
|||
} else {
|
||||
ok = false;
|
||||
}
|
||||
if ("synonym".equals(code)) {
|
||||
String vcode = value.isPrimitive() ? value.primitiveValue() : null;
|
||||
warning(errors, "2024-03-06", IssueType.BUSINESSRULE, cs.line(), cs.col(), stack.getLiteralPath(), codes.contains(vcode), I18nConstants.CODESYSTEM_PROPERTY_SYNONYM_CHECK, vcode);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
|
|
@ -232,7 +232,7 @@ public class ConceptMapValidator extends BaseValidator {
|
|||
} else {
|
||||
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), sourceScope != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_UNKNOWN, e.getValue());
|
||||
}
|
||||
if (ctxt.source.version == null && ctxt.source.cs != null) {
|
||||
if (ctxt.source.version == null && ctxt.source.cs != null && !CodeSystemUtilities.isExemptFromMultipleVersionChecking(ctxt.source.url)) {
|
||||
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(null, valContext.getAppContext(), ctxt.source.url);
|
||||
warning(errors, NO_RULE_DATE, IssueType.INVALID, grp.line(), grp.col(), stack.getLiteralPath(), possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
|
||||
ctxt.source.url, ctxt.source.cs.getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
|
||||
|
@ -249,7 +249,7 @@ public class ConceptMapValidator extends BaseValidator {
|
|||
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), targetScope != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_UNKNOWN, e.getValue());
|
||||
|
||||
}
|
||||
if (ctxt.target.version == null && ctxt.target.cs != null) {
|
||||
if (ctxt.target.version == null && ctxt.target.cs != null && !CodeSystemUtilities.isExemptFromMultipleVersionChecking(ctxt.target.url)) {
|
||||
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(null, valContext.getAppContext(), ctxt.target.url);
|
||||
warning(errors, NO_RULE_DATE, IssueType.INVALID, grp.line(), grp.col(), stack.getLiteralPath(), possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
|
||||
ctxt.target.url, ctxt.target.cs.getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
package org.hl7.fhir.validation.instance.type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.r5.elementmodel.Element;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.Enumeration;
|
||||
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.utilities.CodingValidationRequest;
|
||||
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
|
||||
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
|
||||
|
@ -24,13 +31,73 @@ import org.hl7.fhir.validation.codesystem.CodeSystemChecker;
|
|||
import org.hl7.fhir.validation.codesystem.GeneralCodeSystemChecker;
|
||||
import org.hl7.fhir.validation.codesystem.SnomedCTChecker;
|
||||
import org.hl7.fhir.validation.instance.InstanceValidator;
|
||||
import org.hl7.fhir.validation.instance.type.ValueSetValidator.PropertyOperation;
|
||||
import org.hl7.fhir.validation.instance.type.ValueSetValidator.PropertyValidationRules;
|
||||
import org.hl7.fhir.validation.instance.utils.NodeStack;
|
||||
import org.hl7.fhir.validation.instance.utils.ValidationContext;
|
||||
|
||||
public class ValueSetValidator extends BaseValidator {
|
||||
public enum PropertyOperation {
|
||||
Equals, IsA, DescendentOf, IsNotA, RegEx, In, NotIn, Generalizes, ChildOf, DescendentLeaf, Exists;
|
||||
|
||||
public String toString() {
|
||||
switch (this) {
|
||||
case ChildOf: return "child-of";
|
||||
case DescendentLeaf: return "descendent-leaf";
|
||||
case DescendentOf: return "descendent-of";
|
||||
case Equals: return "=";
|
||||
case Exists: return "exists";
|
||||
case Generalizes: return "generalizes";
|
||||
case In: return "in";
|
||||
case IsA: return "is-a";
|
||||
case IsNotA: return "is-not-a";
|
||||
case NotIn: return "not-in";
|
||||
case RegEx: return "regex";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class PropertyValidationRules {
|
||||
boolean repeating;
|
||||
PropertyFilterType type;
|
||||
EnumSet<PropertyOperation> ops;
|
||||
protected PropertyValidationRules(boolean repeating, PropertyFilterType type, PropertyOperation... ops) {
|
||||
super();
|
||||
this.repeating = repeating;
|
||||
this.type = type;
|
||||
this.ops = EnumSet.noneOf(PropertyOperation.class);
|
||||
for (PropertyOperation op : ops) {
|
||||
this.ops.add(op);
|
||||
}
|
||||
}
|
||||
public PropertyValidationRules(boolean repeating, PropertyFilterType type, EnumSet<PropertyOperation> ops) {
|
||||
super();
|
||||
this.repeating = repeating;
|
||||
this.type = type;
|
||||
this.ops = ops;
|
||||
}
|
||||
|
||||
public boolean isRepeating() {
|
||||
return repeating;
|
||||
}
|
||||
public PropertyFilterType getType() {
|
||||
return type;
|
||||
}
|
||||
public EnumSet<PropertyOperation> getOps() {
|
||||
return ops;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public enum PropertyFilterType {
|
||||
Boolean, Integer, Decimal, Code, DateTime, ValidCode, Coding
|
||||
}
|
||||
|
||||
private static final int TOO_MANY_CODES_TO_VALIDATE = 1000;
|
||||
|
||||
|
||||
private CodeSystemChecker getSystemValidator(String system, List<ValidationMessage> errors) {
|
||||
if (system == null) {
|
||||
return new GeneralCodeSystemChecker(context, xverManager, debug, errors);
|
||||
|
@ -40,7 +107,7 @@ public class ValueSetValidator extends BaseValidator {
|
|||
default: return new GeneralCodeSystemChecker(context, xverManager, debug, errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class VSCodingValidationRequest extends CodingValidationRequest {
|
||||
|
||||
private NodeStack stack;
|
||||
|
@ -53,13 +120,13 @@ public class ValueSetValidator extends BaseValidator {
|
|||
public NodeStack getStack() {
|
||||
return stack;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public ValueSetValidator(InstanceValidator parent) {
|
||||
super(parent);
|
||||
}
|
||||
|
||||
|
||||
public boolean validateValueSet(ValidationContext valContext, List<ValidationMessage> errors, Element vs, NodeStack stack) {
|
||||
boolean ok = true;
|
||||
if (!VersionUtilities.isR2Ver(context.getVersion())) {
|
||||
|
@ -80,22 +147,22 @@ public class ValueSetValidator extends BaseValidator {
|
|||
if (parent.isForPublication()) {
|
||||
if (isHL7(vs)) {
|
||||
boolean ok = true;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("url", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "url") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("version", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "version") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("title", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "title") && ok;
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("name", false), I18nConstants.VALUESET_SHAREABLE_EXTRA_MISSING_HL7, "name");
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("status", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "status") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("experimental", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "experimental") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("description", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "description") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("url", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "url") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("version", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "version") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("title", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "title") && ok;
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("name", false), I18nConstants.VALUESET_SHAREABLE_EXTRA_MISSING_HL7, "name");
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("status", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "status") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("experimental", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "experimental") && ok;
|
||||
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("description", false), I18nConstants.VALUESET_SHAREABLE_MISSING_HL7, "description") && ok;
|
||||
return ok;
|
||||
} else {
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("url", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "url");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("version", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "version");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("title", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "title");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("name", false), I18nConstants.VALUESET_SHAREABLE_EXTRA_MISSING, "name");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("status", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "status");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("experimental", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "experimental");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, vs.line(), vs.col(), stack.getLiteralPath(), vs.hasChild("description", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "description");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("url", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "url");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("version", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "version");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("title", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "title");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("name", false), I18nConstants.VALUESET_SHAREABLE_EXTRA_MISSING, "name");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("status", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "status");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("experimental", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "experimental");
|
||||
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, stack, vs.hasChild("description", false), I18nConstants.VALUESET_SHAREABLE_MISSING, "description");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -118,7 +185,7 @@ public class ValueSetValidator extends BaseValidator {
|
|||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
private boolean validateValueSetInclude(ValidationContext valContext, List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
|
||||
boolean ok = true;
|
||||
String system = include.getChildValue("system");
|
||||
|
@ -143,17 +210,17 @@ public class ValueSetValidator extends BaseValidator {
|
|||
i++;
|
||||
}
|
||||
if (valuesets.size() > 1) {
|
||||
warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, stack.getLiteralPath(), false, I18nConstants.VALUESET_IMPORT_UNION_INTERSECTION);
|
||||
warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, stack, false, I18nConstants.VALUESET_IMPORT_UNION_INTERSECTION);
|
||||
}
|
||||
if (system != null) {
|
||||
rule(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), Utilities.isAbsoluteUrl(system), system.startsWith("#") ? I18nConstants.VALUESET_INCLUDE_SYSTEM_ABSOLUTE_FRAG : I18nConstants.VALUESET_INCLUDE_SYSTEM_ABSOLUTE, system);
|
||||
rule(errors, "2024-03-06", IssueType.INVALID, stack, Utilities.isAbsoluteUrl(system), system.startsWith("#") ? I18nConstants.VALUESET_INCLUDE_SYSTEM_ABSOLUTE_FRAG : I18nConstants.VALUESET_INCLUDE_SYSTEM_ABSOLUTE, system);
|
||||
if (system.startsWith("#")) {
|
||||
List<Element> cs = new ArrayList<>();
|
||||
for (Element contained : vsSrc.getChildrenByName("contained")) {
|
||||
if (("#"+contained.getIdBase()).equals(system)) {
|
||||
ok = false; // see absolute check above.
|
||||
|
||||
if (rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), "CodeSystem".equals(contained.fhirType()), I18nConstants.VALUESET_INCLUDE_CS_NOT_CS, system, contained.fhirType())) {
|
||||
if (rule(errors, "2024-02-10", IssueType.INVALID, stack, "CodeSystem".equals(contained.fhirType()), I18nConstants.VALUESET_INCLUDE_CS_NOT_CS, system, contained.fhirType())) {
|
||||
if (version == null || version.equals(contained.getChildValue("version"))) {
|
||||
cs.add(contained);
|
||||
}
|
||||
|
@ -161,16 +228,16 @@ public class ValueSetValidator extends BaseValidator {
|
|||
}
|
||||
}
|
||||
if (cs.isEmpty()) {
|
||||
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_NOT_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_NOT_FOUND, system, version) && ok;
|
||||
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack, false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_NOT_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_NOT_FOUND, system, version) && ok;
|
||||
} else {
|
||||
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), cs.size() == 1, version == null ? I18nConstants.VALUESET_INCLUDE_CS_MULTI_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_MULTI_FOUND, system, version) && ok;
|
||||
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack, cs.size() == 1, version == null ? I18nConstants.VALUESET_INCLUDE_CS_MULTI_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_MULTI_FOUND, system, version) && ok;
|
||||
}
|
||||
}
|
||||
if (version == null) {
|
||||
CodeSystem cs = context.fetchCodeSystem(system);
|
||||
if (cs != null) {
|
||||
if (cs != null && !CodeSystemUtilities.isExemptFromMultipleVersionChecking(system)) {
|
||||
Set<String> possibleVersions = fetcher.fetchCanonicalResourceVersions(null, valContext.getAppContext(), system);
|
||||
warning(errors, NO_RULE_DATE, IssueType.INVALID, include.line(), include.col(), stack.getLiteralPath(), possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
|
||||
warning(errors, NO_RULE_DATE, IssueType.INVALID, stack, possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
|
||||
system, cs.getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
|
||||
}
|
||||
}
|
||||
|
@ -179,24 +246,25 @@ public class ValueSetValidator extends BaseValidator {
|
|||
List<Element> filters = include.getChildrenByName("filter");
|
||||
|
||||
CodeSystemChecker slv = getSystemValidator(system, errors);
|
||||
CodeSystem cs = null;
|
||||
if (!Utilities.noString(system)) {
|
||||
CodeSystem cs = context.fetchCodeSystem(system, version);
|
||||
cs = context.fetchCodeSystem(system, version);
|
||||
if (cs != null) { // if it's null, we can't analyse this
|
||||
switch (cs.getContent()) {
|
||||
case EXAMPLE:
|
||||
warning(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_CONTENT : I18nConstants.VALUESET_INCLUDE_CSVER_CONTENT, system, cs.getContent().toCode(), version);
|
||||
warning(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 FRAGMENT:
|
||||
hint(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_CONTENT : I18nConstants.VALUESET_INCLUDE_CSVER_CONTENT, system, cs.getContent().toCode(), version);
|
||||
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:
|
||||
ok = rule(errors, "2024-03-06", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_SUPPLEMENT : I18nConstants.VALUESET_INCLUDE_CSVER_SUPPLEMENT, system, cs.getSupplements(), version) && ok;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
boolean systemOk = true;
|
||||
int cc = 0;
|
||||
List<VSCodingValidationRequest> batch = new ArrayList<>();
|
||||
|
@ -213,7 +281,7 @@ public class ValueSetValidator extends BaseValidator {
|
|||
}
|
||||
if (((InstanceValidator) parent).isValidateValueSetCodesOnTxServer() && batch.size() > 0 & !context.isNoTerminologyServer()) {
|
||||
if (batch.size() > TOO_MANY_CODES_TO_VALIDATE) {
|
||||
ok = hint(errors, "2023-09-06", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.VALUESET_INC_TOO_MANY_CODES, batch.size()) && ok;
|
||||
ok = hint(errors, "2023-09-06", IssueType.BUSINESSRULE, stack, false, I18nConstants.VALUESET_INC_TOO_MANY_CODES, batch.size()) && ok;
|
||||
} else {
|
||||
long t = System.currentTimeMillis();
|
||||
if (parent.isDebug()) {
|
||||
|
@ -241,14 +309,12 @@ public class ValueSetValidator extends BaseValidator {
|
|||
|
||||
int cf = 0;
|
||||
for (Element filter : filters) {
|
||||
if (systemOk && !validateValueSetIncludeFilter(errors, include, stack.push(filter, cf, null, null), system, version, slv)) {
|
||||
systemOk = false;
|
||||
}
|
||||
ok = validateValueSetIncludeFilter(errors, filter, stack.push(filter, cf, null, null), system, version, cs, slv) & ok;
|
||||
cf++;
|
||||
}
|
||||
slv.finish(include, stack);
|
||||
} else {
|
||||
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING);
|
||||
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
@ -258,7 +324,7 @@ public class ValueSetValidator extends BaseValidator {
|
|||
String code = concept.getChildValue("code");
|
||||
String display = concept.getChildValue("display");
|
||||
slv.checkConcept(code, display);
|
||||
|
||||
|
||||
if (version == null) {
|
||||
ValidationResult vv = context.validateCode(ValidationOptions.defaults(), new Coding(system, code, null), null);
|
||||
if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
|
||||
|
@ -274,9 +340,9 @@ public class ValueSetValidator extends BaseValidator {
|
|||
return false;
|
||||
} else {
|
||||
boolean ok = vv.isOk();
|
||||
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code);
|
||||
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code);
|
||||
if (vv.getMessage() != null) {
|
||||
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, vv.getMessage());
|
||||
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, false, vv.getMessage());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -286,9 +352,9 @@ public class ValueSetValidator extends BaseValidator {
|
|||
return false;
|
||||
} else {
|
||||
boolean ok = vv.isOk();
|
||||
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code);
|
||||
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code);
|
||||
if (vv.getMessage() != null) {
|
||||
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), false, vv.getMessage());
|
||||
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, false, vv.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,19 +365,282 @@ public class ValueSetValidator extends BaseValidator {
|
|||
String code = concept.getChildValue("code");
|
||||
String display = concept.getChildValue("display");
|
||||
slv.checkConcept(code, display);
|
||||
|
||||
|
||||
Coding c = new Coding(system, code, null);
|
||||
if (version != null) {
|
||||
c.setVersion(version);
|
||||
c.setVersion(version);
|
||||
}
|
||||
return new VSCodingValidationRequest(stack, c);
|
||||
}
|
||||
|
||||
private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element filter, NodeStack push, String system, String version, CodeSystemChecker slv) {
|
||||
//
|
||||
// String display = concept.getChildValue("display");
|
||||
// slv.checkConcept(code, display);
|
||||
private boolean validateValueSetIncludeFilter(List<ValidationMessage> errors, Element filter, NodeStack stack, String system, String version, CodeSystem cs, CodeSystemChecker slv) {
|
||||
boolean ok = true;
|
||||
String property = filter.getChildValue("property");
|
||||
String op = filter.getChildValue("op");
|
||||
String value = filter.getChildValue("value");
|
||||
|
||||
if (property != null) {
|
||||
List<String> knownNames = new ArrayList<>();
|
||||
knownNames.add("concept");
|
||||
knownNames.add("code");
|
||||
knownNames.add("status");
|
||||
knownNames.add("inactive");
|
||||
knownNames.add("effectiveDate");
|
||||
knownNames.add("deprecationDate");
|
||||
knownNames.add("retirementDate");
|
||||
knownNames.add("notSelectable");
|
||||
if (cs == null || cs.hasHierarchyMeaning()) {
|
||||
knownNames.add("parent");
|
||||
knownNames.add("child");
|
||||
knownNames.add("partOf");
|
||||
}
|
||||
knownNames.add("synonym");
|
||||
knownNames.add("comment");
|
||||
knownNames.add("itemWeight");
|
||||
if (cs != null) {
|
||||
for (CodeSystemFilterComponent f : cs.getFilter()) {
|
||||
addName(knownNames, f.getCode());
|
||||
}
|
||||
for (PropertyComponent p : cs.getProperty()) {
|
||||
addName(knownNames, p.getCode());
|
||||
}
|
||||
}
|
||||
for (String s : getSystemKnownNames(system)) {
|
||||
addName(knownNames, s);
|
||||
}
|
||||
boolean pok = false;
|
||||
if (cs == null) {
|
||||
pok = hint(errors, "2024-03-09", IssueType.INVALID, stack, knownNames.contains(property), I18nConstants.VALUESET_UNKNOWN_FILTER_PROPERTY_NO_CS, property, system, CommaSeparatedStringBuilder.join(",", knownNames));
|
||||
} else {
|
||||
pok = warning(errors, "2024-03-09", IssueType.INVALID, stack, knownNames.contains(property), I18nConstants.VALUESET_UNKNOWN_FILTER_PROPERTY, property, system, CommaSeparatedStringBuilder.join(",", knownNames));
|
||||
}
|
||||
if (pok) {
|
||||
PropertyValidationRules rules = rulesForFilter(system, cs, property);
|
||||
if (rules != null) {
|
||||
if (!rules.getOps().isEmpty()) {
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, opInSet(op, rules.getOps()), I18nConstants.VALUESET_BAD_FILTER_OP, op, property, CommaSeparatedStringBuilder.join(",", rules.getOps())) && ok;
|
||||
}
|
||||
|
||||
if ("exists".equals(op)) {
|
||||
ok = checkFilterValue(errors, stack, system, version, ok, property, value, PropertyFilterType.Boolean) && ok;
|
||||
} else if (rules.isRepeating()) {
|
||||
for (String v : value.split("\\,")) {
|
||||
ok = checkFilterValue(errors, stack, system, version, ok, property, v, rules.getType()) && ok;
|
||||
}
|
||||
} else {
|
||||
ok = checkFilterValue(errors, stack, system, version, ok, property, value, rules.getType()) && ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
private boolean opInSet(String op, EnumSet<PropertyOperation> ops) {
|
||||
switch (op) {
|
||||
case "=": return ops.contains(PropertyOperation.Equals);
|
||||
case "is-a": return ops.contains(PropertyOperation.IsA);
|
||||
case "descendent-of": return ops.contains(PropertyOperation.DescendentOf);
|
||||
case "is-not-a": return ops.contains(PropertyOperation.IsNotA);
|
||||
case "regex": return ops.contains(PropertyOperation.RegEx);
|
||||
case "in": return ops.contains(PropertyOperation.In);
|
||||
case "not-in": return ops.contains(PropertyOperation.NotIn);
|
||||
case "generalizes": return ops.contains(PropertyOperation.Generalizes);
|
||||
case "child-of": return ops.contains(PropertyOperation.ChildOf);
|
||||
case "descendent-leaf": return ops.contains(PropertyOperation.DescendentLeaf);
|
||||
case "exists": return ops.contains(PropertyOperation.Exists);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkFilterValue(List<ValidationMessage> errors, NodeStack stack, String system, String version,boolean ok, String property, String value, PropertyFilterType type) {
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
case Boolean:
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack,
|
||||
Utilities.existsInList(value, "true", "false"),
|
||||
I18nConstants.VALUESET_BAD_FILTER_VALUE_BOOLEAN, property, value) && ok;
|
||||
break;
|
||||
case Code:
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack,
|
||||
value.trim().equals(value),
|
||||
I18nConstants.VALUESET_BAD_FILTER_VALUE_CODE, property, value) && ok;
|
||||
break;
|
||||
case DateTime:
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(),
|
||||
value.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),
|
||||
I18nConstants.VALUESET_BAD_FILTER_VALUE_DATETIME, property, value) && ok;
|
||||
break;
|
||||
case Decimal:
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(),
|
||||
Utilities.isDecimal(value, true),
|
||||
I18nConstants.VALUESET_BAD_FILTER_VALUE_DECIMAL, property, value) && ok;
|
||||
break;
|
||||
case Integer:
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(),
|
||||
Utilities.isInteger(value),
|
||||
I18nConstants.VALUESET_BAD_FILTER_VALUE_INTEGER, property, value) && ok;
|
||||
break;
|
||||
case ValidCode :
|
||||
ValidationResult vr = context.validateCode(baseOptions, system, version, value, null);
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(),
|
||||
vr.isOk(),
|
||||
I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_CODE, property, value, system, vr.getMessage()) && ok;
|
||||
break;
|
||||
case Coding :
|
||||
Coding code = Coding.fromLiteral(value);
|
||||
if (code == null) {
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, false, I18nConstants.VALUESET_BAD_FILTER_VALUE_CODED, property, value) && ok;
|
||||
} else {
|
||||
vr = context.validateCode(baseOptions, code, null);
|
||||
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, vr.isOk(), I18nConstants.VALUESET_BAD_FILTER_VALUE_CODED_INVALID, property, value, vr.getMessage()) && ok;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private PropertyValidationRules rulesForFilter(String system, CodeSystem cs, String property) {
|
||||
var ops = EnumSet.noneOf(PropertyOperation.class);
|
||||
|
||||
return true;
|
||||
if (cs != null) {
|
||||
|
||||
for (CodeSystemFilterComponent f : cs.getFilter()) {
|
||||
if (property.equals(f.getCode())) {
|
||||
for (Enumeration<FilterOperator> op : f.getOperator()) {
|
||||
ops.add(toOp(op));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (PropertyComponent p : cs.getProperty()) {
|
||||
if (property.equals(p.getCode())) {
|
||||
if (p.getType() != null) {
|
||||
switch (p.getType()) {
|
||||
case BOOLEAN: return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
case CODE: return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
|
||||
case CODING: return new PropertyValidationRules(false, PropertyFilterType.Coding, ops);
|
||||
case DATETIME: return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
|
||||
case DECIMAL: return new PropertyValidationRules(false, PropertyFilterType.Decimal, ops);
|
||||
case INTEGER: return new PropertyValidationRules(false, PropertyFilterType.Integer, ops);
|
||||
case STRING: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (property) {
|
||||
case "concept" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf, PropertyOperation.DescendentLeaf, PropertyOperation.IsNotA, PropertyOperation.NotIn));
|
||||
case "code" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.RegEx));
|
||||
case "status" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
|
||||
case "inactive" : return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
case "effectiveDate" : return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
|
||||
case "deprecationDate" : return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
|
||||
case "retirementDate" : return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
|
||||
case "notSelectable" : return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
case "parent" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
|
||||
case "child" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
|
||||
case "partOf" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
|
||||
case "synonym" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
|
||||
case "comment" : return null;
|
||||
case "itemWeight" : return new PropertyValidationRules(false, PropertyFilterType.Decimal, ops);
|
||||
}
|
||||
switch (system) {
|
||||
case "http://loinc.org" :
|
||||
if (Utilities.existsInList(property, "copyright", "STATUS", "CLASS", "CONSUMER_NAME", "ORDER_OBS", "DOCUMENT_SECTION")) {
|
||||
return new PropertyValidationRules(false, PropertyFilterType.Code);
|
||||
} else if ("CLASSTYPE".equals(property)) {
|
||||
return new PropertyValidationRules(false, PropertyFilterType.Integer, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
|
||||
} else {
|
||||
return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
|
||||
}
|
||||
case "http://snomed.info/sct":
|
||||
switch (property) {
|
||||
case "constraint": return null; // for now
|
||||
case "expressions": return new PropertyValidationRules(false, PropertyFilterType.Boolean, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
|
||||
default:
|
||||
return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
|
||||
}
|
||||
case "http://www.nlm.nih.gov/research/umls/rxnorm" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
|
||||
case "http://unitsofmeasure.org" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
|
||||
case "http://www.ama-assn.org/go/cpt" :
|
||||
switch (property) {
|
||||
case "modifier": return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
case "kind" : return new PropertyValidationRules(true, PropertyFilterType.Code, ops); // for now
|
||||
case "modified": return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
case "code" : return null;
|
||||
case "telemedicine": return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
case "orthopox" : return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
|
||||
}
|
||||
}
|
||||
if (ops != null) {
|
||||
return new PropertyValidationRules(false, null, ops);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private EnumSet<PropertyOperation> addToOps(EnumSet<PropertyOperation> set, PropertyOperation... ops) {
|
||||
for (PropertyOperation op : ops) {
|
||||
set.add(op);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private PropertyOperation toOp(Enumeration<FilterOperator> op) {
|
||||
switch (op.getValue()) {
|
||||
case CHILDOF: return PropertyOperation.ChildOf;
|
||||
case DESCENDENTLEAF: return PropertyOperation.DescendentLeaf;
|
||||
case DESCENDENTOF: return PropertyOperation.DescendentOf;
|
||||
case EQUAL: return PropertyOperation.Equals;
|
||||
case EXISTS: return PropertyOperation.Exists;
|
||||
case GENERALIZES: return PropertyOperation.Generalizes;
|
||||
case IN: return PropertyOperation.In;
|
||||
case ISA: return PropertyOperation.IsA;
|
||||
case ISNOTA: return PropertyOperation.IsNotA;
|
||||
case NOTIN: return PropertyOperation.NotIn;
|
||||
case REGEX: return PropertyOperation.RegEx;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void addName(List<String> knownNames, String code) {
|
||||
if (code != null && !knownNames.contains(code)) {
|
||||
knownNames.add(code);
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getSystemKnownNames(String system) {
|
||||
switch (system) {
|
||||
case "http://loinc.org" : return new String[] {"parent", "ancestor", "copyright", "STATUS", "COMPONENT", "PROPERTY", "TIME_ASPCT", "SYSTEM", "SCALE_TYP", "METHOD_TYP", "CLASS", "CONSUMER_NAME", "CLASSTYPE", "ORDER_OBS", "DOCUMENT_SECTION"};
|
||||
|
||||
case "http://snomed.info/sct": return new String[] { "constraint", "expressions", "410662002", "42752001", "47429007", "116676008", "116686009", "118168003", "118169006", "118170007", "118171006", "127489000", "131195008",
|
||||
"246075003", "246090004", "246093002", "246112005", "246454002", "246456000", "246501002", "246513007", "246514001", "255234002", "260507000",
|
||||
"260686004", "260870009", "263502005", "272741003", "288556008", "363589002", "363698007", "363699004", "363700003", "363701004", "363702006",
|
||||
"363703001", "363704007", "363705008", "363709002", "363710007", "363713009", "363714003", "370129005", "370130000", "370131001", "370132008",
|
||||
"370133003", "370134009", "370135005", "371881003", "405813007", "405814001", "405815000", "405816004", "408729009", "408730004", "408731000",
|
||||
"408732007", "410675002", "411116001", "418775008", "419066007", "424226004", "424244007", "424361007", "424876005", "425391005", "609096000",
|
||||
"704319004", "704320005", "704321009", "704322002", "704323007", "704324001", "704325000", "704326004", "704327008", "704346009", "704347000",
|
||||
"704647008", "718497002", "719715003", "719722006", "726542003", "726633004", "732943007", "732945000", "732947008", "733722007", "733725009",
|
||||
"733928003", "733930001", "733931002", "733932009", "733933004", "734136001", "734137005", "736472000", "736473005", "736474004", "736475003",
|
||||
"736476002", "736518005", "738774007", "762705008", "762706009", "762949000", "762951001", "763032000", "766939001", "774081006", "774158006",
|
||||
"774159003", "774160008", "774163005", "827081001", "836358009", "840560000", "860779006", "860781008", "1003703000", "1003735000", "1142135004",
|
||||
"1142136003", "1142137007", "1142138002", "1142139005", "1142140007", "1142141006", "1142142004", "1142143009", "1148793005", "1148965004",
|
||||
"1148967007", "1148968002", "1148969005", "1149366004", "1149367008", "1230370004", "320091000221107" };
|
||||
// list from http://tx.fhir.org/r4/ValueSet/$expand?url=http://snomed.info/sct?fhir_vs=isa/410662002
|
||||
|
||||
case "http://www.nlm.nih.gov/research/umls/rxnorm" : return new String[] { "STY", "SAB", "TTY", "SY", "SIB", "RN", "PAR", "CHD", "RB", "RO", "IN", "PIN", "MIN", "BN", "SCD", "SBD", "GPCK", "BPCK", "SCDC", "SCDF", "SCDFP", "SCDG", "SCDGP", "SBDC", "SBDF", "SBDFP", "SBDG", "DF", "DFG" };
|
||||
case "http://unitsofmeasure.org" : return new String[] { "property", "canonical" };
|
||||
case "http://www.ama-assn.org/go/cpt" : return new String[] { "modifier", "kind", "modified", "code", "telemedicine", "orthopox" };
|
||||
case "urn:ietf:bcp:47" : return new String[] {"language", "region", "script", "variant", "extension", "ext-lang", "private-use" };
|
||||
default: return new String[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -704,7 +704,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -728,7 +727,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -751,7 +749,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -775,7 +772,6 @@ v: {
|
|||
"error" : "The provided code 'http://loinc.org#3141-9' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult--0|5.0.0'",
|
||||
"class" : "UNKNOWN",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome",
|
||||
"issue" : [{
|
||||
|
@ -816,7 +812,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -841,7 +836,6 @@ v: {
|
|||
"error" : "The provided code 'http://loinc.org#3141-9 ('Body weight Measured')' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult|5.0.0'",
|
||||
"class" : "UNKNOWN",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome",
|
||||
"issue" : [{
|
||||
|
@ -882,7 +876,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -906,7 +899,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -930,7 +922,6 @@ v: {
|
|||
"system" : "http://loinc.org",
|
||||
"version" : "2.74",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome"
|
||||
}
|
||||
|
@ -955,7 +946,6 @@ v: {
|
|||
"error" : "Wrong whitespace in Display Name 'Active motion ' for http://loinc.org#LA6715-2. Valid display is 'Active motion' (en-US) (for the language(s) '--')",
|
||||
"class" : "UNKNOWN",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome",
|
||||
"issue" : [{
|
||||
|
@ -997,7 +987,6 @@ v: {
|
|||
"error" : "Wrong whitespace in Display Name 'Good, strong cry; normal rate and effort of breathing ' for http://loinc.org#LA6727-7. Valid display is 'Good, strong cry; normal rate and effort of breathing' (en-US) (for the language(s) '--')",
|
||||
"class" : "UNKNOWN",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome",
|
||||
"issue" : [{
|
||||
|
@ -1021,3 +1010,43 @@ v: {
|
|||
|
||||
}
|
||||
-------------------------------------------------------------------------------------
|
||||
{"code" : {
|
||||
"system" : "http://loinc.org",
|
||||
"code" : "LP43571-6"
|
||||
}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"true", "profile": {
|
||||
"resourceType" : "Parameters",
|
||||
"parameter" : [{
|
||||
"name" : "profile-url",
|
||||
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
|
||||
}]
|
||||
}}####
|
||||
v: {
|
||||
"code" : "LP43571-6",
|
||||
"severity" : "error",
|
||||
"error" : "Unknown code 'LP43571-6' in the CodeSystem 'http://loinc.org' version '2.77'",
|
||||
"class" : "UNKNOWN",
|
||||
"server" : "http://tx-dev.fhir.org/r4",
|
||||
"unknown-systems" : "",
|
||||
"issues" : {
|
||||
"resourceType" : "OperationOutcome",
|
||||
"issue" : [{
|
||||
"extension" : [{
|
||||
"url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server",
|
||||
"valueUrl" : "http://tx-dev.fhir.org/r4"
|
||||
}],
|
||||
"severity" : "error",
|
||||
"code" : "code-invalid",
|
||||
"details" : {
|
||||
"coding" : [{
|
||||
"system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
|
||||
"code" : "invalid-code"
|
||||
}],
|
||||
"text" : "Unknown code 'LP43571-6' in the CodeSystem 'http://loinc.org' version '2.77'"
|
||||
},
|
||||
"location" : ["Coding.code"],
|
||||
"expression" : ["Coding.code"]
|
||||
}]
|
||||
}
|
||||
|
||||
}
|
||||
-------------------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue