From df3456d293f72ad297bbca99ef16d95dc12f7d55 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Sun, 25 Feb 2024 19:14:12 +1100 Subject: [PATCH] Add support for subsumes in tx client --- .../txClient/TerminologyClientR2.java | 8 ++ .../txClient/TerminologyClientR3.java | 7 ++ .../txClient/TerminologyClientR4.java | 20 +++- .../fhir/r5/context/BaseWorkerContext.java | 79 +++++++++++++- .../hl7/fhir/r5/context/IWorkerContext.java | 7 ++ .../client/ITerminologyClient.java | 1 + .../client/TerminologyClientR5.java | 6 ++ .../utilities/TerminologyCache.java | 100 +++++++++++++++--- 8 files changed, 213 insertions(+), 15 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java index 31cdb891f..21be0fed9 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java @@ -117,6 +117,13 @@ public class TerminologyClientR2 implements ITerminologyClient { p2 = client.operateType(org.hl7.fhir.dstu2.model.ValueSet.class, "validate-code", 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 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); return result == null ? null : (Bundle) VersionConvertorFactory_10_50.convertResource(result); } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java index 8aa22c3e5..839c4aed4 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java @@ -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); + } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java index e0af0a4ff..fd5f68f1b 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java @@ -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 public Parameters validateVS(Parameters pin) throws FHIRException { try { @@ -249,6 +268,5 @@ public class TerminologyClientR4 implements ITerminologyClient { org.hl7.fhir.r4.model.Bundle result = client.search(type, criteria); return result == null ? null : (Bundle) VersionConvertorFactory_40_50.convertResource(result); } - } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 745a01124..33d883a08 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -1347,7 +1347,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte Set systems = findRelevantSystems(code, vs); 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) { txLog("$validate "+csumm+(vs == null ? "" : " for "+ txCache.summary(vs))+" on "+tc.getAddress()); } else { @@ -1377,6 +1377,83 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte 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 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) { return new ValueSetExpander(this, new TerminologyOperationContext(this, options)).setDebug(logger.isDebugLogging()); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 1238b6400..6145f0a54 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -640,4 +640,11 @@ public interface IWorkerContext { public T findTxResource(Class class_, String canonical); public T findTxResource(Class 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); + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/ITerminologyClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/ITerminologyClient.java index e5bbf0a2b..c7f2dfcc8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/ITerminologyClient.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/ITerminologyClient.java @@ -54,6 +54,7 @@ public interface ITerminologyClient { ValueSet expandValueset(ValueSet vs, Parameters p) throws FHIRException; Parameters validateCS(Parameters pin) throws FHIRException; Parameters validateVS(Parameters pin) throws FHIRException; + Parameters subsumes(Parameters pin) throws FHIRException; ITerminologyClient setTimeoutFactor(int i) throws FHIRException; ToolingClientLogger getLogger(); ITerminologyClient setLogger(ToolingClientLogger txLog) throws FHIRException; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java index 002be62a3..ef257dda3 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/client/TerminologyClientR5.java @@ -135,6 +135,11 @@ public class TerminologyClientR5 implements ITerminologyClient { return client.operateType(CodeSystem.class, "validate-code", pin); } + @Override + public Parameters subsumes(Parameters pin) { + return client.operateType(CodeSystem.class, "subsumes", pin); + } + @Override public Parameters validateVS(Parameters pin) { return client.operateType(ValueSet.class, "validate-code", pin); @@ -256,4 +261,5 @@ public class TerminologyClientR5 implements ITerminologyClient { return client.search(type, criteria); } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java index 83cacdcab..f02055817 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/utilities/TerminologyCache.java @@ -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() { return systemNameKeyGenerator; } @@ -218,6 +233,7 @@ public class TerminologyCache { private boolean persistent; private ValidationResult v; private ValueSetExpansionOutcome e; + private SubsumesResult s; } private class NamedCache { @@ -381,9 +397,9 @@ public class TerminologyCache { if (code.hasSystem()) { ct.setName(code.getSystem()); ct.hasVersion = code.hasVersion(); - } - else + } else { ct.name = NAME_FOR_NO_SYSTEM; + } ct.setName(vsUrl); JsonParser json = new JsonParser(); json.setOutputStyle(OutputStyle.PRETTY); @@ -633,6 +649,9 @@ public class TerminologyCache { if (ce.e.getValueset() != null) sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\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 { sw.write("v: {\r\n"); boolean first = true; @@ -743,15 +762,17 @@ public class TerminologyCache { CacheEntry ce = new CacheEntry(); ce.persistent = true; ce.request = request; - boolean e = resultString.charAt(0) == 'e'; + char e = resultString.charAt(0); resultString = resultString.substring(3); JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(resultString); String error = loadJS(o.get("error")); - if (e) { + if (e == 'e') { if (o.has("valueSet")) ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server")); else ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN, o.has("from-server")); + } else if (e == 's') { + ce.s = new SubsumesResult(o.get("result").getAsBoolean()); } else { String t = loadJS(o.get("severity")); IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t); @@ -934,15 +955,15 @@ public class TerminologyCache { public Map servers() { Map servers = new HashMap<>(); - 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/r4", "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/r3", "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://local.fhir.org/r2", "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/r5", "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/r4", "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/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); + } + } + } + } \ No newline at end of file