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

#1146: Additional fixes for Medication-related resource retrieves #1478

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2919,16 +2919,19 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) {
if (hasFHIRHelpers
&& propertyType instanceof NamedType
&& ((NamedType) propertyType).getSimpleName().equals("Reference")
&& namedType.getSimpleName().equals("MedicationRequest")) {
&& (namedType.getSimpleName().equals("MedicationRequest")
|| namedType.getSimpleName().equals("MedicationAdministration")
|| namedType.getSimpleName().equals("MedicationDispense")
|| namedType.getSimpleName().equals("MedicationStatement"))) {
// TODO: This is a model-specific special case to support QICore
// This functionality needs to be generalized to a retrieve mapping in the model
// info
// But that requires a model info change (to represent references, right now the
// model info only
// includes context relationships)
// The reference expands to [MedicationRequest] MR with [Medication] M such that
// M.id =
// Last(Split(MR.medication.reference, '/')) and M.code in <valueset>
// info. But that requires a model info change (to represent references, right
// now the model info only includes context relationships)
// The reference expands to
// [MedicationRequest] MR
// with [Medication] M
// such that M.id = Last(Split(MR.medication.reference, '/'))
// and M.code in <valueset>
Retrieve mrRetrieve = buildRetrieve(
ctx, useStrictRetrieveTyping, namedType, classType, null, null, null, null, null, null);
retrieves.add(mrRetrieve);
Expand All @@ -2939,7 +2942,7 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) {
Retrieve mRetrieve = buildRetrieve(
ctx, useStrictRetrieveTyping, mNamedType, mClassType, null, null, null, null, null, null);
retrieves.add(mRetrieve);
mRetrieve.setResultType(new ListType((DataType) namedType));
mRetrieve.setResultType(new ListType(mDataType));
Query q = of.createQuery();
AliasedQuerySource aqs = of.createAliasedQuerySource()
.withExpression(mrRetrieve)
Expand Down Expand Up @@ -2968,9 +2971,27 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) {
Equal e = of.createEqual().withOperand(idProperty, last);
libraryBuilder.resolveBinaryCall("System", "Equal", e);

// Apply target mapping if this is a profile-informed model info
if (DataTypes.equal(idType, libraryBuilder.resolveTypeName("System", "String"))) {
idProperty.setPath("id.value");
}
if (DataTypes.equal(refType, libraryBuilder.resolveTypeName("System", "String"))) {
refProperty.setPath("medication.reference.value");
}

DataType mCodeType = libraryBuilder.resolvePath((DataType) mNamedType, "code");
Property mProperty = of.createProperty().withPath("code");
mProperty.setResultType(mCodeType);
Property mProperty = libraryBuilder.buildProperty("M", "code", false, mCodeType);
Expression mCodeProperty = mProperty;

// Apply target mapping if this is a profile-informed model info
if (DataTypes.equal(mCodeType, libraryBuilder.resolveTypeName("System", "Concept"))) {
FunctionRef toConcept = of.createFunctionRef()
.withLibraryName("FHIRHelpers")
.withName("ToConcept")
.withOperand(mCodeProperty);
toConcept.setResultType(mCodeType);
mCodeProperty = toConcept;
}
String mCodeComparator = "~";
if (terminology.getResultType() instanceof ListType) {
mCodeComparator = "in";
Expand All @@ -2985,9 +3006,9 @@ public Expression visitRetrieve(cqlParser.RetrieveContext ctx) {

Expression terminologyComparison = null;
if (mCodeComparator.equals("in")) {
terminologyComparison = libraryBuilder.resolveIn(mProperty, terminology);
terminologyComparison = libraryBuilder.resolveIn(mCodeProperty, terminology);
} else {
BinaryExpression equivalent = of.createEquivalent().withOperand(mProperty, terminology);
BinaryExpression equivalent = of.createEquivalent().withOperand(mCodeProperty, terminology);
libraryBuilder.resolveBinaryCall("System", "Equivalent", equivalent);
terminologyComparison = equivalent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.cqframework.cql.cql2elm.TestUtils.visitFile;
import static org.cqframework.cql.cql2elm.TestUtils.visitFileLibrary;
import static org.cqframework.cql.cql2elm.matchers.QuickDataType.quickDataType;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

Expand All @@ -16,10 +17,7 @@
import org.cqframework.cql.cql2elm.LibraryBuilder;
import org.cqframework.cql.cql2elm.TestUtils;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.hl7.cql.model.ChoiceType;
import org.hl7.cql.model.ClassType;
import org.hl7.cql.model.DataType;
import org.hl7.cql.model.NamespaceInfo;
import org.hl7.cql.model.*;
import org.hl7.elm.r1.*;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -907,4 +905,105 @@ void overloadForwardOutput() throws IOException {
String.format(
"You used a string literal: [Encounter] here that matches an identifier in scope: [Encounter]. Did you mean to use the identifier instead?")));
}

@Test
void medicationRequest() throws IOException {
CqlTranslator translator = TestUtils.runSemanticTest("fhir/r401/TestMedicationRequest.cql", 0);
Library library = translator.toELM();
Map<String, ExpressionDef> defs = new HashMap<>();

if (library.getStatements() != null) {
for (ExpressionDef def : library.getStatements().getDef()) {
defs.put(def.getName(), def);
}
}

ExpressionDef def = defs.get("Antithrombotic Therapy at Discharge");
assertThat(def, notNullValue());
assertThat(def.getExpression(), instanceOf(Query.class));
Query q = (Query) def.getExpression();
assertThat(q.getSource().size(), is(1));
assertThat(q.getSource().get(0).getExpression(), instanceOf(Retrieve.class));
Retrieve r = (Retrieve) q.getSource().get(0).getExpression();
assertThat(r.getTemplateId(), is("http://hl7.org/fhir/StructureDefinition/MedicationRequest"));
assertThat(r.getCodeProperty(), is("medication"));
assertThat(r.getCodeComparator(), is("in"));
assertThat(r.getCodes(), instanceOf(ValueSetRef.class));
ValueSetRef vsr = (ValueSetRef) r.getCodes();
assertThat(vsr.getName(), is("Antithrombotic Therapy"));

def = defs.get("Antithrombotic Therapy at Discharge (2)");
assertThat(def, notNullValue());
assertThat(def.getExpression(), instanceOf(Union.class));
Union u = (Union) def.getExpression();
assertThat(u.getOperand().size(), is(2));
assertThat(u.getOperand().get(0), instanceOf(Retrieve.class));
r = (Retrieve) u.getOperand().get(0);
assertThat(r.getTemplateId(), is("http://hl7.org/fhir/StructureDefinition/MedicationRequest"));
assertThat(r.getCodeProperty(), is("medication"));
assertThat(r.getCodeComparator(), is("in"));
assertThat(r.getCodes(), instanceOf(ValueSetRef.class));
vsr = (ValueSetRef) r.getCodes();
assertThat(vsr.getName(), is("Antithrombotic Therapy"));

assertThat(u.getOperand().get(1), instanceOf(Query.class));
q = (Query) u.getOperand().get(1);
assertThat(q.getSource().size(), is(1));
assertThat(q.getSource().get(0).getExpression(), instanceOf(Retrieve.class));
r = (Retrieve) q.getSource().get(0).getExpression();
assertThat(r.getTemplateId(), is("http://hl7.org/fhir/StructureDefinition/MedicationRequest"));
assertThat(r.getCodeProperty() == null, is(true));
assertThat(r.getCodes() == null, is(true));
assertThat(q.getRelationship(), notNullValue());
assertThat(q.getRelationship().size(), is(1));
assertThat(q.getRelationship().get(0), instanceOf(With.class));
With w = (With) q.getRelationship().get(0);
assertThat(w.getExpression(), instanceOf(Retrieve.class));
r = (Retrieve) w.getExpression();
assertThat(r.getTemplateId(), is("http://hl7.org/fhir/StructureDefinition/Medication"));
assertThat(r.getCodeProperty() == null, is(true));
assertThat(r.getCodes() == null, is(true));
assertThat(r.getResultType(), instanceOf(ListType.class));
assertThat(((ListType) r.getResultType()).getElementType(), instanceOf(ClassType.class));
assertThat(((ClassType) ((ListType) r.getResultType()).getElementType()).getName(), is("FHIR.Medication"));
assertThat(w.getSuchThat(), instanceOf(And.class));
And a = (And) w.getSuchThat();
assertThat(a.getOperand().get(0), instanceOf(Equal.class));
Equal eq = (Equal) a.getOperand().get(0);
assertThat(eq.getOperand().get(0), instanceOf(FunctionRef.class));
FunctionRef fr = (FunctionRef) eq.getOperand().get(0);
assertThat(fr.getLibraryName(), is("FHIRHelpers"));
assertThat(fr.getName(), is("ToString"));
assertThat(fr.getOperand().size(), is(1));
assertThat(fr.getOperand().get(0), instanceOf(Property.class));
Property p = (Property) fr.getOperand().get(0);
assertThat(p.getScope(), is("M"));
assertThat(p.getPath(), is("id"));
assertThat(eq.getOperand().get(1), instanceOf(Last.class));
Last l = (Last) eq.getOperand().get(1);
assertThat(l.getSource(), instanceOf(Split.class));
Split s = (Split) l.getSource();
assertThat(s.getStringToSplit(), instanceOf(FunctionRef.class));
fr = (FunctionRef) s.getStringToSplit();
assertThat(fr.getLibraryName(), is("FHIRHelpers"));
assertThat(fr.getName(), is("ToString"));
assertThat(fr.getOperand().size(), is(1));
assertThat(fr.getOperand().get(0), instanceOf(Property.class));
p = (Property) fr.getOperand().get(0);
assertThat(p.getScope(), is("MR"));
assertThat(p.getPath(), is("medication.reference"));
// assertThat(s.getSeparator(), is("/"));
assertThat(a.getOperand().get(1), instanceOf(InValueSet.class));
InValueSet ivs = (InValueSet) a.getOperand().get(1);
assertThat(ivs.getValueset().getName(), is("Antithrombotic Therapy"));
assertThat(ivs.getCode(), instanceOf(FunctionRef.class));
fr = (FunctionRef) ivs.getCode();
assertThat(fr.getLibraryName(), is("FHIRHelpers"));
assertThat(fr.getName(), is("ToConcept"));
assertThat(fr.getOperand().size(), is(1));
assertThat(fr.getOperand().get(0), instanceOf(Property.class));
p = (Property) fr.getOperand().get(0);
assertThat(p.getScope(), is("M"));
assertThat(p.getPath(), is("code"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.cql2elm.LibraryBuilder;
import org.cqframework.cql.cql2elm.TestUtils;
import org.hl7.cql.model.ClassType;
import org.hl7.cql.model.ListType;
import org.hl7.elm.r1.*;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -370,12 +372,38 @@ void medicationRequest() throws IOException {
assertThat(r.getTemplateId(), is("http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medication"));
assertThat(r.getCodeProperty() == null, is(true));
assertThat(r.getCodes() == null, is(true));
assertThat(r.getResultType(), instanceOf(ListType.class));
assertThat(((ListType) r.getResultType()).getElementType(), instanceOf(ClassType.class));
assertThat(((ClassType) ((ListType) r.getResultType()).getElementType()).getName(), is("QICore.Medication"));
assertThat(w.getSuchThat(), instanceOf(And.class));
And a = (And) w.getSuchThat();
assertThat(a.getOperand().get(0), instanceOf(Equal.class));
Equal eq = (Equal) a.getOperand().get(0);
assertThat(eq.getOperand().get(0), instanceOf(Property.class));
Property p = (Property) eq.getOperand().get(0);
assertThat(p.getScope(), is("M"));
assertThat(p.getPath(), is("id.value"));
assertThat(eq.getOperand().get(1), instanceOf(Last.class));
Last l = (Last) eq.getOperand().get(1);
assertThat(l.getSource(), instanceOf(Split.class));
Split s = (Split) l.getSource();
assertThat(s.getStringToSplit(), instanceOf(Property.class));
p = (Property) s.getStringToSplit();
assertThat(p.getScope(), is("MR"));
assertThat(p.getPath(), is("medication.reference.value"));
// assertThat(s.getSeparator(), is("/"));
assertThat(a.getOperand().get(1), instanceOf(InValueSet.class));
InValueSet ivs = (InValueSet) a.getOperand().get(1);
assertThat(ivs.getValueset().getName(), is("Antithrombotic Therapy"));
assertThat(ivs.getCode(), instanceOf(FunctionRef.class));
FunctionRef fr = (FunctionRef) ivs.getCode();
assertThat(fr.getLibraryName(), is("FHIRHelpers"));
assertThat(fr.getName(), is("ToConcept"));
assertThat(fr.getOperand().size(), is(1));
assertThat(fr.getOperand().get(0), instanceOf(Property.class));
p = (Property) fr.getOperand().get(0);
assertThat(p.getScope(), is("M"));
assertThat(p.getPath(), is("code"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
library TestMedicationRequest

using FHIR version '4.0.1'

include FHIRHelpers version '4.0.1'

valueset "Antithrombotic Therapy": 'http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1110.62'

context Patient

define "Antithrombotic Therapy at Discharge":
["MedicationRequest": medication in "Antithrombotic Therapy"] Antithrombotic

define "Antithrombotic Therapy at Discharge (2)":
["MedicationRequest": "Antithrombotic Therapy"]
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,21 @@
"valueString": "G10002"
}, {
"url": "targetProperty",
"valueString": "medication"
"valueString": "medication.reference.value"
} ]
} ],
"type": "Medication",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medication" ],
"mustSupport": [ "id" ]
"mustSupport": [ "id.value", "code" ],
"codeFilter": [ {
"path": "code",
"valueSet": "tbd"
} ]
}, {
"id": "G10002",
"type": "MedicationRequest",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ],
"mustSupport": [ "medication.reference", "status", "status.value", "intent", "intent.value", "doNotPerform", "doNotPerform.value", "dosageInstruction" ]
"mustSupport": [ "medication.reference.value", "status", "status.value", "intent", "intent.value", "doNotPerform", "doNotPerform.value", "dosageInstruction" ]
}, {
"type": "MedicationRequest",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"resourceType": "Library",
"name": "EffectiveDataRequirements",
"extension": [ {
"url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode",
"valueCoding": {
Expand Down Expand Up @@ -30,6 +29,7 @@
"display": "virtual"
}
} ],
"name": "EffectiveDataRequirements",
"status": "active",
"type": {
"coding": [ {
Expand Down Expand Up @@ -455,11 +455,19 @@
}, {
"type": "Medication",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medication" ],
"mustSupport": [ "id" ]
"mustSupport": [ "id.value", "code" ],
"codeFilter": [ {
"path": "code",
"valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1561"
} ]
}, {
"type": "Medication",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medication" ],
"mustSupport": [ "id" ]
"mustSupport": [ "id.value", "code" ],
"codeFilter": [ {
"path": "code",
"valueSet": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113883.3.526.3.1562"
} ]
}, {
"type": "MedicationRequest",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ],
Expand All @@ -471,7 +479,7 @@
}, {
"type": "MedicationRequest",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ],
"mustSupport": [ "medication.reference" ]
"mustSupport": [ "medication.reference.value" ]
}, {
"type": "MedicationRequest",
"profile": [ "http://hl7.org/fhir/us/qicore/StructureDefinition/qicore-medicationrequest" ],
Expand Down
Loading