Skip to content

Commit

Permalink
Add support for converting CQL Tuples to FHIR Parameters (#1422)
Browse files Browse the repository at this point in the history
* Adding Tuple conversion and tests

* Conversion for CQL tuples to FHIR Parameters

* Fix types

* Fix some mistakes on the tests. Whoops

* Use data-absent extension

* Tuple updates

* Address SonarCloud warnings

* Added nested tuple and empty tuple handling

* Nested and empty tuple tests

* Updated formatting

---------

Co-authored-by: Bryn Rhodes <[email protected]>
  • Loading branch information
JPercival and brynrhodes authored Oct 10, 2024
1 parent 4e3be26 commit b50ea52
Show file tree
Hide file tree
Showing 10 changed files with 700 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public boolean isFhirType(Object value) {
}

@Override
public Iterable<Object> toFhirTypes(Iterable<?> values) {
public List<Object> toFhirTypes(Iterable<?> values) {
List<Object> converted = new ArrayList<>();
for (Object value : values) {
if (value == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,86 @@ public ICompositeType toFhirRange(Interval value) {
return range;
}

private static BooleanType emptyBooleanWithExtension(String url, Type value) {
var result = new BooleanType((String) null);
result.addExtension().setUrl(url).setValue(value);
return result;
}

private static void addPartWithNameAndValue(
Parameters.ParametersParameterComponent param, String key, Object value) {
if (value instanceof Parameters.ParametersParameterComponent) {
var part = (Parameters.ParametersParameterComponent) value;
part.setName(key);
param.addPart(part);
} else {
var part = param.addPart().setName(key);
if (value instanceof Resource) {
part.setResource((Resource) value);
} else if (value instanceof Type) {
part.setValue((Type) value);
} else {
throw new IllegalArgumentException(
"Unsupported FHIR type: " + value.getClass().getName());
}
}
}

private static Iterable<?> asIterable(Object value) {
if (value instanceof Iterable) {
return (Iterable<?>) value;
} else {
return null;
}
}

private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) {
if (value == null) {
// Null value, add a single empty value with an extension indicating the reason
var dataAbsentValue = emptyBooleanWithExtension(
DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE));
addPartWithNameAndValue(param, key, dataAbsentValue);
return;
}

var iterable = asIterable(value);
if (iterable == null) {
// Single, non-null value
addPartWithNameAndValue(param, key, toFhirType(value));
return;
}

if (!iterable.iterator().hasNext()) {
// Empty list
var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true));
addPartWithNameAndValue(param, key, emptyListValue);
} else {
// Non-empty list, one part per value
var fhirTypes = this.toFhirTypes(iterable);
for (var fhirType : fhirTypes) {
addPartWithNameAndValue(param, key, fhirType);
}
}
}

@Override
public IBase toFhirTuple(Tuple value) {
if (value == null) {
return null;
}

throw new NotImplementedException("can't convert Tuples");
var parameters = new Parameters();
var param = parameters.addParameter();

if (value.getElements().isEmpty()) {
param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true)));
}

for (String key : value.getElements().keySet()) {
addElementToParameter(param, key, value.getElements().get(key));
}

return param;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,86 @@ public ICompositeType toFhirRange(Interval value) {
return range;
}

private static BooleanType emptyBooleanWithExtension(String url, Type value) {
var result = new BooleanType((String) null);
result.addExtension().setUrl(url).setValue(value);
return result;
}

private static void addPartWithNameAndValue(
Parameters.ParametersParameterComponent param, String key, Object value) {
if (value instanceof Parameters.ParametersParameterComponent) {
var part = (Parameters.ParametersParameterComponent) value;
part.setName(key);
param.addPart(part);
} else {
var part = param.addPart().setName(key);
if (value instanceof Resource) {
part.setResource((Resource) value);
} else if (value instanceof Type) {
part.setValue((Type) value);
} else {
throw new IllegalArgumentException(
"Unsupported FHIR type: " + value.getClass().getName());
}
}
}

private static Iterable<?> asIterable(Object value) {
if (value instanceof Iterable) {
return (Iterable<?>) value;
} else {
return null;
}
}

private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) {
if (value == null) {
// Null value, add a single empty value with an extension indicating the reason
var dataAbsentValue = emptyBooleanWithExtension(
DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE));
addPartWithNameAndValue(param, key, dataAbsentValue);
return;
}

var iterable = asIterable(value);
if (iterable == null) {
// Single, non-null value
addPartWithNameAndValue(param, key, toFhirType(value));
return;
}

if (!iterable.iterator().hasNext()) {
// Empty list
var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true));
addPartWithNameAndValue(param, key, emptyListValue);
} else {
// Non-empty list, one part per value
var fhirTypes = this.toFhirTypes(iterable);
for (var fhirType : fhirTypes) {
addPartWithNameAndValue(param, key, fhirType);
}
}
}

