Update terminology server implementation and tests for changes to test cases

This commit is contained in:
Grahame Grieve 2023-07-21 05:38:09 +10:00
parent 93e1b3e4b3
commit cf1b53bba0
8 changed files with 143 additions and 102 deletions

View File

@ -483,9 +483,8 @@ public class CodeSystemUtilities {
}
private static boolean hasUse(ConceptPropertyComponent p, String use) {
for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_METADATA)) {
Extension se = ext.getExtensionByUrl("use");
if (se != null && se.hasValueCoding() && use.equals(se.getValueCoding().getCode())) {
for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) {
if (ext.hasValueCoding() && use.equals(ext.getValueCoding().getCode())) {
return true;
}
}
@ -781,7 +780,7 @@ public class CodeSystemUtilities {
} else {
code = defineProperty(ret, p.getCode(), propertyTypeForType(p.getValue()));
}
fdef.addProperty().setCode(code).setValue(p.getValue()).copyExtensions(p, "http://hl7.org/fhir/StructureDefinition/alternate-code-metadata");
fdef.addProperty().setCode(code).setValue(p.getValue()).copyExtensions(p, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
}
}
for (ConceptDefinitionComponent t : fdef.getConcept()) {

View File

@ -114,14 +114,15 @@ import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
public class ValueSetExpander {
public class ValueSetExpander extends ValueSetProcessBase {
private static final boolean REPORT_VERSION_ANYWAY = false;
private static final boolean REPORT_VERSION_ANYWAY = true;
private IWorkerContext context;
private ValueSet focus;
@ -247,14 +248,14 @@ public class ValueSetExpander {
if (csProps != null && p.hasValue()) {
for (ConceptPropertyComponent cp : csProps) {
if (p.getValue().primitiveValue().equals(cp.getCode())) {
n.addProperty().setCode(cp.getCode()).setValue(cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-metadata");
n.addProperty().setCode(cp.getCode()).setValue(cp.getValue()).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
}
}
}
if (expProps != null && p.hasValue()) {
for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent cp : expProps) {
if (p.getValue().primitiveValue().equals(cp.getCode())) {
n.addProperty(cp).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-metadata");
n.addProperty(cp).copyExtensions(cp, "http://hl7.org/fhir/StructureDefinition/alternate-code-use", "http://hl7.org/fhir/StructureDefinition/alternate-code-status");
}
}
}
@ -327,40 +328,15 @@ public class ValueSetExpander {
private List<String> getCodesForConcept(ValueSetExpansionContainsComponent focus, Parameters expParams) {
List<String> codes = new ArrayList<>();
List<String> uses = new ArrayList<>();
codes.add(focus.getCode());
boolean all = false;
for (ParametersParameterComponent p : expParams.getParameter()) {
if ("alternateCodes".equals(p.getName())) {
if (p.hasValueBooleanType()) {
all = p.getValueBooleanType().booleanValue();
} else if (p.getValue().isPrimitive()) {
String s = p.getValue().primitiveValue();
if (!Utilities.noString(s)) {
uses.add(s);
}
}
}
}
for (org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p : focus.getProperty()) {
if ("alternateCode".equals(p.getCode()) && (all || hasUse(p, uses)) && p.getValue().isPrimitive()) {
if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) {
codes.add(p.getValue().primitiveValue());
}
}
return codes;
}
private static boolean hasUse(org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent p, List<String> uses) {
for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_METADATA)) {
Extension se = ext.getExtensionByUrl("use");
if (se != null && se.hasValueCoding() && Utilities.existsInList(se.getValueCoding().getCode(), uses)) {
return true;
}
}
return false;
}
private List<ConceptDefinitionDesignationComponent> convert(List<ConceptReferenceDesignationComponent> designations) {
List<ConceptDefinitionDesignationComponent> list = new ArrayList<ConceptDefinitionDesignationComponent>();
for (ConceptReferenceDesignationComponent d : designations) {
@ -405,23 +381,9 @@ public class ValueSetExpander {
private List<String> getCodesForConcept(ConceptDefinitionComponent focus, Parameters expParams) {
List<String> codes = new ArrayList<>();
List<String> uses = new ArrayList<>();
codes.add(focus.getCode());
boolean all = false;
for (ParametersParameterComponent p : expParams.getParameter()) {
if ("alternateCodes".equals(p.getName())) {
if (p.hasValueBooleanType()) {
all = p.getValueBooleanType().booleanValue();
} else if (p.getValue().isPrimitive()) {
String s = p.getValue().primitiveValue();
if (!Utilities.noString(s)) {
uses.add(s);
}
}
}
}
for (ConceptPropertyComponent p : focus.getProperty()) {
if ("alternateCode".equals(p.getCode()) && (all || hasUse(p, uses)) && p.getValue().isPrimitive()) {
if ("alternateCode".equals(p.getCode()) && (altCodeParams.passes(p.getExtension())) && p.getValue().isPrimitive()) {
codes.add(p.getValue().primitiveValue());
}
}
@ -429,9 +391,8 @@ public class ValueSetExpander {
}
private static boolean hasUse(ConceptPropertyComponent p, List<String> uses) {
for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_METADATA)) {
Extension se = ext.getExtensionByUrl("use");
if (se != null && se.hasValueCoding() && Utilities.existsInList(se.getValueCoding().getCode(), uses)) {
for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_USE)) {
if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) {
return true;
}
}
@ -533,6 +494,9 @@ public class ValueSetExpander {
public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension {
if (expParams == null)
expParams = makeDefaultExpansion();
altCodeParams.seeParameters(expParams);
altCodeParams.seeValueSet(source);
source.checkNoModifiers("ValueSet", "expanding");
focus = source.copy();
focus.setIdBase(null);
@ -838,6 +802,13 @@ public class ValueSetExpander {
UriType u = new UriType(cs.getUrl() + (cs.hasVersion() ? "|"+cs.getVersion() : ""));
if (!existsInParams(exp.getParameter(), "version", u))
exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(u));
if (cs.hasUserData("supplements.installed")) {
for (String s : cs.getUserString("supplements.installed").split("\\,")) {
u = new UriType(s);
if (!existsInParams(exp.getParameter(), "version", u))
exp.getParameter().add(new ValueSetExpansionParameterComponent().setName("version").setValue(u));
}
}
}
if (inc.getConcept().size() == 0 && inc.getFilter().size() == 0) {
// special case - add all the code system

View File

@ -0,0 +1,80 @@
package org.hl7.fhir.r5.terminologies.utilities;
import java.util.*;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities;
public class ValueSetProcessBase {
public static class AlternateCodesProcessingRules {
private boolean all;
private List<String> uses = new ArrayList<>();
public AlternateCodesProcessingRules(boolean b) {
all = b;
}
private void seeParameter(DataType value) {
if (value != null) {
if (value instanceof BooleanType) {
all = ((BooleanType) value).booleanValue();
uses.clear();
} else if (value.isPrimitive()) {
String s = value.primitiveValue();
if (!Utilities.noString(s)) {
uses.add(s);
}
}
}
}
public void seeParameters(Parameters pp) {
for (ParametersParameterComponent p : pp.getParameter()) {
String name = p.getName();
if ("includeAlternateCodes".equals(name)) {
DataType value = p.getValue();
seeParameter(value);
}
}
}
public void seeValueSet(ValueSet vs) {
for (Extension ext : vs.getCompose().getExtension()) {
if ("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param".equals(ext.getUrl())) {
String name = ext.getExtensionString("name");
Extension value = ext.getExtensionByUrl("value");
if ("includeAlternateCodes".equals(name) && value != null && value.hasValue()) {
seeParameter(value.getValue());
}
}
}
}
public boolean passes(List<Extension> extensions) {
if (all) {
return true;
}
for (Extension ext : extensions) {
if (ToolingExtensions.EXT_CS_ALTERNATE_USE.equals(ext.getUrl())) {
if (ext.hasValueCoding() && Utilities.existsInList(ext.getValueCoding().getCode(), uses)) {
return true;
}
}
}
return false;
}
}
protected AlternateCodesProcessingRules altCodeParams = new AlternateCodesProcessingRules(false);
protected AlternateCodesProcessingRules allAltCodes = new AlternateCodesProcessingRules(true);
}

View File

@ -80,6 +80,7 @@ import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
import org.hl7.fhir.r5.terminologies.providers.SpecialCodeSystem;
import org.hl7.fhir.r5.terminologies.providers.URICodeSystem;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationContextResourceProxy;
@ -93,7 +94,7 @@ import org.hl7.fhir.utilities.validation.ValidationOptions.ValueSetMode;
import com.google.j2objc.annotations.ReflectionSupport.Level;
public class ValueSetValidator {
public class ValueSetValidator extends ValueSetProcessBase {
private ValueSet valueset;
private IWorkerContext context;
@ -112,6 +113,7 @@ public class ValueSetValidator {
this.options = options;
this.expansionProfile = expansionProfile;
this.txCaps = txCaps;
analyseValueSet();
}
public ValueSetValidator(ValidationOptions options, ValueSet source, IWorkerContext context, ValidationContextCarrier ctxt, Parameters expansionProfile, TerminologyCapabilities txCaps) {
@ -142,6 +144,8 @@ public class ValueSetValidator {
}
private void analyseValueSet() {
altCodeParams.seeParameters(expansionProfile);
altCodeParams.seeValueSet(valueset);
if (localContext != null) {
if (valueset != null) {
for (ConceptSetComponent i : valueset.getCompose().getInclude()) {
@ -651,7 +655,7 @@ public class ValueSetValidator {
}
private ValidationResult validateCode(String path, Coding code, CodeSystem cs, CodeableConcept vcc) {
ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode(), true);
ConceptDefinitionComponent cc = cs.hasUserData("tx.cs.special") ? ((SpecialCodeSystem) cs.getUserData("tx.cs.special")).findConcept(code) : findCodeInConcept(cs.getConcept(), code.getCode(), allAltCodes);
if (cc == null) {
if (cs.getContent() == CodeSystemContentMode.FRAGMENT) {
String msg = context.formatMessage(I18nConstants.UNKNOWN_CODE__IN_FRAGMENT, code.getCode(), cs.getUrl());
@ -815,18 +819,18 @@ public class ValueSetValidator {
return true;
}
private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, boolean allAlternates) {
private ConceptDefinitionComponent findCodeInConcept(ConceptDefinitionComponent concept, String code, AlternateCodesProcessingRules altCodeRules) {
if (code.equals(concept.getCode())) {
return concept;
}
ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code, allAlternates);
ConceptDefinitionComponent cc = findCodeInConcept(concept.getConcept(), code, altCodeRules);
if (cc != null) {
return cc;
}
if (concept.hasUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK)) {
List<ConceptDefinitionComponent> children = (List<ConceptDefinitionComponent>) concept.getUserData(CodeSystemUtilities.USER_DATA_CROSS_LINK);
for (ConceptDefinitionComponent c : children) {
cc = findCodeInConcept(c, code, allAlternates);
cc = findCodeInConcept(c, code, altCodeRules);
if (cc != null) {
return cc;
}
@ -835,15 +839,15 @@ public class ValueSetValidator {
return null;
}
private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code, boolean allAlternates) {
private ConceptDefinitionComponent findCodeInConcept(List<ConceptDefinitionComponent> concept, String code, AlternateCodesProcessingRules altCodeRules) {
for (ConceptDefinitionComponent cc : concept) {
if (code.equals(cc.getCode())) {
return cc;
}
if (Utilities.existsInList(code, alternateCodes(cc, allAlternates))) {
if (Utilities.existsInList(code, alternateCodes(cc, altCodeRules))) {
return cc;
}
ConceptDefinitionComponent c = findCodeInConcept(cc, code, allAlternates);
ConceptDefinitionComponent c = findCodeInConcept(cc, code, altCodeRules);
if (c != null) {
return c;
}
@ -852,40 +856,16 @@ public class ValueSetValidator {
}
private List<String> alternateCodes(ConceptDefinitionComponent focus, boolean allAlternates) {
private List<String> alternateCodes(ConceptDefinitionComponent focus, AlternateCodesProcessingRules altCodeRules) {
List<String> codes = new ArrayList<>();
List<String> uses = new ArrayList<>();
boolean all = false;
for (ParametersParameterComponent p : expansionProfile.getParameter()) {
if ("alternateCodes".equals(p.getName())) {
if (p.hasValueBooleanType()) {
all = p.getValueBooleanType().booleanValue();
} else if (p.getValue().isPrimitive()) {
String s = p.getValue().primitiveValue();
if (!Utilities.noString(s)) {
uses.add(s);
}
}
}
}
for (ConceptPropertyComponent p : focus.getProperty()) {
if ("alternateCode".equals(p.getCode()) && (allAlternates || all || hasUse(p, uses)) && p.getValue().isPrimitive()) {
if ("alternateCode".equals(p.getCode()) && (altCodeRules.passes(p.getExtension())) && p.getValue().isPrimitive()) {
codes.add(p.getValue().primitiveValue());
}
}
return codes;
}
private static boolean hasUse(ConceptPropertyComponent p, List<String> uses) {
for (Extension ext : p.getExtensionsByUrl(ToolingExtensions.EXT_CS_ALTERNATE_METADATA)) {
Extension se = ext.getExtensionByUrl("use");
if (se != null && se.hasValueCoding() && Utilities.existsInList(se.getValueCoding().getCode(), uses)) {
return true;
}
}
return false;
}
private String systemForCodeInValueSet(String code, List<String> problems) {
Set<String> sys = new HashSet<>();
@ -945,7 +925,7 @@ public class ValueSetValidator {
}
}
} else {
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code, true);
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code, allAltCodes);
if (cc != null) {
sys.add(vsi.getSystem());
}
@ -1127,7 +1107,7 @@ public class ValueSetValidator {
}
List<ConceptDefinitionComponent> list = cs.getConcept();
ok = validateCodeInConceptList(code, cs, list, true);
ok = validateCodeInConceptList(code, cs, list, allAltCodes);
if (ok && vsi.hasConcept()) {
for (ConceptReferenceComponent cc : vsi.getConcept()) {
if (cc.getCode().equals(code)) {
@ -1137,7 +1117,7 @@ public class ValueSetValidator {
return false;
} else {
// recheck that this is a valid alternate code
ok = validateCodeInConceptList(code, cs, list, false);
ok = validateCodeInConceptList(code, cs, list, altCodeParams);
return ok;
}
}
@ -1206,24 +1186,24 @@ public class ValueSetValidator {
if (!excludeRoot && code.equals(f.getValue())) {
return true;
}
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue(), false);
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue(), altCodeParams);
if (cc == null) {
return false;
}
ConceptDefinitionComponent cc2 = findCodeInConcept(cc, code, false);
ConceptDefinitionComponent cc2 = findCodeInConcept(cc, code, altCodeParams);
return cc2 != null && cc2 != cc;
}
public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list, boolean allAlternates) {
public boolean validateCodeInConceptList(String code, CodeSystem def, List<ConceptDefinitionComponent> list, AlternateCodesProcessingRules altCodeRules) {
if (def.getCaseSensitive()) {
for (ConceptDefinitionComponent cc : list) {
if (cc.getCode().equals(code)) {
return true;
}
if (Utilities.existsInList(code, alternateCodes(cc, allAlternates))) {
if (Utilities.existsInList(code, alternateCodes(cc, altCodeRules))) {
return true;
}
if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept(), allAlternates)) {
if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept(), altCodeRules)) {
return true;
}
}
@ -1232,7 +1212,7 @@ public class ValueSetValidator {
if (cc.getCode().equalsIgnoreCase(code)) {
return true;
}
if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept(), allAlternates)) {
if (cc.hasConcept() && validateCodeInConceptList(code, def, cc.getConcept(), altCodeRules)) {
return true;
}
}

View File

@ -351,8 +351,16 @@ public class CompareUtilities extends BaseTestingUtilities {
private static boolean matches(String actualJsonString, String expectedJsonString) {
if (expectedJsonString.startsWith("$") && expectedJsonString.endsWith("$")) {
if (expectedJsonString.startsWith("$choice:")) {
return Utilities.existsInList(actualJsonString, readChoices(expectedJsonString));
return Utilities.existsInList(actualJsonString, readChoices(8, expectedJsonString));
} else if (expectedJsonString.startsWith("$fragments:")) {
List<String> fragments = readChoices(11, expectedJsonString);
for (String f : fragments) {
if (!actualJsonString.toLowerCase().contains(f.toLowerCase())) {
return false;
}
}
return true;
} else {
switch (expectedJsonString) {
case "$$" : return true;
@ -367,9 +375,9 @@ public class CompareUtilities extends BaseTestingUtilities {
}
}
private static List<String> readChoices(String s) {
private static List<String> readChoices(int offset, String s) {
List<String> list = new ArrayList<>();
s = s.substring(8, s.length()-1);
s = s.substring(offset, s.length()-1);
for (String p : s.split("\\|")) {
list.add(p);
}

View File

@ -141,7 +141,8 @@ public class ToolingExtensions {
public static final String EXT_EXTENSION_STYLE = "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-extension-style";
public static final String EXT_LOGICAL_TARGET = "http://hl7.org/fhir/tools/StructureDefinition/logical-target";
public static final String EXT_PROFILE_MAPPING = "http://hl7.org/fhir/tools/StructureDefinition/profile-mapping";
public static final String EXT_CS_ALTERNATE_METADATA = "http://hl7.org/fhir/StructureDefinition/alternate-code-metadata";
public static final String EXT_CS_ALTERNATE_USE = "http://hl7.org/fhir/StructureDefinition/alternate-code-use";
public static final String EXT_CS_ALTERNATE_STATUS = "http://hl7.org/fhir/StructureDefinition/alternate-code-status";
// validated
// private static final String EXT_OID = "http://hl7.org/fhir/StructureDefinition/valueset-oid";

View File

@ -47,7 +47,8 @@ public class TxTesterScrubbers {
"http://hl7.org/fhir/test/ValueSet/extensions-bad-supplement",
"http://hl7.org/fhir/test/ValueSet/simple-all",
"http://hl7.org/fhir/test/ValueSet/simple-enumerated",
"http://hl7.org/fhir/StructureDefinition/alternate-code-metadata",
"http://hl7.org/fhir/StructureDefinition/alternate-code-use",
"http://hl7.org/fhir/StructureDefinition/alternate-code-status",
"http://hl7.org/fhir/test/ValueSet/simple-filter-isa");
}

View File

@ -123,6 +123,7 @@ public class TerminologyServiceTests {
}
ValidationEngine engine = new ValidationEngine(this.baseEngine);
for (String s : setup.suite.forceArray("setup").asStrings()) {
// System.out.println(s);
Resource res = loadResource(s);
engine.seeResource(res);
}
@ -245,9 +246,9 @@ public class TerminologyServiceTests {
if (p.hasParameter("mode") && "lenient-display-validation".equals(p.getParameterString("mode"))) {
options = options.setDisplayWarningMode(true);
}
engine.getContext().getExpansionParameters().clearParameters("alternateCodes");
engine.getContext().getExpansionParameters().clearParameters("includeAlternateCodes");
for (ParametersParameterComponent pp : p.getParameter()) {
if ("alternateCodes".equals(pp.getName())) {
if ("includeAlternateCodes".equals(pp.getName())) {
engine.getContext().getExpansionParameters().addParameter(pp.copy());
}
}