Skip to content

Commit

Permalink
Fix #641 Multi prefix search for other codesystems
Browse files Browse the repository at this point in the history
This fixes a bug when filtering FHIR ValueSets that are not using SNOMED CT. The bug fix enables searching with multiple words or prefixes. Before the fix only searching with one word or prefix was working.
  • Loading branch information
kaicode committed Nov 25, 2024
1 parent 956d861 commit efb6939
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -943,7 +943,7 @@ private void addClause(Query queryClause, BoolQuery.Builder boolBuilder, boolean
}
}

private List<String> analyze(String text, StandardAnalyzer analyzer) {
public static List<String> analyze(String text, StandardAnalyzer analyzer) {
List<String> result = new ArrayList<>();
try {
TokenStream tokenStream = analyzer.tokenStream("contents", text);
Expand All @@ -953,12 +953,13 @@ private List<String> analyze(String text, StandardAnalyzer analyzer) {
result.add(attr.toString());
}
} catch (IOException e) {
logger.error("Failed to analyze text {}", text, e);
LoggerFactory.getLogger(DescriptionService.class)
.error("Failed to analyze text {}", text, e);
}
return result;
}

private String constructSimpleQueryString(String searchTerm) {
public static String constructSimpleQueryString(String searchTerm) {
return (searchTerm.trim().replace(" ", "* ") + "*").replace("**", "*");
}

Expand Down Expand Up @@ -1000,7 +1001,7 @@ private String constructRegexQuery(String term) {
return regexBuilder.toString();
}

private String constructSearchTerm(List<String> tokens) {
public static String constructSearchTerm(List<String> tokens) {
StringBuilder builder = new StringBuilder();
for (String token : tokens) {
builder.append(token);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.snomed.snowstorm.fhir.services;

import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.PrefixQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Operator;
import com.google.common.base.Strings;
import it.unimi.dsi.fastutil.longs.LongArrayList;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.hl7.fhir.r4.model.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand All @@ -14,6 +14,7 @@
import org.snomed.snowstorm.core.data.domain.QueryConcept;
import org.snomed.snowstorm.core.data.domain.ReferenceSetMember;
import org.snomed.snowstorm.core.data.services.ConceptService;
import org.snomed.snowstorm.core.data.services.DescriptionService;
import org.snomed.snowstorm.core.data.services.QueryService;
import org.snomed.snowstorm.core.data.services.ReferenceSetMemberService;
import org.snomed.snowstorm.core.data.services.pojo.MemberSearchRequest;
Expand All @@ -29,22 +30,24 @@
import org.snomed.snowstorm.rest.pojo.SearchAfterPageRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.client.elc.Queries;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.stereotype.Service;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import static co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders.bool;
import static io.kaicode.elasticvc.api.ComponentService.LARGE_PAGE;
import static io.kaicode.elasticvc.helper.QueryHelper.termQuery;
import static io.kaicode.elasticvc.helper.QueryHelper.termsQuery;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
import static co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders.*;
import static io.kaicode.elasticvc.helper.QueryHelper.*;
import static org.snomed.snowstorm.core.data.services.ReferenceSetMemberService.AGGREGATION_MEMBER_COUNTS_BY_REFERENCE_SET;
import static org.snomed.snowstorm.core.util.CollectionUtils.orEmpty;
import static org.snomed.snowstorm.fhir.services.FHIRHelper.*;
Expand Down Expand Up @@ -387,7 +390,10 @@ private BoolQuery.Builder getFhirConceptQuery(CodeSelectionCriteria codeSelectio
BoolQuery.Builder masterQuery = bool();
masterQuery.must(contentQuery.build()._toQuery());
if (termFilter != null) {
masterQuery.must(PrefixQuery.of(pq -> pq.field(FHIRConcept.Fields.DISPLAY).value(termFilter.toLowerCase()))._toQuery());
List<String> elasticAnalyzedWords = DescriptionService.analyze(termFilter, new StandardAnalyzer());
String searchTerm = DescriptionService.constructSearchTerm(elasticAnalyzedWords);
String query = DescriptionService.constructSimpleQueryString(searchTerm);
masterQuery.filter(Queries.queryStringQuery(FHIRConcept.Fields.DISPLAY, query, Operator.And, 2.0f)._toQuery());
}
return masterQuery;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;

public class FHIRValueSetProviderExpandGenericTest extends AbstractFHIRTest {

Expand Down Expand Up @@ -196,4 +196,54 @@ public void testExpandIncludesOtherValueSet() {
assertEquals(2, valueSet.getExpansion().getContains().size());
}

@Test
void testSearchMultipleWordsOrPrefixes() {
// "display": "additive, propagating",
System.out.println("----------");
ResponseEntity<String> response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs", HttpMethod.GET, null, String.class);
System.out.println("Search no filter");
assertExpand(response, 8, null);

System.out.println("----------");
response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=additive", HttpMethod.GET, null, String.class);
System.out.println("Search additive");
System.out.println(response.getBody());
assertExpand(response, 2, "[AP|'additive, propagating', AN|'additive, non-propagating']");

System.out.println("----------");
response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=additive propagating", HttpMethod.GET, null, String.class);
System.out.println("Search additive propagating");
System.out.println(response.getBody());
assertExpand(response, 2, "[AP|'additive, propagating', AN|'additive, non-propagating']");

System.out.println("----------");
response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=add", HttpMethod.GET, null, String.class);
System.out.println("Search add");
System.out.println(response.getBody());
assertExpand(response, 2, "[AP|'additive, propagating', AN|'additive, non-propagating']");

System.out.println("----------");
response = restTemplate.exchange(baseUrl + "/ValueSet/$expand?url=http://terminology.hl7.org/CodeSystem/v3-ContextControl?fhir_vs&filter=add prop non", HttpMethod.GET, null, String.class);
System.out.println("Search add prop non");
System.out.println(response.getBody());
assertExpand(response, 1, "[AN|'additive, non-propagating']");

System.out.println("----------");
}

private void assertExpand(ResponseEntity<String> response, int expected, String expectedCodingString) {
assertEquals(HttpStatus.OK, response.getStatusCode());
assertNotNull(response.getBody());
ValueSet valueSet = fhirJsonParser.parseResource(ValueSet.class, response.getBody());
List<ValueSet.ValueSetExpansionContainsComponent> contains = valueSet.getExpansion().getContains();
assertEquals(expected, contains.size());
if (expectedCodingString != null) {
assertEquals(expectedCodingString, toCodingsString(contains));
}
}

private String toCodingsString(List<ValueSet.ValueSetExpansionContainsComponent> contains) {
return contains.stream().map(coding -> "%s|'%s'".formatted(coding.getCode(), coding.getDisplay())).toList().toString();
}

}

0 comments on commit efb6939

Please sign in to comment.