Merge branch 'master' into 2566_update_s13n_and_validation_handling

This commit is contained in:
Nick Goupinets 2021-04-29 17:25:32 -04:00
commit 8cde076f1e
28 changed files with 8122 additions and 802 deletions

View File

@ -70,6 +70,9 @@ page.server_jpa_mdm.mdm_operations=MDM Operations
page.server_jpa_mdm.mdm_details=MDM Technical Details page.server_jpa_mdm.mdm_details=MDM Technical Details
page.server_jpa_mdm.mdm_expansion=MDM Search Expansion page.server_jpa_mdm.mdm_expansion=MDM Search Expansion
section.server_jpa_cql.title=JPA Server: CQL
page.server_jpa_cql.cql=CQL Getting Started
section.server_jpa_partitioning.title=JPA Server: Partitioning and Multitenancy section.server_jpa_partitioning.title=JPA Server: Partitioning and Multitenancy
page.server_jpa_partitioning.partitioning=Partitioning and Multitenancy page.server_jpa_partitioning.partitioning=Partitioning and Multitenancy
page.server_jpa_partitioning.partition_interceptor_examples=Partition Interceptor Examples page.server_jpa_partitioning.partition_interceptor_examples=Partition Interceptor Examples

View File

@ -0,0 +1,38 @@
# CQL Getting Started
## Introduction
Clinical Quality Language (CQL) is a high-level, domain-specific language focused on clinical quality and targeted at measure and decision support artifact authors. HAPI embeds a [CQL engine](https://github.com/DBCG/cql_engine) allowing the evaluation of clinical knowledge artifacts that use CQL to describe their logic.
A more detailed description of CQL is available at the [CQL Specification Implementation Guide](https://cql.hl7.org/)
The FHIR [Clinical Reasoning module](http://www.hl7.org/fhir/clinicalreasoning-module.html) defines a set of resources, profiles, operations, etc. that can be used to work with clinical knowledge within FHIR. HAPI provides implementation for some of those operations, described in more detail below.
## Working Example
A complete working example of HAPI CQL can be found in the [JPA Server Starter](/hapi-fhir/docs/server_jpa/get_started.html) project. You may wish to browse its source to see how it is set up.
## Overview
To get up and running with HAPI CQL, you can enable it using the `hapi.properties` file in the JPA Server Starter by setting `hapi.fhir.enable_cql` key to `true`. If you are running your own server follow the instructions below to [enable it in HAPI FHIR directly](#cql-settings).
Once you've enabled CQL processing, the next step is to load the appropriate knowledge artifact resources into your server.
## CQL Settings
There are two Spring beans available that add CQL processing to HAPI. You can enable CQL processing by importing the appropriate version for your server configuration.
* `ca.uhn.fhir.cql.config.CqlDstu3Config`
* `ca.uhn.fhir.cql.config.CqlR4Config`
## Operations
HAPI provides implementations for some Measure operations for DSTU3 and R4
### $evaluate-measure
The [$evaluate-measure](http://hl7.org/fhir/measure-operation-evaluate-measure.html) operation allows the evaluation of a clinical quality measure. This operation is invoked on an instance of a Measure resource:
`http://base/Measure/measureId/$evaluate-measure?subject=124&periodStart=2014-01&periodend=2014-03`
The Measure will be evaluated, including any CQL that is referenced. The CQL evaluation requires that all the supporting knowledge artifacts for a given Measure be loaded on the HAPI server, including `Libaries` and `ValueSets`.

View File

@ -437,25 +437,25 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("non-existing-code").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("non-existing-code").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = http://loinc.org#non-existing-code)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = http://loinc.org#non-existing-code)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Valid code with no system // Valid code with no system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem(null).setCode("CODE3").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem(null).setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = null#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = null#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Valid code with wrong system // Valid code with wrong system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://foo").setCode("CODE3").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem("http://foo").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = http://foo#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = http://foo#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Code that exists but isn't in the valueset // Code that exists but isn't in the valueset
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/observation-category#vital-signs)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/observation-category#vital-signs)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Invalid code in built-in VS/CS // Invalid code in built-in VS/CS
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
@ -813,7 +813,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
outcome = (OperationOutcome) e.getOperationOutcome(); outcome = (OperationOutcome) e.getOperationOutcome();
ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("None of the codes provided are in the value set http://example.com/valueset (http://example.com/valueset), and a code from this value set is required) (codes = http://example.com/foo-foo#some-code)", outcome.getIssueFirstRep().getDiagnostics()); assertEquals("None of the codings provided are in the value set http://example.com/valueset (http://example.com/valueset), and a coding from this value set is required) (codes = http://example.com/foo-foo#some-code)", outcome.getIssueFirstRep().getDiagnostics());
assertEquals(OperationOutcome.IssueSeverity.ERROR, outcome.getIssueFirstRep().getSeverity()); assertEquals(OperationOutcome.IssueSeverity.ERROR, outcome.getIssueFirstRep().getSeverity());
} }
} }
@ -872,25 +872,25 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("non-existing-code").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("non-existing-code").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = http://loinc.org#non-existing-code)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = http://loinc.org#non-existing-code)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Valid code with no system // Valid code with no system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem(null).setCode("CODE3").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem(null).setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = null#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = null#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Valid code with wrong system // Valid code with wrong system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://foo").setCode("CODE3").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem("http://foo").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = http://foo#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = http://foo#CODE3)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Code that exists but isn't in the valueset // Code that exists but isn't in the valueset
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Display 3"); obs.getCode().getCodingFirstRep().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
assertEquals("None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a code from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/observation-category#vital-signs)", oo.getIssueFirstRep().getDiagnostics(), encode(oo)); assertEquals("None of the codings provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult), and a coding from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/observation-category#vital-signs)", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Invalid code in built-in VS/CS // Invalid code in built-in VS/CS
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED); obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
@ -1028,7 +1028,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(allergy)); ourLog.info(myFhirCtx.newJsonParser().encodeResourceToString(allergy));
OperationOutcome oo = validateAndReturnOutcome(allergy); OperationOutcome oo = validateAndReturnOutcome(allergy);
assertThat(encode(oo), containsString("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/allergyintolerance-clinical")); assertThat(encode(oo), containsString("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/allergyintolerance-clinical"));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -1138,7 +1138,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
// It would be ok for this to produce 0 issues, or just an information message too // It would be ok for this to produce 0 issues, or just an information message too
assertEquals(1, OperationOutcomeUtil.getIssueCount(myFhirCtx, oo)); assertEquals(1, OperationOutcomeUtil.getIssueCount(myFhirCtx, oo));
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type), and a code should come from this value set unless it has no suitable code and the validator cannot judge what is suitable) (codes = http://foo#bar)", OperationOutcomeUtil.getFirstIssueDetails(myFhirCtx, oo)); assertEquals("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://foo#bar)", OperationOutcomeUtil.getFirstIssueDetails(myFhirCtx, oo));
} }
@ -1630,7 +1630,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome(); OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/condition-clinical")); assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/condition-clinical"));
} }
} }

