From 5bfa8e181146281c14885b723b6d7a4791997294 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 4 Sep 2020 11:06:47 -0600 Subject: [PATCH 01/15] fixed sction for no gap --- .../providers/MeasureOperationsProvider.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 05993372d..3b7b7c79d 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -12,6 +12,7 @@ import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.LibraryResolutionProvider; import org.opencds.cqf.cql.engine.data.DataProvider; @@ -335,7 +336,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje for (IBaseResource resource : measures) { Measure measureResource = (Measure) resource; - + Composition.SectionComponent section = new Composition.SectionComponent(); if (measureResource.hasTitle()) { @@ -415,10 +416,13 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje section.addEntry( new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); - composition.addSection(section); - detectedIssues.add(detectedIssue); + }else { + section.setText(new Narrative() + .setStatus(Narrative.NarrativeStatus.GENERATED) + .setDiv(new XhtmlNode().setValue("

No detected issues.

"))); } + composition.addSection(section); reports.add(report); // TODO - add other types of improvement notation cases @@ -427,7 +431,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje Parameters parameters = new Parameters(); if((null == status || status == "") //everything || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) //filter out closed-gap that has issues for OPEN-GAP - ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ //filet out open-gap without issues for CLOSE-GAP + ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ //filter out open-gap without issues for CLOSE-GAP careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); for (MeasureReport rep : reports) { careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); @@ -441,7 +445,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje Reference newEvaluatedResourceItem = new Reference(); newEvaluatedResourceItem.setReference(parameter.getResource().getId()); List evalResourceExt = new ArrayList<>(); - evalResourceExt.add(new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-ppopulationReference", + evalResourceExt.add(new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-populationReference", new CodeableConcept() .addCoding(new Coding("http://teminology.hl7.org/CodeSystem/measure-population", "initial-population", "initial-population")))); newEvaluatedResourceItem.setExtension(evalResourceExt); @@ -484,7 +488,7 @@ private List getMeasureList(SearchParameterMap theParams, String } return finalMeasureList; }else { - return + return //TODO: this needs to be restricted to only the current measure. It seems to be returning all versions in history. this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000) .stream() @@ -589,7 +593,7 @@ public Resource submitData(RequestDetails details, @IdParam IdType theId, /* * TODO - resource validation using $data-requirements operation (params are the * provided id and the measurement period from the MeasureReport) - * + * * TODO - profile validation ... not sure how that would work ... (get * StructureDefinition from URL or must it be stored in Ruler?) */ From 861404671f6ac130cf4d526e5b1f327259319658 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 4 Sep 2020 11:43:32 -0600 Subject: [PATCH 02/15] added display to detectedissue coding --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 3b7b7c79d..b18e0daf5 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -411,7 +411,10 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); detectedIssue.getEvidence().add(new DetectedIssue.DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); CodeableConcept code = new CodeableConcept() - .addCoding(new Coding().setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category").setCode("care-gap")); + .addCoding(new Coding() + .setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category") + .setCode("care-gap") + .setDisplay("Gap in Care Detected")); detectedIssue.setCode(code); section.addEntry( From a08532b8470e1f9364094936a5caef07cc2c6414 Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 4 Sep 2020 13:59:50 -0600 Subject: [PATCH 03/15] refactored group iterator to actually move through group and identify the right group --- .../cqf/r4/providers/MeasureOperationsProvider.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index b18e0daf5..b9a1daf39 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -237,7 +237,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe (getPatientListFromGroup(subject)) .forEach(groupSubject ->{ Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); - if(null != patientGapBundle){ + if(null != patientGapBundle){ returnParams.addParameter(new Parameters.ParametersParameterComponent() .setName("Gaps in Care Report - " + groupSubject) .setResource(patientGapBundle)); @@ -262,9 +262,14 @@ private List getPatientListFromGroup(String subjectGroupRef){ Iterable groupRetrieve = dataProvider.retrieve("Group", "id", subjectGroupRef, "Group", null, null, null, null, null, null, null, null); Group group; - if (groupRetrieve.iterator().hasNext()) { - group = (Group) groupRetrieve.iterator().next(); - group.getMember().forEach(member -> patientList.add(member.getEntity().getReference())); + Iterator objIterator = groupRetrieve.iterator(); + if (objIterator.hasNext()) { + while(objIterator.hasNext()) { + group = (Group) objIterator.next(); + if (group.getIdElement().getIdPart().equalsIgnoreCase(subjectGroupRef.substring(subjectGroupRef.lastIndexOf("/") + 1)) ) { + group.getMember().forEach(member -> patientList.add(member.getEntity().getReference())); + } + } } return patientList; } From fb19006aaaa9f85a5a0cf71237acd2ed6caf74fa Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 9 Sep 2020 23:34:05 -0600 Subject: [PATCH 04/15] Fix bug in status implementation Fix name of parameter --- .../providers/MeasureOperationsProvider.java | 69 ++++++++++--------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index b9a1daf39..7773bc444 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -229,7 +229,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure, status, organization)) { if(subject.startsWith("Patient/")){ returnParams.addParameter(new Parameters.ParametersParameterComponent() - .setName("Gaps in Care Report - " + subject) + .setName("result") .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status))); return returnParams; }else if(subject.startsWith("Group/")) { @@ -239,7 +239,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); if(null != patientGapBundle){ returnParams.addParameter(new Parameters.ParametersParameterComponent() - .setName("Gaps in Care Report - " + groupSubject) + .setName("result") .setResource(patientGapBundle)); } }); @@ -337,7 +337,6 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje List reports = new ArrayList<>(); List detectedIssues = new ArrayList(); MeasureReport report = null; - boolean hasIssue = false; for (IBaseResource resource : measures) { Measure measureResource = (Measure) resource; @@ -407,9 +406,10 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // as a code String improvementNotation = measureResource.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); if (((improvementNotation.equals("increase")) && (proportion < 1.0)) - || ((improvementNotation.equals("decrease")) && (proportion > 0.0)) - && (null == status || "".equalsIgnoreCase(status) || "open-gap".equalsIgnoreCase(status))) { - hasIssue = true; + || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { + if (status != null && status.equalsIgnoreCase("closed-gap")) { + continue; + } DetectedIssue detectedIssue = new DetectedIssue(); detectedIssue.setId(UUID.randomUUID().toString()); detectedIssue.setStatus(DetectedIssue.DetectedIssueStatus.FINAL); @@ -426,49 +426,50 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); detectedIssues.add(detectedIssue); }else { + if (status != null && status.equalsIgnoreCase("open-gap")) { + continue; + } section.setText(new Narrative() .setStatus(Narrative.NarrativeStatus.GENERATED) .setDiv(new XhtmlNode().setValue("

No detected issues.

"))); } - composition.addSection(section); + composition.addSection(section); reports.add(report); // TODO - add other types of improvement notation cases } } Parameters parameters = new Parameters(); - if((null == status || status == "") //everything - || (hasIssue && !"closed-gap".equalsIgnoreCase(status)) //filter out closed-gap that has issues for OPEN-GAP - ||(!hasIssue && !"open-gap".equalsIgnoreCase(status))){ //filter out open-gap without issues for CLOSE-GAP - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); - for (MeasureReport rep : reports) { - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); - if (report.hasContained()) { - for (Resource contained : report.getContained()) { - if (contained instanceof Bundle) { - addEvaluatedResourcesToParameters((Bundle) contained, parameters); - if(null != parameters && !parameters.isEmpty()) { - List evaluatedResource = new ArrayList<>(); - parameters.getParameter().forEach(parameter -> { - Reference newEvaluatedResourceItem = new Reference(); - newEvaluatedResourceItem.setReference(parameter.getResource().getId()); - List evalResourceExt = new ArrayList<>(); - evalResourceExt.add(new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-populationReference", - new CodeableConcept() - .addCoding(new Coding("http://teminology.hl7.org/CodeSystem/measure-population", "initial-population", "initial-population")))); - newEvaluatedResourceItem.setExtension(evalResourceExt); - evaluatedResource.add(newEvaluatedResourceItem); - }); - report.setEvaluatedResource(evaluatedResource); - } + + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); + for (MeasureReport rep : reports) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); + if (report.hasContained()) { + for (Resource contained : report.getContained()) { + if (contained instanceof Bundle) { + addEvaluatedResourcesToParameters((Bundle) contained, parameters); + if(null != parameters && !parameters.isEmpty()) { + List evaluatedResource = new ArrayList<>(); + parameters.getParameter().forEach(parameter -> { + Reference newEvaluatedResourceItem = new Reference(); + newEvaluatedResourceItem.setReference(parameter.getResource().getId()); + List evalResourceExt = new ArrayList<>(); + evalResourceExt.add(new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-populationReference", + new CodeableConcept() + .addCoding(new Coding("http://teminology.hl7.org/CodeSystem/measure-population", "initial-population", "initial-population")))); + newEvaluatedResourceItem.setExtension(evalResourceExt); + evaluatedResource.add(newEvaluatedResourceItem); + }); + report.setEvaluatedResource(evaluatedResource); } } } } - for (DetectedIssue detectedIssue : detectedIssues) { - careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); - } } + for (DetectedIssue detectedIssue : detectedIssues) { + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); + } + if(careGapReport.getEntry().isEmpty()){ return null; } From 7dc3e22c1a97a8b69a37e4ac0e33a07e117b94b3 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 10 Sep 2020 11:33:44 -0600 Subject: [PATCH 05/15] Use measure id instead of name --- .../providers/MeasureOperationsProvider.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 7773bc444..1c765c28c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -477,33 +477,18 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje } private List getMeasureList(SearchParameterMap theParams, String measure){ - if(measure != null && measure.length() > 0){ - List finalMeasureList = new ArrayList<>(); - List allMeasures = this.measureResourceProvider - .getDao() - .search(theParams) - .getResources(0, 1000); - for(String singleName: measure.split(",")){ - if (singleName.equals("")) { - continue; - } - allMeasures.forEach(measureResource -> { - if(((Measure)measureResource).getName().equalsIgnoreCase(singleName.trim())) { - if (measureResource != null) { - finalMeasureList.add(measureResource); - } - } - }); + List finalMeasureList = new ArrayList<>(); + for(String theId: measure.split(",")){ + if (theId.equals("")) { + continue; + } + Measure measureResource = this.measureResourceProvider.getDao().read(new IdType(theId.trim())); + if (measureResource != null) { + finalMeasureList.add(measureResource); } - return finalMeasureList; - }else { - return - //TODO: this needs to be restricted to only the current measure. It seems to be returning all versions in history. - this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000) - .stream() - .filter(resource -> ((Measure)resource).getUrl() != null && !((Measure)resource).getUrl().equals("")) - .collect(Collectors.toList()); } + + return finalMeasureList; } @Operation(name = "$collect-data", idempotent = true, type = Measure.class) From afa93ff0f75e792414c1011493a4173aaed6d1ae Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 10 Sep 2020 12:36:40 -0600 Subject: [PATCH 06/15] Change parameter name to return --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 1c765c28c..dbc1fb684 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -229,7 +229,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure, status, organization)) { if(subject.startsWith("Patient/")){ returnParams.addParameter(new Parameters.ParametersParameterComponent() - .setName("result") + .setName("return") .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status))); return returnParams; }else if(subject.startsWith("Group/")) { @@ -239,7 +239,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); if(null != patientGapBundle){ returnParams.addParameter(new Parameters.ParametersParameterComponent() - .setName("result") + .setName("return") .setResource(patientGapBundle)); } }); From c77f942039cce3a81b45cef8150a11b26a404a33 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 10 Sep 2020 13:06:39 -0600 Subject: [PATCH 07/15] Protect against null measure --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index dbc1fb684..a2f350b88 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -478,6 +478,10 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje private List getMeasureList(SearchParameterMap theParams, String measure){ List finalMeasureList = new ArrayList<>(); + if(measure == null || measure.length() <= 0) { + return finalMeasureList; + } + for(String theId: measure.split(",")){ if (theId.equals("")) { continue; From ee42e7f5f072cd06917a17bb2a9eb8e4c9be7b93 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Thu, 10 Sep 2020 13:24:15 -0600 Subject: [PATCH 08/15] Fix bug in measure list --- .../providers/MeasureOperationsProvider.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index a2f350b88..fd0d55e07 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -477,22 +477,26 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje } private List getMeasureList(SearchParameterMap theParams, String measure){ - List finalMeasureList = new ArrayList<>(); - if(measure == null || measure.length() <= 0) { - return finalMeasureList; - } - - for(String theId: measure.split(",")){ - if (theId.equals("")) { - continue; - } - Measure measureResource = this.measureResourceProvider.getDao().read(new IdType(theId.trim())); - if (measureResource != null) { - finalMeasureList.add(measureResource); + if(measure != null && measure.length() > 0) { + List finalMeasureList = new ArrayList<>(); + for(String theId: measure.split(",")){ + if (theId.equals("")) { + continue; + } + Measure measureResource = this.measureResourceProvider.getDao().read(new IdType(theId.trim())); + if (measureResource != null) { + finalMeasureList.add(measureResource); + } } - } - - return finalMeasureList; + return finalMeasureList; + } + + return + //TODO: this needs to be restricted to only the current measure. It seems to be returning all versions in history. + this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000) + .stream() + .filter(resource -> ((Measure)resource).getUrl() != null && !((Measure)resource).getUrl().equals("")) + .collect(Collectors.toList()); } @Operation(name = "$collect-data", idempotent = true, type = Measure.class) From a3c1cb0ffee1a7b03884bf3e0df8ff26fb974a8b Mon Sep 17 00:00:00 2001 From: bryant Date: Fri, 2 Oct 2020 07:25:43 -0600 Subject: [PATCH 09/15] Added Ids to parameters and Ids and identifier to bundles. --- .../providers/MeasureOperationsProvider.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 431559075..415ac2b52 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -222,15 +222,17 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe @OperationParam(name = "topic") String topic,@OperationParam(name = "practitioner") String practitioner, @OperationParam(name = "measure") String measure, @OperationParam(name="status")String status, @OperationParam(name = "organization") String organization){ - //TODO: status - optional if null all gaps - if closed-gap code only those gaps that are closed if open-gap code only those that are open //TODO: topic should allow many and be a union of them //TODO: "The Server needs to make sure that practitioner is authorized to get the gaps in care report for and know what measures the practitioner are eligible or qualified." Parameters returnParams = new Parameters(); if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure, status, organization)) { if(subject.startsWith("Patient/")){ - returnParams.addParameter(new Parameters.ParametersParameterComponent() + Parameters.ParametersParameterComponent newParameter = new Parameters.ParametersParameterComponent() .setName("return") - .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status))); + .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status)); + //TODO - is this supposed to be something like "id": "multiple-gaps-indv-report01"?? + newParameter.setId(UUID.randomUUID().toString()); + returnParams.addParameter(newParameter); return returnParams; }else if(subject.startsWith("Group/")) { returnParams.setId((status==null?"all-gaps": status) + "-" + subject.replace("/","_") + "-report"); @@ -238,9 +240,12 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe .forEach(groupSubject ->{ Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); if(null != patientGapBundle){ - returnParams.addParameter(new Parameters.ParametersParameterComponent() - .setName("return") - .setResource(patientGapBundle)); + Parameters.ParametersParameterComponent newParameter = new Parameters.ParametersParameterComponent() + .setName("return") + .setResource(patientGapBundle); + //TODO - is this supposed to be something like "id": "multiple-gaps-indv-report01"?? + newParameter.setId(UUID.randomUUID().toString()); + returnParams.addParameter(newParameter); } }); } @@ -323,6 +328,9 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); careGapReport.setTimestamp(new Date()); + careGapReport.setId(UUID.randomUUID().toString()); + //TODO - this MIGHT be a specific string + careGapReport.setIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:uuid:" + UUID.randomUUID().toString())); Composition composition = new Composition(); composition.setStatus(Composition.CompositionStatus.FINAL) From 89772c458c3bf0b1ad8b823f97ce3af962fe6229 Mon Sep 17 00:00:00 2001 From: rob-reynolds <4z&ChMu6yGn%IL> Date: Wed, 28 Oct 2020 06:50:38 -0600 Subject: [PATCH 10/15] Temporarily update collect data to work with DEQM RI --- .../providers/MeasureOperationsProvider.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 415ac2b52..b60f1ed4b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -597,33 +597,36 @@ public org.hl7.fhir.r4.model.Library dataRequirements(@IdParam IdType theId, @SuppressWarnings("unchecked") @Operation(name = "$submit-data", idempotent = true, type = Measure.class) public Resource submitData(RequestDetails details, @IdParam IdType theId, - @OperationParam(name = "measurereport", min = 1, max = 1, type = MeasureReport.class) MeasureReport report, + @OperationParam(name = "measureReport", min = 1, max = 1, type = MeasureReport.class) MeasureReport report, @OperationParam(name = "resource") List resources) { Bundle transactionBundle = new Bundle().setType(Bundle.BundleType.TRANSACTION); + //TODO: measureReport should be measurereport. Temporarily updated to work with DEQM RI /* * TODO - resource validation using $data-requirements operation (params are the * provided id and the measurement period from the MeasureReport) - * + * * TODO - profile validation ... not sure how that would work ... (get * StructureDefinition from URL or must it be stored in Ruler?) */ transactionBundle.addEntry(createTransactionEntry(report)); - for (IAnyResource resource : resources) { - Resource res = (Resource) resource; - if (res instanceof Bundle) { - for (Bundle.BundleEntryComponent entry : createTransactionBundle((Bundle) res).getEntry()) { - transactionBundle.addEntry(entry); + if (resources != null) { + for (IAnyResource resource : resources) { + Resource res = (Resource) resource; + if (res instanceof Bundle) { + for (Bundle.BundleEntryComponent entry : createTransactionBundle((Bundle) res).getEntry()) { + transactionBundle.addEntry(entry); + } + } else { + // Build transaction bundle + transactionBundle.addEntry(createTransactionEntry(res)); } - } else { - // Build transaction bundle - transactionBundle.addEntry(createTransactionEntry(res)); } } - return (Resource) ((IFhirSystemDao)this.registry.getSystemDao()).transaction(details, transactionBundle); + return (Resource) this.registry.getSystemDao().transaction(details, transactionBundle); } private Bundle createTransactionBundle(Bundle bundle) { From e9c23e30398d434e7c73a0327b910536f2fac6c0 Mon Sep 17 00:00:00 2001 From: Jreyno77 Date: Fri, 23 Apr 2021 10:49:06 -0600 Subject: [PATCH 11/15] Updates to match latest specification --- .../providers/MeasureOperationsProvider.java | 377 +++++++++++------- 1 file changed, 223 insertions(+), 154 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 5e0fc6256..e3cdb7bbd 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -1,13 +1,16 @@ package org.opencds.cqf.r4.providers; import java.util.*; -import java.util.stream.Collectors; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; +import com.google.common.base.Strings; + import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.rest.annotation.*; + +import org.hibernate.cfg.NotYetImplementedException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; @@ -16,7 +19,6 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.opencds.cqf.common.evaluation.EvaluationProviderFactory; import org.opencds.cqf.common.providers.LibraryResolutionProvider; -import org.opencds.cqf.cql.engine.data.DataProvider; import org.opencds.cqf.cql.engine.execution.LibraryLoader; import org.opencds.cqf.tooling.library.r4.NarrativeProvider; import org.opencds.cqf.tooling.measure.r4.CqfMeasure; @@ -29,7 +31,6 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.rp.r4.MeasureResourceProvider; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.MethodOutcome; @@ -222,96 +223,151 @@ public MeasureReport evaluateMeasure(@IdParam IdType theId, @Operation(name = "$care-gaps", idempotent = true, type = Measure.class) public Parameters careGapsReport(@OperationParam(name = "periodStart") String periodStart, - @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, - @OperationParam(name = "topic") String topic,@OperationParam(name = "practitioner") String practitioner, - @OperationParam(name = "measure") String measure, @OperationParam(name="status")String status, - @OperationParam(name = "organization") String organization){ + @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, + @OperationParam(name = "topic") String topic, @OperationParam(name = "practitioner") String practitioner, + @OperationParam(name = "measureId") List measureId, @OperationParam(name = "measureIdentifier") List measureIdentifier, + @OperationParam(name = "measureUrl") List measureUrl, @OperationParam(name="status")String status, + @OperationParam(name = "organization") String organization, @OperationParam(name = "program") String program){ //TODO: topic should allow many and be a union of them //TODO: "The Server needs to make sure that practitioner is authorized to get the gaps in care report for and know what measures the practitioner are eligible or qualified." Parameters returnParams = new Parameters(); - if(careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measure, status, organization)) { - if(subject.startsWith("Patient/")){ - Parameters.ParametersParameterComponent newParameter = new Parameters.ParametersParameterComponent() - .setName("return") - .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure, status)); - //TODO - is this supposed to be something like "id": "multiple-gaps-indv-report01"?? - newParameter.setId(UUID.randomUUID().toString()); - returnParams.addParameter(newParameter); - return returnParams; - }else if(subject.startsWith("Group/")) { + + if (careGapParameterValidation(periodStart, periodEnd, subject, topic, practitioner, measureId, measureIdentifier, measureUrl, status, organization, program)) { + List measures = resolveMeasures(measureId, measureIdentifier, measureUrl); + if (subject.startsWith("Patient/")){ + resolvePatientGapBundleForMeasures(periodStart, periodEnd, subject, topic, status, returnParams, measures, "return", organization); + } else if (subject.startsWith("Group/")) { returnParams.setId((status==null?"all-gaps": status) + "-" + subject.replace("/","_") + "-report"); (getPatientListFromGroup(subject)) - .forEach(groupSubject ->{ - Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, groupSubject, topic, measure, status); - if(null != patientGapBundle){ - Parameters.ParametersParameterComponent newParameter = new Parameters.ParametersParameterComponent() - .setName("return") - .setResource(patientGapBundle); - //TODO - is this supposed to be something like "id": "multiple-gaps-indv-report01"?? - newParameter.setId(UUID.randomUUID().toString()); - returnParams.addParameter(newParameter); - } - }); + .forEach(groupSubject -> resolvePatientGapBundleForMeasures(periodStart, periodEnd, subject, topic, status, returnParams, measures, "return", organization)); } - return returnParams; - } - if (practitioner == null || practitioner.equals("")) { - return new Parameters().addParameter( - new Parameters.ParametersParameterComponent() - .setName("Gaps in Care Report - " + subject) - .setResource(patientCareGap(periodStart, periodEnd, subject, topic, measure,status))); - } - return returnParams; - } - - private List getPatientListFromGroup(String subjectGroupRef){ - List patientList = new ArrayList<>(); - - DataProvider dataProvider = this.factory.createDataProvider("FHIR", "4"); - Iterable groupRetrieve = dataProvider.retrieve("Group", "id", subjectGroupRef, "Group", null, null, null, - null, null, null, null, null); - Group group; - Iterator objIterator = groupRetrieve.iterator(); - if (objIterator.hasNext()) { - while(objIterator.hasNext()) { - group = (Group) objIterator.next(); - if (group.getIdElement().getIdPart().equalsIgnoreCase(subjectGroupRef.substring(subjectGroupRef.lastIndexOf("/") + 1)) ) { - group.getMember().forEach(member -> patientList.add(member.getEntity().getReference())); - } + else if (Strings.isNullOrEmpty(practitioner)) { + String parameterName = "Gaps in Care Report - " + subject; + resolvePatientGapBundleForMeasures(periodStart, periodEnd, subject, topic, status, returnParams, measures, parameterName, organization); } + return returnParams; } - return patientList; + return returnParams; } - @SuppressWarnings("unused") private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, - String practitioner, String measure, String status, String organization){ - if(periodStart == null || periodStart.equals("") || - periodEnd == null || periodEnd.equals("")){ + String practitioner, List measureId, List measureIdentifier, List measureUrl, String status, String organization, String program) { + if(Strings.isNullOrEmpty(periodStart) || Strings.isNullOrEmpty(periodEnd)) { throw new IllegalArgumentException("periodStart and periodEnd are required."); } //TODO - remove this - covered in check of subject/practitioner/organization - left in for now 'cause we need a subject to develop - if (subject == null || subject.equals("")) { + if (Strings.isNullOrEmpty(subject)) { throw new IllegalArgumentException("Subject is required."); } - if(null != subject) { + if (!Strings.isNullOrEmpty(organization)) { + // //TODO - add this - left out for now 'cause we need a subject to develop + // if (!Strings.isNullOrEmpty(subject)) { + // throw new IllegalArgumentException("If a organization is specified then only organization or practitioner may be specified."); + // } + } + if(!Strings.isNullOrEmpty(practitioner) && Strings.isNullOrEmpty(organization)){ + throw new IllegalArgumentException("If a practitioner is specified then an organization must also be specified."); + } + if (!Strings.isNullOrEmpty(practitioner) && Strings.isNullOrEmpty(organization)) { + // //TODO - add this - left out for now 'cause we need a subject to develop + // if (!Strings.isNullOrEmpty(subject)) { + // throw new IllegalArgumentException("If practitioner and organization is specified then subject may not be specified."); + // } + } + if(Strings.isNullOrEmpty(subject) && Strings.isNullOrEmpty(practitioner) && Strings.isNullOrEmpty(organization)) { + throw new IllegalArgumentException("periodStart AND periodEnd AND (subject OR organization OR (practitioner AND organization)) MUST be provided"); + } + if(!Strings.isNullOrEmpty(subject)) { if (!subject.startsWith("Patient/") && !subject.startsWith("Group/")) { throw new IllegalArgumentException("Subject must follow the format of either 'Patient/ID' OR 'Group/ID'."); } } - if(null != status && (!status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap"))){ + if(!Strings.isNullOrEmpty(status) && (!status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap"))){ throw new IllegalArgumentException("If status is present, it must be either 'open-gap' or 'closed-gap'."); } - if(null != practitioner && null == organization){ - throw new IllegalArgumentException("If a practitioner is specified then an organization must also be specified."); - } - if(null == subject && null == practitioner && null == organization){ - throw new IllegalArgumentException("periodStart AND periodEnd AND (subject OR organization OR (practitioner AND organization)) MUST be provided"); + if (measureIdentifier != null && !measureIdentifier.isEmpty()) { + throw new NotYetImplementedException("measureIdentifier Not Yet Implemented."); } return true; } - private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, String measure, String status) { + private List resolveMeasures(List measureId, List measureIdentifier, List measureUrl) { + List measures = new ArrayList(); + if (measureId == null || measureId.isEmpty()) { + logger.info("No measure Ids found."); + } else { + measureId.forEach(id -> { + Measure measure = this.measureResourceProvider.getDao().read(new IdType(id)); + if (measure != null) { + measures.add(measure); + } + }); + } + logger.info("measureIdentifier: " + measureIdentifier + "Not Yet Supported"); + if (measureUrl == null || measureUrl.isEmpty()) { + logger.info("No measure urls found."); + } else { + measureUrl.forEach(url -> { + Measure measure = resolveMeasureByUrl(url); + if (measure != null) { + measures.add(measure); + } + }); + } + return measures; + } + + private Measure resolveMeasureByUrl(CanonicalType url) { + String urlValue = url.getValueAsString(); + if (!urlValue.contains("/Measure/")) { + throw new IllegalArgumentException("Invalid resource type for determining Measure from url: " + url); + } + String [] urlsplit = urlValue.split("/Measure/"); + if (urlsplit.length != 2) { + throw new IllegalArgumentException("Invalid url, Measure.url SHALL be /Measure/"); + } + String canonicalBase = urlsplit[0]; + + String measureName = urlsplit[1]; + IdType measureIdType = new IdType(); + if (measureName.contains("|")) { + String[] nameVersion = measureName.split("\\|"); + String name = nameVersion[0]; + String version = nameVersion[1]; + measureIdType.setValue(name).withVersion(version); + + } else { + measureIdType.setValue(measureName); + } + Measure measure = this.measureResourceProvider.getDao().read(measureIdType); + return measure; + } + + private void resolvePatientGapBundleForMeasures(String periodStart, String periodEnd, String subject, String topic, String status, + Parameters returnParams, List measures, String name, String organization) { + Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, subject, topic, measures, status, organization); + if (patientGapBundle != null) { + Parameters.ParametersParameterComponent newParameter = new Parameters.ParametersParameterComponent() + .setName(name) + .setResource(patientGapBundle); + //TODO - is this supposed to be something like "id": "multiple-gaps-indv-report01"?? + newParameter.setId(UUID.randomUUID().toString()); + returnParams.addParameter(newParameter); + } + } + + private List getPatientListFromGroup(String subjectGroupRef){ + List patientList = new ArrayList<>(); + IBaseResource baseGroup = registry.getResourceDao("Group").read(new IdType(subjectGroupRef)); + if (baseGroup == null) { + throw new RuntimeException("Could not find Group/" + subjectGroupRef); + } + Group group = (Group) baseGroup; + group.getMember().forEach(member -> patientList.add(member.getEntity().getReference())); + return patientList; + } + + private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, List measures, String status, String organization) { //TODO: this is an org hack. Need to figure out what the right thing is. IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); List org = orgDao.search(new SearchParameterMap()).getResources(0, 1); @@ -327,7 +383,6 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje TokenParam topicParam = new TokenParam(topic); theParams.add("topic", topicParam); } - List measures = getMeasureList(theParams, measure); Bundle careGapReport = new Bundle(); careGapReport.setType(Bundle.BundleType.DOCUMENT); @@ -343,30 +398,33 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje .setDate(new Date()) .setType(new CodeableConcept() .addCoding(new Coding() - .setCode("gaps-doc") - .setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/gaps-doc-type") + .setCode("96315-7") + .setSystem("http://loinc.org") .setDisplay("Gaps in Care Report"))); + if (organization != null) { + composition.setCustodian(new Reference(organization.startsWith("Organization/") ? organization : "Organization/" + organization)); + } + List reports = new ArrayList<>(); List detectedIssues = new ArrayList(); MeasureReport report = null; - for (IBaseResource resource : measures) { - Measure measureResource = (Measure) resource; + for (Measure measure : measures) { Composition.SectionComponent section = new Composition.SectionComponent(); - if (measureResource.hasTitle()) { - section.setTitle(measureResource.getTitle()); + if (measure.hasTitle()) { + section.setTitle(measure.getTitle()); } // TODO - this is configured for patient-level evaluation only - report = evaluateMeasure(measureResource.getIdElement(), periodStart, periodEnd, null, "patient", subject, null, + report = evaluateMeasure(measure.getIdElement(), periodStart, periodEnd, null, "patient", subject, null, null, null, null, null, null); report.setId(UUID.randomUUID().toString()); report.setDate(new Date()); - report.setImprovementNotation(measureResource.getImprovementNotation()); + report.setImprovementNotation(measure.getImprovementNotation()); //TODO: this is an org hack && requires an Organization to be in the ruler if (org != null && org.size() > 0) { report.setReporter(new Reference("Organization/" + org.get(0).getIdElement().getIdPart())); @@ -376,76 +434,56 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje //TODO: DetectedIssue //section.addEntry(new Reference("MeasureReport/" + report.getId())); - if (report.hasGroup() && measureResource.hasScoring()) { - int numerator = 0; - int denominator = 0; - for (MeasureReport.MeasureReportGroupComponent group : report.getGroup()) { - if (group.hasPopulation()) { - for (MeasureReport.MeasureReportGroupPopulationComponent population : group.getPopulation()) { - // TODO - currently configured for measures with only 1 numerator and 1 - // denominator - if (population.hasCode()) { - if (population.getCode().hasCoding()) { - for (Coding coding : population.getCode().getCoding()) { - if (coding.hasCode()) { - if (coding.getCode().equals("numerator") && population.hasCount()) { - numerator = population.getCount(); - } else if (coding.getCode().equals("denominator") - && population.hasCount()) { - denominator = population.getCount(); - } - } - } - } - } - } - } - } - - //TODO: implement this per the spec - //Holding off on implementation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) - double proportion = 0.0; - if (measureResource.getScoring().hasCoding() && denominator != 0) { - for (Coding coding : measureResource.getScoring().getCoding()) { - if (coding.hasCode() && coding.getCode().equals("proportion")) { - if (denominator != 0.0 ) { - proportion = numerator / denominator; - } - } - } - } - + if (report.hasGroup() && measure.hasScoring()) { + double proportion = resolveProportion(report, measure); // TODO - this is super hacky ... change once improvementNotation is specified // as a code - String improvementNotation = measureResource.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); - if (((improvementNotation.equals("increase")) && (proportion < 1.0)) - || ((improvementNotation.equals("decrease")) && (proportion > 0.0))) { - if (status != null && status.equalsIgnoreCase("closed-gap")) { + String improvementNotation = measure.getImprovementNotation().getCodingFirstRep().getCode().toLowerCase(); + DetectedIssue detectedIssue = new DetectedIssue(); + detectedIssue.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/gaps-detectedissue-deqm")); + if (closedGap(improvementNotation, proportion)) { + if (notReportingClosedGaps(status)) { continue; } - DetectedIssue detectedIssue = new DetectedIssue(); - detectedIssue.setId(UUID.randomUUID().toString()); - detectedIssue.setStatus(DetectedIssue.DetectedIssueStatus.FINAL); - detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); - detectedIssue.getEvidence().add(new DetectedIssue.DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); - CodeableConcept code = new CodeableConcept() - .addCoding(new Coding() - .setSystem("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/detectedissue-category") - .setCode("care-gap") - .setDisplay("Gap in Care Detected")); - detectedIssue.setCode(code); - - section.addEntry( - new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); - detectedIssues.add(detectedIssue); - }else { - if (status != null && status.equalsIgnoreCase("open-gap")) { + else { + detectedIssue.addModifierExtension( + new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-gapStatus", + new CodeableConcept( + new Coding("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/gaps-status", "closed-gap", null) + )) + ); + } + } else { + if (notReportingOpenGaps(status)) { continue; } + else { + detectedIssue.addModifierExtension( + new Extension("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/extension-gapStatus", + new CodeableConcept( + new Coding("http://hl7.org/fhir/us/davinci-deqm/CodeSystem/gaps-status", "open-gap", null) + )) + ); + } section.setText(new Narrative() .setStatus(Narrative.NarrativeStatus.GENERATED) .setDiv(new XhtmlNode().setValue("

No detected issues.

"))); } + + detectedIssue.setId(UUID.randomUUID().toString()); + detectedIssue.setStatus(DetectedIssue.DetectedIssueStatus.FINAL); + detectedIssue.setPatient(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)); + detectedIssue.getEvidence().add(new DetectedIssue.DetectedIssueEvidenceComponent().addDetail(new Reference("MeasureReport/" + report.getId()))); + CodeableConcept code = new CodeableConcept() + .addCoding(new Coding() + .setSystem("http://terminology.hl7.org/CodeSystem/v3-ActCode") + .setCode("CAREGAP") + .setDisplay("Care Gaps")); + detectedIssue.setCode(code); + + section.addEntry( + new Reference("DetectedIssue/" + detectedIssue.getIdElement().getIdPart())); + detectedIssues.add(detectedIssue); composition.addSection(section); reports.add(report); @@ -489,27 +527,58 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje return careGapReport; } - private List getMeasureList(SearchParameterMap theParams, String measure){ - if(measure != null && measure.length() > 0) { - List finalMeasureList = new ArrayList<>(); - for(String theId: measure.split(",")){ - if (theId.equals("")) { - continue; + private double resolveProportion(MeasureReport report, Measure measure) { + int numerator = 0; + int denominator = 0; + for (MeasureReport.MeasureReportGroupComponent group : report.getGroup()) { + if (group.hasPopulation()) { + for (MeasureReport.MeasureReportGroupPopulationComponent population : group.getPopulation()) { + // TODO - currently configured for measures with only 1 numerator and 1 + // denominator + if (population.hasCode()) { + if (population.getCode().hasCoding()) { + for (Coding coding : population.getCode().getCoding()) { + if (coding.hasCode()) { + if (coding.getCode().equals("numerator") && population.hasCount()) { + numerator = population.getCount(); + } else if (coding.getCode().equals("denominator") + && population.hasCount()) { + denominator = population.getCount(); + } + } + } + } + } } - Measure measureResource = this.measureResourceProvider.getDao().read(new IdType(theId.trim())); - if (measureResource != null) { - finalMeasureList.add(measureResource); + } + } + + //TODO: implement this per the spec + //Holding off on implementation using Measure Score pending guidance re consideration for programs that don't perform the calculation (they just use numer/denom) + double proportion = 0.0; + if (measure.getScoring().hasCoding() && denominator != 0) { + for (Coding coding : measure.getScoring().getCoding()) { + if (coding.hasCode() && coding.getCode().equals("proportion")) { + if (denominator != 0.0 ) { + proportion = numerator / denominator; + } } } - return finalMeasureList; - } - - return - //TODO: this needs to be restricted to only the current measure. It seems to be returning all versions in history. - this.measureResourceProvider.getDao().search(theParams).getResources(0, 1000) - .stream() - .filter(resource -> ((Measure)resource).getUrl() != null && !((Measure)resource).getUrl().equals("")) - .collect(Collectors.toList()); + } + return proportion; + } + + private boolean notReportingOpenGaps(String status) { + return (status != null && status.equalsIgnoreCase("closed-gap") && !status.equalsIgnoreCase("open-gap")) || status == null; + } + + private boolean notReportingClosedGaps(String status) { + return (status != null && status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap")) || status == null; + } + + private boolean closedGap(String improvementNotation, double proportion) { + return ((improvementNotation.equals("increase")) && (proportion < 1.0)) + || ((improvementNotation.equals("decrease")) && (proportion > 0.0)); } @Operation(name = "$collect-data", idempotent = true, type = Measure.class) From e47dbbd7d4aed1d97602be81c3b8c634fa87906a Mon Sep 17 00:00:00 2001 From: rob-reynolds Date: Wed, 28 Apr 2021 20:10:55 -0600 Subject: [PATCH 12/15] Made status a List Require status Return empty params if no result Fixed bug in gap detection --- .../providers/MeasureOperationsProvider.java | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index e3cdb7bbd..9575b2e75 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -226,7 +226,7 @@ public Parameters careGapsReport(@OperationParam(name = "periodStart") String pe @OperationParam(name = "periodEnd") String periodEnd, @OperationParam(name = "subject") String subject, @OperationParam(name = "topic") String topic, @OperationParam(name = "practitioner") String practitioner, @OperationParam(name = "measureId") List measureId, @OperationParam(name = "measureIdentifier") List measureIdentifier, - @OperationParam(name = "measureUrl") List measureUrl, @OperationParam(name="status")String status, + @OperationParam(name = "measureUrl") List measureUrl, @OperationParam(name="status")List status, @OperationParam(name = "organization") String organization, @OperationParam(name = "program") String program){ //TODO: topic should allow many and be a union of them //TODO: "The Server needs to make sure that practitioner is authorized to get the gaps in care report for and know what measures the practitioner are eligible or qualified." @@ -251,7 +251,7 @@ else if (Strings.isNullOrEmpty(practitioner)) { } private Boolean careGapParameterValidation(String periodStart, String periodEnd, String subject, String topic, - String practitioner, List measureId, List measureIdentifier, List measureUrl, String status, String organization, String program) { + String practitioner, List measureId, List measureIdentifier, List measureUrl, List status, String organization, String program) { if(Strings.isNullOrEmpty(periodStart) || Strings.isNullOrEmpty(periodEnd)) { throw new IllegalArgumentException("periodStart and periodEnd are required."); } @@ -282,8 +282,13 @@ private Boolean careGapParameterValidation(String periodStart, String periodEnd, throw new IllegalArgumentException("Subject must follow the format of either 'Patient/ID' OR 'Group/ID'."); } } - if(!Strings.isNullOrEmpty(status) && (!status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap"))){ - throw new IllegalArgumentException("If status is present, it must be either 'open-gap' or 'closed-gap'."); + if (status == null || status.isEmpty()) { + throw new IllegalArgumentException("Status is required."); + } + for (String statusValue: status) { + if(!Strings.isNullOrEmpty(statusValue) && (!statusValue.equalsIgnoreCase("open-gap") && !statusValue.equalsIgnoreCase("closed-gap"))){ + throw new IllegalArgumentException("Status must be either 'open-gap', 'closed-gap', or both."); + } } if (measureIdentifier != null && !measureIdentifier.isEmpty()) { throw new NotYetImplementedException("measureIdentifier Not Yet Implemented."); @@ -343,7 +348,7 @@ private Measure resolveMeasureByUrl(CanonicalType url) { return measure; } - private void resolvePatientGapBundleForMeasures(String periodStart, String periodEnd, String subject, String topic, String status, + private void resolvePatientGapBundleForMeasures(String periodStart, String periodEnd, String subject, String topic, List status, Parameters returnParams, List measures, String name, String organization) { Bundle patientGapBundle = patientCareGap(periodStart, periodEnd, subject, topic, measures, status, organization); if (patientGapBundle != null) { @@ -367,7 +372,7 @@ private List getPatientListFromGroup(String subjectGroupRef){ return patientList; } - private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, List measures, String status, String organization) { + private Bundle patientCareGap(String periodStart, String periodEnd, String subject, String topic, List measures, List status, String organization) { //TODO: this is an org hack. Need to figure out what the right thing is. IFhirResourceDao orgDao = this.registry.getResourceDao(Organization.class); List org = orgDao.search(new SearchParameterMap()).getResources(0, 1); @@ -490,6 +495,9 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje // TODO - add other types of improvement notation cases } } + if (reports.isEmpty()) { + return null; + } Parameters parameters = new Parameters(); careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); @@ -521,9 +529,6 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(detectedIssue)); } - if(careGapReport.getEntry().isEmpty()){ - return null; - } return careGapReport; } @@ -568,17 +573,17 @@ private double resolveProportion(MeasureReport report, Measure measure) { return proportion; } - private boolean notReportingOpenGaps(String status) { - return (status != null && status.equalsIgnoreCase("closed-gap") && !status.equalsIgnoreCase("open-gap")) || status == null; + private boolean notReportingOpenGaps(List status) { + return !status.stream().anyMatch(x -> x.equalsIgnoreCase("open-gap")); } - private boolean notReportingClosedGaps(String status) { - return (status != null && status.equalsIgnoreCase("open-gap") && !status.equalsIgnoreCase("closed-gap")) || status == null; + private boolean notReportingClosedGaps(List status) { + return !status.stream().anyMatch(x -> x.equalsIgnoreCase("closed-gap")); } private boolean closedGap(String improvementNotation, double proportion) { - return ((improvementNotation.equals("increase")) && (proportion < 1.0)) - || ((improvementNotation.equals("decrease")) && (proportion > 0.0)); + return ((improvementNotation.equals("increase")) && (proportion > 0.0)) + || ((improvementNotation.equals("decrease")) && (proportion < 1.0)); } @Operation(name = "$collect-data", idempotent = true, type = Measure.class) From 783e0cf615ca207bc9ecb5779eeabec2c0391e5f Mon Sep 17 00:00:00 2001 From: rob-reynolds Date: Thu, 29 Apr 2021 07:09:18 -0600 Subject: [PATCH 13/15] Add missing extensions --- .../opencds/cqf/r4/providers/MeasureOperationsProvider.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 9575b2e75..23979ae0c 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -390,6 +390,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje } Bundle careGapReport = new Bundle(); + careGapReport.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/gaps-bundle-deqm")); careGapReport.setType(Bundle.BundleType.DOCUMENT); careGapReport.setTimestamp(new Date()); careGapReport.setId(UUID.randomUUID().toString()); @@ -397,6 +398,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje careGapReport.setIdentifier(new Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:uuid:" + UUID.randomUUID().toString())); Composition composition = new Composition(); + composition.setMeta(new Meta().addProfile("http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/gaps-composition-deqm")); composition.setStatus(Composition.CompositionStatus.FINAL) .setSubject(new Reference(subject.startsWith("Patient/") ? subject : "Patient/" + subject)) .setTitle("Care Gap Report for " + subject) @@ -499,7 +501,7 @@ private Bundle patientCareGap(String periodStart, String periodEnd, String subje return null; } Parameters parameters = new Parameters(); - + careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(composition)); for (MeasureReport rep : reports) { careGapReport.addEntry(new Bundle.BundleEntryComponent().setResource(rep)); From 39f1644bbb461897b3be3ecee6a71c93904d0020 Mon Sep 17 00:00:00 2001 From: rob-reynolds Date: Thu, 13 May 2021 11:05:38 -0600 Subject: [PATCH 14/15] Removed incorrect comment --- .../org/opencds/cqf/r4/providers/MeasureOperationsProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index 23979ae0c..b5cb8884b 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -681,7 +681,6 @@ public Resource submitData(RequestDetails details, @IdParam IdType theId, @OperationParam(name = "resource") List resources) { Bundle transactionBundle = new Bundle().setType(Bundle.BundleType.TRANSACTION); - //TODO: measureReport should be measurereport. Temporarily updated to work with DEQM RI /* * TODO - resource validation using $data-requirements operation (params are the * provided id and the measurement period from the MeasureReport) From cc286825e46c74d52ce0599df8d68e122f85e9fe Mon Sep 17 00:00:00 2001 From: rob-reynolds Date: Thu, 13 May 2021 11:38:27 -0600 Subject: [PATCH 15/15] Added TODO to fix to get the measure by url --- .../org/opencds/cqf/r4/providers/MeasureOperationsProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java index b5cb8884b..61aa59ec7 100644 --- a/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java +++ b/r4/src/main/java/org/opencds/cqf/r4/providers/MeasureOperationsProvider.java @@ -333,6 +333,7 @@ private Measure resolveMeasureByUrl(CanonicalType url) { } String canonicalBase = urlsplit[0]; + //TODO: need to do a lookup based on Measure name in order to get the Id. String measureName = urlsplit[1]; IdType measureIdType = new IdType(); if (measureName.contains("|")) {