diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/QuestionableActivityReportController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/QuestionableActivityReportController.java new file mode 100644 index 0000000000..3468c4745a --- /dev/null +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/QuestionableActivityReportController.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.questionableActivity.QuestionableActivityReport; +import gov.healthit.chpl.util.LogMethodUsage; +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/questionable-activity", description = "Allows retrieval of data used by Questionable Activity Report.") +@RestController +@RequestMapping("/report-data/questionable-activity") +public class QuestionableActivityReportController { + private ReportDataManager reportDataManager; + + @Autowired + public QuestionableActivityReportController(ReportDataManager reportDataManager) { + this.reportDataManager = reportDataManager; + } + + @Operation(summary = "Retrieves the data used to generate the Questionable Activity Report.", + security = { + @SecurityRequirement(name = SwaggerSecurityRequirement.API_KEY) + }) + @LogMethodUsage + @RequestMapping(value = "", method = RequestMethod.GET, produces = "application/json; charset=utf-8") + public @ResponseBody List getQuestionableActivityReports() { + return reportDataManager.getQuestionableActivityService().getQuestionableActivityReports(); + } +} \ No newline at end of file diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/QuestionableActivityTriggerConcept.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/QuestionableActivityTriggerConcept.java index 61a74aacf8..8e804635ce 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/QuestionableActivityTriggerConcept.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/QuestionableActivityTriggerConcept.java @@ -48,6 +48,12 @@ public enum QuestionableActivityTriggerConcept implements Serializable { CURES_UPDATE_REMOVED("Cures Update Designation Removed"), GAP_EDITED("GAP Status Edited"); + public static final String LEVEL_LISTING = "Listing"; + public static final String LEVEL_CRITERIA = "Certification Criteria"; + public static final String LEVEL_DEVELOPER = "Developer"; + public static final String LEVEL_PRODUCT = "Product"; + public static final String LEVEL_VERSION = "Version"; + private final String name; QuestionableActivityTriggerConcept(String input) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/entity/QuestionableActivitySearchResultEntity.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/entity/QuestionableActivitySearchResultEntity.java index c54266444d..2b9d3eb142 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/entity/QuestionableActivitySearchResultEntity.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/entity/QuestionableActivitySearchResultEntity.java @@ -3,15 +3,14 @@ import java.util.Date; import java.util.UUID; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - import org.hibernate.annotations.Immutable; import gov.healthit.chpl.questionableactivity.search.QuestionableActivitySearchResult; import gov.healthit.chpl.util.DateUtil; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -52,9 +51,6 @@ public class QuestionableActivitySearchResultEntity { @Column(name = "activity_sso_user_id") private UUID ssoUserId; - //@Column(name = "user_contact_info") - //private String username; - @Column(name = "certification_status_change_reason") private String certificationStatusChangeReason; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/search/QuestionableActivitySearchResult.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/search/QuestionableActivitySearchResult.java index ffd849b168..24b6d2b517 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/search/QuestionableActivitySearchResult.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/questionableactivity/search/QuestionableActivitySearchResult.java @@ -9,10 +9,9 @@ import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonIgnore; -import tools.jackson.databind.annotation.JsonDeserialize; -import tools.jackson.databind.annotation.JsonSerialize; import gov.healthit.chpl.domain.auth.User; +import gov.healthit.chpl.questionableactivity.QuestionableActivityTriggerConcept; import gov.healthit.chpl.util.DateUtil; import gov.healthit.chpl.util.EasternToSystemLocalDateTimeDeserializer; import gov.healthit.chpl.util.SystemToEasternLocalDateTimeSerializer; @@ -20,6 +19,8 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import tools.jackson.databind.annotation.JsonDeserialize; +import tools.jackson.databind.annotation.JsonSerialize; @Builder @AllArgsConstructor @@ -27,8 +28,6 @@ @Data public class QuestionableActivitySearchResult implements Serializable { private static final long serialVersionUID = -8153861360218726537L; - private static final String LEVEL_LISTING = "Listing"; - private static final String LEVEL_CRITERIA = "Certification Criteria"; private static final String ADDED_PREFIX = "Added"; private static final String REMOVED_PREFIX = "Removed"; @@ -96,7 +95,8 @@ public List toListOfStringsForCsv(String listingsReportUrlPartBegin) { csvFields.add(chplProductNumber); csvFields.add(certificationStatusName); csvFields.add(DateUtil.formatInEasternTime(DateUtil.fromSystemToEastern(activityDate))); - if (LEVEL_LISTING.equals(triggerLevel) || LEVEL_CRITERIA.equals(triggerLevel)) { + if (QuestionableActivityTriggerConcept.LEVEL_LISTING.equals(triggerLevel) + || QuestionableActivityTriggerConcept.LEVEL_CRITERIA.equals(triggerLevel)) { csvFields.add(listingsReportUrlPartBegin + "/" + listingId); } else { csvFields.add(""); 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..4d2689815e 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 @@ -25,6 +25,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.questionableActivity.QuestionableActivityReportService; import gov.healthit.chpl.report.questionableurl.QuestionableUrlReportService; import gov.healthit.chpl.report.realworldtesting.RealWorldTestingReportDataService; import gov.healthit.chpl.report.servicebaseurllistreport.ServiceBaseUrlListReportService; @@ -58,6 +59,7 @@ public class ReportDataManager { private CriteriaUpToDateReportService criteriaUpToDateReportService; private ListingAttributeReportService listingAttributeReportService; private QuestionableUrlReportService questionableUrlReportService; + private QuestionableActivityReportService questionableActivityReportService; private ServiceBaseUrlListReportService serviceBaseUrlListReportService; private SvapReportService svapReportService; private RealWorldTestingReportDataService realWorldTestingReportDataService; @@ -77,6 +79,7 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS CriteriaUpToDateReportService criteriaUpToDateReportService, ListingAttributeReportService listingAttributeReportService, QuestionableUrlReportService questionableUrlReportService, + QuestionableActivityReportService questionableActivityReportService, ServiceBaseUrlListReportService serviceBaseUrlListReportService, SvapReportService svapReportService, RealWorldTestingReportDataService realWorldTestingReportDataService, @@ -96,6 +99,7 @@ public ReportDataManager(CriteriaMigrationReportService criteriaMigrationReportS this.criteriaUpToDateReportService = criteriaUpToDateReportService; this.listingAttributeReportService = listingAttributeReportService; this.questionableUrlReportService = questionableUrlReportService; + this.questionableActivityReportService = questionableActivityReportService; this.serviceBaseUrlListReportService = serviceBaseUrlListReportService; this.svapReportService = svapReportService; this.realWorldTestingReportDataService = realWorldTestingReportDataService; @@ -299,6 +303,11 @@ public QuestionableUrlReportService getQuestionableUrlService() { return questionableUrlReportService; } + @Synchronized("lock") + public QuestionableActivityReportService getQuestionableActivityService() { + return questionableActivityReportService; + } + @Synchronized("lock") public CriteriaUpToDateReportService getCriteriaAttributeUpToDateService() { return criteriaUpToDateReportService; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReport.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReport.java new file mode 100644 index 0000000000..348bba9287 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReport.java @@ -0,0 +1,28 @@ +package gov.healthit.chpl.report.questionableActivity; + +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 QuestionableActivityReport { + private String activityType; + private String developer; + + @JsonDeserialize(using = LocalDateDeserializer.class) + @JsonSerialize(using = LocalDateSerializer.class) + private LocalDate activityDate; + + /** + * The CHPL Product Number, developer name, product name, ONC-ACB name, etc depending on the "type" of the activity + */ + private String relatedItem; + private Long relatedItemId; + private String relatedItemUrl; +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReportDao.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReportDao.java new file mode 100644 index 0000000000..48dd77cbd6 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReportDao.java @@ -0,0 +1,93 @@ +package gov.healthit.chpl.report.questionableActivity; + +import java.util.List; +import java.util.stream.Collectors; + +import org.ff4j.FF4j; +import org.joda.time.LocalDateTime; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Repository; + +import gov.healthit.chpl.dao.impl.BaseDAOImpl; +import gov.healthit.chpl.questionableactivity.QuestionableActivityTriggerConcept; +import gov.healthit.chpl.questionableactivity.entity.QuestionableActivitySearchResultEntity; +import gov.healthit.chpl.questionableactivity.search.QuestionableActivitySearchResult; +import jakarta.persistence.Query; + +@Repository +public class QuestionableActivityReportDao extends BaseDAOImpl { + private static final int REPORT_MONTHS = 6; + + private String unformattedListingDetailsUrl; + private String unformattedDeveloperDetailsUrl; + private FF4j ff4j; + + @Autowired + public QuestionableActivityReportDao(@Value("${chplUrlBegin}") String chplUrlBegin, + @Value("${listingDetailsUrlPart}") String listingDetailsUrlPart, + @Value("${developerUrlPart}") String developerUrlPart) { + this.unformattedListingDetailsUrl = chplUrlBegin + listingDetailsUrlPart; + this.unformattedDeveloperDetailsUrl = chplUrlBegin + developerUrlPart; + } + + public List getQuestionableActivityReports() { + Query query = entityManager.createQuery("SELECT qa " + + "FROM QuestionableActivitySearchResultEntity qa " + + "WHERE qa.activityDate >= :activityDate ", + QuestionableActivitySearchResultEntity.class); + query.setParameter("activityDate", LocalDateTime.now().minusMonths(REPORT_MONTHS).toDate()); + + List queryResults = query.getResultList(); + List results = queryResults.stream() + .map(entity -> entity.toDomain()) + .map(activity -> QuestionableActivityReport.builder() + .activityType(activity.getTriggerName()) + .activityDate(activity.getActivityDate().toLocalDate()) + .developer(activity.getDeveloperName()) + .relatedItem(getRelatedItemName(activity)) + .relatedItemId(getRelatedItemId(activity)) + .relatedItemUrl(getRelatedItemUrl(activity)) + .build()) + .collect(Collectors.toList()); + return results; + } + + private String getRelatedItemName(QuestionableActivitySearchResult activity) { + if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_LISTING) + || activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_CRITERIA)) { + return activity.getChplProductNumber(); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_DEVELOPER)) { + return activity.getDeveloperName(); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_PRODUCT)) { + return activity.getProductName(); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_VERSION)) { + return activity.getVersionName(); + } + return activity.getDescription(); + } + + private Long getRelatedItemId(QuestionableActivitySearchResult activity) { + if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_LISTING) + || activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_CRITERIA)) { + return activity.getListingId(); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_DEVELOPER)) { + return activity.getDeveloperId(); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_PRODUCT)) { + return activity.getProductId(); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_VERSION)) { + return activity.getVersionId(); + } + return null; + } + + private String getRelatedItemUrl(QuestionableActivitySearchResult activity) { + if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_LISTING) + || activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_CRITERIA)) { + return String.format(unformattedListingDetailsUrl, activity.getListingId()); + } else if (activity.getTriggerLevel().equals(QuestionableActivityTriggerConcept.LEVEL_DEVELOPER)) { + return String.format(unformattedDeveloperDetailsUrl, activity.getDeveloperId()); + } + return null; + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReportService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReportService.java new file mode 100644 index 0000000000..badf432e54 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/report/questionableActivity/QuestionableActivityReportService.java @@ -0,0 +1,25 @@ +package gov.healthit.chpl.report.questionableActivity; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Component +public class QuestionableActivityReportService { + private QuestionableActivityReportDao questionableActivityReportDao; + + @Autowired + public QuestionableActivityReportService(QuestionableActivityReportDao questionableActivityReportDao) { + this.questionableActivityReportDao = questionableActivityReportDao; + } + + @Transactional + public List getQuestionableActivityReports() { + return questionableActivityReportDao.getQuestionableActivityReports(); + } +}