From 20d6a779b5f70017485955c079d2fd846efd6424 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Thu, 5 Sep 2024 17:03:28 -0700 Subject: [PATCH 01/10] Adding FileSystemInvocation class that allows evaluating a measure using a data from the locally running file system --- .../measureeval/FileSystemInvocation.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java diff --git a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java new file mode 100644 index 000000000..0e35a55af --- /dev/null +++ b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java @@ -0,0 +1,109 @@ +package com.lantanagroup.link.measureeval; + +import ca.uhn.fhir.context.FhirContext; +import com.lantanagroup.link.measureeval.services.MeasureEvaluator; +import org.apache.commons.io.FileUtils; +import org.hl7.fhir.r4.model.*; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used to invoke measure evaluation using arbitrary artifacts on the file system for ease of testing and debugging new measures. + * Format: `java -jar measureeval.jar -cp com.lantanagroup.link.measureeval.FileSystemInvocation "" "" "" ""` + * Example: `java -jar measureeval.jar -cp com.lantanagroup.link.measureeval.FileSystemInvocation "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31"` + * Careful when specifying file paths to use forward-slash instead of windows' backslash, as that will cause java to interpret the backslash as an escape character and not as a path separator, leading to potentially incorrect parameter interpretation. + * The `start` and `end` parameters must be in a valid DateTime format from the FHIR specification. + * The `measure-bundle-path` can be a path to a single measure bundle file or a directory containing each of the resources needed for the measure. + * The `patient-bundle-path` must be a path to a single (JSON or XML) Bundle file or a directory of files containing the patient data to be used in the evaluation. + * The response from the operation is the MeasureReport resource being printed to the console in JSON format. + */ +public class FileSystemInvocation { + private static final FhirContext fhirContext = FhirContext.forR4Cached(); + + private static Bundle getBundle(String measureBundlePath) throws IOException { + System.out.println("Loading measure bundle from: " + measureBundlePath); + + File measureBundleFile = new File(measureBundlePath); + + if (!measureBundleFile.exists()) { + throw new IllegalArgumentException("Measure bundle file does not exist: " + measureBundlePath); + } + + if (measureBundleFile.isFile()) { + String measureBundleContent = FileUtils.readFileToString(measureBundleFile); + if (measureBundlePath.toLowerCase().endsWith(".json")) { + return (Bundle) fhirContext.newJsonParser().parseResource(measureBundleContent); + } else if (measureBundlePath.toLowerCase().endsWith(".xml")) { + return (Bundle) fhirContext.newXmlParser().parseResource(measureBundleContent); + } else { + throw new IllegalArgumentException("Unsupported measure bundle file format: " + measureBundlePath); + } + } else { + // Parse and load each file in the directory + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.COLLECTION); + + File[] files = measureBundleFile.listFiles(); + + if (files != null) { + List loaded = new ArrayList<>(); + + for (File file : files) { + String filePath = file.getAbsolutePath(); + if (file.isFile() && (filePath.endsWith(".json") || filePath.endsWith(".xml"))) { + String fileName = file.getName().substring(0, file.getName().lastIndexOf('.')); + + if (loaded.contains(fileName)) { + System.out.println("Skipping duplicate file: " + filePath); + } + + Resource resource; + + if (filePath.endsWith(".json")) { + resource = (Resource) fhirContext.newJsonParser().parseResource(FileUtils.readFileToString(file)); + } else { + resource = (Resource) fhirContext.newXmlParser().parseResource(FileUtils.readFileToString(file)); + } + + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(resource)); + loaded.add(fileName); + } else { + System.out.println("Skipping file: " + filePath); + } + } + } + + System.out.println("Loaded " + bundle.getEntry().size() + " resources from directory: " + measureBundlePath); + + return bundle; + } + } + + public static void main(String[] args) { + String measureBundlePath = args[0]; + String patientBundlePath = args[1]; + String start = args[2]; + String end = args[3]; + + try { + Bundle measureBundle = getBundle(measureBundlePath); + Bundle patientBundle = getBundle(patientBundlePath); + MeasureEvaluator evaluator = MeasureEvaluator.compile(fhirContext, measureBundle); + Patient patient = patientBundle.getEntry().stream() + .filter(e -> e.getResource() instanceof Patient) + .map(e -> (Patient) e.getResource()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Patient resource not found in bundle")); + var report = evaluator.evaluate(new DateTimeType(start), new DateTimeType(end), + new StringType("Patient/" + patient.getIdElement().getIdPart()), patientBundle); + String json = fhirContext.newJsonParser().encodeResourceToString(report); + System.out.println(json); + } catch (Exception e) { + System.err.println("Error occurred while evaluating measure: " + e.getMessage()); + e.printStackTrace(); + } + } +} From 089060db5da2d12514bf32b4908eeb2a64dab5e2 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 17 Sep 2024 15:10:16 -0700 Subject: [PATCH 02/10] Making the command more flexible to allow a path of files for both measure and test data Extending the documentation into a README.md on the root of the measureeval project --- Java/measureeval/README.md | 38 ++++ .../measureeval/FileSystemInvocation.java | 173 ++++++++++++------ .../services/MeasureEvaluator.java | 14 +- .../src/main/resources/logback.xml | 23 +++ 4 files changed, 195 insertions(+), 53 deletions(-) create mode 100644 Java/measureeval/README.md create mode 100644 Java/measureeval/src/main/resources/logback.xml diff --git a/Java/measureeval/README.md b/Java/measureeval/README.md new file mode 100644 index 000000000..e470d1ed7 --- /dev/null +++ b/Java/measureeval/README.md @@ -0,0 +1,38 @@ +# measureeval + +## Building for use as CLI + +Perform the following from the `/Java` directory, which builds the measureeval JAR file _and_ the dependent shared +module: + +```bash +mvn clean install -pl measureeval -am +``` + +## Running the CLI + +To bypass the JAR manifest's main() class that runs it as a service, run the JAR with the following parameters: +`-Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher` + +### Parameters + +| Parameter | Description | +|---------------------|--------------------------------------------------------------------------------------| +| measure-bundle-path | The path to the measure bundle JSON file or a directory of resource JSON/XML files. | +| patient-bundle-path | The path to the patient bundle JSON file or a directory of resources JSON/XML files. | +| start | The start date for the measurement period in FHIR Date or DateTime format. | +| end | The end date for the measurement period in FHIR Date or DateTime format. | + +### Format/Example + +Format: + +```bash +java -cp measureeval.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "" "" "" "" +``` + +Example: + +```bash +java -cp measureeval.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" +``` \ No newline at end of file diff --git a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java index 0e35a55af..5328ace37 100644 --- a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java +++ b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java @@ -4,6 +4,8 @@ import com.lantanagroup.link.measureeval.services.MeasureEvaluator; import org.apache.commons.io.FileUtils; import org.hl7.fhir.r4.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -12,8 +14,7 @@ /** * This class is used to invoke measure evaluation using arbitrary artifacts on the file system for ease of testing and debugging new measures. - * Format: `java -jar measureeval.jar -cp com.lantanagroup.link.measureeval.FileSystemInvocation "" "" "" ""` - * Example: `java -jar measureeval.jar -cp com.lantanagroup.link.measureeval.FileSystemInvocation "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31"` + * See the main measureeval project's README for more details about how to execute the JAR from the command line. * Careful when specifying file paths to use forward-slash instead of windows' backslash, as that will cause java to interpret the backslash as an escape character and not as a path separator, leading to potentially incorrect parameter interpretation. * The `start` and `end` parameters must be in a valid DateTime format from the FHIR specification. * The `measure-bundle-path` can be a path to a single measure bundle file or a directory containing each of the resources needed for the measure. @@ -22,67 +23,133 @@ */ public class FileSystemInvocation { private static final FhirContext fhirContext = FhirContext.forR4Cached(); + private static final Logger logger = LoggerFactory.getLogger(FileSystemInvocation.class); private static Bundle getBundle(String measureBundlePath) throws IOException { - System.out.println("Loading measure bundle from: " + measureBundlePath); + logger.info("Loading measure bundle from: {}", measureBundlePath); - File measureBundleFile = new File(measureBundlePath); + try { + File measureBundleFile = new File(measureBundlePath); - if (!measureBundleFile.exists()) { - throw new IllegalArgumentException("Measure bundle file does not exist: " + measureBundlePath); - } + if (!measureBundleFile.exists()) { + throw new IllegalArgumentException("Measure bundle file does not exist: " + measureBundlePath); + } - if (measureBundleFile.isFile()) { - String measureBundleContent = FileUtils.readFileToString(measureBundleFile); - if (measureBundlePath.toLowerCase().endsWith(".json")) { - return (Bundle) fhirContext.newJsonParser().parseResource(measureBundleContent); - } else if (measureBundlePath.toLowerCase().endsWith(".xml")) { - return (Bundle) fhirContext.newXmlParser().parseResource(measureBundleContent); + if (measureBundleFile.isFile()) { + String measureBundleContent = FileUtils.readFileToString(measureBundleFile); + if (measureBundlePath.toLowerCase().endsWith(".json")) { + return (Bundle) fhirContext.newJsonParser().parseResource(measureBundleContent); + } else if (measureBundlePath.toLowerCase().endsWith(".xml")) { + return (Bundle) fhirContext.newXmlParser().parseResource(measureBundleContent); + } else { + throw new IllegalArgumentException("Unsupported measure bundle file format: " + measureBundlePath); + } } else { - throw new IllegalArgumentException("Unsupported measure bundle file format: " + measureBundlePath); - } - } else { - // Parse and load each file in the directory - Bundle bundle = new Bundle(); - bundle.setType(Bundle.BundleType.COLLECTION); + // Parse and load each file in the directory + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.COLLECTION); - File[] files = measureBundleFile.listFiles(); + File[] files = measureBundleFile.listFiles(); - if (files != null) { - List loaded = new ArrayList<>(); + if (files != null) { + List loaded = new ArrayList<>(); - for (File file : files) { - String filePath = file.getAbsolutePath(); - if (file.isFile() && (filePath.endsWith(".json") || filePath.endsWith(".xml"))) { - String fileName = file.getName().substring(0, file.getName().lastIndexOf('.')); + for (File file : files) { + String filePath = file.getAbsolutePath(); + if (file.isFile() && (filePath.endsWith(".json") || filePath.endsWith(".xml"))) { + String fileName = file.getName().substring(0, file.getName().lastIndexOf('.')); - if (loaded.contains(fileName)) { - System.out.println("Skipping duplicate file: " + filePath); - } + if (loaded.contains(fileName)) { + logger.warn("Skipping duplicate file: {}", filePath); + } - Resource resource; + Resource resource; - if (filePath.endsWith(".json")) { - resource = (Resource) fhirContext.newJsonParser().parseResource(FileUtils.readFileToString(file)); + if (filePath.endsWith(".json")) { + resource = (Resource) fhirContext.newJsonParser().parseResource(FileUtils.readFileToString(file)); + } else { + resource = (Resource) fhirContext.newXmlParser().parseResource(FileUtils.readFileToString(file)); + } + + bundle.addEntry(new Bundle.BundleEntryComponent().setResource(resource)); + loaded.add(fileName); } else { - resource = (Resource) fhirContext.newXmlParser().parseResource(FileUtils.readFileToString(file)); + logger.warn("Skipping file: {}", filePath); } - - bundle.addEntry(new Bundle.BundleEntryComponent().setResource(resource)); - loaded.add(fileName); - } else { - System.out.println("Skipping file: " + filePath); } } + + logger.info("Loaded " + bundle.getEntry().size() + " resources from directory: " + measureBundlePath); + + return bundle; } + } catch (IOException ex) { + logger.error("Error occurred while loading measure bundle: {}", ex.getMessage()); + throw ex; + } + } - System.out.println("Loaded " + bundle.getEntry().size() + " resources from directory: " + measureBundlePath); - return bundle; + public static List getBundlesFromDirectoryAndSubDirectories(String directory) { + List bundles = new ArrayList<>(); + File[] files = new File(directory).listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + bundles.addAll(getBundlesFromDirectoryAndSubDirectories(file.getAbsolutePath())); + } else { + try { + if (!file.getAbsolutePath().toLowerCase().endsWith(".json") && !file.getAbsolutePath().toLowerCase().endsWith(".xml")) { + continue; + } + Bundle bundle = getBundle(file.getAbsolutePath()); + bundles.add(bundle); + } catch (IOException e) { + System.err.println("Error occurred while loading bundle: " + e.getMessage()); + continue; + } + } + } + } + return bundles; + } + + public static String getGroupPopulations(MeasureReport measureReport) { + StringBuilder populations = new StringBuilder(); + for (MeasureReport.MeasureReportGroupComponent group : measureReport.getGroup()) { + populations.append("Group: ").append(group.getId()).append("\n"); + for (MeasureReport.MeasureReportGroupPopulationComponent population : group.getPopulation()) { + populations.append("Population: ").append(population.getCode().getCodingFirstRep().getDisplay()).append(" - ").append(population.getCount()).append("\n"); + } } + return populations.toString(); + } + + private static Patient findPatient(Bundle bundle) { + return bundle.getEntry().stream() + .filter(e -> e.getResource() instanceof Patient) + .map(e -> (Patient) e.getResource()) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Patient resource not found in bundle")); + } + + private static void evaluatePatientBundle(String patientBundlePath, Bundle patientBundle, String start, String end, MeasureEvaluator evaluator) { + Patient patient = findPatient(patientBundle); + var report = evaluator.evaluate( + new DateTimeType(start), + new DateTimeType(end), + new StringType("Patient/" + patient.getIdElement().getIdPart()), + patientBundle); + String json = fhirContext.newJsonParser().encodeResourceToString(report); + logger.info("Summary of evaluate for patient/groups/populations:\nPatient: {}\n{}\nJSON: {}", patient.getIdElement().getIdPart(), getGroupPopulations(report), json); } public static void main(String[] args) { + if (args.length != 4) { + System.err.println("Invalid number of arguments. Expected 4 arguments: "); + System.exit(1); + } + String measureBundlePath = args[0]; String patientBundlePath = args[1]; String start = args[2]; @@ -90,17 +157,21 @@ public static void main(String[] args) { try { Bundle measureBundle = getBundle(measureBundlePath); - Bundle patientBundle = getBundle(patientBundlePath); - MeasureEvaluator evaluator = MeasureEvaluator.compile(fhirContext, measureBundle); - Patient patient = patientBundle.getEntry().stream() - .filter(e -> e.getResource() instanceof Patient) - .map(e -> (Patient) e.getResource()) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Patient resource not found in bundle")); - var report = evaluator.evaluate(new DateTimeType(start), new DateTimeType(end), - new StringType("Patient/" + patient.getIdElement().getIdPart()), patientBundle); - String json = fhirContext.newJsonParser().encodeResourceToString(report); - System.out.println(json); + MeasureEvaluator evaluator = MeasureEvaluator.compile(fhirContext, measureBundle, true); + + File patientBundleFile = new File(patientBundlePath); + + if (patientBundleFile.isDirectory()) { + List patientBundles = getBundlesFromDirectoryAndSubDirectories(patientBundlePath); + + for (Bundle patientBundle : patientBundles) { + logger.info("\n==================================================="); + evaluatePatientBundle(patientBundlePath, patientBundle, start, end, evaluator); + } + } else { + Bundle patientBundle = getBundle(patientBundlePath); + evaluatePatientBundle(patientBundlePath, patientBundle, start, end, evaluator); + } } catch (Exception e) { System.err.println("Error occurred while evaluating measure: " + e.getMessage()); e.printStackTrace(); diff --git a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/services/MeasureEvaluator.java b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/services/MeasureEvaluator.java index 2cc22fc29..167da7b24 100644 --- a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/services/MeasureEvaluator.java +++ b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/services/MeasureEvaluator.java @@ -29,7 +29,11 @@ public class MeasureEvaluator { private final Bundle bundle; private final Measure measure; - private MeasureEvaluator(FhirContext fhirContext, Bundle bundle) { + public MeasureEvaluator(FhirContext fhirContext, Bundle bundle) { + this(fhirContext, bundle, false); + } + + private MeasureEvaluator(FhirContext fhirContext, Bundle bundle, boolean isDebug) { this.fhirContext = fhirContext; options = MeasureEvaluationOptions.defaultOptions(); EvaluationSettings evaluationSettings = options.getEvaluationSettings(); @@ -42,6 +46,8 @@ private MeasureEvaluator(FhirContext fhirContext, Bundle bundle) { .setTerminologyParameterMode(RetrieveSettings.TERMINOLOGY_FILTER_MODE.FILTER_IN_MEMORY) .setSearchParameterMode(RetrieveSettings.SEARCH_FILTER_MODE.FILTER_IN_MEMORY) .setProfileMode(RetrieveSettings.PROFILE_MODE.DECLARED); + evaluationSettings.getCqlOptions().getCqlEngineOptions().setDebugLoggingEnabled(isDebug); + this.bundle = bundle; measure = bundle.getEntry().stream() .map(Bundle.BundleEntryComponent::getResource) @@ -52,7 +58,11 @@ private MeasureEvaluator(FhirContext fhirContext, Bundle bundle) { } public static MeasureEvaluator compile(FhirContext fhirContext, Bundle bundle) { - MeasureEvaluator instance = new MeasureEvaluator(fhirContext, bundle); + return compile(fhirContext, bundle, false); + } + + public static MeasureEvaluator compile(FhirContext fhirContext, Bundle bundle, boolean isDebug) { + MeasureEvaluator instance = new MeasureEvaluator(fhirContext, bundle, isDebug); instance.compile(); return instance; } diff --git a/Java/measureeval/src/main/resources/logback.xml b/Java/measureeval/src/main/resources/logback.xml new file mode 100644 index 000000000..0af8429e6 --- /dev/null +++ b/Java/measureeval/src/main/resources/logback.xml @@ -0,0 +1,23 @@ + + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + \ No newline at end of file From 63279ab8cddd2680c7e782570c807a5acca083b8 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 17 Sep 2024 15:13:03 -0700 Subject: [PATCH 03/10] Explicitly defining the main class in spring-boot-maven plugin --- Java/measureeval/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/Java/measureeval/pom.xml b/Java/measureeval/pom.xml index 36ab66e33..af0f5fa88 100644 --- a/Java/measureeval/pom.xml +++ b/Java/measureeval/pom.xml @@ -142,6 +142,7 @@ + com.lantanagroup.link.measureeval.MeasureEvalApplication org.projectlombok From 35bf6c25c72e17275cd20dbc25ca9b271b3d02f1 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 17 Sep 2024 15:13:59 -0700 Subject: [PATCH 04/10] Expliciting reading FileSystemInvocation as UTF-8 --- .../lantanagroup/link/measureeval/FileSystemInvocation.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java index 5328ace37..1c1c71b63 100644 --- a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java +++ b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java @@ -36,7 +36,7 @@ private static Bundle getBundle(String measureBundlePath) throws IOException { } if (measureBundleFile.isFile()) { - String measureBundleContent = FileUtils.readFileToString(measureBundleFile); + String measureBundleContent = FileUtils.readFileToString(measureBundleFile, "UTF-8"); if (measureBundlePath.toLowerCase().endsWith(".json")) { return (Bundle) fhirContext.newJsonParser().parseResource(measureBundleContent); } else if (measureBundlePath.toLowerCase().endsWith(".xml")) { @@ -66,9 +66,9 @@ private static Bundle getBundle(String measureBundlePath) throws IOException { Resource resource; if (filePath.endsWith(".json")) { - resource = (Resource) fhirContext.newJsonParser().parseResource(FileUtils.readFileToString(file)); + resource = (Resource) fhirContext.newJsonParser().parseResource(FileUtils.readFileToString(file, "UTF-8")); } else { - resource = (Resource) fhirContext.newXmlParser().parseResource(FileUtils.readFileToString(file)); + resource = (Resource) fhirContext.newXmlParser().parseResource(FileUtils.readFileToString(file, "UTF-8")); } bundle.addEntry(new Bundle.BundleEntryComponent().setResource(resource)); From fbab6936cc8334563c55bc8444d5423df7d8755d Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 17 Sep 2024 15:30:00 -0700 Subject: [PATCH 05/10] Updates based on feedback from Steven in PR --- Java/measureeval/README.md | 4 ++-- .../link/measureeval/FileSystemInvocation.java | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Java/measureeval/README.md b/Java/measureeval/README.md index e470d1ed7..543e07d4d 100644 --- a/Java/measureeval/README.md +++ b/Java/measureeval/README.md @@ -28,11 +28,11 @@ To bypass the JAR manifest's main() class that runs it as a service, run the JAR Format: ```bash -java -cp measureeval.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "" "" "" "" +java -cp measureeval-.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "" "" "" "" ``` Example: ```bash -java -cp measureeval.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" +java -cp measureeval-.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" ``` \ No newline at end of file diff --git a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java index 1c1c71b63..6499cc275 100644 --- a/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java +++ b/Java/measureeval/src/main/java/com/lantanagroup/link/measureeval/FileSystemInvocation.java @@ -2,7 +2,9 @@ import ca.uhn.fhir.context.FhirContext; import com.lantanagroup.link.measureeval.services.MeasureEvaluator; +import com.lantanagroup.link.measureeval.utils.StreamUtils; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.hl7.fhir.r4.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,6 +12,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; /** @@ -38,9 +41,9 @@ private static Bundle getBundle(String measureBundlePath) throws IOException { if (measureBundleFile.isFile()) { String measureBundleContent = FileUtils.readFileToString(measureBundleFile, "UTF-8"); if (measureBundlePath.toLowerCase().endsWith(".json")) { - return (Bundle) fhirContext.newJsonParser().parseResource(measureBundleContent); + return fhirContext.newJsonParser().parseResource(Bundle.class, measureBundleContent); } else if (measureBundlePath.toLowerCase().endsWith(".xml")) { - return (Bundle) fhirContext.newXmlParser().parseResource(measureBundleContent); + return fhirContext.newXmlParser().parseResource(Bundle.class, measureBundleContent); } else { throw new IllegalArgumentException("Unsupported measure bundle file format: " + measureBundlePath); } @@ -52,12 +55,14 @@ private static Bundle getBundle(String measureBundlePath) throws IOException { File[] files = measureBundleFile.listFiles(); if (files != null) { - List loaded = new ArrayList<>(); + HashSet loaded = new HashSet<>(); for (File file : files) { String filePath = file.getAbsolutePath(); - if (file.isFile() && (filePath.endsWith(".json") || filePath.endsWith(".xml"))) { - String fileName = file.getName().substring(0, file.getName().lastIndexOf('.')); + String fileExtension = file.isFile() ? FilenameUtils.getExtension(filePath) : null; + + if (file.isFile() && (fileExtension.equalsIgnoreCase("json") || fileExtension.equalsIgnoreCase("xml"))) { + String fileName = FilenameUtils.getBaseName(filePath); if (loaded.contains(fileName)) { logger.warn("Skipping duplicate file: {}", filePath); @@ -129,7 +134,7 @@ private static Patient findPatient(Bundle bundle) { return bundle.getEntry().stream() .filter(e -> e.getResource() instanceof Patient) .map(e -> (Patient) e.getResource()) - .findFirst() + .reduce(StreamUtils::toOnlyElement) .orElseThrow(() -> new IllegalArgumentException("Patient resource not found in bundle")); } From d5963088ac02bf6b9c5068d7fa6d68e5eb48e4ba Mon Sep 17 00:00:00 2001 From: Steven Williams Date: Fri, 20 Sep 2024 11:49:11 -0400 Subject: [PATCH 06/10] Add Maven profile for CLI --- Java/measureeval/README.md | 13 +++++-------- Java/measureeval/pom.xml | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Java/measureeval/README.md b/Java/measureeval/README.md index 543e07d4d..4e20c4cef 100644 --- a/Java/measureeval/README.md +++ b/Java/measureeval/README.md @@ -6,13 +6,10 @@ Perform the following from the `/Java` directory, which builds the measureeval J module: ```bash -mvn clean install -pl measureeval -am +mvn -P cli -pl measureeval -am clean package ``` -## Running the CLI - -To bypass the JAR manifest's main() class that runs it as a service, run the JAR with the following parameters: -`-Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher` +We use the `cli` Maven profile to ensure that `FileSystemInvocation` is used as the main class. ### Parameters @@ -28,11 +25,11 @@ To bypass the JAR manifest's main() class that runs it as a service, run the JAR Format: ```bash -java -cp measureeval-.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "" "" "" "" +java -jar measureeval-.jar "" "" "" "" ``` Example: ```bash -java -cp measureeval-.jar -Dloader.main=com.lantanagroup.link.measureeval.FileSystemInvocation org.springframework.boot.loader.launch.PropertiesLauncher "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" -``` \ No newline at end of file +java -jar measureeval-.jar "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" +``` diff --git a/Java/measureeval/pom.xml b/Java/measureeval/pom.xml index 188c94c77..42a8a49e9 100644 --- a/Java/measureeval/pom.xml +++ b/Java/measureeval/pom.xml @@ -146,7 +146,7 @@ - com.lantanagroup.link.measureeval.MeasureEvalApplication + ${mainClass} org.projectlombok @@ -157,4 +157,23 @@ + + + + api + + true + + + com.lantanagroup.link.measureeval.MeasureEvalApplication + + + + + cli + + com.lantanagroup.link.measureeval.FileSystemInvocation + + + From fc9df0e24afb13b14412861a9b326d50e4f0bb8f Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 24 Sep 2024 10:14:24 -0700 Subject: [PATCH 07/10] Renaming `logback.xml` to `logback-cli.xml` so-as not to override the `logback-spring.xml` when running as a service Updating `pom.xml` to use a specific file name and set the logback-cli.xml file as the primary for the `cli` profile in maven --- Java/measureeval/pom.xml | 4 ++++ .../src/main/resources/{logback.xml => logback-cli.xml} | 0 2 files changed, 4 insertions(+) rename Java/measureeval/src/main/resources/{logback.xml => logback-cli.xml} (100%) diff --git a/Java/measureeval/pom.xml b/Java/measureeval/pom.xml index 42a8a49e9..f95f81b0e 100644 --- a/Java/measureeval/pom.xml +++ b/Java/measureeval/pom.xml @@ -173,7 +173,11 @@ cli com.lantanagroup.link.measureeval.FileSystemInvocation + src/main/resources/logback-cli.xml + + measureeval-cli + diff --git a/Java/measureeval/src/main/resources/logback.xml b/Java/measureeval/src/main/resources/logback-cli.xml similarity index 100% rename from Java/measureeval/src/main/resources/logback.xml rename to Java/measureeval/src/main/resources/logback-cli.xml From 4ad1d2f828ff4c91a6908f883e65cd5fb33f040e Mon Sep 17 00:00:00 2001 From: Sean McIlvenna <5384694+seanmcilvenna@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:19:28 -0700 Subject: [PATCH 08/10] Removing final line in README.md to test signed commit --- Java/measureeval/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Java/measureeval/README.md b/Java/measureeval/README.md index 4e20c4cef..2f70ca246 100644 --- a/Java/measureeval/README.md +++ b/Java/measureeval/README.md @@ -32,4 +32,4 @@ Example: ```bash java -jar measureeval-.jar "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" -``` +``` \ No newline at end of file From f9532e8833416c4145400aff39c76e14e43389e2 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 24 Sep 2024 10:32:13 -0700 Subject: [PATCH 09/10] Re-adding line to test new git signing key --- Java/measureeval/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Java/measureeval/README.md b/Java/measureeval/README.md index 2f70ca246..4e20c4cef 100644 --- a/Java/measureeval/README.md +++ b/Java/measureeval/README.md @@ -32,4 +32,4 @@ Example: ```bash java -jar measureeval-.jar "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" -``` \ No newline at end of file +``` From 851b867e70611d2af6724e21f0eebb1b38163070 Mon Sep 17 00:00:00 2001 From: Sean McIlvenna Date: Tue, 24 Sep 2024 10:42:59 -0700 Subject: [PATCH 10/10] Modifying readme to use correct filename for measureeval-cli.jar --- Java/measureeval/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Java/measureeval/README.md b/Java/measureeval/README.md index 4e20c4cef..e7448f51b 100644 --- a/Java/measureeval/README.md +++ b/Java/measureeval/README.md @@ -25,11 +25,11 @@ We use the `cli` Maven profile to ensure that `FileSystemInvocation` is used as Format: ```bash -java -jar measureeval-.jar "" "" "" "" +java -jar measureeval-cli.jar "" "" "" "" ``` Example: ```bash -java -jar measureeval-.jar "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" +java -jar measureeval-cli.jar "C:/path/to/measure-bundle.json" "C:/path/to/patient-bundle.json" "2021-01-01" "2021-12-31" ```