-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#48 - Added a STU3 data provider that serves up resources from a Bund…
…le of source data
- Loading branch information
Chris Schuler
committed
May 15, 2018
1 parent
c9cf9db
commit 255757a
Showing
2 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
210 changes: 210 additions & 0 deletions
210
src/main/java/org/opencds/cqf/helpers/DataProviderHelper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package org.opencds.cqf.helpers; | ||
|
||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; | ||
import ca.uhn.fhir.model.dstu2.composite.CodingDt; | ||
import ca.uhn.fhir.model.dstu2.composite.PeriodDt; | ||
import ca.uhn.fhir.model.primitive.CodeDt; | ||
import ca.uhn.fhir.model.primitive.DateDt; | ||
import ca.uhn.fhir.model.primitive.DateTimeDt; | ||
import ca.uhn.fhir.model.primitive.InstantDt; | ||
import org.hl7.fhir.dstu3.model.*; | ||
import org.opencds.cqf.cql.runtime.Code; | ||
import org.opencds.cqf.cql.runtime.DateTime; | ||
import org.opencds.cqf.cql.runtime.Interval; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Date; | ||
import java.util.List; | ||
|
||
public class DataProviderHelper { | ||
// Get lowest precision from list of date objects | ||
public static String getPrecision(List<Object> dateObjects) { | ||
int precision = 7; | ||
for (Object dateObject : dateObjects) { | ||
if (dateObject instanceof Interval) { | ||
DateTime start = (DateTime) ((Interval) dateObject).getStart(); | ||
DateTime end = (DateTime) ((Interval) dateObject).getEnd(); | ||
|
||
if (start != null) { | ||
if (precision > start.getPartial().size()) { | ||
precision = start.getPartial().size(); | ||
} | ||
} | ||
if (end != null) { | ||
if (precision > end.getPartial().size()) { | ||
precision = end.getPartial().size(); | ||
} | ||
} | ||
} else { | ||
precision = ((DateTime) dateObject).getPartial().size(); | ||
} | ||
} | ||
|
||
switch (precision) { | ||
case 1: return "year"; | ||
case 2: return "month"; | ||
case 3: return "day"; | ||
case 4: return "hour"; | ||
case 5: return "minute"; | ||
case 6: return "second"; | ||
default: return "millisecond"; | ||
} | ||
} | ||
|
||
public static Object getDstu2DateTime(Object dateObject) { | ||
if (dateObject instanceof DateDt) { | ||
return DateTime.fromJavaDate(((DateDt) dateObject).getValue()); | ||
} else if (dateObject instanceof DateTimeDt) { | ||
return DateTime.fromJavaDate(((DateTimeDt) dateObject).getValue()); | ||
} else if (dateObject instanceof InstantDt) { | ||
return DateTime.fromJavaDate(((InstantDt) dateObject).getValue()); | ||
} else if (dateObject instanceof PeriodDt) { | ||
return new Interval( | ||
DateTime.fromJavaDate(((PeriodDt) dateObject).getStart()), true, | ||
DateTime.fromJavaDate(((PeriodDt) dateObject).getEnd()), true | ||
); | ||
} | ||
return dateObject; | ||
} | ||
|
||
public static Object getDstu2Code(Object codeObject) { | ||
if (codeObject instanceof CodeDt) { | ||
return ((CodeDt) codeObject).getValue(); | ||
} else if (codeObject instanceof CodingDt) { | ||
return new Code().withSystem(((CodingDt) codeObject).getSystem()).withCode(((CodingDt) codeObject).getCode()); | ||
} | ||
else if (codeObject instanceof CodeableConceptDt) { | ||
List<Code> codes = new ArrayList<>(); | ||
for (CodingDt coding : ((CodeableConceptDt) codeObject).getCoding()) { | ||
codes.add((Code) getDstu2Code(coding)); | ||
} | ||
return codes; | ||
} | ||
return codeObject; | ||
} | ||
|
||
public static Object getStu3DateTime(Object dateObject) { | ||
if (dateObject instanceof Date) { | ||
return DateTime.fromJavaDate((Date) dateObject); | ||
} else if (dateObject instanceof DateTimeType) { | ||
return DateTime.fromJavaDate(((DateTimeType) dateObject).getValue()); | ||
} else if (dateObject instanceof InstantType) { | ||
return DateTime.fromJavaDate(((InstantType) dateObject).getValue()); | ||
} else if (dateObject instanceof Period) { | ||
return new Interval( | ||
DateTime.fromJavaDate(((Period) dateObject).getStart()), true, | ||
DateTime.fromJavaDate(((Period) dateObject).getEnd()), true | ||
); | ||
} | ||
return dateObject; | ||
} | ||
|
||
public static Object getStu3Code(Object codeObject) { | ||
if (codeObject instanceof CodeType) { | ||
return ((CodeType) codeObject).getValue(); | ||
} else if (codeObject instanceof Coding) { | ||
return new Code().withSystem(((Coding) codeObject).getSystem()).withCode(((Coding) codeObject).getCode()); | ||
} | ||
else if (codeObject instanceof CodeableConcept) { | ||
List<Code> codes = new ArrayList<>(); | ||
for (Coding coding : ((CodeableConcept) codeObject).getCoding()) { | ||
codes.add((Code) getStu3Code(coding)); | ||
} | ||
return codes; | ||
} | ||
return codeObject; | ||
} | ||
|
||
public static boolean checkCodeMembership(Iterable<Code> codes, Object codeObject) { | ||
// for now, just checking whether code values are equal... TODO - add intelligent checks for system and version | ||
for (Code code : codes) { | ||
if (codeObject instanceof String && code.getCode().equals(codeObject)) { | ||
return true; | ||
} | ||
else if (codeObject instanceof Code && code.getCode().equals(((Code) codeObject).getCode())) { | ||
return true; | ||
} | ||
else if (codeObject instanceof Iterable) { | ||
for (Object obj : (Iterable) codeObject) { | ||
if (code.getCode().equals(((Code) obj).getCode())) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
public static String convertStu3PathToSearchParam(String type, String path) { | ||
path = path.replace(".value", ""); | ||
switch (type) { | ||
case "AllergyIntolerance": | ||
if (path.equals("clinicalStatus")) return "clinical-status"; | ||
else if (path.contains("substance")) return "code"; | ||
else if (path.equals("assertedDate")) return "date"; | ||
else if (path.equals("lastOccurrence")) return "last-date"; | ||
else if (path.startsWith("reaction")) { | ||
if (path.endsWith("manifestation")) return "manifestation"; | ||
else if (path.endsWith("onset")) return "onset"; | ||
else if (path.endsWith("exposureRoute")) return "route"; | ||
else if (path.endsWith("severity")) return "severity"; | ||
} | ||
else if (path.equals("verificationStatus")) return "verification-status"; | ||
break; | ||
case "Claim": | ||
if (path.contains("careTeam")) return "care-team"; | ||
else if (path.contains("payee")) return "payee"; | ||
break; | ||
case "Condition": | ||
if (path.equals("abatementDateTime")) return "abatement-date"; | ||
else if (path.equals("abatementPeriod")) return "abatement-date"; | ||
else if (path.equals("abatementRange")) return "abatement-age"; | ||
else if (path.equals("onsetDateTime")) return "onset-date"; | ||
else if (path.equals("onsetPeriod")) return "onset-date"; | ||
else if (path.equals("onsetRange")) return "onset-age"; | ||
break; | ||
case "MedicationRequest": | ||
if (path.equals("authoredOn")) return "authoredon"; | ||
else if (path.equals("medicationCodeableConcept")) return "code"; | ||
else if (path.equals("medicationReference")) return "medication"; | ||
else if (path.contains("event")) return "date"; | ||
else if (path.contains("performer")) return "intended-dispenser"; | ||
else if (path.contains("requester")) return "requester"; | ||
break; | ||
case "NutritionOrder": | ||
if (path.contains("additiveType")) return "additive"; | ||
else if (path.equals("dateTime")) return "datetime"; | ||
else if (path.contains("baseFormulaType")) return "formula"; | ||
else if (path.contains("oralDiet")) return "oraldiet"; | ||
else if (path.equals("orderer")) return "provider"; | ||
else if (path.contains("supplement")) return "supplement"; | ||
break; | ||
case "ProcedureRequest": | ||
if (path.equals("authoredOn")) return "authored"; | ||
else if (path.equals("basedOn")) return "based-on"; | ||
else if (path.equals("bodySite")) return "body-site"; | ||
else if (path.equals("context")) return "encounter"; | ||
else if (path.equals("performerType")) return "performer-type"; | ||
else if (path.contains("requester")) return "requester"; | ||
break; | ||
case "ReferralRequest": | ||
if (path.equals("authoredOn")) return "authored"; | ||
else if (path.equals("basedOn")) return "based-on"; | ||
else if (path.equals("context")) return "encounter"; | ||
else if (path.equals("groupIdentifier")) return "group-identifier"; | ||
else if (path.equals("occurrence")) return "occurrence-date"; | ||
else if (path.contains("requester")) return "requester"; | ||
else if (path.equals("serviceRequested")) return "service"; | ||
break; | ||
case "VisionPrescription": | ||
if (path.equals("dateWritten")) return "datewritten"; | ||
break; | ||
default: | ||
if (path.startsWith("effective")) return "date"; | ||
else if (path.equals("period")) return "date"; | ||
else if (path.equals("vaccineCode")) return "vaccine-code"; | ||
break; | ||
} | ||
return path.replace('.', '-').toLowerCase(); | ||
} | ||
} |
130 changes: 130 additions & 0 deletions
130
src/main/java/org/opencds/cqf/providers/BundleDataProviderStu3.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package org.opencds.cqf.providers; | ||
|
||
import org.hl7.fhir.dstu3.model.Bundle; | ||
import org.hl7.fhir.dstu3.model.Resource; | ||
import org.opencds.cqf.cql.data.fhir.FhirDataProviderStu3; | ||
import org.opencds.cqf.cql.elm.execution.InEvaluator; | ||
import org.opencds.cqf.cql.elm.execution.IncludesEvaluator; | ||
import org.opencds.cqf.cql.runtime.Code; | ||
import org.opencds.cqf.cql.runtime.DateTime; | ||
import org.opencds.cqf.cql.runtime.Interval; | ||
import org.opencds.cqf.cql.terminology.ValueSetInfo; | ||
import org.opencds.cqf.helpers.DataProviderHelper; | ||
|
||
import java.util.*; | ||
|
||
public class BundleDataProviderStu3 extends FhirDataProviderStu3 { | ||
|
||
private Map<String, List<Object>> resourceMap; | ||
|
||
public BundleDataProviderStu3(Bundle sourceData) { | ||
resourceMap = new HashMap<>(); | ||
|
||
// populate map | ||
if (sourceData.hasEntry()) { | ||
for (Bundle.BundleEntryComponent entry : sourceData.getEntry()) { | ||
if (entry.hasResource()) { | ||
Resource resource = entry.getResource(); | ||
if (resourceMap.containsKey(resource.fhirType())) { | ||
resourceMap.get(resource.fhirType()).add(resource); | ||
} | ||
else { | ||
List<Object> resources = new ArrayList<>(); | ||
resources.add(resource); | ||
resourceMap.put(resource.fhirType(), resources); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public Iterable<Object> retrieve(String context, Object contextValue, String dataType, String templateId, | ||
String codePath, Iterable<Code> codes, String valueSet, String datePath, | ||
String dateLowPath, String dateHighPath, Interval dateRange) | ||
{ | ||
if (codePath == null && (codes != null || valueSet != null)) { | ||
throw new IllegalArgumentException("A code path must be provided when filtering on codes or a valueset."); | ||
} | ||
|
||
if (dataType == null) { | ||
throw new IllegalArgumentException("A data type (i.e. Procedure, Valueset, etc...) must be specified for clinical data retrieval"); | ||
} | ||
|
||
List<Object> resourcesOfType = resourceMap.get(dataType); | ||
|
||
if (resourcesOfType == null) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
// no resources or no filtering -> return list | ||
if (resourcesOfType.isEmpty() || (dateRange == null && codePath == null)) { | ||
return resourcesOfType; | ||
} | ||
|
||
List<Object> returnList = new ArrayList<>(); | ||
for (Object resource : resourcesOfType) { | ||
boolean includeResource = true; | ||
if (dateRange != null) { | ||
if (datePath != null) { | ||
if (dateHighPath != null || dateLowPath != null) { | ||
throw new IllegalArgumentException("If the datePath is specified, the dateLowPath and dateHighPath attributes must not be present."); | ||
} | ||
|
||
Object dateObject = DataProviderHelper.getStu3DateTime(resolvePath(resource, datePath)); | ||
DateTime date = dateObject instanceof DateTime ? (DateTime) dateObject : null; | ||
Interval dateInterval = dateObject instanceof Interval ? (Interval) dateObject : null; | ||
String precision = DataProviderHelper.getPrecision(Arrays.asList(dateRange, date)); | ||
if (date != null && !(InEvaluator.in(date, dateRange, precision))) { | ||
includeResource = false; | ||
} | ||
|
||
else if (dateInterval != null && !((Boolean) IncludesEvaluator.includes(dateRange, dateInterval, precision))) { | ||
includeResource = false; | ||
} | ||
} else { | ||
if (dateHighPath == null && dateLowPath == null) { | ||
throw new IllegalArgumentException("If the datePath is not given, either the lowDatePath or highDatePath must be provided."); | ||
} | ||
|
||
DateTime lowDate = dateLowPath == null ? null : (DateTime) DataProviderHelper.getStu3DateTime(resolvePath(resource, dateLowPath)); | ||
DateTime highDate = dateHighPath == null ? null : (DateTime) DataProviderHelper.getStu3DateTime(resolvePath(resource, dateHighPath)); | ||
|
||
String precision = DataProviderHelper.getPrecision(Arrays.asList(dateRange, lowDate, highDate)); | ||
|
||
Interval interval = new Interval(lowDate, true, highDate, true); | ||
|
||
if (!(Boolean) IncludesEvaluator.includes(dateRange, interval, precision)) { | ||
includeResource = false; | ||
} | ||
} | ||
} | ||
|
||
if (codePath != null && !codePath.equals("") && includeResource) { | ||
if (valueSet != null && terminologyProvider != null) { | ||
if (valueSet.startsWith("urn:oid:")) { | ||
valueSet = valueSet.replace("urn:oid:", ""); | ||
} | ||
ValueSetInfo valueSetInfo = new ValueSetInfo().withId(valueSet); | ||
codes = terminologyProvider.expand(valueSetInfo); | ||
} | ||
if (codes != null) { | ||
Object codeObject = DataProviderHelper | ||
.getStu3Code( | ||
resolvePath( | ||
resource, | ||
DataProviderHelper.convertStu3PathToSearchParam(dataType, codePath) | ||
) | ||
); | ||
includeResource = DataProviderHelper.checkCodeMembership(codes, codeObject); | ||
} | ||
|
||
if (includeResource) { | ||
returnList.add(resource); | ||
} | ||
} | ||
} | ||
|
||
return returnList; | ||
} | ||
} |