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..e40706ed73 --- /dev/null +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/RealWorldTestingReportController.java @@ -0,0 +1,51 @@ +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 Real World Testing Plans report.", + description = "Retrieves the data used to generate the Real World Testing Plans 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(); + } + + @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-api/src/main/resources/log4j2-xinclude-file-appenders.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-file-appenders.xml index d1d537b166..c4bddca963 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,6 +808,18 @@ 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.xml b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml index eec409eb68..8a828d5224 100644 --- a/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml +++ b/chpl/chpl-api/src/main/resources/log4j2-xinclude-loggers.xml @@ -262,6 +262,10 @@ + + + + diff --git a/chpl/chpl-resources/src/main/resources/jobs.xml b/chpl/chpl-resources/src/main/resources/jobs.xml index 02daaf75ff..50fb2c2872 100644 --- a/chpl/chpl-resources/src/main/resources/jobs.xml +++ b/chpl/chpl-resources/src/main/resources/jobs.xml @@ -922,6 +922,15 @@ false + + realWorldTestingSummaryReportCreatorJob + systemJobs + Populate RWT reporting tables + gov.healthit.chpl.scheduler.job.RealWorldTestingSummaryReportCreatorJob + true + false + + attestationReportCreatorJob systemJobs diff --git a/chpl/chpl-resources/src/main/resources/system-triggers.xml b/chpl/chpl-resources/src/main/resources/system-triggers.xml index eb75d67bae..93546b7dd7 100644 --- a/chpl/chpl-resources/src/main/resources/system-triggers.xml +++ b/chpl/chpl-resources/src/main/resources/system-triggers.xml @@ -245,6 +245,17 @@ 0 30 6 * * ? + + + realWorldTestingSummaryReportCreator + realWorldTestingSummaryReportCreatorJobTrigger + realWorldTestingSummaryReportCreatorJob + systemJobs + MISFIRE_INSTRUCTION_DO_NOTHING + 0 15 6 * * ? + + + attestationReportCreator 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/ReportDataManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/ReportDataManager.java index 5c26bceca8..04544f85a9 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 @@ -20,6 +20,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; @@ -47,6 +48,8 @@ public class ReportDataManager { private ReportMetadataDAO reportMetadataDAO; private CriteriaAttributeReportService criteriaAttributeReportService; private ServiceBaseUrlListReportService serviceBaseUrlListReportService; + private RealWorldTestingReportDataService realWorldTestingReportDataService; + @Autowired @@ -59,7 +62,9 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS AttestationReportService attestationReportService, ReportMetadataDAO reportMetadataDAO, CriteriaAttributeReportService criteriaAttributeReportService, - ServiceBaseUrlListReportService serviceBaseUrlListReportService) { + ServiceBaseUrlListReportService serviceBaseUrlListReportService, + RealWorldTestingReportDataService realWorldTestingReportDataService) { + this.criteriaMigrationReportService = criteriaMigrationReportService; this.developerReportsService = developerReportsService; this.surveillanceReportsService = surveillanceReportsService; @@ -71,6 +76,7 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS this.reportMetadataDAO = reportMetadataDAO; this.criteriaAttributeReportService = criteriaAttributeReportService; this.serviceBaseUrlListReportService = serviceBaseUrlListReportService; + this.realWorldTestingReportDataService = realWorldTestingReportDataService; } public List getReportMetadataByReportGroup(String reportGroup) { @@ -258,6 +264,11 @@ public List getUrlUptimeMonitors() { return serviceBaseUrlListReportService.getUrlUptimeMonitors(); } + @Synchronized("lock") + public RealWorldTestingReportDataService getRealWorldTestingReportDataService() { + return realWorldTestingReportDataService; + } + @Synchronized("lock") public List getAttestationReports() { return attestationReportService.getAttestationReports(); 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..cf32ea4253 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportDao.java @@ -0,0 +1,99 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +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() + .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()); + entity.setRequiresCheckCount(realWorldTestingSummaryReport.getRequiresCheckCount()); + update(entity); + } + } + + 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); + 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; + } + + 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/RealWorldTestingPlanSummaryReportEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingPlanSummaryReportEntity.java new file mode 100644 index 0000000000..09313c9a26 --- /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 = "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/RealWorldTestingReportDataService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java new file mode 100644 index 0000000000..e88c73269e --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingReportDataService.java @@ -0,0 +1,43 @@ +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(); + } + } + + @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 new file mode 100644 index 0000000000..c3ede71113 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingResultsSummaryReportDao.java @@ -0,0 +1,99 @@ +package gov.healthit.chpl.report.realworldtesting; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +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()); + entity.setRequiresCheckCount(realWorldTestingSummaryReport.getRequiresCheckCount()); + update(entity); + } + } + + 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); + 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; + } + + 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(); + } + +} 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 new file mode 100644 index 0000000000..ee2cebecec --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/realworldtesting/RealWorldTestingSummaryReport.java @@ -0,0 +1,28 @@ +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; + + private Long realWorldTestingYear; + private CertificationBody certificationBody; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + 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 new file mode 100644 index 0000000000..e668bb607a --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/scheduler/job/RealWorldTestingSummaryReportCreatorJob.java @@ -0,0 +1,189 @@ +package gov.healthit.chpl.scheduler.job; + +import java.time.LocalDate; +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; +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.RealWorldTestingResultsSummaryReportDao; +import gov.healthit.chpl.report.realworldtesting.RealWorldTestingSummaryReport; +import gov.healthit.chpl.util.DateUtil; +import lombok.extern.log4j.Log4j2; + +@Log4j2(topic = "realWorldTestingSummaryReportCreatorJobLogger") +public class RealWorldTestingSummaryReportCreatorJob extends QuartzJob { + + @Autowired + private RealWorldTestingReportService rwtReportService; + + @Autowired + private RealWorldTestingPlanSummaryReportDao realWorldTestingPlanSummaryReportDao; + + @Autowired + private RealWorldTestingResultsSummaryReportDao realWorldTestingResultsSummaryReportDao; + + @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); + processRwtResultsCounts(reportRows); + } + }); + + } catch (Exception e) { + LOGGER.catching(e); + } finally { + LOGGER.info("********* Completed the Real World Report Creator job. *********"); + } + + } + + 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 -> { + Long eligibleListingCountForAcb = reportRows.stream() + .filter(row -> row.getAcbName().equals(acb.getName()) + && isListingValidAsOfDate(row.getCertificationDate(), reportDate)) + .collect(Collectors.counting()); + + rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() + .realWorldTestingYear(rwtEligibilityYear.longValue()) + .certificationBody(acb) + .checkedDate(reportDate) + .checkedCount(calculateResulltsCount(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) + .requiresCheckCount(eligibleListingCountForAcb) + .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("Outside 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 -> { + Long eligibleListingCountForAcb = reportRows.stream() + .filter(row -> row.getAcbName().equals(acb.getName()) + && isListingValidAsOfDate(row.getCertificationDate(), reportDate)) + .collect(Collectors.counting()); + + rwtSummaryReports.add(RealWorldTestingSummaryReport.builder() + .realWorldTestingYear(rwtEligibilityYear.longValue()) + .certificationBody(acb) + .checkedDate(reportDate) + .checkedCount(calculatePlanCount(reportRows, rwtEligibilityYear, acb, reportDate).longValue()) + .requiresCheckCount(eligibleListingCountForAcb) + .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); + } + }); + LOGGER.info("Completed gathering RWT Plan submissions."); + } + + 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); + } + + private Boolean isListingValidAsOfDate(LocalDate listingCertificationDate, LocalDate date) { + return date.isAfter(listingCertificationDate) + || date.equals(listingCertificationDate); + } +}