Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute phenotype FHIR Bundle (#46) #52

Merged
merged 1 commit into from
Mar 8, 2021
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
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<bintray.package>phema-elm-to-ohdsi</bintray.package>
<ohdsi.webapi.version>2.6.0</ohdsi.webapi.version>
<ohdsi.circe.version>1.7.0</ohdsi.circe.version>
<hapi.fhir.version>5.3.0</hapi.fhir.version>
</properties>

<repositories>
Expand Down Expand Up @@ -226,6 +227,12 @@
<version>2.3.1</version>
</dependency>

<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId>
<version>${hapi.fhir.version}</version>
</dependency>

<!-- OHDSI dependencies -->
<dependency>
<groupId>org.ohdsi</groupId>
Expand Down
34 changes: 22 additions & 12 deletions src/main/java/edu/phema/elm_to_omop/ElmToOmopConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import edu.phema.elm_to_omop.helper.Config;
import edu.phema.elm_to_omop.helper.MyFormatter;
import edu.phema.elm_to_omop.io.OmopWriter;
import edu.phema.elm_to_omop.phenotype.BundlePhenotype;
import edu.phema.elm_to_omop.phenotype.FilePhenotype;
import edu.phema.elm_to_omop.phenotype.IPhenotype;
import edu.phema.elm_to_omop.phenotype.PhenotypeException;
import edu.phema.elm_to_omop.repository.OmopRepositoryService;
import edu.phema.elm_to_omop.vocabulary.ConceptCodeCsvFileValuesetService;
Expand Down Expand Up @@ -68,21 +70,29 @@ public void run(String[] args) {
OmopRepositoryService omopService = new OmopRepositoryService(domain, source);
OmopWriter omopWriter = new OmopWriter(logger);

// Initialize phenotype, which will do the following:
//
// 1. Determine if the user has specified which expression(s) is/are the phenotype definitions of interest.
// the CQL/ELM can be vague if not explicitly defined otherwise.
// 2. Read the elm file and set up the objects
FilePhenotype phenotype = new FilePhenotype(config.getInputFileName(), config.getPhenotypeExpressions());

// read the value set csv and add to the objects. If the tab is specified, we assume that it is a spreadsheet. Otherwise we will use the
// default CSV reader.
IValuesetService valuesetService = null;
if (config.isTabSpecified()) {
IPhenotype phenotype = null;
if (config.isUsingBundle()) {
BundlePhenotype bundlePhenotype = new BundlePhenotype(
config.getInputBundleName(), config.getPhenotypeExpressions(), omopService);
valuesetService = bundlePhenotype.getValuesetService();
phenotype = bundlePhenotype;
} else {
// Initialize phenotype, which will do the following:
//
// 1. Determine if the user has specified which expression(s) is/are the phenotype definitions of interest.
// the CQL/ELM can be vague if not explicitly defined otherwise.
// 2. Read the elm file and set up the objects
phenotype = new FilePhenotype(config.getInputFileName(), config.getPhenotypeExpressions());

// read the value set csv and add to the objects. If the tab is specified, we assume that it is a spreadsheet. Otherwise we will use the
// default CSV reader.
if (config.isTabSpecified()) {
valuesetService = new SpreadsheetValuesetService(omopService, config.getVsFileName(), config.getTab());
}
else {
}
else {
valuesetService = new ConceptCodeCsvFileValuesetService(omopService, config.getVsFileName(), true);
}
}

List<PhemaConceptSet> conceptSets = valuesetService.getConceptSets();
Expand Down
53 changes: 41 additions & 12 deletions src/main/java/edu/phema/elm_to_omop/helper/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public final class Config {

private String inputFileName;

private String inputBundleName;

private String vsFileName;

private String outFileName;
Expand Down Expand Up @@ -116,6 +118,7 @@ public String getProperty(final String key) {
public void setConfig() {
omopBaseURL = getProperty("OMOP_BASE_URL");
inputFileName = getProperty("INPUT_FILE_NAME");
inputBundleName = getProperty("INPUT_BUNDLE_NAME");
vsFileName = getProperty("VS_FILE_NAME");
outFileName = getProperty("OUT_FILE_NAME");
source = getProperty("SOURCE");
Expand All @@ -125,6 +128,10 @@ public void setConfig() {
runPropertyCheck();
}

public String getInputBundleName() {
return inputBundleName;
}

public String getInputFileName() {
return inputFileName;
}
Expand Down Expand Up @@ -169,6 +176,9 @@ private void setArg(final String inArg) {
if (prop.equalsIgnoreCase("INPUT_FILE_NAME")) {
inputFileName = val;
}
if (prop.equalsIgnoreCase("INPUT_BUNDLE_NAME")) {
inputBundleName = val;
}
if (prop.equalsIgnoreCase("VS_FILE_NAME")) {
vsFileName = val;
}
Expand Down Expand Up @@ -203,25 +213,36 @@ private void runPropertyCheck() {
if (omopBaseURL == null) {
logger.severe("ERROR - missing parameter OMOP_BASE_URL");
}
if (inputFileName == null) {

boolean loadBundle = (inputBundleName != null) && (!inputBundleName.equals(""));
boolean loadFile = (inputFileName != null) && (!inputFileName.equals(""));

if (!loadBundle && !loadFile) {
logger.severe("ERROR - missing either INPUT_FILE_NAME or INPUT_BUNDLE_NAME");
} else if (loadBundle && loadFile) {
logger.severe("ERROR - specify either INPUT_FILE_NAME or INPUT_BUNDLE_NAME, but not both");
} else if (loadFile) {
if (inputFileName == null) {
logger.severe("ERROR - missing parameter INPUT_FILE_NAME");
}
if (vsFileName == null) {
}
if (vsFileName == null) {
logger.severe("ERROR - missing parameter VS_FILE_NAME");
}
if (tab == null) {
logger.severe("INFO - missing optional parameter VS_TAB");
}
}

if (outFileName == null) {
logger.severe("ERROR - missing parameter OUT_FILE_NAME");
}
if (source == null) {
logger.severe("ERROR - missing parameter SOURCE");
}
if (tab == null) {
logger.severe("INFO - missing optional parameter VS_TAB");
}
}

public String configString() {
return String.format("OMOP_BASE_URL=%s, INPUT_FILE_NAME=%s, VS_FILE_NAME=%s, OUT_FILE_NAME=%s, SOURCE=%s, VS_TAB=%s", omopBaseURL, inputFileName, vsFileName, outFileName, source, tab);
return String.format("OMOP_BASE_URL=%s, INPUT_BUNDLE_NAME=%s, INPUT_FILE_NAME=%s, VS_FILE_NAME=%s, OUT_FILE_NAME=%s, SOURCE=%s, VS_TAB=%s", omopBaseURL, inputBundleName, inputFileName, vsFileName, outFileName, source, tab);
}

public String getConfigFileName() {
Expand Down Expand Up @@ -252,6 +273,10 @@ public void setOmopBaseURL(String omopBaseURL) {
this.omopBaseURL = omopBaseURL;
}

public void setInputBundleName(String inputBundleName) {
this.inputBundleName = inputBundleName;
}

public void setInputFileName(String inputFileName) {
this.inputFileName = inputFileName;
}
Expand All @@ -276,11 +301,15 @@ public void setPhenotypeExpressions(List<String> phenotypeExpressions) {
this.phenotypeExpressions = phenotypeExpressions;
}

/**
* Determine if a value has been specified for the value set spreadsheet tab.
* @return
*/
public boolean isTabSpecified() {
public boolean isUsingBundle() {
return !this.inputBundleName.equals("");
}

/**
* Determine if a value has been specified for the value set spreadsheet tab.
* @return
*/
public boolean isTabSpecified() {
return (this.tab != null && !this.tab.equals(""));
}
}
154 changes: 154 additions & 0 deletions src/main/java/edu/phema/elm_to_omop/io/FhirReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package edu.phema.elm_to_omop.io;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import edu.phema.elm_to_omop.phenotype.BundlePhenotype;
import edu.phema.elm_to_omop.phenotype.PhenotypeException;
import edu.phema.elm_to_omop.repository.IOmopRepositoryService;
import edu.phema.elm_to_omop.repository.OmopRepositoryException;
import edu.phema.elm_to_omop.vocabulary.FhirBundleConceptSetService;
import edu.phema.elm_to_omop.vocabulary.IValuesetService;
import edu.phema.elm_to_omop.vocabulary.ValuesetServiceException;
import edu.phema.elm_to_omop.vocabulary.phema.PhemaConceptSet;
import edu.phema.elm_to_omop.vocabulary.phema.PhemaValueSet;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.ValueSet;
import org.ohdsi.circe.vocabulary.Concept;
import org.ohdsi.circe.vocabulary.ConceptSetExpression;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class FhirReader {
// Initial file-based support will focus just on JSON files
public static final String BUNDLE_EXTENSION = ".json";
private IOmopRepositoryService repository;
private static Map<String, String> codeSystemUriToOmopName = new HashMap<String, String>() {{
// TODO Add more mappings as needed
put("http://www.ama-assn.org/go/cpt", "CPT4");
put("http://hl7.org/fhir/sid/icd-9-cm", "ICD9CM");
put("http://hl7.org/fhir/sid/icd-9-proc", "ICD9Proc");
put("http://loinc.org", "LOINC");
put("http://www.nlm.nih.gov/research/umls/rxnorm", "RxNorm");
put("http://snomed.info/sct", "SNOMED");
put("http://hl7.org/fhir/sid/icd-10", "ICD10");
put("http://hl7.org/fhir/sid/icd-10-cm", "ICD10CM");
put("http://www.icd10data.com/icd10pcs", "ICD10PCS");
put("http://hl7.org/fhir/sid/icd-10-pcs", "ICD10PCS");
put("http://hl7.org/fhir/sid/ndc", "NDC");
put("http://hl7.org/fhir/ndfrt", "NDFRT");
put("http://unitsofmeasure.org", "UCUM");
put("http://www.whocc.no/atc", "ATC");
put("http://terminology.hl7.org/CodeSystem/HCPCS", "HCPCS");
}};

public FhirReader(IOmopRepositoryService repository) {
this.repository = repository;
}

/**
* Given a FHIR Bundle stored as a JSON file, read all components (libraries, valuesets, etc.)
* and prepare it as a consolidated BundlePhenotype
* @param bundleFilePath The path to the Bundle JSON file
* @param omopService OMOP service to resolve concepts
* @return The consoldiated BundlePhenotype
* @throws PhenotypeException
*/
public static BundlePhenotype readBundleFromFile(String bundleFilePath, IOmopRepositoryService omopService) throws PhenotypeException {
File file = new File(bundleFilePath);
FhirContext ctx = FhirContext.forR4();
IParser parser = ctx.newJsonParser();
try {
Bundle bundle = parser.parseResource(Bundle.class, new FileReader(file));
List<Bundle.BundleEntryComponent> entries = bundle.getEntry();
// Load all of the libraries - this includes phenotype and supporting libraries
List<Library> libraryEntries = entries.stream()
.filter(x -> x.getResource().getResourceType() == ResourceType.Library)
.map(x -> (Library)x.getResource())
.collect(Collectors.toList());

List<ValueSet> valueSetEntries = entries.stream()
.filter(x -> x.getResource().getResourceType() == ResourceType.ValueSet)
.map(x -> (ValueSet)x.getResource())
.collect(Collectors.toList());

IValuesetService service = new FhirBundleConceptSetService(valueSetEntries, omopService);
BundlePhenotype phenotype = new BundlePhenotype();
phenotype.addLibraries(libraryEntries);
phenotype.setValuesetService(service);
return phenotype;
} catch (Exception e) {
throw new PhenotypeException("Error reading phenotype bundle", e);
}
}

/**
* Convert FHIR ValueSet resources to the PhemaConceptSet representation
* @param valueSets List of FHIR ValueSet resources
* @return List of PhemaConceptSets
* @throws IOException
* @throws OmopRepositoryException
* @throws InvalidFormatException
* @throws ValuesetServiceException
*/
public List<PhemaConceptSet> getConceptSets(List<ValueSet> valueSets) throws IOException, OmopRepositoryException, InvalidFormatException, ValuesetServiceException {
List<PhemaConceptSet> conceptSets = new ArrayList<PhemaConceptSet>(valueSets.size());
for (int index = 0; index < valueSets.size(); index++) {
ValueSet valueSet = valueSets.get(index);
conceptSets.add(getConceptSet(valueSet, index));
}
return conceptSets;
}

/**
* Convert a ValueSet to a PhemaConceptSet
* @param valueSet FHIR ValueSet resource to convert
* @param index The index the valueset appears - must be unique
* @return PhemaConceptSet containing all codes from the value set
* @throws OmopRepositoryException
* @throws ValuesetServiceException
*/
private PhemaConceptSet getConceptSet(ValueSet valueSet, int index) throws OmopRepositoryException, ValuesetServiceException {
// TODO - account for more than just "include", but this will work for now
List<Concept> concepts = new ArrayList<>();
List<ValueSet.ConceptSetComponent> components = valueSet.getCompose().getInclude();
for (ValueSet.ConceptSetComponent component : components) {
String codeSystem = component.getSystem();
if (!codeSystemUriToOmopName.containsKey(codeSystem)) {
throw new ValuesetServiceException(String.format("Unable to transform the code system %s", codeSystem));
}
codeSystem = codeSystemUriToOmopName.get(codeSystem);
for (ValueSet.ConceptReferenceComponent concept : component.getConcept()) {
concepts.addAll(repository.vocabularySearch(concept.getCode(), codeSystem));
}
}

PhemaConceptSet conceptSet = new PhemaConceptSet();
conceptSet.setOid(valueSet.getIdElement().getIdPart());
conceptSet.id = index;
conceptSet.name = valueSet.getName();
ArrayList<ConceptSetExpression.ConceptSetItem> itemsList = new ArrayList<>();
for (Concept concept : concepts) {
ConceptSetExpression.ConceptSetItem item = new ConceptSetExpression.ConceptSetItem();
item.concept = concept;
itemsList.add(item);
}

ConceptSetExpression.ConceptSetItem[] items = new ConceptSetExpression.ConceptSetItem[itemsList.size()];
ConceptSetExpression expression = new ConceptSetExpression();
expression.items = itemsList.toArray(items);

conceptSet.expression = expression;

return conceptSet;
}
}
Loading