Skip to content
Open
  •  
  •  
  •  
96 changes: 96 additions & 0 deletions analyze_failing_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""
Analyze remaining failing tests to understand expected vs actual populations.
"""

import json
from pathlib import Path

MEASUREREPORT_DIR = Path("cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/QICore/input/tests/measurereport")

FAILING_TESTS = [
"699e12b2-26d4-43a8-add0-bcdd6629fe88",
"eb7ec114-0c95-4e73-98ad-772a8197ffff",
"ac67c1e3-d0df-4745-bc85-d4ec0a18e8f3",
"81d2ade5-fa91-428c-b39f-3f0b8b7b2c16",
"f2a7180d-acd8-4394-acdd-8959d861ef65",
"ee5db0d0-8af1-4521-a060-aed5b026e194",
"2e186c68-d7f4-4b2e-9f8a-e73c79905e7e",
]

def get_test_description(report):
"""Extract the test case description from the MeasureReport."""
for ext in report.get("extension", []):
if ext.get("url") == "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-testCaseDescription":
return ext.get("valueMarkdown", "")
return ""

def get_subject_from_report(report):
"""Get subject patient ID from MeasureReport."""
for contained in report.get("contained", []):
if contained.get("resourceType") == "Parameters":
for param in contained.get("parameter", []):
if param.get("name") == "subject":
return param.get("valueString", "")
return None

def get_populations(group):
"""Extract population counts from a group."""
populations = {}
for pop in group.get("population", []):
code = None
for coding in pop.get("code", {}).get("coding", []):
if coding.get("system") == "http://terminology.hl7.org/CodeSystem/measure-population":
code = coding.get("code")
break
if code:
populations[code] = pop.get("count", 0)
return populations

def get_evaluated_resources(report):
"""Get list of evaluated resources from MeasureReport."""
resources = []
for eval_res in report.get("evaluatedResource", []):
ref = eval_res.get("reference", "")
resources.append(ref)
return resources

def get_measure_id(report):
"""Extract measure ID from measure URL."""
measure_url = report.get("measure", "")
return measure_url.split("/")[-1] if "/" in measure_url else measure_url

def main():
for report_id in FAILING_TESTS:
report_file = MEASUREREPORT_DIR / f"{report_id}.json"
if not report_file.exists():
print(f"Report not found: {report_id}")
continue

with open(report_file, 'r') as f:
report = json.load(f)

measure_id = get_measure_id(report)
patient_id = get_subject_from_report(report)
description = get_test_description(report)
resources = get_evaluated_resources(report)

groups = report.get("group", [])
if groups:
populations = get_populations(groups[0])

print(f"\n{'='*70}")
print(f"Report: {report_id}")
print(f"Measure: {measure_id}")
print(f"Patient: {patient_id}")
print(f"Description: {description}")
print(f"\nExpected Populations:")
for pop, count in populations.items():
print(f" {pop}: {count}")
print(f"\nEvaluated Resources ({len(resources)}):")
for res in resources:
print(f" {res}")


if __name__ == "__main__":
main()
60 changes: 60 additions & 0 deletions check_all_refs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
Check for Condition and Procedure resources missing subject/patient references.
"""

import json
from pathlib import Path

TESTS_DIR = Path("cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/QICore/input/tests")

def check_resources_for_patient_ref(resource_type):
"""Check all resources of a given type for missing patient/subject references."""
res_dir = TESTS_DIR / resource_type.lower()
if not res_dir.exists():
print(f"Directory not found: {res_dir}")
return []

missing = []
for res_file in sorted(res_dir.glob("*.json")):
with open(res_file, 'r') as f:
try:
resource = json.load(f)
except json.JSONDecodeError:
print(f" Error parsing: {res_file}")
continue

# Check for subject or patient reference
has_subject = "subject" in resource and resource["subject"].get("reference")
has_patient = "patient" in resource and resource["patient"].get("reference")

if not has_subject and not has_patient:
missing.append(res_file.stem)

return missing


