Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Clinical Reasoning plugin #692

Merged
merged 29 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ea58766
Reproducing issues with CR/HAPI cds-hooks processing
c-schuler Mar 4, 2024
b3aa943
Add new $questionnaire operation
barhodes Feb 12, 2024
1c58e4d
Update StarterIpsConfig.java
barhodes Feb 13, 2024
fc708df
Update pom.xml
barhodes Mar 4, 2024
2ff9983
Merge remote-tracking branch 'origin/cds-hooks-issue-repro' into br-2…
JPercival Mar 7, 2024
4ecc7ca
Fixes for configuration properties
JPercival Mar 12, 2024
a7bc396
Update VS Code launch properties
JPercival Mar 12, 2024
e43bfab
Merge remote-tracking branch 'origin/master' into br-cs-merged
JPercival Mar 12, 2024
6217261
Fixes for CQL logging
JPercival Mar 15, 2024
9d7a930
Update pom.xml
barhodes Mar 20, 2024
9c4fe1d
Remove unused threaded option
barhodes Mar 20, 2024
b3a68a6
Update to 7.1.7
barhodes Mar 28, 2024
4eedff7
Fix some CR defaults
JPercival Apr 3, 2024
1de364c
Update to new snapshot
JPercival Apr 11, 2024
c1f5046
Merge branch 'rel_7_1_tracking' into br-cs-merged
barhodes May 8, 2024
69e3ad6
Update pom.xml
barhodes May 8, 2024
da03ef9
Fix test settings
barhodes May 8, 2024
a0ad815
Do not make prefetch calls if all items are present
barhodes May 14, 2024
4a40970
Add alternative application.yaml for CDS settings.
barhodes May 15, 2024
69839e6
Add CdsHook tests and documentation
barhodes May 17, 2024
3badb7d
Merge branch 'master' into br-cs-merged
barhodes Jun 3, 2024
9953703
Comment out custom prefetch until it is more mature
barhodes Jun 3, 2024
efcea5c
Removal of DataEndpoint parameter is not longer needed
barhodes Jun 3, 2024
db48699
Fix debug logging setting being overwritten
barhodes Jun 7, 2024
c34f04d
Revert change
barhodes Jun 7, 2024
bf83fa1
Remove unused class
barhodes Jun 7, 2024
87ffa01
Adding Rec10 Test
c-schuler Jun 10, 2024
6a8a0cf
Disable test until fixed
barhodes Jun 11, 2024
e5bb1dd
fix tests
barhodes Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add CdsHook tests and documentation
  • Loading branch information
barhodes committed May 17, 2024
commit 69839e61eb2502ccc573863da45e36ad42e3b716
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,11 @@ The server may be configured with subscription support by enabling properties in

## Enabling Clinical Reasoning

