Skip to content

Commit

Permalink
#1146: Additional fixes for Medication-related resource retrieves
Browse files Browse the repository at this point in the history
  • Loading branch information
brynrhodes committed Dec 18, 2024
1 parent b4b0701 commit 3fc38e3
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 24 deletions.
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

0 comments on commit 3fc38e3

Please sign in to comment.