Better handle failure to find imported value sets when expanding, and track & report expansion source

And render supplement dependencies + correct error handling for expansion failure
This commit is contained in:
Grahame Grieve 2025-01-17 06:39:59 +11:00
parent 74c2868cc1
commit 652cc8fba6
13 changed files with 242 additions and 23 deletions

View File

@ -240,7 +240,7 @@ public class CodeSystemUtilities {
return null; return null;
} }
private static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) { public static ConceptDefinitionComponent findCode(List<ConceptDefinitionComponent> list, String code) {
for (ConceptDefinitionComponent c : list) { for (ConceptDefinitionComponent c : list) {
if (c.getCode().equals(code)) if (c.getCode().equals(code))
return c; return c;

View File

@ -943,6 +943,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
try { try {
ValueSet result = tc.getClient().expandValueset(vs, p); ValueSet result = tc.getClient().expandValueset(vs, p);
res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
if (res != null && res.getValueset() != null) {
res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, tc.getHost());
}
} catch (Exception e) { } catch (Exception e) {
res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, true); res = new ValueSetExpansionOutcome(e.getMessage() == null ? e.getClass().getName() : e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, true);
if (txLog != null) { if (txLog != null) {
@ -1013,6 +1016,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} }
} }
res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId()); res = new ValueSetExpansionOutcome(result).setTxLink(txLog.getLastId());
if (res != null && res.getValueset() != null) {
res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, tc.getHost());
}
} catch (Exception e) { } catch (Exception e) {
res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId()); res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId());
} }
@ -1085,6 +1091,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
res = null; res = null;
try { try {
res = vse.expand(vs, p); res = vse.expand(vs, p);
if (res != null && res.getValueset() != null) {
res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, vse.getSource());
}
} catch (Exception e) { } catch (Exception e) {
allErrors.addAll(vse.getAllErrors()); allErrors.addAll(vse.getAllErrors());
e.printStackTrace(); e.printStackTrace();
@ -1098,7 +1107,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT); txCache.cacheExpansion(cacheToken, res, TerminologyCache.TRANSIENT);
return res; return res;
} }
if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR || isNoTerminologyServer()) { // this class is created specifically to say: don't consult the server if (res.getErrorClass() == TerminologyServiceErrorClass.INTERNAL_ERROR || isNoTerminologyServer() || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNKNOWN) { // this class is created specifically to say: don't consult the server
return new ValueSetExpansionOutcome(res.getError(), res.getErrorClass(), false); return new ValueSetExpansionOutcome(res.getError(), res.getErrorClass(), false);
} }
@ -1133,6 +1142,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId()); res = new ValueSetExpansionOutcome((e.getMessage() == null ? e.getClass().getName() : e.getMessage()), TerminologyServiceErrorClass.UNKNOWN, allErrors, true).setTxLink(txLog == null ? null : txLog.getLastId());
} }
} }
if (res != null && res.getValueset() != null) {
res.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, tc.getHost());
}
txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT); txCache.cacheExpansion(cacheToken, res, TerminologyCache.PERMANENT);
return res; return res;
} }
@ -1698,6 +1710,9 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
} }
} }
Set<String> unknownSystems = new HashSet<>(); Set<String> unknownSystems = new HashSet<>();
if ("8517006".equals(code.getCodingFirstRep().getCode())) {
DebugUtilities.breakpoint();
}
List<OperationOutcomeIssueComponent> issues = new ArrayList<>(); List<OperationOutcomeIssueComponent> issues = new ArrayList<>();

View File

