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

Remove IMRs from report/measure context #734

Merged
merged 3 commits into from
Mar 22, 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
11 changes: 6 additions & 5 deletions api/src/main/java/com/lantanagroup/link/api/ReportGenerator.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.lantanagroup.link.api;

import com.lantanagroup.link.Constants;
import com.lantanagroup.link.FhirHelper;
import com.lantanagroup.link.IReportAggregator;
import com.lantanagroup.link.ReportIdHelper;
import com.lantanagroup.link.config.api.ApiConfig;
Expand Down Expand Up @@ -73,15 +74,15 @@ public void generate(QueryPhase queryPhase) throws ExecutionException, Interrupt
}
try {
MeasureReport measureReport = generate(measureServiceWrapper, patient);
synchronized (this) {
measureContext.getPatientReportsByPatientId().put(patient.getId(), measureReport);
if (queryPhase == QueryPhase.INITIAL && FhirHelper.hasNonzeroPopulationCount(measureReport)) {
measureContext.getSupplementalPatientsOfInterest().add(patient);
}
} catch (Exception e) {
logger.error("Error generating measure report for patient {}", patient.getId(), e);
} finally {
int completed = progress.incrementAndGet();
double percent = Math.round((completed * 100.0) / pois.size());
logger.info("Progress ({}%) for report {} is {} of {}", String.format("%.2f", percent), reportContext.getMasterIdentifierValue(), completed, pois.size());
double percent = (completed * 100.0) / pois.size();
logger.info("Progress ({}%) for report {} is {} of {}", String.format("%.1f", percent), reportContext.getMasterIdentifierValue(), completed, pois.size());
}
}))
.get();
Expand Down Expand Up @@ -111,7 +112,7 @@ private MeasureReport generate(MeasureServiceWrapper measureServiceWrapper, Pati
}