View File

@ -26,7 +26,6 @@ public class ResourceProviderR5ConceptMapTest extends BaseResourceProviderR5Test
@Test @Test
public void testTranslateWithConceptMapUrlAndVersion() { public void testTranslateWithConceptMapUrlAndVersion() {
//- conceptMap1 v1 //- conceptMap1 v1
ConceptMap conceptMap1 = new ConceptMap(); ConceptMap conceptMap1 = new ConceptMap();
conceptMap1.setUrl(CM_URL).setVersion("v1").setSource(new UriType(VS_URL)).setTarget(new UriType(VS_URL_2)); conceptMap1.setUrl(CM_URL).setVersion("v1").setSource(new UriType(VS_URL)).setTarget(new UriType(VS_URL_2));
@ -63,7 +62,6 @@ public class ResourceProviderR5ConceptMapTest extends BaseResourceProviderR5Test
ourLog.info("ConceptMap: 2 \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap2)); ourLog.info("ConceptMap: 2 \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conceptMap2));
Parameters inParams = new Parameters(); Parameters inParams = new Parameters();
inParams.addParameter().setName("url").setValue(new UriType(CM_URL)); inParams.addParameter().setName("url").setValue(new UriType(CM_URL));
inParams.addParameter().setName("conceptMapVersion").setValue(new StringType("v2")); inParams.addParameter().setName("conceptMapVersion").setValue(new StringType("v2"));
@ -73,7 +71,6 @@ public class ResourceProviderR5ConceptMapTest extends BaseResourceProviderR5Test
ourLog.info("Request Parameters:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(inParams)); ourLog.info("Request Parameters:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(inParams));
Parameters respParams = myClient Parameters respParams = myClient
.operation() .operation()
.onType(ConceptMap.class) .onType(ConceptMap.class)
@ -82,8 +79,7 @@ public class ResourceProviderR5ConceptMapTest extends BaseResourceProviderR5Test
.execute(); .execute();
ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParams)); ourLog.info("Response Parameters\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(respParams));
ParametersParameterComponent param = getParameterByName(respParams, "result"); ParametersParameterComponent param = getParameterByName(respParams, "result");
assertTrue(((BooleanType) param.getValue()).booleanValue()); assertTrue(((BooleanType) param.getValue()).booleanValue());
@ -92,7 +88,8 @@ public class ResourceProviderR5ConceptMapTest extends BaseResourceProviderR5Test
assertEquals(1, getNumberOfParametersByName(respParams, "match")); assertEquals(1, getNumberOfParametersByName(respParams, "match"));
param = getParametersByName(respParams, "match").get(0); param = getParametersByName(respParams, "match").get(0);
assertEquals(2, param.getPart().size());
assertEquals(3, param.getPart().size());
ParametersParameterComponent part = getPartByName(param, "concept"); ParametersParameterComponent part = getPartByName(param, "concept");
Coding coding = (Coding) part.getValue(); Coding coding = (Coding) part.getValue();
@ -104,7 +101,9 @@ public class ResourceProviderR5ConceptMapTest extends BaseResourceProviderR5Test
part = getPartByName(param, "source"); part = getPartByName(param, "source");
assertEquals(CM_URL, ((UriType) part.getValue()).getValueAsString()); assertEquals(CM_URL, ((UriType) part.getValue()).getValueAsString());
part = getPartByName(param, "equivalence");
assertEquals("relatedto", part.getValue().toString());
} }
@Test @Test

View File

