Skip to content

Commit

Permalink
subject-list reportType bug fix (#487)
Browse files Browse the repository at this point in the history
* subject-list reportType bug fix

* remove comment

* stratum subject list prefix for correct list intersection

* remove integer resources from subjectList

* remove integer resources from subjectList

* remove boolean basis enforcement on stratum population subjects

---------

Co-authored-by: JP <[email protected]>
  • Loading branch information
Capt-Mac and JPercival authored Jul 1, 2024
1 parent 65c59bb commit e35317d
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.hl7.fhir.r4.model.Period;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.StringType;
import org.opencds.cqf.cql.engine.runtime.Code;
import org.opencds.cqf.cql.engine.runtime.Date;
Expand Down Expand Up @@ -486,7 +487,15 @@ protected void buildStratumPopulation(
return;
}

// temporarily adding prefix of resourceType to match pattern in build population
// TODO: do Stratum receive resource based subjects?

subjectIds = subjectIds.stream()
.map(t -> ResourceType.Patient.toString().concat("/").concat(t))
.collect(Collectors.toList());

Set<String> intersection = new HashSet<>(subjectIds);

intersection.retainAll(popSubjectIds);
sgpc.setCount(intersection.size());

Expand All @@ -498,6 +507,11 @@ protected void buildStratumPopulation(
}
}

protected String getPopulationResourceIds(Object resourceObject) {
var resource = (Resource) resourceObject;
return resource.getId();
}