@Override
public IBase toFhirTuple(Tuple value) {
if (value == null) {
return null;
}

throw new NotImplementedException("can't convert Tuples");
var parameters = new Parameters();
var param = parameters.addParameter();

if (value.getElements().isEmpty()) {
param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true)));
}

for (String key : value.getElements().keySet()) {
addElementToParameter(param, key, value.getElements().get(key));
}

return param;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.opencds.cqf.cql.engine.fhir.converter;

import java.math.BigDecimal;
import java.util.List;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.ICompositeType;
Expand Down Expand Up @@ -28,6 +29,12 @@
*/
public interface FhirTypeConverter {

static final String EMPTY_LIST_EXT_URL = "http://hl7.org/fhir/StructureDefinition/cqf-isEmptyList";
static final String EMPTY_TUPLE_EXT_URL = "http://hl7.org/fhir/StructureDefinition/cqf-isEmptyTuple";
static final String DATA_ABSENT_REASON_EXT_URL = "http://hl7.org/fhir/StructureDefinition/data-absent-reason";
static final String DATA_ABSENT_REASON_UNKNOWN_CODE = "unknown";
static final String CQL_TYPE_EXT_URL = "http://hl7.org/fhir/StructureDefinition/cqf-cqlType";

// CQL-to-FHIR conversions

/**
Expand All @@ -53,9 +60,9 @@ public interface FhirTypeConverter {
* nulls, and sublist hierarchy
*
* @param values an Iterable containing CQL structures, nulls, or sublists
* @return an Iterable containing FHIR types, nulls, and sublists
* @return an List containing FHIR types, nulls, and sublists
*/
public Iterable<Object> toFhirTypes(Iterable<?> values);
public List<Object> toFhirTypes(Iterable<?> values);

/**
* Converts a String to a FHIR Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ public ICompositeType toFhirPeriod(Interval value) {

return period;
} else if (getSimpleName(value.getPointType().getTypeName()).equals("Date")) {
// TODO: This will construct DateTimeType values in FHIR with the system timezone id, not the
// TODO: This will construct DateTimeType values in FHIR with the system
// timezone id, not the
// timezoneoffset of the evaluation request..... this is a bug waiting to happen
if (value.getStart() != null) {
period.setStart(toFhirDate((Date) value.getStart()).getValue());
Expand Down Expand Up @@ -212,13 +213,86 @@ public ICompositeType toFhirRange(Interval value) {
return range;
}

private static BooleanType emptyBooleanWithExtension(String url, Type value) {
var result = new BooleanType((String) null);
result.addExtension().setUrl(url).setValue(value);
return result;
}

private static void addPartWithNameAndValue(
Parameters.ParametersParameterComponent param, String key, Object value) {
if (value instanceof Parameters.ParametersParameterComponent) {
var part = (Parameters.ParametersParameterComponent) value;
part.setName(key);
param.addPart(part);
} else {
var part = param.addPart().setName(key);
if (value instanceof Resource) {
part.setResource((Resource) value);
} else if (value instanceof Type) {
part.setValue((Type) value);
} else {
throw new IllegalArgumentException(
"Unsupported FHIR type: " + value.getClass().getName());
}
}
}

private static Iterable<?> asIterable(Object value) {
if (value instanceof Iterable) {
return (Iterable<?>) value;
} else {
return null;
}
}

private void addElementToParameter(Parameters.ParametersParameterComponent param, String key, Object value) {
if (value == null) {
// Null value, add a single empty value with an extension indicating the reason
var dataAbsentValue = emptyBooleanWithExtension(
DATA_ABSENT_REASON_EXT_URL, new CodeType(DATA_ABSENT_REASON_UNKNOWN_CODE));
addPartWithNameAndValue(param, key, dataAbsentValue);
return;
}

var iterable = asIterable(value);
if (iterable == null) {
// Single, non-null value
addPartWithNameAndValue(param, key, toFhirType(value));
return;
}

if (!iterable.iterator().hasNext()) {
// Empty list
var emptyListValue = emptyBooleanWithExtension(EMPTY_LIST_EXT_URL, new BooleanType(true));
addPartWithNameAndValue(param, key, emptyListValue);
} else {
// Non-empty list, one part per value
var fhirTypes = this.toFhirTypes(iterable);
for (var fhirType : fhirTypes) {
addPartWithNameAndValue(param, key, fhirType);
}
}
}

@Override
public IBase toFhirTuple(Tuple value) {
if (value == null) {
return null;
}

throw new NotImplementedException("can't convert Tuples");
var parameters = new Parameters();
var param = parameters.addParameter();

if (value.getElements().isEmpty()) {
param.setValue(emptyBooleanWithExtension(EMPTY_TUPLE_EXT_URL, new BooleanType(true)));
}

for (String key : value.getElements().keySet()) {
addElementToParameter(param, key, value.getElements().get(key));
}

return param;
}

@Override
Expand Down
Loading

0 comments on commit b50ea52

Please sign in to comment.