@ -90,7 +90,20 @@ public class ValueSetRenderer extends TerminologyRenderer {
if (vs.hasCopyright()) if (vs.hasCopyright())
generateCopyright(x, r); generateCopyright(x, r);
} }
if (vs.hasExtension(ToolingExtensions.EXT_VS_CS_SUPPL_NEEDED)) {
var p = x.para();
p.tx("This ValueSet requires the Code system Supplement ");
String u = ToolingExtensions.readStringExtension(vs, ToolingExtensions.EXT_VS_CS_SUPPL_NEEDED);
CodeSystem cs = context.getContext().fetchResource(CodeSystem.class, u);
if (cs == null) {
p.code().tx(u);
} else if (!cs.hasWebPath()) {
p.ah(u).tx(cs.present());
} else {
p.ah(cs.getWebPath()).tx(cs.present());
}
p.tx(".");
}
if (vs.hasExpansion()) { if (vs.hasExpansion()) {
// for now, we just accept an expansion if there is one // for now, we just accept an expansion if there is one
generateExpansion(status, r, x, vs, false, maps); generateExpansion(status, r, x, vs, false, maps);
@ -498,14 +511,26 @@ public class ValueSetRenderer extends TerminologyRenderer {
if (versions.size() == 1 && versions.get(s).size() == 1) { if (versions.size() == 1 && versions.get(s).size() == 1) {
for (String v : versions.get(s)) { // though there'll only be one for (String v : versions.get(s)) { // though there'll only be one
XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); XhtmlNode p = x.para().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION)+" "); if (!vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION)+" ");
} else if ("internal".equals(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))) {
p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION_INTERNAL)+" ");
} else {
p.tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSION_SRVR, vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))+" ");
}
expRef(p, s, v, vs); expRef(p, s, v, vs);
} }
} else { } else {
for (String v : versions.get(s)) { for (String v : versions.get(s)) {
if (first) { if (first) {
div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px"); div = x.div().style("border: black 1px dotted; background-color: #EEEEEE; padding: 8px; margin-bottom: 8px");
div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS)); if (!vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS));
} else if ("internal".equals(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE))) {
div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS_INTERNAL));
} else {
div.para().tx(context.formatPhrase(RenderingContext.VALUE_SET_EXPANSIONS_SRVR, vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE)));
}
ul = div.ul(); ul = div.ul();
first = false; first = false;
} }

View File

@ -48,8 +48,10 @@ import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.IntegerType; import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Enumerations.FilterOperator; import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.Identifier; import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Meta; import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
@ -65,6 +67,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptDefinitionComponentSorter;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities.ConceptStatus;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.utils.CanonicalResourceUtilities; import org.hl7.fhir.r5.utils.CanonicalResourceUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.UserDataNames; import org.hl7.fhir.r5.utils.UserDataNames;
@ -501,4 +504,97 @@ public class ValueSetUtilities extends TerminologyUtilities {
} }
} }
} }
public static String versionFromExpansionParams(Parameters expParameters, String system, String defaultVersion) {
for (ParametersParameterComponent p : expParameters.getParameter()) {
if ("system-version".equals(p.getName()) || "force-system-version".equals(p.getName())) {
String v = p.getValue().primitiveValue();
if (v.startsWith(system+"|")) {
String ver = v.substring(v.indexOf("|")+1);
if (defaultVersion == null || ver.startsWith(defaultVersion) || "force-system-version".equals(p.getName())) {
return ver;
}
}
}
}
return defaultVersion;
}
public static boolean isImplicitLoincValueSet(String url) {
return url.startsWith("http://loinc.org/vs");
}
public static boolean isImplicitSCTValueSet(String url) {
return url.startsWith("http://snomed.info/sct") && url.contains("?fhir_vs");
}
public static ValueSet makeImplicitValueSet(String url, String version) {
if (url.startsWith("http://snomed.info/sct")) {
return makeImplicitSCTVS(url, version);
} else if (url.startsWith("http://loinc.org/vs")) {
return makeImplicitLoincVS(url, version);
} else {
throw new FHIRException("Unknown implicit value set URL "+url);
}
}
private static ValueSet makeImplicitSCTVS(String url, String version) {
String query = url.substring(url.indexOf("?")+1);
if ("fhir_vs".equals(query)) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
vs.getCompose().addInclude().setSystem("http://snomed.info/sct");
return vs;
} else if (query.startsWith("fhir_vs=isa/")) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
vs.getCompose().addInclude().setSystem("http://snomed.info/sct").addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue(query.substring(12));
return vs;
} else if (query.equals("fhir_vs=refset")) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
vs.getCompose().addInclude().setSystem("http://snomed.info/sct").addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("refset-base");
return vs;
} else if (query.startsWith("fhir_vs=refset/")) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
vs.getCompose().addInclude().setSystem("http://snomed.info/sct").addFilter().setProperty("concept").setOp(FilterOperator.IN).setValue(query.substring(15));
return vs;
} else {
throw new FHIRException("Unknown implicit SNOMED CT value set URL "+url);
}
}
private static ValueSet makeImplicitLoincVS(String url, String version) {
if (url.equals("http://loinc.org/vs")) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
vs.getCompose().addInclude().setSystem("http://loinc.org");
return vs;
} else if (url.startsWith("http://loinc.org/vs/LP")) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
vs.getCompose().addInclude().setSystem("http://loinc.org").addFilter().setProperty("ancestor").setOp(FilterOperator.EQUAL).setValue(url.substring(21));
return vs;
} else if (url.startsWith("http://loinc.org/vs/LL")) {
ValueSet vs = new ValueSet();
vs.setUrl(url);
vs.setVersion(version);
// this isn't the actual definition, but it won't matter to us internally
vs.getCompose().addInclude().setSystem("http://loinc.org").addFilter().setProperty("answer-list").setOp(FilterOperator.EQUAL).setValue(url.substring(21));
return vs;
} else {
throw new FHIRException("Unknown implicit LOINC value set URL "+url);
}
}
} }

