Add support for locally processed special case code systems (and an example - rgb)

This commit is contained in:
Grahame Grieve 2023-03-10 21:28:08 +11:00
parent 1212b85b5e
commit 10d560c859
10 changed files with 144 additions and 41 deletions

View File

@ -827,21 +827,26 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
// ok, first we try to expand locally
ValueSetExpanderSimple vse = constructValueSetExpanderSimple();
res = null;
try {
res = vse.expand(vs, p);
allErrors.addAll(vse.getAllErrors());
if (res.getValueset() != null) {
if (!res.getValueset().hasUrl()) {
throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET));
}
txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
}
} catch (Exception e) {
allErrors.addAll(vse.getAllErrors());
e.printStackTrace();
res = new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN);
}
allErrors.addAll(vse.getAllErrors());
if (res.getValueset() != null) {
if (!res.getValueset().hasUrl()) {
throw new Error(formatMessage(I18nConstants.NO_URL_IN_EXPAND_VALUE_SET));
}
txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
return res;
}
if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR) { // this class is created specifically to say: don't consult the server
return new ValueSetExpansionOutcome(res.getError(), res.getErrorClass());
}
// if that failed, we try to expand on the server
if (addDependentResources(p, vs)) {
p.addParameter().setName("cache-id").setValue(new StringType(cacheId));

View File

@ -343,7 +343,7 @@ public class StructureMapRenderer extends TerminologyRenderer {
}
}
if (r.getTarget().size() > 1) {
x.b().tx(" -> ");
x.color(COLOR_SYNTAX).b().tx(" -> ");
boolean first = true;
for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) {
if (first)
@ -360,7 +360,7 @@ public class StructureMapRenderer extends TerminologyRenderer {
renderTarget(x, rt, false);
}
} else if (r.hasTarget()) {
x.b().tx(" -> ");
x.color(COLOR_SYNTAX).b().tx(" -> ");
renderTarget(x, r.getTarget().get(0), canBeAbbreviated);
}
if (r.hasRule()) {

View File

@ -0,0 +1,37 @@
package org.hl7.fhir.r5.terminologies;
import java.util.List;
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.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
import org.hl7.fhir.r5.terminologies.providers.ColorRGBProvider;
/**
* For special code systems where the code system resource isn't enough, but we can support them internall
* Usually, small grammar based code systems
*
* @author grahamegrieve
*
*/
public abstract class CodeSystemProvider {
public static CodeSystemProvider factory(String system) {
switch (system) {
case "http://hl7.org/fhir/color-rgb" : return new ColorRGBProvider();
default:
return null;
}
}
public abstract void includeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp,
List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive,
List<ValueSetExpansionPropertyComponent> vsProps) throws CodeSystemProviderExtension;
protected abstract Boolean checkCode(String code);
}

View File

@ -601,39 +601,50 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe
problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_NO_SYSTEM, valueset.getVersionedUrl(), i, vsi.getSystem()));
return false;
}
CodeSystem cs = resolveCodeSystem(vsi.getSystem(), vsi.getVersion());
if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) {
CodeSystemProvider csp = CodeSystemProvider.factory(vsi.getSystem());
if (csp != null) {
Boolean ok = csp.checkCode(code);
if (ok == null) {
problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE, valueset.getVersionedUrl(), vsi.getSystem()));
sys.add(vsi.getSystem());
} else if (ok) {
sys.add(vsi.getSystem());
}
} else {
CodeSystem cs = resolveCodeSystem(vsi.getSystem(), vsi.getVersion());
if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) {
if (vsi.hasConcept()) {
if (vsi.hasConcept()) {
for (ConceptReferenceComponent cc : vsi.getConcept()) {
boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code);
if (match) {
sys.add(vsi.getSystem());
}
}
} else {
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code);
if (cc != null) {
sys.add(vsi.getSystem());
}
}
} else if (vsi.hasConcept()) {
for (ConceptReferenceComponent cc : vsi.getConcept()) {
boolean match = cs.getCaseSensitive() ? cc.getCode().equals(code) : cc.getCode().equalsIgnoreCase(code);
boolean match = cc.getCode().equals(code);
if (match) {
sys.add(vsi.getSystem());
}
}
} else {
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), code);
if (cc != null) {
sys.add(vsi.getSystem());
}
}
} else if (vsi.hasConcept()) {
for (ConceptReferenceComponent cc : vsi.getConcept()) {
boolean match = cc.getCode().equals(code);
if (match) {
sys.add(vsi.getSystem());
}
}
} else {
// we'll try to expand this one then
ValueSetExpansionOutcome vse = context.expandVS(vsi, false, false);
if (vse.isOk()) {
if (!checkSystems(vse.getValueset().getExpansion().getContains(), code, sys, problems)) {
// we'll try to expand this one then
ValueSetExpansionOutcome vse = context.expandVS(vsi, false, false);
if (vse.isOk()) {
if (!checkSystems(vse.getValueset().getExpansion().getContains(), code, sys, problems)) {
return false;
}
} else {
problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_UNKNOWN_SYSTEM, valueset.getVersionedUrl(), i, vsi.getSystem(), vse.getAllErrors().toString()));
return false;
}
} else {
problems.add(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SYSTEM__VALUE_SET_HAS_INCLUDE_WITH_UNKNOWN_SYSTEM, valueset.getVersionedUrl(), i, vsi.getSystem(), vse.getAllErrors().toString()));
return false;
}
}
}

View File

