make HL7 WG validation rule publication dependent

This commit is contained in:
Grahame Grieve 2024-11-19 12:47:04 +11:00
parent a0b87c7947
commit d7d2ea50f3
7 changed files with 97 additions and 75 deletions

View File

@ -176,6 +176,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi
protected XVerExtensionManager xverManager;
protected IValidatorResourceFetcher fetcher;
protected IValidationPolicyAdvisor policyAdvisor;
protected boolean noTerminologyChecks;
// these two related to removing warnings on extensible bindings in structures that have derivatives that replace their bindings
protected List<TrackedLocationRelatedMessage> trackedMessages = new ArrayList<>();
@ -235,6 +236,7 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi
this.baseOptions = parent.baseOptions;
this.fetcher = parent.fetcher;
this.policyAdvisor = parent.policyAdvisor;
this.noTerminologyChecks = parent.noTerminologyChecks;
}
private boolean doingLevel(IssueSeverity error) {

View File

@ -8,6 +8,7 @@ import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.validation.ValidatorSession;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
@ -76,8 +77,20 @@ public abstract class CodeSystemChecker extends BaseValidator {
public PropertyValidationRules rulesForFilter(String property, EnumSet<PropertyOperation> ops) {
switch (property) {
case "concept" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf, PropertyOperation.DescendentLeaf, PropertyOperation.IsNotA, PropertyOperation.NotIn));
case "code" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, addToOps(ops, PropertyOperation.Equals, PropertyOperation.RegEx));
case "concept" :
return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error,
VersionUtilities.isR5Plus(context.getVersion()) ?
addToOps(ops, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf,
PropertyOperation.DescendentLeaf, PropertyOperation.IsNotA, PropertyOperation.Generalizes, PropertyOperation.ChildOf, PropertyOperation.NotIn) :
addToOps(ops, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf,
PropertyOperation.IsNotA, PropertyOperation.Generalizes, PropertyOperation.NotIn));
case "code" :
return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error,
VersionUtilities.isR5Plus(context.getVersion()) ?
addToOps(ops, PropertyOperation.RegEx, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf,
PropertyOperation.DescendentLeaf, PropertyOperation.IsNotA, PropertyOperation.Generalizes, PropertyOperation.ChildOf, PropertyOperation.NotIn) :
addToOps(ops, PropertyOperation.RegEx, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf,
PropertyOperation.IsNotA, PropertyOperation.Generalizes, PropertyOperation.NotIn));
case "status" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None, ops);
case "inactive" : return new PropertyValidationRules(PropertyFilterType.Boolean,null, ops);
case "effectiveDate" : return new PropertyValidationRules(PropertyFilterType.DateTime, null, ops);

View File