protected void buildPopulation(
BuilderContext bc,
MeasureGroupPopulationComponent measurePopulation,
Expand All @@ -522,16 +536,27 @@ protected void buildPopulation(
addEvaluatedResourceReferences(bc, populationDef.id(), populationDef.getEvaluatedResources());

// This is a temporary list carried forward to stratifiers
Set<String> populationSet = populationDef.getSubjects();
// subjectResult set defined by basis of Measure
Set<String> populationSet;
if (bc.measureDef.isBooleanBasis()) {
populationSet = populationDef.getSubjects().stream()
.map(t -> ResourceType.Patient.toString().concat("/").concat(t))
.collect(Collectors.toSet());
} else {
populationSet = populationDef.getResources().stream()
.filter(Resource.class::isInstance)
.map(this::getPopulationResourceIds)
.collect(Collectors.toSet());
}

measurePopulation.setUserData(POPULATION_SUBJECT_SET, populationSet);

// Report Type behavior
if (Objects.requireNonNull(bc.report().getType()) == MeasureReport.MeasureReportType.SUBJECTLIST) {
if (!populationSet.isEmpty()) {
ListResource subjectList = createIdList(UUID.randomUUID().toString(), populationSet);
bc.addContained(subjectList);
reportPopulation.setSubjectResults(new Reference("#" + subjectList.getId()));
}
if (Objects.requireNonNull(bc.report().getType()) == MeasureReport.MeasureReportType.SUBJECTLIST
&& !populationSet.isEmpty()) {
ListResource subjectList = createIdList(UUID.randomUUID().toString(), populationSet);
bc.addContained(subjectList);
reportPopulation.setSubjectResults(new Reference("#" + subjectList.getId()));
}

// Population Type behavior
Expand Down Expand Up @@ -714,10 +739,6 @@ private Coding codeDefToCoding(CodeDef c) {
return cd;
}

private String createResourceReference(String resourceType, String id) {
return new StringBuilder(resourceType).append("/").append(id).toString();
}

protected Period getPeriod(Interval measurementPeriod) {
if (measurementPeriod.getStart() instanceof DateTime) {
DateTime dtStart = (DateTime) measurementPeriod.getStart();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,32 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opencds.cqf.fhir.cr.measure.common.MeasureConstants.EXT_SDE_REFERENCE_URL;
import static org.opencds.cqf.fhir.cr.measure.common.MeasureInfo.EXT_URL;
import static org.opencds.cqf.fhir.test.Resources.getResourcePath;

import ca.uhn.fhir.context.FhirContext;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.ListResource;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupComponent;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupPopulationComponent;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupStratifierComponent;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ResourceType;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.SEARCH_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE;
Expand All @@ -30,6 +37,7 @@
import org.opencds.cqf.fhir.cr.measure.common.MeasureConstants;
import org.opencds.cqf.fhir.cr.measure.r4.Measure.SelectedGroup.SelectedReference;
import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.opencds.cqf.fhir.utility.r4.ContainedHelper;
import org.opencds.cqf.fhir.utility.repository.ig.IgRepository;

public class Measure {
Expand Down Expand Up @@ -104,6 +112,7 @@ public Given repositoryFor(String repositoryPath) {
this.repository = new IgRepository(
FhirContext.forR4Cached(),
Paths.get(getResourcePath(this.getClass()) + "/" + CLASS_PATH + "/" + repositoryPath));

return this;
}

Expand Down Expand Up @@ -309,6 +318,163 @@ public SelectedReport hasReportType(String reportType) {
assertEquals(reportType, ref.getDisplay());
return this;
}

public SelectedContained contained(Selector<Resource, MeasureReport> containedSelector) {
var c = containedSelector.select(value());
return new SelectedContained(c, this);
}

public SelectedContained contained(ResourceType theResourceType, String theId) {
/*
* SelectedContained will only be useful for Observation resources
* Explanation: Contained resourceIds are randomly generated at runtime and are not searchable or known before testing.
* List resources can only be verified from population reference id, which is generated at runtime.
* There are no other unique references on List to Measure Report population, so it is not reverse searchable.
* Observation resources will leverage sde-code reference to isolate contained resources.
*/
return this.contained(t -> getContainedResources(t).stream()
.filter(x -> x.getResourceType().equals(theResourceType))
.filter(y -> y.getId().equals(theId))
.findFirst()
.orElseThrow());
}

private List<Resource> getContainedResources(MeasureReport theMeasureReport) {
return ContainedHelper.getAllContainedResources(theMeasureReport);
}

public SelectedReport containedObservationsHaveMatchingExtension() {
List<String> contained = getContainedIdsPerResourceType(ResourceType.Observation);
List<String> extIds = getExtensionIds();

// contained Observations have a matching reference
for (String s : contained) {
assertTrue(extIds.contains(s));
}
// extension references have a matching Observation
for (String extId : extIds) {
// inline resource references concat prefix '#' to indicate they are not persisted
assertTrue(contained.contains(extId.replace("#", "")));
}
return this;
}

public SelectedReport containedListHasCorrectResourceType(String theResourceType) {
var resourceType = ContainedHelper.getAllContainedResources(value()).stream()
.filter(t -> t.getResourceType().equals(ResourceType.List))
.map(x -> (ListResource) x)
.findFirst()
.orElseThrow()
.getEntryFirstRep()
.getItem()
.getReference();
assertTrue(resourceType.contains(theResourceType));
return this;
}

private List<String> getContainedIdsPerResourceType(ResourceType theResourceType) {
List<String> containedIds = new ArrayList<>();
List<Resource> resources = ContainedHelper.getAllContainedResources(value());
for (Resource resource : resources) {
if (resource.getResourceType().equals(theResourceType)) {
containedIds.add(resource.getId());
}
}
return containedIds;
}

private List<String> getExtensionIds() {
List<String> extReferences = new ArrayList<>();
List<Extension> exts = value().getExtensionsByUrl(EXT_SDE_REFERENCE_URL);
for (Extension ext : exts) {
extReferences.add(ext.getValue().toString());
}
return extReferences;
}

public SelectedReport subjectResultsValidation() {
List<String> contained = getContainedIdsPerResourceType(ResourceType.List);
List<String> subjectRefs = subjectResultReferences();
// where lists are contained resources
if (!contained.isEmpty()) {
// all contained List resources have a matching population referencing it
for (String s : contained) {
assertTrue(subjectRefs.stream().anyMatch(t -> t.contains(s)));
// validate matching counts for resource and MeasureReport population count
var listEntryCount = getListEntrySize(value(), s);
var populationCount = getPopulationCount(value(), s);
assertEquals(populationCount, listEntryCount);
}
// all referenced resources are found in contained array
for (String subjectRef : subjectRefs) {
// inline resource references concat prefix '#' to indicate they are not persisted
assertTrue(contained.contains(subjectRef.replace("#", "")));
}
}

return this;
}

private int getPopulationCount(MeasureReport theMeasureReport, String theSubjectResultId) {
return theMeasureReport.getGroup().stream()
.map(t -> t.getPopulation().stream()
.filter(MeasureReportGroupPopulationComponent::hasSubjectResults)
.filter(x -> x.getSubjectResults().getReference().contains(theSubjectResultId))
.findFirst()
.orElseThrow())
.findFirst()
.orElseThrow()
.getCount();
}

private int getListEntrySize(MeasureReport theMeasureReport, String theResourceId) {
var entry = (ListResource) ContainedHelper.getAllContainedResources(theMeasureReport).stream()
.filter(t -> t.getResourceType().equals(ResourceType.List))
.filter(x -> x.getId().equals(theResourceId))
.findAny()
.orElseThrow();
return entry.getEntry().size();
}

private List<String> subjectResultReferences() {
List<String> refs = new ArrayList<>();
// loop through all groups and populations
var groupPops = value().getGroup();
for (MeasureReportGroupComponent groupPop : groupPops) {
var pops = groupPop.getPopulation();
for (MeasureReportGroupPopulationComponent pop : pops) {
if (pop.getSubjectResults().hasReference()) {
refs.add(pop.getSubjectResults().getReference());
}
}
}
return refs;
}
}

static class SelectedContained extends Selected<Resource, SelectedReport> {

public SelectedContained(Resource value, SelectedReport parent) {
super(value, parent);
}

public SelectedContained observationHasExtensionUrl() {
var obs = (Observation) value();
assertEquals(EXT_URL, obs.getExtension().get(0).getUrl());
return this;
}

public SelectedContained observationHasCode(String theCode) {
var obs = (Observation) value();
assertEquals(theCode, obs.getCode().getCoding().get(0).getCode());
return this;
}

public SelectedContained observationCount(int theCount) {
var obs = (Observation) value();
assertEquals(theCount, obs.getValueIntegerType().getValue());
return this;
}
}

static class SelectedGroup extends Selected<MeasureReport.MeasureReportGroupComponent, SelectedReport> {
Expand Down Expand Up @@ -487,7 +653,7 @@ public SelectedStratumPopulation(
}

public SelectedStratumPopulation hasCount(int count) {
assertEquals(this.value().getCount(), count);
assertEquals(count, this.value().getCount());
return this;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ void ProportionBooleanBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Patient")
.firstGroup()
.population("initial-population")
.hasCount(1)
Expand Down Expand Up @@ -310,6 +312,8 @@ void ProportionResourceBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Encounter")
.firstGroup()
.population("initial-population")
.hasCount(2)
Expand Down Expand Up @@ -409,6 +413,8 @@ void RatioBooleanBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Patient")
.firstGroup()
.population("initial-population")
.hasCount(1)
Expand Down Expand Up @@ -530,6 +536,8 @@ void RatioResourceBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Encounter")
.firstGroup()
.population("initial-population")
.hasCount(2)
Expand Down Expand Up @@ -595,6 +603,8 @@ void CohortResourceBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Encounter")
.firstGroup()
.population("initial-population")
.hasSubjectResults()
Expand Down Expand Up @@ -644,6 +654,8 @@ void CohortBooleanBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Patient")
.firstGroup()
.population("initial-population")
.hasSubjectResults()
Expand Down Expand Up @@ -726,6 +738,8 @@ void ContinuousVariableResourceBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Encounter")
.firstGroup()
.population("initial-population")
.hasCount(2)
Expand Down Expand Up @@ -801,6 +815,8 @@ void ContinuousVariableBooleanBasisSingleGroup_SubjectList() {
when.then()
.hasReportType("Subject List")
.hasSubjectReference("Patient/female-1988")
.subjectResultsValidation()
.containedListHasCorrectResourceType("Patient")
.firstGroup()
.population("initial-population")
.hasCount(1)
Expand Down

0 comments on commit e35317d

Please sign in to comment.