Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,29 @@ private MeasureSeedSpec measureSeedSpecFor(String measureName) {
"urn:workwell:vs:ldl-labs",
false
);
case "Breast Cancer Screening" -> new MeasureSeedSpec(
"cms125",
"cms125-eligible",
"urn:workwell:vs:cms125-eligible",
"cms125-excluded",
"urn:workwell:vs:cms125-excluded",
"mammogram",
"urn:workwell:vs:cms125-mammogram",
false,
820
);
case "Diabetes: Hemoglobin A1c (HbA1c) Poor Control (> 9%)" -> new MeasureSeedSpec(
"cms122",
"cms122-diabetes",
"urn:workwell:vs:cms122-diabetes",
"cms122-excluded",
"urn:workwell:vs:cms122-excluded",
"hba1c-obs",
"urn:workwell:vs:cms122-hba1c",
false,
365,
true
);
default -> null;
};
}
Expand All @@ -576,14 +599,36 @@ private SeededInput input(
SeededOutcome targetOutcome
) {
int window = spec.complianceWindowDays();
boolean hasWaiver = targetOutcome == SeededOutcome.EXCLUDED;

// Observation-based measures (e.g. CMS122 HbA1c) derive compliance from a
// numeric value, not a recency window. Use observationValue; daysSinceLastExam
// is set to a recent value so the observation has a timestamp.
if (spec.observationBased()) {
Float observationValue = switch (targetOutcome) {
case COMPLIANT -> 7.5f;
case OVERDUE -> 10.5f;
case MISSING_DATA, DUE_SOON -> null;
case EXCLUDED -> 7.5f;
};
Integer daysAgo = observationValue != null ? 30 : null;
SyntheticFhirBundleBuilder.ExamConfig config = new SyntheticFhirBundleBuilder.ExamConfig(
daysAgo, hasWaiver, true,
spec.enrollmentCode(), spec.enrollmentVs(),
spec.waiverCode(), spec.waiverVs(),
spec.examCode(), spec.examVs(), false,
observationValue
);
return new SeededInput(employee, config, targetOutcome.name());
}

Integer daysSinceLastExam = switch (targetOutcome) {
case COMPLIANT -> window / 3;
case DUE_SOON -> window - 10;
case OVERDUE -> window + 60;
case MISSING_DATA -> null;
case EXCLUDED -> window + 150;
};
boolean hasWaiver = targetOutcome == SeededOutcome.EXCLUDED;
SyntheticFhirBundleBuilder.ExamConfig config = new SyntheticFhirBundleBuilder.ExamConfig(
daysSinceLastExam,
hasWaiver,
Expand Down Expand Up @@ -629,13 +674,21 @@ private record MeasureSeedSpec(
String examCode,
String examVs,
boolean useImmunization,
int complianceWindowDays
int complianceWindowDays,
boolean observationBased
) {
MeasureSeedSpec(String rateKey, String enrollmentCode, String enrollmentVs,
String waiverCode, String waiverVs, String examCode, String examVs,
boolean useImmunization) {
this(rateKey, enrollmentCode, enrollmentVs, waiverCode, waiverVs,
examCode, examVs, useImmunization, 365);
examCode, examVs, useImmunization, 365, false);
}

MeasureSeedSpec(String rateKey, String enrollmentCode, String enrollmentVs,
String waiverCode, String waiverVs, String examCode, String examVs,
boolean useImmunization, int complianceWindowDays) {
this(rateKey, enrollmentCode, enrollmentVs, waiverCode, waiverVs,
examCode, examVs, useImmunization, complianceWindowDays, false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Immunization;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Procedure;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;

public class SyntheticFhirBundleBuilder {
Expand Down Expand Up @@ -49,7 +51,31 @@ public Bundle buildBundle(
));
}

if (config.daysSinceLastExam() != null) {
if (config.observationValue() != null) {
Observation observation = new Observation();
observation.setId(employee.externalId() + "-observation");
observation.setStatus(Observation.ObservationStatus.FINAL);
observation.setSubject(new Reference("Patient/" + employee.externalId()));
observation.getCode().addCoding()
.setSystem(config.examValueSet())
.setCode(config.examCode())
.setDisplay(config.examCode());
if (config.daysSinceLastExam() != null) {
String effectiveDateTime = evaluationDate
.minusDays(config.daysSinceLastExam())
.atStartOfDay()
.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
observation.setEffective(new org.hl7.fhir.r4.model.DateTimeType(effectiveDateTime));
}
observation.setValue(new Quantity()
.setValue(java.math.BigDecimal.valueOf(config.observationValue()))
.setUnit("%")
.setSystem("http://unitsofmeasure.org")
.setCode("%"));
bundle.addEntry().setResource(observation);
}

if (config.daysSinceLastExam() != null && config.observationValue() == null) {
String performedDateTime = evaluationDate
.minusDays(config.daysSinceLastExam())
.atStartOfDay()
Expand Down Expand Up @@ -106,7 +132,17 @@ public record ExamConfig(
String waiverValueSet,
String examCode,
String examValueSet,
boolean useImmunization
boolean useImmunization,
Float observationValue
) {
public ExamConfig(Integer daysSinceLastExam, boolean hasWaiver, boolean programEnrolled,
String programEnrollmentCode, String programEnrollmentValueSet,
String waiverCode, String waiverValueSet,
String examCode, String examValueSet, boolean useImmunization) {
this(daysSinceLastExam, hasWaiver, programEnrolled,
programEnrollmentCode, programEnrollmentValueSet,
waiverCode, waiverValueSet, examCode, examValueSet,
useImmunization, null);
}
}
}
123 changes: 123 additions & 0 deletions backend/src/main/java/com/workwell/measure/MeasureService.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ private void ensureInstanceSeeds() {
ensureDiabetesHbA1cSeed();
ensureObesityBmiSeed();
ensureCholesterolLdlSeed();
ensureCms125Seed();
ensureCms122Seed();
ensureCmsEcqmCatalogSeed();
}
}
Expand Down Expand Up @@ -1151,6 +1153,127 @@ private void ensureCmsEcqmCatalogSeed() {
}
}

private void ensureCms125Seed() {
// CMS125 — Breast Cancer Screening (mammogram within 27 months / 820 days).
// We seed this as Active with full CQL BEFORE ensureCmsEcqmCatalogSeed() runs.
// Because that method skips existing v1.0 versions, the Active record persists.
// policy_ref must start with "CMS125v" so the catalog seed can match by prefix.
UUID measureId;
try {
measureId = jdbcTemplate.queryForObject(
"SELECT id FROM measures WHERE policy_ref LIKE 'CMS125v%'",
UUID.class
);
} catch (EmptyResultDataAccessException ex) {
measureId = UUID.randomUUID();
jdbcTemplate.update(
"INSERT INTO measures (id, name, policy_ref, owner, tags, created_at, updated_at) VALUES (?, ?, ?, ?, ?::text[], NOW(), NOW())",
measureId,
"Breast Cancer Screening",
"CMS125v14",
"WorkWell Studio",
"{ecqm,cms,cancer-screening,preventive}"
);
}

Integer existing = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM measure_versions WHERE measure_id = ? AND version = ?",
Integer.class, measureId, "v1.0"
);
if (existing != null && existing > 0) {
jdbcTemplate.update(
"UPDATE measure_versions SET cql_text = ?, compile_status = 'COMPILED', compile_result = ?::jsonb WHERE measure_id = ? AND version = ?",
loadSeedCql("cms125.cql"),
toJson(Map.of("status", "COMPILED", "warnings", List.of(), "errors", List.of())),
measureId, "v1.0"
);
return;
}

Map<String, Object> spec = new LinkedHashMap<>();
spec.put("description", "Breast Cancer Screening (CMS125v14 / MIPS 112): women 50–74 who had a mammogram in the measurement period or 26 months prior.");
spec.put("eligibilityCriteria", Map.of(
"roleFilter", "All",
"siteFilter", "All Sites",
"programEnrollmentText", "Breast Cancer Screening Eligible"
));
spec.put("exclusions", List.of(Map.of("label", "Clinical Exclusion", "criteriaText", "Bilateral mastectomy or history of breast cancer — documented exclusion on file")));
spec.put("complianceWindow", "27 months (820 days)");
spec.put("requiredDataElements", List.of("Last mammogram date", "Eligible population flag", "Exclusion status"));
spec.put("testFixtures", List.of());
spec.put("cmsEcqmId", "CMS125v14");
spec.put("mipsQualityId", "112");

jdbcTemplate.update(
"INSERT INTO measure_versions (id, measure_id, version, status, spec_json, cql_text, compile_status, compile_result, change_summary, approved_by, activated_at, created_at) VALUES (?, ?, ?, ?, ?::jsonb, ?, ?, ?::jsonb, ?, ?, NOW(), NOW())",
UUID.randomUUID(), measureId, "v1.0", "Active",
toJson(spec), loadSeedCql("cms125.cql"), "COMPILED",
toJson(Map.of("status", "COMPILED", "warnings", List.of(), "errors", List.of())),
"CMS125v14 Breast Cancer Screening — Active seed with full CQL (27-month mammogram window)",
"system"
);
}

private void ensureCms122Seed() {
// CMS122 — Diabetes: HbA1c Poor Control (> 9%).
// Numeric Observation-based measure. An HbA1c value > 9% → OVERDUE (poor control).
// policy_ref must start with "CMS122v" so the catalog seed can match by prefix.
UUID measureId;
try {
measureId = jdbcTemplate.queryForObject(
"SELECT id FROM measures WHERE policy_ref LIKE 'CMS122v%'",
UUID.class
);
} catch (EmptyResultDataAccessException ex) {
measureId = UUID.randomUUID();
jdbcTemplate.update(
"INSERT INTO measures (id, name, policy_ref, owner, tags, created_at, updated_at) VALUES (?, ?, ?, ?, ?::text[], NOW(), NOW())",
measureId,
"Diabetes: Hemoglobin A1c (HbA1c) Poor Control (> 9%)",
"CMS122v14",
"WorkWell Studio",
"{ecqm,cms,diabetes}"
);
}

Integer existing = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM measure_versions WHERE measure_id = ? AND version = ?",
Integer.class, measureId, "v1.0"
);
if (existing != null && existing > 0) {
jdbcTemplate.update(
"UPDATE measure_versions SET cql_text = ?, compile_status = 'COMPILED', compile_result = ?::jsonb WHERE measure_id = ? AND version = ?",
loadSeedCql("cms122.cql"),
toJson(Map.of("status", "COMPILED", "warnings", List.of(), "errors", List.of())),
measureId, "v1.0"
);
return;
}

Map<String, Object> spec = new LinkedHashMap<>();
spec.put("description", "Diabetes: HbA1c Poor Control (CMS122v14 / MIPS 1): patients 18–75 with diabetes whose most recent HbA1c result is > 9% (poor control). OVERDUE indicates intervention is needed.");
spec.put("eligibilityCriteria", Map.of(
"roleFilter", "All",
"siteFilter", "All Sites",
"programEnrollmentText", "Diabetes Diagnosis"
));
spec.put("exclusions", List.of(Map.of("label", "Clinical Exclusion", "criteriaText", "Hospice care, advanced illness, or other clinical exclusion")));
spec.put("complianceWindow", "Annual — based on HbA1c value, not recency");
spec.put("requiredDataElements", List.of("Most recent HbA1c lab value", "Diabetes diagnosis", "Exclusion status"));
spec.put("testFixtures", List.of());
spec.put("cmsEcqmId", "CMS122v14");
spec.put("mipsQualityId", "1");

jdbcTemplate.update(
"INSERT INTO measure_versions (id, measure_id, version, status, spec_json, cql_text, compile_status, compile_result, change_summary, approved_by, activated_at, created_at) VALUES (?, ?, ?, ?, ?::jsonb, ?, ?, ?::jsonb, ?, ?, NOW(), NOW())",
UUID.randomUUID(), measureId, "v1.0", "Active",
toJson(spec), loadSeedCql("cms122.cql"), "COMPILED",
toJson(Map.of("status", "COMPILED", "warnings", List.of(), "errors", List.of())),
"CMS122v14 Diabetes HbA1c Poor Control — Active seed with numeric Observation CQL",
"system"
);
}