@ -565,7 +565,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean errorForUnknownProfiles;
private boolean noInvariantChecks;
private boolean wantInvariantInMessage;
private boolean noTerminologyChecks;
private boolean hintAboutNonMustSupport;
private boolean showMessagesFromReferences;
private String validationLanguage;
@ -6010,7 +6009,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
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)) {
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null || url.contains("http://hl7.org/fhir/sid") || !forPublication, I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) {
if (wg != null) {
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)) {

View File

@ -638,7 +638,7 @@ public class CodeSystemValidator extends BaseValidator {
private boolean validateSupplementConcept(List<ValidationMessage> errors, Element concept, NodeStack stack, String supp, ValidationOptions options) {
String code = concept.getChildValue("code");
if (!Utilities.noString(code)) {
if (!Utilities.noString(code) && !noTerminologyChecks) {
var canonical = new CanonicalPair(supp);
org.hl7.fhir.r5.terminologies.utilities.ValidationResult res = context.validateCode(options, canonical.getUrl(), canonical.getVersion(), code, null);
return rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), res.isOk(), I18nConstants.CODESYSTEM_CS_SUPP_INVALID_CODE, supp, code);

View File

@ -161,7 +161,7 @@ public class ConceptMapValidator extends BaseValidator {
if (!batch.isEmpty()) {
if (batch.size() > TOO_MANY_CODES_TO_VALIDATE) {
ok = hint(errors, "2023-09-06", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.CONCEPTMAP_VS_TOO_MANY_CODES, batch.size()) && ok;
} else {
} else if (!noTerminologyChecks) {
try {
long t = System.currentTimeMillis();
context.validateCodeBatch(ValidationOptions.defaults(), batch, null);
@ -313,7 +313,7 @@ public class ConceptMapValidator extends BaseValidator {
if (display != null) {
warning(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), CodeSystemUtilities.checkDisplay(ctxt.source.cs, cd, display.getValue()), I18nConstants.CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID, display.getValue(), CommaSeparatedStringBuilder.joinWrapped(", ", "'", "'", CodeSystemUtilities.getDisplays(ctxt.source.cs, cd)), ctxt.source.cs.getVersionedUrl()+"#"+cd.getCode());
}
if (ctxt.hasSourceVS() && ctxt.source != null) {
if (!noTerminologyChecks && ctxt.hasSourceVS() && ctxt.source != null) {
ValidationResult vr = context.validateCode(options.withCheckValueSetOnly().withNoServer(), ctxt.source.url, ctxt.source.version, c, null, ctxt.sourceScope.vs);
if (!warningOrError(ctxt.source.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-09-06", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), vr.isOk(), I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID_VS, c, ctxt.sourceScope.vs.getVersionedUrl())) {
ok = (ctxt.source.cs.getContent() != CodeSystemContentMode.COMPLETE) & ok;
@ -350,7 +350,7 @@ public class ConceptMapValidator extends BaseValidator {
if (display != null) {
warning(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), CodeSystemUtilities.checkDisplay(ctxt.target.cs, cd, display.getValue()), I18nConstants.CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID, display.getValue(), CommaSeparatedStringBuilder.joinWrapped(", ", "'", "'", CodeSystemUtilities.getDisplays(ctxt.target.cs, cd)), ctxt.target.cs.getVersionedUrl()+"#"+cd.getCode());
}
if (ctxt.hasTargetVS() && ctxt.target != null) {
if (!!noTerminologyChecks && ctxt.hasTargetVS() && ctxt.target != null) {
ValidationResult vr = context.validateCode(options.withCheckValueSetOnly().withNoServer(), ctxt.target.url, ctxt.target.version, c, null, ctxt.targetScope.vs);
if (!warningOrError(ctxt.target.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-09-06", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), vr.isOk(), I18nConstants.CONCEPTMAP_GROUP_TARGET_CODE_INVALID_VS, c, ctxt.targetScope.vs.getVersionedUrl())) {
ok = (ctxt.target.cs.getContent() != CodeSystemContentMode.COMPLETE) && ok;

View File

@ -767,20 +767,22 @@ public class QuestionnaireValidator extends BaseValidator {
}
}
long t = System.nanoTime();
ValidationContextCarrier vc = makeValidationContext(errors, qSrc);
ValidationResult res = context.validateCode(new ValidationOptions(FhirPublication.R5, stack.getWorkingLang()), c, vs, vc);
timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
if (!res.isOk()) {
if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
txWarning(errors, NO_RULE_DATE, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_BADOPTION_CS, c.getSystem(), c.getCode(), vs.present());
} else {
ok = txRule(errors, NO_RULE_DATE, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_BADOPTION, c.getSystem(), c.getCode(), vs.present(), res.getMessage()) && ok;
if (!noTerminologyChecks) {
long t = System.nanoTime();
ValidationContextCarrier vc = makeValidationContext(errors, qSrc);
ValidationResult res = context.validateCode(new ValidationOptions(FhirPublication.R5, stack.getWorkingLang()), c, vs, vc);
timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
if (!res.isOk()) {
if (res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
txWarning(errors, NO_RULE_DATE, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_BADOPTION_CS, c.getSystem(), c.getCode(), vs.present());
} else {
ok = txRule(errors, NO_RULE_DATE, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_BADOPTION, c.getSystem(), c.getCode(), vs.present(), res.getMessage()) && ok;
}
} else if (res.getSeverity() != null) {
super.addValidationMessage(errors, NO_RULE_DATE, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), res.getMessage(), res.getSeverity(), Source.TerminologyEngine, null);
} else if (res.getMessage() != null) {
super.addValidationMessage(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, value.line(), value.col(), stack.getLiteralPath(), res.getMessage(), res.getSeverity() == null ? IssueSeverity.INFORMATION : res.getSeverity(), Source.TerminologyEngine, null);
}
} else if (res.getSeverity() != null) {
super.addValidationMessage(errors, NO_RULE_DATE, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), res.getMessage(), res.getSeverity(), Source.TerminologyEngine, null);
} else if (res.getMessage() != null) {
super.addValidationMessage(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, value.line(), value.col(), stack.getLiteralPath(), res.getMessage(), res.getSeverity() == null ? IssueSeverity.INFORMATION : res.getSeverity(), Source.TerminologyEngine, null);
}
} catch (Exception e) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_CODING, e.getMessage());

View File

@ -98,9 +98,11 @@ public class ValueSetValidator extends BaseValidator {
public PropertyFilterType getType() {
return type;
}
public EnumSet<PropertyOperation> getOps() {
return ops;
}
public CodeValidationRule getCodeValidation() {
return codeValidation;
}
@ -316,34 +318,36 @@ public class ValueSetValidator extends BaseValidator {
}
}
boolean systemOk = true;
int cc = 0;
List<VSCodingValidationRequest> batch = new ArrayList<>();
boolean first = true;
if (concepts.size() > TOO_MANY_CODES_TO_VALIDATE) {
hint(errors, "2023-09-06", IssueType.BUSINESSRULE, stack, false, I18nConstants.VALUESET_INC_TOO_MANY_CODES, batch.size());
} else {
if (((InstanceValidator) parent).isValidateValueSetCodesOnTxServer() && !context.isNoTerminologyServer()) {
try {
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);
first = false;
} else if (systemOk) {
batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version, csChecker));
if (batch.size() > VALIDATION_BATCH_SIZE) {
executeValidationBatch(errors, vsid, retired, system, version, batch);
batch.clear();
if (!noTerminologyChecks) {
boolean systemOk = true;
int cc = 0;
List<VSCodingValidationRequest> batch = new ArrayList<>();
boolean first = true;
if (concepts.size() > TOO_MANY_CODES_TO_VALIDATE) {
hint(errors, "2023-09-06", IssueType.BUSINESSRULE, stack, false, I18nConstants.VALUESET_INC_TOO_MANY_CODES, batch.size());
} else {
if (((InstanceValidator) parent).isValidateValueSetCodesOnTxServer() && !context.isNoTerminologyServer()) {
try {
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);
first = false;
} else if (systemOk) {
batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version, csChecker));
if (batch.size() > VALIDATION_BATCH_SIZE) {
executeValidationBatch(errors, vsid, retired, system, version, batch);
batch.clear();
}
}
}
cc++;
}
executeValidationBatch(errors, vsid, retired, system, version, batch);
} catch (Exception e) {
ok = false;
VSCodingValidationRequest cv = batch.get(0);
rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, cv.getStack().getLiteralPath(), false, e.getMessage());
cc++;
}
executeValidationBatch(errors, vsid, retired, system, version, batch);
} catch (Exception e) {
ok = false;
VSCodingValidationRequest cv = batch.get(0);
rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, cv.getStack().getLiteralPath(), false, e.getMessage());
}
}
}
}
@ -386,36 +390,38 @@ public class ValueSetValidator extends BaseValidator {
String display = concept.getChildValue("display");
slv.checkConcept(code, display);
if (version == null) {
ValidationResult vv = context.validateCode(ValidationOptions.defaults().withExampleOK(), new Coding(system, code, null), null);
if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
if (isExampleUrl(system)) {
if (isAllowExamples()) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_EXAMPLE_SYSTEM_HINT, system);
if (!!noTerminologyChecks) {
if (version == null) {
ValidationResult vv = context.validateCode(ValidationOptions.defaults().withExampleOK(), new Coding(system, code, null), null);
if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
if (isExampleUrl(system)) {
if (isAllowExamples()) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_EXAMPLE_SYSTEM_HINT, system);
} else {
rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_EXAMPLE_SYSTEM_ERROR, system);
}
} else {
rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_EXAMPLE_SYSTEM_ERROR, system);
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_UNC_SYSTEM_WARNING, system, vv.getMessage());
}
return false;
} else {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_UNC_SYSTEM_WARNING, system, vv.getMessage());
boolean ok = vv.isOk();
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code, vv.getMessage());
if (vv.getMessage() != null) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, false, vv.getMessage());
}
}
return false;
} else {
boolean ok = vv.isOk();
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE, system, code, vv.getMessage());
if (vv.getMessage() != null) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, false, vv.getMessage());
}
}
} else {
ValidationResult vv = context.validateCode(ValidationOptions.defaults().withExampleOK(), new Coding(system, code, null).setVersion(version), null);
if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_UNC_SYSTEM_WARNING_VER, system+"#"+version, vv.getMessage());
return false;
} else {
boolean ok = vv.isOk();
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code, vv.getMessage());
if (vv.getMessage() != null) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, false, vv.getMessage());
ValidationResult vv = context.validateCode(ValidationOptions.defaults().withExampleOK(), new Coding(system, code, null).setVersion(version), null);
if (vv.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stackInc.getLiteralPath(), false, I18nConstants.VALUESET_UNC_SYSTEM_WARNING_VER, system+"#"+version, vv.getMessage());
return false;
} else {
boolean ok = vv.isOk();
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, ok, I18nConstants.VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER, system, version, code, vv.getMessage());
if (vv.getMessage() != null) {
hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack, false, vv.getMessage());
}
}
}
}
@ -517,7 +523,7 @@ public class ValueSetValidator extends BaseValidator {
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack,
value.trim().equals(value),
I18nConstants.VALUESET_BAD_FILTER_VALUE_CODE, property, value) && ok;
if (rules.getCodeValidation() == CodeValidationRule.Error || rules.getCodeValidation() == CodeValidationRule.Warning) {
if (!noTerminologyChecks && (rules.getCodeValidation() == CodeValidationRule.Error || rules.getCodeValidation() == CodeValidationRule.Warning)) {
ValidationResult vr = context.validateCode(baseOptions, system, version, value, null);
if (rules.getCodeValidation() == CodeValidationRule.Error) {
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(), vr.isOk(), rules.isChange() ? I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_CODE_CHANGE : I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_CODE, property, value, system, vr.getMessage()) && ok;
@ -557,7 +563,7 @@ public class ValueSetValidator extends BaseValidator {
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 {
} else if (!noTerminologyChecks) {
ValidationResult 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;
}