@ -22,14 +22,15 @@ package ca.uhn.fhir.cql.common.provider;
import org.cqframework.cql.cql2elm.FhirLibrarySourceProvider; import org.cqframework.cql.cql2elm.FhirLibrarySourceProvider;
import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.elm.r1.VersionedIdentifier;
import org.opencds.cqf.cql.evaluator.cql2elm.content.LibraryContentType;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.function.Function; import java.util.function.Function;
public class LibrarySourceProvider<LibraryType, AttachmentType> public class LibraryContentProvider<LibraryType, AttachmentType>
implements org.cqframework.cql.cql2elm.LibrarySourceProvider { implements org.opencds.cqf.cql.evaluator.cql2elm.content.LibraryContentProvider {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LibrarySourceProvider.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LibraryContentProvider.class);
private FhirLibrarySourceProvider innerProvider; private FhirLibrarySourceProvider innerProvider;
private LibraryResolutionProvider<LibraryType> provider; private LibraryResolutionProvider<LibraryType> provider;
@ -37,7 +38,7 @@ public class LibrarySourceProvider<LibraryType, AttachmentType>
private Function<AttachmentType, String> getContentType; private Function<AttachmentType, String> getContentType;
private Function<AttachmentType, byte[]> getContent; private Function<AttachmentType, byte[]> getContent;
public LibrarySourceProvider(LibraryResolutionProvider<LibraryType> provider, public LibraryContentProvider(LibraryResolutionProvider<LibraryType> provider,
Function<LibraryType, Iterable<AttachmentType>> getAttachments, Function<LibraryType, Iterable<AttachmentType>> getAttachments,
Function<AttachmentType, String> getContentType, Function<AttachmentType, byte[]> getContent) { Function<AttachmentType, String> getContentType, Function<AttachmentType, byte[]> getContent) {
@ -50,7 +51,13 @@ public class LibrarySourceProvider<LibraryType, AttachmentType>
} }
@Override @Override
public InputStream getLibrarySource(VersionedIdentifier versionedIdentifier) { public InputStream getLibraryContent(VersionedIdentifier versionedIdentifier, LibraryContentType libraryContentType){
// TODO: Support loading ELM
if (libraryContentType != LibraryContentType.CQL) {
return null;
}
try { try {
LibraryType lib = this.provider.resolveLibraryByName(versionedIdentifier.getId(), LibraryType lib = this.provider.resolveLibraryByName(versionedIdentifier.getId(),
versionedIdentifier.getVersion()); versionedIdentifier.getVersion());

View File

@ -27,6 +27,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.cqframework.cql.cql2elm.model.Model; import org.cqframework.cql.cql2elm.model.Model;
import org.cqframework.cql.elm.execution.Library;
import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.elm.r1.VersionedIdentifier;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -46,4 +47,9 @@ public abstract class BaseCqlConfig {
Map<VersionedIdentifier, Model> globalModelCache() { Map<VersionedIdentifier, Model> globalModelCache() {
return new ConcurrentHashMap<VersionedIdentifier, Model>(); return new ConcurrentHashMap<VersionedIdentifier, Model>();
} }
@Bean(name="globalLibraryCache")
Map<org.cqframework.cql.elm.execution.VersionedIdentifier, Library> globalLibraryCache() {
return new ConcurrentHashMap<org.cqframework.cql.elm.execution.VersionedIdentifier, Library>();
}
} }

View File

@ -26,12 +26,20 @@ import ca.uhn.fhir.cql.common.provider.EvaluationProviderFactory;
import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider; import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider;
import ca.uhn.fhir.cql.dstu3.evaluation.ProviderFactory; import ca.uhn.fhir.cql.dstu3.evaluation.ProviderFactory;
import ca.uhn.fhir.cql.dstu3.helper.LibraryHelper; import ca.uhn.fhir.cql.dstu3.helper.LibraryHelper;
import ca.uhn.fhir.cql.dstu3.listener.ElmCacheResourceChangeListener;
import ca.uhn.fhir.cql.dstu3.provider.JpaTerminologyProvider; import ca.uhn.fhir.cql.dstu3.provider.JpaTerminologyProvider;
import ca.uhn.fhir.cql.dstu3.provider.LibraryResolutionProviderImpl; import ca.uhn.fhir.cql.dstu3.provider.LibraryResolutionProviderImpl;
import ca.uhn.fhir.cql.dstu3.provider.MeasureOperationsProvider; import ca.uhn.fhir.cql.dstu3.provider.MeasureOperationsProvider;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.model.Model; import org.cqframework.cql.cql2elm.model.Model;
import org.cqframework.cql.elm.execution.Library;
import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.elm.r1.VersionedIdentifier;
import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver; import org.opencds.cqf.cql.engine.fhir.model.Dstu3FhirModelResolver;
import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.cql.engine.model.ModelResolver;
@ -60,7 +68,7 @@ public class CqlDstu3Config extends BaseCqlConfig {
@Lazy @Lazy
@Bean @Bean
LibraryResolutionProvider libraryResolutionProvider() { LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> libraryResolutionProvider() {
return new LibraryResolutionProviderImpl(); return new LibraryResolutionProviderImpl();
} }
@ -70,13 +78,28 @@ public class CqlDstu3Config extends BaseCqlConfig {
return new MeasureOperationsProvider(); return new MeasureOperationsProvider();
} }
@Lazy
@Bean @Bean
public ModelResolver fhirModelResolver() { public ModelResolver fhirModelResolver() {
return new CachingModelResolverDecorator(new Dstu3FhirModelResolver()); return new CachingModelResolverDecorator(new Dstu3FhirModelResolver());
} }
@Lazy
@Bean @Bean
public LibraryHelper libraryHelper(Map<VersionedIdentifier, Model> globalModelCache) { public LibraryHelper libraryHelper(Map<VersionedIdentifier, Model> globalModelCache, Map<org.cqframework.cql.elm.execution.VersionedIdentifier, Library> globalLibraryCache, CqlTranslatorOptions cqlTranslatorOptions) {
return new LibraryHelper(globalModelCache); return new LibraryHelper(globalModelCache, globalLibraryCache, cqlTranslatorOptions);
}
@Bean
public CqlTranslatorOptions cqlTranslatorOptions() {
return CqlTranslatorOptions.defaultOptions().withCompatibilityLevel("1.3");
}
@Bean
public ElmCacheResourceChangeListener elmCacheResourceChangeListener(IResourceChangeListenerRegistry resourceChangeListenerRegistry, IFhirResourceDao<org.hl7.fhir.dstu3.model.Library> libraryDao, Map<org.cqframework.cql.elm.execution.VersionedIdentifier, Library> globalLibraryCache) {
ElmCacheResourceChangeListener listener = new ElmCacheResourceChangeListener(libraryDao, globalLibraryCache);
resourceChangeListenerRegistry.registerResourceResourceChangeListener("Library", new SearchParameterMap(), listener, 1000);
return listener;
} }
} }

View File

@ -27,12 +27,20 @@ import ca.uhn.fhir.cql.common.provider.EvaluationProviderFactory;
import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider; import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider;
import ca.uhn.fhir.cql.r4.evaluation.ProviderFactory; import ca.uhn.fhir.cql.r4.evaluation.ProviderFactory;
import ca.uhn.fhir.cql.r4.helper.LibraryHelper; import ca.uhn.fhir.cql.r4.helper.LibraryHelper;
import ca.uhn.fhir.cql.r4.listener.ElmCacheResourceChangeListener;
import ca.uhn.fhir.cql.r4.provider.JpaTerminologyProvider; import ca.uhn.fhir.cql.r4.provider.JpaTerminologyProvider;
import ca.uhn.fhir.cql.r4.provider.LibraryResolutionProviderImpl; import ca.uhn.fhir.cql.r4.provider.LibraryResolutionProviderImpl;
import ca.uhn.fhir.cql.r4.provider.MeasureOperationsProvider; import ca.uhn.fhir.cql.r4.provider.MeasureOperationsProvider;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.cache.IResourceChangeListenerRegistry;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.model.Model; import org.cqframework.cql.cql2elm.model.Model;
import org.cqframework.cql.elm.execution.Library;
import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.elm.r1.VersionedIdentifier;
import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver;
import org.opencds.cqf.cql.engine.model.ModelResolver; import org.opencds.cqf.cql.engine.model.ModelResolver;
@ -55,19 +63,21 @@ public class CqlR4Config extends BaseCqlConfig {
@Lazy @Lazy
@Bean @Bean
TerminologyProvider terminologyProvider(ITermReadSvcR4 theITermReadSvc, DaoRegistry theDaoRegistry, IValidationSupport theValidationSupport) { TerminologyProvider terminologyProvider(ITermReadSvcR4 theITermReadSvc, DaoRegistry theDaoRegistry,
IValidationSupport theValidationSupport) {
return new JpaTerminologyProvider(theITermReadSvc, theDaoRegistry, theValidationSupport); return new JpaTerminologyProvider(theITermReadSvc, theDaoRegistry, theValidationSupport);
} }
@Lazy @Lazy
@Bean @Bean
EvaluationProviderFactory evaluationProviderFactory(FhirContext theFhirContext, DaoRegistry theDaoRegistry, TerminologyProvider theLocalSystemTerminologyProvider, ModelResolver modelResolver) { EvaluationProviderFactory evaluationProviderFactory(FhirContext theFhirContext, DaoRegistry theDaoRegistry,
TerminologyProvider theLocalSystemTerminologyProvider, ModelResolver modelResolver) {
return new ProviderFactory(theFhirContext, theDaoRegistry, theLocalSystemTerminologyProvider, modelResolver); return new ProviderFactory(theFhirContext, theDaoRegistry, theLocalSystemTerminologyProvider, modelResolver);
} }
@Lazy @Lazy
@Bean @Bean
LibraryResolutionProvider libraryResolutionProvider() { LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResolutionProvider() {
return new LibraryResolutionProviderImpl(); return new LibraryResolutionProviderImpl();
} }
@ -77,13 +87,30 @@ public class CqlR4Config extends BaseCqlConfig {
return new MeasureOperationsProvider(); return new MeasureOperationsProvider();
} }
@Lazy
@Bean @Bean
public ModelResolver fhirModelResolver() { public ModelResolver fhirModelResolver() {
return new CachingModelResolverDecorator(new R4FhirModelResolver()); return new CachingModelResolverDecorator(new R4FhirModelResolver());
} }
@Lazy
@Bean @Bean
public LibraryHelper libraryHelper(Map<VersionedIdentifier, Model> globalModelCache) { public LibraryHelper libraryHelper(Map<VersionedIdentifier, Model> globalModelCache,
return new LibraryHelper(globalModelCache); Map<org.cqframework.cql.elm.execution.VersionedIdentifier, Library> globalLibraryCache,
CqlTranslatorOptions cqlTranslatorOptions) {
return new LibraryHelper(globalModelCache, globalLibraryCache, cqlTranslatorOptions);
}
@Lazy
@Bean
public CqlTranslatorOptions cqlTranslatorOptions() {
return CqlTranslatorOptions.defaultOptions();
}
@Bean
public ElmCacheResourceChangeListener elmCacheResourceChangeListener(IResourceChangeListenerRegistry resourceChangeListenerRegistry, IFhirResourceDao<org.hl7.fhir.r4.model.Library> libraryDao, Map<org.cqframework.cql.elm.execution.VersionedIdentifier, Library> globalLibraryCache) {
ElmCacheResourceChangeListener listener = new ElmCacheResourceChangeListener(libraryDao, globalLibraryCache);
resourceChangeListenerRegistry.registerResourceResourceChangeListener("Library", new SearchParameterMap(), listener, 1000);
return listener;
} }
} }