Set `hapi.fhir.cr_enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable [Clinical Quality Language](https://cql.hl7.org/) on this server.
Set `hapi.fhir.cr_enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable [Clinical Quality Language](https://cql.hl7.org/) on this server. An alternate settings file, [cds.application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/cds.application.yaml), exists with the Clinical Reasoning module enabled and default settings that have been found to work with most CDS and dQM test cases.

## Enabling CDS Hooks

Set `hapi.fhir.cdshooks.enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable [CDS Hooks](https://cds-hooks.org/) on this server. The Clinical Reasoning module must also be enabled because this implementation of CDS Hooks includes [CDS on FHIR](https://build.fhir.org/clinicalreasoning-cds-on-fhir.html). An example CDS Service using CDS on FHIR is available in the CdsHooksServletIT test class.

## Enabling MDM (EMPI)

Expand Down
141 changes: 141 additions & 0 deletions src/test/java/ca/uhn/fhir/jpa/starter/CdsHooksServletIT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package ca.uhn.fhir.jpa.starter;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.cr.config.RepositoryConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.searchparam.config.NicknameServiceConfig;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry;
import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson;
import ca.uhn.hapi.fhir.cdshooks.config.CdsHooksConfig;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
classes = {
Application.class,
NicknameServiceConfig.class,
RepositoryConfig.class,
CdsHooksConfig.class
}, properties = {
"spring.profiles.include=storageSettingsTest",
"spring.datasource.url=jdbc:h2:mem:dbr4",
"hapi.fhir.enable_repository_validating_interceptor=true",
"hapi.fhir.fhir_version=r4",
"hapi.fhir.cr.enabled=true",
"hapi.fhir.cr.caregaps.section_author=Organization/alphora-author",
"hapi.fhir.cr.caregaps.reporter=Organization/alphora",
"hapi.fhir.cdshooks.enabled=true",
"spring.main.allow-bean-definition-overriding=true"})
class CdsHooksServletIT implements IServerSupport {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CdsHooksServletIT.class);
private final FhirContext ourCtx = FhirContext.forR4Cached();
private final IParser ourParser = ourCtx.newJsonParser();
private IGenericClient ourClient;
private String ourCdsBase;

@Autowired
DaoRegistry myDaoRegistry;

@Autowired
ICdsServiceRegistry myCdsServiceRegistry;

@LocalServerPort
private int port;

@BeforeEach
void beforeEach() {
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http:https://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourCdsBase = "http:https://localhost:" + port + "/cds-services";
}

private JsonArray getCdsServices() throws IOException {
var response = callCdsServicesDiscovery();
String result = EntityUtils.toString(response.getEntity());
Gson gsonResponse = new Gson();
JsonObject services = gsonResponse.fromJson(result, JsonObject.class);
return !services.has("services") ? new JsonArray() : services.get("services").getAsJsonArray();
}

private CloseableHttpResponse callCdsServicesDiscovery() {
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet request = new HttpGet(ourCdsBase);
request.addHeader("Content-Type", "application/json");
return httpClient.execute(request);
} catch (IOException ioe) {
fail(ioe.getMessage());
return null;
}
}

@Test
void testGetCdsServices() {
var response = callCdsServicesDiscovery();
assertEquals(200, response.getStatusLine().getStatusCode());
}

@Test
void testCdsHooks() throws IOException, InterruptedException {
loadBundle("r4/HelloWorld-Bundle.json", ourCtx, ourClient);
await().atMost(10000, TimeUnit.MILLISECONDS).until(() -> getCdsServices().size(), equalTo(1));
var cdsRequest = "{\n" +
" \"hookInstance\": \"12345\",\n" +
" \"hook\": \"patient-view\",\n" +
" \"context\": {\n" +
" \"userId\": \"Practitioner/example\",\n" +
" \"patientId\": \"Patient/example-hello-world\"\n" +
" },\n" +
" \"prefetch\": {\n" +
" \"item1\": {\n" +
" \"resourceType\": \"Patient\",\n" +
" \"id\": \"example-hello-world\",\n" +
" \"gender\": \"male\",\n" +
" \"birthDate\": \"2000-01-01\"\n" +
" }\n" +
" }\n" +
"}";
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost request = new HttpPost(ourCdsBase + "/hello-world");
request.setEntity(new StringEntity(cdsRequest));
request.addHeader("Content-Type", "application/json");

CloseableHttpResponse httpResponse = httpClient.execute(request);
String result = EntityUtils.toString(httpResponse.getEntity());
Gson gsonResponse = new Gson();
JsonObject response = gsonResponse.fromJson(result, JsonObject.class);
assertNotNull(response);
JsonArray cards = response.getAsJsonArray("cards");
assertEquals(1, cards.size());
assertEquals("\"Hello World!\"", cards.get(0).getAsJsonObject().get("summary").toString());
} catch (IOException ioe) {
fail(ioe.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,6 @@ private int loadDataFromDirectory(String theDirectoryName) throws IOException {
return count;
}

private Bundle loadBundle(String theLocation, FhirContext theCtx, IGenericClient theClient) throws IOException {
String json = stringFromResource(theLocation);
Bundle bundle = (Bundle) theCtx.newJsonParser().parseResource(json);
Bundle result = theClient.transaction().withBundle(bundle).execute();
return result;
}

@Test
void testWebsocketSubscription() throws Exception {
/*
Expand Down
7 changes: 0 additions & 7 deletions src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,6 @@ public void testCQLEvaluateMeasureEXM130() throws IOException {
assertEquals(measureUrl + "|0.0.003", report.getMeasure());
}

private org.hl7.fhir.r4.model.Bundle loadBundle(String theLocation, FhirContext theCtx, IGenericClient theClient) throws IOException {
String json = stringFromResource(theLocation);
org.hl7.fhir.r4.model.Bundle bundle = (org.hl7.fhir.r4.model.Bundle) theCtx.newJsonParser().parseResource(json);
org.hl7.fhir.r4.model.Bundle result = theClient.transaction().withBundle(bundle).execute();
return result;
}

public Parameters runCqlExecution(Parameters parameters) {

var results = ourClient.operation().onServer()
Expand Down
8 changes: 8 additions & 0 deletions src/test/java/ca/uhn/fhir/jpa/starter/IServerSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
Expand All @@ -28,6 +30,12 @@ default IBaseResource loadResource(String theLocation, FhirContext theFhirContex
}
}

default IBaseBundle loadBundle(String theLocation, FhirContext theFhirContext, IGenericClient theClient) throws IOException {
String json = stringFromResource(theLocation);
IBaseBundle bundle = (IBaseBundle) theFhirContext.newJsonParser().parseResource(json);
return theClient.transaction().withBundle(bundle).execute();
}

default String stringFromResource(String theLocation) throws IOException {
InputStream is = null;
if (theLocation.startsWith(File.separator)) {
Expand Down
Loading
Loading