View File

@ -1,6 +1,8 @@
package org.hl7.fhir.r5.terminologies.client; package org.hl7.fhir.r5.terminologies.client;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
@ -219,4 +221,13 @@ public class TerminologyClientContext {
TerminologyClientContext.canUseCacheId = canUseCacheId; TerminologyClientContext.canUseCacheId = canUseCacheId;
} }
public String getHost() {
try {
URL uri = new URL(getAddress());
return uri.getHost();
} catch (MalformedURLException e) {
return getAddress();
}
}
} }

View File

@ -70,7 +70,9 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
@ -116,6 +118,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities; import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander.UnknownValueSetException;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider; import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension; import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext; import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
@ -135,6 +138,26 @@ import org.hl7.fhir.utilities.i18n.I18nConstants;
public class ValueSetExpander extends ValueSetProcessBase { public class ValueSetExpander extends ValueSetProcessBase {
public class UnknownValueSetException extends FHIRException {
protected UnknownValueSetException() {
super();
}
protected UnknownValueSetException(String message, Throwable cause) {
super(message, cause);
}
protected UnknownValueSetException(String message) {
super(message);
}
protected UnknownValueSetException(Throwable cause) {
super(cause);
}
}
public class Token { public class Token {
private String system; private String system;
private String code; private String code;
@ -169,6 +192,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
private boolean checkCodesWhenExpanding; private boolean checkCodesWhenExpanding;
private boolean includeAbstract = true; private boolean includeAbstract = true;
private boolean debug; private boolean debug;
private Set<String> sources = new HashSet<>();
private AcceptLanguageHeader langs; private AcceptLanguageHeader langs;
private List<Token> designations = new ArrayList<>(); private List<Token> designations = new ArrayList<>();
@ -612,6 +636,9 @@ public class ValueSetExpander extends ValueSetProcessBase {
if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) { if ((cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) && context.supportsSystem(exc.getSystem(), opContext.getOptions().getFhirVersion())) {
ValueSetExpansionOutcome vse = context.expandVS(new TerminologyOperationDetails(requiredSupplements), exc, false, false); ValueSetExpansionOutcome vse = context.expandVS(new TerminologyOperationDetails(requiredSupplements), exc, false, false);
ValueSet valueset = vse.getValueset(); ValueSet valueset = vse.getValueset();
if (valueset.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
sources.add(valueset.getUserString(UserDataNames.VS_EXPANSION_SOURCE));
}
if (valueset == null) if (valueset == null)
throw failTSE("Error Expanding ValueSet: "+vse.getError()); throw failTSE("Error Expanding ValueSet: "+vse.getError());
excludeCodes(wc, valueset.getExpansion()); excludeCodes(wc, valueset.getExpansion());
@ -677,6 +704,8 @@ public class ValueSetExpander extends ValueSetProcessBase {
} }
} catch (ETooCostly e) { } catch (ETooCostly e) {
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false); return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.TOO_COSTLY, allErrors, false);
} catch (UnknownValueSetException e) {
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.VALUESET_UNKNOWN, allErrors, false);
} catch (Exception e) { } catch (Exception e) {
if (debug) { if (debug) {
e.printStackTrace(); e.printStackTrace();
@ -870,9 +899,9 @@ public class ValueSetExpander extends ValueSetProcessBase {
boolean pinned = !url.equals(value); boolean pinned = !url.equals(value);
String ver = pinned ? url.substring(value.length()+1) : null; String ver = pinned ? url.substring(value.length()+1) : null;
if (context.fetchResource(CodeSystem.class, url, valueSet) != null) { if (context.fetchResource(CodeSystem.class, url, valueSet) != null) {
throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED : I18nConstants.VS_EXP_IMPORT_CS, true, value, ver); throw failUnk(pinned ? I18nConstants.VS_EXP_IMPORT_CS_PINNED : I18nConstants.VS_EXP_IMPORT_CS, true, value, ver);
} else { } else {
throw fail(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED : I18nConstants.VS_EXP_IMPORT_UNK, true, value, ver); throw failUnk(pinned ? I18nConstants.VS_EXP_IMPORT_UNK_PINNED : I18nConstants.VS_EXP_IMPORT_UNK, true, value, ver);
} }
} }
checkCanonical(exp, vs, focus); checkCanonical(exp, vs, focus);
@ -880,13 +909,19 @@ public class ValueSetExpander extends ValueSetProcessBase {
expParams = expParams.copy(); expParams = expParams.copy();
expParams.addParameter("activeOnly", true); expParams.addParameter("activeOnly", true);
} }
ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams); ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors);
ValueSetExpansionOutcome vso = expander.expand(vs, expParams);
if (vso.getError() != null) { if (vso.getError() != null) {
addErrors(vso.getAllErrors()); addErrors(vso.getAllErrors());
throw fail(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError()); if (vso.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNKNOWN) {
throw failUnk(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
} else {
throw fail(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
}
} else if (vso.getValueset() == null) { } else if (vso.getValueset() == null) {
throw fail(I18nConstants.VS_EXP_IMPORT_FAIL, true, vs.getUrl()); throw fail(I18nConstants.VS_EXP_IMPORT_FAIL, true, vs.getUrl());
} }
sources.addAll(expander.sources);
if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
if (!existsInParams(exp.getParameter(), "used-valueset", u)) if (!existsInParams(exp.getParameter(), "used-valueset", u))
@ -938,10 +973,12 @@ public class ValueSetExpander extends ValueSetProcessBase {
expParams = expParams.copy(); expParams = expParams.copy();
expParams.addParameter("activeOnly", true); expParams.addParameter("activeOnly", true);
} }
ValueSetExpansionOutcome vso = new ValueSetExpander(context, opContext.copy(), allErrors).expand(vs, expParams); ValueSetExpander expander = new ValueSetExpander(context, opContext.copy(), allErrors);
ValueSetExpansionOutcome vso = expander.expand(vs, expParams);
sources.addAll(expander.sources);
if (vso.getError() != null) { if (vso.getError() != null) {
addErrors(vso.getAllErrors()); addErrors(vso.getAllErrors());
throw fail(I18nConstants.VS_EXP_IMPORT_ERROR_X, true, vs.getUrl(), vso.getError()); throw fail(I18nConstants.VS_EXP_IMPORT_ERROR, true, vs.getUrl(), vso.getError());
} else if (vso.getValueset() == null) { } else if (vso.getValueset() == null) {
throw fail(I18nConstants.VS_EXP_IMPORT_FAIL_X, true, vs.getUrl()); throw fail(I18nConstants.VS_EXP_IMPORT_FAIL_X, true, vs.getUrl());
} }
@ -1055,6 +1092,9 @@ public class ValueSetExpander extends ValueSetProcessBase {
throw failTSE("Unable to expand imported value set: " + vso.getError()); throw failTSE("Unable to expand imported value set: " + vso.getError());
} }
ValueSet vs = vso.getValueset(); ValueSet vs = vso.getValueset();
if (vs.hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
sources.add(vs.getUserString(UserDataNames.VS_EXPANSION_SOURCE));
}
if (vs.hasVersion() || REPORT_VERSION_ANYWAY) { if (vs.hasVersion() || REPORT_VERSION_ANYWAY) {
UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : "")); UriType u = new UriType(vs.getUrl() + (vs.hasVersion() ? "|"+vs.getVersion() : ""));
if (!existsInParams(exp.getParameter(), "used-valueset", u)) { if (!existsInParams(exp.getParameter(), "used-valueset", u)) {
@ -1328,6 +1368,12 @@ public class ValueSetExpander extends ValueSetProcessBase {
return new FHIRException(msg); return new FHIRException(msg);
} }
private UnknownValueSetException failUnk(String msgId, boolean check, Object... params) {
String msg = context.formatMessage(msgId, params);
allErrors.add(msg);
return new UnknownValueSetException(msg);
}
private ETooCostly failCostly(String msg) { private ETooCostly failCostly(String msg) {
allErrors.add(msg); allErrors.add(msg);
return new ETooCostly(msg); return new ETooCostly(msg);
@ -1372,5 +1418,13 @@ public class ValueSetExpander extends ValueSetProcessBase {
return this; return this;
} }
public String getSource() {
if (sources.isEmpty()) {
return "internal";
} else {
return CommaSeparatedStringBuilder.join(", ", Utilities.sorted(sources));
}
}
} }

