Add support for subsumes in tx client

This commit is contained in:
Grahame Grieve 2024-02-25 19:14:12 +11:00
parent 981d201046
commit df3456d293
8 changed files with 213 additions and 15 deletions

View File

@ -117,6 +117,13 @@ public class TerminologyClientR2 implements ITerminologyClient {
p2 = client.operateType(org.hl7.fhir.dstu2.model.ValueSet.class, "validate-code", p2); p2 = client.operateType(org.hl7.fhir.dstu2.model.ValueSet.class, "validate-code", p2);
return (Parameters) VersionConvertorFactory_10_50.convertResource(p2); return (Parameters) VersionConvertorFactory_10_50.convertResource(p2);
} }
@Override
public Parameters subsumes(Parameters pin) throws FHIRException {
org.hl7.fhir.dstu2.model.Parameters p2 = (org.hl7.fhir.dstu2.model.Parameters) VersionConvertorFactory_10_50.convertResource(pin);
p2 = client.operateType(org.hl7.fhir.dstu2.model.ValueSet.class, "subsumes", p2);
return (Parameters) VersionConvertorFactory_10_50.convertResource(p2);
}
@Override @Override
public Parameters validateVS(Parameters pin) throws FHIRException { public Parameters validateVS(Parameters pin) throws FHIRException {
@ -238,4 +245,5 @@ public class TerminologyClientR2 implements ITerminologyClient {
org.hl7.fhir.dstu2.model.Bundle result = client.search(type, criteria); org.hl7.fhir.dstu2.model.Bundle result = client.search(type, criteria);
return result == null ? null : (Bundle) VersionConvertorFactory_10_50.convertResource(result); return result == null ? null : (Bundle) VersionConvertorFactory_10_50.convertResource(result);
} }
} }

View File

@ -242,4 +242,11 @@ public class TerminologyClientR3 implements ITerminologyClient {
} }
@Override
public Parameters subsumes(Parameters pin) throws FHIRException {
org.hl7.fhir.dstu3.model.Parameters p2 = (org.hl7.fhir.dstu3.model.Parameters) VersionConvertorFactory_30_50.convertResource(pin);
p2 = client.operateType(org.hl7.fhir.dstu3.model.CodeSystem.class, "subsumes", p2);
return (Parameters) VersionConvertorFactory_30_50.convertResource(p2);
}
} }

View File

@ -111,6 +111,25 @@ public class TerminologyClientR4 implements ITerminologyClient {
} }
} }
@Override
public Parameters subsumes(Parameters pin) throws FHIRException {
try {
org.hl7.fhir.r4.model.Parameters p2 = (org.hl7.fhir.r4.model.Parameters) VersionConvertorFactory_40_50.convertResource(pin);
p2 = client.operateType(org.hl7.fhir.r4.model.CodeSystem.class, "subsumes", p2);
return (Parameters) VersionConvertorFactory_40_50.convertResource(p2);
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), e);
}
} catch (IOException e) {
throw new FHIRException(e);
}
}
@Override @Override
public Parameters validateVS(Parameters pin) throws FHIRException { public Parameters validateVS(Parameters pin) throws FHIRException {
try { try {
@ -249,6 +268,5 @@ public class TerminologyClientR4 implements ITerminologyClient {
org.hl7.fhir.r4.model.Bundle result = client.search(type, criteria); org.hl7.fhir.r4.model.Bundle result = client.search(type, criteria);
return result == null ? null : (Bundle) VersionConvertorFactory_40_50.convertResource(result); return result == null ? null : (Bundle) VersionConvertorFactory_40_50.convertResource(result);
} }
} }

View File

@ -1347,7 +1347,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
Set<String> systems = findRelevantSystems(code, vs); Set<String> systems = findRelevantSystems(code, vs);
TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false); TerminologyClientContext tc = terminologyClientManager.chooseServer(vs, systems, false);
String csumm =cachingAllowed && txCache != null ? txCache.summary(code) : null; String csumm = cachingAllowed && txCache != null ? txCache.summary(code) : null;
if (cachingAllowed && txCache != null) { if (cachingAllowed && txCache != null) {
txLog("$validate "+csumm+(vs == null ? "" : " for "+ txCache.summary(vs))+" on "+tc.getAddress()); txLog("$validate "+csumm+(vs == null ? "" : " for "+ txCache.summary(vs))+" on "+tc.getAddress());
} else { } else {
@ -1377,6 +1377,83 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
return res; return res;
} }
/**
* ask the terminology system whether parent subsumes child.
*
* @return true if it does, false if it doesn't, and null if it's not know whether it does
*/
public Boolean subsumes(ValidationOptions optionsArg, Coding parent, Coding child) {
ValidationOptions options = optionsArg != null ? optionsArg : ValidationOptions.defaults();
if (parent.hasSystem()) {
codeSystemsUsed.add(parent.getSystem());
} else {
return null;
}
if (child.hasSystem()) {
codeSystemsUsed.add(child.getSystem());
} else {
return null;
}
final CacheToken cacheToken = cachingAllowed && txCache != null ? txCache.generateSubsumesToken(options, parent, child, expParameters) : null;
if (cachingAllowed && txCache != null) {
Boolean res = txCache.getSubsumes(cacheToken);
if (res != null) {
return res;
}
}
if (options.isUseClient() && parent.getSystem().equals(child.getSystem())) {
CodeSystem cs = fetchCodeSystem(parent.getSystem());
if (cs != null) {
Boolean b = CodeSystemUtilities.subsumes(cs, parent.getCode(), child.getCode());
if (txCache != null && cachingAllowed) {
txCache.cacheSubsumes(cacheToken, b, true);
}
return b;
}
}
if (!terminologyClientManager.hasClient() || !options.isUseServer() || unsupportedCodeSystems.contains(parent.getSystem()) || unsupportedCodeSystems.contains(child.getSystem()) || noTerminologyServer) {
return null;
}
Set<String> systems = new HashSet<>();
systems.add(parent.getSystem());
systems.add(child.getSystem());
TerminologyClientContext tc = terminologyClientManager.chooseServer(null, systems, false);
txLog("$subsumes "+parent.toString()+" > "+child.toString()+" on "+tc.getAddress());
try {
Parameters pIn = new Parameters();
pIn.addParameter().setName("codingA").setValue(parent);
pIn.addParameter().setName("codingB").setValue(child);
if (txLog != null) {
txLog.clearLastId();
}
Parameters pOut = tc.getClient().subsumes(pIn);
return processSubsumesResult(pOut, tc.getClient().getAddress());
} catch (Exception e) {
// e.printStackTrace();
}
return null;
}
public Boolean processSubsumesResult(Parameters pOut, String server) {
for (ParametersParameterComponent p : pOut.getParameter()) {
if (p.hasValue()) {
if (p.getName().equals("outcome")) {
return Utilities.existsInList(p.getValue().primitiveValue(), "equivalent", "subsumes");
}
}
}
return null;
}
protected ValueSetExpander constructValueSetExpanderSimple(ValidationOptions options) { protected ValueSetExpander constructValueSetExpanderSimple(ValidationOptions options) {
return new ValueSetExpander(this, new TerminologyOperationContext(this, options)).setDebug(logger.isDebugLogging()); return new ValueSetExpander(this, new TerminologyOperationContext(this, options)).setDebug(logger.isDebugLogging());
} }

