From ee52f40df12e69603c016a3a0e010841407c5228 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Mon, 14 Sep 2026 14:29:23 -0400 Subject: [PATCH 1/4] feat!: Add report-data endpoint to get surveillance per criteria [#OCD-5429] --- .../web/controller/ReportDataController.java | 12 +++++ .../gov/healthit/chpl/CHPLServiceConfig.java | 4 +- .../surveillance/SurveillanceDAO.java | 41 ++++++++++++++- .../entity/SurveillanceRequirementEntity.java | 3 ++ .../chpl/report/ReportDataManager.java | 6 +++ .../criteriaattribute/SvapReportDao.java | 2 +- .../nonconformity/NonconformityReportDao.java | 2 +- .../surveillance/SurveillanceByCriteria.java | 51 +++++++++++++++++++ .../SurveillanceByRequirementType.java | 26 ++++++++++ .../SurveillanceReportsService.java | 5 ++ .../chpl/report/svap/SvapReportService.java | 2 +- .../CertificationCriterionService.java | 4 +- 12 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByCriteria.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java index f45235ba31..7a216195b0 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java @@ -22,6 +22,7 @@ import gov.healthit.chpl.report.surveillance.CapCounts; import gov.healthit.chpl.report.surveillance.NonconformityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceActivityCounts; +import gov.healthit.chpl.report.surveillance.SurveillanceByCriteria; import gov.healthit.chpl.report.surveillance.SurveillanceByDeveloper; import gov.healthit.chpl.scheduler.job.summarystatistics.data.CertificationBodyStatistic; import gov.healthit.chpl.search.domain.ListingSearchResult; @@ -72,6 +73,17 @@ public ReportDataController(ReportDataManager reportDataManager, return reportDataManager.getSurveillanceOpenDuringTheLastYearForActiveDevelopers(); } + @Operation(summary = "Retrieves the data about each surveillance including the related criterion.", + description = "Retrieves the data about each surveillance including the related criterion.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @LogMethodUsage + @RequestMapping(value = "/surveillance-by-criteria", method = RequestMethod.GET, produces = "application/json; charset=utf-8") + public @ResponseBody List getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings() { + return reportDataManager.getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings(); + } + @Operation(summary = "Retrieves the data used to generate the Surveillance Activity Counts report.", description = "Retrieves the data used to generate the Surveillance Activity Counts report.", security = { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java index 66d727c7a9..fcc5f28393 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java @@ -136,8 +136,8 @@ private Properties additionalProperties() { // Set the two below properties to true to see the generated SQL // Very useful for debugging - properties.setProperty("hibernate.show_sql", "false"); - properties.setProperty("hibernate.format_sql", "false"); + properties.setProperty("hibernate.show_sql", "true"); + properties.setProperty("hibernate.format_sql", "true"); return properties; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java index 0d1ea0d1d6..31cd008c01 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java @@ -32,8 +32,11 @@ import gov.healthit.chpl.entity.developer.DeveloperSearchResultEntity; import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.exception.UserPermissionRetrievalException; +import gov.healthit.chpl.report.surveillance.SurveillanceByCriteria; import gov.healthit.chpl.report.surveillance.SurveillanceByDeveloper; import gov.healthit.chpl.search.entity.ListingSearchEntity; +import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.util.CertificationStatusUtil; import gov.healthit.chpl.util.NullSafeEvaluator; import jakarta.persistence.Query; import lombok.extern.log4j.Log4j2; @@ -52,13 +55,16 @@ public class SurveillanceDAO extends BaseDAOImpl { + "LEFT OUTER JOIN FETCH ncs.type nct " + "WHERE surv.deleted <> true "; + private CertificationCriterionService criteriaService; private String unformattedListingDetailsUrl; private String unformattedDeveloperDetailsUrl; @Autowired - public SurveillanceDAO(@Value("${chplUrlBegin}") String chplUrlBegin, + public SurveillanceDAO(CertificationCriterionService criteriaService, + @Value("${chplUrlBegin}") String chplUrlBegin, @Value("${developerUrlPart}") String developerUrlPart, @Value("${listingDetailsUrlPart}") String listingDetailsUrlPart) { + this.criteriaService = criteriaService; this.unformattedDeveloperDetailsUrl = chplUrlBegin + developerUrlPart; this.unformattedListingDetailsUrl = chplUrlBegin + listingDetailsUrlPart; } @@ -369,6 +375,39 @@ public List getSurveillanceOpenDuringTheLastYearForActi } + public List getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings() { + Query query = entityManager.createQuery("SELECT DISTINCT surv, reqType " + + "FROM SurveillanceEntity surv " + + "JOIN ListingSearchEntity listing ON listing.id = surv.certifiedProductId AND listing.certificationStatus IN (:activeCertificationStatusNames) " + + "JOIN SurveillanceTypeEntity surveillanceType ON surv.surveillanceTypeId = surveillanceType.id " + + "JOIN SurveillanceRequirementEntity req ON req.surveillanceId = surv.id AND req.deleted = false " + + "JOIN RequirementTypeEntity reqType ON req.requirementType.id = reqType.id " + + "WHERE (surv.endDate IS NULL OR surv.endDate >= :oneYearAgo) " + + "AND reqType.requirementGroupType.id = :criterionGroupTypeId " + + "AND surv.deleted = false"); + + List results = new ArrayList(); + List entities = query + .setParameter("oneYearAgo", LocalDate.now().minusYears(1)) + .setParameter("activeCertificationStatusNames", CertificationStatusUtil.getActiveStatusNames()) + .setParameter("criterionGroupTypeId", RequirementGroupType.CERTIFIED_CAPABILITY_ID) + .getResultList(); + + for (Object[] entity : entities) { + SurveillanceEntity surveillance = (SurveillanceEntity) entity[0]; + RequirementTypeEntity requirementType = (RequirementTypeEntity) entity[1]; + results.add(SurveillanceByCriteria.builder() + // RequirementType "id" matches the criterion ID in the db view + .criterion(criteriaService.get(requirementType.getId())) + .criterionSortOrder(criteriaService.getCriterionSortIndex(requirementType.getId())) + .surveillanceId(surveillance.getId()) + .surveillanceStartDate(surveillance.getStartDate()) + .surveillanceEndDate(surveillance.getEndDate()) + .build()); + } + return results; + } + public void deleteSurveillance(Surveillance surv) throws EntityRetrievalException { LOGGER.debug("Looking for surveillance with id " + surv.getId() + " to delete."); SurveillanceEntity toDelete = fetchSurveillanceById(surv.getId()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/entity/SurveillanceRequirementEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/entity/SurveillanceRequirementEntity.java index 37bee4ad19..25e10eaf98 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/entity/SurveillanceRequirementEntity.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/entity/SurveillanceRequirementEntity.java @@ -46,6 +46,9 @@ public class SurveillanceRequirementEntity extends EntityAudit { @Column(name = "surveillance_id") private Long surveillanceId; + @Column(name = "certification_criterion_id") + private Long criterionId; + @OneToOne(optional = true, fetch = FetchType.LAZY) @JoinColumn(name = "requirement_type_id") private RequirementTypeEntity requirementType; 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 c5f8086dcc..8c187252cb 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 @@ -33,6 +33,7 @@ import gov.healthit.chpl.report.surveillance.CapCounts; import gov.healthit.chpl.report.surveillance.NonconformityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceActivityCounts; +import gov.healthit.chpl.report.surveillance.SurveillanceByCriteria; import gov.healthit.chpl.report.surveillance.SurveillanceByDeveloper; import gov.healthit.chpl.report.surveillance.SurveillanceReportsService; import gov.healthit.chpl.report.svap.SvapReportService; @@ -119,6 +120,11 @@ public List getSurveillanceOpenDuringTheLastYearForActi return surveillanceReportsService.getSurveillanceOpenDuringTheLastYearForActiveDevelopers(); } + @Synchronized("lock") + public List getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings() { + return surveillanceReportsService.getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings(); + } + @Synchronized("lock") public SurveillanceActivityCounts getSurveillanceActivityCounts() { return surveillanceReportsService.getSurveiilanceActivityCounts(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/criteriaattribute/SvapReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/criteriaattribute/SvapReportDao.java index f7c59aafb5..112e932605 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/criteriaattribute/SvapReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/criteriaattribute/SvapReportDao.java @@ -197,7 +197,7 @@ public List getCriteriaWithAnySvap(CertificationStatus cert .listingCountAttestingToCriteria(cc.getCount()) .listingCountAttestingToCriteriaAndAnySvap(lookupCountByCriteria(criteriaWithAnySvapCounts, cc.getCriterion())) .certificationStatusName(certificationStatus.getName()) - .sortOrder(certificationCriterionService.getCertificationResultSortIndex(cc.getCriterion().getId())) + .sortOrder(certificationCriterionService.getCriterionSortIndex(cc.getCriterion().getId())) .build()) .peek(x -> LOGGER.info(x.toString())) .toList(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/nonconformity/NonconformityReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/nonconformity/NonconformityReportDao.java index aaa25e3ac6..db5ca7c98d 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/nonconformity/NonconformityReportDao.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/nonconformity/NonconformityReportDao.java @@ -45,7 +45,7 @@ public List getNonconformityCounts() { .filter(nc -> nc.getType().getId().equals(ncType.getId())) .count()) .nonconformityType(ncType.toDomain()) - .displayOrder(certificationCriterionService.getCertificationResultSortIndex(ncType.getId())) + .displayOrder(certificationCriterionService.getCriterionSortIndex(ncType.getId())) .build()) .filter(ncCount -> !ncCount.getCount().equals(0L)) .toList(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByCriteria.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByCriteria.java new file mode 100644 index 0000000000..3edc368808 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByCriteria.java @@ -0,0 +1,51 @@ +package gov.healthit.chpl.report.surveillance; + +import java.time.LocalDate; + +import org.apache.commons.lang3.StringUtils; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.certificationCriteria.CriterionStatus; +import gov.healthit.chpl.domain.concept.CertificationEditionConcept; +import gov.healthit.chpl.util.LocalDateDeserializer; +import gov.healthit.chpl.util.LocalDateSerializer; +import lombok.Builder; +import lombok.Data; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; + +@Data +@Builder +public class SurveillanceByCriteria { + + private CertificationCriterion criterion; + private Integer criterionSortOrder; + private Long surveillanceId; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate surveillanceStartDate; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate surveillanceEndDate; + + @JsonProperty(access = Access.READ_ONLY) + public CriterionStatus getStatus() { + if (!StringUtils.isEmpty(criterion.getCertificationEdition()) + && (criterion.getCertificationEdition().equals(CertificationEditionConcept.CERTIFICATION_EDITION_2011.getYear()) + || criterion.getCertificationEdition().equals(CertificationEditionConcept.CERTIFICATION_EDITION_2014.getYear()))) { + return CriterionStatus.RETIRED; + } else { + LocalDate end = criterion.getEndDay() != null ? criterion.getEndDay() : LocalDate.MAX; + if (end.isBefore(LocalDate.now())) { + return CriterionStatus.REMOVED; + } + return CriterionStatus.ACTIVE; + } + } + +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java new file mode 100644 index 0000000000..253dec99ea --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java @@ -0,0 +1,26 @@ +package gov.healthit.chpl.report.surveillance; + +import java.time.LocalDate; + +import gov.healthit.chpl.util.LocalDateDeserializer; +import gov.healthit.chpl.util.LocalDateSerializer; +import lombok.Builder; +import lombok.Data; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; + +@Data +@Builder +public class SurveillanceByRequirementType { + + private SurveillanceByRequirementType type; + private Long surveillanceId; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate surveillanceStartDate; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate surveillanceEndDate; +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java index b56c4e9527..bcb9d0171d 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java @@ -45,6 +45,11 @@ public List getSurveillanceOpenDuringTheLastYearForActi return surveillanceDao.getSurveillanceOpenDuringTheLastYearForActiveDevelopers(); } + @Transactional + public List getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings() { + return surveillanceDao.getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings(); + } + public SurveillanceActivityCounts getSurveiilanceActivityCounts() { StatisticsSnapshot stats = getStatistics(); return SurveillanceActivityCounts.builder() diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/svap/SvapReportService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/svap/SvapReportService.java index ea11fa29f1..612aa2abc9 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/svap/SvapReportService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/svap/SvapReportService.java @@ -78,7 +78,7 @@ public List getCertificationCriteria() { .filter(cc -> cc.getStatus().equals(CriterionStatus.ACTIVE)) .map(cc -> { CertificationCriterionWithOrder ccwo = new CertificationCriterionWithOrder(cc); - ccwo.setOrder(certificationCriterionService.getCertificationResultSortIndex(cc.getId()).longValue()); + ccwo.setOrder(certificationCriterionService.getCriterionSortIndex(cc.getId()).longValue()); return ccwo; }) .toList(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java index e564f90f13..49f10d1d90 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/service/CertificationCriterionService.java @@ -249,7 +249,7 @@ private String getFieldValue(Field field) { } public int sortCriteria(CertificationCriterion c1, CertificationCriterion c2) { - return getCertificationResultSortIndex(c1.getId()) - getCertificationResultSortIndex(c2.getId()); + return getCriterionSortIndex(c1.getId()) - getCriterionSortIndex(c2.getId()); } public static String formatCriteriaNumber(String number) { @@ -313,7 +313,7 @@ public String coerceToCriterionNumberFormat(String input) { return input; } - public Integer getCertificationResultSortIndex(Long criterionId) { + public Integer getCriterionSortIndex(Long criterionId) { Integer index = referenceSortingCriteriaList.indexOf(criterionId); if (index.equals(-1)) { // This is case when the criteria ID is not in the array, just make From 1b838bd999e0a5a2360fb99143431c8f95b4756c Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Mon, 14 Sep 2026 15:05:10 -0400 Subject: [PATCH 2/4] feat!: Add report data for surveillance-by-requirement-type [#OCD-5429] --- .../web/controller/ReportDataController.java | 12 +++++++ .../gov/healthit/chpl/CHPLServiceConfig.java | 4 +-- .../surveillance/SurveillanceDAO.java | 36 ++++++++++++++++++- .../chpl/report/ReportDataManager.java | 6 ++++ .../SurveillanceByRequirementType.java | 3 +- .../SurveillanceReportsService.java | 5 +++ 6 files changed, 62 insertions(+), 4 deletions(-) diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java index 7a216195b0..6cab06acc4 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/ReportDataController.java @@ -24,6 +24,7 @@ import gov.healthit.chpl.report.surveillance.SurveillanceActivityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceByCriteria; import gov.healthit.chpl.report.surveillance.SurveillanceByDeveloper; +import gov.healthit.chpl.report.surveillance.SurveillanceByRequirementType; import gov.healthit.chpl.scheduler.job.summarystatistics.data.CertificationBodyStatistic; import gov.healthit.chpl.search.domain.ListingSearchResult; import gov.healthit.chpl.util.LogMethodUsage; @@ -84,6 +85,17 @@ public ReportDataController(ReportDataManager reportDataManager, return reportDataManager.getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings(); } + @Operation(summary = "Retrieves the data about each surveillance including the requirement type.", + description = "Retrieves the data about each surveillance including the requirement type.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @LogMethodUsage + @RequestMapping(value = "/surveillance-by-requirement-type", method = RequestMethod.GET, produces = "application/json; charset=utf-8") + public @ResponseBody List getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings() { + return reportDataManager.getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings(); + } + @Operation(summary = "Retrieves the data used to generate the Surveillance Activity Counts report.", description = "Retrieves the data used to generate the Surveillance Activity Counts report.", security = { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java index fcc5f28393..66d727c7a9 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/CHPLServiceConfig.java @@ -136,8 +136,8 @@ private Properties additionalProperties() { // Set the two below properties to true to see the generated SQL // Very useful for debugging - properties.setProperty("hibernate.show_sql", "true"); - properties.setProperty("hibernate.format_sql", "true"); + properties.setProperty("hibernate.show_sql", "false"); + properties.setProperty("hibernate.format_sql", "false"); return properties; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java index 31cd008c01..4e49cdcede 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java @@ -34,6 +34,7 @@ import gov.healthit.chpl.exception.UserPermissionRetrievalException; import gov.healthit.chpl.report.surveillance.SurveillanceByCriteria; import gov.healthit.chpl.report.surveillance.SurveillanceByDeveloper; +import gov.healthit.chpl.report.surveillance.SurveillanceByRequirementType; import gov.healthit.chpl.search.entity.ListingSearchEntity; import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.util.CertificationStatusUtil; @@ -372,7 +373,6 @@ public List getSurveillanceOpenDuringTheLastYearForActi .build()); } return results; - } public List getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings() { @@ -408,6 +408,40 @@ public List getSurveillanceByCriteriaOpenDuringTheLastYe return results; } + public List getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings() { + Query query = entityManager.createQuery("SELECT DISTINCT surv, reqGroupType " + + "FROM SurveillanceEntity surv " + + "JOIN ListingSearchEntity listing ON listing.id = surv.certifiedProductId AND listing.certificationStatus IN (:activeCertificationStatusNames) " + + "JOIN SurveillanceTypeEntity surveillanceType ON surv.surveillanceTypeId = surveillanceType.id " + + "JOIN SurveillanceRequirementEntity req ON req.surveillanceId = surv.id AND req.deleted = false " + + "JOIN RequirementTypeEntity reqType ON req.requirementType.id = reqType.id " + + "JOIN RequirementGroupTypeEntity reqGroupType ON reqType.requirementGroupType.id = reqGroupType.id " + + "WHERE (surv.endDate IS NULL OR surv.endDate >= :oneYearAgo) " + + "AND surv.deleted = false"); + + List results = new ArrayList(); + List entities = query + .setParameter("oneYearAgo", LocalDate.now().minusYears(1)) + .setParameter("activeCertificationStatusNames", CertificationStatusUtil.getActiveStatusNames()) + .getResultList(); + + for (Object[] entity : entities) { + SurveillanceEntity surveillance = (SurveillanceEntity) entity[0]; + RequirementGroupTypeEntity requirementType = (RequirementGroupTypeEntity) entity[1]; + results.add(SurveillanceByRequirementType.builder() + // RequirementType "id" matches the criterion ID in the db view + .requirementType(RequirementGroupType.builder() + .id(requirementType.getId()) + .name(requirementType.getName()) + .build()) + .surveillanceId(surveillance.getId()) + .surveillanceStartDate(surveillance.getStartDate()) + .surveillanceEndDate(surveillance.getEndDate()) + .build()); + } + return results; + } + public void deleteSurveillance(Surveillance surv) throws EntityRetrievalException { LOGGER.debug("Looking for surveillance with id " + surv.getId() + " to delete."); SurveillanceEntity toDelete = fetchSurveillanceById(surv.getId()); 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 8c187252cb..6d385be518 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 @@ -35,6 +35,7 @@ import gov.healthit.chpl.report.surveillance.SurveillanceActivityCounts; import gov.healthit.chpl.report.surveillance.SurveillanceByCriteria; import gov.healthit.chpl.report.surveillance.SurveillanceByDeveloper; +import gov.healthit.chpl.report.surveillance.SurveillanceByRequirementType; import gov.healthit.chpl.report.surveillance.SurveillanceReportsService; import gov.healthit.chpl.report.svap.SvapReportService; import gov.healthit.chpl.scheduler.job.summarystatistics.data.CertificationBodyStatistic; @@ -125,6 +126,11 @@ public List getSurveillanceByCriteriaOpenDuringTheLastYe return surveillanceReportsService.getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings(); } + @Synchronized("lock") + public List getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings() { + return surveillanceReportsService.getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings(); + } + @Synchronized("lock") public SurveillanceActivityCounts getSurveillanceActivityCounts() { return surveillanceReportsService.getSurveiilanceActivityCounts(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java index 253dec99ea..f919e305e0 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java @@ -2,6 +2,7 @@ import java.time.LocalDate; +import gov.healthit.chpl.domain.surveillance.RequirementGroupType; import gov.healthit.chpl.util.LocalDateDeserializer; import gov.healthit.chpl.util.LocalDateSerializer; import lombok.Builder; @@ -13,7 +14,7 @@ @Builder public class SurveillanceByRequirementType { - private SurveillanceByRequirementType type; + private RequirementGroupType requirementType; private Long surveillanceId; @JsonDeserialize(using = LocalDateDeserializer.class) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java index bcb9d0171d..115c48c127 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceReportsService.java @@ -50,6 +50,11 @@ public List getSurveillanceByCriteriaOpenDuringTheLastYe return surveillanceDao.getSurveillanceByCriteriaOpenDuringTheLastYearForActiveListings(); } + @Transactional + public List getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings() { + return surveillanceDao.getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings(); + } + public SurveillanceActivityCounts getSurveiilanceActivityCounts() { StatisticsSnapshot stats = getStatistics(); return SurveillanceActivityCounts.builder() From 5fa0e406dd1146354c80dd8db353cbf212fcecd7 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Tue, 15 Sep 2026 10:33:05 -0400 Subject: [PATCH 3/4] build: Update maven library [#OCD-5429] --- chpl/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chpl/pom.xml b/chpl/pom.xml index 05ae372adf..6b41c1837d 100644 --- a/chpl/pom.xml +++ b/chpl/pom.xml @@ -25,7 +25,7 @@ 6.1.0 2.26.1 1.18.46 - 3.15.0 + 3.16.0 3.5.0 3.6.0 3.5.0 From feec9b7488a08f72dec03f0fb0ba0820759f1352 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 17 Sep 2026 14:17:20 -0400 Subject: [PATCH 4/4] feat: Add req type+group to response for drill-down in charts [#OCD-5429] --- .../surveillance/SurveillanceDAO.java | 58 ++++++++++++++++--- .../surveillance/RequirementGroupType.java | 1 + .../SurveillanceByRequirementType.java | 5 +- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java index 4e49cdcede..da69db1dc3 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/compliance/surveillance/SurveillanceDAO.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository; import gov.healthit.chpl.caching.CacheNames; +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.compliance.surveillance.entity.NonconformityTypeEntity; import gov.healthit.chpl.compliance.surveillance.entity.RequirementGroupTypeEntity; import gov.healthit.chpl.compliance.surveillance.entity.RequirementTypeEntity; @@ -21,6 +22,7 @@ import gov.healthit.chpl.compliance.surveillance.entity.SurveillanceResultTypeEntity; import gov.healthit.chpl.compliance.surveillance.entity.SurveillanceTypeEntity; import gov.healthit.chpl.dao.impl.BaseDAOImpl; +import gov.healthit.chpl.domain.CertificationEdition; import gov.healthit.chpl.domain.NonconformityType; import gov.healthit.chpl.domain.surveillance.RequirementGroupType; import gov.healthit.chpl.domain.surveillance.RequirementType; @@ -45,6 +47,8 @@ @Repository("surveillanceDAO") @Log4j2 public class SurveillanceDAO extends BaseDAOImpl { + private static final Long ICS_REQUIREMENT_OFFSET = 70000L; + private static final String SURVEILLANCE_FULL_HQL = "SELECT DISTINCT surv " + "FROM SurveillanceEntity surv " @@ -409,7 +413,7 @@ public List getSurveillanceByCriteriaOpenDuringTheLastYe } public List getSurveillanceByRequirementTypeOpenDuringTheLastYearForActiveListings() { - Query query = entityManager.createQuery("SELECT DISTINCT surv, reqGroupType " + Query query = entityManager.createQuery("SELECT DISTINCT surv, reqGroupType, reqType " + "FROM SurveillanceEntity surv " + "JOIN ListingSearchEntity listing ON listing.id = surv.certifiedProductId AND listing.certificationStatus IN (:activeCertificationStatusNames) " + "JOIN SurveillanceTypeEntity surveillanceType ON surv.surveillanceTypeId = surveillanceType.id " @@ -427,13 +431,13 @@ public List getSurveillanceByRequirementTypeOpenD for (Object[] entity : entities) { SurveillanceEntity surveillance = (SurveillanceEntity) entity[0]; - RequirementGroupTypeEntity requirementType = (RequirementGroupTypeEntity) entity[1]; + RequirementGroupTypeEntity requirementGroup = (RequirementGroupTypeEntity) entity[1]; + RequirementTypeEntity requirementType = (RequirementTypeEntity) entity[2]; + + RequirementType fullReqType = buildRequirementType(requirementGroup, requirementType); results.add(SurveillanceByRequirementType.builder() - // RequirementType "id" matches the criterion ID in the db view - .requirementType(RequirementGroupType.builder() - .id(requirementType.getId()) - .name(requirementType.getName()) - .build()) + .requirementType(fullReqType) + .criterionSortOrder(isCriterion(fullReqType) ? criteriaService.getCriterionSortIndex(fullReqType.getId()) : null) .surveillanceId(surveillance.getId()) .surveillanceStartDate(surveillance.getStartDate()) .surveillanceEndDate(surveillance.getEndDate()) @@ -442,6 +446,46 @@ public List getSurveillanceByRequirementTypeOpenD return results; } + private RequirementType buildRequirementType(RequirementGroupTypeEntity requirementGroup, RequirementTypeEntity requirementType) { + RequirementType type = RequirementType.builder() + .requirementGroupType(RequirementGroupType.builder() + .id(requirementGroup.getId()) + .name(requirementGroup.getName()) + .build()) + .title(requirementType.getTitle()) + .build(); + if (!StringUtils.isEmpty(requirementType.getNumber())) { + //it's a criterion, but does it have the ICS offset? + CertificationCriterion criterion = null; + if (requirementGroup.getId().equals(RequirementGroupType.INHERITED_CERTIFIED_STATUS_ID)) { + criterion = criteriaService.get(requirementType.getId() - ICS_REQUIREMENT_OFFSET); + } else { + criterion = criteriaService.get(requirementType.getId()); + } + + if (criterion != null) { + type.setCertificationEdition(CertificationEdition.builder() + .id(criterion.getCertificationEditionId()) + .name(criterion.getCertificationEdition()) + .build()); + type.setEdition(criterion.getCertificationEdition()); + type.setEndDay(criterion.getEndDay()); + type.setId(criterion.getId()); + type.setNumber(criterion.getNumber()); + type.setStartDay(criterion.getStartDay()); + } else { + LOGGER.error("Unable to find criterion for requirement group ID " + requirementGroup.getId() + " and requirement type ID " + requirementType.getId());; + } + } + return type; + } + + private boolean isCriterion(RequirementType type) { + return type.getRequirementGroupType().getId().equals(RequirementGroupType.CERTIFIED_CAPABILITY_ID) + || (type.getRequirementGroupType().getId().equals(RequirementGroupType.INHERITED_CERTIFIED_STATUS_ID) + && !StringUtils.isEmpty(type.getNumber())); + } + public void deleteSurveillance(Surveillance surv) throws EntityRetrievalException { LOGGER.debug("Looking for surveillance with id " + surv.getId() + " to delete."); SurveillanceEntity toDelete = fetchSurveillanceById(surv.getId()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/surveillance/RequirementGroupType.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/surveillance/RequirementGroupType.java index 97f39e35b3..0de339f1c6 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/surveillance/RequirementGroupType.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/surveillance/RequirementGroupType.java @@ -29,6 +29,7 @@ public class RequirementGroupType implements Serializable { public static final Long RWT_SUBMISSION_ID = 4L; public static final Long ATTESTATION_SUBMISSION_ID = 5L; public static final Long DEVELOPER_SUBMISSION_REQUIREMENT_ID = 6L; + public static final Long INHERITED_CERTIFIED_STATUS_ID = 7L; @Schema(description = "Requirement group type internal ID") private Long id; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java index f919e305e0..2604d9823c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/surveillance/SurveillanceByRequirementType.java @@ -2,7 +2,7 @@ import java.time.LocalDate; -import gov.healthit.chpl.domain.surveillance.RequirementGroupType; +import gov.healthit.chpl.domain.surveillance.RequirementType; import gov.healthit.chpl.util.LocalDateDeserializer; import gov.healthit.chpl.util.LocalDateSerializer; import lombok.Builder; @@ -14,7 +14,8 @@ @Builder public class SurveillanceByRequirementType { - private RequirementGroupType requirementType; + private RequirementType requirementType; + private Integer criterionSortOrder; private Long surveillanceId; @JsonDeserialize(using = LocalDateDeserializer.class)