Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4aedf42
feat: Exclude removed criteria from RWT eligibility checks
kekey1 Jul 21, 2026
5a73bfa
feat-flag!: Include different RWT criteria in report based on flag state
kekey1 Jul 22, 2026
c00493d
Merge branch 'staging' into OCD-5321
kekey1 Jul 22, 2026
aa2e672
flat-feat!: Dynamically add RWT cols to attestation check-in report
kekey1 Jul 22, 2026
dedc04a
Revert "flat-feat!: Dynamically add RWT cols to attestation check-in …
kekey1 Jul 23, 2026
d3c9e2b
fix: Use correct grammar in RWT email body
kekey1 Jul 23, 2026
fb3b235
feat: Append "SVAP" to non-G-criteria column headings
kekey1 Jul 23, 2026
fbf37d5
feat: Include SVAP in RWT eligibility calculations
kekey1 Jul 23, 2026
f7f2f14
fix: Correctly print CSV row
kekey1 Jul 23, 2026
cb7b862
fix: Name column correctly
kekey1 Jul 24, 2026
7124b2c
fix: Correctly comment the HTI-5 requirement
kekey1 Jul 24, 2026
f753646
feat-flag!: Exclude RWT Plans from Questionable URL report calculations
kekey1 Jul 27, 2026
c6a8810
feat-flag!: Remove access to RWT Plans URL/Check Date in API response
kekey1 Jul 27, 2026
8e6216b
feat-flag!: Ignore RWT Plans columns in listing upload, csv download
kekey1 Jul 27, 2026
3dadb93
flag-feat: Do not save RWT Plans on listing create, update
kekey1 Jul 27, 2026
13ebcd4
feat: Exclude RWT Plans fields from "basic" API response
kekey1 Jul 28, 2026
69d0412
feat-flag: Remove RWT Plans from RWT email body
kekey1 Jul 28, 2026
e626b52
feat-flag: Do not allow RWT Plans change request type to be created
kekey1 Jul 28, 2026
301a4b8
feat-flag: Exclude RWT Plans in change request types response
kekey1 Jul 28, 2026
03b6995
feat-flag: Do not include RWT Plans in questionable url data gathering
kekey1 Jul 28, 2026
aaf7d9e
feat-flag: Stop recording questionable activity for RWT Plans changes
kekey1 Jul 28, 2026
62c0130
feat-flag: Do not include RWT Plans cr attachment in cr report email
kekey1 Jul 28, 2026
31315ef
feat-flag: Do not count Plans in RWT summary overnight job
kekey1 Jul 28, 2026
f925ed1
feat-flag: Ignore RWT Plans in special RWT upload file
kekey1 Jul 28, 2026
6a67f7e
feat-flag: Ignore RWT Plans subscriptions and don't create new ones
kekey1 Jul 28, 2026
878c90c
feat-flag: Remove RWT Plans from listing search result data
kekey1 Jul 28, 2026
34e6888
Merge branch 'staging' into OCD-5321
kekey1 Aug 4, 2026
ea93f72
Merge branch 'staging' into OCD-5385
kekey1 Aug 4, 2026
765b44d
Merge branch 'OCD-5321' into OCD-5385
kekey1 Aug 4, 2026
088d2bf
feat-flag: Remove RWT Plans from RWT Report attachment
kekey1 Aug 4, 2026
7a0737a
feat-flag: Ignore Plans updates in RWT Upload file
kekey1 Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.stereotype.Component;

import gov.healthit.chpl.api.deprecatedUsage.DeprecatedResponseField;
import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData;
import gov.healthit.chpl.sed.DeprecatedSedSummaryData;
import gov.healthit.chpl.sed.DeprecatedSedTestTaskData;
import lombok.extern.log4j.Log4j2;
Expand All @@ -29,7 +30,7 @@ public boolean hasIgnoreMarker(MapperConfig<?> config, AnnotatedMember m) {
if (super.hasIgnoreMarker(config, m)) {
return true;
} else {
return isDeprecatedAndIgnorable(m) || isSedAndIgnorable(m);
return isDeprecatedAndIgnorable(m) || isSedAndIgnorable(m) || isRwtAndIgnorable(m);
}
}

Expand All @@ -45,4 +46,10 @@ private boolean isSedAndIgnorable(AnnotatedMember m) {
boolean isSedTestTask = _findAnnotation(m, DeprecatedSedTestTaskData.class) != null;
return isHti5Erd && (isSedSummary || isSedTestTask);
}