View File

@ -640,4 +640,11 @@ public interface IWorkerContext {
public <T extends Resource> T findTxResource(Class<T> class_, String canonical); public <T extends Resource> T findTxResource(Class<T> class_, String canonical);
public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version); public <T extends Resource> T findTxResource(Class<T> class_, String canonical, String version);
/**
* ask the terminology system whether parent subsumes child.
*
* @return true if it does, false if it doesn't, and null if it's not know whether it does
*/
public Boolean subsumes(ValidationOptions options, Coding parent, Coding child);
} }

View File

@ -54,6 +54,7 @@ public interface ITerminologyClient {
ValueSet expandValueset(ValueSet vs, Parameters p) throws FHIRException; ValueSet expandValueset(ValueSet vs, Parameters p) throws FHIRException;
Parameters validateCS(Parameters pin) throws FHIRException; Parameters validateCS(Parameters pin) throws FHIRException;
Parameters validateVS(Parameters pin) throws FHIRException; Parameters validateVS(Parameters pin) throws FHIRException;
Parameters subsumes(Parameters pin) throws FHIRException;
ITerminologyClient setTimeoutFactor(int i) throws FHIRException; ITerminologyClient setTimeoutFactor(int i) throws FHIRException;
ToolingClientLogger getLogger(); ToolingClientLogger getLogger();
ITerminologyClient setLogger(ToolingClientLogger txLog) throws FHIRException; ITerminologyClient setLogger(ToolingClientLogger txLog) throws FHIRException;

View File

@ -135,6 +135,11 @@ public class TerminologyClientR5 implements ITerminologyClient {
return client.operateType(CodeSystem.class, "validate-code", pin); return client.operateType(CodeSystem.class, "validate-code", pin);
} }
@Override
public Parameters subsumes(Parameters pin) {
return client.operateType(CodeSystem.class, "subsumes", pin);
}
@Override @Override
public Parameters validateVS(Parameters pin) { public Parameters validateVS(Parameters pin) {
return client.operateType(ValueSet.class, "validate-code", pin); return client.operateType(ValueSet.class, "validate-code", pin);
@ -256,4 +261,5 @@ public class TerminologyClientR5 implements ITerminologyClient {
return client.search(type, criteria); return client.search(type, criteria);
} }
} }

View File

