From f179bc1f6c0e879de8f37f985aa3ecc8d385e743 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 23 Apr 2026 09:42:12 -0400 Subject: [PATCH 01/14] feat!: Add 2026 CMS ID Validator [#OCD-4928] --- .../certificationId/CertificationIdDAO.java | 12 +- .../chpl/certificationId/Validator2026.java | 131 ++++++++++++++++++ .../certificationId/ValidatorFactory.java | 13 +- 3 files changed, 138 insertions(+), 18 deletions(-) create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java index 82d15405fa..0f03460b64 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java @@ -1,6 +1,5 @@ package gov.healthit.chpl.certificationId; -import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -307,7 +306,7 @@ private List getAllCertificationIdsWit private String generateCertificationIdString(String year) throws EntityCreationException { StringBuffer newId = new StringBuffer(); - newId.append(getYearPartOfNewCertIdString(year)); + newId.append(year); newId.append("C"); int suffixLength = (CERT_ID_LENGTH - newId.length()); @@ -335,13 +334,4 @@ private String generateCertificationIdString(String year) throws EntityCreationE return newId.toString(); } - - private String getYearPartOfNewCertIdString(String year) { - LocalDate now = LocalDate.now(); - //TODO: Remove with //OCD-4928 - if (now.isBefore(certIdYearCalculator.getInitialCmsIdTransitionToAnnualFormatDay())) { - return "00" + year.substring(year.length() - 2); - } - return year; - } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java new file mode 100644 index 0000000000..48a3e107b4 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -0,0 +1,131 @@ +package gov.healthit.chpl.certificationId; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; +import gov.healthit.chpl.util.Util; + +/** + * Validator for CMS EHR ID generation for the year 2026 + */ +public class Validator2026 extends Validator { + + private List requiredCriteria; + private List cpoeCriteriaOr; + private List dpCriteriaOr; + + public Validator2026(CertificationCriterionService certificationCriterionService) { + + requiredCriteria = Stream.of(certificationCriterionService.get(Criteria2015.A_5), + certificationCriterionService.get(Criteria2015.A_14), + certificationCriterionService.get(Criteria2015.B_1_CURES), + certificationCriterionService.get(Criteria2015.B_11), + certificationCriterionService.get(Criteria2015.C_1), + certificationCriterionService.get(Criteria2015.G_7), + certificationCriterionService.get(Criteria2015.G_9_CURES), + certificationCriterionService.get(Criteria2015.G_10)).collect(Collectors.toCollection(ArrayList::new)); + + cpoeCriteriaOr = Stream.of(certificationCriterionService.get(Criteria2015.A_1), + certificationCriterionService.get(Criteria2015.A_2), + certificationCriterionService.get(Criteria2015.A_3)) + .collect(Collectors.toList()); + + dpCriteriaOr = Stream.of(certificationCriterionService.get(Criteria2015.H_1), + certificationCriterionService.get(Criteria2015.H_2)) + .collect(Collectors.toList()); + + this.counts.put("criteriaRequired", requiredCriteria.size()); + this.counts.put("criteriaRequiredMet", 0); + this.counts.put("criteriaCpoeRequired", 1); + this.counts.put("criteriaCpoeRequiredMet", 0); + this.counts.put("criteriaDpRequired", 1); + this.counts.put("criteriaDpRequiredMet", 0); + this.counts.put("criteriaDsRequired", 0); + this.counts.put("criteriaDsRequiredMet", 0); + this.counts.put("cqmsInpatientRequired", 0); + this.counts.put("cqmsInpatientRequiredMet", 0); + this.counts.put("cqmsAmbulatoryRequired", 0); + this.counts.put("cqmsAmbulatoryRequiredMet", 0); + this.counts.put("cqmsAmbulatoryCoreRequired", 0); + this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); + this.counts.put("domainsRequired", 0); + this.counts.put("domainsRequiredMet", 0); + } + + public boolean onValidate() { + return isCriteriaValid(); + } + + protected boolean isCriteriaValid() { + this.counts.put("criteriaRequired", requiredCriteria.size()); + boolean requiredCriteriaValid = true; + for (CertificationCriterion crit : requiredCriteria) { + Optional metRequiredCriterion = criteriaMet.keySet().stream() + .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) + .findAny(); + + if (metRequiredCriterion.isPresent()) { + this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + } else { + missingAnd.add(Util.formatCriteriaNumber(crit)); + requiredCriteriaValid = false; + } + } + + boolean cpoeValid = isCPOEValid(); + boolean dpValid = isDPValid(); + + this.counts.put("criteriaRequired", + this.counts.get("criteriaRequired") + + this.counts.get("criteriaCpoeRequired") + + this.counts.get("criteriaDsRequired") + + this.counts.get("criteriaDpRequired")); + this.counts.put("criteriaRequiredMet", + this.counts.get("criteriaRequiredMet") + + this.counts.get("criteriaCpoeRequiredMet") + + this.counts.get("criteriaDsRequiredMet") + + this.counts.get("criteriaDpRequiredMet")); + + return (requiredCriteriaValid && cpoeValid && dpValid); + } + + protected boolean isCPOEValid() { + for (CertificationCriterion crit : cpoeCriteriaOr) { + if (criteriaMetContainsCriterion(crit)) { + this.counts.put("criteriaCpoeRequiredMet", 1); + return true; + } + } + missingOr.add(cpoeCriteriaOr.stream() + .map(cpoeCrit -> Util.formatCriteriaNumber(cpoeCrit)) + .collect(Collectors.toCollection(ArrayList::new))); + return false; + } + + protected boolean isDPValid() { + for (CertificationCriterion crit : dpCriteriaOr) { + if (criteriaMetContainsCriterion(crit)) { + this.counts.put("criteriaDpRequiredMet", 1); + return true; + } + } + missingOr.add(dpCriteriaOr.stream() + .map(dpCrit -> Util.formatCriteriaNumber(dpCrit)) + .collect(Collectors.toCollection(ArrayList::new))); + return false; + } + + protected boolean isCqmsValid() { + return true; + } + + protected boolean isDomainsValid() { + return true; + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java index 8174badd6f..81b682cfa1 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java @@ -44,12 +44,10 @@ public ValidatorFactory(CertificationCriterionService certificationCriterionServ this.certIdYearToValidatorClassMap.put("2014/2015", Validator20142015.class); this.certIdYearToValidatorClassMap.put("2015", Validator2015.class); this.certIdYearToValidatorClassMap.put("2025", Validator2025.class); - //TODO: we will need to create 2026, 2027, etc validators before the cmsIdStartDayOfYear - //day comes for the current year (so before 9/1/2026 we need a 2026 validator) - //OCD-4928 - //Added the below line to be able to test CMS IDs during the overlap window, but in reality - //we will need a new Validator2026 class. - this.certIdYearToValidatorClassMap.put("2026", Validator2025.class); + this.certIdYearToValidatorClassMap.put("2026", Validator2026.class); + //TODO: we will need to create 2027, 2028, etc validators before the cmsIdStartDayOfYear + //day comes for the current year (so before 9/1/20XX) + this.certIdYearToValidatorClassMap.put("2027", Validator2026.class); } public Validator getValidator(String certIdYear) throws InvalidArgumentsException { @@ -84,7 +82,8 @@ private Validator getNewInstance(Class validatorClazz) { LOGGER.error("Could not instantiate validator " + validatorClazz, ex); } } else if (validatorClazz.equals(Validator2015.class) - || validatorClazz.equals(Validator2025.class)) { + || validatorClazz.equals(Validator2025.class) + || validatorClazz.equals(Validator2026.class)) { try { result = (Validator) validatorClazz.getDeclaredConstructors()[0].newInstance(certificationCriterionService); } catch (Exception ex) { From 4b1904c95a330daaaee0f102a3eb716c9ad9dd9b Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 23 Apr 2026 12:46:31 -0400 Subject: [PATCH 02/14] refactor: Convert string hashmaps to objects for cmsid validation [#OCD-4928] --- .../CertificationIdMetPercentages.java | 18 ++ .../CertificationIdRequirements.java | 28 ++++ .../CertificationIdResults.java | 5 +- .../chpl/certificationId/Validator.java | 84 ++++------ .../chpl/certificationId/Validator2014.java | 140 ++++++++-------- .../certificationId/Validator20142015.java | 156 +++++++++--------- .../chpl/certificationId/Validator2015.java | 72 ++++---- .../chpl/certificationId/Validator2025.java | 68 ++++---- .../chpl/certificationId/Validator2026.java | 72 ++++---- .../certificationId/ValidatorDefault.java | 10 -- 10 files changed, 332 insertions(+), 321 deletions(-) create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java new file mode 100644 index 0000000000..98273ba4ba --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java @@ -0,0 +1,18 @@ +package gov.healthit.chpl.certificationId; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CertificationIdMetPercentages { + + private int criteriaMet; + private int cqmDomains; + private int cqmsInpatient; + private int cqmsAmbulatory; +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java new file mode 100644 index 0000000000..5cb36b13d6 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java @@ -0,0 +1,28 @@ +package gov.healthit.chpl.certificationId; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CertificationIdRequirements { + + private int criteriaRequired = 0; + private int criteriaRequiredMet = 0; + private int criteriaCpoeRequired = 0; + private int criteriaCpoeRequiredMet = 0; + private int criteriaTocRequired = 0; + private int criteriaTocRequiredMet = 0; + private int criteriaDpRequired = 0; + private int criteriaDpRequiredMet = 0; + private int criteriaDsRequired = 0; + private int criteriaDsRequiredMet = 0; + private int cqmsInpatientRequired = 0; + private int cqmsInpatientRequiredMet = 0; + private int cqmsAmbulatoryRequired = 0; + private int cqmsAmbulatoryRequiredMet = 0; + private int cqmsAmbulatoryCoreRequired = 0; + private int cqmsAmbulatoryCoreRequiredMet = 0; + private int domainsRequired = 0; + private int domainsRequiredMet = 0; +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java index f0162d55da..adc7bb884a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java @@ -3,7 +3,6 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.TreeMap; import gov.healthit.chpl.dto.CertifiedProductDetailsDTO; @@ -14,8 +13,8 @@ public class CertificationIdResults implements Serializable { private static final long serialVersionUID = 4350936762994127624L; private List products; private String ehrCertificationId; - private Map metCounts; - private Map metPercentages; + private CertificationIdRequirements metCounts; + private CertificationIdMetPercentages metPercentages; private ArrayList missingAnd = new ArrayList(); private List> missingOr = new ArrayList>(); private List> missingCombo = new ArrayList>(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java index 2f823b3206..8c5db790db 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java @@ -10,28 +10,28 @@ import gov.healthit.chpl.certificationCriteria.CertificationCriterion; public abstract class Validator { - protected Map criteriaMet = new HashMap(100); - protected Map cqmsMet = new HashMap(100); - protected Map domainsMet = new HashMap(10); + private Map criteriaMet = new HashMap(); + private Map cqmsMet = new HashMap(); + private Map domainsMet = new HashMap(); // missing criteria where all in the set are required - protected ArrayList missingAnd = new ArrayList(); + private ArrayList missingAnd = new ArrayList(); // missing 1 criteria from each of the following sets - protected List> missingOr = new ArrayList>(); + private List> missingOr = new ArrayList>(); // missing at least one of the following combinations of criteria - protected List> missingCombo = new ArrayList>(); + private List> missingCombo = new ArrayList>(); // missing X criteria from the OR list of criteria - protected List>> missingXOr = new ArrayList>>(); + private List>> missingXOr = new ArrayList>>(); - protected Map percents = new HashMap(); - protected Map counts = new HashMap(); - protected boolean valid = false; + private CertificationIdMetPercentages percents = new CertificationIdMetPercentages(); + private CertificationIdRequirements counts = new CertificationIdRequirements(); + private boolean valid = false; - public Map getCounts() { + public CertificationIdRequirements getCounts() { return this.counts; } - public Map getPercents() { + public CertificationIdMetPercentages getPercents() { return this.percents; } @@ -75,10 +75,6 @@ public boolean isValid() { protected abstract boolean isDomainsValid(); - // ********************************************************************** - // validate - // - // ********************************************************************** public boolean validate(List certDtos, List cqmDtos) { this.collectMetData(certDtos, cqmDtos); this.valid = this.onValidate(); @@ -86,10 +82,6 @@ public boolean validate(List certDtos, List c return this.isValid(); } - // ********************************************************************** - // collectMetData - // - // ********************************************************************** protected void collectMetData(List certDtos, List cqmDtos) { // Collect criteria met @@ -124,41 +116,25 @@ protected void collectMetData(List certDtos, List editionYears) { String attYearString = null; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2014.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2014.java index 99e10d84fd..c00e4e37bc 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2014.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2014.java @@ -59,20 +59,20 @@ public class Validator2014 extends Validator { "CMS155", "CMS156", "CMS165", "CMS166")); public Validator2014() { - this.counts.put("criteriaRequired", REQUIRED_CRITERIA.size()); - this.counts.put("criteriaRequiredMet", 0); - this.counts.put("criteriaCpoeRequired", 1); - this.counts.put("criteriaCpoeRequiredMet", 0); - this.counts.put("criteriaTocRequired", 2); - this.counts.put("criteriaTocRequiredMet", 0); - this.counts.put("cqmsInpatientRequired", 16); - this.counts.put("cqmsInpatientRequiredMet", 0); - this.counts.put("cqmsAmbulatoryRequired", 3); - this.counts.put("cqmsAmbulatoryRequiredMet", 0); - this.counts.put("cqmsAmbulatoryCoreRequired", 6); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); - this.counts.put("domainsRequired", 3); - this.counts.put("domainsRequiredMet", 0); + this.getCounts().setCriteriaRequired(REQUIRED_CRITERIA.size()); + this.getCounts().setCriteriaRequiredMet(0); + this.getCounts().setCriteriaCpoeRequired(1); + this.getCounts().setCriteriaCpoeRequiredMet(0); + this.getCounts().setCriteriaTocRequired(2); + this.getCounts().setCriteriaTocRequiredMet(0); + this.getCounts().setCqmsInpatientRequired(16); + this.getCounts().setCqmsInpatientRequiredMet(0); + this.getCounts().setCqmsAmbulatoryRequired(3); + this.getCounts().setCqmsAmbulatoryRequiredMet(0); + this.getCounts().setCqmsAmbulatoryCoreRequired(6); + this.getCounts().setCqmsAmbulatoryCoreRequiredMet(0); + this.getCounts().setDomainsRequired(3); + this.getCounts().setDomainsRequiredMet(0); } // ********************************************************************** @@ -92,28 +92,26 @@ public boolean onValidate() { // Must meet all required criteria. // ********************************************************************** protected boolean isCriteriaValid() { - this.counts.put("criteriaRequired", REQUIRED_CRITERIA.size()); + this.getCounts().setCriteriaRequired(REQUIRED_CRITERIA.size()); boolean criteriaValid = true; for (String crit : REQUIRED_CRITERIA) { if (!criteriaMetContainsCriterion(crit)) { criteriaValid = false; - missingAnd.add(crit); + getMissingAnd().add(crit); } else { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } } boolean cpoeValid = isCPOEValid(); boolean tocValid = isTOCValid(); - this.counts.put( - "criteriaRequired", - this.counts.get("criteriaRequired") + this.counts.get("criteriaCpoeRequired") - + this.counts.get("criteriaTocRequired")); - this.counts.put( - "criteriaRequiredMet", - this.counts.get("criteriaRequiredMet") + this.counts.get("criteriaCpoeRequiredMet") - + this.counts.get("criteriaTocRequiredMet")); + this.getCounts().setCriteriaRequired( + this.getCounts().getCriteriaRequired() + this.getCounts().getCriteriaCpoeRequired() + + this.getCounts().getCriteriaTocRequired()); + this.getCounts().setCriteriaRequiredMet( + this.getCounts().getCriteriaRequiredMet() + this.getCounts().getCriteriaCpoeRequiredMet() + + this.getCounts().getCriteriaTocRequiredMet()); return (criteriaValid && cpoeValid && tocValid); } @@ -132,27 +130,27 @@ protected boolean isCqmsValid() { valid = true; } if (!valid) { - if (this.counts.get("cqmsInpatientRequiredMet") < this.counts.get("cqmsInpatientRequired")) { - String needed = String.valueOf((this.counts.get("cqmsInpatientRequired") - inpatientCqmCount)); + if (this.getCounts().getCqmsInpatientRequiredMet() < this.getCounts().getCqmsInpatientRequired()) { + String needed = String.valueOf((this.getCounts().getCqmsInpatientRequired() - inpatientCqmCount)); TreeMap> missingInpatient = new TreeMap>(); missingInpatient.put(needed, (ArrayList) INPATIENT_CQMS); - missingXOr.add(missingInpatient); + getMissingXOr().add(missingInpatient); } - if (coreAmbulatory < this.counts.get("cqmsAmbulatoryCoreRequired")) { - String missing = String.valueOf(this.counts.get("cqmsAmbulatoryCoreRequired") - coreAmbulatory); + if (coreAmbulatory < this.getCounts().getCqmsAmbulatoryCoreRequired()) { + String missing = String.valueOf(this.getCounts().getCqmsAmbulatoryCoreRequired() - coreAmbulatory); TreeMap> missingCoreAmbulatory = new TreeMap>(); - missingCoreAmbulatory.put(missing, (ArrayList) this.AMBULATORY_CORE_CQMS); - missingXOr.add(missingCoreAmbulatory); - } else if (((this.counts.get("cqmsAmbulatoryRequiredMet") + this.counts.get("cqmsAmbulatoryCoreRequiredMet")) - < (this.counts.get("cqmsAmbulatoryRequired") + this.counts.get("cqmsAmbulatoryCoreRequired")))) { - String missing = String.valueOf((this.counts.get("cqmsAmbulatoryCoreRequired") + this.counts - .get("cqmsAmbulatoryRequired")) - (coreAmbulatory + nonCoreAmbulatory)); + missingCoreAmbulatory.put(missing, (ArrayList) AMBULATORY_CORE_CQMS); + getMissingXOr().add(missingCoreAmbulatory); + } else if ((this.getCounts().getCqmsAmbulatoryRequiredMet() + this.getCounts().getCqmsAmbulatoryCoreRequiredMet()) + < (this.getCounts().getCqmsAmbulatoryRequired() + this.getCounts().getCqmsAmbulatoryCoreRequired())) { + String missing = String.valueOf((this.getCounts().getCqmsAmbulatoryCoreRequired() + this.getCounts() + .getCqmsAmbulatoryRequired()) - (coreAmbulatory + nonCoreAmbulatory)); TreeMap> missingAmbulatory = new TreeMap>(); ArrayList combined = new ArrayList(); - combined.addAll(this.AMBULATORY_CORE_CQMS); - combined.addAll(this.AMBULATORY_CQMS); + combined.addAll(AMBULATORY_CORE_CQMS); + combined.addAll(AMBULATORY_CQMS); missingAmbulatory.put(missing, combined); - missingXOr.add(missingAmbulatory); + getMissingXOr().add(missingAmbulatory); } } return valid; @@ -164,10 +162,10 @@ protected boolean isCqmsValid() { // At least 3 CQM Domains must be met. // ********************************************************************** protected boolean isDomainsValid() { - this.counts.put("domainsRequiredMet", - this.domainsMet.size() >= this.counts.get("domainsRequired") ? this.counts.get("domainsRequired") - : this.domainsMet.size()); - return (this.counts.get("domainsRequiredMet") >= this.counts.get("domainsRequired")); + this.getCounts().setDomainsRequiredMet( + this.getDomainsMet().size() >= this.getCounts().getDomainsRequired() ? this.getCounts().getDomainsRequired() + : this.getDomainsMet().size()); + return (this.getCounts().getDomainsRequiredMet() >= this.getCounts().getDomainsRequired()); } // ********************************************************************** @@ -177,12 +175,12 @@ protected boolean isDomainsValid() { // ********************************************************************** protected boolean isInpatientCqmsValid() { for (String cqm : INPATIENT_CQMS) { - if (this.cqmsMet.containsKey(cqm)) { + if (this.getCqmsMet().containsKey(cqm)) { ++inpatientCqmCount; } } - this.counts.put("cqmsInpatientRequiredMet", inpatientCqmCount); - return (this.counts.get("cqmsInpatientRequiredMet") >= this.counts.get("cqmsInpatientRequired")); + this.getCounts().setCqmsInpatientRequiredMet(inpatientCqmCount); + return (this.getCounts().getCqmsInpatientRequiredMet() >= this.getCounts().getCqmsInpatientRequired()); } // ********************************************************************** @@ -196,20 +194,20 @@ protected boolean isAmbulatoryCqmsValid() { int nonCoreAmbulatory = 0; int coreAmbulatory = 0; - for (String cqm : cqmsMet.keySet()) { - if (this.AMBULATORY_CORE_CQMS.contains(cqm)) { + for (String cqm : getCqmsMet().keySet()) { + if (AMBULATORY_CORE_CQMS.contains(cqm)) { ++coreAmbulatory; } - if (this.AMBULATORY_CQMS.contains(cqm)) { + if (AMBULATORY_CQMS.contains(cqm)) { ++nonCoreAmbulatory; } } - this.counts.put("cqmsAmbulatoryRequiredMet", nonCoreAmbulatory); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", coreAmbulatory); + this.getCounts().setCqmsAmbulatoryRequiredMet(nonCoreAmbulatory); + this.getCounts().setCqmsAmbulatoryCoreRequiredMet(coreAmbulatory); - return (this.counts.get("cqmsAmbulatoryCoreRequiredMet") >= this.counts.get("cqmsAmbulatoryCoreRequired")) - && ((this.counts.get("cqmsAmbulatoryRequiredMet") + this.counts.get("cqmsAmbulatoryCoreRequiredMet")) - >= (this.counts.get("cqmsAmbulatoryRequired") + this.counts.get("cqmsAmbulatoryCoreRequired"))); + return (this.getCounts().getCqmsAmbulatoryCoreRequiredMet() >= this.getCounts().getCqmsAmbulatoryCoreRequired()) + && ((this.getCounts().getCqmsAmbulatoryRequiredMet() + this.getCounts().getCqmsAmbulatoryCoreRequiredMet()) + >= (this.getCounts().getCqmsAmbulatoryRequired() + this.getCounts().getCqmsAmbulatoryCoreRequired())); } // ********************************************************************** @@ -221,11 +219,11 @@ protected boolean isAmbulatoryCqmsValid() { protected boolean isCPOEValid() { for (String crit : CPOE_CRITERIA) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaCpoeRequiredMet", 1); + this.getCounts().setCriteriaCpoeRequiredMet(1); return true; } } - missingOr.add(new ArrayList(CPOE_CRITERIA)); + getMissingOr().add(new ArrayList(CPOE_CRITERIA)); return false; } @@ -239,46 +237,46 @@ protected boolean isTOCValid() { // 170.314(b)(1) and 170.314(b)(2) and 170.314(b)(8) and 170.314(h)(1) if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)")) { - this.counts.put("criteriaTocRequiredMet", 4); - this.counts.put("criteriaTocRequired", 4); + this.getCounts().setCriteriaTocRequiredMet(4); + this.getCounts().setCriteriaTocRequired(4); return true; } // 170.314(b)(1) and 170.314(b)(2) and 170.314(h)(1) if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (h)(1)")) { - this.counts.put("criteriaTocRequiredMet", 3); - this.counts.put("criteriaTocRequired", 3); + this.getCounts().setCriteriaTocRequiredMet(3); + this.getCounts().setCriteriaTocRequired(3); return true; } // 170.314(b)(1) and 170.314(b)(2) and 170.314(b)(8) if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)")) { - this.counts.put("criteriaTocRequiredMet", 3); - this.counts.put("criteriaTocRequired", 3); + this.getCounts().setCriteriaTocRequiredMet(3); + this.getCounts().setCriteriaTocRequired(3); return true; } // 170.314(b)(8) and 170.314(h)(1) if (criteriaMetContainsCriterion("170.314 (b)(8)") && this.criteriaMetContainsCriterion("170.314 (h)(1)")) { - this.counts.put("criteriaTocRequiredMet", 2); - this.counts.put("criteriaTocRequired", 2); + this.getCounts().setCriteriaTocRequiredMet(2); + this.getCounts().setCriteriaTocRequired(2); return true; } // 170.314(b)(1) and 170.314(b)(2) if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)")) { - this.counts.put("criteriaTocRequiredMet", 2); - this.counts.put("criteriaTocRequired", 2); + this.getCounts().setCriteriaTocRequiredMet(2); + this.getCounts().setCriteriaTocRequired(2); return true; } - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)"))); return false; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator20142015.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator20142015.java index b79a3a8820..42b40916fc 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator20142015.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator20142015.java @@ -6,17 +6,17 @@ public class Validator20142015 extends Validator { public Validator20142015() { - this.counts.put("criteriaRequired", 0); // This number is calculated + this.getCounts().setCriteriaRequired(0); // This number is calculated // during the checks - this.counts.put("criteriaRequiredMet", 0); - this.counts.put("cqmsInpatientRequired", 0); - this.counts.put("cqmsInpatientRequiredMet", 0); - this.counts.put("cqmsAmbulatoryRequired", 0); - this.counts.put("cqmsAmbulatoryRequiredMet", 0); - this.counts.put("cqmsAmbulatoryCoreRequired", 0); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); - this.counts.put("domainsRequired", 0); - this.counts.put("domainsRequiredMet", 0); + this.getCounts().setCriteriaRequiredMet(0); + this.getCounts().setCqmsInpatientRequired(0); + this.getCounts().setCqmsInpatientRequiredMet(0); + this.getCounts().setCqmsAmbulatoryRequired(0); + this.getCounts().setCqmsAmbulatoryRequiredMet(0); + this.getCounts().setCqmsAmbulatoryCoreRequired(0); + this.getCounts().setCqmsAmbulatoryCoreRequiredMet(0); + this.getCounts().setDomainsRequired(0); + this.getCounts().setDomainsRequiredMet(0); } // ********************************************************************** @@ -36,7 +36,7 @@ public boolean onValidate() { // Must meet all required criteria. // ********************************************************************** protected boolean isCriteriaValid() { - this.counts.put("criteriaRequired", 7 + 3 + 8); + this.getCounts().setCriteriaRequired(7 + 3 + 8); // 7 categories 1 pt per category + 3 pts for cqm category + 8 pts for // ps category @@ -53,10 +53,10 @@ protected boolean isCriteriaValid() { || criteriaMetContainsCriterion("170.314 (a)(19)") || criteriaMetContainsCriterion("170.314 (a)(20)")) || (criteriaMetContainsCriterion("170.315 (a)(1)") || criteriaMetContainsCriterion("170.315 (a)(2)") || criteriaMetContainsCriterion("170.315 (a)(3)"))) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); cpoe = true; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (a)(1)", "170.314 (a)(18)", "170.314 (a)(19)", + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (a)(1)", "170.314 (a)(18)", "170.314 (a)(19)", "170.314 (a)(20)", "170.315 (a)(1)", "170.315 (a)(2)", "170.315 (a)(3)"))); } @@ -64,45 +64,45 @@ protected boolean isCriteriaValid() { // 170.315(a)(5). if (criteriaMetContainsCriterion("170.314 (a)(3)") || criteriaMetContainsCriterion("170.315 (a)(5)")) { recordDemo = true; - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (a)(3)", "170.315 (a)(5)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (a)(3)", "170.315 (a)(5)"))); } // (3)(i) Problem list at 45 CFR 170.314(a)(5); or (ii) 45 CFR // 170.315(a)(6). if (criteriaMetContainsCriterion("170.314 (a)(5)") || criteriaMetContainsCriterion("170.315 (a)(6)")) { problemList = true; - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (a)(5)", "170.315 (a)(6)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (a)(5)", "170.315 (a)(6)"))); } // (4)(i) Medication list at 45 CFR 170.314(a)(6); or (ii) 45 CFR // 170.315(a)(7). if (criteriaMetContainsCriterion("170.314 (a)(6)") || criteriaMetContainsCriterion("170.315 (a)(7)")) { medList = true; - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (a)(6)", "170.315 (a)(7)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (a)(6)", "170.315 (a)(7)"))); } // (5)(i) Medication allergy list 45 CFR 170.314(a)(7); or (ii) 45 CFR // 170.315(a)(8). if (criteriaMetContainsCriterion("170.314 (a)(7)") || criteriaMetContainsCriterion("170.315 (a)(8)")) { medAllergyList = true; - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (a)(7)", "170.315 (a)(8)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (a)(7)", "170.315 (a)(8)"))); } // (6)(i) Clinical decision support at 45 CFR 170.314(a)(8); or (ii) 45 // CFR 170.315(a)(9). if (criteriaMetContainsCriterion("170.314 (a)(8)") || criteriaMetContainsCriterion("170.315 (a)(9)")) { clinicalDecision = true; - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (a)(8)", "170.315 (a)(9)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (a)(8)", "170.315 (a)(9)"))); } boolean critToc = this.isCriteriaTOCValid(); @@ -129,7 +129,7 @@ protected boolean isCriteriaTOCValid() { if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterion("170.315 (h)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -139,7 +139,7 @@ && criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterio if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -148,7 +148,7 @@ && criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterio if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -157,7 +157,7 @@ && criteriaMetContainsCriterion("170.315 (h)(2)")) { if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (b)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -165,7 +165,7 @@ && criteriaMetContainsCriterion("170.315 (b)(1)")) { // (iv) 45 CFR 170.314(b)(1), (b)(2), (b)(8), and (h)(1). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -173,7 +173,7 @@ && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterio // (vii) 45 CFR 170.314(b)(1), (b)(2), (h)(1), and 170.315(h)(2). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -181,7 +181,7 @@ && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterio // (viii) 45 CFR 170.314(b)(1), (b)(2), (b)(8), and 170.315(h)(2). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -189,7 +189,7 @@ && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterio // (xii) 45 CFR 170.314(b)(1), (b)(2), (h)(1), and 170.315(b)(1). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (b)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -197,7 +197,7 @@ && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterio // (xiii) 45 CFR 170.314(b)(1), (b)(2), (b)(8), and 170.315(b)(1). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.315 (b)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -205,7 +205,7 @@ && criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterio // (ii) 45 CFR 170.314(b)(1), (b)(2), and (h)(1). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (h)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -213,7 +213,7 @@ && criteriaMetContainsCriterion("170.314 (h)(1)")) { // (iii) 45 CFR 170.314(b)(1), (b)(2), and (b)(8). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.314 (b)(8)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -221,7 +221,7 @@ && criteriaMetContainsCriterion("170.314 (b)(8)")) { // (vi) 45 CFR 170.314(b)(1), (b)(2), and 170.315(h)(2). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -229,7 +229,7 @@ && criteriaMetContainsCriterion("170.315 (h)(2)")) { // (x) 45 CFR 170.314(b)(8), (h)(1), and 170.315(h)(2). if (criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -237,7 +237,7 @@ && criteriaMetContainsCriterion("170.315 (h)(2)")) { // (xi) 45 CFR 170.314(b)(1), (b)(2), and 170.315(b)(1). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)") && criteriaMetContainsCriterion("170.315 (b)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -245,7 +245,7 @@ && criteriaMetContainsCriterion("170.315 (b)(1)")) { // (xv) 45 CFR 170.314(b)(8), (h)(1), and 170.315(b)(1). if (criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (b)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } @@ -253,72 +253,72 @@ && criteriaMetContainsCriterion("170.315 (b)(1)")) { // (xxi) 45 CFR 170.315(b)(1), (h)(1), and (h)(2) if (criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterion("170.315 (h)(1)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } // (v) 45 CFR 170.314(b)(8) and (h)(1). if (criteriaMetContainsCriterion("170.314 (b)(8)") && criteriaMetContainsCriterion("170.314 (h)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } // (i) 45 CFR 170.314(b)(1) and (2). if (criteriaMetContainsCriterion("170.314 (b)(1)") && criteriaMetContainsCriterion("170.314 (b)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } // (xviii) 45 CFR 170.314(h)(1) and 170.315(b)(1). if (criteriaMetContainsCriterion("170.314 (h)(1)") && criteriaMetContainsCriterion("170.315 (b)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } // (xix) 45 CFR 170.315(b)(1) and (h)(1). if (criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterion("170.315 (h)(1)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } // (xx) 45 CFR 170.315(b)(1) and (h)(2). if (criteriaMetContainsCriterion("170.315 (b)(1)") && criteriaMetContainsCriterion("170.315 (h)(2)")) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); flag = true; return true; } if (!flag) { - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", "170.315 (b)(1)", "170.315 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", "170.315 (b)(1)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)", "170.315 (b)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (h)(1)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (h)(1)", "170.315 (b)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.315 (b)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.315 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.315 (b)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)", "170.315 (b)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.315 (b)(1)", "170.315 (h)(1)", "170.315 (h)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.314 (h)(1)", "170.315 (b)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.315 (b)(1)", "170.315 (h)(1)"))); - missingCombo.add(new ArrayList(Arrays.asList("170.315 (b)(1)", "170.315 (h)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.314 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (h)(1)", "170.315 (h)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.315 (h)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (h)(1)", "170.315 (b)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)", "170.315 (b)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.315 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.314 (b)(8)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.315 (h)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)", "170.315 (h)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)", "170.315 (b)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)", "170.315 (b)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.315 (b)(1)", "170.315 (h)(1)", "170.315 (h)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(8)", "170.314 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (b)(1)", "170.314 (b)(2)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.314 (h)(1)", "170.315 (b)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.315 (b)(1)", "170.315 (h)(1)"))); + getMissingCombo().add(new ArrayList(Arrays.asList("170.315 (b)(1)", "170.315 (h)(2)"))); } return false; @@ -337,24 +337,24 @@ protected boolean isCriteriaCqmValid() { if (criteriaMetContainsCriterion("170.314 (c)(1)") || criteriaMetContainsCriterion("170.315 (c)(1)")) { ++cqmCritCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (c)(1)", "170.315 (c)(1)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (c)(1)", "170.315 (c)(1)"))); } // (2) 45 CFR 170.314(c)(2) or 170.315(c)(2) if (criteriaMetContainsCriterion("170.314 (c)(2)") || criteriaMetContainsCriterion("170.315 (c)(2)")) { ++cqmCritCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (c)(2)", "170.315 (c)(2)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (c)(2)", "170.315 (c)(2)"))); } // (3) 45 CFR 170.314(c)(3) or 170.315(c)(3) if (criteriaMetContainsCriterion("170.314 (c)(3)") || criteriaMetContainsCriterion("170.315 (c)(3)")) { ++cqmCritCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (c)(3)", "170.315 (c)(3)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (c)(3)", "170.315 (c)(3)"))); } - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + cqmCritCount); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + cqmCritCount); return (cqmCritCount == cqmCritRequired); } @@ -373,59 +373,59 @@ protected boolean isCriteriaPSValid() { if (criteriaMetContainsCriterion("170.314 (d)(1)") || criteriaMetContainsCriterion("170.315 (d)(1)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(1)", "170.315 (d)(1)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(1)", "170.315 (d)(1)"))); } // (2) 45 CFR 170.314(d)(2) or 170.315(d)(2); if (criteriaMetContainsCriterion("170.314 (d)(2)") || criteriaMetContainsCriterion("170.315 (d)(2)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(2)", "170.315 (d)(2)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(2)", "170.315 (d)(2)"))); } // (3) 45 CFR 170.314(d)(3) or 170.315(d)(3); if (criteriaMetContainsCriterion("170.314 (d)(3)") || criteriaMetContainsCriterion("170.315 (d)(3)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(3)", "170.315 (d)(3)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(3)", "170.315 (d)(3)"))); } // (4) 45 CFR 170.314(d)(4) or 170.315(d)(4); if (criteriaMetContainsCriterion("170.314 (d)(4)") || criteriaMetContainsCriterion("170.315 (d)(4)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(4)", "170.315 (d)(4)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(4)", "170.315 (d)(4)"))); } // (5) 45 CFR 170.314(d)(5) or 170.315(d)(5); if (criteriaMetContainsCriterion("170.314 (d)(5)") || criteriaMetContainsCriterion("170.315 (d)(5)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(5)", "170.315 (d)(5)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(5)", "170.315 (d)(5)"))); } // (6) 45 CFR 170.314(d)(6) or 170.315(d)(6); if (criteriaMetContainsCriterion("170.314 (d)(6)") || criteriaMetContainsCriterion("170.315 (d)(6)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(6)", "170.315 (d)(6)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(6)", "170.315 (d)(6)"))); } // (7) 45 CFR 170.314(d)(7) or 170.315(d)(7); if (criteriaMetContainsCriterion("170.314 (d)(7)") || criteriaMetContainsCriterion("170.315 (d)(7)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(7)", "170.315 (d)(7)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(7)", "170.315 (d)(7)"))); } // (8) 45 CFR 170.314(d)(8) or 170.315(d)(8); if (criteriaMetContainsCriterion("170.314 (d)(8)") || criteriaMetContainsCriterion("170.315 (d)(8)")) { ++psCritMetCount; } else { - missingOr.add(new ArrayList(Arrays.asList("170.314 (d)(8)", "170.315 (d)(8)"))); + getMissingOr().add(new ArrayList(Arrays.asList("170.314 (d)(8)", "170.315 (d)(8)"))); } - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + psCritMetCount); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + psCritMetCount); return (psCritMetCount == psCritMetRequired); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java index cd46e5164f..040cfd9484 100755 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java @@ -46,22 +46,22 @@ public Validator2015(CertificationCriterionService certificationCriterionService certificationCriterionService.get(Criteria2015.H_2)) .collect(Collectors.toList()); - this.counts.put("criteriaRequired", requiredCriteria.size()); - this.counts.put("criteriaRequiredMet", 0); - this.counts.put("criteriaCpoeRequired", 1); - this.counts.put("criteriaCpoeRequiredMet", 0); - this.counts.put("criteriaDsRequired", 1); - this.counts.put("criteriaDsRequiredMet", 0); - this.counts.put("criteriaDpRequired", 1); - this.counts.put("criteriaDpRequiredMet", 0); - this.counts.put("cqmsInpatientRequired", 0); - this.counts.put("cqmsInpatientRequiredMet", 0); - this.counts.put("cqmsAmbulatoryRequired", 0); - this.counts.put("cqmsAmbulatoryRequiredMet", 0); - this.counts.put("cqmsAmbulatoryCoreRequired", 0); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); - this.counts.put("domainsRequired", 0); - this.counts.put("domainsRequiredMet", 0); + this.getCounts().setCriteriaRequired(requiredCriteria.size()); + this.getCounts().setCriteriaRequiredMet(0); + this.getCounts().setCriteriaCpoeRequired(1); + this.getCounts().setCriteriaCpoeRequiredMet(0); + this.getCounts().setCriteriaDsRequired(1); + this.getCounts().setCriteriaDsRequiredMet(0); + this.getCounts().setCriteriaDpRequired(1); + this.getCounts().setCriteriaDpRequiredMet(0); + this.getCounts().setCqmsInpatientRequired(0); + this.getCounts().setCqmsInpatientRequiredMet(0); + this.getCounts().setCqmsAmbulatoryRequired(0); + this.getCounts().setCqmsAmbulatoryRequiredMet(0); + this.getCounts().setCqmsAmbulatoryCoreRequired(0); + this.getCounts().setCqmsAmbulatoryCoreRequiredMet(0); + this.getCounts().setDomainsRequired(0); + this.getCounts().setDomainsRequiredMet(0); } public boolean onValidate() { @@ -69,17 +69,17 @@ public boolean onValidate() { } protected boolean isCriteriaValid() { - this.counts.put("criteriaRequired", requiredCriteria.size()); + this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; for (CertificationCriterion crit : requiredCriteria) { - Optional metRequiredCriterion = criteriaMet.keySet().stream() + Optional metRequiredCriterion = getCriteriaMet().keySet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); if (metRequiredCriterion.isPresent()) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingAnd.add(Util.formatCriteriaNumber(crit)); + getMissingAnd().add(Util.formatCriteriaNumber(crit)); requiredCriteriaValid = false; } } @@ -88,16 +88,16 @@ protected boolean isCriteriaValid() { boolean dsValid = isDecisionSupportValid(); boolean dpValid = isDPValid(); - this.counts.put("criteriaRequired", - this.counts.get("criteriaRequired") - + this.counts.get("criteriaCpoeRequired") - + this.counts.get("criteriaDsRequired") - + this.counts.get("criteriaDpRequired")); - this.counts.put("criteriaRequiredMet", - this.counts.get("criteriaRequiredMet") - + this.counts.get("criteriaCpoeRequiredMet") - + this.counts.get("criteriaDsRequiredMet") - + this.counts.get("criteriaDpRequiredMet")); + this.getCounts().setCriteriaRequired( + this.getCounts().getCriteriaRequired() + + this.getCounts().getCriteriaCpoeRequired() + + this.getCounts().getCriteriaDsRequired() + + this.getCounts().getCriteriaDpRequired()); + this.getCounts().setCriteriaRequiredMet( + this.getCounts().getCriteriaRequiredMet() + + this.getCounts().getCriteriaCpoeRequiredMet() + + this.getCounts().getCriteriaDsRequiredMet() + + this.getCounts().getCriteriaDpRequiredMet()); return (requiredCriteriaValid && cpoeValid && dsValid && dpValid); } @@ -105,11 +105,11 @@ protected boolean isCriteriaValid() { protected boolean isCPOEValid() { for (CertificationCriterion crit : cpoeCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaCpoeRequiredMet", 1); + this.getCounts().setCriteriaCpoeRequiredMet(1); return true; } } - missingOr.add(cpoeCriteriaOr.stream() + getMissingOr().add(cpoeCriteriaOr.stream() .map(cpoeCrit -> Util.formatCriteriaNumber(cpoeCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; @@ -118,11 +118,11 @@ protected boolean isCPOEValid() { protected boolean isDecisionSupportValid() { for (CertificationCriterion crit : decisionSupportRequiredCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaDsRequiredMet", 1); + this.getCounts().setCriteriaDsRequiredMet(1); return true; } } - missingOr.add(decisionSupportRequiredCriteriaOr.stream() + getMissingOr().add(decisionSupportRequiredCriteriaOr.stream() .map(dsCrit -> Util.formatCriteriaNumber(dsCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; @@ -131,11 +131,11 @@ protected boolean isDecisionSupportValid() { protected boolean isDPValid() { for (CertificationCriterion crit : dpCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaDpRequiredMet", 1); + this.getCounts().setCriteriaDpRequiredMet(1); return true; } } - missingOr.add(dpCriteriaOr.stream() + getMissingOr().add(dpCriteriaOr.stream() .map(dpCrit -> Util.formatCriteriaNumber(dpCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java index 8ff82cd478..1a3328ccca 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java @@ -40,24 +40,24 @@ public Validator2025(CertificationCriterionService certificationCriterionService certificationCriterionService.get(Criteria2015.H_2)) .collect(Collectors.toList()); - this.counts.put("criteriaRequired", requiredCriteria.size()); - this.counts.put("criteriaRequiredMet", 0); - this.counts.put("criteriaCpoeRequired", 1); - this.counts.put("criteriaCpoeRequiredMet", 0); - this.counts.put("criteriaDpRequired", 1); - this.counts.put("criteriaDpRequiredMet", 0); + this.getCounts().setCriteriaRequired(requiredCriteria.size()); + this.getCounts().setCriteriaRequiredMet(0); + this.getCounts().setCriteriaCpoeRequired(1); + this.getCounts().setCriteriaCpoeRequiredMet(0); + this.getCounts().setCriteriaDpRequired(1); + this.getCounts().setCriteriaDpRequiredMet(0); //Decision support criteria (or "ds") was the a9 or b11 that was required before 2025. //Starting with the 2025 calendar year cert ids, b11 is just a required criteria - this.counts.put("criteriaDsRequired", 0); - this.counts.put("criteriaDsRequiredMet", 0); - this.counts.put("cqmsInpatientRequired", 0); - this.counts.put("cqmsInpatientRequiredMet", 0); - this.counts.put("cqmsAmbulatoryRequired", 0); - this.counts.put("cqmsAmbulatoryRequiredMet", 0); - this.counts.put("cqmsAmbulatoryCoreRequired", 0); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); - this.counts.put("domainsRequired", 0); - this.counts.put("domainsRequiredMet", 0); + this.getCounts().setCriteriaDsRequired(0); + this.getCounts().setCriteriaDsRequiredMet(0); + this.getCounts().setCqmsInpatientRequired(0); + this.getCounts().setCqmsInpatientRequiredMet(0); + this.getCounts().setCqmsAmbulatoryRequired(0); + this.getCounts().setCqmsAmbulatoryRequiredMet(0); + this.getCounts().setCqmsAmbulatoryCoreRequired(0); + this.getCounts().setCqmsAmbulatoryCoreRequiredMet(0); + this.getCounts().setDomainsRequired(0); + this.getCounts().setDomainsRequiredMet(0); } public boolean onValidate() { @@ -65,17 +65,17 @@ public boolean onValidate() { } protected boolean isCriteriaValid() { - this.counts.put("criteriaRequired", requiredCriteria.size()); + this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; for (CertificationCriterion crit : requiredCriteria) { - Optional metRequiredCriterion = criteriaMet.keySet().stream() + Optional metRequiredCriterion = getCriteriaMet().keySet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); if (metRequiredCriterion.isPresent()) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingAnd.add(Util.formatCriteriaNumber(crit)); + this.getMissingAnd().add(Util.formatCriteriaNumber(crit)); requiredCriteriaValid = false; } } @@ -83,16 +83,16 @@ protected boolean isCriteriaValid() { boolean cpoeValid = isCPOEValid(); boolean dpValid = isDPValid(); - this.counts.put("criteriaRequired", - this.counts.get("criteriaRequired") - + this.counts.get("criteriaCpoeRequired") - + this.counts.get("criteriaDsRequired") - + this.counts.get("criteriaDpRequired")); - this.counts.put("criteriaRequiredMet", - this.counts.get("criteriaRequiredMet") - + this.counts.get("criteriaCpoeRequiredMet") - + this.counts.get("criteriaDsRequiredMet") - + this.counts.get("criteriaDpRequiredMet")); + this.getCounts().setCriteriaRequired( + this.getCounts().getCriteriaRequired() + + this.getCounts().getCriteriaCpoeRequired() + + this.getCounts().getCriteriaDsRequired() + + this.getCounts().getCriteriaDpRequired()); + this.getCounts().setCriteriaRequiredMet( + this.getCounts().getCriteriaRequiredMet() + + this.getCounts().getCriteriaCpoeRequiredMet() + + this.getCounts().getCriteriaDsRequiredMet() + + this.getCounts().getCriteriaDpRequiredMet()); return (requiredCriteriaValid && cpoeValid && dpValid); } @@ -100,11 +100,11 @@ protected boolean isCriteriaValid() { protected boolean isCPOEValid() { for (CertificationCriterion crit : cpoeCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaCpoeRequiredMet", 1); + this.getCounts().setCriteriaCpoeRequiredMet(1); return true; } } - missingOr.add(cpoeCriteriaOr.stream() + getMissingOr().add(cpoeCriteriaOr.stream() .map(cpoeCrit -> Util.formatCriteriaNumber(cpoeCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; @@ -113,11 +113,11 @@ protected boolean isCPOEValid() { protected boolean isDPValid() { for (CertificationCriterion crit : dpCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaDpRequiredMet", 1); + this.getCounts().setCriteriaDpRequiredMet(1); return true; } } - missingOr.add(dpCriteriaOr.stream() + getMissingOr().add(dpCriteriaOr.stream() .map(dpCrit -> Util.formatCriteriaNumber(dpCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 48a3e107b4..86203d5293 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -12,7 +12,7 @@ import gov.healthit.chpl.util.Util; /** - * Validator for CMS EHR ID generation for the year 2026 + * Validator for CMS EHR ID generation for the year 2025 */ public class Validator2026 extends Validator { @@ -40,22 +40,24 @@ public Validator2026(CertificationCriterionService certificationCriterionService certificationCriterionService.get(Criteria2015.H_2)) .collect(Collectors.toList()); - this.counts.put("criteriaRequired", requiredCriteria.size()); - this.counts.put("criteriaRequiredMet", 0); - this.counts.put("criteriaCpoeRequired", 1); - this.counts.put("criteriaCpoeRequiredMet", 0); - this.counts.put("criteriaDpRequired", 1); - this.counts.put("criteriaDpRequiredMet", 0); - this.counts.put("criteriaDsRequired", 0); - this.counts.put("criteriaDsRequiredMet", 0); - this.counts.put("cqmsInpatientRequired", 0); - this.counts.put("cqmsInpatientRequiredMet", 0); - this.counts.put("cqmsAmbulatoryRequired", 0); - this.counts.put("cqmsAmbulatoryRequiredMet", 0); - this.counts.put("cqmsAmbulatoryCoreRequired", 0); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); - this.counts.put("domainsRequired", 0); - this.counts.put("domainsRequiredMet", 0); + this.getCounts().setCriteriaRequired(requiredCriteria.size()); + this.getCounts().setCriteriaRequiredMet(0); + this.getCounts().setCriteriaCpoeRequired(1); + this.getCounts().setCriteriaCpoeRequiredMet(0); + this.getCounts().setCriteriaDpRequired(1); + this.getCounts().setCriteriaDpRequiredMet(0); + //Decision support criteria (or "ds") was the a9 or b11 that was required before 2025. + //Starting with the 2025 calendar year cert ids, b11 is just a required criteria + this.getCounts().setCriteriaDsRequired(0); + this.getCounts().setCriteriaDsRequiredMet(0); + this.getCounts().setCqmsInpatientRequired(0); + this.getCounts().setCqmsInpatientRequiredMet(0); + this.getCounts().setCqmsAmbulatoryRequired(0); + this.getCounts().setCqmsAmbulatoryRequiredMet(0); + this.getCounts().setCqmsAmbulatoryCoreRequired(0); + this.getCounts().setCqmsAmbulatoryCoreRequiredMet(0); + this.getCounts().setDomainsRequired(0); + this.getCounts().setDomainsRequiredMet(0); } public boolean onValidate() { @@ -63,17 +65,17 @@ public boolean onValidate() { } protected boolean isCriteriaValid() { - this.counts.put("criteriaRequired", requiredCriteria.size()); + this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; for (CertificationCriterion crit : requiredCriteria) { - Optional metRequiredCriterion = criteriaMet.keySet().stream() + Optional metRequiredCriterion = getCriteriaMet().keySet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); if (metRequiredCriterion.isPresent()) { - this.counts.put("criteriaRequiredMet", this.counts.get("criteriaRequiredMet") + 1); + this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); } else { - missingAnd.add(Util.formatCriteriaNumber(crit)); + this.getMissingAnd().add(Util.formatCriteriaNumber(crit)); requiredCriteriaValid = false; } } @@ -81,16 +83,16 @@ protected boolean isCriteriaValid() { boolean cpoeValid = isCPOEValid(); boolean dpValid = isDPValid(); - this.counts.put("criteriaRequired", - this.counts.get("criteriaRequired") - + this.counts.get("criteriaCpoeRequired") - + this.counts.get("criteriaDsRequired") - + this.counts.get("criteriaDpRequired")); - this.counts.put("criteriaRequiredMet", - this.counts.get("criteriaRequiredMet") - + this.counts.get("criteriaCpoeRequiredMet") - + this.counts.get("criteriaDsRequiredMet") - + this.counts.get("criteriaDpRequiredMet")); + this.getCounts().setCriteriaRequired( + this.getCounts().getCriteriaRequired() + + this.getCounts().getCriteriaCpoeRequired() + + this.getCounts().getCriteriaDsRequired() + + this.getCounts().getCriteriaDpRequired()); + this.getCounts().setCriteriaRequiredMet( + this.getCounts().getCriteriaRequiredMet() + + this.getCounts().getCriteriaCpoeRequiredMet() + + this.getCounts().getCriteriaDsRequiredMet() + + this.getCounts().getCriteriaDpRequiredMet()); return (requiredCriteriaValid && cpoeValid && dpValid); } @@ -98,11 +100,11 @@ protected boolean isCriteriaValid() { protected boolean isCPOEValid() { for (CertificationCriterion crit : cpoeCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaCpoeRequiredMet", 1); + this.getCounts().setCriteriaCpoeRequiredMet(1); return true; } } - missingOr.add(cpoeCriteriaOr.stream() + getMissingOr().add(cpoeCriteriaOr.stream() .map(cpoeCrit -> Util.formatCriteriaNumber(cpoeCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; @@ -111,11 +113,11 @@ protected boolean isCPOEValid() { protected boolean isDPValid() { for (CertificationCriterion crit : dpCriteriaOr) { if (criteriaMetContainsCriterion(crit)) { - this.counts.put("criteriaDpRequiredMet", 1); + this.getCounts().setCriteriaDpRequiredMet(1); return true; } } - missingOr.add(dpCriteriaOr.stream() + getMissingOr().add(dpCriteriaOr.stream() .map(dpCrit -> Util.formatCriteriaNumber(dpCrit)) .collect(Collectors.toCollection(ArrayList::new))); return false; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorDefault.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorDefault.java index a5136c33e6..1d89a7b0e4 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorDefault.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorDefault.java @@ -3,16 +3,6 @@ public class ValidatorDefault extends Validator { public ValidatorDefault() { - this.counts.put("criteriaRequired", 0); - this.counts.put("criteriaRequiredMet", 0); - this.counts.put("cqmsInpatientRequired", 0); - this.counts.put("cqmsInpatientRequiredMet", 0); - this.counts.put("cqmsAmbulatoryRequired", 0); - this.counts.put("cqmsAmbulatoryRequiredMet", 0); - this.counts.put("cqmsAmbulatoryCoreRequired", 0); - this.counts.put("cqmsAmbulatoryCoreRequiredMet", 0); - this.counts.put("domainsRequired", 0); - this.counts.put("domainsRequiredMet", 0); } // ********************************************************************** From 398c81ef4b6b4db7563690e177825fb6c1affbe4 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 23 Apr 2026 14:16:12 -0400 Subject: [PATCH 03/14] refactor: Clean up some unneeded comments [#OCD-4928] --- .../gov/healthit/chpl/certificationId/Validator2015.java | 5 ----- .../gov/healthit/chpl/certificationId/Validator2025.java | 3 --- .../gov/healthit/chpl/certificationId/Validator2026.java | 5 ----- 3 files changed, 13 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java index 040cfd9484..9371c5fdfc 100755 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java @@ -11,11 +11,6 @@ import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.util.Util; -/** - * Validator for CMS EHR ID generation for 2015 Edition, post Cures rule. - * @author alarned - * - */ public class Validator2015 extends Validator { private List requiredCriteria; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java index 1a3328ccca..e2b47f78fd 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java @@ -11,9 +11,6 @@ import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.util.Util; -/** - * Validator for CMS EHR ID generation for the year 2025 - */ public class Validator2025 extends Validator { private List requiredCriteria; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 86203d5293..5f32319971 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -11,9 +11,6 @@ import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.util.Util; -/** - * Validator for CMS EHR ID generation for the year 2025 - */ public class Validator2026 extends Validator { private List requiredCriteria; @@ -46,8 +43,6 @@ public Validator2026(CertificationCriterionService certificationCriterionService this.getCounts().setCriteriaCpoeRequiredMet(0); this.getCounts().setCriteriaDpRequired(1); this.getCounts().setCriteriaDpRequiredMet(0); - //Decision support criteria (or "ds") was the a9 or b11 that was required before 2025. - //Starting with the 2025 calendar year cert ids, b11 is just a required criteria this.getCounts().setCriteriaDsRequired(0); this.getCounts().setCriteriaDsRequiredMet(0); this.getCounts().setCqmsInpatientRequired(0); From 185414bf48618a656c4f7b64e781f1b7a11b121d Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Wed, 29 Apr 2026 10:01:55 -0400 Subject: [PATCH 04/14] feat: Get listing details when calculating CMS ID [#OCD-4928] --- .../controller/CertificationIdController.java | 27 +++--- .../certificationId/CertificationIdDAO.java | 14 ++- .../CertificationIdLookupResults.java | 44 ++++------ .../CertificationIdManager.java | 5 +- .../CertificationIdMetPercentages.java | 1 + .../CertificationIdRequirements.java | 2 + .../CertificationIdResults.java | 13 +-- .../CertificationIdSearchService.java | 61 ++++++------- .../CertificationIdYearCalculator.java | 5 ++ .../chpl/certificationId/Validator.java | 66 ++++++++------ .../chpl/certificationId/Validator2015.java | 2 +- .../chpl/certificationId/Validator2025.java | 2 +- .../chpl/certificationId/Validator2026.java | 85 +++++++++++++++++-- .../certificationId/ValidatorFactory.java | 25 +++++- .../domain/CertifiedProductSearchDetails.java | 4 + 15 files changed, 219 insertions(+), 137 deletions(-) diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java index 60f3ae8b3e..cb06135fd2 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java @@ -1,5 +1,7 @@ package gov.healthit.chpl.web.controller; +import static gov.healthit.chpl.util.LambdaExceptionUtil.rethrowFunction; + import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -98,8 +100,7 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic message = "This endpoint is deprecated and will be removed. Please use certification-ids/search", removalDate = "2026-10-01") public @ResponseBody CertificationIdResults searchCertificationIdDeprecated( - @RequestParam(required = false) List ids) throws InvalidArgumentsException, - CertificationIdException { + @RequestParam(required = false) List ids) throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { return certIdSearchService.findCertificationByListingIds(ids, null, false); } @@ -114,17 +115,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic MediaType.APPLICATION_JSON_VALUE }) public @ResponseBody List searchCertificationId( - @RequestParam(required = true) List listingIds) throws InvalidArgumentsException, - CertificationIdException { + @RequestParam(required = true) List listingIds) throws InvalidArgumentsException, EntityRetrievalException, + CertificationIdException, Exception { List certificationYears = certIdYearCalculator.getValidCertIdYearsToday(); return certificationYears.stream() - .map(certYear -> { - try { - return certIdSearchService.findCertificationByListingIds(listingIds, certYear, false); - } catch (InvalidArgumentsException | CertificationIdException ex) { - throw new RuntimeException(ex); - } - }) + .map(rethrowFunction(certYear -> certIdSearchService.findCertificationByListingIds(listingIds, certYear, false))) .collect(Collectors.toList()); } @@ -143,9 +138,8 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @DeprecatedApi(friendlyUrl = "/certification_ids", httpMethod = "POST", message = "This endpoint is deprecated and will be removed. Please POST to /certification-ids", removalDate = "2026-10-01") - public @ResponseBody CertificationIdResults createCertificationIdDeprecated( - @RequestParam(required = true) List ids) throws InvalidArgumentsException, - CertificationIdException { + public @ResponseBody CertificationIdResults createCertificationIdDeprecated(@RequestParam(required = true) List ids) + throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { return certIdSearchService.findCertificationByListingIds(ids, null, true); } @@ -160,9 +154,8 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @RequestMapping(value = "/certification-ids", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = { MediaType.APPLICATION_JSON_VALUE }) - public @ResponseBody CertificationIdResults createCertificationId( - @RequestBody CertificationIdCreateBody createBody) throws InvalidArgumentsException, - CertificationIdException { + public @ResponseBody CertificationIdResults createCertificationId(@RequestBody CertificationIdCreateBody createBody) + throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { return certIdSearchService.findCertificationByListingIds(createBody.getListingIds(), createBody.getYear(), true); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java index 0f03460b64..89ef8cd670 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java @@ -19,7 +19,6 @@ import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.certificationCriteria.CertificationCriterionEntity; import gov.healthit.chpl.dao.impl.BaseDAOImpl; -import gov.healthit.chpl.dto.CertifiedProductDetailsDTO; import gov.healthit.chpl.exception.EntityCreationException; import gov.healthit.chpl.exception.EntityRetrievalException; import jakarta.persistence.Query; @@ -117,9 +116,9 @@ public List getAllCertificationIdsWithPro return results; } - public CertificationIdDTO getByListings(List listings, String year) + public CertificationIdDTO getByListings(List listingIds, String year) throws EntityRetrievalException { - CertificationIdEntity entity = getEntityByListings(listings, year); + CertificationIdEntity entity = getEntityByListings(listingIds, year); if (entity == null) { return null; } @@ -240,11 +239,8 @@ private CertificationIdEntity getEntityByCertificationId(String certificationId) return entity; } - private CertificationIdEntity getEntityByListings(List listings, String year) + private CertificationIdEntity getEntityByListings(List listingIds, String year) throws EntityRetrievalException { - List productIds = listings.stream() - .map(listing -> listing.getId()) - .toList(); CertificationIdEntity entity = null; // Lookup the EHR Certification ID record by: @@ -275,8 +271,8 @@ private CertificationIdEntity getEntityByListings(List results = query.getResultList(); if (!CollectionUtils.isEmpty(results) && results.size() > 1) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java index a8facc09ab..6d5555994c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java @@ -7,10 +7,8 @@ import java.util.List; import java.util.Set; -import org.apache.commons.lang3.StringUtils; - import gov.healthit.chpl.certificationCriteria.CertificationCriterion; -import gov.healthit.chpl.dto.CertifiedProductDetailsDTO; +import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import lombok.Data; import lombok.Singular; @@ -42,32 +40,24 @@ public static class Product implements Serializable { private String classification; private String additionalSoftware; - /** Constructor. - * - * @param dto object to construct from - */ - public Product(CertifiedProductDetailsDTO dto) { - this.id = dto.getId(); - this.name = dto.getProduct().getName(); - this.version = dto.getVersion().getVersion(); - if (!StringUtils.isEmpty(dto.getChplProductNumber())) { - this.setChplProductNumber(dto.getChplProductNumber()); - } else { - this.setChplProductNumber(dto.getYearCode() + "." + dto.getTestingLabCode() + "." - + dto.getCertificationBodyCode() + "." + dto.getDeveloper().getDeveloperCode() + "." - + dto.getProductCode() + "." + dto.getVersionCode() + "." + dto.getIcsCode() + "." - + dto.getAdditionalSoftwareCode() + "." + dto.getCertifiedDateCode()); - } - this.year = dto.getYear(); - this.curesUpdate = dto.getCuresUpdate(); - this.practiceType = dto.getPracticeTypeName(); - this.acb = dto.getCertificationBodyName(); - this.vendor = dto.getDeveloper().getName(); - this.classification = dto.getProductClassificationName(); + public Product(CertifiedProductSearchDetails listing) { + this.id = listing.getId(); + this.name = listing.getProduct().getName(); + this.version = listing.getVersion().getVersion(); + this.setChplProductNumber(listing.getChplProductNumber()); + this.year = listing.getEdition().getName(); + this.curesUpdate = listing.getCuresUpdate(); + this.practiceType = listing.getPracticeType().get(CertifiedProductSearchDetails.PRACTICE_TYPE_NAME_KEY) != null + ? listing.getPracticeType().get(CertifiedProductSearchDetails.PRACTICE_TYPE_NAME_KEY).toString() : null; + this.acb = listing.getCertifyingBody().get(CertifiedProductSearchDetails.ACB_NAME_KEY) != null + ? listing.getCertifyingBody().get(CertifiedProductSearchDetails.ACB_NAME_KEY).toString() : null; + this.vendor = listing.getDeveloper().getName(); + this.classification = listing.getClassificationType().get(CertifiedProductSearchDetails.CLASSIFICATION_TYPE_NAME_KEY) != null + ? listing.getClassificationType().get(CertifiedProductSearchDetails.CLASSIFICATION_TYPE_NAME_KEY).toString() : null; this.additionalSoftware = ""; try { - if (null != dto.getProductAdditionalSoftware()) { - this.additionalSoftware = URLEncoder.encode(dto.getProductAdditionalSoftware(), "UTF-8"); + if (null != listing.getProductAdditionalSoftware()) { + this.additionalSoftware = URLEncoder.encode(listing.getProductAdditionalSoftware(), "UTF-8"); } } catch (final UnsupportedEncodingException ex) { // Do nothing diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdManager.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdManager.java index 82d00f5f51..dd0dab8941 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdManager.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdManager.java @@ -16,7 +16,6 @@ import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.domain.schedule.ChplJob; import gov.healthit.chpl.domain.schedule.ChplOneTimeTrigger; -import gov.healthit.chpl.dto.CertifiedProductDetailsDTO; import gov.healthit.chpl.exception.ActivityException; import gov.healthit.chpl.exception.EntityCreationException; import gov.healthit.chpl.exception.EntityRetrievalException; @@ -38,8 +37,8 @@ public CertificationIdManager(CertificationIdDAO certificationIdDao, } @Transactional(readOnly = true) - public CertificationIdDTO getByListings(List listings, String year) throws EntityRetrievalException { - return certificationIdDao.getByListings(listings, year); + public CertificationIdDTO getByListings(List listingIds, String year) throws EntityRetrievalException { + return certificationIdDao.getByListings(listingIds, year); } @Transactional(readOnly = true) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java index 98273ba4ba..80df812fd2 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java @@ -12,6 +12,7 @@ public class CertificationIdMetPercentages { private int criteriaMet; + private int criteriaUpToDate; private int cqmDomains; private int cqmsInpatient; private int cqmsAmbulatory; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java index 5cb36b13d6..1d9e0fa5f5 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdRequirements.java @@ -17,6 +17,8 @@ public class CertificationIdRequirements { private int criteriaDpRequiredMet = 0; private int criteriaDsRequired = 0; private int criteriaDsRequiredMet = 0; + private int criteriaUpToDateRequired = 0; + private int criteriaUpToDateMet = 0; private int cqmsInpatientRequired = 0; private int cqmsInpatientRequiredMet = 0; private int cqmsAmbulatoryRequired = 0; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java index adc7bb884a..471b062327 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdResults.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.TreeMap; -import gov.healthit.chpl.dto.CertifiedProductDetailsDTO; +import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import lombok.Data; @Data @@ -16,6 +16,7 @@ public class CertificationIdResults implements Serializable { private CertificationIdRequirements metCounts; private CertificationIdMetPercentages metPercentages; private ArrayList missingAnd = new ArrayList(); + private ArrayList missingUpToDate = new ArrayList(); private List> missingOr = new ArrayList>(); private List> missingCombo = new ArrayList>(); private List>> missingXOr = new ArrayList>>(); @@ -35,11 +36,11 @@ public static class Product implements Serializable { private String version; private String chplProductNumber; - public Product(CertifiedProductDetailsDTO dto) { - this.name = dto.getProduct().getName(); - this.productId = dto.getId(); - this.version = dto.getVersion().getVersion(); - this.chplProductNumber = dto.getChplProductNumber(); + public Product(CertifiedProductSearchDetails listing) { + this.name = listing.getProduct().getName(); + this.productId = listing.getId(); + this.version = listing.getVersion().getVersion(); + this.chplProductNumber = listing.getChplProductNumber(); } } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java index 5a6a1098fe..dd2e577701 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java @@ -1,6 +1,7 @@ package gov.healthit.chpl.certificationId; -import java.util.ArrayList; +import static gov.healthit.chpl.util.LambdaExceptionUtil.rethrowFunction; + import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -11,15 +12,14 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.certifiedproduct.CertifiedProductDetailsManager; +import gov.healthit.chpl.domain.CertifiedProductSearchDetails; import gov.healthit.chpl.domain.concept.CertificationEditionConcept; -import gov.healthit.chpl.dto.CertifiedProductDetailsDTO; import gov.healthit.chpl.exception.ActivityException; import gov.healthit.chpl.exception.CertificationIdException; import gov.healthit.chpl.exception.EntityCreationException; import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.exception.InvalidArgumentsException; -import gov.healthit.chpl.manager.CertifiedProductManager; import jakarta.transaction.Transactional; import lombok.extern.log4j.Log4j2; @@ -27,17 +27,17 @@ @Log4j2 public class CertificationIdSearchService { private CertificationIdManager certificationIdManager; - private CertifiedProductManager certifiedProductManager; + private CertifiedProductDetailsManager cpdManager; private CertificationIdYearCalculator certIdYearCalculator; private ValidatorFactory validatorFactory; @Autowired public CertificationIdSearchService(CertificationIdManager certificationIdManager, - CertifiedProductManager certifiedProductManager, + CertifiedProductDetailsManager cpdManager, CertificationIdYearCalculator certIdYearCalculator, ValidatorFactory validatorFactory) { this.certificationIdManager = certificationIdManager; - this.certifiedProductManager = certifiedProductManager; + this.cpdManager = cpdManager; this.certIdYearCalculator = certIdYearCalculator; this.validatorFactory = validatorFactory; } @@ -56,26 +56,21 @@ public CertificationIdLookupResults findCertificationIdByCertificationId(String // Find the listings associated with the Cert ID List listingIds = certificationIdManager.getListingIdsByCertificationId(certId.getId()); - List listingDtos = certifiedProductManager.getDetailsByIds(listingIds); + List listings = listingIds.stream() + .map(rethrowFunction(id -> cpdManager.getCertifiedProductDetails(id))) + .toList(); // Add product data to results - results.setProducts(listingDtos.stream() + results.setProducts(listings.stream() .map(listing -> new CertificationIdLookupResults.Product(listing)) .collect(Collectors.toList())); // Add criteria and cqms met to results if (includeCriteria || includeCqms) { Validator validator = this.validatorFactory.getValidator(certId.getYear()); - - // Lookup Criteria for Validating - List criteria = certificationIdManager.getCriteriaMetByListingIds(listingIds); - - // Lookup CQMs for Validating - List cqmDtos = certificationIdManager.getCqmsMetByListingIds(listingIds); - - boolean isValid = validator.validate(criteria, cqmDtos); + boolean isValid = validator.validate(listings); if (isValid) { if (includeCriteria) { - results.setCriteria(validator.getCriteriaMet().keySet()); + results.setCriteria(validator.getCriteriaMet()); } if (includeCqms) { results.setCqms(validator.getCqmsMet().keySet()); @@ -94,20 +89,17 @@ public CertificationIdLookupResults findCertificationIdByCertificationId(String } public CertificationIdResults findCertificationByListingIds(List listingIds, String certificationYear, Boolean create) - throws InvalidArgumentsException, CertificationIdException { + throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { if (CollectionUtils.isEmpty(listingIds)) { return null; } - List listings = new ArrayList(); - try { - listings = certifiedProductManager.getDetailsByIds(listingIds); - } catch (EntityRetrievalException ex) { - LOGGER.error(ex.getMessage(), ex); - } + List listings = listingIds.stream() + .map(rethrowFunction(id -> cpdManager.getCertifiedProductDetails(id))) + .toList(); if (create) { - Optional invalidListing = listings.stream() + Optional invalidListing = listings.stream() .filter(listing -> !isEditionlessOrCuresUpdate(listing)) .findAny(); if (invalidListing.isPresent()) { @@ -127,13 +119,7 @@ public CertificationIdResults findCertificationByListingIds(List listingId //this will throw an error if an invalid year is passed in Validator validator = this.validatorFactory.getValidator(results.getYear()); - // Lookup Criteria for Validating - List criteria = certificationIdManager.getCriteriaMetByListingIds(listingIds); - - // Lookup CQMs for Validating - List cqms = certificationIdManager.getCqmsMetByListingIds(listingIds); - - boolean isValid = validator.validate(criteria, cqms); + boolean isValid = validator.validate(listings); results.setValid(isValid); results.setMetPercentages(validator.getPercents()); results.setMetCounts(validator.getCounts()); @@ -141,12 +127,13 @@ public CertificationIdResults findCertificationByListingIds(List listingId results.setMissingOr(validator.getMissingOr()); results.setMissingAnd(validator.getMissingAnd()); results.setMissingXOr(validator.getMissingXOr()); + results.setMissingUpToDate(validator.getMissingUpToDate()); // Lookup CERT ID if (validator.isValid()) { CertificationIdDTO existingCertId = null; try { - existingCertId = certificationIdManager.getByListings(listings, results.getYear()); + existingCertId = certificationIdManager.getByListings(listingIds, results.getYear()); if (existingCertId != null) { results.setEhrCertificationId(existingCertId.getCertificationId()); } else { @@ -164,11 +151,11 @@ public CertificationIdResults findCertificationByListingIds(List listingId return results; } - private boolean isEditionlessOrCuresUpdate(CertifiedProductDetailsDTO listing) { - if (StringUtils.isEmpty(listing.getYear()) && listing.getCuresUpdate() == null) { + private boolean isEditionlessOrCuresUpdate(CertifiedProductSearchDetails listing) { + if (listing.getEdition() == null && listing.getCuresUpdate() == null) { return true; } - return listing.getYear().equals(CertificationEditionConcept.CERTIFICATION_EDITION_2015.getYear()) + return listing.getEdition().getName().equals(CertificationEditionConcept.CERTIFICATION_EDITION_2015.getYear()) && BooleanUtils.isTrue(listing.getCuresUpdate()); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdYearCalculator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdYearCalculator.java index 3d861176da..7bd60964d5 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdYearCalculator.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdYearCalculator.java @@ -35,6 +35,11 @@ public CertificationIdYearCalculator(@Value("${cmsIdStartDayOfYear}") String cms MonthDay.parse(cmsIdEndDayOfOverlap, monthDayFormatter)); } + public LocalDate getCmsIdStartDayOfCurrentYear() { + String currentCertIdYear = getCurrentCertIdYear(); + return LocalDate.parse(annualCertIdChangeMmDd + "/" + currentCertIdYear, dtFormatter); + } + public String getCurrentCertIdYear() { return getCurrentCertIdYear(DEFAULT_CERTID_YEAR); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java index 8c5db790db..b2d2d6749c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java @@ -2,20 +2,30 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.cqm.CQMResultDetails; +import gov.healthit.chpl.domain.CertifiedProductSearchDetails; public abstract class Validator { - private Map criteriaMet = new HashMap(); + private Set criteriaMet = new LinkedHashSet(); private Map cqmsMet = new HashMap(); private Map domainsMet = new HashMap(); // missing criteria where all in the set are required private ArrayList missingAnd = new ArrayList(); + // criteria are present but not up-to-date + private ArrayList missingUpToDate = new ArrayList(); // missing 1 criteria from each of the following sets private List> missingOr = new ArrayList>(); // missing at least one of the following combinations of criteria @@ -35,7 +45,7 @@ public CertificationIdMetPercentages getPercents() { return this.percents; } - public Map getCriteriaMet() { + public Set getCriteriaMet() { return this.criteriaMet; } @@ -47,6 +57,10 @@ public ArrayList getMissingAnd() { return missingAnd; } + public ArrayList getMissingUpToDate() { + return missingUpToDate; + } + public List> getMissingOr() { return missingOr; } @@ -75,27 +89,32 @@ public boolean isValid() { protected abstract boolean isDomainsValid(); - public boolean validate(List certDtos, List cqmDtos) { - this.collectMetData(certDtos, cqmDtos); + public boolean validate(List listings) { + this.collectMetData(listings); this.valid = this.onValidate(); this.calculatePercentages(); return this.isValid(); } - protected void collectMetData(List certDtos, List cqmDtos) { - + protected void collectMetData(List listings) { // Collect criteria met - if (null != certDtos) { - criteriaMet = new HashMap(certDtos.size()); - for (CertificationCriterion certDetail : certDtos) { - criteriaMet.put(certDetail, 1); - } + if (!CollectionUtils.isEmpty(listings)) { + criteriaMet = listings.stream() + .flatMap(listing -> listing.getCertificationResults().stream()) + .filter(certResult -> BooleanUtils.isTrue(certResult.getSuccess())) + .map(certResult -> certResult.getCriterion()) + .collect(Collectors.toSet()); } // Collect cqms and domains met - if (null != cqmDtos) { - cqmsMet = new HashMap(cqmDtos.size()); - for (CQMMetDTO cqmDetail : cqmDtos) { + if (!CollectionUtils.isEmpty(listings)) { + List attestedCqms = listings.stream() + .flatMap(listing -> listing.getCqmResults().stream()) + .filter(cqmResult -> cqmResult.getSuccess() || !CollectionUtils.isEmpty(cqmResult.getSuccessVersions())) + .collect(Collectors.toList()); + + cqmsMet = new HashMap(attestedCqms.size()); + for (CQMResultDetails cqmDetail : attestedCqms) { // See what version we've already met... Integer verMet = cqmsMet.get(cqmDetail.getCmsId()); if (null == verMet) { @@ -103,6 +122,7 @@ protected void collectMetData(List certDtos, List verMet) { cqmsMet.put(cqmDetail.getCmsId(), ver); @@ -113,13 +133,15 @@ protected void collectMetData(List certDtos, List editionYears) { String attYearString = null; - if ((null != editionYears) && (editionYears.size() > 0)) { // Get the lowest year... @@ -149,22 +170,17 @@ public static String calculateAttestationYear(SortedSet editionYears) { attYearString += "/" + editionYears.last().toString(); } } - return attYearString; } protected Boolean criteriaMetContainsCriterion(String criterion) { - Boolean found = false; - for (CertificationCriterion cert : criteriaMet.keySet()) { - if (cert.getNumber().equalsIgnoreCase(criterion)) { - found = true; - } - } - return found; + return criteriaMet.stream() + .filter(cert -> cert.getNumber().equals(criterion)) + .findAny().isPresent(); } protected Boolean criteriaMetContainsCriterion(CertificationCriterion criterion) { - return criteriaMet.keySet().stream() + return criteriaMet.stream() .filter(cert -> cert.getId().equals(criterion.getId())) .findAny().isPresent(); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java index 9371c5fdfc..773e240113 100755 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2015.java @@ -67,7 +67,7 @@ protected boolean isCriteriaValid() { this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; for (CertificationCriterion crit : requiredCriteria) { - Optional metRequiredCriterion = getCriteriaMet().keySet().stream() + Optional metRequiredCriterion = getCriteriaMet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java index e2b47f78fd..668a3ce7fc 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2025.java @@ -65,7 +65,7 @@ protected boolean isCriteriaValid() { this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; for (CertificationCriterion crit : requiredCriteria) { - Optional metRequiredCriterion = getCriteriaMet().keySet().stream() + Optional metRequiredCriterion = getCriteriaMet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 5f32319971..95dac8fcf8 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -1,32 +1,61 @@ package gov.healthit.chpl.certificationId; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.collections.CollectionUtils; + +import gov.healthit.chpl.attribute.CodeSetsUpToDateService; +import gov.healthit.chpl.attribute.GroupedStandardsUpToDateService; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; +import gov.healthit.chpl.standard.BaselineStandardService; import gov.healthit.chpl.util.Util; public class Validator2026 extends Validator { + private static final int NUM_CRITERIA_REQUIRING_UPDATES = 4; //a5, b1, g9, g10 + private CertificationCriterionService certificationCriterionService; + private CertificationIdYearCalculator certIdYearCalculator; + private BaselineStandardService baselineStandardService; + private GroupedStandardsUpToDateService groupedStandardService; + private CodeSetsUpToDateService codeSetService; private List requiredCriteria; private List cpoeCriteriaOr; private List dpCriteriaOr; - - public Validator2026(CertificationCriterionService certificationCriterionService) { - - requiredCriteria = Stream.of(certificationCriterionService.get(Criteria2015.A_5), + private List criteriaToCheckForUpdates; + private CertificationCriterion a5, b1, g9, g10; + + public Validator2026(CertificationCriterionService certificationCriterionService, + CertificationIdYearCalculator certIdYearCalculator, + BaselineStandardService baselineStandardService, + GroupedStandardsUpToDateService groupedStandardService, + CodeSetsUpToDateService codeSetService) { + this.certificationCriterionService = certificationCriterionService; + this.certIdYearCalculator = certIdYearCalculator; + this.baselineStandardService = baselineStandardService; + this.groupedStandardService = groupedStandardService; + this.codeSetService = codeSetService; + + a5 = certificationCriterionService.get(Criteria2015.A_5); + b1 = certificationCriterionService.get(Criteria2015.B_1_CURES); + g9 = certificationCriterionService.get(Criteria2015.G_9_CURES); + g10 = certificationCriterionService.get(Criteria2015.G_10); + criteriaToCheckForUpdates = Stream.of(a5, b1, g9, g10).toList(); + + requiredCriteria = Stream.of(a5, certificationCriterionService.get(Criteria2015.A_14), - certificationCriterionService.get(Criteria2015.B_1_CURES), + b1, certificationCriterionService.get(Criteria2015.B_11), certificationCriterionService.get(Criteria2015.C_1), certificationCriterionService.get(Criteria2015.G_7), - certificationCriterionService.get(Criteria2015.G_9_CURES), - certificationCriterionService.get(Criteria2015.G_10)).collect(Collectors.toCollection(ArrayList::new)); + g9, + g10).collect(Collectors.toCollection(ArrayList::new)); cpoeCriteriaOr = Stream.of(certificationCriterionService.get(Criteria2015.A_1), certificationCriterionService.get(Criteria2015.A_2), @@ -39,6 +68,8 @@ public Validator2026(CertificationCriterionService certificationCriterionService this.getCounts().setCriteriaRequired(requiredCriteria.size()); this.getCounts().setCriteriaRequiredMet(0); + this.getCounts().setCriteriaUpToDateRequired(NUM_CRITERIA_REQUIRING_UPDATES); + this.getCounts().setCriteriaUpToDateMet(0); this.getCounts().setCriteriaCpoeRequired(1); this.getCounts().setCriteriaCpoeRequiredMet(0); this.getCounts().setCriteriaDpRequired(1); @@ -56,14 +87,14 @@ public Validator2026(CertificationCriterionService certificationCriterionService } public boolean onValidate() { - return isCriteriaValid(); + return isCriteriaValid() && areAttributesUpToDate(); } protected boolean isCriteriaValid() { this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; for (CertificationCriterion crit : requiredCriteria) { - Optional metRequiredCriterion = getCriteriaMet().keySet().stream() + Optional metRequiredCriterion = getCriteriaMet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); @@ -118,6 +149,42 @@ protected boolean isDPValid() { return false; } + private boolean areAttributesUpToDate() { + //Get the date on which to determine which attributes are required. + //Ex: on 9/1/2026 see whichever standards and code sets were required + //and then we need to confirm the listings being used for cert id creation + //have those attributes today. + LocalDate dayToCalculateRequiredAttributes = certIdYearCalculator.getCmsIdStartDayOfCurrentYear(); + + List criteriaNotUpToDate = new ArrayList(); + criteriaToCheckForUpdates.stream() + .forEach(criterion -> { + boolean upToDate = true; + upToDate = upToDate && areBaselineStandardsUpToDateForCriterion(criterion, dayToCalculateRequiredAttributes); + upToDate = upToDate && areGroupedStandardsUpToDateForCriterion(criterion, dayToCalculateRequiredAttributes); + upToDate = upToDate && areCodeSetsUpToDateForCriterion(criterion, dayToCalculateRequiredAttributes); + if (!upToDate) { + criteriaNotUpToDate.add(criterion); + } else { + this.getCounts().setCriteriaUpToDateMet(this.getCounts().getCriteriaUpToDateMet() + 1); + } + }); + return CollectionUtils.isEmpty(criteriaNotUpToDate); + } + + private boolean areBaselineStandardsUpToDateForCriterion(CertificationCriterion criterion, LocalDate asOfDate) { + + } + + private boolean areGroupedStandardsUpToDateForCriterion(CertificationCriterion criterion, LocalDate asOfDate) { + + } + + private boolean areCodeSetsUpToDateForCriterion(CertificationCriterion criterion, LocalDate asOfDate) { + //what code sets were required on the date + codeSetse + } + protected boolean isCqmsValid() { return true; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java index 81b682cfa1..2ab1f81a0a 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java @@ -8,11 +8,14 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import gov.healthit.chpl.attribute.CodeSetsUpToDateService; +import gov.healthit.chpl.attribute.GroupedStandardsUpToDateService; import gov.healthit.chpl.email.ChplHtmlEmailBuilder; import gov.healthit.chpl.exception.InvalidArgumentsException; import gov.healthit.chpl.notifier.ChplTeamNotifier; import gov.healthit.chpl.notifier.InvalidCertificationIdYearMessage; import gov.healthit.chpl.service.CertificationCriterionService; +import gov.healthit.chpl.standard.BaselineStandardService; import gov.healthit.chpl.util.ErrorMessageUtil; import lombok.extern.log4j.Log4j2; @@ -22,6 +25,10 @@ public class ValidatorFactory { private Map> certIdYearToValidatorClassMap; private CertificationCriterionService certificationCriterionService; + private CertificationIdYearCalculator certIdYearCalculator; + private BaselineStandardService baselineStandardService; + private GroupedStandardsUpToDateService groupedStandardService; + private CodeSetsUpToDateService codeSetService; private ChplTeamNotifier chplTeamNotifier; private ChplHtmlEmailBuilder chplHtmlEmailBuilder; private Environment env; @@ -29,11 +36,19 @@ public class ValidatorFactory { @Autowired public ValidatorFactory(CertificationCriterionService certificationCriterionService, + CertificationIdYearCalculator certIdYearCalculator, + BaselineStandardService baselineStandardService, + GroupedStandardsUpToDateService groupedStandardService, + CodeSetsUpToDateService codeSetService, ChplTeamNotifier chplTeamNotifier, ChplHtmlEmailBuilder chplHtmlEmailBuilder, Environment env, ErrorMessageUtil msgUtil) { this.certificationCriterionService = certificationCriterionService; + this.certIdYearCalculator = certIdYearCalculator; + this.baselineStandardService = baselineStandardService; + this.groupedStandardService = groupedStandardService; + this.codeSetService = codeSetService; this.chplTeamNotifier = chplTeamNotifier; this.chplHtmlEmailBuilder = chplHtmlEmailBuilder; this.env = env; @@ -82,13 +97,19 @@ private Validator getNewInstance(Class validatorClazz) { LOGGER.error("Could not instantiate validator " + validatorClazz, ex); } } else if (validatorClazz.equals(Validator2015.class) - || validatorClazz.equals(Validator2025.class) - || validatorClazz.equals(Validator2026.class)) { + || validatorClazz.equals(Validator2025.class)) { try { result = (Validator) validatorClazz.getDeclaredConstructors()[0].newInstance(certificationCriterionService); } catch (Exception ex) { LOGGER.error("Could not instantiate validator " + validatorClazz, ex); } + } else if (validatorClazz.equals(Validator2026.class)) { + try { + result = (Validator) validatorClazz.getDeclaredConstructors()[0].newInstance( + certificationCriterionService, certIdYearCalculator, baselineStandardService, groupedStandardService, codeSetService); + } catch (Exception ex) { + LOGGER.error("Could not instantiate validator " + validatorClazz, ex); + } } return result; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java index 5edb55e53d..12c7d24fe4 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertifiedProductSearchDetails.java @@ -56,6 +56,10 @@ public class CertifiedProductSearchDetails implements Serializable { public static final String ACB_CODE_KEY = "code"; public static final String EDITION_ID_KEY = "id"; public static final String EDITION_NAME_KEY = "name"; + public static final String PRACTICE_TYPE_ID_KEY = "id"; + public static final String PRACTICE_TYPE_NAME_KEY = "name"; + public static final String CLASSIFICATION_TYPE_ID_KEY = "id"; + public static final String CLASSIFICATION_TYPE_NAME_KEY = "name"; @Schema(description = "The internal ID of the certified product.") private Long id; From b8824eadd4ea26ceaec2b32e50723238bfecdf7f Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Wed, 29 Apr 2026 15:36:34 -0400 Subject: [PATCH 05/14] wip: getting closer with up-to-date cmsid logic [#OCD-4928] --- .../CertificationIdSearchService.java | 6 ++- .../chpl/certificationId/Validator.java | 31 ++++++++------- .../chpl/certificationId/Validator2026.java | 39 ++++++++++++++----- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java index dd2e577701..59f01b9dc4 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java @@ -67,7 +67,8 @@ public CertificationIdLookupResults findCertificationIdByCertificationId(String // Add criteria and cqms met to results if (includeCriteria || includeCqms) { Validator validator = this.validatorFactory.getValidator(certId.getYear()); - boolean isValid = validator.validate(listings); + validator.getListings().addAll(listings); + boolean isValid = validator.validate(); if (isValid) { if (includeCriteria) { results.setCriteria(validator.getCriteriaMet()); @@ -118,8 +119,9 @@ public CertificationIdResults findCertificationByListingIds(List listingId // Validate the collection //this will throw an error if an invalid year is passed in Validator validator = this.validatorFactory.getValidator(results.getYear()); + validator.getListings().addAll(listings); - boolean isValid = validator.validate(listings); + boolean isValid = validator.validate(); results.setValid(isValid); results.setMetPercentages(validator.getPercents()); results.setMetCounts(validator.getCounts()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java index b2d2d6749c..bc07b621d8 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java @@ -12,6 +12,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.cqm.CQMResultDetails; @@ -21,6 +22,7 @@ public abstract class Validator { private Set criteriaMet = new LinkedHashSet(); private Map cqmsMet = new HashMap(); private Map domainsMet = new HashMap(); + private List listings = new ArrayList(); // missing criteria where all in the set are required private ArrayList missingAnd = new ArrayList(); @@ -49,6 +51,10 @@ public Set getCriteriaMet() { return this.criteriaMet; } + public List getListings() { + return this.listings; + } + public Map getCqmsMet() { return this.cqmsMet; } @@ -89,14 +95,14 @@ public boolean isValid() { protected abstract boolean isDomainsValid(); - public boolean validate(List listings) { - this.collectMetData(listings); + public boolean validate() { + this.collectMetData(); this.valid = this.onValidate(); this.calculatePercentages(); return this.isValid(); } - protected void collectMetData(List listings) { + protected void collectMetData() { // Collect criteria met if (!CollectionUtils.isEmpty(listings)) { criteriaMet = listings.stream() @@ -115,20 +121,15 @@ protected void collectMetData(List listings) { cqmsMet = new HashMap(attestedCqms.size()); for (CQMResultDetails cqmDetail : attestedCqms) { - // See what version we've already met... - Integer verMet = cqmsMet.get(cqmDetail.getCmsId()); - if (null == verMet) { - verMet = Integer.valueOf(0); - } + // Store the attested versions + Integer highestVersion = cqmDetail.getSuccessVersions().stream() + .map(ver -> Integer.parseInt(ver.substring(1))) + .max(Integer::compareTo) + .orElse(null); - // ...store the version that's higher. - - Integer ver = Integer.parseInt(cqmDetail.getVersion().substring(1)); - if (ver > verMet) { - cqmsMet.put(cqmDetail.getCmsId(), ver); - } + cqmsMet.put(cqmDetail.getCmsId(), highestVersion); - if (null != cqmDetail.getDomain()) { + if (!StringUtils.isEmpty(cqmDetail.getDomain())) { domainsMet.put(cqmDetail.getDomain(), 1); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 95dac8fcf8..0f5a676f57 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -8,13 +8,16 @@ import java.util.stream.Stream; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import gov.healthit.chpl.attribute.CodeSetsUpToDateService; import gov.healthit.chpl.attribute.GroupedStandardsUpToDateService; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; import gov.healthit.chpl.standard.BaselineStandardService; +import gov.healthit.chpl.standard.Standard; import gov.healthit.chpl.util.Util; public class Validator2026 extends Validator { @@ -159,11 +162,18 @@ private boolean areAttributesUpToDate() { List criteriaNotUpToDate = new ArrayList(); criteriaToCheckForUpdates.stream() .forEach(criterion -> { - boolean upToDate = true; - upToDate = upToDate && areBaselineStandardsUpToDateForCriterion(criterion, dayToCalculateRequiredAttributes); - upToDate = upToDate && areGroupedStandardsUpToDateForCriterion(criterion, dayToCalculateRequiredAttributes); - upToDate = upToDate && areCodeSetsUpToDateForCriterion(criterion, dayToCalculateRequiredAttributes); - if (!upToDate) { + //any one cert result for the criterion being checked must be fully up-to-date + List certResultsForCriterion = this.getListings().stream() + .flatMap(listing -> listing.getCertificationResults().stream()) + .filter(certResult -> certResult.getCriterion().getId().equals(criterion.getId()) && BooleanUtils.isTrue(certResult.getSuccess())) + .collect(Collectors.toList()); + + CertificationResult fullyUpToDateCertResultForCriterion = certResultsForCriterion.stream() + .filter(certResult -> isCertResultFullyUpToDate(certResult, dayToCalculateRequiredAttributes)) + .findAny() + .orElse(null); + + if (fullyUpToDateCertResultForCriterion == null) { criteriaNotUpToDate.add(criterion); } else { this.getCounts().setCriteriaUpToDateMet(this.getCounts().getCriteriaUpToDateMet() + 1); @@ -172,17 +182,28 @@ private boolean areAttributesUpToDate() { return CollectionUtils.isEmpty(criteriaNotUpToDate); } - private boolean areBaselineStandardsUpToDateForCriterion(CertificationCriterion criterion, LocalDate asOfDate) { + private boolean isCertResultFullyUpToDate(CertificationResult certResult, LocalDate asOfDate) { + return areBaselineStandardsUpToDate(certResult, asOfDate) + && areGroupedStandardsUpToDate(certResult, asOfDate) + && areCodeSetsUpToDate(certResult, asOfDate); + } + + private boolean areBaselineStandardsUpToDate(CertificationResult certResult, LocalDate asOfDate) { + List baselineStandardsRequiredAsOfDate = baselineStandardService.getBaselineStandards(certResult.getCriterion(), asOfDate, asOfDate); + boolean doesAnyCertResultHaveAllBaselineStandards = certResultsForCriterion.stream() + .filter(certResult -> doesCertResultHaveAllStandards(certResult, baselineStandardsRequiredAsOfDate)) + .findAny() + .orElse(null); } - private boolean areGroupedStandardsUpToDateForCriterion(CertificationCriterion criterion, LocalDate asOfDate) { + private boolean areGroupedStandardsUpToDate(CertificationResult certResult, LocalDate asOfDate) { } - private boolean areCodeSetsUpToDateForCriterion(CertificationCriterion criterion, LocalDate asOfDate) { + private boolean areCodeSetsUpToDate(CertificationResult certResult, LocalDate asOfDate) { //what code sets were required on the date - codeSetse + codeSetService.getAttributeUpToDate(null, null) } protected boolean isCqmsValid() { From 2a53cdf46c93bc6cca325823e2d0cfd5089d8da9 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 30 Apr 2026 17:00:39 -0400 Subject: [PATCH 06/14] feat: Complete filling out up-to-date required criteria [#OCD-4928] --- .../BaselineStandardsUpToDateService.java | 14 +-- .../GroupedStandardsUpToDateService.java | 14 +-- .../certificationId/CertificationIdDAO.java | 11 +- .../chpl/certificationId/Validator2026.java | 108 +++++++++++------- .../certificationId/ValidatorFactory.java | 22 ++-- .../standard/BaselineStandardService.java | 53 ++++----- .../healthit/chpl/standard/StandardDAO.java | 4 +- .../chpl/standard/StandardGroupService.java | 28 ++--- 8 files changed, 130 insertions(+), 124 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/BaselineStandardsUpToDateService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/BaselineStandardsUpToDateService.java index 0a06cea2de..4fbe51c647 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/BaselineStandardsUpToDateService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/BaselineStandardsUpToDateService.java @@ -12,7 +12,6 @@ import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; -import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.standard.BaselineStandardService; import gov.healthit.chpl.standard.Standard; import gov.healthit.chpl.standard.StandardDAO; @@ -104,14 +103,9 @@ private Boolean isStandardInList(Standard standardToCheck, List standa } private boolean doesCriterionHaveAnyStandards(CertificationCriterion criterion, Logger logger) { - try { - return standardDao.getAllStandardCriteriaMap().stream() - .filter(map -> map.getCriterion().getId().equals(criterion.getId())) - .findAny() - .isPresent(); - } catch (EntityRetrievalException e) { - logger.error("Could not retrieve Standards for Criterion.", e); - return false; - } + return standardDao.getAllStandardCriteriaMap().stream() + .filter(map -> map.getCriterion().getId().equals(criterion.getId())) + .findAny() + .isPresent(); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/GroupedStandardsUpToDateService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/GroupedStandardsUpToDateService.java index a8fee4db2e..2c05379cb9 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/GroupedStandardsUpToDateService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/attribute/GroupedStandardsUpToDateService.java @@ -16,7 +16,6 @@ import gov.healthit.chpl.certificationCriteria.CertificationCriterion; import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.domain.CertifiedProductSearchDetails; -import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.standard.Standard; import gov.healthit.chpl.standard.StandardDAO; import gov.healthit.chpl.standard.StandardGroupService; @@ -189,14 +188,9 @@ private Boolean isStandardInList(Standard standardToCheck, List standa } private List getAllStandardsForCriterion(CertificationCriterion criterion, Logger logger) { - try { - return standardDao.getAllStandardCriteriaMap().stream() - .filter(map -> map.getCriterion().getId().equals(criterion.getId())) - .map(map -> map.getStandard()) - .toList(); - } catch (EntityRetrievalException e) { - logger.error("Could not retrieve Standards for Criterion.", e); - return List.of(); - } + return standardDao.getAllStandardCriteriaMap().stream() + .filter(map -> map.getCriterion().getId().equals(criterion.getId())) + .map(map -> map.getStandard()) + .toList(); } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java index 89ef8cd670..3e080e1a8b 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdDAO.java @@ -1,5 +1,6 @@ package gov.healthit.chpl.certificationId; +import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -302,7 +303,7 @@ private List getAllCertificationIdsWit private String generateCertificationIdString(String year) throws EntityCreationException { StringBuffer newId = new StringBuffer(); - newId.append(year); + newId.append(getYearPartOfNewCertIdString(year)); newId.append("C"); int suffixLength = (CERT_ID_LENGTH - newId.length()); @@ -330,4 +331,12 @@ private String generateCertificationIdString(String year) throws EntityCreationE return newId.toString(); } + + private String getYearPartOfNewCertIdString(String year) { + LocalDate now = LocalDate.now(); + if (now.isBefore(certIdYearCalculator.getInitialCmsIdTransitionToAnnualFormatDay())) { + return "00" + year.substring(year.length() - 2); + } + return year; + } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 0f5a676f57..184d2eacd7 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -3,6 +3,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -10,23 +11,23 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; -import gov.healthit.chpl.attribute.CodeSetsUpToDateService; -import gov.healthit.chpl.attribute.GroupedStandardsUpToDateService; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; +import gov.healthit.chpl.codeset.CodeSet; +import gov.healthit.chpl.codeset.CodeSetDAO; import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; -import gov.healthit.chpl.standard.BaselineStandardService; import gov.healthit.chpl.standard.Standard; +import gov.healthit.chpl.standard.StandardCriteriaMap; +import gov.healthit.chpl.standard.StandardDAO; +import gov.healthit.chpl.util.DateUtil; import gov.healthit.chpl.util.Util; public class Validator2026 extends Validator { private static final int NUM_CRITERIA_REQUIRING_UPDATES = 4; //a5, b1, g9, g10 - private CertificationCriterionService certificationCriterionService; private CertificationIdYearCalculator certIdYearCalculator; - private BaselineStandardService baselineStandardService; - private GroupedStandardsUpToDateService groupedStandardService; - private CodeSetsUpToDateService codeSetService; + private StandardDAO standardDao; + private CodeSetDAO codeSetDao; private List requiredCriteria; private List cpoeCriteriaOr; @@ -36,14 +37,11 @@ public class Validator2026 extends Validator { public Validator2026(CertificationCriterionService certificationCriterionService, CertificationIdYearCalculator certIdYearCalculator, - BaselineStandardService baselineStandardService, - GroupedStandardsUpToDateService groupedStandardService, - CodeSetsUpToDateService codeSetService) { - this.certificationCriterionService = certificationCriterionService; + StandardDAO standardDao, + CodeSetDAO codeSetDao) { this.certIdYearCalculator = certIdYearCalculator; - this.baselineStandardService = baselineStandardService; - this.groupedStandardService = groupedStandardService; - this.codeSetService = codeSetService; + this.standardDao = standardDao; + this.codeSetDao = codeSetDao; a5 = certificationCriterionService.get(Criteria2015.A_5); b1 = certificationCriterionService.get(Criteria2015.B_1_CURES); @@ -90,7 +88,10 @@ public Validator2026(CertificationCriterionService certificationCriterionService } public boolean onValidate() { - return isCriteriaValid() && areAttributesUpToDate(); + //written this way so both validation checks get called and we have all missing info at once + boolean isCriteriaValid = isCriteriaValid(); + boolean areAttributesUpToDate = areAttributesUpToDate(); + return isCriteriaValid && areAttributesUpToDate; } protected boolean isCriteriaValid() { @@ -159,7 +160,6 @@ private boolean areAttributesUpToDate() { //have those attributes today. LocalDate dayToCalculateRequiredAttributes = certIdYearCalculator.getCmsIdStartDayOfCurrentYear(); - List criteriaNotUpToDate = new ArrayList(); criteriaToCheckForUpdates.stream() .forEach(criterion -> { //any one cert result for the criterion being checked must be fully up-to-date @@ -168,42 +168,72 @@ private boolean areAttributesUpToDate() { .filter(certResult -> certResult.getCriterion().getId().equals(criterion.getId()) && BooleanUtils.isTrue(certResult.getSuccess())) .collect(Collectors.toList()); - CertificationResult fullyUpToDateCertResultForCriterion = certResultsForCriterion.stream() - .filter(certResult -> isCertResultFullyUpToDate(certResult, dayToCalculateRequiredAttributes)) - .findAny() - .orElse(null); - - if (fullyUpToDateCertResultForCriterion == null) { - criteriaNotUpToDate.add(criterion); - } else { - this.getCounts().setCriteriaUpToDateMet(this.getCounts().getCriteriaUpToDateMet() + 1); + if (!CollectionUtils.isEmpty(certResultsForCriterion)) { + CertificationResult fullyUpToDateCertResultForCriterion = certResultsForCriterion.stream() + .filter(certResult -> isCertResultFullyUpToDate(certResult, dayToCalculateRequiredAttributes)) + .findAny() + .orElse(null); + + if (fullyUpToDateCertResultForCriterion == null) { + getMissingUpToDate().add(Util.formatCriteriaNumber(criterion)); + } else { + this.getCounts().setCriteriaUpToDateMet(this.getCounts().getCriteriaUpToDateMet() + 1); + } } }); - return CollectionUtils.isEmpty(criteriaNotUpToDate); + return CollectionUtils.isEmpty(getMissingUpToDate()); } private boolean isCertResultFullyUpToDate(CertificationResult certResult, LocalDate asOfDate) { - return areBaselineStandardsUpToDate(certResult, asOfDate) - && areGroupedStandardsUpToDate(certResult, asOfDate) + return areStandardsUpToDate(certResult, asOfDate) && areCodeSetsUpToDate(certResult, asOfDate); } - private boolean areBaselineStandardsUpToDate(CertificationResult certResult, LocalDate asOfDate) { - List baselineStandardsRequiredAsOfDate = baselineStandardService.getBaselineStandards(certResult.getCriterion(), asOfDate, asOfDate); - - boolean doesAnyCertResultHaveAllBaselineStandards = certResultsForCriterion.stream() - .filter(certResult -> doesCertResultHaveAllStandards(certResult, baselineStandardsRequiredAsOfDate)) - .findAny() - .orElse(null); + private boolean areStandardsUpToDate(CertificationResult certResult, LocalDate asOfDate) { + List stdCriteriaMaps = standardDao.getAllStandardCriteriaMap(); + stdCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(certResult.getCriterion().getId())); + List requiredStandardsForCriterionAsOfDate = stdCriteriaMaps.stream() + .map(map -> map.getStandard()) + .filter(std -> std.getStartDay().isBefore(asOfDate) + && (std.getRequiredDay() != null && DateUtil.isOnOrBefore(std.getRequiredDay(), asOfDate)) + && (std.getEndDay() == null || std.getEndDay().isAfter(asOfDate))) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(requiredStandardsForCriterionAsOfDate)) { + return !requiredStandardsForCriterionAsOfDate.stream() + .filter(requiredStandard -> !isStandardOnCertResult(requiredStandard, certResult)) + .findAny() + .isPresent(); + } + return true; } - private boolean areGroupedStandardsUpToDate(CertificationResult certResult, LocalDate asOfDate) { - + private boolean isStandardOnCertResult(Standard standard, CertificationResult certResult) { + return certResult.getStandards().stream() + .filter(certResultStd -> certResultStd.getStandard().getId().equals(standard.getId())) + .findAny() + .isPresent(); } private boolean areCodeSetsUpToDate(CertificationResult certResult, LocalDate asOfDate) { - //what code sets were required on the date - codeSetService.getAttributeUpToDate(null, null) + List codeSetsRequiredForCriterion = null; + Map> codeSetMaps = codeSetDao.getCodeSetCriteriaMaps(); + if (codeSetMaps.containsKey(certResult.getCriterion().getId())) { + codeSetsRequiredForCriterion = codeSetMaps.get(certResult.getCriterion().getId()).stream() + .filter(codeSet -> DateUtil.isOnOrBefore(codeSet.getRequiredDay(), asOfDate)) + .collect(Collectors.toList()); + return !codeSetsRequiredForCriterion.stream() + .filter(requiredCodeSet -> !isCodeSetOnCertResult(requiredCodeSet, certResult)) + .findAny() + .isPresent(); + } + return true; + } + + private Boolean isCodeSetOnCertResult(CodeSet codeSet, CertificationResult certResult) { + return certResult.getCodeSets().stream() + .filter(cs -> cs.getId().equals(codeSet.getId())) + .findAny() + .isPresent(); } protected boolean isCqmsValid() { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java index 2ab1f81a0a..b5a47c4626 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java @@ -8,14 +8,13 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; -import gov.healthit.chpl.attribute.CodeSetsUpToDateService; -import gov.healthit.chpl.attribute.GroupedStandardsUpToDateService; +import gov.healthit.chpl.codeset.CodeSetDAO; import gov.healthit.chpl.email.ChplHtmlEmailBuilder; import gov.healthit.chpl.exception.InvalidArgumentsException; import gov.healthit.chpl.notifier.ChplTeamNotifier; import gov.healthit.chpl.notifier.InvalidCertificationIdYearMessage; import gov.healthit.chpl.service.CertificationCriterionService; -import gov.healthit.chpl.standard.BaselineStandardService; +import gov.healthit.chpl.standard.StandardDAO; import gov.healthit.chpl.util.ErrorMessageUtil; import lombok.extern.log4j.Log4j2; @@ -26,9 +25,8 @@ public class ValidatorFactory { private Map> certIdYearToValidatorClassMap; private CertificationCriterionService certificationCriterionService; private CertificationIdYearCalculator certIdYearCalculator; - private BaselineStandardService baselineStandardService; - private GroupedStandardsUpToDateService groupedStandardService; - private CodeSetsUpToDateService codeSetService; + private StandardDAO standardDao; + private CodeSetDAO codeSetDao; private ChplTeamNotifier chplTeamNotifier; private ChplHtmlEmailBuilder chplHtmlEmailBuilder; private Environment env; @@ -37,18 +35,16 @@ public class ValidatorFactory { @Autowired public ValidatorFactory(CertificationCriterionService certificationCriterionService, CertificationIdYearCalculator certIdYearCalculator, - BaselineStandardService baselineStandardService, - GroupedStandardsUpToDateService groupedStandardService, - CodeSetsUpToDateService codeSetService, + StandardDAO standardDao, + CodeSetDAO codeSetDao, ChplTeamNotifier chplTeamNotifier, ChplHtmlEmailBuilder chplHtmlEmailBuilder, Environment env, ErrorMessageUtil msgUtil) { this.certificationCriterionService = certificationCriterionService; this.certIdYearCalculator = certIdYearCalculator; - this.baselineStandardService = baselineStandardService; - this.groupedStandardService = groupedStandardService; - this.codeSetService = codeSetService; + this.standardDao = standardDao; + this.codeSetDao = codeSetDao; this.chplTeamNotifier = chplTeamNotifier; this.chplHtmlEmailBuilder = chplHtmlEmailBuilder; this.env = env; @@ -106,7 +102,7 @@ private Validator getNewInstance(Class validatorClazz) { } else if (validatorClazz.equals(Validator2026.class)) { try { result = (Validator) validatorClazz.getDeclaredConstructors()[0].newInstance( - certificationCriterionService, certIdYearCalculator, baselineStandardService, groupedStandardService, codeSetService); + certificationCriterionService, certIdYearCalculator, standardDao, codeSetDao); } catch (Exception ex) { LOGGER.error("Could not instantiate validator " + validatorClazz, ex); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/BaselineStandardService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/BaselineStandardService.java index 42f1d8b181..b38fae63da 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/BaselineStandardService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/BaselineStandardService.java @@ -9,7 +9,6 @@ import org.springframework.stereotype.Component; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; -import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.util.DateUtil; import lombok.extern.log4j.Log4j2; @@ -27,42 +26,32 @@ public BaselineStandardService(StandardGroupService standardGroupService, } public List getBaselineStandards(CertificationCriterion criterion, - LocalDate standardCheckDateRangeStart, LocalDate standardCheckDateRangeEnd) { - try { - List stdCriteriaMaps = standardDao.getAllStandardCriteriaMap(); - Map> standardGroups = standardGroupService.getGroupedStandardsForCriteria(criterion, standardCheckDateRangeStart, standardCheckDateRangeEnd); + LocalDate standardCheckDateRangeStart, LocalDate standardCheckDateRangeEnd) { + List stdCriteriaMaps = standardDao.getAllStandardCriteriaMap(); + Map> standardGroups = standardGroupService.getGroupedStandardsForCriteria(criterion, standardCheckDateRangeStart, standardCheckDateRangeEnd); - stdCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(criterion.getId())); - return stdCriteriaMaps.stream() - .filter(stdCriteriaMap -> !isStandardInAGroup(standardGroups, stdCriteriaMap.getStandard()) - && DateUtil.isDateBetweenInclusive( - Pair.of(stdCriteriaMap.getStandard().getRequiredDay() != null ? stdCriteriaMap.getStandard().getRequiredDay().plusDays(1) : null, - stdCriteriaMap.getStandard().getEndDay()), - standardCheckDateRangeEnd)) - .map(map -> map.getStandard()) - .toList(); - } catch (EntityRetrievalException e) { - LOGGER.info("Error retrieving Standards for Criterion"); - throw new RuntimeException(e); - } + stdCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(criterion.getId())); + return stdCriteriaMaps.stream() + .filter(stdCriteriaMap -> !isStandardInAGroup(standardGroups, stdCriteriaMap.getStandard()) + && DateUtil.isDateBetweenInclusive( + Pair.of(stdCriteriaMap.getStandard().getRequiredDay() != null ? stdCriteriaMap.getStandard().getRequiredDay().plusDays(1) : null, + stdCriteriaMap.getStandard().getEndDay()), + standardCheckDateRangeEnd)) + .map(map -> map.getStandard()) + .toList(); } public List getActiveBaselineStandardsForCriterion(CertificationCriterion criterion, - LocalDate standardCheckDateRangeStart, LocalDate standardCheckDateRangeEnd) { - try { - List standardCriteriaMaps = standardDao.getAllStandardCriteriaMap(); - Map> standardGroups = standardGroupService.getGroupedStandardsForCriteria(criterion, standardCheckDateRangeStart, standardCheckDateRangeEnd); + LocalDate standardCheckDateRangeStart, LocalDate standardCheckDateRangeEnd) { + List standardCriteriaMaps = standardDao.getAllStandardCriteriaMap(); + Map> standardGroups = standardGroupService.getGroupedStandardsForCriteria(criterion, standardCheckDateRangeStart, standardCheckDateRangeEnd); - standardCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(criterion.getId())); - return standardCriteriaMaps.stream() - .filter(map -> !isStandardInAGroup(standardGroups, map.getStandard()) - && DateUtil.isDateBetweenInclusive(Pair.of(map.getStandard().getStartDay(), map.getStandard().getEndDay()), standardCheckDateRangeStart)) - .map(map -> map.getStandard()) - .toList(); - } catch (EntityRetrievalException e) { - LOGGER.info("Error retrieving Standards for Criterion"); - throw new RuntimeException(e); - } + standardCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(criterion.getId())); + return standardCriteriaMaps.stream() + .filter(map -> !isStandardInAGroup(standardGroups, map.getStandard()) + && DateUtil.isDateBetweenInclusive(Pair.of(map.getStandard().getStartDay(), map.getStandard().getEndDay()), standardCheckDateRangeStart)) + .map(map -> map.getStandard()) + .toList(); } private Boolean isStandardInAGroup(Map> standardGroups, Standard standard) { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardDAO.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardDAO.java index f878f9d626..1ec3f88e3c 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardDAO.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardDAO.java @@ -154,7 +154,7 @@ public Map> getStandardCriteriaMaps() { } @Transactional - public List getAllStandardCriteriaMap() throws EntityRetrievalException { + public List getAllStandardCriteriaMap() { return getAllStandardCriteriaMapEntities().stream() .map(e -> e.toDomain(criterionComparator)) .collect(Collectors.toList()); @@ -212,7 +212,7 @@ private StandardEntity getEntityById(Long id) throws EntityRetrievalException { return entity; } - private List getAllStandardCriteriaMapEntities() throws EntityRetrievalException { + private List getAllStandardCriteriaMapEntities() { return entityManager.createQuery("SELECT DISTINCT scm " + "FROM StandardCriteriaMapEntity scm " + "JOIN FETCH scm.criterion c " diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardGroupService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardGroupService.java index 835bbd09c4..e479042daf 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardGroupService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/standard/StandardGroupService.java @@ -11,7 +11,6 @@ import org.springframework.stereotype.Component; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; -import gov.healthit.chpl.exception.EntityRetrievalException; import gov.healthit.chpl.util.DateUtil; import lombok.extern.log4j.Log4j2; @@ -27,21 +26,16 @@ public StandardGroupService(StandardDAO standardDAO) { } public Map> getGroupedStandardsForCriteria(CertificationCriterion criterion, LocalDate validAsOfDateRangeStart, LocalDate validAsOfDateRangeEnd) { - try { - Map> groupedStandardsForCriteria = standardDAO.getAllStandardCriteriaMap().stream() - .filter(stdCriteriaMap -> stdCriteriaMap.getCriterion().getId().equals(criterion.getId()) - && StringUtils.isNotEmpty(stdCriteriaMap.getStandard().getGroupName()) - && DateUtil.datesOverlap(Pair.of(stdCriteriaMap.getStandard().getStartDay(), stdCriteriaMap.getStandard().getEndDay()), - Pair.of(validAsOfDateRangeStart, validAsOfDateRangeEnd))) - .collect(Collectors.groupingBy(value -> value.getStandard().getGroupName(), Collectors.mapping(value -> value.getStandard(), Collectors.toList()))); - - //Remove any entries where the group only has 1 standard in the list - groupedStandardsForCriteria.entrySet().removeIf(entry -> entry.getValue().size() < 2); - - return groupedStandardsForCriteria; - } catch (EntityRetrievalException e) { - LOGGER.error("Error retrieving all StandardCriteriaMaps: {}", e.getStackTrace(), e); - throw new RuntimeException(e); - } + Map> groupedStandardsForCriteria = standardDAO.getAllStandardCriteriaMap().stream() + .filter(stdCriteriaMap -> stdCriteriaMap.getCriterion().getId().equals(criterion.getId()) + && StringUtils.isNotEmpty(stdCriteriaMap.getStandard().getGroupName()) + && DateUtil.datesOverlap(Pair.of(stdCriteriaMap.getStandard().getStartDay(), stdCriteriaMap.getStandard().getEndDay()), + Pair.of(validAsOfDateRangeStart, validAsOfDateRangeEnd))) + .collect(Collectors.groupingBy(value -> value.getStandard().getGroupName(), Collectors.mapping(value -> value.getStandard(), Collectors.toList()))); + + //Remove any entries where the group only has 1 standard in the list + groupedStandardsForCriteria.entrySet().removeIf(entry -> entry.getValue().size() < 2); + + return groupedStandardsForCriteria; } } From afcbb3299d2dd86c267fc845f81bc4355f764954 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Fri, 1 May 2026 13:12:47 -0400 Subject: [PATCH 07/14] refactor: Use correct logic to check for code sets present [#OCD-4928] --- .../healthit/chpl/certificationId/Validator2026.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 184d2eacd7..386fce7187 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -221,17 +221,19 @@ private boolean areCodeSetsUpToDate(CertificationResult certResult, LocalDate as codeSetsRequiredForCriterion = codeSetMaps.get(certResult.getCriterion().getId()).stream() .filter(codeSet -> DateUtil.isOnOrBefore(codeSet.getRequiredDay(), asOfDate)) .collect(Collectors.toList()); - return !codeSetsRequiredForCriterion.stream() - .filter(requiredCodeSet -> !isCodeSetOnCertResult(requiredCodeSet, certResult)) - .findAny() - .isPresent(); + if (!CollectionUtils.isEmpty(codeSetsRequiredForCriterion)) { + return !codeSetsRequiredForCriterion.stream() + .filter(requiredCodeSet -> !isCodeSetOnCertResult(requiredCodeSet, certResult)) + .findAny() + .isPresent(); + } } return true; } private Boolean isCodeSetOnCertResult(CodeSet codeSet, CertificationResult certResult) { return certResult.getCodeSets().stream() - .filter(cs -> cs.getId().equals(codeSet.getId())) + .filter(cs -> cs.getCodeSet().getId().equals(codeSet.getId())) .findAny() .isPresent(); } From 8c6ad0a415e12c80cbaf2852b67be23ee2f3c08c Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Fri, 1 May 2026 13:13:12 -0400 Subject: [PATCH 08/14] feat: Attempt to make getting listing details for cms id more efficient [#OCD-4928] --- .../CertificationIdSearchService.java | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java index 59f01b9dc4..bce48fde9e 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java @@ -1,9 +1,11 @@ package gov.healthit.chpl.certificationId; -import static gov.healthit.chpl.util.LambdaExceptionUtil.rethrowFunction; - +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; @@ -56,9 +58,7 @@ public CertificationIdLookupResults findCertificationIdByCertificationId(String // Find the listings associated with the Cert ID List listingIds = certificationIdManager.getListingIdsByCertificationId(certId.getId()); - List listings = listingIds.stream() - .map(rethrowFunction(id -> cpdManager.getCertifiedProductDetails(id))) - .toList(); + List listings = getAllListingDetails(listingIds); // Add product data to results results.setProducts(listings.stream() .map(listing -> new CertificationIdLookupResults.Product(listing)) @@ -95,9 +95,7 @@ public CertificationIdResults findCertificationByListingIds(List listingId return null; } - List listings = listingIds.stream() - .map(rethrowFunction(id -> cpdManager.getCertifiedProductDetails(id))) - .toList(); + List listings = getAllListingDetails(listingIds); if (create) { Optional invalidListing = listings.stream() @@ -153,6 +151,37 @@ public CertificationIdResults findCertificationByListingIds(List listingId return results; } + private List getAllListingDetails(List listingIds) { + ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); + List>> futures = new ArrayList>>(); + listingIds.stream() + .forEach(listingId -> futures.add(CompletableFuture + .supplyAsync(() -> getCertifiedProductSearchDetails(listingId), executorService))); + + CompletableFuture[] futuresArray = futures.toArray(new CompletableFuture[0]); + CompletableFuture>> listFuture = CompletableFuture.allOf(futuresArray) + .thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList())); + List listings = listFuture.join().stream() + .filter(opt -> opt.isPresent()) + .map(opt -> opt.get()) + .collect(Collectors.toList()); + try { + executorService.close(); + } catch (Exception ex) { + LOGGER.error("Executor service did not properly close", ex); + } + return listings; + } + + protected Optional getCertifiedProductSearchDetails(Long listingId) { + try { + return Optional.of(cpdManager.getCertifiedProductDetails(listingId)); + } catch (EntityRetrievalException e) { + LOGGER.error(String.format("Could not retrieve listing: %s", listingId), e); + return Optional.empty(); + } + } + private boolean isEditionlessOrCuresUpdate(CertifiedProductSearchDetails listing) { if (listing.getEdition() == null && listing.getCuresUpdate() == null) { return true; From 625675ccf6635ab3bed89970572642dd2b9cfbb4 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Fri, 1 May 2026 15:09:53 -0400 Subject: [PATCH 09/14] wip: How to account for required criteria met but not up-to-date in %s [#OCD-4928] --- .../certificationId/CertificationIdLookupResults.java | 2 +- .../CertificationIdMetPercentages.java | 1 - .../gov/healthit/chpl/certificationId/Validator.java | 3 --- .../healthit/chpl/certificationId/Validator2026.java | 11 +++++++++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java index 6d5555994c..35edbaecbf 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdLookupResults.java @@ -45,7 +45,7 @@ public Product(CertifiedProductSearchDetails listing) { this.name = listing.getProduct().getName(); this.version = listing.getVersion().getVersion(); this.setChplProductNumber(listing.getChplProductNumber()); - this.year = listing.getEdition().getName(); + this.year = listing.getEdition() == null ? null : listing.getEdition().getName(); this.curesUpdate = listing.getCuresUpdate(); this.practiceType = listing.getPracticeType().get(CertifiedProductSearchDetails.PRACTICE_TYPE_NAME_KEY) != null ? listing.getPracticeType().get(CertifiedProductSearchDetails.PRACTICE_TYPE_NAME_KEY).toString() : null; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java index 80df812fd2..98273ba4ba 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdMetPercentages.java @@ -12,7 +12,6 @@ public class CertificationIdMetPercentages { private int criteriaMet; - private int criteriaUpToDate; private int cqmDomains; private int cqmsInpatient; private int cqmsAmbulatory; diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java index bc07b621d8..c1b5534c10 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator.java @@ -140,9 +140,6 @@ protected void calculatePercentages() { this.percents.setCriteriaMet(this.counts.getCriteriaRequired() == 0 ? 0 : Math.min((int) Math.floor((this.counts.getCriteriaRequiredMet() * 100.0) / this.counts.getCriteriaRequired()), 100)); - this.percents.setCriteriaUpToDate(this.counts.getCriteriaUpToDateRequired() == 0 - ? 0 - : Math.min((int) Math.floor((this.counts.getCriteriaUpToDateMet() * 100.0) / this.counts.getCriteriaUpToDateRequired()), 100)); this.percents.setCqmDomains(this.counts.getDomainsRequired() == 0 ? 0 : Math.min((int) Math.floor((this.counts.getDomainsRequiredMet() * 100.0) / this.counts.getDomainsRequired()), 100)); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 386fce7187..977d90b90b 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -153,6 +153,17 @@ protected boolean isDPValid() { return false; } + @Override + protected void calculatePercentages() { + getPercents().setCriteriaMet((getCounts().getCriteriaRequired() + getCounts().getCriteriaUpToDateRequired())== 0 + ? 0 + : Math.min((int) Math.floor(((getCounts().getCriteriaRequiredMet() + getCounts().getCriteriaUpToDateMet()) * 100.0) + / (getCounts().getCriteriaRequired() + getCounts().getCriteriaUpToDateRequired())), 100)); + getPercents().setCqmDomains(0); + getPercents().setCqmsInpatient(0); + getPercents().setCqmsAmbulatory(0); + } + private boolean areAttributesUpToDate() { //Get the date on which to determine which attributes are required. //Ex: on 9/1/2026 see whichever standards and code sets were required From 9e926bed4a42e1cce5f842a234fbafda4f7523d8 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Tue, 5 May 2026 10:32:24 -0400 Subject: [PATCH 10/14] fix: Calculate %s correctly; doesn't count if criteria not up-to-date [#OCD-4928] --- .../chpl/certificationId/Validator2026.java | 123 +++++++++--------- 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index 977d90b90b..ca8b6387d6 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,7 +25,6 @@ import gov.healthit.chpl.util.Util; public class Validator2026 extends Validator { - private static final int NUM_CRITERIA_REQUIRING_UPDATES = 4; //a5, b1, g9, g10 private CertificationIdYearCalculator certIdYearCalculator; private StandardDAO standardDao; private CodeSetDAO codeSetDao; @@ -32,8 +32,7 @@ public class Validator2026 extends Validator { private List requiredCriteria; private List cpoeCriteriaOr; private List dpCriteriaOr; - private List criteriaToCheckForUpdates; - private CertificationCriterion a5, b1, g9, g10; + private List upToDateCriteriaFound; public Validator2026(CertificationCriterionService certificationCriterionService, CertificationIdYearCalculator certIdYearCalculator, @@ -43,20 +42,16 @@ public Validator2026(CertificationCriterionService certificationCriterionService this.standardDao = standardDao; this.codeSetDao = codeSetDao; - a5 = certificationCriterionService.get(Criteria2015.A_5); - b1 = certificationCriterionService.get(Criteria2015.B_1_CURES); - g9 = certificationCriterionService.get(Criteria2015.G_9_CURES); - g10 = certificationCriterionService.get(Criteria2015.G_10); - criteriaToCheckForUpdates = Stream.of(a5, b1, g9, g10).toList(); - - requiredCriteria = Stream.of(a5, + upToDateCriteriaFound = new ArrayList(); + requiredCriteria = Stream.of(certificationCriterionService.get(Criteria2015.A_5), certificationCriterionService.get(Criteria2015.A_14), - b1, + certificationCriterionService.get(Criteria2015.B_1_CURES), certificationCriterionService.get(Criteria2015.B_11), certificationCriterionService.get(Criteria2015.C_1), certificationCriterionService.get(Criteria2015.G_7), - g9, - g10).collect(Collectors.toCollection(ArrayList::new)); + certificationCriterionService.get(Criteria2015.G_9_CURES), + certificationCriterionService.get(Criteria2015.G_10)) + .collect(Collectors.toCollection(ArrayList::new)); cpoeCriteriaOr = Stream.of(certificationCriterionService.get(Criteria2015.A_1), certificationCriterionService.get(Criteria2015.A_2), @@ -67,98 +62,101 @@ public Validator2026(CertificationCriterionService certificationCriterionService certificationCriterionService.get(Criteria2015.H_2)) .collect(Collectors.toList()); - this.getCounts().setCriteriaRequired(requiredCriteria.size()); - this.getCounts().setCriteriaRequiredMet(0); - this.getCounts().setCriteriaUpToDateRequired(NUM_CRITERIA_REQUIRING_UPDATES); - this.getCounts().setCriteriaUpToDateMet(0); this.getCounts().setCriteriaCpoeRequired(1); this.getCounts().setCriteriaCpoeRequiredMet(0); this.getCounts().setCriteriaDpRequired(1); this.getCounts().setCriteriaDpRequiredMet(0); - this.getCounts().setCriteriaDsRequired(0); - this.getCounts().setCriteriaDsRequiredMet(0); - this.getCounts().setCqmsInpatientRequired(0); - this.getCounts().setCqmsInpatientRequiredMet(0); - this.getCounts().setCqmsAmbulatoryRequired(0); - this.getCounts().setCqmsAmbulatoryRequiredMet(0); - this.getCounts().setCqmsAmbulatoryCoreRequired(0); - this.getCounts().setCqmsAmbulatoryCoreRequiredMet(0); - this.getCounts().setDomainsRequired(0); - this.getCounts().setDomainsRequiredMet(0); + this.getCounts().setCriteriaRequired(requiredCriteria.size() + this.getCounts().getCriteriaDpRequired() + this.getCounts().getCriteriaCpoeRequired()); + this.getCounts().setCriteriaRequiredMet(0); } public boolean onValidate() { //written this way so both validation checks get called and we have all missing info at once - boolean isCriteriaValid = isCriteriaValid(); boolean areAttributesUpToDate = areAttributesUpToDate(); + boolean isCriteriaValid = isCriteriaValid(); return isCriteriaValid && areAttributesUpToDate; } protected boolean isCriteriaValid() { - this.getCounts().setCriteriaRequired(requiredCriteria.size()); boolean requiredCriteriaValid = true; + int requiredCriteriaMet = 0; for (CertificationCriterion crit : requiredCriteria) { Optional metRequiredCriterion = getCriteriaMet().stream() .filter(criterionMet -> criterionMet.getId().equals(crit.getId())) .findAny(); - if (metRequiredCriterion.isPresent()) { - this.getCounts().setCriteriaRequiredMet(this.getCounts().getCriteriaRequiredMet() + 1); - } else { + if (metRequiredCriterion.isPresent() && upToDateCriteriaFound.contains(crit)) { + requiredCriteriaMet++; + } else if (!metRequiredCriterion.isPresent()) { this.getMissingAnd().add(Util.formatCriteriaNumber(crit)); requiredCriteriaValid = false; + } else { + requiredCriteriaValid = false; } } boolean cpoeValid = isCPOEValid(); boolean dpValid = isDPValid(); - this.getCounts().setCriteriaRequired( - this.getCounts().getCriteriaRequired() - + this.getCounts().getCriteriaCpoeRequired() - + this.getCounts().getCriteriaDsRequired() - + this.getCounts().getCriteriaDpRequired()); this.getCounts().setCriteriaRequiredMet( - this.getCounts().getCriteriaRequiredMet() + requiredCriteriaMet + this.getCounts().getCriteriaCpoeRequiredMet() - + this.getCounts().getCriteriaDsRequiredMet() + this.getCounts().getCriteriaDpRequiredMet()); return (requiredCriteriaValid && cpoeValid && dpValid); } protected boolean isCPOEValid() { - for (CertificationCriterion crit : cpoeCriteriaOr) { - if (criteriaMetContainsCriterion(crit)) { - this.getCounts().setCriteriaCpoeRequiredMet(1); - return true; - } + //they could have both "or" criteria, only 1 has to be up-to-date + List metCpoeCriteriaOr = cpoeCriteriaOr.stream() + .filter(orCriterion -> criteriaMetContainsCriterion(orCriterion)) + .collect(Collectors.toList()); + + Optional upToDateMetCpoeCriterionOr = metCpoeCriteriaOr.stream() + .filter(metOrCriterion -> upToDateCriteriaFound.contains(metOrCriterion)) + .findAny(); + + if (!CollectionUtils.isEmpty(metCpoeCriteriaOr) && upToDateMetCpoeCriterionOr.isPresent()) { + this.getCounts().setCriteriaCpoeRequiredMet(1); + return true; + } else if (CollectionUtils.isEmpty(metCpoeCriteriaOr)) { + getMissingOr().add(cpoeCriteriaOr.stream() + .map(cpoeCrit -> Util.formatCriteriaNumber(cpoeCrit)) + .collect(Collectors.toCollection(ArrayList::new))); + return false; + } else { + return false; } - getMissingOr().add(cpoeCriteriaOr.stream() - .map(cpoeCrit -> Util.formatCriteriaNumber(cpoeCrit)) - .collect(Collectors.toCollection(ArrayList::new))); - return false; } protected boolean isDPValid() { - for (CertificationCriterion crit : dpCriteriaOr) { - if (criteriaMetContainsCriterion(crit)) { - this.getCounts().setCriteriaDpRequiredMet(1); - return true; - } + List metDpCriteriaOr = dpCriteriaOr.stream() + .filter(orCriterion -> criteriaMetContainsCriterion(orCriterion)) + .collect(Collectors.toList()); + + Optional upToDateMetDpCriterionOr = metDpCriteriaOr.stream() + .filter(metOrCriterion -> upToDateCriteriaFound.contains(metOrCriterion)) + .findAny(); + + if (!CollectionUtils.isEmpty(metDpCriteriaOr) && upToDateMetDpCriterionOr.isPresent()) { + this.getCounts().setCriteriaDpRequiredMet(1); + return true; + } else if (CollectionUtils.isEmpty(metDpCriteriaOr)) { + getMissingOr().add(dpCriteriaOr.stream() + .map(dpCrit -> Util.formatCriteriaNumber(dpCrit)) + .collect(Collectors.toCollection(ArrayList::new))); + return false; + } else { + return false; } - getMissingOr().add(dpCriteriaOr.stream() - .map(dpCrit -> Util.formatCriteriaNumber(dpCrit)) - .collect(Collectors.toCollection(ArrayList::new))); - return false; } @Override protected void calculatePercentages() { - getPercents().setCriteriaMet((getCounts().getCriteriaRequired() + getCounts().getCriteriaUpToDateRequired())== 0 + getPercents().setCriteriaMet(getCounts().getCriteriaRequired() == 0 ? 0 - : Math.min((int) Math.floor(((getCounts().getCriteriaRequiredMet() + getCounts().getCriteriaUpToDateMet()) * 100.0) - / (getCounts().getCriteriaRequired() + getCounts().getCriteriaUpToDateRequired())), 100)); + : Math.min((int) Math.floor((getCounts().getCriteriaRequiredMet() * 100.0) + / getCounts().getCriteriaRequired()), 100)); getPercents().setCqmDomains(0); getPercents().setCqmsInpatient(0); getPercents().setCqmsAmbulatory(0); @@ -171,6 +169,10 @@ private boolean areAttributesUpToDate() { //have those attributes today. LocalDate dayToCalculateRequiredAttributes = certIdYearCalculator.getCmsIdStartDayOfCurrentYear(); + List criteriaToCheckForUpdates = Stream.of(requiredCriteria, cpoeCriteriaOr, dpCriteriaOr) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + criteriaToCheckForUpdates.stream() .forEach(criterion -> { //any one cert result for the criterion being checked must be fully up-to-date @@ -189,6 +191,7 @@ private boolean areAttributesUpToDate() { getMissingUpToDate().add(Util.formatCriteriaNumber(criterion)); } else { this.getCounts().setCriteriaUpToDateMet(this.getCounts().getCriteriaUpToDateMet() + 1); + upToDateCriteriaFound.add(criterion); } } }); From fe57e51d7a408dfe67641feeac959f5203b04a62 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Tue, 5 May 2026 14:40:03 -0400 Subject: [PATCH 11/14] refactor: Add some logging [#OCD-4928] --- .../chpl/web/controller/CertificationIdController.java | 6 ++++++ .../chpl/certificationId/CertificationIdSearchService.java | 2 ++ .../service/CertificationResultService.java | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java index cb06135fd2..ac6b2bb61f 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java @@ -33,11 +33,14 @@ import gov.healthit.chpl.exception.InvalidArgumentsException; import gov.healthit.chpl.exception.ValidationException; import gov.healthit.chpl.util.SwaggerSecurityRequirement; +import gov.healthit.chpl.util.Util; import gov.healthit.chpl.web.controller.annotation.DeprecatedApi; 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 = "certification-ids", description = "All certification ID operations.") @RestController public class CertificationIdController { @@ -118,6 +121,9 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @RequestParam(required = true) List listingIds) throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException, Exception { List certificationYears = certIdYearCalculator.getValidCertIdYearsToday(); + LOGGER.info("Searching for certification ID for listings: " + + Util.joinListGrammatically(listingIds.stream().map(id -> id.toString()).toList(), "and") + + " for year(s) " + Util.joinListGrammatically(certificationYears, "and")); return certificationYears.stream() .map(rethrowFunction(certYear -> certIdSearchService.findCertificationByListingIds(listingIds, certYear, false))) .collect(Collectors.toList()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java index bce48fde9e..39f795f409 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java @@ -152,6 +152,7 @@ public CertificationIdResults findCertificationByListingIds(List listingId } private List getAllListingDetails(List listingIds) { + LOGGER.info("Getting all listing details for cert id search"); ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); List>> futures = new ArrayList>>(); listingIds.stream() @@ -170,6 +171,7 @@ private List getAllListingDetails(List list } catch (Exception ex) { LOGGER.error("Executor service did not properly close", ex); } + LOGGER.info("Got all listing details for cert id search"); return listings; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java index c78c5f88ee..a725f11f4f 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java @@ -74,7 +74,7 @@ private void populateTestTasks(CertificationResultDetailsDTO certResult, Certifi if (newTestTask.matches(currTestTask)) { alreadyExists = true; if (!currTestTask.getCriteria().add(criteria)) { - LOGGER.warn("Cannot add criteria " + criteria.getNumber() + " to test task " + currTestTask.getId() + " for listing " + searchDetails.getId() + " because it already exists."); + LOGGER.debug("Cannot add criteria " + criteria.getNumber() + " to test task " + currTestTask.getId() + " for listing " + searchDetails.getId() + " because it already exists."); } } } @@ -82,7 +82,7 @@ private void populateTestTasks(CertificationResultDetailsDTO certResult, Certifi newTestTask.getCriteria().add(criteria); searchDetails.getSed().getTestTasks().add(newTestTask); } else { - LOGGER.warn("Not adding test task " + newTestTask.getId() + " to the listing " + searchDetails.getId() + " because one with the same data has already been found."); + LOGGER.debug("Not adding test task " + newTestTask.getId() + " to the listing " + searchDetails.getId() + " because one with the same data has already been found."); } } } From ebb13db98343b7c169d2f6de838e03d5e81b5641 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Tue, 5 May 2026 14:42:53 -0400 Subject: [PATCH 12/14] fix: Limit thread pool size to 2 for cert id search [#OCD-4928] --- .../chpl/certificationId/CertificationIdSearchService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java index 39f795f409..ebcfcbbe67 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java @@ -28,6 +28,7 @@ @Service @Log4j2 public class CertificationIdSearchService { + private static final int THREAD_POOL_SIZE = 2; private CertificationIdManager certificationIdManager; private CertifiedProductDetailsManager cpdManager; private CertificationIdYearCalculator certIdYearCalculator; @@ -153,7 +154,7 @@ public CertificationIdResults findCertificationByListingIds(List listingId private List getAllListingDetails(List listingIds) { LOGGER.info("Getting all listing details for cert id search"); - ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); + ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); List>> futures = new ArrayList>>(); listingIds.stream() .forEach(listingId -> futures.add(CompletableFuture From d6ab3655ffc98390a3ba1e1b9e3cd48f54e69470 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Thu, 14 May 2026 11:28:00 -0400 Subject: [PATCH 13/14] refactor: Move upToDate calculation to listing details cert results [#OCD-4928] --- .../controller/CertificationIdController.java | 4 - .../CertificationIdSearchService.java | 2 - .../chpl/certificationId/Validator2026.java | 76 ++------ .../certificationId/ValidatorFactory.java | 14 +- .../service/CertificationResultService.java | 26 ++- .../CertificationResultUpToDateService.java | 169 ++++++++++++++++++ .../chpl/domain/CertificationResult.java | 2 + 7 files changed, 203 insertions(+), 90 deletions(-) create mode 100644 chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultUpToDateService.java diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java index ac6b2bb61f..cf316c7ac0 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java @@ -33,7 +33,6 @@ import gov.healthit.chpl.exception.InvalidArgumentsException; import gov.healthit.chpl.exception.ValidationException; import gov.healthit.chpl.util.SwaggerSecurityRequirement; -import gov.healthit.chpl.util.Util; import gov.healthit.chpl.web.controller.annotation.DeprecatedApi; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -121,9 +120,6 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @RequestParam(required = true) List listingIds) throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException, Exception { List certificationYears = certIdYearCalculator.getValidCertIdYearsToday(); - LOGGER.info("Searching for certification ID for listings: " - + Util.joinListGrammatically(listingIds.stream().map(id -> id.toString()).toList(), "and") - + " for year(s) " + Util.joinListGrammatically(certificationYears, "and")); return certificationYears.stream() .map(rethrowFunction(certYear -> certIdSearchService.findCertificationByListingIds(listingIds, certYear, false))) .collect(Collectors.toList()); diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java index ebcfcbbe67..b9b11342ec 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/CertificationIdSearchService.java @@ -153,7 +153,6 @@ public CertificationIdResults findCertificationByListingIds(List listingId } private List getAllListingDetails(List listingIds) { - LOGGER.info("Getting all listing details for cert id search"); ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); List>> futures = new ArrayList>>(); listingIds.stream() @@ -172,7 +171,6 @@ private List getAllListingDetails(List list } catch (Exception ex) { LOGGER.error("Executor service did not properly close", ex); } - LOGGER.info("Got all listing details for cert id search"); return listings; } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java index ca8b6387d6..a47565a5a9 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/Validator2026.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -13,21 +12,15 @@ import org.apache.commons.lang3.BooleanUtils; import gov.healthit.chpl.certificationCriteria.CertificationCriterion; -import gov.healthit.chpl.codeset.CodeSet; -import gov.healthit.chpl.codeset.CodeSetDAO; +import gov.healthit.chpl.certifiedproduct.service.CertificationResultUpToDateService; import gov.healthit.chpl.domain.CertificationResult; import gov.healthit.chpl.service.CertificationCriterionService; import gov.healthit.chpl.service.CertificationCriterionService.Criteria2015; -import gov.healthit.chpl.standard.Standard; -import gov.healthit.chpl.standard.StandardCriteriaMap; -import gov.healthit.chpl.standard.StandardDAO; -import gov.healthit.chpl.util.DateUtil; import gov.healthit.chpl.util.Util; public class Validator2026 extends Validator { private CertificationIdYearCalculator certIdYearCalculator; - private StandardDAO standardDao; - private CodeSetDAO codeSetDao; + private CertificationResultUpToDateService certResultUpToDateService; private List requiredCriteria; private List cpoeCriteriaOr; @@ -36,11 +29,9 @@ public class Validator2026 extends Validator { public Validator2026(CertificationCriterionService certificationCriterionService, CertificationIdYearCalculator certIdYearCalculator, - StandardDAO standardDao, - CodeSetDAO codeSetDao) { + CertificationResultUpToDateService certResultUpToDateService) { this.certIdYearCalculator = certIdYearCalculator; - this.standardDao = standardDao; - this.codeSetDao = codeSetDao; + this.certResultUpToDateService = certResultUpToDateService; upToDateCriteriaFound = new ArrayList(); requiredCriteria = Stream.of(certificationCriterionService.get(Criteria2015.A_5), @@ -183,7 +174,10 @@ private boolean areAttributesUpToDate() { if (!CollectionUtils.isEmpty(certResultsForCriterion)) { CertificationResult fullyUpToDateCertResultForCriterion = certResultsForCriterion.stream() - .filter(certResult -> isCertResultFullyUpToDate(certResult, dayToCalculateRequiredAttributes)) + //the below filter is saying "if the cert result is up-to-date with today's attributes then we don't need to check if it is up-to-date with past attributes" + //but if it's NOT currently up-to-date, the check runs to see if it is at least up-to-date with the attributes + //that were required at the beginning of the CMS ID creation window (9/1) because then it counts towards CMS ID + .filter(certResult -> certResult.isUpToDate() || areAttributesFullyUpToDateAsOf(certResult, dayToCalculateRequiredAttributes)) .findAny() .orElse(null); @@ -198,58 +192,8 @@ private boolean areAttributesUpToDate() { return CollectionUtils.isEmpty(getMissingUpToDate()); } - private boolean isCertResultFullyUpToDate(CertificationResult certResult, LocalDate asOfDate) { - return areStandardsUpToDate(certResult, asOfDate) - && areCodeSetsUpToDate(certResult, asOfDate); - } - - private boolean areStandardsUpToDate(CertificationResult certResult, LocalDate asOfDate) { - List stdCriteriaMaps = standardDao.getAllStandardCriteriaMap(); - stdCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(certResult.getCriterion().getId())); - List requiredStandardsForCriterionAsOfDate = stdCriteriaMaps.stream() - .map(map -> map.getStandard()) - .filter(std -> std.getStartDay().isBefore(asOfDate) - && (std.getRequiredDay() != null && DateUtil.isOnOrBefore(std.getRequiredDay(), asOfDate)) - && (std.getEndDay() == null || std.getEndDay().isAfter(asOfDate))) - .collect(Collectors.toList()); - if (!CollectionUtils.isEmpty(requiredStandardsForCriterionAsOfDate)) { - return !requiredStandardsForCriterionAsOfDate.stream() - .filter(requiredStandard -> !isStandardOnCertResult(requiredStandard, certResult)) - .findAny() - .isPresent(); - } - return true; - } - - private boolean isStandardOnCertResult(Standard standard, CertificationResult certResult) { - return certResult.getStandards().stream() - .filter(certResultStd -> certResultStd.getStandard().getId().equals(standard.getId())) - .findAny() - .isPresent(); - } - - private boolean areCodeSetsUpToDate(CertificationResult certResult, LocalDate asOfDate) { - List codeSetsRequiredForCriterion = null; - Map> codeSetMaps = codeSetDao.getCodeSetCriteriaMaps(); - if (codeSetMaps.containsKey(certResult.getCriterion().getId())) { - codeSetsRequiredForCriterion = codeSetMaps.get(certResult.getCriterion().getId()).stream() - .filter(codeSet -> DateUtil.isOnOrBefore(codeSet.getRequiredDay(), asOfDate)) - .collect(Collectors.toList()); - if (!CollectionUtils.isEmpty(codeSetsRequiredForCriterion)) { - return !codeSetsRequiredForCriterion.stream() - .filter(requiredCodeSet -> !isCodeSetOnCertResult(requiredCodeSet, certResult)) - .findAny() - .isPresent(); - } - } - return true; - } - - private Boolean isCodeSetOnCertResult(CodeSet codeSet, CertificationResult certResult) { - return certResult.getCodeSets().stream() - .filter(cs -> cs.getCodeSet().getId().equals(codeSet.getId())) - .findAny() - .isPresent(); + private boolean areAttributesFullyUpToDateAsOf(CertificationResult certResult, LocalDate asOfDate) { + return certResultUpToDateService.isUpToDate(certResult, asOfDate); } protected boolean isCqmsValid() { diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java index b5a47c4626..d0f76fc4a8 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certificationId/ValidatorFactory.java @@ -8,13 +8,12 @@ import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; -import gov.healthit.chpl.codeset.CodeSetDAO; +import gov.healthit.chpl.certifiedproduct.service.CertificationResultUpToDateService; import gov.healthit.chpl.email.ChplHtmlEmailBuilder; import gov.healthit.chpl.exception.InvalidArgumentsException; import gov.healthit.chpl.notifier.ChplTeamNotifier; import gov.healthit.chpl.notifier.InvalidCertificationIdYearMessage; import gov.healthit.chpl.service.CertificationCriterionService; -import gov.healthit.chpl.standard.StandardDAO; import gov.healthit.chpl.util.ErrorMessageUtil; import lombok.extern.log4j.Log4j2; @@ -25,8 +24,7 @@ public class ValidatorFactory { private Map> certIdYearToValidatorClassMap; private CertificationCriterionService certificationCriterionService; private CertificationIdYearCalculator certIdYearCalculator; - private StandardDAO standardDao; - private CodeSetDAO codeSetDao; + private CertificationResultUpToDateService certResultUpToDateService; private ChplTeamNotifier chplTeamNotifier; private ChplHtmlEmailBuilder chplHtmlEmailBuilder; private Environment env; @@ -35,16 +33,14 @@ public class ValidatorFactory { @Autowired public ValidatorFactory(CertificationCriterionService certificationCriterionService, CertificationIdYearCalculator certIdYearCalculator, - StandardDAO standardDao, - CodeSetDAO codeSetDao, + CertificationResultUpToDateService certResultUpToDateService, ChplTeamNotifier chplTeamNotifier, ChplHtmlEmailBuilder chplHtmlEmailBuilder, Environment env, ErrorMessageUtil msgUtil) { this.certificationCriterionService = certificationCriterionService; this.certIdYearCalculator = certIdYearCalculator; - this.standardDao = standardDao; - this.codeSetDao = codeSetDao; + this.certResultUpToDateService = certResultUpToDateService; this.chplTeamNotifier = chplTeamNotifier; this.chplHtmlEmailBuilder = chplHtmlEmailBuilder; this.env = env; @@ -102,7 +98,7 @@ private Validator getNewInstance(Class validatorClazz) { } else if (validatorClazz.equals(Validator2026.class)) { try { result = (Validator) validatorClazz.getDeclaredConstructors()[0].newInstance( - certificationCriterionService, certIdYearCalculator, standardDao, codeSetDao); + certificationCriterionService, certIdYearCalculator, certResultUpToDateService); } catch (Exception ex) { LOGGER.error("Could not instantiate validator " + validatorClazz, ex); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java index a725f11f4f..f3616b78a4 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultService.java @@ -26,16 +26,20 @@ public class CertificationResultService { private CertificationResultRules certRules; private CertificationResultManager certResultManager; - private CertificationResultDetailsDAO certificationResultDetailsDAO; + private CertificationResultDetailsDAO certificationResultDetailsDao; + private CertificationResultUpToDateService certResultUpToDateService; private CertificationResultComparator certResultComparator; @Autowired - public CertificationResultService(CertificationResultRules certRules, CertificationResultManager certResultManager, - CertificationResultDetailsDAO certificationResultDetailsDAO, + public CertificationResultService(CertificationResultRules certRules, + CertificationResultManager certResultManager, + CertificationResultDetailsDAO certificationResultDetailsDao, + CertificationResultUpToDateService certResultUpToDateService, CertificationResultComparator certResultComparator) { this.certRules = certRules; this.certResultManager = certResultManager; - this.certificationResultDetailsDAO = certificationResultDetailsDAO; + this.certificationResultDetailsDao = certificationResultDetailsDao; + this.certResultUpToDateService = certResultUpToDateService; this.certResultComparator = certResultComparator; } @@ -48,19 +52,19 @@ public List getCertificationResults(CertifiedProductSearchD private List getCertificationResultDetailsDTOs(Long id) { List certificationResultDetailsDTOs = null; - certificationResultDetailsDTOs = certificationResultDetailsDAO.getAllCertResultsForListing(id); + certificationResultDetailsDTOs = certificationResultDetailsDao.getAllCertResultsForListing(id); return certificationResultDetailsDTOs; } private CertificationResult getCertificationResult(CertificationResultDetailsDTO certResult, - CertifiedProductSearchDetails searchDetails) { + CertifiedProductSearchDetails listing) { CertificationResult result = new CertificationResult(certResult, certRules); CertificationCriterion criteria = result.getCriterion(); - populateSed(certResult, searchDetails, result, criteria); - populateTestTasks(certResult, searchDetails, criteria); - + populateSed(certResult, listing, result, criteria); + populateTestTasks(certResult, listing, criteria); + populateUpToDate(listing, result); return result; } @@ -109,4 +113,8 @@ private void populateSed(CertificationResultDetailsDTO certResult, CertifiedProd result.setSed(null); } } + + private void populateUpToDate(CertifiedProductSearchDetails listing, CertificationResult certResult) { + certResult.setUpToDate(certResultUpToDateService.isUpToDate(certResult)); + } } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultUpToDateService.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultUpToDateService.java new file mode 100644 index 0000000000..6942fa0222 --- /dev/null +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/certifiedproduct/service/CertificationResultUpToDateService.java @@ -0,0 +1,169 @@ +package gov.healthit.chpl.certifiedproduct.service; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import gov.healthit.chpl.codeset.CodeSet; +import gov.healthit.chpl.codeset.CodeSetDAO; +import gov.healthit.chpl.domain.CertificationResult; +import gov.healthit.chpl.functionalitytested.FunctionalityTested; +import gov.healthit.chpl.functionalitytested.FunctionalityTestedDAO; +import gov.healthit.chpl.standard.Standard; +import gov.healthit.chpl.standard.StandardCriteriaMap; +import gov.healthit.chpl.standard.StandardDAO; +import gov.healthit.chpl.util.DateUtil; +import jakarta.transaction.Transactional; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +@Component +public class CertificationResultUpToDateService { + private StandardDAO standardDao; + private CodeSetDAO codeSetDao; + private FunctionalityTestedDAO functionatlityTestedDao; + + @Autowired + public CertificationResultUpToDateService(StandardDAO standardDao, + CodeSetDAO codeSetDao, + FunctionalityTestedDAO functionatlityTestedDao) { + this.standardDao = standardDao; + this.codeSetDao = codeSetDao; + this.functionatlityTestedDao = functionatlityTestedDao; + } + + @Transactional + public boolean isUpToDate(CertificationResult certResult, LocalDate asOfDate) { + List stdCriteriaMaps = standardDao.getAllStandardCriteriaMap(); + stdCriteriaMaps.removeIf(map -> !map.getCriterion().getId().equals(certResult.getCriterion().getId())); + List standardsForCriterion = stdCriteriaMaps.stream() + .map(map -> map.getStandard()) + .collect(Collectors.toList()); + + return areNonGroupedStandardsUpToDate(standardsForCriterion, certResult, asOfDate) + && areGroupedStandardsUpToDate(standardsForCriterion, certResult, asOfDate) + && areCodeSetsUpToDate(certResult, asOfDate) + && isFunctionalityTestedUpToDate(certResult, asOfDate); + } + + @Transactional + public boolean isUpToDate(CertificationResult certResult) { + return isUpToDate(certResult, LocalDate.now()); + } + + private boolean areNonGroupedStandardsUpToDate(List standardsForCriterion, CertificationResult certResult, LocalDate asOfDate) { + List baselineStandardsUpToDate = standardsForCriterion.stream() + .filter(std -> StringUtils.isEmpty(std.getGroupName())) + .map(std -> areStandardsRequiredAndPresent(Stream.of(std).toList(), certResult, asOfDate)) + .map(bool -> Boolean.valueOf(bool)) + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(baselineStandardsUpToDate)) { + return !baselineStandardsUpToDate.stream() + .filter(groupUpToDate -> !groupUpToDate) + .findAny() + .isPresent(); + } + return true; + } + + private boolean areGroupedStandardsUpToDate(List standardsForCriterion, CertificationResult certResult, LocalDate asOfDate) { + Map> standardGroupsForCriterion = standardsForCriterion.stream() + .filter(std -> !StringUtils.isEmpty(std.getGroupName())) + .collect(Collectors.groupingBy(std -> std.getGroupName())); + + List groupsUpToDate = standardGroupsForCriterion.keySet().stream() + .map(groupName -> standardGroupsForCriterion.get(groupName)) + .map(stdList -> areStandardsRequiredAndPresent(stdList, certResult, asOfDate)) + .map(bool -> Boolean.valueOf(bool)) + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(groupsUpToDate)) { + return !groupsUpToDate.stream() + .filter(groupUpToDate -> !groupUpToDate) + .findAny() + .isPresent(); + } + return true; + } + + private boolean areStandardsRequiredAndPresent(List standards, CertificationResult certResult, LocalDate asOfDate) { + List requiredStandards = standards.stream() + .filter(std -> DateUtil.isOnOrBefore(std.getStartDay(), asOfDate) + && (std.getRequiredDay() != null && std.getRequiredDay().isBefore(asOfDate)) + && (std.getEndDay() == null || std.getEndDay().isAfter(asOfDate))) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(requiredStandards)) { + return !requiredStandards.stream() + .filter(requiredStandard -> !isStandardOnCertResult(requiredStandard, certResult)) + .findAny() + .isPresent(); + } + return true; + } + + private boolean isStandardOnCertResult(Standard standard, CertificationResult certResult) { + return certResult.getStandards().stream() + .filter(certResultStd -> certResultStd.getStandard().getId().equals(standard.getId())) + .findAny() + .isPresent(); + } + + private boolean areCodeSetsUpToDate(CertificationResult certResult, LocalDate asOfDate) { + List codeSetsRequiredForCriterion = null; + Map> codeSetMaps = codeSetDao.getCodeSetCriteriaMaps(); + if (codeSetMaps.containsKey(certResult.getCriterion().getId())) { + codeSetsRequiredForCriterion = codeSetMaps.get(certResult.getCriterion().getId()).stream() + .filter(codeSet -> DateUtil.isOnOrBefore(codeSet.getStartDay(), asOfDate) + && codeSet.getRequiredDay().isBefore(asOfDate)) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(codeSetsRequiredForCriterion)) { + return !codeSetsRequiredForCriterion.stream() + .filter(requiredCodeSet -> !isCodeSetOnCertResult(requiredCodeSet, certResult)) + .findAny() + .isPresent(); + } + } + return true; + } + + private Boolean isCodeSetOnCertResult(CodeSet codeSet, CertificationResult certResult) { + return certResult.getCodeSets().stream() + .filter(cs -> cs.getCodeSet().getId().equals(codeSet.getId())) + .findAny() + .isPresent(); + } + + private boolean isFunctionalityTestedUpToDate(CertificationResult certResult, LocalDate asOfDate) { + List functionalityTestedRequiredForCriterion = null; + Map> ftMaps = functionatlityTestedDao.getFunctionalitiesTestedCriteriaMaps(); + if (ftMaps.containsKey(certResult.getCriterion().getId())) { + functionalityTestedRequiredForCriterion = ftMaps.get(certResult.getCriterion().getId()).stream() + .filter(ft -> DateUtil.isOnOrBefore(ft.getStartDay(), asOfDate) + && (ft.getRequiredDay() != null && ft.getRequiredDay().isBefore(asOfDate)) + && (ft.getEndDay() == null || ft.getEndDay().isAfter(asOfDate))) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(functionalityTestedRequiredForCriterion)) { + return !functionalityTestedRequiredForCriterion.stream() + .filter(requiredFt -> !isFunctionalityTestedOnCertResult(requiredFt, certResult)) + .findAny() + .isPresent(); + } + } + return true; + } + + private Boolean isFunctionalityTestedOnCertResult(FunctionalityTested functionalityTested, CertificationResult certResult) { + return certResult.getFunctionalitiesTested().stream() + .filter(ft -> ft.getFunctionalityTested().getId().equals(functionalityTested.getId())) + .findAny() + .isPresent(); + } +} diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertificationResult.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertificationResult.java index f99644de11..8dcaef17bd 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertificationResult.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/domain/CertificationResult.java @@ -179,6 +179,8 @@ public class CertificationResult implements Serializable { @JsonProperty(access = Access.WRITE_ONLY) private String number; + private boolean isUpToDate; + @JsonIgnore private CertificationResultStandardComparator standardComparator; @JsonIgnore From 4feee561cfb63cf9e8895efb2ba06b8bce8138c8 Mon Sep 17 00:00:00 2001 From: Katy Ekey Date: Tue, 26 May 2026 10:59:20 -0400 Subject: [PATCH 14/14] feat!: Indicate CMS ID resources unavailable depending on flag state [#OCD-5300] --- .../chpl/ApiExceptionControllerAdvice.java | 7 ++ .../controller/CertificationIdController.java | 76 +++++++++++++++---- .../java/gov/healthit/chpl/FeatureList.java | 1 + 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/ApiExceptionControllerAdvice.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/ApiExceptionControllerAdvice.java index 38dba45409..10221a599a 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/ApiExceptionControllerAdvice.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/ApiExceptionControllerAdvice.java @@ -45,6 +45,7 @@ import gov.healthit.chpl.user.cognito.authentication.CognitoAuthenticationChallengeException; import gov.healthit.chpl.user.cognito.authentication.CognitoPasswordResetRequiredException; import gov.healthit.chpl.util.ErrorMessageUtil; +import jakarta.servlet.UnavailableException; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.log4j.Log4j2; @@ -64,6 +65,12 @@ public ResponseEntity exception(NotImplementedException e) { return new ResponseEntity(new ErrorResponse(e.getMessage()), HttpStatus.NOT_IMPLEMENTED); } + @ExceptionHandler(UnavailableException.class) + public ResponseEntity exception(UnavailableException e) { + //TODO - what is the right response code? Could also do like Unauthorized or "Not Allowed" + return new ResponseEntity(new ErrorResponse(e.getMessage()), HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS); + } + @ExceptionHandler(AccessDeniedException.class) public ResponseEntity exception(AccessDeniedException e) { return new ResponseEntity( diff --git a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java index cf316c7ac0..7321b4598f 100644 --- a/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java +++ b/chpl/chpl-api/src/main/java/gov/healthit/chpl/web/controller/CertificationIdController.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; +import org.ff4j.FF4j; import org.quartz.SchedulerException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -18,6 +19,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import gov.healthit.chpl.FeatureList; import gov.healthit.chpl.certificationId.CertificationIdCreateBody; import gov.healthit.chpl.certificationId.CertificationIdLookupResults; import gov.healthit.chpl.certificationId.CertificationIdManager; @@ -37,6 +39,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.UnavailableException; import lombok.extern.log4j.Log4j2; @Log4j2 @@ -47,14 +50,17 @@ public class CertificationIdController { private CertificationIdSearchService certIdSearchService; private CertificationIdManager certificationIdManager; private CertificationIdYearCalculator certIdYearCalculator; + private FF4j ff4j; @Autowired public CertificationIdController(CertificationIdSearchService certIdSearchService, CertificationIdManager certificationIdManager, - CertificationIdYearCalculator certIdYearCalculator) { + CertificationIdYearCalculator certIdYearCalculator, + FF4j ff4j) { this.certIdSearchService = certIdSearchService; this.certificationIdManager = certificationIdManager; this.certIdYearCalculator = certIdYearCalculator; + this.ff4j = ff4j; } @Operation(summary = "Generate the CMS EHR Certification ID Report and email the results to the logged-in user.", @@ -69,7 +75,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @DeprecatedApi(friendlyUrl = "/certification_ids/report-request", httpMethod = "POST", message = "This endpoint is deprecated and will be removed. Please use certification-ids/report-request", removalDate = "2026-10-01") - public @ResponseBody ChplOneTimeTrigger triggerCmsIdReportDeprecated() throws SchedulerException, ValidationException { + public @ResponseBody ChplOneTimeTrigger triggerCmsIdReportDeprecated() + throws SchedulerException, ValidationException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } ChplOneTimeTrigger jobTrigger = certificationIdManager.triggerCmsIdReport(); return jobTrigger; } @@ -82,7 +92,10 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic }) @RequestMapping(value = "/certification-ids/report-request", method = RequestMethod.POST, produces = "application/json; charset=utf-8") - public @ResponseBody ChplOneTimeTrigger triggerCmsIdReport() throws SchedulerException, ValidationException { + public @ResponseBody ChplOneTimeTrigger triggerCmsIdReport() throws SchedulerException, ValidationException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } ChplOneTimeTrigger jobTrigger = certificationIdManager.triggerCmsIdReport(); return jobTrigger; } @@ -102,7 +115,12 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic message = "This endpoint is deprecated and will be removed. Please use certification-ids/search", removalDate = "2026-10-01") public @ResponseBody CertificationIdResults searchCertificationIdDeprecated( - @RequestParam(required = false) List ids) throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { + @RequestParam(required = false) List ids) + throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } + return certIdSearchService.findCertificationByListingIds(ids, null, false); } @@ -118,7 +136,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic }) public @ResponseBody List searchCertificationId( @RequestParam(required = true) List listingIds) throws InvalidArgumentsException, EntityRetrievalException, - CertificationIdException, Exception { + CertificationIdException, UnavailableException, Exception { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } + List certificationYears = certIdYearCalculator.getValidCertIdYearsToday(); return certificationYears.stream() .map(rethrowFunction(certYear -> certIdSearchService.findCertificationByListingIds(listingIds, certYear, false))) @@ -141,7 +163,10 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic message = "This endpoint is deprecated and will be removed. Please POST to /certification-ids", removalDate = "2026-10-01") public @ResponseBody CertificationIdResults createCertificationIdDeprecated(@RequestParam(required = true) List ids) - throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { + throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } return certIdSearchService.findCertificationByListingIds(ids, null, true); } @@ -157,7 +182,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic MediaType.APPLICATION_JSON_VALUE }) public @ResponseBody CertificationIdResults createCertificationId(@RequestBody CertificationIdCreateBody createBody) - throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException { + throws InvalidArgumentsException, EntityRetrievalException, CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } + return certIdSearchService.findCertificationByListingIds(createBody.getListingIds(), createBody.getYear(), true); } @@ -181,7 +210,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @RequestParam(required = false, defaultValue = "false") Boolean includeCriteria, @RequestParam(required = false, defaultValue = "false") Boolean includeCqms) throws InvalidArgumentsException, - EntityRetrievalException, CertificationIdException { + EntityRetrievalException, CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } + return certIdSearchService.findCertificationIdByCertificationId(certificationId, includeCriteria, includeCqms); } @@ -201,7 +234,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic @RequestParam(required = false, defaultValue = "false") Boolean includeCriteria, @RequestParam(required = false, defaultValue = "false") Boolean includeCqms) throws InvalidArgumentsException, - EntityRetrievalException, CertificationIdException { + EntityRetrievalException, CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } + return certIdSearchService.findCertificationIdByCertificationId(certificationId, includeCriteria, includeCqms); } @@ -220,7 +257,10 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic removalDate = "2026-10-01") public @ResponseBody CertificationIdVerifyResults verifyCertificationIdsDeprecated( @RequestBody final CertificationIdVerificationBodyDeprecated body) throws InvalidArgumentsException, - CertificationIdException { + CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } return this.verify(body.getIds()); } @@ -235,7 +275,11 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic }) public @ResponseBody CertificationIdVerifyResults verifyCertificationIds( @RequestBody CertificationIdVerificationBody body) throws InvalidArgumentsException, - CertificationIdException { + CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } + return this.verify(body.getCertificationIds()); } @@ -253,7 +297,10 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic removalDate = "2026-10-01") public @ResponseBody CertificationIdVerifyResults verifyCertificationIdsDeprecated( @RequestParam("ids") final List certificationIds) throws InvalidArgumentsException, - CertificationIdException { + CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } return this.verify(certificationIds); } @@ -267,7 +314,10 @@ public CertificationIdController(CertificationIdSearchService certIdSearchServic }) public @ResponseBody CertificationIdVerifyResults verifyCertificationIds( @RequestParam("certificationIds") List certificationIds) throws InvalidArgumentsException, - CertificationIdException { + CertificationIdException, UnavailableException { + if (ff4j.check(FeatureList.CMS_DISABLED)) { + throw new UnavailableException("This endpoint is not currently available."); + } return this.verify(certificationIds); } diff --git a/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java b/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java index 4263c6b8de..0482168218 100644 --- a/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java +++ b/chpl/chpl-service/src/main/java/gov/healthit/chpl/FeatureList.java @@ -4,6 +4,7 @@ public final class FeatureList { private FeatureList() { } + public static final String CMS_DISABLED = "cms-disabled"; public static final String DEMOGRAPHIC_CHANGE_REQUEST = "demographic-change-request"; public static final String INSIGHTS_DISPLAY = "insights-display"; public static final String SERVICE_BASE_URL_LIST_CHANGE_REQUEST = "sbul-change-request";