private boolean isRwtAndIgnorable(AnnotatedMember m) {
boolean isHti5Erd = ff4j.check(FeatureList.HTI_5_ERD);
boolean isRwtPlans = _findAnnotation(m, DeprecatedRwtPlansData.class) != null;
return isHti5Erd && isRwtPlans;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,10 @@ realWorldTestingCriteriaKeys={2021: 'criterion.170_315_b_1_old,\
criterion.170_315_g_7,\
criterion.170_315_g_8,\
criterion.170_315_g_9_cures,\
criterion.170_315_g_10,\
criterion.170_315_g_31,\
criterion.170_315_g_32,\
criterion.170_315_g_33,\
criterion.170_315_g_10,\
criterion.170_315_h_1,\
criterion.170_315_h_2,\
criterion.170_315_j_20,\
Expand Down
1 change: 1 addition & 0 deletions chpl/chpl-resources/src/main/resources/errors.properties
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ changeRequest.listingUrl.serviceBaseUrlList.sameUrl=No change to the Service Bas
changeRequest.listingUrl.serviceBaseUrlList.missing=The Service Base URL List may not be blank for %s.
changeRequest.missingDetails=Details about what is being changed are required.
changeRequest.listingUrl.rwtUrl.featureDisabled=Creating a Change Request to update a Real World Testing URL is not enabled.
changeRequest.listingUrl.rwtPlans.notAllowed=Creating a change request for an RWT Plans URL is not allowed.
changeRequest.listingUrl.rwtUrl.missing=The Real World Testing URL may not be blank for %s.
changeRequest.listingUrl.checkDateRequired=The Check Date is required when accepting a Real World Testing URL change request.
changeRequest.listingUrl.checkDateFuture=The Check Date may not be in the future when accepting a Real World Testing URL change request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,25 @@ public class ListingCsvDataWriter {
private static final int DEVELOPER_CONTACT_COL_START = 18;
private static final int SVAP_NOTICE_URL_COL = 21;
private static final int RWT_COL_START = 22;
private static final int TARGETED_USERS_COL = 26;
private static final int QMS_STD_START_COL = 27;
private static final int ICS_START_COL = 30;
private static final int ACCESSIBILITY_STANDARDS_START_COL = 32;
private static final int K1_URL_COL = 34;
private static final int CQM_START_COL = 35;
private static final int TARGETED_USERS_COL = 24;
private static final int TARGETED_USERS_COL_PRE_HTI5 = 26;
private static final int QMS_STD_START_COL = 25;
private static final int QMS_STD_START_COL_PRE_HTI5 = 27;
private static final int ICS_START_COL = 28;
private static final int ICS_START_COL_PRE_HTI5 = 30;
private static final int ACCESSIBILITY_STANDARDS_START_COL = 30;
private static final int ACCESSIBILITY_STANDARDS_START_COL_PRE_HTI5 = 32;
private static final int K1_URL_COL = 32;
private static final int K1_URL_COL_PRE_HTI5 = 34;
private static final int CQM_START_COL = 33;
private static final int CQM_START_COL_PRE_HTI5 = 35;
private static final int SED_REPORT_URL_COL = 38;
private static final int SED_INTENDED_USERS_COL = 39;
private static final int SED_TESTING_DATE_COL = 40;
private static final int PARTICIPANT_START_COL = 41;
private static final int TASK_START_COL = 50;
private static final int CRITERIA_START_COL_PRE_HTI5 = 65;
private static final int CRITERIA_START_COL = 38;
private static final int CRITERIA_START_COL = 36;
private static final int ADDITIONAL_SOFTWARE_COL_COUNT = 5;
private static final int UCD_PROCESS_COL_COUNT = 2;
private static final int SED_TESTING_COL_COUNT = 2;
Expand Down Expand Up @@ -125,7 +131,7 @@ public List<List<String>> getCsvData(CertifiedProductSearchDetails listing, int
addQmsStandards(csvDataMatrix, listing.getQmsStandards());
addIcs(csvDataMatrix, listing.getIcs());
addAccessibilityStandards(csvDataMatrix, listing.getAccessibilityStandards());
csvDataMatrix[0][K1_URL_COL] = listing.getMandatoryDisclosures();
csvDataMatrix[0][(!ff4j.check(FeatureList.HTI_5_ERD) ? K1_URL_COL_PRE_HTI5 : K1_URL_COL)] = listing.getMandatoryDisclosures();
addCqms(csvDataMatrix, listing.getCqmResults());
if (!ff4j.check(FeatureList.HTI_5_ERD)) {
csvDataMatrix[0][SED_REPORT_URL_COL] = listing.getSedReportFileLocation();
Expand Down Expand Up @@ -333,47 +339,50 @@ private void addDeveloperContact(String[][] csvDataMatrix, Developer developer)

private void addRwtData(String[][] csvDataMatrix, CertifiedProductSearchDetails listing) {
int col = RWT_COL_START;
csvDataMatrix[0][col++] = !StringUtils.isEmpty(listing.getRwtPlansUrl()) ? listing.getRwtPlansUrl() : "";
csvDataMatrix[0][col++] = listing.getRwtPlansCheckDate() != null ? dateFormat.format(DateUtil.toDate(listing.getRwtPlansCheckDate())) : "";
if (!ff4j.check(FeatureList.HTI_5_ERD)) {
csvDataMatrix[0][col++] = !StringUtils.isEmpty(listing.getRwtPlansUrl()) ? listing.getRwtPlansUrl() : "";
csvDataMatrix[0][col++] = listing.getRwtPlansCheckDate() != null ? dateFormat.format(DateUtil.toDate(listing.getRwtPlansCheckDate())) : "";
}
csvDataMatrix[0][col++] = !StringUtils.isEmpty(listing.getRwtResultsUrl()) ? listing.getRwtResultsUrl() : "";
csvDataMatrix[0][col++] = listing.getRwtResultsCheckDate() != null ? dateFormat.format(DateUtil.toDate(listing.getRwtResultsCheckDate())) : "";
}

private void addTargetedUsers(String[][] csvDataMatrix, List<CertifiedProductTargetedUser> targetedUsers) {
int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? TARGETED_USERS_COL_PRE_HTI5 : TARGETED_USERS_COL;
for (int i = 0; i < targetedUsers.size(); i++) {
CertifiedProductTargetedUser targetedUser = targetedUsers.get(i);
csvDataMatrix[i][TARGETED_USERS_COL] = targetedUser.getTargetedUserName();
csvDataMatrix[i][currCol] = targetedUser.getTargetedUserName();
}
}

private void addQmsStandards(String[][] csvDataMatrix, List<CertifiedProductQmsStandard> qmsStandards) {
for (int i = 0; i < qmsStandards.size(); i++) {
int col = QMS_STD_START_COL;
int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? QMS_STD_START_COL_PRE_HTI5 : QMS_STD_START_COL;
CertifiedProductQmsStandard qmsStandard = qmsStandards.get(i);
csvDataMatrix[i][col++] = qmsStandard.getQmsStandardName();
csvDataMatrix[i][col++] = qmsStandard.getApplicableCriteria();
csvDataMatrix[i][col++] = qmsStandard.getQmsModification();
csvDataMatrix[i][currCol++] = qmsStandard.getQmsStandardName();
csvDataMatrix[i][currCol++] = qmsStandard.getApplicableCriteria();
csvDataMatrix[i][currCol++] = qmsStandard.getQmsModification();
}
}

private void addIcs(String[][] csvDataMatrix, InheritedCertificationStatus ics) {
if (ics != null && !CollectionUtils.isEmpty(ics.getParents())) {
int col = ICS_START_COL;
csvDataMatrix[0][col++] = "1";
int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? ICS_START_COL_PRE_HTI5 : ICS_START_COL;
csvDataMatrix[0][currCol++] = "1";
for (int i = 0; i < ics.getParents().size(); i++) {
CertifiedProduct parent = ics.getParents().get(i);
csvDataMatrix[i][col++] = parent.getChplProductNumber();
csvDataMatrix[i][currCol++] = parent.getChplProductNumber();
}
}
}

private void addAccessibilityStandards(String[][] csvDataMatrix, List<CertifiedProductAccessibilityStandard> accStds) {
if (!CollectionUtils.isEmpty(accStds)) {
int col = ACCESSIBILITY_STANDARDS_START_COL;
csvDataMatrix[0][col++] = "1";
int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? ACCESSIBILITY_STANDARDS_START_COL_PRE_HTI5 : ACCESSIBILITY_STANDARDS_START_COL;
csvDataMatrix[0][currCol++] = "1";
for (int i = 0; i < accStds.size(); i++) {
CertifiedProductAccessibilityStandard accStd = accStds.get(i);
csvDataMatrix[i][col] = accStd.getAccessibilityStandardName();
csvDataMatrix[i][currCol] = accStd.getAccessibilityStandardName();
}
}
}
Expand All @@ -384,11 +393,11 @@ private void addCqms(String[][] csvDataMatrix, List<CQMResultDetails> cqmResults
.collect(Collectors.toList());
for (int i = 0; i < attestedCqms.size(); i++) {
CQMResultDetails cqm = attestedCqms.get(i);
int col = CQM_START_COL;
csvDataMatrix[i][col++] = cqm.getCmsId();
csvDataMatrix[i][col++] =
int currCol = !ff4j.check(FeatureList.HTI_5_ERD) ? CQM_START_COL_PRE_HTI5 : CQM_START_COL;
csvDataMatrix[i][currCol++] = cqm.getCmsId();
csvDataMatrix[i][currCol++] =
cqm.getSuccessVersions().stream().collect(Collectors.joining(";"));
csvDataMatrix[i][col++] =
csvDataMatrix[i][currCol++] =
cqm.getCriteria().stream()
.map(crit -> Util.formatCriteriaNumber(crit.getCriterion()))
.collect(Collectors.joining(";"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,17 @@ public List<String> getCsvHeadings(CertifiedProductSearchDetails listing) {
HeadingPostHti5.DEVELOPER_EMAIL.getHeading(),
HeadingPostHti5.DEVELOPER_PHONE.getHeading(),
HeadingPostHti5.DEVELOPER_CONTACT_NAME.getHeading(),
HeadingPostHti5.SVAP_NOTICE_URL.getHeading(),
HeadingPostHti5.RWT_PLANS_URL.getHeading(),
HeadingPostHti5.RWT_PLANS_CHECK_DATE.getHeading(),
HeadingPostHti5.SVAP_NOTICE_URL.getHeading())
.collect(Collectors.toList());

if (!ff4j.check(FeatureList.HTI_5_ERD)) {
headings.addAll(Stream.of(
HeadingPreHti5.RWT_PLANS_URL.getHeading(),
HeadingPreHti5.RWT_PLANS_CHECK_DATE.getHeading())
.collect(Collectors.toList()));
}

headings.addAll(Stream.of(
HeadingPostHti5.RWT_RESULTS_URL.getHeading(),
HeadingPostHti5.RWT_RESULTS_CHECK_DATE.getHeading(),
HeadingPostHti5.TARGETED_USERS.getHeading(),
Expand All @@ -81,8 +89,8 @@ public List<String> getCsvHeadings(CertifiedProductSearchDetails listing) {
HeadingPostHti5.K_1_URL.getHeading(),
HeadingPostHti5.CQM_NUMBER.getHeading(),
HeadingPostHti5.CQM_VERSION.getHeading(),
HeadingPostHti5.CQM_CRITERIA.getHeading()
).collect(Collectors.toList());
HeadingPostHti5.CQM_CRITERIA.getHeading())
.collect(Collectors.toList()));

if (!ff4j.check(FeatureList.HTI_5_ERD)) {
headings.addAll(Stream.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ public List<ChangeRequestType> getChangeRequestTypes() {
|| (type.getName().equals(ChangeRequestType.SBUL_TYPE)
&& ff4j.check(FeatureList.SERVICE_BASE_URL_LIST_CHANGE_REQUEST))
|| (type.getName().equals(ChangeRequestType.RWT_PLANS_TYPE)
&& ff4j.check(FeatureList.RWT_CHANGE_REQUEST))
&& ff4j.check(FeatureList.RWT_CHANGE_REQUEST)
&& !ff4j.check(FeatureList.HTI_5_ERD))
|| (type.getName().equals(ChangeRequestType.RWT_RESULTS_TYPE)
&& ff4j.check(FeatureList.RWT_CHANGE_REQUEST))
)
Expand All @@ -190,6 +191,10 @@ public ChangeRequest createChangeRequest(ChangeRequest changeRequest)
if (!ff4j.check(FeatureList.SERVICE_BASE_URL_LIST_CHANGE_REQUEST)
&& changeRequest.getChangeRequestType().isSbul()) {
throw new InvalidArgumentsException(msgUtil.getMessage("changeRequest.listingUrl.serviceBaseUrlList.featureDisabled"));
} else if (ff4j.check(FeatureList.RWT_CHANGE_REQUEST)
&& ff4j.check(FeatureList.HTI_5_ERD)
&& changeRequest.getChangeRequestType().isRwtPlans()) {
throw new InvalidArgumentsException(msgUtil.getMessage("changeRequest.listingUrl.rwtPlans.notAllowed"));
} else if (!ff4j.check(FeatureList.RWT_CHANGE_REQUEST)
&& isRwtChangeRequestType(changeRequest.getChangeRequestType())) {
throw new InvalidArgumentsException(msgUtil.getMessage("changeRequest.listingUrl.rwtUrl.featureDisabled"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ public Long create(CertifiedProductSearchDetails listing) throws EntityCreationE
entity.setIcs(listing.getIcs() == null || listing.getIcs().getInherits() == null ? Boolean.FALSE : listing.getIcs().getInherits());
entity.setAccessibilityCertified(listing.getAccessibilityCertified());
entity.setSvapNoticeUrl(listing.getSvapNoticeUrl());
entity.setRwtPlansUrl(listing.getRwtPlansUrl());
entity.setRwtPlansCheckDate(listing.getRwtPlansCheckDate());
if (!ff4j.check(FeatureList.HTI_5_ERD)) {
entity.setRwtPlansUrl(listing.getRwtPlansUrl());
entity.setRwtPlansCheckDate(listing.getRwtPlansCheckDate());
}
entity.setRwtResultsUrl(listing.getRwtResultsUrl());
entity.setRwtResultsCheckDate(listing.getRwtResultsCheckDate());
entity.setChplProductNumber(null);
Expand Down Expand Up @@ -122,8 +124,10 @@ public CertifiedProductDTO update(CertifiedProductDTO dto) throws EntityRetrieva
entity.setCertificationBodyId(dto.getCertificationBodyId());
entity.setCertificationEditionId(dto.getCertificationEditionId());
entity.setProductVersionId(dto.getProductVersionId());
entity.setRwtPlansUrl(dto.getRwtPlansUrl());
entity.setRwtPlansCheckDate(dto.getRwtPlansCheckDate());
if (!ff4j.check(FeatureList.HTI_5_ERD)) {
entity.setRwtPlansUrl(dto.getRwtPlansUrl());
entity.setRwtPlansCheckDate(dto.getRwtPlansCheckDate());
}
entity.setRwtResultsUrl(dto.getRwtResultsUrl());
entity.setRwtResultsCheckDate(dto.getRwtResultsCheckDate());
entity.setSvapNoticeUrl(dto.getSvapNoticeUrl());
Expand Down Expand Up @@ -623,7 +627,9 @@ public List<CertifiedProductSummaryDTO> getSummaryByUrl(final String url, final
queryStr += " AND cp.sedReportFileLocation = :url ";
break;
case REAL_WORLD_TESTING_PLANS:
queryStr += " AND cp.rwtPlansUrl = :url ";
if (!ff4j.check(FeatureList.HTI_5_ERD)) {
queryStr += " AND cp.rwtPlansUrl = :url ";
}
break;
case REAL_WORLD_TESTING_RESULTS:
queryStr += " AND cp.rwtResultsUrl = :url ";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonProperty.Access;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonSerialize;

import gov.healthit.chpl.domain.compliance.DirectReview;
import gov.healthit.chpl.domain.surveillance.Surveillance;
import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData;
import gov.healthit.chpl.sed.CertifiedProductSed;
import gov.healthit.chpl.targeteduser.CertifiedProductTargetedUser;
import gov.healthit.chpl.util.DateUtil;
Expand All @@ -26,6 +25,8 @@
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonSerialize;

@Data
@Builder
Expand Down Expand Up @@ -192,9 +193,11 @@ public class CertifiedProductSearchBasicDetails implements Serializable {
@Schema(description = "Indicates whether the direct reviews were available when the call was made")
private boolean directReviewsAvailable;

@DeprecatedRwtPlansData
@Schema(description = "URL where the listing's Real World Testing Plan is located")
private String rwtPlansUrl;

@DeprecatedRwtPlansData
@Schema(description = "Date the listing's Real World Testing Plan was submitted")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import gov.healthit.chpl.domain.compliance.DirectReview;
import gov.healthit.chpl.domain.surveillance.Surveillance;
import gov.healthit.chpl.entity.CertificationStatusType;
import gov.healthit.chpl.realworldtesting.DeprecatedRwtPlansData;
import gov.healthit.chpl.sed.CertifiedProductSed;
import gov.healthit.chpl.sed.DeprecatedSedSummaryData;
import gov.healthit.chpl.targeteduser.CertifiedProductTargetedUser;
Expand Down Expand Up @@ -250,9 +251,11 @@ public class CertifiedProductSearchDetails implements Serializable {
@Schema(description = "All data related to safety-enhanced design for this listing.")
private CertifiedProductSed sed = new CertifiedProductSed();

@DeprecatedRwtPlansData
@Schema(description = "URL where the listings Real World Testing Plan is located")
private String rwtPlansUrl;

@DeprecatedRwtPlansData
@Schema(description = "Date the listings Real World Testing Plan was submitted")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
Expand Down
Loading
Loading