View File

@ -39,6 +39,7 @@ import org.hl7.fhir.dstu3.model.Measure;
import org.hl7.fhir.dstu3.model.MeasureReport; import org.hl7.fhir.dstu3.model.MeasureReport;
import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Quantity;
import org.hl7.fhir.dstu3.model.Reference; import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.Resource;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
@ -677,7 +678,7 @@ public class MeasureEvaluation {
.setValue(new StringType(sdeKey)); .setValue(new StringType(sdeKey));
obsExtension.addExtension(extExtPop); obsExtension.addExtension(extExtPop);
obs.addExtension(obsExtension); obs.addExtension(obsExtension);
obs.setValue(new IntegerType(sdeAccumulatorValue)); obs.setValue(new Quantity(sdeAccumulatorValue));
if(!isSingle) { if(!isSingle) {
valueCoding.setCode(sdeAccumulatorKey); valueCoding.setCode(sdeAccumulatorKey);
obsCodeableConcept.setCoding(Collections.singletonList(valueCoding)); obsCodeableConcept.setCoding(Collections.singletonList(valueCoding));

View File

@ -20,11 +20,12 @@ package ca.uhn.fhir.cql.dstu3.helper;
* #L% * #L%
*/ */
import ca.uhn.fhir.cql.common.evaluation.LibraryLoader;
import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider; import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider;
import ca.uhn.fhir.cql.common.provider.LibrarySourceProvider;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.LibraryManager;
import ca.uhn.fhir.cql.common.provider.LibraryContentProvider;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.cql2elm.ModelManager;
import org.cqframework.cql.cql2elm.model.Model; import org.cqframework.cql.cql2elm.model.Model;
import org.cqframework.cql.elm.execution.Library; import org.cqframework.cql.elm.execution.Library;
@ -36,11 +37,13 @@ import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.RelatedArtifact; import org.hl7.fhir.dstu3.model.RelatedArtifact;
import org.hl7.fhir.dstu3.model.Resource; import org.hl7.fhir.dstu3.model.Resource;
import org.opencds.cqf.cql.evaluator.cql2elm.model.CacheAwareModelManager; import org.opencds.cqf.cql.evaluator.cql2elm.model.CacheAwareModelManager;
import org.opencds.cqf.cql.evaluator.engine.execution.PrivateCachingLibraryLoaderDecorator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.opencds.cqf.cql.evaluator.engine.execution.CacheAwareLibraryLoaderDecorator;
import org.opencds.cqf.cql.evaluator.engine.execution.TranslatingLibraryLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -48,22 +51,24 @@ public class LibraryHelper {
private static final Logger ourLog = LoggerFactory.getLogger(LibraryHelper.class); private static final Logger ourLog = LoggerFactory.getLogger(LibraryHelper.class);
private final Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache; private final Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache;
private Map<VersionedIdentifier, Library> libraryCache;
private CqlTranslatorOptions translatorOptions;
public LibraryHelper(Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache) {
public LibraryHelper(Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache, Map<VersionedIdentifier, Library> libraryCache, CqlTranslatorOptions translatorOptions) {
this.modelCache = modelCache; this.modelCache = modelCache;
this.libraryCache = libraryCache;
this.translatorOptions = translatorOptions;
} }
public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader( public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader(
LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> provider) { LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> provider) {
ModelManager modelManager = new CacheAwareModelManager(this.modelCache); ModelManager modelManager = new CacheAwareModelManager(this.modelCache);
LibraryManager libraryManager = new LibraryManager(modelManager);
libraryManager.getLibrarySourceLoader().clearProviders();
libraryManager.getLibrarySourceLoader().registerProvider( List<org.opencds.cqf.cql.evaluator.cql2elm.content.LibraryContentProvider> contentProviders = Collections.singletonList(new LibraryContentProvider<org.hl7.fhir.dstu3.model.Library, Attachment>(
new LibrarySourceProvider<org.hl7.fhir.dstu3.model.Library, Attachment>( provider, x -> x.getContent(), x -> x.getContentType(), x -> x.getData()));
provider, x -> x.getContent(), x -> x.getContentType(), x -> x.getData()));
return new PrivateCachingLibraryLoaderDecorator(new LibraryLoader(libraryManager, modelManager)); return new CacheAwareLibraryLoaderDecorator(new TranslatingLibraryLoader(modelManager, contentProviders, translatorOptions), libraryCache);
} }
public List<Library> loadLibraries(Measure measure, public List<Library> loadLibraries(Measure measure,
@ -115,11 +120,13 @@ public class LibraryHelper {
if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) {
if (artifact.getResource().getReferenceElement().getResourceType().equals("Library")) { if (artifact.getResource().getReferenceElement().getResourceType().equals("Library")) {
org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart()); org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart());
if (library != null) {
if (library != null && isLogicLibrary(library)) { if (isLogicLibrary(library)) {
libraries.add( libraries.add(libraryLoader
libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())));
); } else {
ourLog.warn("Library {} not included as part of evaluation context. Only Libraries with the 'logic-library' type are included.", library.getId());
}
} }
} }
} }

View File

@ -0,0 +1,72 @@
package ca.uhn.fhir.cql.dstu3.listener;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.cqframework.cql.elm.execution.Library;
import org.cqframework.cql.elm.execution.VersionedIdentifier;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.cache.IResourceChangeEvent;
import ca.uhn.fhir.jpa.cache.IResourceChangeListener;
public class ElmCacheResourceChangeListener implements IResourceChangeListener {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElmCacheResourceChangeListener.class);
private IFhirResourceDao<org.hl7.fhir.dstu3.model.Library> libraryDao;
private Map<VersionedIdentifier, Library> globalLibraryCache;
public ElmCacheResourceChangeListener(IFhirResourceDao<org.hl7.fhir.dstu3.model.Library> libraryDao, Map<VersionedIdentifier, Library> globalLibraryCache) {
this.libraryDao = libraryDao;
this.globalLibraryCache = globalLibraryCache;
}
@Override
public void handleInit(Collection<IIdType> theResourceIds) {
// Intentionally empty. Only cache ELM on eval request
}
@Override
public void handleChange(IResourceChangeEvent theResourceChangeEvent) {
if (theResourceChangeEvent == null) {
return;
}
this.invalidateCacheByIds(theResourceChangeEvent.getDeletedResourceIds());
this.invalidateCacheByIds(theResourceChangeEvent.getUpdatedResourceIds());
}
private void invalidateCacheByIds(List<IIdType> theIds) {
if (theIds == null) {
return;
}
for (IIdType id : theIds) {
this.invalidateCacheById(id);
}
}
private void invalidateCacheById(IIdType theId) {
if (!theId.getResourceType().equals("Library")) {
return;
}
try {
org.hl7.fhir.dstu3.model.Library library = this.libraryDao.read(theId);
this.globalLibraryCache.remove(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()));
}
// This happens when a Library is deleted entirely so it's impossible to look up name and version.
catch (Exception e) {
// TODO: This needs to be smarter... the issue is that ELM is cached with library name and version as the key since
// that's the access path the CQL engine uses, but change notifications occur with the resource Id, which is not
// necessarily tied to the resource name. In any event, if a unknown resource is deleted, clear all libraries as a workaround.
// One option is to maintain a cache with multiple indices.
ourLog.debug("Failed to locate resource {} to look up name and version. Clearing all libraries from cache.", theId.getValueAsString());
this.globalLibraryCache.clear();
}
}
}