def main():
resource_types = ["Condition", "Procedure", "Observation", "MedicationRequest",
"MedicationAdministration", "ServiceRequest", "DiagnosticReport"]

total_missing = 0
for res_type in resource_types:
missing = check_resources_for_patient_ref(res_type)
if missing:
print(f"\n{res_type}: {len(missing)} resources MISSING subject/patient reference")
for res_id in missing:
print(f" {res_type}/{res_id}")
total_missing += len(missing)
else:
res_dir = TESTS_DIR / res_type.lower()
if res_dir.exists():
count = len(list(res_dir.glob("*.json")))
print(f"{res_type}: OK ({count} resources all have references)")

print(f"\n{'='*60}")
print(f"TOTAL: {total_missing} resources missing subject/patient reference")


if __name__ == "__main__":
main()
157 changes: 157 additions & 0 deletions check_missing_refs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""
Check for resources missing subject/patient references in failing tests.
"""

import json
from pathlib import Path

MEASUREREPORT_DIR = Path("cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/QICore/input/tests/measurereport")
TESTS_DIR = Path("cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/QICore/input/tests")

# Failing test MeasureReport IDs
FAILING_TESTS = [
"2a364e88-7272-444d-a264-e931bba5391e",
"a6399df7-7d9a-45da-a64b-97f695646ce6",
"4138e2f8-7c51-4cbf-82b7-9983b775991a",
"0fb98a8a-a7ac-49a3-a1bd-e042373dc1c6",
"b0513b24-8789-4c07-a13d-322d9defbeb8",
"699e12b2-26d4-43a8-add0-bcdd6629fe88",
"a821b7fb-7913-45e4-82e2-cf232818d643",
"eb7ec114-0c95-4e73-98ad-772a8197ffff",
"ac67c1e3-d0df-4745-bc85-d4ec0a18e8f3",
"1f48c160-8aba-4e86-bd5d-c5c4bdef1afd",
"9eeadd82-4599-4b8b-95a5-f1d59697b451",
"af8c832f-f1ad-407a-9751-575339d08367",
"e66fcfe4-57f5-4259-bb05-540d4f6a864c",
"81d2ade5-fa91-428c-b39f-3f0b8b7b2c16",
"f2a7180d-acd8-4394-acdd-8959d861ef65",
"ee5db0d0-8af1-4521-a060-aed5b026e194",
"6244d8f6-995c-4a0e-9d86-9c3abfc3fcb7",
"a754b13e-2ef7-4c69-a205-f9af9a9a089e",
"1e896d30-3808-482a-b8a3-51198a58d4a6",
"2e186c68-d7f4-4b2e-9f8a-e73c79905e7e",
"6c210a7d-98b1-4d37-a268-45d14a7e7b1d",
]

def get_subject_from_report(report_id):
"""Get subject patient ID from MeasureReport."""
report_file = MEASUREREPORT_DIR / f"{report_id}.json"
if not report_file.exists():
return None

with open(report_file, 'r') as f:
report = json.load(f)

for contained in report.get("contained", []):
if contained.get("resourceType") == "Parameters":
for param in contained.get("parameter", []):
if param.get("name") == "subject":
return param.get("valueString", "")
return None


def get_evaluated_resources(report_id):
"""Get list of evaluated resources from MeasureReport."""
report_file = MEASUREREPORT_DIR / f"{report_id}.json"
if not report_file.exists():
return []

with open(report_file, 'r') as f:
report = json.load(f)

resources = []
for eval_res in report.get("evaluatedResource", []):
ref = eval_res.get("reference", "")
if "/" in ref:
res_type, res_id = ref.split("/", 1)
resources.append((res_type, res_id))
return resources


def check_resource_for_patient_ref(res_type, res_id, expected_patient_id):
"""Check if a resource has the correct patient/subject reference."""
# Find the resource file
res_type_lower = res_type.lower()
res_file = TESTS_DIR / res_type_lower / f"{res_id}.json"

if not res_file.exists():
return None, f"File not found: {res_file}"

with open(res_file, 'r') as f:
resource = json.load(f)

# Check for subject or patient reference
subject_ref = None
patient_ref = None

if "subject" in resource:
subject_ref = resource["subject"].get("reference", "")
if "patient" in resource:
patient_ref = resource["patient"].get("reference", "")

expected_ref = f"Patient/{expected_patient_id}"

if subject_ref:
if subject_ref == expected_ref:
return True, f"subject: {subject_ref}"
else:
return False, f"subject mismatch: has '{subject_ref}', expected '{expected_ref}'"
elif patient_ref:
if patient_ref == expected_ref:
return True, f"patient: {patient_ref}"
else:
return False, f"patient mismatch: has '{patient_ref}', expected '{expected_ref}'"
else:
return False, "MISSING subject/patient reference"


def main():
missing_refs = []
mismatched_refs = []

for report_id in FAILING_TESTS:
patient_id = get_subject_from_report(report_id)
if not patient_id:
print(f"Could not find patient for report {report_id}")
continue

print(f"\n=== Report: {report_id} ===")
print(f"Patient: {patient_id}")

resources = get_evaluated_resources(report_id)
for res_type, res_id in resources:
if res_type == "Patient":
continue # Skip patient resources

has_ref, msg = check_resource_for_patient_ref(res_type, res_id, patient_id)

if has_ref is None:
print(f" {res_type}/{res_id}: {msg}")
elif has_ref:
pass # print(f" {res_type}/{res_id}: OK - {msg}")
else:
print(f" {res_type}/{res_id}: {msg}")
if "MISSING" in msg:
missing_refs.append((report_id, patient_id, res_type, res_id))
else:
mismatched_refs.append((report_id, patient_id, res_type, res_id, msg))

print("\n" + "="*60)
print(f"SUMMARY: {len(missing_refs)} resources MISSING subject/patient reference")
print(f"SUMMARY: {len(mismatched_refs)} resources with MISMATCHED references")
print("="*60)

if missing_refs:
print("\nMISSING REFERENCES:")
for report_id, patient_id, res_type, res_id in missing_refs:
print(f" {res_type}/{res_id} (patient: {patient_id})")

if mismatched_refs:
print("\nMISMATCHED REFERENCES:")
for report_id, patient_id, res_type, res_id, msg in mismatched_refs:
print(f" {res_type}/{res_id} - {msg}")


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ private Double calculateGroupScore(String measureUrl, GroupDef groupDef, Measure

final QuantityDef quantityDef = scoreContinuousVariable(measureObsPop);

// We want to record the aggregate result for later computation for continuous variable reports
// We want to record the aggregate result for later computation for continuous variable report
measureObsPop.setAggregationResult(quantityDef);

return quantityDef != null ? quantityDef.value() : null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.opencds.cqf.fhir.cr.measure.MeasureStratifierType;
import org.opencds.cqf.fhir.cr.measure.common.ContinuousVariableObservationAggregateMethod;
import org.opencds.cqf.fhir.cr.measure.common.GroupDef;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType;
import org.opencds.cqf.fhir.cr.measure.common.PopulationDef;
import org.opencds.cqf.fhir.cr.measure.common.StratifierDef;
import org.opencds.cqf.fhir.cr.measure.common.StratumDef;
Expand Down Expand Up @@ -109,6 +110,21 @@ public static String getStratumDefText(StratifierDef stratifierDef, StratumDef s
return stratumText;
}

public static boolean doesReportPopulationTypeMatch(
MeasureReportGroupPopulationComponent groupPopulation, MeasurePopulationType populationType) {
return populationType
.toCode()
.equals(groupPopulation.getCode().getCodingFirstRep().getCode());
}

public static boolean doesStratumPopulationTypeMatch(
StratifierGroupPopulationComponent stratumPopulation, MeasurePopulationType populationType) {

return populationType
.toCode()
.equals(stratumPopulation.getCode().getCodingFirstRep().getCode());
}

/**
* Check if a MeasureReport stratum matches a StratumDef by comparing text representations.
*
Expand Down
Loading
Loading