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

Refactor how Questionnaires are generated during the $apply operation #505

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d656478
Refactor Questionnaire generation during $apply operation
barhodes Aug 19, 2024
3669a03
Add evaluate processor
barhodes Aug 19, 2024
7b2f970
Fix operation processors not using the proxy repository when endpoint…
barhodes Aug 20, 2024
2c9cfbc
$extract now uses the Questionnaire item for definition based extraction
barhodes Aug 20, 2024
6e6a0d4
Handle answers of type decimal or integer with a Questionnaire Unit e…
barhodes Aug 21, 2024
a5540d8
spotless
barhodes Aug 21, 2024
e5eda2f
Add evaluate tests
barhodes Aug 21, 2024
6b2e3bf
Add support for Questionnaire Launch Context extension in $populate
barhodes Aug 27, 2024
d50db73
Add tests for InputParameterResolver
barhodes Aug 29, 2024
f5f4fb2
Add tests
barhodes Sep 3, 2024
7fc5ce2
Refactor Item generation with Case Feature Expressions
barhodes Sep 17, 2024
fb2234a
Add list of resources already packaged to prevent duplicate recursion
barhodes Sep 17, 2024
1b83a3b
Add method to construct custom library
barhodes Sep 17, 2024
8e436c3
fix merge issues
barhodes Sep 17, 2024
dfd846f
Add data requirements visitor and processor
barhodes Oct 2, 2024
dc1c363
Add tests
barhodes Oct 2, 2024
2e884a1
cleanup
barhodes Oct 2, 2024
d7f538a
cleanup
barhodes Oct 2, 2024
a3eac47
Merge branch 'master' into questionnaire-generation-refactor-2
barhodes Oct 3, 2024
3d7dee5
Add tests
barhodes Oct 3, 2024
0ffe79d
Add tests
barhodes Oct 3, 2024
1217140
Add tests
barhodes Oct 3, 2024
e29738d
Add ValueSetProcessor with $package and $data-requirements operations
barhodes Oct 3, 2024
5fef4ab
Add tests and cleanup
barhodes Oct 3, 2024
feae8b6
cleanup
barhodes Oct 3, 2024
e08273f
cleanup
barhodes Oct 4, 2024
96475c3
Strip non canonical relatedArtifacts from the module-definition retur…
barhodes Oct 4, 2024
41d3738
Merge branch 'master' into questionnaire-generation-refactor-2
barhodes Oct 4, 2024
1292dec
fix merge issue
barhodes Oct 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.fhirpath.IFhirPath;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition;
import org.opencds.cqf.fhir.utility.FhirPathCache;
Expand All @@ -28,25 +30,38 @@ public LibraryConstructor(FhirContext fhirContext) {
public String constructCqlLibrary(
String expression, List<Pair<String, String>> libraries, List<CqlParameterDefinition> parameters) {
logger.debug("Constructing expression for local evaluation");
return constructCqlLibrary(
"expression",
"1.0.0",
Arrays.asList(String.format("%ndefine \"return\":%n %s", expression)),
libraries,
parameters);
}

public String constructCqlLibrary(
String name,
String version,
List<String> expressions,
List<Pair<String, String>> libraries,
List<CqlParameterDefinition> parameters) {

StringBuilder sb = new StringBuilder();

constructHeader(sb);
constructHeader(sb, name, version);
constructUsings(sb);
constructIncludes(sb, libraries);
constructParameters(sb, parameters);
constructExpression(sb, expression);
constructContext(sb, null);
for (var expression : expressions) {
sb.append(String.format("%s%n%n", expression));
}

String cql = sb.toString();

logger.debug(cql);
return cql;
}

private void constructExpression(StringBuilder sb, String expression) {
sb.append(String.format("%ndefine \"return\":%n %s", expression));
}

private String getFhirVersionString(FhirVersionEnum fhirVersion) {
// The version of the DSTU3 enum is 3.0.2 which the CQL Engine does not support.
return fhirVersion == FhirVersionEnum.DSTU3 ? "3.0.1" : fhirVersion.getFhirVersionString();
Expand All @@ -70,6 +85,7 @@ private void constructIncludes(StringBuilder sb, List<Pair<String, String>> libr
sb.append("\n");
}
}
sb.append("\n");
}

