From 8980df2364fac1d07fd1f726568fba9c684fe136 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 21 Jul 2023 21:17:12 +1000 Subject: [PATCH] add value set qa checking --- .../fhir/utilities/i18n/I18nConstants.java | 2 + .../src/main/resources/Messages.properties | 2 + .../instance/type/ValueSetValidator.java | 95 +++++++++++++++++-- 3 files changed, 93 insertions(+), 6 deletions(-) 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 2b27e79a1..3e3097977 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 @@ -922,6 +922,8 @@ public class I18nConstants { public static final String TERMINOLOGY_TX_HINT = "TERMINOLOGY_TX_HINT"; public static final String TERMINOLOGY_TX_WARNING = "TERMINOLOGY_TX_WARNING"; public static final String SD_ED_TYPE_PROFILE_WRONG_TYPE = "SD_ED_TYPE_PROFILE_WRONG_TYPE"; + public static final String VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = "VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED"; + public static final String VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = "VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 5fa6b2064..3ed046162 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -977,3 +977,5 @@ TERMINOLOGY_TX_HINT = {1} TERMINOLOGY_TX_WARNING = {1} SD_ED_TYPE_WRONG_TYPE_one = The element has a type {0} which is different to the type {1} on the base profile {2} SD_ED_TYPE_WRONG_TYPE_other = The element has a type {0} which is not in the types {1} on the base profile {2} +VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED = This include has some concepts with displays and some without - check that this is what is intended +VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED = This SNOMED-CT based include has some concepts with semantic tags (FSN terms) and some without (preferred terms) - check that this is what is intended diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java index bf1dfc6af..82bee6298 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java @@ -22,10 +22,79 @@ import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.validation.BaseValidator; import org.hl7.fhir.validation.TimeTracker; import org.hl7.fhir.validation.instance.InstanceValidator; +import org.hl7.fhir.validation.instance.type.ValueSetValidator.SystemLevelValidator; import org.hl7.fhir.validation.instance.utils.NodeStack; public class ValueSetValidator extends BaseValidator { + public class SystemLevelValidator { + protected List errors; + protected Element inc; + protected NodeStack stack; + + private boolean noDisplay = false; + private boolean hasDisplay = false; + protected SystemLevelValidator(List errors, Element inc, NodeStack stack) { + super(); + this.errors = errors; + this.inc = inc; + this.stack = stack; + } + + public void checkConcept(String code, String display) { + if (Utilities.noString(display)) { + noDisplay = true; + } else { + hasDisplay = true; + } + } + + public void finish() { + hint(errors, "2023-07-21", IssueType.BUSINESSRULE, inc.line(), inc.col(), stack.getLiteralPath(), !(noDisplay && hasDisplay), I18nConstants.VALUESET_CONCEPT_DISPLAY_PRESENCE_MIXED); + } + } + + public class SnomedCTValidator extends SystemLevelValidator { + private boolean noTag = false; + private boolean hasTag = false; + + protected SnomedCTValidator(List errors, Element inc, NodeStack stack) { + super(errors, inc, stack); + } + public void checkConcept(String code, String display) { + super.checkConcept(code, display); + if (!Utilities.noString(display)) { + boolean tagged = display.endsWith(")") && display.indexOf("(") > display.length() - 20; + if (tagged) { + hasTag = true; + } else { + noTag = true; + } + } + } + public void finish() { + hint(errors, "2023-07-21", IssueType.BUSINESSRULE, inc.line(), inc.col(), stack.getLiteralPath(), !(noTag && hasTag), I18nConstants.VALUESET_CONCEPT_DISPLAY_SCT_TAG_MIXED); + } + } + + public class GeneralValidator extends SystemLevelValidator { + + protected GeneralValidator(List errors, Element inc, NodeStack stack) { + super(errors, inc, stack); + } + + } + + private SystemLevelValidator getSystemValidator(String system, List errors, Element inc, NodeStack stack) { + if (system == null) { + return new GeneralValidator(errors, inc, stack); + } + switch (system) { + case "http://snomed.info/sct" :return new SnomedCTValidator(errors, inc, stack); + default: return new GeneralValidator(errors, inc, stack); + } + } + public class VSCodingValidationRequest extends CodingValidationRequest { private NodeStack stack; @@ -139,6 +208,8 @@ public class ValueSetValidator extends BaseValidator { } List concepts = include.getChildrenByName("concept"); List filters = include.getChildrenByName("filter"); + + SystemLevelValidator slv = getSystemValidator(system, errors, include, stack); if (!Utilities.noString(system)) { boolean systemOk = true; int cc = 0; @@ -147,10 +218,10 @@ public class ValueSetValidator extends BaseValidator { for (Element concept : concepts) { // we treat the first differently because we want to know if tbe 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); + systemOk = validateValueSetIncludeConcept(errors, concept, stack, stack.push(concept, cc, null, null), system, version, slv); first = false; } else if (systemOk) { - batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version)); + batch.add(prepareValidateValueSetIncludeConcept(errors, concept, stack.push(concept, cc, null, null), system, version, slv)); } cc++; } @@ -180,19 +251,24 @@ 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)) { + if (systemOk && !validateValueSetIncludeFilter(errors, include, stack.push(filter, cf, null, null), system, version, slv)) { systemOk = false; } cf++; } + slv.finish(); } else { warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), filters.size() == 0 && concepts.size() == 0, I18nConstants.VALUESET_NO_SYSTEM_WARNING); } return ok; } - private boolean validateValueSetIncludeConcept(List errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version) { + + private boolean validateValueSetIncludeConcept(List errors, Element concept, NodeStack stackInc, NodeStack stack, String system, String version, SystemLevelValidator slv) { 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) { @@ -229,8 +305,11 @@ public class ValueSetValidator extends BaseValidator { return true; } - private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List errors, Element concept, NodeStack stack, String system, String version) { + private VSCodingValidationRequest prepareValidateValueSetIncludeConcept(List errors, Element concept, NodeStack stack, String system, String version, SystemLevelValidator slv) { 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); @@ -238,7 +317,11 @@ public class ValueSetValidator extends BaseValidator { return new VSCodingValidationRequest(stack, c); } - private boolean validateValueSetIncludeFilter(List errors, Element include, NodeStack push, String system, String version) { + private boolean validateValueSetIncludeFilter(List errors, Element filter, NodeStack push, String system, String version, SystemLevelValidator slv) { +// +// String display = concept.getChildValue("display"); +// slv.checkConcept(code, display); + return true; } }