Skip to content

Commit

Permalink
Initial collect-data test
Browse files Browse the repository at this point in the history
  • Loading branch information
JPercival committed Jan 31, 2022
1 parent d2c8205 commit 9e280b1
Show file tree
Hide file tree
Showing 13 changed files with 384 additions and 57 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
},
"cSpell.words": [
"DEQM",
"Eicrs",
"Gson",
"mgsc",
"numer",
"reindex"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.opencds.cqf.ruler.utility;

public class CanonicalParts {
public final class CanonicalParts {
private final String url;
private final String idPart;
private final String resourceType;
Expand Down
12 changes: 5 additions & 7 deletions core/src/main/java/org/opencds/cqf/ruler/utility/Libraries.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

import com.google.common.collect.ImmutableMap;

import org.apache.commons.lang3.tuple.Triple;
import org.cqframework.cql.elm.execution.Library;
import org.cqframework.cql.elm.execution.UsingDef;
Expand Down Expand Up @@ -78,12 +79,9 @@ public static String getVersion(IBaseResource library) {
return libraryFunctions.getVersion().apply(library);
}

private static Map<String, String> urlsByModelName = new HashMap<String, String>() {
{
put("FHIR", "http://hl7.org/fhir");
put("QDM", "urn:healthit-gov:qdm:v5_4");
}
};
private static Map<String, String> urlsByModelName = ImmutableMap.of(
"FHIR", "http://hl7.org/fhir",
"QDM", "urn:healthit-gov:qdm:v5_4");

// Returns a list of (Model, Version, Url) for the usings in library. The
// "System" using is excluded.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import org.hl7.fhir.instance.model.api.IBase;

class LibraryFunctions {
final class LibraryFunctions {

private final Function<IBase, List<IBase>> getAttachments;
private final Function<IBase, String> getContentType;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.opencds.cqf.ruler.casereporting.r4;

import static ca.uhn.fhir.model.valueset.BundleTypeEnum.COLLECTION;
import static org.hl7.fhir.r4.model.Bundle.BundleType.DOCUMENT;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -14,14 +17,14 @@
import org.hl7.fhir.r4.model.ListResource;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ResourceType;
import org.opencds.cqf.ruler.provider.DaoRegistryOperationProvider;
import org.opencds.cqf.ruler.utility.Searches;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider;
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
Expand All @@ -36,61 +39,71 @@ public class MeasureDataProcessProvider extends DaoRegistryOperationProvider {

@Operation(name = "$extract-line-list-data", idempotent = true, type = MeasureReport.class)
public Bundle extractLineListData(RequestDetails details,
@OperationParam(name = "measureReport", min = 0, max = 1, type = MeasureReport.class) MeasureReport measureReport,
@OperationParam(name = "subjectList") List<String> subjectList) {
@OperationParam(name = "measureReport", min = 0, max = 1, type = MeasureReport.class) MeasureReport measureReport,
@OperationParam(name = "subjectList") List<String> subjectList) {
IVersionSpecificBundleFactory bundleFactory = measureResourceProvider.getContext().newBundleFactory();

Map<String, Reference> populationSubjectListReferenceMap = new HashMap<String, Reference>();
Map<String, Reference> populationSubjectListReferenceMap = new HashMap<>();
gatherSubjectList(measureReport, subjectList, populationSubjectListReferenceMap);
gatherEicrs(bundleFactory, populationSubjectListReferenceMap);
Bundle bundle = (Bundle) bundleFactory.getResourceBundle();

if (bundle != null) {
if (bundle.getEntry().size() == 1 && bundle.getEntry().get(0).getResource() instanceof Bundle) {
return (Bundle) bundle.getEntry().get(0).getResource();
}
if (bundle != null && bundle.hasEntry() && bundle.getEntryFirstRep().getResource() instanceof Bundle) {
return (Bundle) bundle.getEntryFirstRep().getResource();
}

return bundle;
}

private void gatherEicrs(IVersionSpecificBundleFactory bundleFactory, Map<String, Reference> populationSubjectListReferenceMap) {
Map<String, Bundle> eicrs = new HashMap<String, Bundle>();
for (Map.Entry<String, Reference> entry : populationSubjectListReferenceMap.entrySet()) {
List<Bundle> bundles = search(Bundle.class, Searches.all()).getAllResourcesTyped();
for (Bundle bundle : bundles) {
if (bundle.hasEntry() && !bundle.getEntry().isEmpty() && bundle.hasType() && bundle.getType().equals(Bundle.BundleType.DOCUMENT)) {
IBaseResource firstEntry = bundle.getEntry().get(0).getResource();
if (!(firstEntry instanceof Composition)) {
logger.debug("Any bundle of type document must have the first entry of type Composition, but found: {}", firstEntry.fhirType());
} else {
Composition composition = (Composition) firstEntry;
String[] referenceSplit = composition.getSubject().getReference().split("/");
if (composition.getSubject().equals(entry.getValue()) || composition.getSubject().getReference().equals(entry.getKey())) {
eicrs.putIfAbsent(entry.getKey(), bundle);
} else if (referenceSplit.length > 1 && referenceSplit[1].equals(entry.getKey())) {
eicrs.putIfAbsent(entry.getKey(), bundle);
}
}
}
private void gatherEicrs(IVersionSpecificBundleFactory bundleFactory,
Map<String, Reference> populationSubjectListReferenceMap) {
Map<String, Bundle> eicrs = new HashMap<>();
List<Bundle> documentBundles = search(Bundle.class, Searches.all())
.getAllResourcesTyped().stream()
.filter(x -> x.hasEntry() && DOCUMENT.equals(x.getType())).collect(Collectors.toList());

for (Bundle bundle : documentBundles) {
IBaseResource firstResource = bundle.getEntryFirstRep().getResource();
if (!(firstResource instanceof Composition)) {
logger.debug("Any bundle of type document must have the first entry of type Composition, but found: {}",
firstResource);
continue;
}

Composition composition = (Composition) firstResource;
Reference compositionSubject = composition.getSubject();
String[] referenceSplit = compositionSubject.getReference().split("/");

for (Map.Entry<String, Reference> entry : populationSubjectListReferenceMap.entrySet()) {
if (compositionSubject.equals(entry.getValue())
|| compositionSubject.getReference().equals(entry.getKey())
|| (referenceSplit.length > 1 && referenceSplit[1].equals(entry.getKey()))) {
eicrs.putIfAbsent(entry.getKey(), bundle);
}
}
}

bundleFactory.addResourcesToBundle(eicrs.values().stream().collect(Collectors.toList()), BundleTypeEnum.COLLECTION, null, null, null);
bundleFactory.addResourcesToBundle(eicrs.values().stream().collect(Collectors.toList()), COLLECTION, null, null,
null);
}

private void gatherSubjectList(MeasureReport report, List<String> subjectList, Map<String, Reference> populationSubjectListReferenceMap) {
if (subjectList != null && !subjectList.isEmpty()) {
private void gatherSubjectList(MeasureReport report, List<String> subjectList,
Map<String, Reference> populationSubjectListReferenceMap) {
if (subjectList == null && report == null) {
throw new IllegalArgumentException("Must have either a measureReport or a subjectList or both.");
}
if (report != null) {
gatherPatientsFromReport(report, populationSubjectListReferenceMap);
}
else {
for (String subject : subjectList) {
populationSubjectListReferenceMap.putIfAbsent(subject, new Reference(subject));
}
} else if (report != null) {
gatherPatientsFromReport(report, populationSubjectListReferenceMap);
} else {
throw new RuntimeException("Must have either a measureReport or a subjectList or both.");
}
}

private void gatherPatientsFromReport(MeasureReport report, Map<String, Reference> populationSubjectListReferenceMap) {
private void gatherPatientsFromReport(MeasureReport report,
Map<String, Reference> populationSubjectListReferenceMap) {
if (report.getSubject() != null) {
populationSubjectListReferenceMap.putIfAbsent(report.getSubject().getReference(), report.getSubject());
}
Expand All @@ -101,9 +114,9 @@ private void gatherPatientsFromReport(MeasureReport report, Map<String, Referenc
populationSubjectListReferenceMap.putIfAbsent(subject.getReference(), subject);
}
if (subject.fhirType().equals("Group")) {
getPatientListFromGroup(subject.getReference()).forEach(patient -> {
populationSubjectListReferenceMap.putIfAbsent(patient.getReference(), patient);
});
getPatientListFromGroup(subject.getReference()).forEach(patient ->
populationSubjectListReferenceMap.putIfAbsent(patient.getReference(), patient)
);
}
}
}
Expand All @@ -120,32 +133,33 @@ private List<Reference> getPatientListFromGroup(String subjectGroupRef) {
} else if (reference.getReferenceElement().getResourceType().equals("Group")) {
patientList.addAll(getPatientListFromGroup(reference.getReference()));
} else {
logger.info(String.format("Group member was not a Patient or a Group, so skipping. \n%s", reference.getReference()));
logger.info("Group member was not a Patient or a Group, so skipping. \n{}",reference.getReference());
}
});
return patientList;
}

private List<Reference> getSubjectResultsFromList(Reference subjectResults) {
List<Reference> results = new ArrayList<Reference>();
List<Reference> results = new ArrayList<>();
if (subjectResults.getReference() == null) {
logger.debug("No subject results found.");
return results;
}

IBaseResource baseList = read(subjectResults.getReferenceElement());
if (baseList == null) {
logger.debug("No subject results found for: {}", subjectResults.getReference());
return results;
}

if (!(baseList instanceof ListResource)) {
throw new RuntimeException(String.format("Population subject reference was not a List, found: %s", baseList.fhirType()));
throw new IllegalArgumentException(
String.format("Population subject reference was not a List, found: %s", baseList.fhirType()));
}

ListResource list = (ListResource) baseList;
list.getEntry().forEach(entry -> {
if (entry.getItemTarget().fhirType().equals("Patient") || entry.getItemTarget().fhirType().equals("Group")) {
if (entry.getItemTarget().getResourceType() == ResourceType.Patient || entry.getItemTarget().getResourceType() == ResourceType.Group) {
results.add(entry.getItem());
}
});

return results;
}
}
15 changes: 15 additions & 0 deletions plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/CrConfig.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.opencds.cqf.ruler.cr;

import org.opencds.cqf.cql.engine.fhir.searchparam.SearchParameterResolver;
import org.opencds.cqf.ruler.cql.CqlConfig;
import org.opencds.cqf.ruler.external.annotations.OnDSTU3Condition;
import org.opencds.cqf.ruler.external.annotations.OnR4Condition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
Expand All @@ -11,6 +13,7 @@
import ca.uhn.fhir.context.FhirContext;

@Configuration
@ConditionalOnBean(CqlConfig.class)
@ConditionalOnProperty(prefix = "hapi.fhir.cr", name = "enabled", havingValue = "true", matchIfMissing = true)
public class CrConfig {
@Bean
Expand Down Expand Up @@ -82,4 +85,16 @@ public org.opencds.cqf.ruler.cr.r4.provider.SubmitDataProvider r4SubmitDataProvi
public org.opencds.cqf.ruler.cr.r4.provider.CollectDataProvider r4CollectDataProvider() {
return new org.opencds.cqf.ruler.cr.r4.provider.CollectDataProvider();
}

@Bean
@Conditional(OnDSTU3Condition.class)
public org.opencds.cqf.ruler.cr.dstu3.provider.DataOperationsProvider dstu3DataRequirementsProvider() {
return new org.opencds.cqf.ruler.cr.dstu3.provider.DataOperationsProvider();
}

@Bean
@Conditional(OnR4Condition.class)
public org.opencds.cqf.ruler.cr.r4.provider.DataOperationsProvider r4DataRequirementsProvider() {
return new org.opencds.cqf.ruler.cr.r4.provider.DataOperationsProvider();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public Parameters collectData(RequestDetails theRequestDetails, @IdParam IdType
@OperationParam(name = "lastReceivedOn") String lastReceivedOn) {

MeasureReport report = measureEvaluateProvider.evaluateMeasure(theRequestDetails, theId, periodStart, periodEnd,
null, patientRef, practitionerRef, lastReceivedOn, null);
"subject", patientRef, practitionerRef, lastReceivedOn, null);
report.setType(MeasureReport.MeasureReportType.DATACOLLECTION);
report.setGroup(null);

Expand Down
68 changes: 68 additions & 0 deletions plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/CqlBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.opencds.cqf.ruler.cr;

public class CqlBuilder {

private final StringBuilder stringBuilder;

private CqlBuilder(String fhirVersion) {
this.stringBuilder = new StringBuilder();
this.stringBuilder.append(cql(fhirVersion));
}

public static CqlBuilder newCql(String fhirVersion) {
return new CqlBuilder(fhirVersion);
}

public CqlBuilder addExpression(String name, String expression) {
this.stringBuilder.append("\n");
this.stringBuilder.append(cqlExpression(name, expression));
return this;
}

public CqlBuilder addSdeRace() {
addExpression(sdeRace(), sdeRaceExpression());
return this;
}

public String build() {
return this.stringBuilder.toString();
}

private String cql(String fhirVersion) {
return libraryHeader(fhirVersion) + measurementPeriod() + patientContext();
}

private String libraryHeader(String fhirVersion) {
return String.format(
"library Test version '1.0.0'\n\nusing FHIR version '%1$s'\ninclude FHIRHelpers version '%1$s'\n\n",
fhirVersion);
}

private String measurementPeriod() {
return "parameter \"Measurement Period\" Interval<Date> default Interval[@2019-01-01, @2019-12-31]\n\n";
}

private String patientContext() {
return "context Patient\n";
}

private String sdeRace() {
return "SDE Race";
}

private String sdeRaceExpression() {
return " (flatten (\n" +
" Patient.extension Extension\n" +
" where Extension.url = 'http://hl7.org/fhir/us/core/StructureDefinition/us-core-race'\n" +
" return Extension.extension\n" +
" )) E\n" +
" where E.url = 'ombCategory'\n" +
" or E.url = 'detailed'\n" +
" return E.value as Coding\n\n";
}

private String cqlExpression(String name, String expression) {
return "define \"" + name + "\":\n" + expression;
}

}
17 changes: 17 additions & 0 deletions plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/Libraries.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.opencds.cqf.ruler.cr.r4;

import org.hl7.fhir.r4.model.Library;

public class Libraries {

public static Library library(String cql) {
Library library = new Library();
library.setId("library-Test");
library.setName("Test");
library.setVersion("1.0.0");
library.setUrl("http://test.com/fhir/Library/Test");
library.getType().getCodingFirstRep().setCode("logic-library");
library.getContentFirstRep().setContentType("text/cql").setData(cql.getBytes());
return library;
}
}
Loading

0 comments on commit 9e280b1

Please sign in to comment.