diff --git a/chpl/chpl-resources/src/main/resources/environment.properties b/chpl/chpl-resources/src/main/resources/environment.properties index d853ab650f..4141afdde2 100644 --- a/chpl/chpl-resources/src/main/resources/environment.properties +++ b/chpl/chpl-resources/src/main/resources/environment.properties @@ -366,10 +366,10 @@ realWorldTestingCriteriaKeys={2021: 'criterion.170_315_b_1_old,\ criterion.170_315_g_7,\ criterion.170_315_g_8,\ criterion.170_315_g_9_cures,\ + criterion.170_315_g_10,\ criterion.170_315_g_31,\ criterion.170_315_g_32,\ criterion.170_315_g_33,\ - criterion.170_315_g_10,\ criterion.170_315_h_1,\ criterion.170_315_h_2,\ criterion.170_315_j_20,\ diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java index 0482168218..dd8d398a15 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java @@ -6,6 +6,7 @@ private FeatureList() { public static final String CMS_DISABLED = "cms-disabled"; public static final String DEMOGRAPHIC_CHANGE_REQUEST = "demographic-change-request"; + public static final String HTI_5_ERD = "hti-5-erd"; public static final String INSIGHTS_DISPLAY = "insights-display"; public static final String SERVICE_BASE_URL_LIST_CHANGE_REQUEST = "sbul-change-request"; public static final String RWT_CHANGE_REQUEST = "rwt-change-request"; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java index 5072e08ee1..475f3c0fe2 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/domain/RealWorldTestingReport.java @@ -1,17 +1,13 @@ package gov.healthit.chpl.realworldtesting.domain; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; - -import gov.healthit.chpl.util.DateUtil; +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data @Builder @@ -34,57 +30,16 @@ public class RealWorldTestingReport { private LocalDate rwtResultsCheckDate; private String rwtPlansMessage; private String rwtResultsMessage; - private Boolean attestsG7; - private Boolean attestsG9; - private Boolean attestsG10; - - public List toListOfStrings() { - List results = new ArrayList(); - results.add(acbName); - results.add(chplProductNumber); - results.add(currentStatus); - results.add(DateUtil.format(certificationDate)); - results.add(productName); - results.add(productId == null ? null : productId.toString()); - results.add(developerName); - results.add(developerId == null ? null : developerId.toString()); - results.add(!CollectionUtils.isEmpty(developerUsers) ? developerUsers.stream().collect(Collectors.joining("; ")) : ""); - results.add(rwtEligibilityYear == null ? null : rwtEligibilityYear.toString()); - results.add(BooleanUtils.isTrue(ics) ? "Yes" : ""); - results.add(rwtPlansUrl); - results.add(rwtPlansCheckDate == null ? null : rwtPlansCheckDate.toString()); - results.add(rwtResultsUrl); - results.add(rwtResultsCheckDate == null ? null : rwtResultsCheckDate.toString()); - results.add(rwtPlansMessage); - results.add(rwtResultsMessage); - results.add(BooleanUtils.isTrue(attestsG7) ? "TRUE" : "FALSE"); - results.add(BooleanUtils.isTrue(attestsG9) ? "TRUE" : "FALSE"); - results.add(BooleanUtils.isTrue(attestsG10) ? "TRUE" : "FALSE"); - return results; - } + private List criterionAndSvapData; - public static List getHeaders() { - List results = new ArrayList(); - results.add("ONC-ACB Name"); - results.add("CHPL ID"); - results.add("Current Status"); - results.add("Certification Date"); - results.add("Product Name"); - results.add("Product DBID"); - results.add("Developer Name"); - results.add("Developer DBID"); - results.add("Developer Users"); - results.add("Initial RWT Year"); - results.add("ICS"); - results.add("RWT Plans URL"); - results.add("RWT Plans Submission Confirmed"); - results.add("RWT Results URL"); - results.add("RWT Results Submission Confirmed"); - results.add("RWT Plans Message"); - results.add("RWT Results Message"); - results.add("170.315 (g)(7)"); - results.add("170.315 (g)(9)"); - results.add("170.315 (g)(10)"); - return results; + @Data + @NoArgsConstructor + @AllArgsConstructor + @Builder + public static class CriterionAndSvapData { + private CertificationCriterion criterion; + private boolean isAttested; + private boolean isGCriterion; + private boolean usesSvap; } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java index f56c77c553..a10d3cec91 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/realworldtesting/manager/RealWorldTestingReportService.java @@ -14,9 +14,9 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; -import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.certifiedproduct.service.CertificationStatusEventsService; import gov.healthit.chpl.dao.CertifiedProductDAO; +import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertificationStatusEvent; import gov.healthit.chpl.domain.Developer; import gov.healthit.chpl.domain.auth.User; @@ -25,8 +25,8 @@ import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.permissions.ResourcePermissionsFactory; import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport.CriterionAndSvapData; import gov.healthit.chpl.service.CertificationCriterionService; -import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.service.realworldtesting.RealWorldTestingEligibility; import gov.healthit.chpl.service.realworldtesting.RealWorldTestingEligiblityReason; import gov.healthit.chpl.service.realworldtesting.RealWorldTestingEligiblityService; @@ -41,23 +41,23 @@ public class RealWorldTestingReportService { private ErrorMessageUtil errorMsg; private Environment env; private CertificationStatusEventsService certificationStatusEventsService; + private CertificationCriterionService criteriaService; private RealWorldTestingEligiblityServiceFactory rwtEligServiceFactory; private ResourcePermissionsFactory resourcePermissionsFactory; private List withdrawnStatuses; - private CertificationCriterion g7, g9, g10; @Autowired public RealWorldTestingReportService(CertifiedProductDAO certifiedProductDAO, ErrorMessageUtil errorMsg, Environment env, CertificationStatusEventsService certificationStatusEventsService, CertificationCriterionService criteriaService, - CertificationCriterionService certificationCriterionService, RealWorldTestingEligiblityServiceFactory rwtEligServiceFactory, ResourcePermissionsFactory resourcePermissionsFactory) { this.certifiedProductDAO = certifiedProductDAO; this.errorMsg = errorMsg; this.env = env; + this.criteriaService = criteriaService; this.certificationStatusEventsService = certificationStatusEventsService; this.rwtEligServiceFactory = rwtEligServiceFactory; this.resourcePermissionsFactory = resourcePermissionsFactory; @@ -67,9 +67,6 @@ public RealWorldTestingReportService(CertifiedProductDAO certifiedProductDAO, CertificationStatusType.WithdrawnByDeveloperUnderReview, CertificationStatusType.Retired, CertificationStatusType.TerminatedByOnc); - g7 = criteriaService.get(Criteria2015.G_7); - g9 = criteriaService.get(Criteria2015.G_9_CURES); - g10 = criteriaService.get(Criteria2015.G_10); } public List getRealWorldTestingReports(List acbIds, Logger logger) { @@ -146,9 +143,7 @@ private RealWorldTestingReport getRealWorldTestingReport(CertifiedProductDetails .rwtPlansCheckDate(listing.getRwtPlansCheckDate()) .rwtResultsUrl(listing.getRwtResultsUrl()) .rwtResultsCheckDate(listing.getRwtResultsCheckDate()) - .attestsG7(attestsCriteria(rwtElig, g7)) - .attestsG9(attestsCriteria(rwtElig, g9)) - .attestsG10(attestsCriteria(rwtElig, g10)) + .criterionAndSvapData(buildCriterionAndSvapData(rwtElig.getAttestedCertificationResults())) .build(); if (rwtElig.getEligibilityYear() != null) { @@ -158,11 +153,15 @@ private RealWorldTestingReport getRealWorldTestingReport(CertifiedProductDetails } } - private boolean attestsCriteria(RealWorldTestingEligibility eligibility, CertificationCriterion criterion) { - return eligibility.getAttestedCriteria().stream() - .filter(crit -> crit.getId().equals(criterion.getId())) - .findAny() - .isPresent(); + private List buildCriterionAndSvapData(List certResults) { + return certResults.stream() + .map(certResult -> CriterionAndSvapData.builder() + .criterion(certResult.getCriterion()) + .isAttested(certResult.getSuccess()) + .isGCriterion(criteriaService.isGCriterion(certResult.getCriterion())) + .usesSvap(!CollectionUtils.isEmpty(certResult.getSvaps())) + .build()) + .collect(Collectors.toList()); } private List getDeveloperUsers(Developer developer) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java index a82c19067e..4c42deba92 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingReportEmailJob.java @@ -1,10 +1,7 @@ package gov.healthit.chpl.scheduler.job; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.Charset; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; @@ -12,8 +9,6 @@ import java.util.Objects; import java.util.stream.Collectors; -import org.apache.commons.csv.CSVFormat; -import org.apache.commons.csv.CSVPrinter; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @@ -32,6 +27,7 @@ import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReportSummary; import gov.healthit.chpl.realworldtesting.manager.RealWorldTestingReportService; import gov.healthit.chpl.scheduler.job.realworldtesting.RealWorldTestingReportSummaryCalculator; +import gov.healthit.chpl.scheduler.presenter.RealWorldTestingCsvPresenter; import gov.healthit.chpl.util.NullSafeEvaluator; import lombok.AllArgsConstructor; import lombok.Getter; @@ -50,6 +46,9 @@ public class RealWorldTestingReportEmailJob implements Job { @Autowired private Environment env; + @Autowired + private RealWorldTestingCsvPresenter csvPresenter; + @Autowired private ChplHtmlEmailBuilder chplHtmlEmailBuilder; @@ -103,7 +102,7 @@ private String createHtmlMessage(JobExecutionContext context, List rows) { LOGGER.info("Generating CSV attachment"); File outputFile = getOutputFile(env.getProperty("rwt.report.filename") + LocalDate.now().toString()); - outputFile = writeToFile(rows, outputFile); + writeToFile(rows, outputFile); LOGGER.info("Completed Generating CSV attachment"); return outputFile; } @@ -120,25 +119,8 @@ private File getOutputFile(String reportFilename) { return temp; } - private File writeToFile(List rows, File outputFile) { - try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outputFile), - Charset.forName("UTF-8").newEncoder()); - CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.EXCEL)) { - writer.write('\ufeff'); - csvPrinter.printRecord(RealWorldTestingReport.getHeaders()); - rows.stream() - .forEach(row -> { - try { - csvPrinter.printRecord(row.toListOfStrings()); - } catch (Exception e) { - LOGGER.error(e); - } - - }); - } catch (Exception e) { - LOGGER.error(e); - } - return outputFile; + private void writeToFile(List rows, File outputFile) { + csvPresenter.presentAsFile(rows, outputFile); } private String getAcbNamesAsBrSeparatedList(JobExecutionContext jobContext) { @@ -169,7 +151,7 @@ private String getEmailSummaryParagraph(List rows) { RealWorldTestingReportSummary summary = RealWorldTestingReportSummaryCalculator.calculateSummariesByEligibityYear(rows, year); paragraph.append("Real World Testing for ").append(summary.getRwtEligibilityYear()).append(":
") .append("
    ") - .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalListings(), 0)).append(" Total listing for this period
  • ") + .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalListings(), 0)).append(" Total listing(s) for this period
  • ") .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalWithdrawn(), 0)).append(" Withdrawn, no longer eligible
  • ") .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalActive(), 0)).append(" Active
  • ") .append("
  • ").append(NullSafeEvaluator.eval(() -> summary.getTotalEligibleViaIcs(), 0)).append(" Eligible via ICS
  • ") diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java index 60be011a8f..22039946f5 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java @@ -8,6 +8,7 @@ import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; +import org.ff4j.FF4j; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,8 @@ import org.springframework.transaction.support.TransactionTemplate; import org.springframework.web.context.support.SpringBeanAutowiringSupport; +import gov.healthit.chpl.FeatureList; +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.developer.search.ActiveListingSearchOptions; import gov.healthit.chpl.developer.search.DeveloperSearchRequest; import gov.healthit.chpl.developer.search.DeveloperSearchResult; @@ -30,6 +33,8 @@ import gov.healthit.chpl.report.realworldtesting.RealWorldTestingResultsSummaryReportDao; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryByAcbReport; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryByDeveloperReport; +import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.util.DateUtil; import lombok.extern.log4j.Log4j2; @@ -54,10 +59,22 @@ public class RealWorldTestingSummaryReportCreatorJob extends QuartzJob { @Autowired private PlatformTransactionManager transactionManager; + @Autowired + private FF4j ff4j; + + @Autowired + private CertificationCriterionService criteriaService; + + private CertificationCriterion g7, g9, g10; + @Override public void execute(JobExecutionContext context) throws JobExecutionException { SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); LOGGER.info("********* Starting the Real World Testing Summary Report Creator job *********"); + g7 = criteriaService.get(Criteria2015.G_7); + g9 = criteriaService.get(Criteria2015.G_9_CURES); + g10 = criteriaService.get(Criteria2015.G_10); + try { List activeAcbIds = certificationBodyManager.getAllActive().stream() .map(acb -> acb.getId()) @@ -66,9 +83,8 @@ public void execute(JobExecutionContext context) throws JobExecutionException { List rwtPlansReports = rwtReportService.getRealWorldTestingReports(activeAcbIds, LOGGER).stream() .collect(Collectors.toList()); - //RWT Results requirement is only enforced for listings with g7, g9, or g10 so we only want to report on those List rwtResultReports = rwtPlansReports.stream() - .filter(report -> report.getAttestsG7() || report.getAttestsG9() || report.getAttestsG10()) + .filter(report -> isRwtResultsRequired(report)) .collect(Collectors.toList()); TransactionOperations transactionOperations = new TransactionTemplate(transactionManager, @@ -86,6 +102,24 @@ public void execute(JobExecutionContext context) throws JobExecutionException { } + private boolean isRwtResultsRequired(RealWorldTestingReport rwtReport) { + //RWT Results requirement is only enforced for listings with g7, g9, or g10 pre HTI-5. + //Post HTI-5 listings with any g-criteria or non-g-criteria using svap have the RWT Results requirement. + if (ff4j.check(FeatureList.HTI_5_ERD)) { + return rwtReport.getCriterionAndSvapData().stream() + .filter(item -> item.isAttested() && (item.isGCriterion() || item.isUsesSvap())) + .findAny() + .isPresent(); + } else { + return rwtReport.getCriterionAndSvapData().stream() + .filter(item -> item.getCriterion().getId().equals(g7.getId()) + || item.getCriterion().getId().equals(g9.getId()) + || item.getCriterion().getId().equals(g10.getId())) + .findAny() + .isPresent(); + } + } + private void processRwtResultsCountsByAcb(List reportRows) { Integer rwtEligibilityYear = LocalDate.now().getYear() - 1; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/presenter/RealWorldTestingCsvPresenter.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/presenter/RealWorldTestingCsvPresenter.java new file mode 100644 index 0000000000..2e0c6884d5 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/presenter/RealWorldTestingCsvPresenter.java @@ -0,0 +1,140 @@ +package gov.healthit.chpl.scheduler.presenter; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.lang3.BooleanUtils; +import org.ff4j.FF4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import gov.healthit.chpl.FeatureList; +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.certificationCriteria.CertificationCriterionComparator; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport.CriterionAndSvapData; +import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; +import gov.healthit.chpl.service.realworldtesting.RealWorldTestingCriteriaService; +import gov.healthit.chpl.util.DateUtil; +import gov.healthit.chpl.util.Util; +import lombok.extern.log4j.Log4j2; + +@Component +@Log4j2(topic = "realWorldTestingReportEmailJobLogger") +public class RealWorldTestingCsvPresenter { + private CertificationCriterionService criteriaService; + private List rwtResultsRequiredCriteria; + + @Autowired + public RealWorldTestingCsvPresenter(RealWorldTestingCriteriaService rwtCriteriaService, + CertificationCriterionService criteriaService, + CertificationCriterionComparator criteriaComparator, + FF4j ff4j) { + this.criteriaService = criteriaService; + Integer currentYear = Calendar.getInstance().get(Calendar.YEAR); + if (ff4j.check(FeatureList.HTI_5_ERD)) { + rwtResultsRequiredCriteria = rwtCriteriaService.getEligibleCriteria(currentYear); + } else { + rwtResultsRequiredCriteria = Stream.of(criteriaService.get(Criteria2015.G_7), + criteriaService.get(Criteria2015.G_9_CURES), + criteriaService.get(Criteria2015.G_10)).collect(Collectors.toList()); + } + rwtResultsRequiredCriteria.stream().sorted(criteriaComparator); + } + + public void presentAsFile(List rwtReports, File outputFile) { + try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outputFile), + Charset.forName("UTF-8").newEncoder()); + CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.EXCEL)) { + writer.write('\ufeff'); + csvPrinter.printRecord(generateHeaderValues()); + for (RealWorldTestingReport rwtReport : rwtReports) { + csvPrinter.printRecord(generateRowValue(rwtReport)); + } + } catch (IOException ex) { + LOGGER.error("Could not write file " + outputFile.getName(), ex); + } + } + + protected List generateHeaderValues() { + List results = new ArrayList(); + results.add("ONC-ACB Name"); + results.add("CHPL ID"); + results.add("Current Status"); + results.add("Certification Date"); + results.add("Product Name"); + results.add("Product DBID"); + results.add("Developer Name"); + results.add("Developer DBID"); + results.add("Developer Users"); + results.add("Initial RWT Year"); + results.add("ICS"); + results.add("RWT Plans URL"); + results.add("RWT Plans Submission Confirmed"); + results.add("RWT Results URL"); + results.add("RWT Results Submission Confirmed"); + results.add("RWT Plans Message"); + results.add("RWT Results Message"); + rwtResultsRequiredCriteria.stream() + .forEach(criterion -> { + if (criteriaService.isGCriterion(criterion)) { + results.add(Util.formatCriteriaNumber(criterion)); + } else { + results.add(Util.formatCriteriaNumber(criterion) + "SVAP"); + } + }); + return results; + } + + protected List generateRowValue(RealWorldTestingReport rwtReport) { + List results = new ArrayList(); + results.add(rwtReport.getAcbName()); + results.add(rwtReport.getChplProductNumber()); + results.add(rwtReport.getCurrentStatus()); + results.add(DateUtil.format(rwtReport.getCertificationDate())); + results.add(rwtReport.getProductName()); + results.add(rwtReport.getProductId() == null ? null : rwtReport.getProductId().toString()); + results.add(rwtReport.getDeveloperName()); + results.add(rwtReport.getDeveloperId() == null ? null : rwtReport.getDeveloperId().toString()); + results.add(!CollectionUtils.isEmpty(rwtReport.getDeveloperUsers()) + ? rwtReport.getDeveloperUsers().stream().collect(Collectors.joining("; ")) + : ""); + results.add(rwtReport.getRwtEligibilityYear() == null ? null : rwtReport.getRwtEligibilityYear().toString()); + results.add(BooleanUtils.isTrue(rwtReport.getIcs()) ? "Yes" : ""); + results.add(rwtReport.getRwtPlansUrl()); + results.add(rwtReport.getRwtPlansCheckDate() == null ? null : rwtReport.getRwtPlansCheckDate().toString()); + results.add(rwtReport.getRwtResultsUrl()); + results.add(rwtReport.getRwtResultsCheckDate() == null ? null : rwtReport.getRwtResultsCheckDate().toString()); + results.add(rwtReport.getRwtPlansMessage()); + results.add(rwtReport.getRwtResultsMessage()); + rwtResultsRequiredCriteria.stream() + .forEach(criterion -> results.add(determineCriteriaReportValue(rwtReport.getCriterionAndSvapData(), criterion))); + return results; + } + + private String determineCriteriaReportValue(List criteriaAndSvapData, CertificationCriterion criterion) { + CriterionAndSvapData criterionAndSvapData = criteriaAndSvapData.stream() + .filter(item -> item.getCriterion().getId().equals(criterion.getId())) + .findAny() + .orElse(null); + if (criterionAndSvapData == null) { + return "FALSE"; + } else if (criterionAndSvapData.isGCriterion()) { + return criterionAndSvapData.isAttested() ? "TRUE" : "FALSE"; + } else { + return criterionAndSvapData.isAttested() && criterionAndSvapData.isUsesSvap() ? "TRUE" : "FALSE"; + } + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java index 80d268f065..d4347a9e9c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java @@ -344,6 +344,27 @@ public List getUscdiCriteria() { get(Criteria2015.G_10)); } + public boolean isGCriterion(CertificationCriterion criterion) { + if (criterion.getId().equals(get(Criteria2015.G_1).getId()) + || criterion.getId().equals(get(Criteria2015.G_2).getId()) + || criterion.getId().equals(get(Criteria2015.G_3).getId()) + || criterion.getId().equals(get(Criteria2015.G_4).getId()) + || criterion.getId().equals(get(Criteria2015.G_5).getId()) + || criterion.getId().equals(get(Criteria2015.G_6_OLD).getId()) + || criterion.getId().equals(get(Criteria2015.G_6_CURES).getId()) + || criterion.getId().equals(get(Criteria2015.G_7).getId()) + || criterion.getId().equals(get(Criteria2015.G_8).getId()) + || criterion.getId().equals(get(Criteria2015.G_9_OLD).getId()) + || criterion.getId().equals(get(Criteria2015.G_9_CURES).getId()) + || criterion.getId().equals(get(Criteria2015.G_10).getId()) + || criterion.getId().equals(get(Criteria2015.G_31).getId()) + || criterion.getId().equals(get(Criteria2015.G_32).getId()) + || criterion.getId().equals(get(Criteria2015.G_33).getId())) { + return true; + } + return false; + } + public static class Criteria2015 { public static final String A_1 = "criterion.170_315_a_1"; public static final String A_2 = "criterion.170_315_a_2"; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java index 5d1557bc26..ac4ffcac0c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingCriteriaService.java @@ -50,6 +50,7 @@ private Integer getYearOrMostRecentPastYear(Integer requestedYear) throws Invali private List getRwtEligibleCriteria(List eligibleCriteriaKeys) { return eligibleCriteriaKeys.stream() .map(key -> certificationCriterionService.get(key)) + .filter(criterion -> !criterion.isRemoved()) .collect(Collectors.toList()); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java index 339ec019ec..619e23f03d 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligibility.java @@ -3,7 +3,7 @@ import java.io.Serializable; import java.util.List; -import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.domain.CertificationResult; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -18,5 +18,5 @@ public class RealWorldTestingEligibility implements Serializable { private RealWorldTestingEligiblityReason reason; private Integer eligibilityYear; - private List attestedCriteria; + private List attestedCertificationResults; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java index 0781940b55..de5e83f9ff 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityService.java @@ -11,20 +11,25 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.logging.log4j.Logger; +import org.ff4j.FF4j; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.activity.history.ListingActivityUtil; import gov.healthit.chpl.activity.history.explorer.RealWorldTestingEligibilityActivityExplorer; import gov.healthit.chpl.activity.history.query.RealWorldTestingEligibilityQuery; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.dao.CertifiedProductDAO; +import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertificationStatusEvent; import gov.healthit.chpl.domain.CertifiedProduct; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import gov.healthit.chpl.dto.ActivityDTO; import gov.healthit.chpl.dto.CertifiedProductDTO; import gov.healthit.chpl.exception.EntityRetrievalException; +import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.util.CertificationStatusUtil; import gov.healthit.chpl.util.DateUtil; import lombok.AllArgsConstructor; @@ -41,19 +46,27 @@ public class RealWorldTestingEligiblityService { private RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer; private ListingActivityUtil listingActivityUtil; private CertifiedProductDAO certifiedProductDAO; + private CertificationCriterionService criteriaService; + private FF4j ff4j; private Map memo = new HashMap(); public RealWorldTestingEligiblityService(RealWorldTestingCriteriaService realWorldTestingCriteriaService, RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer, ListingActivityUtil listingActivityUtil, - CertifiedProductDAO certifiedProductDAO, LocalDate rwtProgramStartDate, Integer rwtProgramFirstEligibilityYear) { + CertifiedProductDAO certifiedProductDAO, + LocalDate rwtProgramStartDate, + Integer rwtProgramFirstEligibilityYear, + CertificationCriterionService criteriaService, + FF4j ff4j) { this.realWorldTestingCriteriaService = realWorldTestingCriteriaService; this.realWorldTestingEligibilityActivityExplorer = realWorldTestingEligibilityActivityExplorer; this.listingActivityUtil = listingActivityUtil; this.certifiedProductDAO = certifiedProductDAO; this.rwtProgramStartDate = rwtProgramStartDate; this.rwtProgramFirstEligibilityYear = rwtProgramFirstEligibilityYear; + this.criteriaService = criteriaService; + this.ff4j = ff4j; } public RealWorldTestingEligibility getRwtEligibilityYearForListing(Long listingId, Logger logger) { @@ -113,9 +126,9 @@ private Optional getRwtEligBasedOnStandardRequireme private RealWorldTestingEligibility getRwtEligibility(Optional listing, RealWorldTestingEligiblityReason reason, Integer currentRwtEligYear) { - List attestedCriteria = new ArrayList(); + List attestedCertificationResults = new ArrayList(); if (listing != null && listing.isPresent()) { - attestedCriteria = listing.get().getCertificationResults().stream() + attestedCertificationResults = listing.get().getCertificationResults().stream() //We might be getting this listing in it's original state from saved JSON. //For most of CHPL before mid-2023, we saved a certification result on the listing for each criteria //and used the "success" field to determine if that listing attested to that criterion. @@ -123,13 +136,12 @@ private RealWorldTestingEligibility getRwtEligibility(Optional BooleanUtils.isTrue(certResult.getSuccess())) - .map(certResult -> certResult.getCriterion()) .collect(Collectors.toList()); } return RealWorldTestingEligibility.builder() .reason(reason) .eligibilityYear(currentRwtEligYear) - .attestedCriteria(attestedCriteria) + .attestedCertificationResults(attestedCertificationResults) .build(); } @@ -207,7 +219,6 @@ private boolean isListingRwtEligible(CertifiedProductSearchDetails listing, Loca return isListingStatusActiveAsOfEligibilityDate(listing, asOfDate) && isCertificationDateBeforeEligibilityDate(listing, asOfDate) && doesListingAttestToEligibleCriteria(listing, asOfDate.getYear()); - } private boolean doesListingAttestToEligibleCriteria(CertifiedProductSearchDetails listing, Integer year) { @@ -215,13 +226,20 @@ private boolean doesListingAttestToEligibleCriteria(CertifiedProductSearchDetail return listing.getCertificationResults().stream() .filter(result -> result.getSuccess() && eligibleCriteria.stream() - .filter(crit -> crit.getId().equals(result.getCriterion().getId())) + .filter(crit -> crit.getId().equals(result.getCriterion().getId()) && isGCriteriaOrUsesSvap(result)) .findAny() .isPresent()) .findAny() .isPresent(); } + private boolean isGCriteriaOrUsesSvap(CertificationResult certResult) { + if (ff4j.check(FeatureList.HTI_5_ERD)) { + return criteriaService.isGCriterion(certResult.getCriterion()) || !CollectionUtils.isEmpty(certResult.getSvaps()); + } + return true; + } + private boolean isCertificationDateBeforeEligibilityDate(CertifiedProductSearchDetails listing, LocalDate eligibilityDate) { if (Objects.isNull(listing) || Objects.isNull(listing.getCertificationDate())) { return false; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java index 28a9160c99..dc40a67988 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/realworldtesting/RealWorldTestingEligiblityServiceFactory.java @@ -2,6 +2,7 @@ import java.time.LocalDate; +import org.ff4j.FF4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -9,6 +10,7 @@ import gov.healthit.chpl.activity.history.ListingActivityUtil; import gov.healthit.chpl.activity.history.explorer.RealWorldTestingEligibilityActivityExplorer; import gov.healthit.chpl.dao.CertifiedProductDAO; +import gov.healthit.chpl.service.CertificationCriterionService; @Component public class RealWorldTestingEligiblityServiceFactory { @@ -19,24 +21,31 @@ public class RealWorldTestingEligiblityServiceFactory { @Value("#{T(java.time.LocalDate).parse('${rwtProgramStartDate}')}") private LocalDate rwtProgramStartDate; + private CertificationCriterionService criteriaService; private RealWorldTestingCriteriaService realWorldTestingCriteriaService; private RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer; private ListingActivityUtil listingActivityUtil; private CertifiedProductDAO certifiedProductDAO; + private FF4j ff4j; @Autowired - public RealWorldTestingEligiblityServiceFactory(RealWorldTestingCriteriaService realWorldTestingCriteriaService, + public RealWorldTestingEligiblityServiceFactory(CertificationCriterionService criteriaService, + RealWorldTestingCriteriaService realWorldTestingCriteriaService, RealWorldTestingEligibilityActivityExplorer realWorldTestingEligibilityActivityExplorer, ListingActivityUtil listingActivityUtil, - CertifiedProductDAO certifiedProductDAO) { + CertifiedProductDAO certifiedProductDAO, + FF4j ff4j) { + this.criteriaService = criteriaService; this.realWorldTestingCriteriaService = realWorldTestingCriteriaService; this.realWorldTestingEligibilityActivityExplorer = realWorldTestingEligibilityActivityExplorer; this.listingActivityUtil = listingActivityUtil; this.certifiedProductDAO = certifiedProductDAO; + this.ff4j = ff4j; } public RealWorldTestingEligiblityService getInstance() { return new RealWorldTestingEligiblityService(realWorldTestingCriteriaService, realWorldTestingEligibilityActivityExplorer, - listingActivityUtil, certifiedProductDAO, rwtProgramStartDate, rwtProgramFirstEligibilityYear); + listingActivityUtil, certifiedProductDAO, rwtProgramStartDate, rwtProgramFirstEligibilityYear, + criteriaService, ff4j); } }