@ -41,7 +41,7 @@ import org.hl7.fhir.r5.model.ValueSet;
public interface ValueSetExpander {
public enum TerminologyServiceErrorClass {
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, BLOCKED_BY_OPTIONS;
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, BLOCKED_BY_OPTIONS, INTERNAL_ERROR;
public boolean isInfrastructure() {
return this == NOSERVICE || this == SERVER_ERROR || this == VALUESET_UNSUPPORTED;

View File

@ -109,6 +109,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities;
@ -407,6 +408,10 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
// that might fail too, but it might not, later.
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.NOSERVICE, allErrors);
} catch (CodeSystemProviderExtension e) {
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
// that might fail too, but it might not, later.
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.INTERNAL_ERROR, allErrors);
} catch (Exception e) {
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
// that might fail too, but it might not, later.
@ -414,11 +419,11 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException {
public ValueSetExpansionOutcome expandInternal(ValueSet source, Parameters expParams) throws FHIRException, FileNotFoundException, ETooCostly, IOException, CodeSystemProviderExtension {
return doExpand(source, expParams);
}
public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException {
public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension {
if (expParams == null)
expParams = makeDefaultExpansion();
source.checkNoModifiers("ValueSet", "expanding");
@ -474,7 +479,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
private void handleCompose(ValueSetComposeComponent compose, ValueSetExpansionComponent exp, Parameters expParams, String ctxt, List<Extension> extensions, ValueSet valueSet)
throws ETooCostly, FileNotFoundException, IOException, FHIRException {
throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
compose.checkNoModifiers("ValueSet.compose", "expanding");
// Exclude comes first because we build up a map of things to exclude
for (ConceptSetComponent inc : compose.getExclude())
@ -594,7 +599,7 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException {
private void includeCodes(ConceptSetComponent inc, ValueSetExpansionComponent exp, Parameters expParams, boolean heirarchical, boolean noInactive, List<Extension> extensions, ValueSet valueSet) throws ETooCostly, FileNotFoundException, IOException, FHIRException, CodeSystemProviderExtension {
inc.checkNoModifiers("Compose.include", "expanding");
List<ValueSet> imports = new ArrayList<ValueSet>();
for (UriType imp : inc.getValueSet()) {
@ -618,7 +623,13 @@ public class ValueSetExpanderSimple extends ValueSetWorker implements ValueSetEx
}
}
private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException {
private void doServerIncludeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp, List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive, List<ValueSetExpansionPropertyComponent> vsProps) throws FHIRException, CodeSystemProviderExtension {
CodeSystemProvider csp = CodeSystemProvider.factory(inc.getSystem());
if (csp != null) {
csp.includeCodes(inc, heirarchical, exp, imports, expParams, extensions, noInactive, vsProps);
return;
}
ValueSetExpansionOutcome vso = context.expandVS(inc, heirarchical, noInactive);
if (vso.getError() != null) {
throw failTSE("Unable to expand imported value set: " + vso.getError());

View File

@ -0,0 +1,9 @@
package org.hl7.fhir.r5.terminologies.providers;
public class CodeSystemProviderExtension extends Exception {
public CodeSystemProviderExtension(String msg) {
super(msg);
}
}

View File

@ -0,0 +1,28 @@
package org.hl7.fhir.r5.terminologies.providers;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
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.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
import org.hl7.fhir.r5.terminologies.CodeSystemProvider;
public class ColorRGBProvider extends CodeSystemProvider {
@Override
public void includeCodes(ConceptSetComponent inc, boolean heirarchical, ValueSetExpansionComponent exp,
List<ValueSet> imports, Parameters expParams, List<Extension> extensions, boolean noInactive,
List<ValueSetExpansionPropertyComponent> vsProps) throws CodeSystemProviderExtension {
throw new CodeSystemProviderExtension("There are 16777216 colors, so the full list of colors is not displayed");
}
@Override
protected Boolean checkCode(String code) {
return code.matches("^\\#[0-9a-fA-F]{6}$");
}
}

View File

@ -849,6 +849,7 @@ public class I18nConstants {
public static final String SM_GROUP_NAME_DUPLICATE = "SM_GROUP_NAME_DUPLICATE";
public static final String CONCEPTMAP_GROUP_SOURCE_INCOMPLETE = "CONCEPTMAP_GROUP_SOURCE_INCOMPLETE";
public static final String CONCEPTMAP_GROUP_TARGET_INCOMPLETE = "CONCEPTMAP_GROUP_TARGET_INCOMPLETE";
public static final String UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE = "UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE";
}

View File

@ -470,6 +470,7 @@ Coding_has_no_system__cannot_validate = Coding has no system - cannot validate
Unable_to_handle_system__concept_filter_with_op__ = Unable to handle system {0} concept filter with op = {1}
Unable_to_handle_system__filter_with_property__ = Unable to handle system {0} filter with property = {1}
Unable_to_resolve_system__value_set_has_include_with_no_system = Unable to resolve system - value set {0} include #{1} has no system
UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE = The code system {1} referred to from value set {0} has a grammar, and the code might be valid in it
Unable_to_resolve_system__value_set_has_include_with_unknown_system = Unable to resolve system - value set {0} include #{1} has system {2} which is unknown, and the server return error {3}
Unable_to_resolve_system__value_set_has_include_with_filter = Unable to resolve system - value set {0} include #{1} has a filter on system {2}
Unable_to_resolve_system__value_set_has_imports = Unable to resolve system - value set has imports