@ -148,6 +148,21 @@ public class TerminologyCache {
} }
} }
public static class SubsumesResult {
private Boolean result;
protected SubsumesResult(Boolean result) {
super();
this.result = result;
}
public Boolean getResult() {
return result;
}
}
protected SystemNameKeyGenerator getSystemNameKeyGenerator() { protected SystemNameKeyGenerator getSystemNameKeyGenerator() {
return systemNameKeyGenerator; return systemNameKeyGenerator;
} }
@ -218,6 +233,7 @@ public class TerminologyCache {
private boolean persistent; private boolean persistent;
private ValidationResult v; private ValidationResult v;
private ValueSetExpansionOutcome e; private ValueSetExpansionOutcome e;
private SubsumesResult s;
} }
private class NamedCache { private class NamedCache {
@ -381,9 +397,9 @@ public class TerminologyCache {
if (code.hasSystem()) { if (code.hasSystem()) {
ct.setName(code.getSystem()); ct.setName(code.getSystem());
ct.hasVersion = code.hasVersion(); ct.hasVersion = code.hasVersion();
} } else {
else
ct.name = NAME_FOR_NO_SYSTEM; ct.name = NAME_FOR_NO_SYSTEM;
}
ct.setName(vsUrl); ct.setName(vsUrl);
JsonParser json = new JsonParser(); JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY); json.setOutputStyle(OutputStyle.PRETTY);
@ -633,6 +649,9 @@ public class TerminologyCache {
if (ce.e.getValueset() != null) if (ce.e.getValueset() != null)
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) {
sw.write("s: {\r\n");
sw.write(" \"result\" : "+ce.s.result+"\r\n}\r\n");
} else { } else {
sw.write("v: {\r\n"); sw.write("v: {\r\n");
boolean first = true; boolean first = true;
@ -743,15 +762,17 @@ public class TerminologyCache {
CacheEntry ce = new CacheEntry(); CacheEntry ce = new CacheEntry();
ce.persistent = true; ce.persistent = true;
ce.request = request; ce.request = request;
boolean e = resultString.charAt(0) == 'e'; char e = resultString.charAt(0);
resultString = resultString.substring(3); resultString = resultString.substring(3);
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) { 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 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') {
ce.s = new SubsumesResult(o.get("result").getAsBoolean());
} else { } else {
String t = loadJS(o.get("severity")); String t = loadJS(o.get("severity"));
IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t); IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t);
@ -934,15 +955,15 @@ public class TerminologyCache {
public Map<String, String> servers() { public Map<String, String> servers() {
Map<String, String> servers = new HashMap<>(); Map<String, String> servers = new HashMap<>();
servers.put("http://local.fhir.org/r2", "tx.fhir.org"); // servers.put("http://local.fhir.org/r2", "tx.fhir.org");
servers.put("http://local.fhir.org/r3", "tx.fhir.org"); // servers.put("http://local.fhir.org/r3", "tx.fhir.org");
servers.put("http://local.fhir.org/r4", "tx.fhir.org"); // servers.put("http://local.fhir.org/r4", "tx.fhir.org");
servers.put("http://local.fhir.org/r5", "tx.fhir.org"); // servers.put("http://local.fhir.org/r5", "tx.fhir.org");
//
servers.put("http://tx-dev.fhir.org/r2", "tx.fhir.org"); // servers.put("http://tx-dev.fhir.org/r2", "tx.fhir.org");
servers.put("http://tx-dev.fhir.org/r3", "tx.fhir.org"); // servers.put("http://tx-dev.fhir.org/r3", "tx.fhir.org");
servers.put("http://tx-dev.fhir.org/r4", "tx.fhir.org"); // servers.put("http://tx-dev.fhir.org/r4", "tx.fhir.org");
servers.put("http://tx-dev.fhir.org/r5", "tx.fhir.org"); // servers.put("http://tx-dev.fhir.org/r5", "tx.fhir.org");
servers.put("http://tx.fhir.org/r2", "tx.fhir.org"); servers.put("http://tx.fhir.org/r2", "tx.fhir.org");
servers.put("http://tx.fhir.org/r3", "tx.fhir.org"); servers.put("http://tx.fhir.org/r3", "tx.fhir.org");
@ -1002,5 +1023,58 @@ public class TerminologyCache {
} }
} }
public CacheToken generateSubsumesToken(ValidationOptions options, Coding parent, Coding child, Parameters expParameters) {
try {
CacheToken ct = new CacheToken();
if (parent.hasSystem()) {
ct.setName(parent.getSystem());
}
if (child.hasSystem()) {
ct.setName(child.getSystem());
}
ct.hasVersion = parent.hasVersion() || child.hasVersion();
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
String expJS = json.composeString(expParameters);
ct.request = "{\"op\": \"subsumes\", \"parent\" : "+json.composeString(parent, "code")+", \"child\" :"+json.composeString(child, "code")+(options == null ? "" : ", "+options.toJson())+", \"profile\": "+expJS+"}";
ct.key = String.valueOf(hashJson(ct.request));
return ct;
} catch (IOException e) {
throw new Error(e);
}
}
public Boolean getSubsumes(CacheToken cacheToken) {
if (cacheToken.key == null) {
return null;
}
synchronized (lock) {
requestCount++;
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = nc.map.get(cacheToken.key);
if (e == null) {
networkCount++;
return null;
} else {
hitCount++;
return e.s.result;
}
}
}
public void cacheSubsumes(CacheToken cacheToken, Boolean b, boolean persistent) {
if (cacheToken.key != null) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = new CacheEntry();
e.request = cacheToken.request;
e.persistent = persistent;
e.s = new SubsumesResult(b);
store(cacheToken, persistent, nc, e);
}
}
}
} }