View File

@ -40,8 +40,6 @@ import org.hl7.fhir.dstu3.model.MeasureReport;
import org.hl7.fhir.dstu3.model.StringType; import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.cql.engine.execution.LibraryLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -54,8 +52,6 @@ import org.springframework.stereotype.Component;
*/ */
@Component @Component
public class MeasureOperationsProvider { public class MeasureOperationsProvider {
private static final Logger logger = LoggerFactory.getLogger(MeasureOperationsProvider.class);
@Autowired @Autowired
private LibraryResolutionProvider<Library> libraryResolutionProvider; private LibraryResolutionProvider<Library> libraryResolutionProvider;
@Autowired @Autowired

View File

@ -20,10 +20,11 @@ package ca.uhn.fhir.cql.r4.helper;
* #L% * #L%
*/ */
import ca.uhn.fhir.cql.common.evaluation.LibraryLoader;
import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider; import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider;
import ca.uhn.fhir.cql.common.provider.LibrarySourceProvider;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.cql.common.provider.LibraryContentProvider;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.LibraryManager; import org.cqframework.cql.cql2elm.LibraryManager;
import org.cqframework.cql.cql2elm.ModelManager; import org.cqframework.cql.cql2elm.ModelManager;
import org.cqframework.cql.cql2elm.model.Model; import org.cqframework.cql.cql2elm.model.Model;
@ -33,14 +34,16 @@ import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.PlanDefinition;
import org.hl7.fhir.r4.model.RelatedArtifact; import org.hl7.fhir.r4.model.RelatedArtifact;
import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Resource;
import org.opencds.cqf.cql.evaluator.cql2elm.model.CacheAwareModelManager; import org.opencds.cqf.cql.evaluator.cql2elm.model.CacheAwareModelManager;
import org.opencds.cqf.cql.evaluator.engine.execution.PrivateCachingLibraryLoaderDecorator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.opencds.cqf.cql.evaluator.engine.execution.CacheAwareLibraryLoaderDecorator;
import org.opencds.cqf.cql.evaluator.engine.execution.TranslatingLibraryLoader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -51,34 +54,43 @@ public class LibraryHelper {
private static final Logger ourLog = LoggerFactory.getLogger(LibraryHelper.class); private static final Logger ourLog = LoggerFactory.getLogger(LibraryHelper.class);
private final Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache; private final Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache;
private Map<VersionedIdentifier, Library> libraryCache;
private CqlTranslatorOptions translatorOptions;
public LibraryHelper(Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache) { public LibraryHelper(Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache,
Map<VersionedIdentifier, Library> libraryCache, CqlTranslatorOptions translatorOptions) {
this.modelCache = modelCache; this.modelCache = modelCache;
this.libraryCache = libraryCache;
this.translatorOptions = translatorOptions;
} }
public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader(LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> provider) { public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader(
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> provider) {
ModelManager modelManager = new CacheAwareModelManager(this.modelCache); ModelManager modelManager = new CacheAwareModelManager(this.modelCache);
LibraryManager libraryManager = new LibraryManager(modelManager); LibraryManager libraryManager = new LibraryManager(modelManager);
libraryManager.getLibrarySourceLoader().clearProviders(); libraryManager.getLibrarySourceLoader().clearProviders();
List<org.opencds.cqf.cql.evaluator.cql2elm.content.LibraryContentProvider> contentProviders = Collections
.singletonList(new LibraryContentProvider<org.hl7.fhir.r4.model.Library, Attachment>(provider,
x -> x.getContent(), x -> x.getContentType(), x -> x.getData()));
libraryManager.getLibrarySourceLoader().registerProvider( return new CacheAwareLibraryLoaderDecorator(
new LibrarySourceProvider<org.hl7.fhir.r4.model.Library, Attachment>(provider, new TranslatingLibraryLoader(modelManager, contentProviders, translatorOptions), libraryCache);
x -> x.getContent(), x -> x.getContentType(), x -> x.getData()));
return new PrivateCachingLibraryLoaderDecorator(new LibraryLoader(libraryManager, modelManager));
} }
public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader(org.cqframework.cql.cql2elm.LibrarySourceProvider provider) { public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader(
org.cqframework.cql.cql2elm.LibrarySourceProvider provider) {
ModelManager modelManager = new CacheAwareModelManager(this.modelCache); ModelManager modelManager = new CacheAwareModelManager(this.modelCache);
LibraryManager libraryManager = new LibraryManager(modelManager); LibraryManager libraryManager = new LibraryManager(modelManager);
libraryManager.getLibrarySourceLoader().clearProviders(); libraryManager.getLibrarySourceLoader().clearProviders();
libraryManager.getLibrarySourceLoader().registerProvider(provider); libraryManager.getLibrarySourceLoader().registerProvider(provider);
return new PrivateCachingLibraryLoaderDecorator(new LibraryLoader(libraryManager, modelManager)); return new CacheAwareLibraryLoaderDecorator(new TranslatingLibraryLoader(modelManager, null, translatorOptions),
libraryCache);
} }
public org.hl7.fhir.r4.model.Library resolveLibraryReference(LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, String reference) { public org.hl7.fhir.r4.model.Library resolveLibraryReference(
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, String reference) {
// Raw references to Library/libraryId or libraryId // Raw references to Library/libraryId or libraryId
if (reference.startsWith("Library/") || !reference.contains("/")) { if (reference.startsWith("Library/") || !reference.contains("/")) {
return libraryResourceProvider.resolveLibraryById(reference.replace("Library/", "")); return libraryResourceProvider.resolveLibraryById(reference.replace("Library/", ""));
@ -92,30 +104,21 @@ public class LibraryHelper {
} }
public List<org.cqframework.cql.elm.execution.Library> loadLibraries(Measure measure, public List<org.cqframework.cql.elm.execution.Library> loadLibraries(Measure measure,
org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader,
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) { LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {
List<org.cqframework.cql.elm.execution.Library> libraries = new ArrayList<org.cqframework.cql.elm.execution.Library>(); List<org.cqframework.cql.elm.execution.Library> libraries = new ArrayList<org.cqframework.cql.elm.execution.Library>();
List<String> messages = new ArrayList<>();
// load libraries // load libraries
//TODO: if there's a bad measure argument, this blows up for an obscure error // TODO: if there's a bad measure argument, this blows up for an obscure error
org.hl7.fhir.r4.model.Library primaryLibrary = null; org.hl7.fhir.r4.model.Library primaryLibrary = null;
for (CanonicalType ref : measure.getLibrary()) {
List<CanonicalType> measureLibraries = measure.getLibrary();
if (measureLibraries.isEmpty()) {
String message = "No libraries found on " + measure.getId() + ". Did you perhaps load a DSTU3 Measure onto an R4 server?";
messages.add(message);
ourLog.warn(message);
}
for (CanonicalType ref : measureLibraries) {
// if library is contained in measure, load it into server // if library is contained in measure, load it into server
String id = ref.getValue(); //CanonicalHelper.getId(ref); String id = ref.getValue(); // CanonicalHelper.getId(ref);
if (id.startsWith("#")) { if (id.startsWith("#")) {
id = id.substring(1); id = id.substring(1);
for (Resource resource : measure.getContained()) { for (Resource resource : measure.getContained()) {
if (resource instanceof org.hl7.fhir.r4.model.Library if (resource instanceof org.hl7.fhir.r4.model.Library
&& resource.getIdElement().getIdPart().equals(id)) { && resource.getIdElement().getIdPart().equals(id)) {
libraryResourceProvider.update((org.hl7.fhir.r4.model.Library) resource); libraryResourceProvider.update((org.hl7.fhir.r4.model.Library) resource);
} }
} }
@ -127,34 +130,30 @@ public class LibraryHelper {
primaryLibrary = library; primaryLibrary = library;
} }
if (library != null && isLogicLibrary(library)) {
if (library != null) { libraries.add(libraryLoader
if (isLogicLibrary(library)) { .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())));
libraries.add(
libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()))
);
} else {
String message = "Skipping library " + library.getId() + " is not a logic library. Probably missing type.coding.system=\"http://terminology.hl7.org/CodeSystem/library-type\"";
messages.add(message);
ourLog.warn(message);
}
} }
} }
if (libraries.isEmpty()) { if (libraries.isEmpty()) {
throw new IllegalArgumentException(String throw new IllegalArgumentException(
.format("Could not load library source for libraries referenced in %s:\n%s", measure.getId(), StringUtils.join("\n", messages))); String.format("Could not load library source for libraries referenced in %s.", measure.getId()));
} }
for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) {
if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource()) { if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON)
&& artifact.hasResource()) {
org.hl7.fhir.r4.model.Library library = null; org.hl7.fhir.r4.model.Library library = null;
library = resolveLibraryReference(libraryResourceProvider, artifact.getResource()); library = resolveLibraryReference(libraryResourceProvider, artifact.getResource());
if (library != null && isLogicLibrary(library)) { if (library != null) {
libraries.add( if (isLogicLibrary(library)) {
libraryLoader.load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())) libraries.add(libraryLoader
); .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())));
} else {
ourLog.warn("Library {} not included as part of evaluation context. Only Libraries with the 'logic-library' type are included.", library.getId());
}
} }
} }
} }
@ -168,12 +167,13 @@ public class LibraryHelper {
} }
if (!library.hasType()) { if (!library.hasType()) {
// If no type is specified, assume it is a logic library based on whether there is a CQL content element. // If no type is specified, assume it is a logic library based on whether there
// is a CQL content element.
if (library.hasContent()) { if (library.hasContent()) {
for (Attachment a : library.getContent()) { for (Attachment a : library.getContent()) {
if (a.hasContentType() && (a.getContentType().equals("text/cql") if (a.hasContentType()
|| a.getContentType().equals("application/elm+xml") && (a.getContentType().equals("text/cql") || a.getContentType().equals("application/elm+xml")
|| a.getContentType().equals("application/elm+json"))) { || a.getContentType().equals("application/elm+json"))) {
return true; return true;
} }
} }
@ -186,8 +186,8 @@ public class LibraryHelper {
} }
for (Coding c : library.getType().getCoding()) { for (Coding c : library.getType().getCoding()) {
if (c.hasSystem() && c.getSystem().equals("http://terminology.hl7.org/CodeSystem/library-type") if (c.hasSystem() && c.getSystem().equals("http://terminology.hl7.org/CodeSystem/library-type") && c.hasCode()
&& c.hasCode() && c.getCode().equals("logic-library")) { && c.getCode().equals("logic-library")) {
return true; return true;
} }
} }
@ -195,27 +195,40 @@ public class LibraryHelper {
return false; return false;
} }
public Library resolveLibraryById(String libraryId, public Library resolveLibraryById(String libraryId, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader,
org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {
// Library library = null;
org.hl7.fhir.r4.model.Library fhirLibrary = libraryResourceProvider.resolveLibraryById(libraryId); org.hl7.fhir.r4.model.Library fhirLibrary = libraryResourceProvider.resolveLibraryById(libraryId);
return libraryLoader return libraryLoader
.load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion())); .load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion()));
} }
public Library resolvePrimaryLibrary(Measure measure, public Library resolvePrimaryLibrary(Measure measure,
org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader, org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader,
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) { LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {
// default is the first library reference // default is the first library reference
String id = CanonicalHelper.getId(measure.getLibrary().get(0)); String id = CanonicalHelper.getId(measure.getLibrary().get(0));
Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider); Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider);
if (library == null) { if (library == null) {
throw new IllegalArgumentException(String.format("Could not resolve primary library for Measure/%s.", throw new IllegalArgumentException(
measure.getIdElement().getIdPart())); String.format("Could not resolve primary library for Measure/%s.", measure.getIdElement().getIdPart()));
}
return library;
}
public Library resolvePrimaryLibrary(PlanDefinition planDefinition,
org.opencds.cqf.cql.engine.execution.LibraryLoader libraryLoader,
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {
String id = CanonicalHelper.getId(planDefinition.getLibrary().get(0));
Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider);
if (library == null) {
throw new IllegalArgumentException(String.format("Could not resolve primary library for PlanDefinition/%s",
planDefinition.getIdElement().getIdPart()));
} }
return library; return library;

View File

@ -0,0 +1,72 @@
package ca.uhn.fhir.cql.r4.listener;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.cqframework.cql.elm.execution.Library;
import org.cqframework.cql.elm.execution.VersionedIdentifier;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.cache.IResourceChangeEvent;
import ca.uhn.fhir.jpa.cache.IResourceChangeListener;
public class ElmCacheResourceChangeListener implements IResourceChangeListener {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElmCacheResourceChangeListener.class);
private IFhirResourceDao<org.hl7.fhir.r4.model.Library> libraryDao;
private Map<VersionedIdentifier, Library> globalLibraryCache;
public ElmCacheResourceChangeListener(IFhirResourceDao<org.hl7.fhir.r4.model.Library> libraryDao, Map<VersionedIdentifier, Library> globalLibraryCache) {
this.libraryDao = libraryDao;
this.globalLibraryCache = globalLibraryCache;
}
@Override
public void handleInit(Collection<IIdType> theResourceIds) {
// Intentionally empty. Only cache ELM on eval request
}
@Override
public void handleChange(IResourceChangeEvent theResourceChangeEvent) {
if (theResourceChangeEvent == null) {
return;
}
this.invalidateCacheByIds(theResourceChangeEvent.getDeletedResourceIds());
this.invalidateCacheByIds(theResourceChangeEvent.getUpdatedResourceIds());
}
private void invalidateCacheByIds(List<IIdType> theIds) {
if (theIds == null) {
return;
}
for (IIdType id : theIds) {
this.invalidateCacheById(id);
}
}
private void invalidateCacheById(IIdType theId) {
if (!theId.getResourceType().equals("Library")) {
return;
}
try {
org.hl7.fhir.r4.model.Library library = this.libraryDao.read(theId);
this.globalLibraryCache.remove(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()));
}
// This happens when a Library is deleted entirely so it's impossible to look up name and version.
catch (Exception e) {
// TODO: This needs to be smarter... the issue is that ELM is cached with library name and version as the key since
// that's the access path the CQL engine uses, but change notifications occur with the resource Id, which is not
// necessarily tied to the resource name. In any event, if a unknown resource is deleted, clear all libraries as a workaround.
// One option is to maintain a cache with multiple indices.
ourLog.debug("Failed to locate resource {} to look up name and version. Clearing all libraries from cache.", theId.getValueAsString());
this.globalLibraryCache.clear();
}
}
}

