001package ca.uhn.fhir.cql.dstu3.evaluation;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server - Clinical Quality Language
006 * %%
007 * Copyright (C) 2014 - 2022 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.cql.common.evaluation.MeasurePopulationType;
025import ca.uhn.fhir.cql.common.evaluation.MeasureScoring;
026import ca.uhn.fhir.cql.dstu3.builder.MeasureReportBuilder;
027import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
028import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
029import ca.uhn.fhir.rest.api.server.IBundleProvider;
030import ca.uhn.fhir.rest.api.server.RequestDetails;
031import ca.uhn.fhir.rest.param.ReferenceParam;
032import org.cqframework.cql.elm.execution.ExpressionDef;
033import org.cqframework.cql.elm.execution.FunctionDef;
034import org.hl7.fhir.dstu3.model.CodeableConcept;
035import org.hl7.fhir.dstu3.model.Coding;
036import org.hl7.fhir.dstu3.model.Extension;
037import org.hl7.fhir.dstu3.model.IdType;
038import org.hl7.fhir.dstu3.model.ListResource;
039import org.hl7.fhir.dstu3.model.Measure;
040import org.hl7.fhir.dstu3.model.MeasureReport;
041import org.hl7.fhir.dstu3.model.Observation;
042import org.hl7.fhir.dstu3.model.Patient;
043import org.hl7.fhir.dstu3.model.Quantity;
044import org.hl7.fhir.dstu3.model.Reference;
045import org.hl7.fhir.dstu3.model.Resource;
046import org.hl7.fhir.dstu3.model.StringType;
047import org.hl7.fhir.dstu3.model.UriType;
048import org.hl7.fhir.instance.model.api.IBaseResource;
049import org.opencds.cqf.cql.engine.execution.Context;
050import org.opencds.cqf.cql.engine.execution.Variable;
051import org.opencds.cqf.cql.engine.runtime.Code;
052import org.opencds.cqf.cql.engine.runtime.Interval;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056import java.lang.reflect.Field;
057import java.util.ArrayList;
058import java.util.Collections;
059import java.util.HashMap;
060import java.util.HashSet;
061import java.util.LinkedHashMap;
062import java.util.List;
063import java.util.Set;
064import java.util.UUID;
065import java.util.stream.Collectors;
066
067public class MeasureEvaluation {
068
069        private static final Logger logger = LoggerFactory.getLogger(MeasureEvaluation.class);
070
071        private final Interval measurementPeriod;
072        private final DaoRegistry registry;
073
074        public MeasureEvaluation(DaoRegistry registry, Interval measurementPeriod) {
075                this.registry = registry;
076                this.measurementPeriod = measurementPeriod;
077        }
078
079        public MeasureReport evaluatePatientMeasure(Measure measure, Context context, String patientId, RequestDetails theRequestDetails) {
080                logger.info("Generating individual report");
081
082                if (patientId == null) {
083                        return evaluatePopulationMeasure(measure, context, theRequestDetails);
084                }
085
086                Patient patient = registry.getResourceDao(Patient.class).read(new IdType(patientId), theRequestDetails);
087                // Iterable<Object> patientRetrieve = provider.retrieve("Patient", "id",
088                // patientId, "Patient", null, null, null, null, null, null, null, null);
089                // Patient patient = null;
090        // if (patientRetrieve.iterator().hasNext()) {
091        // patient = (Patient) patientRetrieve.iterator().next();
092        // }
093
094
095
096        boolean isSingle = true;
097
098
099                return evaluate(measure, context,
100                patient == null ? Collections.emptyList() : Collections.singletonList(patient),
101                MeasureReport.MeasureReportType.INDIVIDUAL, isSingle);
102    }
103
104        public MeasureReport evaluatePatientListMeasure(Measure measure, Context context, String practitionerRef, RequestDetails theRequestDetails) {
105                logger.info("Generating patient-list report");
106
107                List<Patient> patients = practitionerRef == null ? getAllPatients(theRequestDetails) : getPractitionerPatients(practitionerRef, theRequestDetails);
108                boolean isSingle = false;
109                return evaluate(measure, context, patients, MeasureReport.MeasureReportType.PATIENTLIST, isSingle);
110        }
111
112        private List<Patient> getPractitionerPatients(String practitionerRef, RequestDetails theRequestDetails) {
113                SearchParameterMap map = SearchParameterMap.newSynchronous();
114                map.add("general-practitioner", new ReferenceParam(
115                        practitionerRef.startsWith("Practitioner/") ? practitionerRef : "Practitioner/" + practitionerRef));
116
117                List<Patient> patients = new ArrayList<>();
118                IBundleProvider patientProvider = registry.getResourceDao("Patient").search(map, theRequestDetails);
119                List<IBaseResource> patientList = patientProvider.getAllResources();
120                patientList.forEach(x -> patients.add((Patient) x));
121                return patients;
122        }
123
124        private List<Patient> getAllPatients(RequestDetails theRequestDetails) {
125                List<Patient> patients = new ArrayList<>();
126                IBundleProvider patientProvider = registry.getResourceDao("Patient").search(SearchParameterMap.newSynchronous(), theRequestDetails);
127                List<IBaseResource> patientList = patientProvider.getAllResources();
128                patientList.forEach(x -> patients.add((Patient) x));
129                return patients;
130        }
131
132        public MeasureReport evaluatePopulationMeasure(Measure measure, Context context, RequestDetails theRequestDetails) {
133                logger.info("Generating summary report");
134
135                boolean isSingle = false;
136                return evaluate(measure, context, getAllPatients(theRequestDetails), MeasureReport.MeasureReportType.SUMMARY, isSingle);
137        }
138
139    @SuppressWarnings("unchecked")
140    private void clearExpressionCache(Context context) {
141        // Hack to clear expression cache
142        // See cqf-ruler github issue #153
143        try {
144            Field privateField = Context.class.getDeclaredField("expressions");
145            privateField.setAccessible(true);
146            LinkedHashMap<String, Object> expressions = (LinkedHashMap<String, Object>) privateField.get(context);
147            expressions.clear();
148
149        } catch (Exception e) {
150            logger.warn("Error resetting expression cache", e);
151        }
152    }
153
154    private Resource evaluateObservationCriteria(Context context, Patient patient, Resource resource, Measure.MeasureGroupPopulationComponent pop, MeasureReport report) {
155        if (pop == null || !pop.hasCriteria()) {
156            return null;
157        }
158
159        context.setContextValue("Patient", patient.getIdElement().getIdPart());
160
161        clearExpressionCache(context);
162
163        String observationName = pop.getCriteria();
164        ExpressionDef ed = context.resolveExpressionRef(observationName);
165        if (!(ed instanceof FunctionDef)) {
166            throw new IllegalArgumentException(Msg.code(1648) + String.format("Measure observation %s does not reference a function definition", observationName));
167        }
168
169        Object result = null;
170        context.pushWindow();
171        try {
172            context.push(new Variable().withName(((FunctionDef)ed).getOperand().get(0).getName()).withValue(resource));
173            result = ed.getExpression().evaluate(context);
174        }
175        finally {
176            context.popWindow();
177        }
178
179        if (result instanceof Resource) {
180            return (Resource)result;
181        }
182
183        Observation obs = new Observation();
184        obs.setStatus(Observation.ObservationStatus.FINAL);
185        obs.setId(UUID.randomUUID().toString());
186        CodeableConcept cc = new CodeableConcept();
187        cc.setText(observationName);
188        obs.setCode(cc);
189        Extension obsExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/cqf-measureInfo");
190        Extension extExtMeasure = new Extension()
191                .setUrl("measure")
192                .setValue(new UriType("http://hl7.org/fhir/us/cqfmeasures/" + report.getMeasure()));
193        obsExtension.addExtension(extExtMeasure);
194        Extension extExtPop = new Extension()
195                .setUrl("populationId")
196                .setValue(new StringType(observationName));
197        obsExtension.addExtension(extExtPop);
198        obs.addExtension(obsExtension);
199        return obs;
200    }
201
202    @SuppressWarnings("unchecked")
203    private Iterable<Resource> evaluateCriteria(Context context, Patient patient,
204            Measure.MeasureGroupPopulationComponent pop) {
205        if (pop == null || !pop.hasCriteria()) {
206            return Collections.emptyList();
207        }
208
209        context.setContextValue("Patient", patient.getIdElement().getIdPart());
210
211        clearExpressionCache(context);
212
213        Object result = context.resolveExpressionRef(pop.getCriteria()).evaluate(context);
214        if (result == null) {
215            return Collections.emptyList();
216        }
217
218        if (result instanceof Boolean) {
219            if (((Boolean) result)) {
220                return Collections.singletonList(patient);
221            } else {
222                return Collections.emptyList();
223            }
224        }
225
226        return (Iterable<Resource>) result;
227    }
228
229    private boolean evaluatePopulationCriteria(Context context, Patient patient,
230            Measure.MeasureGroupPopulationComponent criteria, HashMap<String, Resource> population,
231            HashMap<String, Patient> populationPatients, Measure.MeasureGroupPopulationComponent exclusionCriteria,
232            HashMap<String, Resource> exclusionPopulation, HashMap<String, Patient> exclusionPatients) {
233        boolean inPopulation = false;
234        if (criteria != null) {
235            for (Resource resource : evaluateCriteria(context, patient, criteria)) {
236                inPopulation = true;
237                population.put(resource.getIdElement().getIdPart(), resource);
238            }
239        }
240
241        if (inPopulation) {
242            // Are they in the exclusion?
243            if (exclusionCriteria != null) {
244                for (Resource resource : evaluateCriteria(context, patient, exclusionCriteria)) {
245                    inPopulation = false;
246                    exclusionPopulation.put(resource.getIdElement().getIdPart(), resource);
247                    population.remove(resource.getIdElement().getIdPart());
248                }
249            }
250        }
251
252        if (inPopulation && populationPatients != null) {
253            populationPatients.put(patient.getIdElement().getIdPart(), patient);
254        }
255        if (!inPopulation && exclusionPatients != null) {
256            exclusionPatients.put(patient.getIdElement().getIdPart(), patient);
257        }
258
259        return inPopulation;
260    }
261
262    private void addPopulationCriteriaReport(MeasureReport report,
263            MeasureReport.MeasureReportGroupComponent reportGroup,
264            Measure.MeasureGroupPopulationComponent populationCriteria, int populationCount,
265            Iterable<Patient> patientPopulation) {
266        if (populationCriteria != null) {
267            MeasureReport.MeasureReportGroupPopulationComponent populationReport = new MeasureReport.MeasureReportGroupPopulationComponent();
268            populationReport.setIdentifier(populationCriteria.getIdentifier());
269            populationReport.setCode(populationCriteria.getCode());
270            if (report.getType() == MeasureReport.MeasureReportType.PATIENTLIST && patientPopulation != null) {
271                ListResource subjectList = new ListResource();
272                subjectList.setId(UUID.randomUUID().toString());
273                populationReport.setPatients(new Reference().setReference("#" + subjectList.getId()));
274                for (Patient patient : patientPopulation) {
275                    ListResource.ListEntryComponent entry = new ListResource.ListEntryComponent()
276                            .setItem(new Reference()
277                                    .setReference(patient.getIdElement().getIdPart().startsWith("Patient/")
278                                            ? patient.getIdElement().getIdPart()
279                                            : String.format("Patient/%s", patient.getIdElement().getIdPart()))
280                                    .setDisplay(patient.getNameFirstRep().getNameAsSingleString()));
281                    subjectList.addEntry(entry);
282                }
283                report.addContained(subjectList);
284            }
285            populationReport.setCount(populationCount);
286            reportGroup.addPopulation(populationReport);
287        }
288    }
289
290    private MeasureReport evaluate(Measure measure, Context context, List<Patient> patients,
291            MeasureReport.MeasureReportType type, boolean isSingle) {
292        MeasureReportBuilder reportBuilder = new MeasureReportBuilder();
293        reportBuilder.buildStatus("complete");
294        reportBuilder.buildType(type);
295        reportBuilder.buildMeasureReference(measure.getIdElement().getValue());
296        if (type == MeasureReport.MeasureReportType.INDIVIDUAL && !patients.isEmpty()) {
297            reportBuilder.buildPatientReference(patients.get(0).getIdElement().getValue());
298        }
299        reportBuilder.buildPeriod(measurementPeriod);
300
301        MeasureReport report = reportBuilder.build();
302
303        HashMap<String, Resource> resources = new HashMap<>();
304        HashMap<String, HashSet<String>> codeToResourceMap = new HashMap<>();
305        Set<String> evaluatedResourcesList = new HashSet<>();
306
307        MeasureScoring measureScoring = MeasureScoring.fromCode(measure.getScoring().getCodingFirstRep().getCode());
308        if (measureScoring == null) {
309            throw new RuntimeException(Msg.code(1649) + "Measure scoring is required in order to calculate.");
310        }
311
312        List<Measure.MeasureSupplementalDataComponent> sde = new ArrayList<>();
313        HashMap<String, HashMap<String, Integer>> sdeAccumulators = null;
314        for (Measure.MeasureGroupComponent group : measure.getGroup()) {
315            MeasureReport.MeasureReportGroupComponent reportGroup = new MeasureReport.MeasureReportGroupComponent();
316            reportGroup.setIdentifier(group.getIdentifier());
317            report.getGroup().add(reportGroup);
318
319            // Declare variables to avoid a hash lookup on every patient
320            // TODO: Isn't quite right, there may be multiple initial populations for a
321            // ratio measure...
322            Measure.MeasureGroupPopulationComponent initialPopulationCriteria = null;
323            Measure.MeasureGroupPopulationComponent numeratorCriteria = null;
324            Measure.MeasureGroupPopulationComponent numeratorExclusionCriteria = null;
325            Measure.MeasureGroupPopulationComponent denominatorCriteria = null;
326            Measure.MeasureGroupPopulationComponent denominatorExclusionCriteria = null;
327            Measure.MeasureGroupPopulationComponent denominatorExceptionCriteria = null;
328            Measure.MeasureGroupPopulationComponent measurePopulationCriteria = null;
329            Measure.MeasureGroupPopulationComponent measurePopulationExclusionCriteria = null;
330            // TODO: Isn't quite right, there may be multiple measure observations...
331            Measure.MeasureGroupPopulationComponent measureObservationCriteria = null;
332
333            HashMap<String, Resource> initialPopulation = null;
334            HashMap<String, Resource> numerator = null;
335            HashMap<String, Resource> numeratorExclusion = null;
336            HashMap<String, Resource> denominator = null;
337            HashMap<String, Resource> denominatorExclusion = null;
338            HashMap<String, Resource> denominatorException = null;
339            HashMap<String, Resource> measurePopulation = null;
340            HashMap<String, Resource> measurePopulationExclusion = null;
341            HashMap<String, Resource> measureObservation = null;
342
343            HashMap<String, Patient> initialPopulationPatients = null;
344            HashMap<String, Patient> numeratorPatients = null;
345            HashMap<String, Patient> numeratorExclusionPatients = null;
346            HashMap<String, Patient> denominatorPatients = null;
347            HashMap<String, Patient> denominatorExclusionPatients = null;
348            HashMap<String, Patient> denominatorExceptionPatients = null;
349            HashMap<String, Patient> measurePopulationPatients = null;
350            HashMap<String, Patient> measurePopulationExclusionPatients = null;
351
352            sdeAccumulators = new HashMap<>();
353            sde = measure.getSupplementalData();
354            for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) {
355                MeasurePopulationType populationType = MeasurePopulationType
356                        .fromCode(pop.getCode().getCodingFirstRep().getCode());
357                if (populationType != null) {
358                    switch (populationType) {
359                        case INITIALPOPULATION:
360                            initialPopulationCriteria = pop;
361                            initialPopulation = new HashMap<String, Resource>();
362                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
363                                initialPopulationPatients = new HashMap<String, Patient>();
364                            }
365                            break;
366                        case NUMERATOR:
367                            numeratorCriteria = pop;
368                            numerator = new HashMap<String, Resource>();
369                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
370                                numeratorPatients = new HashMap<String, Patient>();
371                            }
372                            break;
373                        case NUMERATOREXCLUSION:
374                            numeratorExclusionCriteria = pop;
375                            numeratorExclusion = new HashMap<String, Resource>();
376                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
377                                numeratorExclusionPatients = new HashMap<String, Patient>();
378                            }
379                            break;
380                        case DENOMINATOR:
381                            denominatorCriteria = pop;
382                            denominator = new HashMap<String, Resource>();
383                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
384                                denominatorPatients = new HashMap<String, Patient>();
385                            }
386                            break;
387                        case DENOMINATOREXCLUSION:
388                            denominatorExclusionCriteria = pop;
389                            denominatorExclusion = new HashMap<String, Resource>();
390                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
391                                denominatorExclusionPatients = new HashMap<String, Patient>();
392                            }
393                            break;
394                        case DENOMINATOREXCEPTION:
395                            denominatorExceptionCriteria = pop;
396                            denominatorException = new HashMap<String, Resource>();
397                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
398                                denominatorExceptionPatients = new HashMap<String, Patient>();
399                            }
400                            break;
401                        case MEASUREPOPULATION:
402                            measurePopulationCriteria = pop;
403                            measurePopulation = new HashMap<String, Resource>();
404                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
405                                measurePopulationPatients = new HashMap<String, Patient>();
406                            }
407                            break;
408                        case MEASUREPOPULATIONEXCLUSION:
409                            measurePopulationExclusionCriteria = pop;
410                            measurePopulationExclusion = new HashMap<String, Resource>();
411                            if (type == MeasureReport.MeasureReportType.PATIENTLIST) {
412                                measurePopulationExclusionPatients = new HashMap<String, Patient>();
413                            }
414                            break;
415                        case MEASUREOBSERVATION:
416                            measureObservationCriteria = pop;
417                            measureObservation = new HashMap<>();
418                            break;
419                    }
420                }
421            }
422
423            switch (measureScoring) {
424                case PROPORTION:
425                case RATIO: {
426
427                    // For each patient in the initial population
428                    for (Patient patient : patients) {
429
430                        // Are they in the initial population?
431                        boolean inInitialPopulation = evaluatePopulationCriteria(context, patient,
432                                initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null,
433                                null);
434                        populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources,
435                                codeToResourceMap);
436
437                        if (inInitialPopulation) {
438                            // Are they in the denominator?
439                            boolean inDenominator = evaluatePopulationCriteria(context, patient, denominatorCriteria,
440                                    denominator, denominatorPatients, denominatorExclusionCriteria,
441                                    denominatorExclusion, denominatorExclusionPatients);
442                            populateResourceMap(context, MeasurePopulationType.DENOMINATOR, resources,
443                                    codeToResourceMap);
444
445                            if (inDenominator) {
446                                // Are they in the numerator?
447                                boolean inNumerator = evaluatePopulationCriteria(context, patient, numeratorCriteria,
448                                        numerator, numeratorPatients, numeratorExclusionCriteria, numeratorExclusion,
449                                        numeratorExclusionPatients);
450                                populateResourceMap(context, MeasurePopulationType.NUMERATOR, resources,
451                                        codeToResourceMap);
452
453                                if (!inNumerator && inDenominator && (denominatorExceptionCriteria != null)) {
454                                    // Are they in the denominator exception?
455                                    boolean inException = false;
456                                    for (Resource resource : evaluateCriteria(context, patient,
457                                            denominatorExceptionCriteria)) {
458                                        inException = true;
459                                        denominatorException.put(resource.getIdElement().getIdPart(), resource);
460                                        denominator.remove(resource.getIdElement().getIdPart());
461                                        populateResourceMap(context, MeasurePopulationType.DENOMINATOREXCEPTION,
462                                                resources, codeToResourceMap);
463                                    }
464                                    if (inException) {
465                                        if (denominatorExceptionPatients != null) {
466                                            denominatorExceptionPatients.put(patient.getIdElement().getIdPart(),
467                                                    patient);
468                                        }
469                                        if (denominatorPatients != null) {
470                                            denominatorPatients.remove(patient.getIdElement().getIdPart());
471                                        }
472                                    }
473                                }
474                            }
475                        }
476                        populateSDEAccumulators(measure, context, patient, sdeAccumulators, sde);
477                    }
478
479                    // Calculate actual measure score, Count(numerator) / Count(denominator)
480                    if (denominator != null && numerator != null && denominator.size() > 0) {
481                        reportGroup.setMeasureScore(numerator.size() / (double) denominator.size());
482                    }
483
484                    break;
485                }
486                case CONTINUOUSVARIABLE: {
487
488                    // For each patient in the patient list
489                    for (Patient patient : patients) {
490
491                        // Are they in the initial population?
492                        boolean inInitialPopulation = evaluatePopulationCriteria(context, patient,
493                                initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null,
494                                null);
495                        populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources,
496                                codeToResourceMap);
497
498                        if (inInitialPopulation) {
499                            // Are they in the measure population?
500                            boolean inMeasurePopulation = evaluatePopulationCriteria(context, patient,
501                                    measurePopulationCriteria, measurePopulation, measurePopulationPatients,
502                                    measurePopulationExclusionCriteria, measurePopulationExclusion,
503                                    measurePopulationExclusionPatients);
504
505                            if (inMeasurePopulation) {
506                                for (Resource resource : measurePopulation.values()) {
507                                    Resource observation = evaluateObservationCriteria(context, patient, resource, measureObservationCriteria, report);
508                                    measureObservation.put(resource.getIdElement().getIdPart(), observation);
509                                    report.addContained(observation);
510                                    // TODO: Add to the evaluatedResources bundle
511                                    //report.getEvaluatedResources().add(new Reference("#" + observation.getId()));
512                                }
513                            }
514                        }
515                        populateSDEAccumulators(measure, context, patient, sdeAccumulators,sde);
516                    }
517
518                    break;
519                }
520                case COHORT: {
521
522                    // For each patient in the patient list
523                    for (Patient patient : patients) {
524                        evaluatePopulationCriteria(context, patient,
525                                initialPopulationCriteria, initialPopulation, initialPopulationPatients, null, null,
526                                null);
527                        populateResourceMap(context, MeasurePopulationType.INITIALPOPULATION, resources,
528                                codeToResourceMap);
529                        populateSDEAccumulators(measure, context, patient, sdeAccumulators, sde);
530                    }
531
532                    break;
533                }
534            }
535
536            // Add population reports for each group
537            addPopulationCriteriaReport(report, reportGroup, initialPopulationCriteria,
538                    initialPopulation != null ? initialPopulation.size() : 0,
539                    initialPopulationPatients != null ? initialPopulationPatients.values() : null);
540            addPopulationCriteriaReport(report, reportGroup, numeratorCriteria,
541                    numerator != null ? numerator.size() : 0,
542                    numeratorPatients != null ? numeratorPatients.values() : null);
543            addPopulationCriteriaReport(report, reportGroup, numeratorExclusionCriteria,
544                    numeratorExclusion != null ? numeratorExclusion.size() : 0,
545                    numeratorExclusionPatients != null ? numeratorExclusionPatients.values() : null);
546            addPopulationCriteriaReport(report, reportGroup, denominatorCriteria,
547                    denominator != null ? denominator.size() : 0,
548                    denominatorPatients != null ? denominatorPatients.values() : null);
549            addPopulationCriteriaReport(report, reportGroup, denominatorExclusionCriteria,
550                    denominatorExclusion != null ? denominatorExclusion.size() : 0,
551                    denominatorExclusionPatients != null ? denominatorExclusionPatients.values() : null);
552            addPopulationCriteriaReport(report, reportGroup, denominatorExceptionCriteria,
553                    denominatorException != null ? denominatorException.size() : 0,
554                    denominatorExceptionPatients != null ? denominatorExceptionPatients.values() : null);
555            addPopulationCriteriaReport(report, reportGroup, measurePopulationCriteria,
556                    measurePopulation != null ? measurePopulation.size() : 0,
557                    measurePopulationPatients != null ? measurePopulationPatients.values() : null);
558            addPopulationCriteriaReport(report, reportGroup, measurePopulationExclusionCriteria,
559                    measurePopulationExclusion != null ? measurePopulationExclusion.size() : 0,
560                    measurePopulationExclusionPatients != null ? measurePopulationExclusionPatients.values() : null);
561            // TODO: Measure Observations...
562        }
563
564        for (String key : codeToResourceMap.keySet()) {
565            org.hl7.fhir.dstu3.model.ListResource list = new org.hl7.fhir.dstu3.model.ListResource();
566            for (String element : codeToResourceMap.get(key)) {
567                org.hl7.fhir.dstu3.model.ListResource.ListEntryComponent comp = new org.hl7.fhir.dstu3.model.ListResource.ListEntryComponent();
568                comp.setItem(new Reference('#' + element));
569                list.addEntry(comp);
570            }
571
572            if (!list.isEmpty()) {
573                                        list.setId("List/" + UUID.randomUUID());
574                                        list.setTitle(key);
575                resources.put(list.getId(), list);
576                list.getEntry().forEach(listResource -> evaluatedResourcesList.add(listResource.getItem().getReference()));
577            }
578        }
579
580        if (!resources.isEmpty()) {
581            List<Reference> evaluatedResourceIds = new ArrayList<>();
582            evaluatedResourcesList.forEach((resource) -> {
583                evaluatedResourceIds.add(new Reference(resource));
584            });
585        }
586
587        if (sdeAccumulators.size() > 0) {
588            report = processAccumulators(report, sdeAccumulators, sde, isSingle, patients);
589        }
590
591        return report;
592    }
593
594    private void populateSDEAccumulators(Measure measure, Context context, Patient patient,HashMap<String, HashMap<String, Integer>> sdeAccumulators,
595                                         List<Measure.MeasureSupplementalDataComponent> sde){
596        context.setContextValue("Patient", patient.getIdElement().getIdPart());
597        List<Object> sdeList = sde.stream().map(sdeItem -> context.resolveExpressionRef(sdeItem.getCriteria()).evaluate(context)).collect(Collectors.toList());
598        if(!sdeList.isEmpty()) {
599            for (int i = 0; i < sdeList.size(); i++) {
600                Object sdeListItem = sdeList.get(i);
601                if(null != sdeListItem) {
602                    String sdeAccumulatorKey = sde.get(i).getId();
603                    if(null == sdeAccumulatorKey || sdeAccumulatorKey.length() < 1){
604                        sdeAccumulatorKey = sde.get(i).getCriteria();
605                    }
606                    HashMap<String, Integer> sdeItemMap = sdeAccumulators.get(sdeAccumulatorKey);
607                    String code = "";
608
609                    switch (sdeListItem.getClass().getSimpleName()) {
610                        case "Code":
611                            code = ((Code) sdeListItem).getCode();
612                            break;
613                        case "ArrayList":
614                            if (((ArrayList<?>) sdeListItem).size() > 0) {
615                                                                                if (((ArrayList<?>) sdeListItem).get(0).getClass().getSimpleName().equals("Coding")) {
616                                                                                                code  = ((Coding) ((ArrayList<?>) sdeListItem).get(0)).getCode();
617                                                                                } else {
618                                                                                                continue;
619                                                                                }
620                            }else{
621                                continue;
622                            }
623                            break;
624                    }
625                    if(null == code){
626                        continue;
627                    }
628                    if (null != sdeItemMap && null != sdeItemMap.get(code)) {
629                        Integer sdeItemValue = sdeItemMap.get(code);
630                        sdeItemValue++;
631                        sdeItemMap.put(code, sdeItemValue);
632                        sdeAccumulators.get(sdeAccumulatorKey).put(code, sdeItemValue);
633                    } else {
634                        if (null == sdeAccumulators.get(sdeAccumulatorKey)) {
635                            HashMap<String, Integer> newSDEItem = new HashMap<>();
636                            newSDEItem.put(code, 1);
637                            sdeAccumulators.put(sdeAccumulatorKey, newSDEItem);
638                        } else {
639                            sdeAccumulators.get(sdeAccumulatorKey).put(code, 1);
640                        }
641                    }
642                }
643            }
644        }
645    }
646
647    private MeasureReport processAccumulators(MeasureReport report, HashMap<String, HashMap<String, Integer>> sdeAccumulators,
648                                              List<Measure.MeasureSupplementalDataComponent> sde, boolean isSingle, List<Patient> patients){
649        List<Reference> newRefList = new ArrayList<>();
650        sdeAccumulators.forEach((sdeKey, sdeAccumulator) -> {
651            sdeAccumulator.forEach((sdeAccumulatorKey, sdeAccumulatorValue)->{
652                Observation obs = new Observation();
653                obs.setStatus(Observation.ObservationStatus.FINAL);
654                obs.setId(UUID.randomUUID().toString());
655                Coding valueCoding = new Coding();
656                if(sdeKey.equalsIgnoreCase("sde-sex")){
657                    valueCoding.setCode(sdeAccumulatorKey);
658                }else {
659                                                  String coreCategory = sdeKey.substring(sdeKey.lastIndexOf('-') >= 0 ? sdeKey.lastIndexOf('-') : 0);
660                    patients.forEach((pt)-> {
661                        pt.getExtension().forEach((ptExt) -> {
662                            if (ptExt.getUrl().contains(coreCategory)) {
663                                String code = ((Coding) ptExt.getExtension().get(0).getValue()).getCode();
664                                if(code.equalsIgnoreCase(sdeAccumulatorKey)) {
665                                    valueCoding.setSystem(((Coding) ptExt.getExtension().get(0).getValue()).getSystem());
666                                    valueCoding.setCode(code);
667                                    valueCoding.setDisplay(((Coding) ptExt.getExtension().get(0).getValue()).getDisplay());
668                                }
669                            }
670                        });
671                    });
672                }
673                CodeableConcept obsCodeableConcept = new CodeableConcept();
674                Extension obsExtension = new Extension().setUrl("http://hl7.org/fhir/StructureDefinition/cqf-measureInfo");
675                Extension extExtMeasure = new Extension()
676                        .setUrl("measure")
677                        .setValue(new StringType("http://hl7.org/fhir/us/cqfmeasures/" + report.getMeasure()));
678                obsExtension.addExtension(extExtMeasure);
679                Extension extExtPop = new Extension()
680                        .setUrl("populationId")
681                        .setValue(new StringType(sdeKey));
682                obsExtension.addExtension(extExtPop);
683                obs.addExtension(obsExtension);
684                obs.setValue(new Quantity(sdeAccumulatorValue));
685                if(!isSingle) {
686                    valueCoding.setCode(sdeAccumulatorKey);
687                    obsCodeableConcept.setCoding(Collections.singletonList(valueCoding));
688                    obs.setCode(obsCodeableConcept);
689                }else{
690                    obs.setCode(new CodeableConcept().setText(sdeKey));
691                    obsCodeableConcept.setCoding(Collections.singletonList(valueCoding));
692                    obs.setValue(obsCodeableConcept);
693                }
694                newRefList.add(new Reference("#" + obs.getId()));
695                report.addContained(obs);
696            });
697        });
698
699        // TODO: Evaluated resources
700        // newRefList.addAll(report.getEvaluatedResource());
701        // report.setEvaluatedResource(newRefList);
702        return report;
703    }
704
705    private void populateResourceMap(Context context, MeasurePopulationType type, HashMap<String, Resource> resources,
706            HashMap<String, HashSet<String>> codeToResourceMap) {
707        if (context.getEvaluatedResources().isEmpty()) {
708            return;
709        }
710
711        if (!codeToResourceMap.containsKey(type.toCode())) {
712            codeToResourceMap.put(type.toCode(), new HashSet<>());
713        }
714
715        HashSet<String> codeHashSet = codeToResourceMap.get((type.toCode()));
716
717        for (Object o : context.getEvaluatedResources()) {
718            if (o instanceof Resource) {
719                                        Resource r = (Resource) o;
720                                        String id = (r.getIdElement().getResourceType() != null ? (r.getIdElement().getResourceType() + "/")
721                                                : "") + r.getIdElement().getIdPart();
722                                        codeHashSet.add(id);
723
724                                        if (!resources.containsKey(id)) {
725                                                resources.put(id, r);
726                                        }
727                                }
728        }
729
730        context.clearEvaluatedResources();
731    }
732}