From ec231f90f391347b63098fac351f78e052a0a96a Mon Sep 17 00:00:00 2001 From: Todd Young Date: Mon, 16 Dec 2024 22:39:15 -0500 Subject: [PATCH 01/10] feat: initial creation of job to gather rwt data for rwt plans report OCD-4517 --- .../src/main/resources/environment.properties | 2 +- .../src/main/resources/jobs.xml | 9 ++ .../RealWorldTestingReportService.java | 8 +- .../RealWorldTestingPlanSummaryReportDao.java | 73 +++++++++++ ...alWorldTestingPlanSummaryReportEntity.java | 65 ++++++++++ .../RealWorldTestingSummaryReport.java | 31 +++++ ...alWorldTestingSummaryReportCreatorJob.java | 119 ++++++++++++++++++ 7 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java diff --git a/chpl/chpl-resources/src/main/resources/environment.properties b/chpl/chpl-resources/src/main/resources/environment.properties index 94cec072b7..db83d1b760 100644 --- a/chpl/chpl-resources/src/main/resources/environment.properties +++ b/chpl/chpl-resources/src/main/resources/environment.properties @@ -220,7 +220,7 @@ rwtProgramFirstEligibilityYear=2022 # Date user can start entering Plan information - Format is MM/DD rwtPlanStartDayOfYear=09/01 # Date when the Plan is considered late - Format is MM/DD -rwtPlanDueDate=12/15 +rwtPlanDueDate=12/17 # Date user can start entering Results information - Format is MM/DD rwtResultsStartDayOfYear=01/01 # Date when the Results is considered late - Format is MM/DD diff --git a/chpl/chpl-resources/src/main/resources/jobs.xml b/chpl/chpl-resources/src/main/resources/jobs.xml index 0b09ab03ef..d69f2b283b 100644 --- a/chpl/chpl-resources/src/main/resources/jobs.xml +++ b/chpl/chpl-resources/src/main/resources/jobs.xml @@ -921,5 +921,14 @@ true false + + + realWorldTestingSummaryReportCreatorJob + systemJobs + Update Task/Participant Friendly Ids Job + gov.healthit.chpl.scheduler.job.RealWorldTestingSummaryReportCreatorJob + true + false + 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 45942c6dfb..838a3dba0c 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 @@ -249,28 +249,28 @@ private boolean isLocalDateEqualOrAfter(LocalDate date1, LocalDate date2) { return date1.isEqual(date2) || date1.isAfter(date2); } - private LocalDate getPlansStartDate(Integer rwtEligYear) { + public LocalDate getPlansStartDate(Integer rwtEligYear) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); String mmdd = env.getProperty("rwtPlanStartDayOfYear"); String mmddyyyy = mmdd + "/" + String.valueOf(rwtEligYear - 1); return LocalDate.parse(mmddyyyy, formatter); } - private LocalDate getPlansLateDate(Integer rwtEligYear) { + public LocalDate getPlansLateDate(Integer rwtEligYear) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); String mmdd = env.getProperty("rwtPlanDueDate"); String mmddyyyy = mmdd + "/" + String.valueOf(rwtEligYear - 1); return LocalDate.parse(mmddyyyy, formatter); } - private LocalDate getResultsStartDate(Integer rwtEligYear) { + public LocalDate getResultsStartDate(Integer rwtEligYear) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); String mmdd = env.getProperty("rwtResultsStartDayOfYear"); String mmddyyyy = mmdd + "/" + String.valueOf(rwtEligYear + 1); return LocalDate.parse(mmddyyyy, formatter); } - private LocalDate getResultsLateDate(Integer rwtEligYear) { + public LocalDate getResultsLateDate(Integer rwtEligYear) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); String mmdd = env.getProperty("rwtResultsDueDate"); String mmddyyyy = mmdd + "/" + String.valueOf(rwtEligYear + 1); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java new file mode 100644 index 0000000000..f9611fa2b1 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java @@ -0,0 +1,73 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.stereotype.Component; + +import gov.healthit.chpl.dao.impl.BaseDAOImpl; +import gov.healthit.chpl.entity.CertificationBodyEntity; +import gov.healthit.chpl.exception.EntityRetrievalException; +import jakarta.persistence.Query; + +@Component +public class RealWorldTestingPlanSummaryReportDao extends BaseDAOImpl { + + public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) throws EntityRetrievalException { + RealWorldTestingPlanSummaryReportEntity entity = getEntityByCheckedDateAndAcb(realWorldTestingSummaryReport.getCheckedDate(), + realWorldTestingSummaryReport.getCertificationBody().getId()); + if (entity == null) { + entity = RealWorldTestingPlanSummaryReportEntity.builder() + .reportDate(realWorldTestingSummaryReport.getReportDate()) + .realWorldTestingYear(realWorldTestingSummaryReport.getRealWorldTestingYear()) + .certificationBody(CertificationBodyEntity.builder() + .id(realWorldTestingSummaryReport.getCertificationBody().getId()) + .build()) + .checkedDate(realWorldTestingSummaryReport.getCheckedDate()) + .checkedCount(realWorldTestingSummaryReport.getCheckedCount()) + .build(); + + create(entity); + } else { + entity.setCheckedCount(realWorldTestingSummaryReport.getCheckedCount()); + update(entity); + } + } + + private RealWorldTestingPlanSummaryReportEntity getEntity(Long id) throws EntityRetrievalException { + Query query = entityManager.createQuery( + "from RealWorldTestingPlanSummaryReportEntity where (NOT deleted = true) and id = :id", RealWorldTestingPlanSummaryReportEntity.class); + query.setParameter("id", id); + List result = query.getResultList(); + + if (result.size() > 1) { + throw new EntityRetrievalException("Data error. Duplicate id in real_world_testing_plan_summary_report table."); + } + + if (result.size() > 0) { + return result.get(0); + } + return null; + } + + private RealWorldTestingPlanSummaryReportEntity getEntityByCheckedDateAndAcb(LocalDate checkedDate, Long certificationBodyId) throws EntityRetrievalException { + Query query = entityManager.createQuery( + "from RealWorldTestingPlanSummaryReportEntity rwtps " + + "where (NOT deleted = true) " + + "and checkedDate = :checkedDate " + + "and rwtps.certificationBody.id = :certificationBodyId", RealWorldTestingPlanSummaryReportEntity.class); + query.setParameter("checkedDate", checkedDate); + query.setParameter("certificationBodyId", certificationBodyId); + List result = query.getResultList(); + + if (result.size() > 1) { + throw new EntityRetrievalException("Data error. Duplicate checked_date in real_world_testing_plan_summary_report table."); + } + + if (result.size() > 0) { + return result.get(0); + } + return null; + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java new file mode 100644 index 0000000000..c5d44a9ce5 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java @@ -0,0 +1,65 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; + +import gov.healthit.chpl.entity.CertificationBodyEntity; +import gov.healthit.chpl.entity.EntityAudit; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@ToString +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "real_world_testing_plan_summary_report") +public class RealWorldTestingPlanSummaryReportEntity extends EntityAudit { + private static final long serialVersionUID = -1208758504334058893L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "report_date") + private LocalDate reportDate; + + @Column(name = "real_world_testing_year") + private Long realWorldTestingYear; + + @OneToOne(optional = true, fetch = FetchType.LAZY) + @JoinColumn(name = "certification_body_id") + private CertificationBodyEntity certificationBody; + + @Column(name = "checked_date") + private LocalDate checkedDate; + + @Column(name = "checked_count") + private Long checkedCount; + + public RealWorldTestingSummaryReport toDomain() { + return RealWorldTestingSummaryReport.builder() + .id(id) + .reportDate(reportDate) + .realWorldTestingYear(realWorldTestingYear) + .certificationBody(certificationBody.toDomain()) + .checkedDate(checkedDate) + .checkedCount(checkedCount) + .build(); + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java new file mode 100644 index 0000000000..4941e94b68 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java @@ -0,0 +1,31 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import gov.healthit.chpl.domain.CertificationBody; +import gov.healthit.chpl.util.LocalDateDeserializer; +import gov.healthit.chpl.util.LocalDateSerializer; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class RealWorldTestingSummaryReport { + private Long id; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate reportDate; + + private Long realWorldTestingYear; + private CertificationBody certificationBody; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate checkedDate; + + private Long checkedCount; +} 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 new file mode 100644 index 0000000000..7b0d61270f --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java @@ -0,0 +1,119 @@ +package gov.healthit.chpl.scheduler.job; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.web.context.support.SpringBeanAutowiringSupport; + +import gov.healthit.chpl.domain.CertificationBody; +import gov.healthit.chpl.manager.CertificationBodyManager; +import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport; +import gov.healthit.chpl.realworldtesting.manager.RealWorldTestingReportService; +import gov.healthit.chpl.report.realworldtesting.RealWorldTestingPlanSummaryReportDao; +import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryReport; +import gov.healthit.chpl.util.DateUtil; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class RealWorldTestingSummaryReportCreatorJob extends QuartzJob { + + @Autowired + private RealWorldTestingReportService rwtReportService; + + @Autowired + private RealWorldTestingPlanSummaryReportDao realWorldTestingPlanSummaryReportDao; + + @Autowired + private CertificationBodyManager certificationBodyManager; + + @Autowired + private JpaTransactionManager txManager; + + @Override + public void execute(JobExecutionContext context) throws JobExecutionException { + SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this); + LOGGER.info("********* Starting the Real World Report Creator job for " + context.getMergedJobDataMap().getString("email") + " *********"); + try { + List activeAcbIds = certificationBodyManager.getAllActive().stream() + .map(acb -> acb.getId()) + .toList(); + + List reportRows = rwtReportService.getRealWorldTestingReports(activeAcbIds, LOGGER); + + TransactionTemplate txTemplate = new TransactionTemplate(txManager); + txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + processRwtPlanCounts(reportRows); + } + }); + + } catch (Exception e) { + LOGGER.catching(e); + } finally { + LOGGER.info("********* Completed the Real World Report Creator job. *********"); + } + + } + + private void processRwtPlanCounts(List reportRows) { + Integer rwtEligibilityYear = LocalDate.now().getYear() + 1; + + if (!isDateInPlansSubmissionWindow(LocalDate.now(), rwtEligibilityYear)) { + LOGGER.info("Out side the RWT Plan submission window. Not collecting data."); + return; + } + + List rwtSummaryReports = new ArrayList(); + + rwtReportService.getPlansStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { + certificationBodyManager.getAllActive().forEach(acb -> { + rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() + .reportDate(reportDate) + .realWorldTestingYear(rwtEligibilityYear.longValue()) + .certificationBody(acb) + .checkedDate(reportDate) + .checkedCount(calculate(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) + .build()); + }); + }); + + rwtSummaryReports.sort(Comparator.comparing(RealWorldTestingSummaryReport::getCheckedDate) + .thenComparing((o1, o2) -> o1.getCertificationBody().getId().compareTo(o2.getCertificationBody().getId()))); + + rwtSummaryReports.forEach(value -> { + LOGGER.info("{} - {} - {}", value.getCheckedCount(), value.getCheckedDate(), value.getCertificationBody().getName()); + try { + realWorldTestingPlanSummaryReportDao.save(value); + } catch (Exception e) { + LOGGER.error("Could not save RealWorldTestingSummaryReport: {}", value.toString(), e); + } + }); + } + + private Integer calculate(List reports, Integer rwtYear, CertificationBody acb, LocalDate checkedDate) { + return reports.stream() + .filter(report -> report.getAcbName().equals(acb.getName()) + && DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getPlansStartDate(rwtYear), checkedDate), + report.getRwtPlansCheckDate())) + .toList() + .size(); + + } + + private boolean isDateInPlansSubmissionWindow(LocalDate dateToTest, Integer rwtYear) { + return DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getPlansStartDate(rwtYear), rwtReportService.getPlansLateDate(rwtYear)), dateToTest); + } +} From 2358501bd3a3c9af2785b62626cb8646a6a0881a Mon Sep 17 00:00:00 2001 From: Todd Young Date: Tue, 17 Dec 2024 14:18:42 -0500 Subject: [PATCH 02/10] feat: add process to job that collects rwt results summary data over time OCD-4517 --- .../src/main/resources/environment.properties | 2 +- .../RealWorldTestingPlanSummaryReportDao.java | 2 +- ...alWorldTestingPlanSummaryReportEntity.java | 8 +- ...alWorldTestingResultsSummaryReportDao.java | 73 +++++++++++++++++++ ...orldTestingResultsSummaryReportEntity.java | 66 +++++++++++++++++ .../RealWorldTestingSummaryReport.java | 5 +- ...alWorldTestingSummaryReportCreatorJob.java | 66 +++++++++++++++-- 7 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportEntity.java diff --git a/chpl/chpl-resources/src/main/resources/environment.properties b/chpl/chpl-resources/src/main/resources/environment.properties index db83d1b760..94cec072b7 100644 --- a/chpl/chpl-resources/src/main/resources/environment.properties +++ b/chpl/chpl-resources/src/main/resources/environment.properties @@ -220,7 +220,7 @@ rwtProgramFirstEligibilityYear=2022 # Date user can start entering Plan information - Format is MM/DD rwtPlanStartDayOfYear=09/01 # Date when the Plan is considered late - Format is MM/DD -rwtPlanDueDate=12/17 +rwtPlanDueDate=12/15 # Date user can start entering Results information - Format is MM/DD rwtResultsStartDayOfYear=01/01 # Date when the Results is considered late - Format is MM/DD diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java index f9611fa2b1..9a95c8df56 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java @@ -18,13 +18,13 @@ public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) th realWorldTestingSummaryReport.getCertificationBody().getId()); if (entity == null) { entity = RealWorldTestingPlanSummaryReportEntity.builder() - .reportDate(realWorldTestingSummaryReport.getReportDate()) .realWorldTestingYear(realWorldTestingSummaryReport.getRealWorldTestingYear()) .certificationBody(CertificationBodyEntity.builder() .id(realWorldTestingSummaryReport.getCertificationBody().getId()) .build()) .checkedDate(realWorldTestingSummaryReport.getCheckedDate()) .checkedCount(realWorldTestingSummaryReport.getCheckedCount()) + .requiresCheckCount(realWorldTestingSummaryReport.getRequiresCheckCount()) .build(); create(entity); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java index c5d44a9ce5..09313c9a26 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java @@ -36,9 +36,6 @@ public class RealWorldTestingPlanSummaryReportEntity extends EntityAudit { @Column(name = "id") private Long id; - @Column(name = "report_date") - private LocalDate reportDate; - @Column(name = "real_world_testing_year") private Long realWorldTestingYear; @@ -52,14 +49,17 @@ public class RealWorldTestingPlanSummaryReportEntity extends EntityAudit { @Column(name = "checked_count") private Long checkedCount; + @Column(name = "requires_check_count") + private Long requiresCheckCount; + public RealWorldTestingSummaryReport toDomain() { return RealWorldTestingSummaryReport.builder() .id(id) - .reportDate(reportDate) .realWorldTestingYear(realWorldTestingYear) .certificationBody(certificationBody.toDomain()) .checkedDate(checkedDate) .checkedCount(checkedCount) + .requiresCheckCount(requiresCheckCount) .build(); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java new file mode 100644 index 0000000000..addc3134f2 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java @@ -0,0 +1,73 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; +import java.util.List; + +import org.springframework.stereotype.Component; + +import gov.healthit.chpl.dao.impl.BaseDAOImpl; +import gov.healthit.chpl.entity.CertificationBodyEntity; +import gov.healthit.chpl.exception.EntityRetrievalException; +import jakarta.persistence.Query; + +@Component +public class RealWorldTestingResultsSummaryReportDao extends BaseDAOImpl { + + public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) throws EntityRetrievalException { + RealWorldTestingResultsSummaryReportEntity entity = getEntityByCheckedDateAndAcb(realWorldTestingSummaryReport.getCheckedDate(), + realWorldTestingSummaryReport.getCertificationBody().getId()); + if (entity == null) { + entity = RealWorldTestingResultsSummaryReportEntity.builder() + .realWorldTestingYear(realWorldTestingSummaryReport.getRealWorldTestingYear()) + .certificationBody(CertificationBodyEntity.builder() + .id(realWorldTestingSummaryReport.getCertificationBody().getId()) + .build()) + .checkedDate(realWorldTestingSummaryReport.getCheckedDate()) + .checkedCount(realWorldTestingSummaryReport.getCheckedCount()) + .requiresCheckCount(realWorldTestingSummaryReport.getRequiresCheckCount()) + .build(); + + create(entity); + } else { + entity.setCheckedCount(realWorldTestingSummaryReport.getCheckedCount()); + update(entity); + } + } + + private RealWorldTestingResultsSummaryReportEntity getEntity(Long id) throws EntityRetrievalException { + Query query = entityManager.createQuery( + "from RealWorldTestingResultsSummaryReportEntity where (NOT deleted = true) and id = :id", RealWorldTestingResultsSummaryReportEntity.class); + query.setParameter("id", id); + List result = query.getResultList(); + + if (result.size() > 1) { + throw new EntityRetrievalException("Data error. Duplicate id in real_world_testing_results_summary_report table."); + } + + if (result.size() > 0) { + return result.get(0); + } + return null; + } + + private RealWorldTestingResultsSummaryReportEntity getEntityByCheckedDateAndAcb(LocalDate checkedDate, Long certificationBodyId) throws EntityRetrievalException { + Query query = entityManager.createQuery( + "from RealWorldTestingResultsSummaryReportEntity rwtrs " + + "where (NOT deleted = true) " + + "and checkedDate = :checkedDate " + + "and rwtrs.certificationBody.id = :certificationBodyId", RealWorldTestingResultsSummaryReportEntity.class); + query.setParameter("checkedDate", checkedDate); + query.setParameter("certificationBodyId", certificationBodyId); + List result = query.getResultList(); + + if (result.size() > 1) { + throw new EntityRetrievalException("Data error. Duplicate checked_date in real_world_testing_results_summary_report table."); + } + + if (result.size() > 0) { + return result.get(0); + } + return null; + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportEntity.java new file mode 100644 index 0000000000..370960fe65 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportEntity.java @@ -0,0 +1,66 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; + +import gov.healthit.chpl.entity.CertificationBodyEntity; +import gov.healthit.chpl.entity.EntityAudit; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Getter +@Setter +@ToString +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "real_world_testing_results_summary_report") +public class RealWorldTestingResultsSummaryReportEntity extends EntityAudit { + private static final long serialVersionUID = 4976557989831765742L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "real_world_testing_year") + private Long realWorldTestingYear; + + @OneToOne(optional = true, fetch = FetchType.LAZY) + @JoinColumn(name = "certification_body_id") + private CertificationBodyEntity certificationBody; + + @Column(name = "checked_date") + private LocalDate checkedDate; + + @Column(name = "checked_count") + private Long checkedCount; + + @Column(name = "requires_check_count") + private Long requiresCheckCount; + + public RealWorldTestingSummaryReport toDomain() { + return RealWorldTestingSummaryReport.builder() + .id(id) + .realWorldTestingYear(realWorldTestingYear) + .certificationBody(certificationBody.toDomain()) + .checkedDate(checkedDate) + .checkedCount(checkedCount) + .requiresCheckCount(requiresCheckCount) + .build(); + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java index 4941e94b68..ee2cebecec 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java @@ -16,10 +16,6 @@ public class RealWorldTestingSummaryReport { private Long id; - @JsonDeserialize(using = LocalDateDeserializer.class) - @JsonSerialize(using = LocalDateSerializer.class) - private LocalDate reportDate; - private Long realWorldTestingYear; private CertificationBody certificationBody; @@ -28,4 +24,5 @@ public class RealWorldTestingSummaryReport { private LocalDate checkedDate; private Long checkedCount; + private Long requiresCheckCount; } 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 7b0d61270f..85da7ca0d3 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 @@ -21,6 +21,7 @@ import gov.healthit.chpl.realworldtesting.domain.RealWorldTestingReport; import gov.healthit.chpl.realworldtesting.manager.RealWorldTestingReportService; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingPlanSummaryReportDao; +import gov.healthit.chpl.report.realworldtesting.RealWorldTestingResultsSummaryReportDao; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryReport; import gov.healthit.chpl.util.DateUtil; import lombok.extern.log4j.Log4j2; @@ -34,9 +35,12 @@ public class RealWorldTestingSummaryReportCreatorJob extends QuartzJob { @Autowired private RealWorldTestingPlanSummaryReportDao realWorldTestingPlanSummaryReportDao; + @Autowired + private RealWorldTestingResultsSummaryReportDao realWorldTestingResultsSummaryReportDao; + @Autowired private CertificationBodyManager certificationBodyManager; - + @Autowired private JpaTransactionManager txManager; @@ -50,13 +54,14 @@ public void execute(JobExecutionContext context) throws JobExecutionException { .toList(); List reportRows = rwtReportService.getRealWorldTestingReports(activeAcbIds, LOGGER); - + TransactionTemplate txTemplate = new TransactionTemplate(txManager); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); txTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { processRwtPlanCounts(reportRows); + processRwtResultsCounts(reportRows); } }); @@ -68,11 +73,47 @@ protected void doInTransactionWithoutResult(TransactionStatus status) { } + private void processRwtResultsCounts(List reportRows) { + Integer rwtEligibilityYear = LocalDate.now().getYear() - 1; + + if (!isDateInResultsSubmissionWindow(LocalDate.now(), rwtEligibilityYear)) { + LOGGER.info("Outside the RWT Results submission window. Not collecting data."); + return; + } + + List rwtSummaryReports = new ArrayList(); + + rwtReportService.getResultsStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { + certificationBodyManager.getAllActive().forEach(acb -> { + rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() + .realWorldTestingYear(rwtEligibilityYear.longValue()) + .certificationBody(acb) + .checkedDate(reportDate) + .checkedCount(calculateResulltsCount(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) + .requiresCheckCount(Long.valueOf(reportRows.size())) + .build()); + }); + }); + + rwtSummaryReports.sort(Comparator.comparing(RealWorldTestingSummaryReport::getCheckedDate) + .thenComparing((o1, o2) -> o1.getCertificationBody().getId().compareTo(o2.getCertificationBody().getId()))); + + rwtSummaryReports.forEach(value -> { + LOGGER.info("{} - {} - {}", value.getCheckedCount(), value.getCheckedDate(), value.getCertificationBody().getName()); + try { + realWorldTestingResultsSummaryReportDao.save(value); + } catch (Exception e) { + LOGGER.error("Could not save RealWorldTestingSummaryReport: {}", value.toString(), e); + } + }); + LOGGER.info("Completed gathering RWT Results submissions."); + } + private void processRwtPlanCounts(List reportRows) { Integer rwtEligibilityYear = LocalDate.now().getYear() + 1; if (!isDateInPlansSubmissionWindow(LocalDate.now(), rwtEligibilityYear)) { - LOGGER.info("Out side the RWT Plan submission window. Not collecting data."); + LOGGER.info("Outside the RWT Plan submission window. Not collecting data."); return; } @@ -81,11 +122,11 @@ private void processRwtPlanCounts(List reportRows) { rwtReportService.getPlansStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { certificationBodyManager.getAllActive().forEach(acb -> { rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() - .reportDate(reportDate) .realWorldTestingYear(rwtEligibilityYear.longValue()) .certificationBody(acb) .checkedDate(reportDate) - .checkedCount(calculate(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) + .checkedCount(calculatePlanCount(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) + .requiresCheckCount(Long.valueOf(reportRows.size())) .build()); }); }); @@ -101,19 +142,32 @@ private void processRwtPlanCounts(List reportRows) { LOGGER.error("Could not save RealWorldTestingSummaryReport: {}", value.toString(), e); } }); + LOGGER.info("Completed gathering RWT Plan submissions."); } - private Integer calculate(List reports, Integer rwtYear, CertificationBody acb, LocalDate checkedDate) { + private Integer calculatePlanCount(List reports, Integer rwtYear, CertificationBody acb, LocalDate checkedDate) { return reports.stream() .filter(report -> report.getAcbName().equals(acb.getName()) && DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getPlansStartDate(rwtYear), checkedDate), report.getRwtPlansCheckDate())) .toList() .size(); + } + private Integer calculateResulltsCount(List reports, Integer rwtYear, CertificationBody acb, LocalDate checkedDate) { + return reports.stream() + .filter(report -> report.getAcbName().equals(acb.getName()) + && DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getResultsStartDate(rwtYear), checkedDate), + report.getRwtResultsCheckDate())) + .toList() + .size(); } private boolean isDateInPlansSubmissionWindow(LocalDate dateToTest, Integer rwtYear) { return DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getPlansStartDate(rwtYear), rwtReportService.getPlansLateDate(rwtYear)), dateToTest); } + + private boolean isDateInResultsSubmissionWindow(LocalDate dateToTest, Integer rwtYear) { + return DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getResultsStartDate(rwtYear), rwtReportService.getResultsLateDate(rwtYear)), dateToTest); + } } From b3e81ba6fef70612e7d5fcdb87cb08a2ad51ba8a Mon Sep 17 00:00:00 2001 From: Todd Young Date: Fri, 3 Jan 2025 22:29:04 -0500 Subject: [PATCH 03/10] feat: add endpoint /report-data/real-world-testing/plans OCD-4517 --- .../RealWorldTestingReportController.java | 41 +++++++++++++++++++ .../src/main/resources/jobs.xml | 2 +- .../chpl/report/ReportDataManager.java | 25 +++++++++-- .../RealWorldTestingPlanSummaryReportDao.java | 25 +++++++++++ .../RealWorldTestingReportDataService.java | 33 +++++++++++++++ 5 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java new file mode 100644 index 0000000000..e775eabaf2 --- /dev/null +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java @@ -0,0 +1,41 @@ +package gov.healthit.chpl.web.controller; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import gov.healthit.chpl.report.ReportDataManager; +import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryReport; +import gov.healthit.chpl.util.SwaggerSecurityRequirement; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Tag(name = "report-data/real-world-testing", description = "Allows retrieval of data used by Real World Testing report.") +@RestController +@RequestMapping("/report-data/real-world-testing") +public class RealWorldTestingReportController { + + private ReportDataManager reportDataManager; + + @Autowired + public RealWorldTestingReportController(ReportDataManager reportDataManager) { + this.reportDataManager = reportDataManager; + } + + @Operation(summary = "Retrieves the data used to generate the Standard Criteria Attribute Summary report.", + description = "Retrieves the data used to generate the Standard Criteria Attribute Summary report.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @RequestMapping(value = "/plans", method = RequestMethod.GET, produces = "application/json; charset=utf-8") + public @ResponseBody List getRealWorldTestingPlanReports() { + return reportDataManager.getRealWorldTestingReportDataService().getRealWorldTestingPlanSummaryReports(); + } +} diff --git a/chpl/chpl-resources/src/main/resources/jobs.xml b/chpl/chpl-resources/src/main/resources/jobs.xml index d69f2b283b..3c1361f8db 100644 --- a/chpl/chpl-resources/src/main/resources/jobs.xml +++ b/chpl/chpl-resources/src/main/resources/jobs.xml @@ -925,7 +925,7 @@ realWorldTestingSummaryReportCreatorJob systemJobs - Update Task/Participant Friendly Ids Job + Populate RWT reporting tables gov.healthit.chpl.scheduler.job.RealWorldTestingSummaryReportCreatorJob true false diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java index 40626336b3..7f01f990ae 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java @@ -24,6 +24,7 @@ import gov.healthit.chpl.report.product.ProductByAcb; import gov.healthit.chpl.report.product.ProductReportsService; import gov.healthit.chpl.report.product.UniqueProductCount; +import gov.healthit.chpl.report.realworldtesting.RealWorldTestingReportDataService; import gov.healthit.chpl.report.servicebaseurllistreport.ServiceBaseUrlListReportService; import gov.healthit.chpl.report.servicebaseurllistreport.UrlUptimeMonitorEx; import gov.healthit.chpl.report.surveillance.CapCounts; @@ -50,12 +51,22 @@ public class ReportDataManager { private StandardReportService standardReportService; private DirectReviewReportsService directReviewReportsService; private ReportMetadataDAO reportMetadataDAO; + private RealWorldTestingReportDataService realWorldTestingReportDataService; + @Autowired - public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportService, DeveloperReportsService developerReportsService, - SurveillanceReportsService surveillanceReportsService, ProductReportsService productReportsService, ListingReportsService listingReportsService, - TestToolReportService testToolReportService, DirectReviewReportsService directReviewReportsService, ReportMetadataDAO reportMetadataDAO, - ServiceBaseUrlListReportService serviceBaseUrlListReportService, StandardReportService standardReportService) { + public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportService, + DeveloperReportsService developerReportsService, + SurveillanceReportsService surveillanceReportsService, + ProductReportsService productReportsService, + ListingReportsService listingReportsService, + TestToolReportService testToolReportService, + DirectReviewReportsService directReviewReportsService, + ReportMetadataDAO reportMetadataDAO, + ServiceBaseUrlListReportService serviceBaseUrlListReportService, + StandardReportService standardReportService, + RealWorldTestingReportDataService realWorldTestingReportDataService) { + this.criteriaMigrationReportService = criteriaMigrationReportService; this.developerReportsService = developerReportsService; this.surveillanceReportsService = surveillanceReportsService; @@ -66,6 +77,7 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS this.standardReportService = standardReportService; this.directReviewReportsService = directReviewReportsService; this.reportMetadataDAO = reportMetadataDAO; + this.realWorldTestingReportDataService = realWorldTestingReportDataService; } public List getReportMetadataByReportGroup(String reportGroup) { @@ -266,4 +278,9 @@ public List getStandardListingReports() { public DirectReviewCounts getDirectReviewCounts() { return directReviewReportsService.getDirectReviewCounts(); } + + @Synchronized("lock") + public RealWorldTestingReportDataService getRealWorldTestingReportDataService() { + return realWorldTestingReportDataService; + } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java index 9a95c8df56..e8ab08f578 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.util.List; +import java.util.Optional; import org.springframework.stereotype.Component; @@ -34,6 +35,21 @@ public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) th } } + public Optional getMaxRealWorldTestingYear() { + return Optional.ofNullable(entityManager.createQuery( + "select MAX(rwtpsr.realWorldTestingYear) " + + "from RealWorldTestingPlanSummaryReportEntity rwtpsr " + + "where (NOT deleted = true)", Long.class) + .getSingleResult()); + + } + + public List getRealWorldTestingReportsByTestingYear(Long realWorldTestingYear) { + return getEntitiesByRealWorldTestingYear(realWorldTestingYear).stream() + .map(entity -> entity.toDomain()) + .toList(); + } + private RealWorldTestingPlanSummaryReportEntity getEntity(Long id) throws EntityRetrievalException { Query query = entityManager.createQuery( "from RealWorldTestingPlanSummaryReportEntity where (NOT deleted = true) and id = :id", RealWorldTestingPlanSummaryReportEntity.class); @@ -70,4 +86,13 @@ private RealWorldTestingPlanSummaryReportEntity getEntityByCheckedDateAndAcb(Loc return null; } + private List getEntitiesByRealWorldTestingYear(Long testingYear){ + return entityManager.createQuery( + "from RealWorldTestingPlanSummaryReportEntity rwtps " + + "where (NOT deleted = true) " + + "and rwtps.realWorldTestingYear = :realWorldTestingYear", RealWorldTestingPlanSummaryReportEntity.class) + .setParameter("realWorldTestingYear", testingYear) + .getResultList(); + } + } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java new file mode 100644 index 0000000000..313c3c18ec --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java @@ -0,0 +1,33 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.util.List; +import java.util.Optional; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import jakarta.transaction.Transactional; + +@Component +public class RealWorldTestingReportDataService { + private RealWorldTestingPlanSummaryReportDao realWorldTestingPlanSummaryReportDao; + private RealWorldTestingResultsSummaryReportDao realWorldTestingResultsSummaryReportDao; + + @Autowired + public RealWorldTestingReportDataService(RealWorldTestingPlanSummaryReportDao realWorldTestingPlanSummaryReportDao, + RealWorldTestingResultsSummaryReportDao realWorldTestingResultsSummaryReportDao) { + + this.realWorldTestingPlanSummaryReportDao = realWorldTestingPlanSummaryReportDao; + this.realWorldTestingResultsSummaryReportDao = realWorldTestingResultsSummaryReportDao; + } + + @Transactional + public List getRealWorldTestingPlanSummaryReports() { + Optional rwtYear = realWorldTestingPlanSummaryReportDao.getMaxRealWorldTestingYear(); + if (rwtYear.isPresent()) { + return realWorldTestingPlanSummaryReportDao.getRealWorldTestingReportsByTestingYear(rwtYear.get()); + } else { + return List.of(); + } + } +} From 25d64fc264c2f0dbd0644ddb4c4422fa5f2f6adc Mon Sep 17 00:00:00 2001 From: Todd Young Date: Fri, 3 Jan 2025 22:40:59 -0500 Subject: [PATCH 04/10] feat: add endpoint /report-data/real-world-testing/plans OCD-4517 --- .../RealWorldTestingReportController.java | 14 +++++++++-- .../RealWorldTestingReportDataService.java | 10 ++++++++ ...alWorldTestingResultsSummaryReportDao.java | 25 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java index e775eabaf2..e40706ed73 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java @@ -29,8 +29,8 @@ public RealWorldTestingReportController(ReportDataManager reportDataManager) { this.reportDataManager = reportDataManager; } - @Operation(summary = "Retrieves the data used to generate the Standard Criteria Attribute Summary report.", - description = "Retrieves the data used to generate the Standard Criteria Attribute Summary report.", + @Operation(summary = "Retrieves the data used to generate the Real World Testing Plans report.", + description = "Retrieves the data used to generate the Real World Testing Plans report.", security = { @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) }) @@ -38,4 +38,14 @@ public RealWorldTestingReportController(ReportDataManager reportDataManager) { public @ResponseBody List getRealWorldTestingPlanReports() { return reportDataManager.getRealWorldTestingReportDataService().getRealWorldTestingPlanSummaryReports(); } + + @Operation(summary = "Retrieves the data used to generate the Real World Testing Results report.", + description = "Retrieves the data used to generate the Real World Testing Results report.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @RequestMapping(value = "/results", method = RequestMethod.GET, produces = "application/json; charset=utf-8") + public @ResponseBody List getRealWorldTestingResultsReports() { + return reportDataManager.getRealWorldTestingReportDataService().getRealWorldTestingResultsSummaryReports(); + } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java index 313c3c18ec..e88c73269e 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java @@ -30,4 +30,14 @@ public List getRealWorldTestingPlanSummaryReports return List.of(); } } + + @Transactional + public List getRealWorldTestingResultsSummaryReports() { + Optional rwtYear = realWorldTestingResultsSummaryReportDao.getMaxRealWorldTestingYear(); + if (rwtYear.isPresent()) { + return realWorldTestingResultsSummaryReportDao.getRealWorldTestingReportsByTestingYear(rwtYear.get()); + } else { + return List.of(); + } + } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java index addc3134f2..4dc54826fe 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.util.List; +import java.util.Optional; import org.springframework.stereotype.Component; @@ -34,6 +35,21 @@ public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) th } } + public Optional getMaxRealWorldTestingYear() { + return Optional.ofNullable(entityManager.createQuery( + "select MAX(rwtrsr.realWorldTestingYear) " + + "from RealWorldTestingResultsSummaryReportEntity rwtrsr " + + "where (NOT deleted = true)", Long.class) + .getSingleResult()); + + } + + public List getRealWorldTestingReportsByTestingYear(Long realWorldTestingYear) { + return getEntitiesByRealWorldTestingYear(realWorldTestingYear).stream() + .map(entity -> entity.toDomain()) + .toList(); + } + private RealWorldTestingResultsSummaryReportEntity getEntity(Long id) throws EntityRetrievalException { Query query = entityManager.createQuery( "from RealWorldTestingResultsSummaryReportEntity where (NOT deleted = true) and id = :id", RealWorldTestingResultsSummaryReportEntity.class); @@ -70,4 +86,13 @@ private RealWorldTestingResultsSummaryReportEntity getEntityByCheckedDateAndAcb( return null; } + private List getEntitiesByRealWorldTestingYear(Long testingYear){ + return entityManager.createQuery( + "from RealWorldTestingResultsSummaryReportEntity rwtrs " + + "where (NOT deleted = true) " + + "and rwtrs.realWorldTestingYear = :realWorldTestingYear", RealWorldTestingResultsSummaryReportEntity.class) + .setParameter("realWorldTestingYear", testingYear) + .getResultList(); + } + } From 3599718496df6becb59af8fc26bb5fc6738d0b85 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Mon, 6 Jan 2025 09:32:46 -0500 Subject: [PATCH 05/10] feat: add logging to RealWorldTestingSummaryReportCreatorJob OCD-4517 --- .../resources/log4j2-xinclude-file-appenders.xml | 12 ++++++++++++ .../main/resources/log4j2-xinclude-loggers-local.xml | 3 +++ .../src/main/resources/log4j2-xinclude-loggers.xml | 4 ++++ .../job/RealWorldTestingSummaryReportCreatorJob.java | 2 +- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml index ab8fa03c06..770530f568 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml @@ -808,4 +808,16 @@ interval="1" modulate="true" /> + + + %d{ISO8601} %-5p (%t) [%C{1}(%M:%L)] %m%n + + + + + diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml index 076d953e2a..28311a9265 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers-local.xml @@ -205,4 +205,7 @@ + + + diff --git a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml index c0b063154a..7d861ceac0 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml @@ -262,4 +262,8 @@ + + + + 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 85da7ca0d3..1d39c6be02 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 @@ -26,7 +26,7 @@ import gov.healthit.chpl.util.DateUtil; import lombok.extern.log4j.Log4j2; -@Log4j2 +@Log4j2(topic = "realWorldTestingSummaryReportCreatorJobLogger") public class RealWorldTestingSummaryReportCreatorJob extends QuartzJob { @Autowired From 1a524a1983c6f71095dee7f2450056f82e1043f4 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Mon, 6 Jan 2025 10:09:58 -0500 Subject: [PATCH 06/10] fix: correctly calculate the number of listings reguiring RWT by ACB OCD-4517 --- .../RealWorldTestingPlanSummaryReportDao.java | 1 + .../RealWorldTestingResultsSummaryReportDao.java | 1 + .../RealWorldTestingSummaryReportCreatorJob.java | 13 +++++++++++-- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java index e8ab08f578..fee471afeb 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java @@ -31,6 +31,7 @@ public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) th create(entity); } else { entity.setCheckedCount(realWorldTestingSummaryReport.getCheckedCount()); + entity.setRequiresCheckCount(realWorldTestingSummaryReport.getRequiresCheckCount()); update(entity); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java index 4dc54826fe..e27ef85cb2 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java @@ -31,6 +31,7 @@ public void save(RealWorldTestingSummaryReport realWorldTestingSummaryReport) th create(entity); } else { entity.setCheckedCount(realWorldTestingSummaryReport.getCheckedCount()); + entity.setRequiresCheckCount(realWorldTestingSummaryReport.getRequiresCheckCount()); update(entity); } } 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 1d39c6be02..f27aed2794 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 @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import org.apache.commons.lang3.tuple.Pair; import org.quartz.JobExecutionContext; @@ -85,12 +86,16 @@ private void processRwtResultsCounts(List reportRows) { rwtReportService.getResultsStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { certificationBodyManager.getAllActive().forEach(acb -> { + Long eligibleListingCountForAcb = reportRows.stream() + .filter(row -> row.getAcbName().equals(acb.getName())) + .collect(Collectors.counting()); + rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() .realWorldTestingYear(rwtEligibilityYear.longValue()) .certificationBody(acb) .checkedDate(reportDate) .checkedCount(calculateResulltsCount(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) - .requiresCheckCount(Long.valueOf(reportRows.size())) + .requiresCheckCount(eligibleListingCountForAcb) .build()); }); }); @@ -121,12 +126,16 @@ private void processRwtPlanCounts(List reportRows) { rwtReportService.getPlansStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { certificationBodyManager.getAllActive().forEach(acb -> { + Long eligibleListingCountForAcb = reportRows.stream() + .filter(row -> row.getAcbName().equals(acb.getName())) + .collect(Collectors.counting()); + rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() .realWorldTestingYear(rwtEligibilityYear.longValue()) .certificationBody(acb) .checkedDate(reportDate) .checkedCount(calculatePlanCount(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) - .requiresCheckCount(Long.valueOf(reportRows.size())) + .requiresCheckCount(eligibleListingCountForAcb) .build()); }); }); From fb40bfb726c01e2daa5844b49bce37abec53f876 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Mon, 6 Jan 2025 22:45:56 -0500 Subject: [PATCH 07/10] feat: add system trigger to run job each night OCD-4517 --- .../src/main/resources/system-triggers.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/chpl/chpl-resources/src/main/resources/system-triggers.xml b/chpl/chpl-resources/src/main/resources/system-triggers.xml index 0c3c528900..5a67f8452f 100644 --- a/chpl/chpl-resources/src/main/resources/system-triggers.xml +++ b/chpl/chpl-resources/src/main/resources/system-triggers.xml @@ -245,5 +245,16 @@ 0 30 6 * * ? + + + realWorldTestingSummaryReportCreator + realWorldTestingSummaryReportCreatorJobTrigger + realWorldTestingSummaryReportCreatorJob + systemJobs + MISFIRE_INSTRUCTION_DO_NOTHING + 0 15 6 * * ? + + + From a9acbcd3d1af7e77d57485a7c101266ed19f9e24 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Tue, 7 Jan 2025 19:59:07 -0500 Subject: [PATCH 08/10] feat: handle listings added during plan subm window OCD-4517 --- .../job/RealWorldTestingSummaryReportCreatorJob.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 f27aed2794..aca4b000a3 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 @@ -127,7 +127,8 @@ private void processRwtPlanCounts(List reportRows) { rwtReportService.getPlansStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { certificationBodyManager.getAllActive().forEach(acb -> { Long eligibleListingCountForAcb = reportRows.stream() - .filter(row -> row.getAcbName().equals(acb.getName())) + .filter(row -> row.getAcbName().equals(acb.getName()) + && isListingValidAsOfDate(row.getCertificationDate(), reportDate)) .collect(Collectors.counting()); rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() @@ -179,4 +180,9 @@ private boolean isDateInPlansSubmissionWindow(LocalDate dateToTest, Integer rwtY private boolean isDateInResultsSubmissionWindow(LocalDate dateToTest, Integer rwtYear) { return DateUtil.isDateBetweenInclusive(Pair.of(rwtReportService.getResultsStartDate(rwtYear), rwtReportService.getResultsLateDate(rwtYear)), dateToTest); } + + private Boolean isListingValidAsOfDate(LocalDate listingCertificationDate, LocalDate date) { + return date.isAfter(listingCertificationDate) + || date.equals(listingCertificationDate); + } } From ebfedacfa38a4568394207724b8f883ec5408c75 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Mon, 13 Jan 2025 11:43:08 -0500 Subject: [PATCH 09/10] feat: apply same logic to listing count for results as applied to plans OCD-4517 --- .../scheduler/job/RealWorldTestingSummaryReportCreatorJob.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 aca4b000a3..e668bb607a 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 @@ -87,7 +87,8 @@ private void processRwtResultsCounts(List reportRows) { rwtReportService.getResultsStartDate(rwtEligibilityYear).datesUntil(LocalDate.now()).forEach(reportDate -> { certificationBodyManager.getAllActive().forEach(acb -> { Long eligibleListingCountForAcb = reportRows.stream() - .filter(row -> row.getAcbName().equals(acb.getName())) + .filter(row -> row.getAcbName().equals(acb.getName()) + && isListingValidAsOfDate(row.getCertificationDate(), reportDate)) .collect(Collectors.counting()); rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() From 9545c3981be6f0b6a54b5252efa0369e6e621b33 Mon Sep 17 00:00:00 2001 From: Todd Young Date: Mon, 13 Jan 2025 14:53:32 -0500 Subject: [PATCH 10/10] refactor: update CheckStyle issues OCD-4517 --- .../realworldtesting/RealWorldTestingPlanSummaryReportDao.java | 2 +- .../RealWorldTestingResultsSummaryReportDao.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java index fee471afeb..cf32ea4253 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java @@ -87,7 +87,7 @@ private RealWorldTestingPlanSummaryReportEntity getEntityByCheckedDateAndAcb(Loc return null; } - private List getEntitiesByRealWorldTestingYear(Long testingYear){ + private List getEntitiesByRealWorldTestingYear(Long testingYear) { return entityManager.createQuery( "from RealWorldTestingPlanSummaryReportEntity rwtps " + "where (NOT deleted = true) " diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java index e27ef85cb2..c3ede71113 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java @@ -87,7 +87,7 @@ private RealWorldTestingResultsSummaryReportEntity getEntityByCheckedDateAndAcb( return null; } - private List getEntitiesByRealWorldTestingYear(Long testingYear){ + private List getEntitiesByRealWorldTestingYear(Long testingYear) { return entityManager.createQuery( "from RealWorldTestingResultsSummaryReportEntity rwtrs " + "where (NOT deleted = true) "