diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/constant/MeasureReportConstants.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/constant/MeasureReportConstants.java index 4ad198d3a..ed87226a1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/constant/MeasureReportConstants.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/constant/MeasureReportConstants.java @@ -21,4 +21,9 @@ private MeasureReportConstants() {} public static final String COUNTRY_CODING_SYSTEM_CODE = "urn:iso:std:iso:3166"; public static final String US_COUNTRY_CODE = "US"; public static final String US_COUNTRY_DISPLAY = "United States of America"; + + public static final String RESOURCE_TYPE_PRACTITIONER = "Practitioner"; + public static final String RESOURCE_TYPE_PRACTITIONER_ROLE = "PractitionerRole"; + public static final String RESOURCE_TYPE_ORGANIZATION = "Organization"; + public static final String RESOURCE_TYPE_LOCATION = "Location"; } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 0d10b5474..1a81fd74d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -18,7 +18,6 @@ import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.fhir.api.Repository; -import org.opencds.cqf.fhir.cr.measure.CareGapsProperties; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; @@ -34,15 +33,13 @@ public class R4MultiMeasureService { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(R4MultiMeasureService.class); private final Repository repository; private final MeasureEvaluationOptions measureEvaluationOptions; - private final CareGapsProperties careGapsProperties; + private String serverBase; public R4MultiMeasureService( - Repository repository, - MeasureEvaluationOptions measureEvaluationOptions, - CareGapsProperties careGapsProperties) { + Repository repository, MeasureEvaluationOptions measureEvaluationOptions, String serverBase) { this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; - this.careGapsProperties = careGapsProperties; + this.serverBase = serverBase; } public Bundle evaluate( @@ -57,7 +54,8 @@ public Bundle evaluate( Bundle additionalData, Parameters parameters, String productLine, - String practitioner) { + String practitioner, + String reporter) { var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); @@ -95,11 +93,16 @@ public Bundle evaluate( // add subject reference for non-individual reportTypes measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, practitioner, subjectId); + + // add reporter if available + if (reporter != null && !reporter.isEmpty()) { + measureReport.setReporter(r4MeasureServiceUtils.getReporter(reporter)); + } // add id to measureReport initializeReport(measureReport); // add report to bundle - bundle.addEntry(getBundleEntry(careGapsProperties.getMyFhirBaseUrl(), measureReport)); + bundle.addEntry(getBundleEntry(serverBase, measureReport)); // progress feedback var measureUrl = measureReport.getMeasure(); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index 9b9e0789b..06adf563f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -7,6 +7,10 @@ import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_SUPPLEMENTALDATA_SEARCHPARAMETER_DEFINITION_DATE; import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_SUPPLEMENTALDATA_SEARCHPARAMETER_URL; import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.MEASUREREPORT_SUPPLEMENTALDATA_SEARCHPARAMETER_VERSION; +import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.RESOURCE_TYPE_LOCATION; +import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.RESOURCE_TYPE_ORGANIZATION; +import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.RESOURCE_TYPE_PRACTITIONER; +import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.RESOURCE_TYPE_PRACTITIONER_ROLE; import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.US_COUNTRY_CODE; import static org.opencds.cqf.fhir.cr.measure.constant.MeasureReportConstants.US_COUNTRY_DISPLAY; @@ -123,4 +127,27 @@ public MeasureReport addSubjectReference(MeasureReport measureReport, String pra } return measureReport; } + + public Reference getReporter(String reporter) { + if (!reporter.isEmpty() && !reporter.contains("/")) { + throw new IllegalArgumentException( + "R4MultiMeasureService requires '[ResourceType]/[ResourceId]' format to set MeasureReport.reporter reference."); + } + Reference reference = null; + if (!reporter.isEmpty()) { + if (reporter.startsWith(RESOURCE_TYPE_PRACTITIONER_ROLE)) { + reference = new Reference(Ids.ensureIdType(reporter, RESOURCE_TYPE_PRACTITIONER_ROLE)); + } else if (reporter.startsWith(RESOURCE_TYPE_PRACTITIONER)) { + reference = new Reference(Ids.ensureIdType(reporter, RESOURCE_TYPE_PRACTITIONER)); + } else if (reporter.startsWith(RESOURCE_TYPE_ORGANIZATION)) { + reference = new Reference(Ids.ensureIdType(reporter, RESOURCE_TYPE_ORGANIZATION)); + } else if (reporter.startsWith(RESOURCE_TYPE_LOCATION)) { + reference = new Reference(Ids.ensureIdType(reporter, RESOURCE_TYPE_LOCATION)); + } else { + throw new IllegalArgumentException("MeasureReport.reporter does not accept ResourceType: " + reporter); + } + } + + return reference; + } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 2047a9479..0e857226f 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -34,7 +34,6 @@ import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.SEARCH_FILTER_MODE; import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE; import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_EXPANSION_MODE; -import org.opencds.cqf.fhir.cr.measure.CareGapsProperties; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureConstants; import org.opencds.cqf.fhir.utility.monad.Either3; @@ -89,7 +88,7 @@ public static MultiMeasure.Given given() { public static class Given { private Repository repository; private MeasureEvaluationOptions evaluationOptions; - private CareGapsProperties careGapsProperties; + private String serverBase; public Given() { this.evaluationOptions = MeasureEvaluationOptions.defaultOptions(); @@ -104,11 +103,7 @@ public Given() { .getTerminologySettings() .setValuesetExpansionMode(VALUESET_EXPANSION_MODE.PERFORM_NAIVE_EXPANSION); - this.careGapsProperties = new CareGapsProperties(); - - this.careGapsProperties.setCareGapsReporter("alphora"); - this.careGapsProperties.setCareGapsCompositionSectionAuthor("alphora-author"); - this.careGapsProperties.setMyFhirBaseUrl("http://localhost"); + this.serverBase = "http://localhost"; } public MultiMeasure.Given repository(Repository repository) { @@ -128,8 +123,8 @@ public MultiMeasure.Given evaluationOptions(MeasureEvaluationOptions evaluationO return this; } - public MultiMeasure.Given careGapsProperties(CareGapsProperties careGapsProperties) { - this.careGapsProperties = careGapsProperties; + public MultiMeasure.Given serverBase(String serverBase) { + this.serverBase = serverBase; return this; } @@ -138,7 +133,7 @@ private R4MeasureProcessor buildProcessor() { } private R4MultiMeasureService buildMeasureService() { - return new R4MultiMeasureService(repository, evaluationOptions, careGapsProperties); + return new R4MultiMeasureService(repository, evaluationOptions, serverBase); } public MultiMeasure.When when() { @@ -167,6 +162,7 @@ public static class When { private Supplier operation; private String practitioner; private String productLine; + private String reporter; public MultiMeasure.When measureId(String measureId) { Either3 eitherMeasure = @@ -220,6 +216,11 @@ public MultiMeasure.When productLine(String productLine) { return this; } + public MultiMeasure.When reporter(String reporter) { + this.reporter = reporter; + return this; + } + public MultiMeasure.When evaluate() { this.operation = () -> service.evaluate( measureIds, @@ -233,7 +234,8 @@ public MultiMeasure.When evaluate() { additionalData, parameters, productLine, - practitioner); + practitioner, + reporter); return this; } @@ -392,6 +394,12 @@ public SelectedMeasureReport hasReportType(String reportType) { assertEquals(reportType, ref.getDisplay()); return this; } + + public SelectedMeasureReport hasReporter(String reporter) { + var ref = this.report().getReporter().getReference(); + assertEquals(reporter, ref); + return this; + } } static class SelectedGroup extends MultiMeasure.Selected { diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index e6a6988fa..87af9322c 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -1,5 +1,7 @@ package org.opencds.cqf.fhir.cr.measure.r4; +import static org.junit.Assert.assertThrows; + import org.junit.jupiter.api.Test; import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; @@ -510,7 +512,7 @@ void MultiMeasure_EightMeasures_SubjectList() { } @Test - void MultiMeasure_EightMeasures_Practitioner() { + void MultiMeasure_EightMeasures_PractitionerJustId() { var when = GIVEN_REPO .when() .measureId("MinimalProportionNoBasisSingleGroup") @@ -544,4 +546,180 @@ void MultiMeasure_EightMeasures_Practitioner() { .population("numerator") .hasCount(0); } + + @Test + void MultiMeasure_EightMeasures_Practitioner() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("Practitioner/tester") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .firstGroup() + .population("initial-population") + .hasCount(1) + .up() + .population("denominator") + .hasCount(0) + .up() + .population("denominator-exclusion") + .hasCount(0) + .up() + .population("denominator-exception") + .hasCount(1) + .up() + .population("numerator-exclusion") + .hasCount(0) + .up() + .population("numerator") + .hasCount(0); + } + + @Test + void MultiMeasure_EightMeasures_ReporterPractitioner() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter("Practitioner/empty") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter("Practitioner/empty"); + } + + @Test + void MultiMeasure_EightMeasures_ReporterPractitionerRole() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter("PractitionerRole/test") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter("PractitionerRole/test"); + } + + @Test + void MultiMeasure_EightMeasures_ReporterLocation() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter("Location/office") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter("Location/office"); + } + + @Test + void MultiMeasure_EightMeasures_ReporterOrganization() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter("Organization/payer") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter("Organization/payer"); + } + + @Test + void MultiMeasure_EightMeasures_ReporterJustId() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter("payer") + .evaluate(); + + assertThrows(IllegalArgumentException.class, () -> when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter("Organization/payer")); + } + + @Test + void MultiMeasure_EightMeasures_ReporterNull() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter(null) + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter(null); + } + + @Test + void MultiMeasure_EightMeasures_ReporterNotAcceptedResource() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .practitioner("tester") + .reporter("Patient/male-2022") + .evaluate(); + + assertThrows(IllegalArgumentException.class, () -> when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Practitioner/tester") + .hasReporter("Patient/male-2022")); + } } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/location/location-office.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/location/location-office.json new file mode 100644 index 000000000..ccb409990 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/location/location-office.json @@ -0,0 +1,4 @@ +{ + "resourceType": "Location", + "id": "office" +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/organization/organization-payer.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/organization/organization-payer.json new file mode 100644 index 000000000..96b56b74c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/organization/organization-payer.json @@ -0,0 +1,4 @@ +{ + "resourceType": "Organization", + "id": "payer" +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/practitionerrole/practitionerrole-test.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/practitionerrole/practitionerrole-test.json new file mode 100644 index 000000000..279a26924 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MinimalMeasureEvaluation/tests/practitionerrole/practitionerrole-test.json @@ -0,0 +1,4 @@ +{ + "resourceType": "PractitionerRole", + "id": "test" +} \ No newline at end of file