View File

@ -1,15 +1,23 @@
package ca.uhn.fhir.cql.config; package ca.uhn.fhir.cql.config;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
@Configuration @Configuration
@Import({SubscriptionSubmitterConfig.class, SubscriptionChannelConfig.class}) @Import({SubscriptionSubmitterConfig.class, SubscriptionChannelConfig.class})
public class TestCqlConfig { public class TestCqlConfig {
@Bean
public DaoConfig daoConfig() {
DaoConfig daoConfig = new DaoConfig();
daoConfig.setAllowExternalReferences(true);
daoConfig.setEnforceReferentialIntegrityOnWrite(false);
daoConfig.setEnforceReferenceTargetTypes(false);
return daoConfig;
}
} }

View File

@ -75,7 +75,7 @@ public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test {
assertNotNull("expected MeasureReport can not be null", expected); assertNotNull("expected MeasureReport can not be null", expected);
assertNotNull("actual MeasureReport can not be null", actual); assertNotNull("actual MeasureReport can not be null", actual);
String errorLocator = String.format("Measure: %s, Subject: %s", expected.getMeasure(), String errorLocator = String.format("Measure: %s, Subject: %s", expected.getMeasure().getReference(),
expected.getPatient().getReference()); expected.getPatient().getReference());
assertEquals(expected.hasGroup(), actual.hasGroup(), errorLocator); assertEquals(expected.hasGroup(), actual.hasGroup(), errorLocator);
@ -83,10 +83,10 @@ public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test {
for (MeasureReportGroupComponent mrgcExpected : expected.getGroup()) { for (MeasureReportGroupComponent mrgcExpected : expected.getGroup()) {
Optional<MeasureReportGroupComponent> mrgcActualOptional = actual.getGroup().stream() Optional<MeasureReportGroupComponent> mrgcActualOptional = actual.getGroup().stream()
.filter(x -> x.getId().equals(mrgcExpected.getId())).findFirst(); .filter(x -> x.getIdentifier() != null && x.getIdentifier().getValue().equals(mrgcExpected.getIdentifier().getValue())).findFirst();
errorLocator = String.format("Measure: %s, Subject: %s, Group: %s", expected.getMeasure(), errorLocator = String.format("Measure: %s, Subject: %s, Group: %s", expected.getMeasure().getReference(),
expected.getPatient().getReference(), mrgcExpected.getId()); expected.getPatient().getReference(), mrgcExpected.getIdentifier().getValue());
assertTrue(errorLocator, mrgcActualOptional.isPresent()); assertTrue(errorLocator, mrgcActualOptional.isPresent());
MeasureReportGroupComponent mrgcActual = mrgcActualOptional.get(); MeasureReportGroupComponent mrgcActual = mrgcActualOptional.get();
@ -94,7 +94,7 @@ public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test {
if (mrgcExpected.getMeasureScore() == null) { if (mrgcExpected.getMeasureScore() == null) {
assertNull(mrgcActual.getMeasureScore(), errorLocator); assertNull(mrgcActual.getMeasureScore(), errorLocator);
} else { } else {
assertNotNull(mrgcActual.getMeasureScore()); assertNotNull(errorLocator, mrgcActual.getMeasureScore());
BigDecimal decimalExpected = mrgcExpected.getMeasureScore(); BigDecimal decimalExpected = mrgcExpected.getMeasureScore();
BigDecimal decimalActual = mrgcActual.getMeasureScore(); BigDecimal decimalActual = mrgcActual.getMeasureScore();
@ -135,10 +135,13 @@ public class CqlMeasureEvaluationDstu3Test extends BaseCqlDstu3Test {
return new DateTimeType(date).getValueAsString(); return new DateTimeType(date).getValueAsString();
} }
// As of 2/11/2021, all the DSTU3 bundles in the Connectathon IG are out of date @Test
// and can't be posted public void test_EXM124_FHIR3_72000() throws IOException {
// @Test this.testMeasureBundle("dstu3/connectathon/EXM124-FHIR3-7.2.000-bundle.json");
// public void test_EXM117_83000() throws IOException { }
// this.testMeasureBundle("dstu3/connectathon/EXM117_FHIR3-8.3.000-bundle.json");
// } @Test
public void test_EXM104_FHIR3_81000() throws IOException {
this.testMeasureBundle("dstu3/connectathon/EXM104-FHIR3-8.1.000-bundle.json");
}
} }

View File

@ -83,7 +83,7 @@ public class CqlMeasureEvaluationR4Test extends BaseCqlR4Test {
for (MeasureReportGroupComponent mrgcExpected : expected.getGroup()) { for (MeasureReportGroupComponent mrgcExpected : expected.getGroup()) {
Optional<MeasureReportGroupComponent> mrgcActualOptional = actual.getGroup().stream() Optional<MeasureReportGroupComponent> mrgcActualOptional = actual.getGroup().stream()
.filter(x -> x.getId().equals(mrgcExpected.getId())).findFirst(); .filter(x -> x.getId() != null && x.getId().equals(mrgcExpected.getId())).findFirst();
errorLocator = String.format("Measure: %s, Subject: %s, Group: %s", expected.getMeasure(), errorLocator = String.format("Measure: %s, Subject: %s, Group: %s", expected.getMeasure(),
expected.getSubject().getReference(), mrgcExpected.getId()); expected.getSubject().getReference(), mrgcExpected.getId());

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -63,6 +63,10 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
</exclusion> </exclusion>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -596,7 +596,7 @@ public class FhirInstanceValidatorDstu3Test {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> issues = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> issues = logResultsAndReturnNonInformationalOnes(output);
assertThat(issues.toString(), containsString("None of the codes provided are in the value set http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext")); assertThat(issues.toString(), containsString("None of the codings provided are in the value set http://phr.kanta.fi/ValueSet/fiphr-vs-medicationcontext"));
} }
@Test @Test
@ -1278,7 +1278,7 @@ public class FhirInstanceValidatorDstu3Test {
assertEquals(1, all.size()); assertEquals(1, all.size());
assertEquals("Patient.identifier[0].type", all.get(0).getLocationString()); assertEquals("Patient.identifier[0].type", all.get(0).getLocationString());
assertEquals( assertEquals(
"None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type), and a code should come from this value set unless it has no suitable code and the validator cannot judge what is suitable) (codes = http://example.com/foo/bar#bar)", "None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://example.com/foo/bar#bar)",
all.get(0).getMessage()); all.get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity());

View File

@ -118,7 +118,7 @@ public class QuestionnaireValidatorDstu3Test {
ValidationResult errors = myVal.validateWithResult(q); ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString()); ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true)); assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages().get(0).getMessage(), containsString("and a code should come from this value set unless it has no suitable code and the validator cannot judge what is suitable) (codes = null#text-box)")); assertThat(errors.getMessages().get(0).getMessage(), containsString("and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = null#text-box)"));
} }
} }