private void constructParameters(StringBuilder sb, List<CqlParameterDefinition> parameters) {
Expand Down Expand Up @@ -98,11 +114,16 @@ private String getTypeDeclaration(String type, Boolean isList) {

private void constructUsings(StringBuilder sb) {
sb.append(String.format(
"using FHIR version '%s'%n",
"using FHIR version '%s'%n%n",
getFhirVersionString(fhirContext.getVersion().getVersion())));
}

private void constructHeader(StringBuilder sb) {
sb.append(String.format("library expression version '1.0.0'%n%n"));
private void constructHeader(StringBuilder sb, String name, String version) {
sb.append(String.format("library %s version '%s'%n%n", name, version));
}

private void constructContext(StringBuilder sb, String contextType) {
sb.append(String.format(
String.format("context %s%n%n", StringUtils.isBlank(contextType) ? "Patient" : contextType)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ public Repository getRepository() {
return repository;
}

public EvaluationSettings getSettings() {
return settings;
}

private Pair<String, Object> buildContextParameter(String patientId) {
Pair<String, Object> contextParameter = null;
if (patientId != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.opencds.cqf.fhir.cql;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.opencds.cqf.fhir.test.Resources.getResourcePath;
import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters;
import static org.opencds.cqf.fhir.utility.r4.Parameters.part;
Expand All @@ -16,9 +17,12 @@
import org.hl7.elm.r1.VersionedIdentifier;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Encounter.EncounterStatus;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Task;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -79,13 +83,35 @@ void fhirPathWithResource() {
((CodeableConcept) result.get(0)).getCodingFirstRep().getCode());
}

@Test
void fhirPathWithContextAndResource() {
var patientId = "Patient/Patient1";
var patient = new Patient().addName(new HumanName().addGiven("Alice")).setId(patientId);
var encounter = new Encounter()
.setSubject(new Reference(patient.getIdElement()))
.setStatus(EncounterStatus.FINISHED);
var params = parameters();
params.addParameter(part("%subject", patient));
params.addParameter(part("%practitioner", new Practitioner().addName(new HumanName().addGiven("Michael"))));
var expression = new CqfExpression(
"text/fhirpath",
"'Encounter: ' + %context.status + ' ' + %resource.code.coding[0].system + ' ' + %resource.code.coding[0].code",
null);

var task = new Task().setCode(new CodeableConcept(new Coding("test-system", "test-code", null)));

var result = libraryEngine.resolveExpression(patientId, expression, params, null, encounter, task);
assertNotNull(result);
assertEquals("Encounter: finished test-system test-code", ((StringType) result.get(0)).getValueAsString());
}

@Test
void expressionWithLibraryReference() {
var patientId = "Patient/Patient1";
var expression =
new CqfExpression("text/cql", "TestLibrary.testExpression", "http://fhir.test/Library/TestLibrary");
var result = libraryEngine.resolveExpression(patientId, expression, null, null, null, null);
assertEquals(((StringType) result.get(0)).getValue(), "I am a test");
assertEquals("I am a test", ((StringType) result.get(0)).getValue());
}

String libraryCql = "library MyLibrary version '1.0.0'\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public class ActivityDefinitionProcessor implements IActivityDefinitionProcessor
protected final EvaluationSettings evaluationSettings;
protected final FhirVersionEnum fhirVersion;
protected final ResourceResolver resourceResolver;
protected final IApplyProcessor applyProcessor;
protected final IRequestResolverFactory requestResolverFactory;
protected IApplyProcessor applyProcessor;
protected IRequestResolverFactory requestResolverFactory;
protected Repository repository;
protected ExtensionResolver extensionResolver;

Expand All @@ -57,12 +57,8 @@ public ActivityDefinitionProcessor(
this.resourceResolver = new ResourceResolver("ActivityDefinition", this.repository);
fhirVersion = repository.fhirContext().getVersion().getVersion();
modelResolver = FhirModelResolverCache.resolverForVersion(fhirVersion);
this.requestResolverFactory = requestResolverFactory == null
? IRequestResolverFactory.getDefault(fhirVersion)
: requestResolverFactory;
this.applyProcessor = applyProcessor != null
? applyProcessor
: new ApplyProcessor(this.repository, this.requestResolverFactory);
this.requestResolverFactory = requestResolverFactory;
this.applyProcessor = applyProcessor;
}

@Override
Expand Down Expand Up @@ -109,8 +105,8 @@ public <C extends IPrimitiveType<String>, R extends IBaseResource> IBaseResource
IBaseDatatype setting,
IBaseDatatype settingContext,
IBaseParameters parameters,
Boolean useServerData,
IBaseBundle bundle,
boolean useServerData,
IBaseBundle data,
IBaseResource dataEndpoint,
IBaseResource contentEndpoint,
IBaseResource terminologyEndpoint) {
Expand All @@ -127,7 +123,7 @@ public <C extends IPrimitiveType<String>, R extends IBaseResource> IBaseResource
settingContext,
parameters,
useServerData,
bundle,
data,
createRestRepository(repository.fhirContext(), dataEndpoint),
createRestRepository(repository.fhirContext(), contentEndpoint),
createRestRepository(repository.fhirContext(), terminologyEndpoint));
Expand All @@ -145,8 +141,8 @@ public <C extends IPrimitiveType<String>, R extends IBaseResource> IBaseResource
IBaseDatatype setting,
IBaseDatatype settingContext,
IBaseParameters parameters,
Boolean useServerData,
IBaseBundle bundle,
boolean useServerData,
IBaseBundle data,
Repository dataRepository,
Repository contentRepository,
Repository terminologyRepository) {
Expand All @@ -164,7 +160,7 @@ public <C extends IPrimitiveType<String>, R extends IBaseResource> IBaseResource
settingContext,
parameters,
useServerData,
bundle,
data,
new LibraryEngine(repository, evaluationSettings));
}

Expand All @@ -180,8 +176,8 @@ public <C extends IPrimitiveType<String>, R extends IBaseResource> IBaseResource
IBaseDatatype setting,
IBaseDatatype settingContext,
IBaseParameters parameters,
Boolean useServerData,
IBaseBundle bundle,
boolean useServerData,
IBaseBundle data,
LibraryEngine libraryEngine) {
if (StringUtils.isBlank(subjectId)) {
throw new IllegalArgumentException("Missing required parameter: 'subject'");
Expand All @@ -203,12 +199,23 @@ public <C extends IPrimitiveType<String>, R extends IBaseResource> IBaseResource
settingContext,
parameters,
useServerData,
bundle,
data,
libraryEngine,
modelResolver);
initApplyProcessor();
return applyProcessor.apply(request);
}

protected void initApplyProcessor() {
applyProcessor = applyProcessor != null
? applyProcessor
: new ApplyProcessor(
repository,
requestResolverFactory != null
? requestResolverFactory
: IRequestResolverFactory.getDefault(fhirVersion));
}

protected <C extends IPrimitiveType<String>, R extends IBaseResource> R resolveActivityDefinition(
Either3<C, IIdType, R> activityDefinition) {
return resourceResolver.resolve(activityDefinition);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.opencds.cqf.fhir.cql.LibraryEngine;
import org.opencds.cqf.fhir.cr.common.ICpgRequest;
import org.opencds.cqf.fhir.cr.inputparameters.IInputParameterResolver;
import org.opencds.cqf.fhir.utility.adapter.QuestionnaireAdapter;

public class ApplyRequest implements ICpgRequest {
private final IBaseResource activityDefinition;
Expand All @@ -27,7 +28,7 @@ public class ApplyRequest implements ICpgRequest {
private final IBaseDatatype settingContext;
private final IBaseParameters parameters;
private final Boolean useServerData;
private final IBaseBundle bundle;
private final IBaseBundle data;
private final LibraryEngine libraryEngine;
private final ModelResolver modelResolver;
private final FhirVersionEnum fhirVersion;
Expand All @@ -47,11 +48,13 @@ public ApplyRequest(
IBaseDatatype setting,
IBaseDatatype settingContext,
IBaseParameters parameters,
Boolean useServerData,
IBaseBundle bundle,
boolean useServerData,
IBaseBundle data,
LibraryEngine libraryEngine,
ModelResolver modelResolver) {
checkNotNull(activityDefinition, "expected non-null value for activityDefinition");
checkNotNull(libraryEngine, "expected non-null value for libraryEngine");
checkNotNull(modelResolver, "expected non-null value for modelResolver");
this.activityDefinition = activityDefinition;
this.subjectId = subjectId;
this.encounterId = encounterId;
Expand All @@ -64,7 +67,7 @@ public ApplyRequest(
this.settingContext = settingContext;
this.parameters = parameters;
this.useServerData = useServerData;
this.bundle = bundle;
this.data = data;
this.libraryEngine = libraryEngine;
this.modelResolver = modelResolver;
fhirVersion = activityDefinition.getStructureFhirVersionEnum();
Expand All @@ -76,7 +79,7 @@ public ApplyRequest(
this.practitionerId,
this.parameters,
this.useServerData,
this.bundle);
this.data);
}

public IBaseResource getActivityDefinition() {
Expand Down Expand Up @@ -134,12 +137,12 @@ public IBaseDatatype getSettingContext() {
}

@Override
public IBaseBundle getBundle() {
return bundle;
public IBaseBundle getData() {
return data;
}

@Override
public Boolean getUseServerData() {
public boolean getUseServerData() {
return useServerData;
}

Expand Down Expand Up @@ -210,4 +213,9 @@ protected final String resolveDefaultLibraryUrl() {
public IBaseResource getQuestionnaire() {
return null;
}

@Override
public QuestionnaireAdapter getQuestionnaireAdapter() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public ReferralRequest resolve(ICpgRequest request) {
if (request.hasPractitionerId()) {
referralRequest.setRequester(
new ReferralRequestRequesterComponent(new Reference(request.getPractitionerId())));
} else if (request.hasOrganizationId() != null) {
} else if (request.hasOrganizationId()) {
referralRequest.setRequester(
new ReferralRequestRequesterComponent(new Reference(request.getOrganizationId())));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.opencds.cqf.fhir.cr.common;

import static org.opencds.cqf.fhir.utility.Parameters.newParameters;

import ca.uhn.fhir.context.FhirVersionEnum;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
import org.opencds.cqf.fhir.cr.visitor.DataRequirementsVisitor;
import org.opencds.cqf.fhir.utility.adapter.AdapterFactory;

public class DataRequirementsProcessor implements IDataRequirementsProcessor {
protected final Repository repository;
protected final FhirVersionEnum fhirVersion;
protected final DataRequirementsVisitor dataRequirementsVisitor;

public DataRequirementsProcessor(Repository repository) {
this(repository, EvaluationSettings.getDefault());
}

public DataRequirementsProcessor(Repository repository, EvaluationSettings evaluationSettings) {
this.repository = repository;
this.fhirVersion = this.repository.fhirContext().getVersion().getVersion();
dataRequirementsVisitor = new DataRequirementsVisitor(evaluationSettings);
}

@Override
public IBaseResource getDataRequirements(IBaseResource resource, IBaseParameters parameters) {
return (IBaseResource) dataRequirementsVisitor.visit(
AdapterFactory.forFhirVersion(fhirVersion).createKnowledgeArtifactAdapter((IDomainResource) resource),
repository,
parameters == null ? newParameters(repository.fhirContext()) : parameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ protected List<IBase> getDynamicValueExpressionResult(
request.getSubjectId().getIdPart(),
cqfExpression,
request.getParameters(),
request.getBundle(),
request.getData(),
context,
resource);
}
Expand Down
Loading