Skip to content

Commit

Permalink
Issue #2162 - introduce a new closure method signature
Browse files Browse the repository at this point in the history
Signed-off-by: John T.E. Timm <johntimm@us.ibm.com>
  • Loading branch information
JohnTimm committed Apr 6, 2021
1 parent 81c6aa5 commit f7ed8ac
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -115,6 +116,16 @@ public Set<Concept> closure(CodeSystem codeSystem, Code code) {
return concepts;
}

@Override
public Map<Code, Set<Concept>> closure(CodeSystem codeSystem, Set<Code> codes) {
Map<Code, Set<Concept>> result = new LinkedHashMap<>();
for (Code code : codes) {
Set<Concept> closure = closure(codeSystem, code);
result.put(code, closure);
}
return result;
}

@Override
public Concept getConcept(CodeSystem codeSystem, Code code) {
checkArguments(codeSystem, code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.testng.Assert;
Expand All @@ -33,6 +34,11 @@ public Set<Concept> closure(CodeSystem codeSystem, Code code) {
return Collections.emptySet();
}

@Override
public Map<Code, Set<Concept>> closure(CodeSystem codeSystem, Set<Code> codes) {
return Collections.emptyMap();
}

@Override
public Concept getConcept(CodeSystem codeSystem, Code code) {
return Concept.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
Expand Down Expand Up @@ -59,6 +62,11 @@ public Set<Concept> closure(CodeSystem codeSystem, Code code) {
return Collections.emptySet();
}

@Override
public Map<Code, Set<Concept>> closure(CodeSystem codeSystem, Set<Code> codes) {
return Collections.emptyMap();
}

@Override
public Concept getConcept(CodeSystem codeSystem, Code code) {
return null;
Expand Down Expand Up @@ -126,7 +134,9 @@ public Set<Concept> closure(Coding coding) {
if (system != null && code != null) {
java.lang.String url = (version != null) ? system.getValue() + "|" + version : system.getValue();
CodeSystem codeSystem = CodeSystemSupport.getCodeSystem(url);
if (codeSystem != null && CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning())) {
if (codeSystem != null &&
(CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning()) ||
codeSystem.getHierarchyMeaning() == null)) {
FHIRTermServiceProvider provider = findProvider(codeSystem);
if (provider.hasConcept(codeSystem, code)) {
return provider.closure(codeSystem, code);
Expand All @@ -137,6 +147,80 @@ public Set<Concept> closure(Coding coding) {
return Collections.emptySet();
}

/**
* Generate a map containing the transitive closures for the code system concepts represented by the giving codings
*
* @param codings
* the codings
* @return
* a map of sets containing the transitive closures for the code system concepts represented by the given codings
*/
public Map<Coding, Set<Concept>> closure(Set<Coding> codings) {
Map<Coding, Set<Concept>> result = new LinkedHashMap<>();

Map<CodeSystem, Set<Code>> codeSetMap = new LinkedHashMap<>();
Map<CodeSystem, Map<Code, Coding>> codingMapMap = new LinkedHashMap<>();

for (Coding coding : codings) {
Uri system = coding.getSystem();
java.lang.String version = (coding.getVersion() != null) ? coding.getVersion().getValue() : null;
Code code = coding.getCode();

if (system == null || code == null) {
return Collections.emptyMap();
}

java.lang.String url = (version != null) ? system.getValue() + "|" + version : system.getValue();

CodeSystem codeSystem = CodeSystemSupport.getCodeSystem(url);

if (codeSystem == null ||
(!CodeSystemHierarchyMeaning.IS_A.equals(codeSystem.getHierarchyMeaning()) &&
codeSystem.getHierarchyMeaning() != null)) {
return Collections.emptyMap();
}

codeSetMap.computeIfAbsent(codeSystem, k -> new LinkedHashSet<>()).add(code);
codingMapMap.computeIfAbsent(codeSystem, k -> new LinkedHashMap<>()).put(code, coding);
}

for (CodeSystem codeSystem : codeSetMap.keySet()) {
Set<Code> codes = codeSetMap.get(codeSystem);

FHIRTermServiceProvider provider = findProvider(codeSystem);
for (Code code : codes) {
if (!provider.hasConcept(codeSystem, code)) {
return Collections.emptyMap();
}
}

Map<Code, Set<Concept>> closureMap = provider.closure(codeSystem, codes);

for (Code code : closureMap.keySet()) {
Coding coding = codingMapMap.get(codeSystem).get(code);
Set<Concept> closure = closureMap.get(code);
result.put(coding, closure);
}
}

return result;
}

/**
* Get a map of sets containing {@link CodeSystem.Concept} instances where all structural
* hierarchies have been flattened
*
* @param codeSystem
* the code system
* @param codes
* the set of roots of hierarchies containing the Concept instances to be flattened
* @return
* a map containing flattened sets of Concept instances for the given trees
*/
public Map<Code, Set<Concept>> closure(CodeSystem codeSystem, Set<Code> codes) {
return findProvider(codeSystem).closure(codeSystem, codes);
}

/**
* Expand the given value set
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
package com.ibm.fhir.term.service.provider;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand All @@ -32,6 +34,16 @@ public Set<Concept> closure(CodeSystem codeSystem, Code code) {
return CodeSystemSupport.getConcepts(concept);
}

@Override
public Map<Code, Set<Concept>> closure(CodeSystem codeSystem, Set<Code> codes) {
Map<Code, Set<Concept>> result = new LinkedHashMap<>();
for (Code code : codes) {
Set<Concept> closure = closure(codeSystem, code);
result.put(code, closure);
}
return result;
}

@Override
public Concept getConcept(CodeSystem codeSystem, Code code) {
Concept concept = CodeSystemSupport.findConcept(codeSystem, code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package com.ibm.fhir.term.spi;

import java.util.List;
import java.util.Map;
import java.util.Set;

import com.ibm.fhir.model.resource.CodeSystem;
Expand All @@ -28,6 +29,19 @@ public interface FHIRTermServiceProvider {
*/
Set<Concept> closure(CodeSystem codeSystem, Code code);

/**
* Get a map of sets containing {@link CodeSystem.Concept} instances where all structural
* hierarchies have been flattened
*
* @param codeSystem
* the code system
* @param codes
* the set of roots of hierarchies containing the Concept instances to be flattened
* @return
* a map containing flattened sets of Concept instances for the given trees
*/
Map<Code, Set<Concept>> closure(CodeSystem codeSystem, Set<Code> codes);

/**
* Get the concept in the provided code system with the specified code.
* Consumers should not expect the returned Concept to contain child concepts, even where
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -716,4 +717,40 @@ public void testHasConcept() {
Assert.assertTrue(provider.hasConcept(codeSystem, Code.of("a")));
Assert.assertFalse(provider.hasConcept(codeSystem, Code.of("zzz")));
}

@Test
public void testClosure1() {
Set<Concept> concepts = provider.closure(codeSystem, Code.of("d"));

List<String> actual = concepts.stream()
.map(concept -> concept.getCode().getValue())
.collect(Collectors.toList());

List<String> expected = Arrays.asList("d", "q", "r", "s");

Assert.assertEquals(actual, expected);
}

@Test
public void testClosure2() {
Code code1 = Code.of("c");
Code code2 = Code.of("d");

Map<Code, Set<Concept>> closureMap = provider.closure(codeSystem, new HashSet<>(Arrays.asList(code1, code2)));

List<String> actual1 = closureMap.get(code1).stream()
.map(concept -> concept.getCode().getValue())
.collect(Collectors.toList());

List<String> expected1 = Arrays.asList("c", "o", "p");

List<String> actual2 = closureMap.get(code2).stream()
.map(concept -> concept.getCode().getValue())
.collect(Collectors.toList());

List<String> expected2 = Arrays.asList("d", "q", "r", "s");

Assert.assertEquals(actual1, expected1);
Assert.assertEquals(actual2, expected2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -188,7 +190,7 @@ public void testSubsumes4() throws Exception {
}

@Test
public void testClosure() throws Exception {
public void testClosure1() throws Exception {
Coding coding = Coding.builder()
.system(Uri.of("http://ibm.com/fhir/CodeSystem/cs5"))
.version(string("1.0.0"))
Expand All @@ -204,6 +206,34 @@ public void testClosure() throws Exception {
assertEquals(actual, Arrays.asList("m", "p", "q", "r"));
}

@Test
public void testClosure2() throws Exception {
Coding coding1 = Coding.builder()
.system(Uri.of("http://ibm.com/fhir/CodeSystem/cs5"))
.version(string("1.0.0"))
.code(Code.of("m"))
.build();

Coding coding2 = Coding.builder()
.system(Uri.of("http://ibm.com/fhir/CodeSystem/cs5"))
.version(string("1.0.0"))
.code(Code.of("n"))
.build();

Map<Coding, Set<Concept>> closure = FHIRTermService.getInstance().closure(new HashSet<>(Arrays.asList(coding1, coding2)));

Set<String> actual1 = closure.get(coding1).stream()
.map(contains -> contains.getCode().getValue())
.collect(Collectors.toSet());

Set<String> actual2 = closure.get(coding2).stream()
.map(contains -> contains.getCode().getValue())
.collect(Collectors.toSet());

assertEquals(actual1, new HashSet<>(Arrays.asList("m", "p", "q", "r")));
assertEquals(actual2, new HashSet<>(Arrays.asList("n", "s")));
}

@Test
public void testValidateCode1() throws Exception {
Coding coding = Coding.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import static com.ibm.fhir.server.util.FHIROperationUtil.getOutputParameters;

import java.time.ZoneOffset;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -33,6 +32,7 @@
import com.ibm.fhir.model.type.DateTime;
import com.ibm.fhir.model.type.Uri;
import com.ibm.fhir.model.type.code.ConceptMapEquivalence;
import com.ibm.fhir.model.type.code.IssueType;
import com.ibm.fhir.model.type.code.PublicationStatus;
import com.ibm.fhir.registry.FHIRRegistry;
import com.ibm.fhir.server.operation.spi.FHIROperationContext;
Expand Down Expand Up @@ -60,15 +60,17 @@ protected Parameters doInvoke(
String name = getName(parameters);
Set<Coding> codingSet = getCodingSet(parameters);
if (codingSet.stream().anyMatch(coding -> coding.getSystem() == null || coding.getCode() == null)) {
throw new FHIROperationException("Parameter(s) named 'concept' must have both a system and a code present");
throw buildExceptionWithIssue("Parameter(s) named 'concept' must have both a system and a code present", IssueType.INVALID);
}
Map<Coding, Set<Concept>> result = new LinkedHashMap<>();
for (Coding coding : codingSet) {
Set<Concept> concepts = service.closure(coding);
Map<Coding, Set<Concept>> result = service.closure(codingSet);
if (result.isEmpty()) {
throw buildExceptionWithIssue("Closure cannot be computed for the provided input parameters", IssueType.NOT_SUPPORTED);
}
for (Coding coding : result.keySet()) {
Set<Concept> concepts = result.get(coding);
if (concepts.isEmpty()) {
throw new FHIROperationException(String.format("Closure cannot be computed for concept '%s' from system '%s'", coding.getCode().getValue(), coding.getSystem().getValue()));
throw buildExceptionWithIssue(String.format("Closure cannot be computed for concept '%s' from system '%s'", coding.getCode().getValue(), coding.getSystem().getValue()), IssueType.NOT_SUPPORTED);
}
result.put(coding, concepts);
}
ConceptMap conceptMap = buildConceptMap(name, result);
return getOutputParameters(conceptMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.ibm.fhir.model.type.CodeableConcept;
import com.ibm.fhir.model.type.Coding;
import com.ibm.fhir.model.type.Element;
import com.ibm.fhir.model.type.code.IssueType;
import com.ibm.fhir.registry.FHIRRegistry;
import com.ibm.fhir.server.operation.spi.FHIROperationContext;
import com.ibm.fhir.server.operation.spi.FHIRResourceHelpers;
Expand Down Expand Up @@ -67,15 +68,15 @@ private void validate(CodeSystem codeSystem, Element codedElement) throws FHIROp
return;
}
}
throw new FHIROperationException("CodeableConcept does not contain a coding element that matches the specified CodeSystem url and/or version");
throw buildExceptionWithIssue("CodeableConcept does not contain a coding element that matches the specified CodeSystem url and/or version", IssueType.INVALID);
}
// codedElement.is(Coding.class)
Coding coding = codedElement.as(Coding.class);
if (coding.getSystem() != null && codeSystem.getUrl() != null && !coding.getSystem().equals(codeSystem.getUrl())) {
throw new FHIROperationException("Coding system does not match the specified CodeSystem url");
throw buildExceptionWithIssue("Coding system does not match the specified CodeSystem url", IssueType.INVALID);
}
if (coding.getVersion() != null && codeSystem.getVersion() != null && !coding.getVersion().equals(codeSystem.getVersion())) {
throw new FHIROperationException("Coding version does not match the specified CodeSystem version");
throw buildExceptionWithIssue("Coding version does not match the specified CodeSystem version", IssueType.INVALID);
}
}
}
Loading

0 comments on commit f7ed8ac

Please sign in to comment.