public void aggregate() throws ParseException {
MeasureReport masterMeasureReport = this.reportAggregator.generate(this.criteria, this.measureContext);
MeasureReport masterMeasureReport = this.reportAggregator.generate(this.tenantService, this.criteria, this.measureContext);
this.measureContext.setMeasureReport(masterMeasureReport);

Aggregate aggregateReport = new Aggregate();
Expand Down
22 changes: 17 additions & 5 deletions core/src/main/java/com/lantanagroup/link/GenericAggregator.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.lantanagroup.link;

import com.lantanagroup.link.config.api.ApiConfig;
import com.lantanagroup.link.db.TenantService;
import com.lantanagroup.link.db.model.PatientMeasureReport;
import com.lantanagroup.link.model.PatientOfInterestModel;
import com.lantanagroup.link.model.ReportContext;
import com.lantanagroup.link.model.ReportCriteria;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -11,7 +14,6 @@
import org.springframework.beans.factory.annotation.Autowired;

import java.text.ParseException;
import java.util.Collection;
import java.util.Optional;

public abstract class GenericAggregator implements IReportAggregator {
Expand All @@ -20,10 +22,12 @@ public abstract class GenericAggregator implements IReportAggregator {
@Autowired
private ApiConfig config;

protected abstract void aggregatePatientReports(MeasureReport masterMeasureReport, Collection<MeasureReport> measureReports);
protected abstract void aggregatePatientReport(MeasureReport masterMeasureReport, MeasureReport measureReport);

protected abstract void finishAggregation(MeasureReport masterMeasureReport);

@Override
public MeasureReport generate(ReportCriteria criteria, ReportContext.MeasureContext measureContext) throws ParseException {
public MeasureReport generate(TenantService tenantService, ReportCriteria criteria, ReportContext.MeasureContext measureContext) throws ParseException {
// Create the master measure report
MeasureReport masterMeasureReport = new MeasureReport();
masterMeasureReport.setId(measureContext.getReportId());
Expand All @@ -39,8 +43,16 @@ public MeasureReport generate(ReportCriteria criteria, ReportContext.MeasureCont
masterMeasureReport.setMeasure(measureContext.getMeasure().getUrl());
}

// TODO: Swap the order of aggregatePatientReports and createGroupsFromMeasure?
this.aggregatePatientReports(masterMeasureReport, measureContext.getPatientReports());
for (PatientOfInterestModel poi : measureContext.getPatientsOfInterest()) {
String pmrId = ReportIdHelper.getPatientMeasureReportId(measureContext.getReportId(), poi.getId());
PatientMeasureReport pmr = tenantService.getPatientMeasureReport(pmrId);
if (pmr == null) {
logger.warn("Patient measure report not found in database: {}", pmrId);
continue;
}
this.aggregatePatientReport(masterMeasureReport, pmr.getMeasureReport());
}
this.finishAggregation(masterMeasureReport);

this.createGroupsFromMeasure(masterMeasureReport, measureContext);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.lantanagroup.link;

import com.lantanagroup.link.db.TenantService;
import com.lantanagroup.link.model.ReportContext;
import com.lantanagroup.link.model.ReportCriteria;
import org.hl7.fhir.r4.model.MeasureReport;

import java.text.ParseException;

public interface IReportAggregator {
MeasureReport generate(ReportCriteria criteria, ReportContext.MeasureContext measureContext) throws ParseException;
MeasureReport generate(TenantService tenantService, ReportCriteria criteria, ReportContext.MeasureContext measureContext) throws ParseException;
}
57 changes: 27 additions & 30 deletions core/src/main/java/com/lantanagroup/link/model/ReportContext.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.lantanagroup.link.model;

import ca.uhn.fhir.rest.client.api.IGenericClient;
import com.lantanagroup.link.FhirHelper;
import com.lantanagroup.link.auth.LinkCredentials;
import com.lantanagroup.link.db.model.PatientList;
import com.lantanagroup.link.db.model.tenant.QueryPlan;
Expand All @@ -19,15 +18,15 @@
@Getter
@Setter
public class ReportContext {
private HttpServletRequest request;
private LinkCredentials user;
private String masterIdentifierValue;
private List<PatientList> patientLists = new ArrayList<>();
private List<PatientOfInterestModel> patientsOfInterest = new ArrayList<>();
private List<MeasureContext> measureContexts = new ArrayList<>();
private QueryPlan queryPlan;
private IGenericClient client;
private List<String> debugPatients = new ArrayList<>();
private volatile HttpServletRequest request;
private volatile LinkCredentials user;
private volatile String masterIdentifierValue;
private volatile List<PatientList> patientLists = new ArrayList<>();
private volatile List<PatientOfInterestModel> initialPatientsOfInterest = new ArrayList<>();
private volatile List<MeasureContext> measureContexts = new ArrayList<>();
private volatile QueryPlan queryPlan;
private volatile IGenericClient client;
private volatile List<String> debugPatients = new ArrayList<>();

public ReportContext() {
}
Expand All @@ -37,10 +36,14 @@ public ReportContext(HttpServletRequest request, LinkCredentials user) {
this.user = user;
}

public List<PatientOfInterestModel> getPatientsOfInterest() {
return initialPatientsOfInterest;
}

public List<PatientOfInterestModel> getPatientsOfInterest(QueryPhase queryPhase) {
switch (queryPhase) {
case INITIAL:
return patientsOfInterest;
return initialPatientsOfInterest;
case SUPPLEMENTAL:
return measureContexts.stream()
.flatMap(measureContext -> measureContext.getPatientsOfInterest(queryPhase).stream())
Expand All @@ -54,33 +57,27 @@ public List<PatientOfInterestModel> getPatientsOfInterest(QueryPhase queryPhase)
@Getter
@Setter
public static class MeasureContext {
private String bundleId;
private Bundle reportDefBundle;
private Measure measure;
private String reportId;
private List<PatientOfInterestModel> patientsOfInterest = new ArrayList<>();
private Map<String, MeasureReport> patientReportsByPatientId = new HashMap<>();
private MeasureReport measureReport;
private volatile String bundleId;
private volatile Bundle reportDefBundle;
private volatile Measure measure;
private volatile String reportId;
private volatile List<PatientOfInterestModel> initialPatientsOfInterest = new ArrayList<>();
private volatile List<PatientOfInterestModel> supplementalPatientsOfInterest = Collections.synchronizedList(new ArrayList<>());
private volatile MeasureReport measureReport;

public List<PatientOfInterestModel> getPatientsOfInterest() {
return initialPatientsOfInterest;
}

public List<PatientOfInterestModel> getPatientsOfInterest(QueryPhase queryPhase) {
switch (queryPhase) {
case INITIAL:
return patientsOfInterest;
return initialPatientsOfInterest;
case SUPPLEMENTAL:
Set<String> patientIds = patientReportsByPatientId.entrySet().stream()
.filter(reportById -> FhirHelper.hasNonzeroPopulationCount(reportById.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
return patientsOfInterest.stream()
.filter(poi -> patientIds.contains(poi.getId()))
.collect(Collectors.toList());
return supplementalPatientsOfInterest;
default:
throw new IllegalArgumentException(queryPhase.toString());
}
}

public Collection<MeasureReport> getPatientReports() {
return patientReportsByPatientId.values();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public void testExecute() {
model.setId("10000");
patientOfInterestModel.add(model);

context.setPatientsOfInterest(patientOfInterestModel);
context.setInitialPatientsOfInterest(patientOfInterestModel);

List<Coding> codes = new ArrayList<>();
codes.add(getLocation().getType().get(0).getCoding().get(0));
Expand Down
29 changes: 14 additions & 15 deletions nhsn/src/main/java/com/lantanagroup/link/nhsn/ReportAggregator.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Optional;

@Component
Expand Down Expand Up @@ -38,23 +37,23 @@ protected void addSubjectResult(
"MeasureReport/" + individualMeasureReport.getIdElement().getIdPart());
}

public void aggregatePatientReports(MeasureReport masterMeasureReport, Collection<MeasureReport> measureReports) {
// aggregate all individual reports in ones
for (MeasureReport patientMeasureReportResource : measureReports) {
for (MeasureReport.MeasureReportGroupComponent group : patientMeasureReportResource.getGroup()) {
for (MeasureReport.MeasureReportGroupPopulationComponent population : group.getPopulation()) {
// Check if group and population code exist in master, if not create
MeasureReport.MeasureReportGroupPopulationComponent measureGroupPopulation = getOrCreateGroupAndPopulation(masterMeasureReport, population, group);
// Add population.count to the master group/population count
measureGroupPopulation.setCount(measureGroupPopulation.getCount() + population.getCount());
// If this population incremented the master
if (population.getCount() > 0) {
// add subject results
addSubjectResult(patientMeasureReportResource, measureGroupPopulation);
}
public void aggregatePatientReport(MeasureReport masterMeasureReport, MeasureReport measureReport) {
for (MeasureReport.MeasureReportGroupComponent group : measureReport.getGroup()) {
for (MeasureReport.MeasureReportGroupPopulationComponent population : group.getPopulation()) {
// Check if group and population code exist in master, if not create
MeasureReport.MeasureReportGroupPopulationComponent measureGroupPopulation = getOrCreateGroupAndPopulation(masterMeasureReport, population, group);
// Add population.count to the master group/population count
measureGroupPopulation.setCount(measureGroupPopulation.getCount() + population.getCount());
// If this population incremented the master
if (population.getCount() > 0) {
// add subject results
addSubjectResult(measureReport, measureGroupPopulation);
}
}
}
}

public void finishAggregation(MeasureReport masterMeasureReport) {
for (MeasureReport.MeasureReportGroupComponent group : masterMeasureReport.getGroup()) {
for (MeasureReport.MeasureReportGroupPopulationComponent population : group.getPopulation()) {
logger.debug(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ public void loadPatientsOfInterest(TenantService tenantService, ReportCriteria c
Collector<PatientOfInterestModel, ?, Map<String, PatientOfInterestModel>> deduplicator =
Collectors.toMap(PatientOfInterestModel::toString, Function.identity(), (poi1, poi2) -> poi1);
Map<String, PatientOfInterestModel> poiMap = context.getPatientsOfInterest().stream().collect(deduplicator);
context.setPatientsOfInterest(new ArrayList<>(poiMap.values()));
context.setInitialPatientsOfInterest(new ArrayList<>(poiMap.values()));
for (ReportContext.MeasureContext measureContext : context.getMeasureContexts()) {
measureContext.setPatientsOfInterest(measureContext.getPatientsOfInterest().stream()
measureContext.setInitialPatientsOfInterest(measureContext.getPatientsOfInterest().stream()
.collect(deduplicator)
.values().stream()
.map(poi -> poiMap.get(poi.toString()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void execute() throws ParseException {
patientOfInterest.setId(patientID);
List<PatientOfInterestModel> patientsOfInterest = new ArrayList<>();
patientsOfInterest.add(patientOfInterest);
context.setPatientsOfInterest(patientsOfInterest);
context.setInitialPatientsOfInterest(patientsOfInterest);
context.setMasterIdentifierValue(reportID);
String bundleId = ReportIdHelper.getPatientDataBundleId(context.getMasterIdentifierValue(), patientOfInterest.getId());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.lantanagroup.link.nhsn;

import com.lantanagroup.link.ReportIdHelper;
import com.lantanagroup.link.db.TenantService;
import com.lantanagroup.link.db.model.PatientMeasureReport;
import com.lantanagroup.link.model.PatientOfInterestModel;
import com.lantanagroup.link.model.ReportContext;
import com.lantanagroup.link.model.ReportCriteria;
import org.hl7.fhir.r4.model.*;
Expand All @@ -9,6 +13,9 @@

import java.text.ParseException;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ReportAggregatorTests {
private Bundle getReportDefBundle() {
Bundle measureDefBundle = new Bundle();
Expand Down Expand Up @@ -36,12 +43,21 @@ private ReportContext.MeasureContext getMeasureContext(String version) {
context.getMeasure().setUrl("http://test.com/fhir/Measure/SomeMeasure");
context.getMeasure().setVersion(version);
context.setReportDefBundle(this.getReportDefBundle());
context.getPatientReportsByPatientId().put("test-patient1", this.getPatientMeasureReport("test-patient1", 1));
context.getPatientReportsByPatientId().put("test-patient2", this.getPatientMeasureReport("test-patient2", 2));
context.getPatientReportsByPatientId().put("test-patient3", this.getPatientMeasureReport("test-patient3", 0));
context.setReportId("abc123");
return context;
}

private void addPatientOfInterest(ReportContext.MeasureContext context, TenantService tenantService, String patientId, int populationCount) {
PatientOfInterestModel poi = new PatientOfInterestModel();
poi.setReference("Patient/" + patientId);
poi.setId(patientId);
context.getPatientsOfInterest().add(poi);
String pmrId = ReportIdHelper.getPatientMeasureReportId(context.getReportId(), patientId);
PatientMeasureReport pmr = new PatientMeasureReport();
pmr.setMeasureReport(this.getPatientMeasureReport(patientId, populationCount));
when(tenantService.getPatientMeasureReport(pmrId)).thenReturn(pmr);
}

@Test
public void aggregationWithoutMeasureVersionTest() throws ParseException {
ReportContext.MeasureContext context = this.getMeasureContext(null);
Expand All @@ -50,7 +66,7 @@ public void aggregationWithoutMeasureVersionTest() throws ParseException {
"2023-08-22T00:00:00Z",
"2023-08-22T23:59:59Z");
ReportAggregator aggregator = new ReportAggregator();
MeasureReport aggregate = aggregator.generate(criteria, context);
MeasureReport aggregate = aggregator.generate(mock(TenantService.class), criteria, context);
Assert.assertEquals("http://test.com/fhir/Measure/SomeMeasure", aggregate.getMeasure());
}

Expand All @@ -62,7 +78,7 @@ public void aggregationWithMeasureVersionTest() throws ParseException {
"2023-08-22T00:00:00Z",
"2023-08-22T23:59:59Z");
ReportAggregator aggregator = new ReportAggregator();
MeasureReport aggregate = aggregator.generate(criteria, context);
MeasureReport aggregate = aggregator.generate(mock(TenantService.class), criteria, context);
Assert.assertEquals("http://test.com/fhir/Measure/SomeMeasure|1.0.1", aggregate.getMeasure());
}

Expand All @@ -74,7 +90,11 @@ public void aggregationCountTest() throws ParseException {
"2023-08-22T00:00:00Z",
"2023-08-22T23:59:59Z");
ReportAggregator aggregator = new ReportAggregator();
MeasureReport aggregate = aggregator.generate(criteria, context);
TenantService tenantService = mock(TenantService.class);
addPatientOfInterest(context, tenantService, "test-patient1", 1);
addPatientOfInterest(context, tenantService, "test-patient2", 2);
addPatientOfInterest(context, tenantService, "test-patient3", 0);
MeasureReport aggregate = aggregator.generate(tenantService, criteria, context);
Assert.assertEquals(MeasurePopulation.INITIALPOPULATION.toCode(), aggregate.getGroupFirstRep().getPopulationFirstRep().getCode().getCodingFirstRep().getCode());
Assert.assertEquals(3, aggregate.getGroupFirstRep().getPopulationFirstRep().getCount());
}
Expand Down
Loading