View File

@ -165,7 +165,7 @@ public class ResourceValidatorDstu3Test {
ValidationResult output = val.validateWithResult(p); ValidationResult output = val.validateWithResult(p);
List<SingleValidationMessage> all = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> all = logResultsAndReturnNonInformationalOnes(output);
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/marital-status (http://hl7.org/fhir/ValueSet/marital-status), and a code should come from this value set unless it has no suitable code and the validator cannot judge what is suitable) (codes = http://hl7.org/fhir/v3/MaritalStatus#FOO)", output.getMessages().get(0).getMessage()); assertEquals("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/marital-status (http://hl7.org/fhir/ValueSet/marital-status), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://hl7.org/fhir/v3/MaritalStatus#FOO)", output.getMessages().get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, output.getMessages().get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, output.getMessages().get(0).getSeverity());
} }

View File

@ -628,7 +628,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnAll(output); List<SingleValidationMessage> errors = logResultsAndReturnAll(output);
assertEquals(1, errors.size()); assertEquals(1, errors.size());
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/report-codes (http://hl7.org/fhir/ValueSet/report-codes), and a code is recommended to come from this value set) (codes = http://loinc.org#1-8)", errors.get(0).getMessage()); assertEquals("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/report-codes (http://hl7.org/fhir/ValueSet/report-codes), and a coding is recommended to come from this value set) (codes = http://loinc.org#1-8)", errors.get(0).getMessage());
} }
@Test @Test
@ -1333,7 +1333,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
List<SingleValidationMessage> all = logResultsAndReturnAll(output); List<SingleValidationMessage> all = logResultsAndReturnAll(output);
assertEquals(1, all.size()); assertEquals(1, all.size());
assertEquals("Patient.identifier[0].type", all.get(0).getLocationString()); assertEquals("Patient.identifier[0].type", all.get(0).getLocationString());
assertThat(all.get(0).getMessage(), containsString("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type")); assertThat(all.get(0).getMessage(), containsString("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type"));
assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity());
} }