View File

@ -56,6 +56,7 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet; import org.hl7.fhir.r5.terminologies.utilities.TerminologyCache.SourcedValueSet;
import org.hl7.fhir.r5.utils.UserDataNames;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.IniFile; import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.StringPair; import org.hl7.fhir.utilities.StringPair;
@ -419,7 +420,7 @@ public class TerminologyCache {
nameCacheToken(vs, ct); nameCacheToken(vs, ct);
JsonParser json = new JsonParser(); JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY); json.setOutputStyle(OutputStyle.PRETTY);
String expJS = json.composeString(expParameters); String expJS = expParameters == null ? "" : json.composeString(expParameters);
if (vs != null && vs.hasUrl() && vs.hasVersion()) { if (vs != null && vs.hasUrl() && vs.hasVersion()) {
ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl()) ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"url\": \""+Utilities.escapeJson(vs.getUrl())
@ -699,8 +700,12 @@ public class TerminologyCache {
sw.write("e: {\r\n"); sw.write("e: {\r\n");
if (ce.e.isFromServer()) if (ce.e.isFromServer())
sw.write(" \"from-server\" : true,\r\n"); sw.write(" \"from-server\" : true,\r\n");
if (ce.e.getValueset() != null) if (ce.e.getValueset() != null) {
if (ce.e.getValueset().hasUserData(UserDataNames.VS_EXPANSION_SOURCE)) {
sw.write(" \"source\" : "+Utilities.escapeJson(ce.e.getValueset().getUserString(UserDataNames.VS_EXPANSION_SOURCE)).trim()+",\r\n");
}
sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n"); sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n");
}
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n"); sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n");
} else if (ce.s != null) { } else if (ce.s != null) {
sw.write("s: {\r\n"); sw.write("s: {\r\n");
@ -820,10 +825,14 @@ public class TerminologyCache {
JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(resultString); JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(resultString);
String error = loadJS(o.get("error")); String error = loadJS(o.get("error"));
if (e == 'e') { if (e == 'e') {
if (o.has("valueSet")) if (o.has("valueSet")) {
ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server")); ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server"));
else if (o.has("source")) {
ce.e.getValueset().setUserData(UserDataNames.VS_EXPANSION_SOURCE, o.get("source").getAsString());
}
} else {
ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server")); ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server"));
}
} else if (e == 's') { } else if (e == 's') {
ce.s = new SubsumesResult(o.get("result").getAsBoolean()); ce.s = new SubsumesResult(o.get("result").getAsBoolean());
} else { } else {

View File

@ -1,7 +1,7 @@
package org.hl7.fhir.r5.terminologies.utilities; package org.hl7.fhir.r5.terminologies.utilities;
public enum TerminologyServiceErrorClass { public enum TerminologyServiceErrorClass {
UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, CODESYSTEM_UNSUPPORTED_VERSION, BLOCKED_BY_OPTIONS, INTERNAL_ERROR, BUSINESS_RULE, TOO_COSTLY, PROCESSING; UNKNOWN, NOSERVICE, SERVER_ERROR, VALUESET_UNSUPPORTED, CODESYSTEM_UNSUPPORTED, CODESYSTEM_UNSUPPORTED_VERSION, BLOCKED_BY_OPTIONS, INTERNAL_ERROR, BUSINESS_RULE, TOO_COSTLY, PROCESSING, VALUESET_UNKNOWN;
public boolean isInfrastructure() { public boolean isInfrastructure() {
return this == NOSERVICE || this == SERVER_ERROR || this == VALUESET_UNSUPPORTED; return this == NOSERVICE || this == SERVER_ERROR || this == VALUESET_UNSUPPORTED;

View File

@ -138,6 +138,6 @@ public class UserDataNames {
public static final String kindling_ballot_package = "ballot.package"; public static final String kindling_ballot_package = "ballot.package";
public static final String archetypeSource = "archetype-source"; public static final String archetypeSource = "archetype-source";
public static final String archetypeName = "archetype-name"; public static final String archetypeName = "archetype-name";
public static final String VS_EXPANSION_SOURCE = "VS_EXPANSION_SOURCE";
} }

View File

@ -865,6 +865,10 @@ public class RenderingI18nContext extends I18nBase {
public static final String VALUE_SET_EXP = "VALUE_SET_EXP"; public static final String VALUE_SET_EXP = "VALUE_SET_EXP";
public static final String VALUE_SET_EXPANSION = "VALUE_SET_EXPANSION"; public static final String VALUE_SET_EXPANSION = "VALUE_SET_EXPANSION";
public static final String VALUE_SET_EXPANSIONS = "VALUE_SET_EXPANSIONS"; public static final String VALUE_SET_EXPANSIONS = "VALUE_SET_EXPANSIONS";
public static final String VALUE_SET_EXPANSION_SRVR = "VALUE_SET_EXPANSION_SRVR";
public static final String VALUE_SET_EXPANSIONS_SRVR = "VALUE_SET_EXPANSIONS_SRVR";
public static final String VALUE_SET_EXPANSION_INTERNAL = "VALUE_SET_EXPANSION_INTERNAL";
public static final String VALUE_SET_EXPANSIONS_INTERNAL = "VALUE_SET_EXPANSIONS_INTERNAL";
public static final String VALUE_SET_EXP_FRAG = "VALUE_SET_EXP_FRAG"; public static final String VALUE_SET_EXP_FRAG = "VALUE_SET_EXP_FRAG";
public static final String VALUE_SET_GENERALIZES = "VALUE_SET_GENERALIZES"; public static final String VALUE_SET_GENERALIZES = "VALUE_SET_GENERALIZES";
public static final String VALUE_SET_HAS = "VALUE_SET_HAS"; public static final String VALUE_SET_HAS = "VALUE_SET_HAS";

View File

@ -1191,15 +1191,13 @@ VS_EXP_IMPORT_UNK = Unable to find included value set ''{0}''
VS_EXP_IMPORT_UNK_PINNED = Unable to find included value set ''{0}'' version ''{1}'' VS_EXP_IMPORT_UNK_PINNED = Unable to find included value set ''{0}'' version ''{1}''
VS_EXP_IMPORT_NULL = Unable to find included value set with no identity VS_EXP_IMPORT_NULL = Unable to find included value set with no identity
VS_EXP_IMPORT_ERROR = Unable to expand included value set ''{0}'': {1} VS_EXP_IMPORT_ERROR = Unable to expand included value set ''{0}'': {1}
VS_EXP_IMPORT_ERROR = Unable to expand included value set ''{0}'', but no error VS_EXP_IMPORT_ERROR_X = Unable to expand included value set ''{0}'', but no error
VS_EXP_IMPORT_ERROR_TOO_COSTLY = Unable to expand excluded value set ''{0}'': too costly
VS_EXP_IMPORT_CS_X = Cannot exclude value set ''{0}'' because it's actually a code system VS_EXP_IMPORT_CS_X = Cannot exclude value set ''{0}'' because it's actually a code system
VS_EXP_IMPORT_CS_PINNED_X = Cannot exclude value set ''{0}'' version ''{1}'' because it's actually a code system VS_EXP_IMPORT_CS_PINNED_X = Cannot exclude value set ''{0}'' version ''{1}'' because it's actually a code system
VS_EXP_IMPORT_UNK_X = Unable to find excluded value set ''{0}'' VS_EXP_IMPORT_UNK_X = Unable to find excluded value set ''{0}''
VS_EXP_IMPORT_UNK_PINNED_X = Unable to find excluded value set ''{0}'' version ''{1}'' VS_EXP_IMPORT_UNK_PINNED_X = Unable to find excluded value set ''{0}'' version ''{1}''
VS_EXP_IMPORT_NUL_XL = Unable to find excluded value set with no identity VS_EXP_IMPORT_NUL_XL = Unable to find excluded value set with no identity
VS_EXP_IMPORT_ERROR_X = Unable to expand excluded value set ''{0}'': {1}
VS_EXP_IMPORT_ERROR_X = Unable to expand excluded value set ''{0}'', but no error
VS_EXP_IMPORT_ERROR_TOO_COSTLY = Unable to expand excluded value set ''{0}'': too costly
VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported yet VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported yet
CONCEPTMAP_VS_NOT_A_VS = Reference must be to a ValueSet, but found a {0} instead CONCEPTMAP_VS_NOT_A_VS = Reference must be to a ValueSet, but found a {0} instead
SD_DERIVATION_NO_CONCRETE = {0} is labeled as an abstract type, but no concrete descendants were found (check definitions - this is usually an error unless concrete definitions are in some other package) SD_DERIVATION_NO_CONCRETE = {0} is labeled as an abstract type, but no concrete descendants were found (check definitions - this is usually an error unless concrete definitions are in some other package)

View File

@ -851,6 +851,10 @@ VALUE_SET_EXISTS = exists
VALUE_SET_EXP = Expansion based on example code system VALUE_SET_EXP = Expansion based on example code system
VALUE_SET_EXPANSION = Expansion based on VALUE_SET_EXPANSION = Expansion based on
VALUE_SET_EXPANSIONS = Expansion based on: VALUE_SET_EXPANSIONS = Expansion based on:
VALUE_SET_EXPANSION_SRVR = Expansion from {0} based on
VALUE_SET_EXPANSIONS_SRVR = Expansion from {0} based on:
VALUE_SET_EXPANSION_INTERNAL = Expansion done internally based on
VALUE_SET_EXPANSIONS_INTERNAL = Expansion done internally based on:
VALUE_SET_EXP_FRAG = Expansion based on code system fragment VALUE_SET_EXP_FRAG = Expansion based on code system fragment
VALUE_SET_GENERALIZES = generalizes VALUE_SET_GENERALIZES = generalizes
VALUE_SET_HAS = This value set has {0} codes in it. In order to keep the publication size manageable, only a selection ({1} codes) of the whole set of codes is shown. VALUE_SET_HAS = This value set has {0} codes in it. In order to keep the publication size manageable, only a selection ({1} codes) of the whole set of codes is shown.

View File

@ -184,6 +184,9 @@ private static TxTestData testData;
case UNKNOWN: case UNKNOWN:
e.setCode(IssueType.UNKNOWN); e.setCode(IssueType.UNKNOWN);
break; break;
case VALUESET_UNKNOWN:
e.setCode(IssueType.UNKNOWN);
break;
case VALUESET_UNSUPPORTED: case VALUESET_UNSUPPORTED:
e.setCode(IssueType.NOTSUPPORTED); e.setCode(IssueType.NOTSUPPORTED);
break; break;