private void ensureHypertensionSeed() {
UUID measureId;
try {
Expand Down
67 changes: 67 additions & 0 deletions backend/src/main/resources/measures/cms122.cql
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
library DiabetesHbA1cPoorControlCQL version '1.0.0'
using FHIR version '4.0.1'
include FHIRHelpers version '4.0.1' called FHIRHelpers

valueset "HbA1c Lab Tests": 'urn:workwell:vs:cms122-hba1c'
valueset "Diabetes Diagnosis": 'urn:workwell:vs:cms122-diabetes'
valueset "CMS122 Exclusions": 'urn:workwell:vs:cms122-excluded'

parameter "Measurement Period" Interval<DateTime>
context Patient

define "Has Diabetes Diagnosis":
exists([Condition] C
where exists(C.code.coding x where x.system = 'urn:workwell:vs:cms122-diabetes' and x.code = 'cms122-diabetes'))

define "Has Exclusion":
exists([Condition] C
where exists(C.code.coding x where x.system = 'urn:workwell:vs:cms122-excluded' and x.code = 'cms122-excluded'))

define "Most Recent HbA1c Observation":
Last(
[Observation] O
where exists(O.code.coding C where C.system = 'urn:workwell:vs:cms122-hba1c' and C.code = 'hba1c-obs')
sort by (effective as FHIR.dateTime)
)

define "Most Recent HbA1c Value":
"Most Recent HbA1c Observation".value as FHIR.Quantity

define "HbA1c Poor Control":
"Most Recent HbA1c Value".value > 9

define "Has Recent HbA1c Result":
"Most Recent HbA1c Observation" is not null

define "Compliant":
"Has Diabetes Diagnosis"
and not "Has Exclusion"
and "Has Recent HbA1c Result"
and not "HbA1c Poor Control"

define "Due Soon":
false

define "Overdue":
"Has Diabetes Diagnosis"
and not "Has Exclusion"
and "Has Recent HbA1c Result"
and "HbA1c Poor Control"

define "Missing Data":
"Has Diabetes Diagnosis"
and not "Has Exclusion"
and not "Has Recent HbA1c Result"

define "Excluded":
"Has Exclusion"

define "Initial Population":
"Has Diabetes Diagnosis" or "Has Exclusion"

define "Outcome Status":
if "Excluded" then 'EXCLUDED'
else if "Missing Data" then 'MISSING_DATA'
else if "Overdue" then 'OVERDUE'
else if "Compliant" then 'COMPLIANT'
else 'MISSING_DATA'
Loading
Loading