View File

@ -945,7 +945,7 @@ public class FhirInstanceValidatorR5Test {
assertEquals(1, all.size()); assertEquals(1, all.size());
assertEquals("Patient.identifier[0].type", all.get(0).getLocationString()); assertEquals("Patient.identifier[0].type", all.get(0).getLocationString());
assertEquals( assertEquals(
"None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type), and a code should come from this value set unless it has no suitable code and the validator cannot judge what is suitable) (codes = http://example.com/foo/bar#bar)", "None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type), and a coding should come from this value set unless it has no suitable code (note that the validator cannot judge what is suitable) (codes = http://example.com/foo/bar#bar)",
all.get(0).getMessage()); all.get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity());

View File

@ -142,7 +142,7 @@ public class ResourceValidatorDstu3FeatureTest {
ValidationResult output = val.validateWithResult(p); ValidationResult output = val.validateWithResult(p);
List<SingleValidationMessage> all = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> all = logResultsAndReturnNonInformationalOnes(output);
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/marital-status (http://hl7.org/fhir/ValueSet/marital-status, and a code should come from this value set unless it has no suitable code) (codes = http://hl7.org/fhir/v3/MaritalStatus#FOO)", output.getMessages().get(0).getMessage()); assertEquals("None of the codings provided are in the value set http://hl7.org/fhir/ValueSet/marital-status (http://hl7.org/fhir/ValueSet/marital-status, and a coding should come from this value set unless it has no suitable code) (codes = http://hl7.org/fhir/v3/MaritalStatus#FOO)", output.getMessages().get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, output.getMessages().get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, output.getMessages().get(0).getSeverity());
} }

View File

@ -743,7 +743,7 @@
<properties> <properties>
<fhir_core_version>5.3.0</fhir_core_version> <fhir_core_version>5.3.10</fhir_core_version>
<ucum_version>1.0.3</ucum_version> <ucum_version>1.0.3</ucum_version>
<surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args> <surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args>
@ -829,8 +829,8 @@
<elastic_apm_version>1.13.0</elastic_apm_version> <elastic_apm_version>1.13.0</elastic_apm_version>
<!-- CQL Support --> <!-- CQL Support -->
<cql-engine.version>1.5.1</cql-engine.version> <cql-engine.version>1.5.1</cql-engine.version>
<cql-evaluator.version>1.1.0</cql-evaluator.version> <cql-evaluator.version>1.2.0</cql-evaluator.version>
<cqframework.version>1.5.1</cqframework.version> <cqframework.version>1.5.2</cqframework.version>
<!-- Site properties --> <!-- Site properties -->
<fontawesomeVersion>5.4.1</fontawesomeVersion> <fontawesomeVersion>5.4.1</fontawesomeVersion>