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

WIP Issue 21 #22

Merged
merged 12 commits into from
Jul 5, 2023
33 changes: 20 additions & 13 deletions fhirpathpy/engine/evaluators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,26 +182,37 @@ def func(acc, res):
actualTypes = model["choiceTypePaths"][childPath]

toAdd = None
toAdd_ = None

if isinstance(actualTypes, list):
# Use actualTypes to find the field's value
for actualType in actualTypes:
field = key + actualType
if isinstance(res.data, (dict, list)) and field in res.data:
toAdd = res.data[field]
childPath = actualType
break
if isinstance(res.data, (dict, list)):
toAdd = res.data.get(field)
toAdd_ = res.data.get(f"_{field}")
if toAdd is not None or toAdd_ is not None:
childPath = actualType
break
else:
if isinstance(res.data, (dict, list)) and key in res.data:
toAdd = res.data[key]
if isinstance(res.data, (dict, list)):
toAdd = res.data.get(key)
toAdd_ = res.data.get(f"_{key}")
if key == 'extension':
childPath = 'Extension'

if util.is_some(toAdd):
if isinstance(toAdd, list):
mapped = [nodes.ResourceNode.create_node(x, childPath) for x in toAdd]
acc = acc + mapped
else:
acc.append(nodes.ResourceNode.create_node(toAdd, childPath))
return acc
if util.is_some(toAdd_):
if isinstance(toAdd_, list):
mapped = [nodes.ResourceNode.create_node(x, childPath) for x in toAdd_]
acc = acc + mapped
else:
acc.append(nodes.ResourceNode.create_node(toAdd_, childPath))
return acc

return func
Expand Down Expand Up @@ -265,9 +276,7 @@ def polarity_expression(ctx, parentData, node):
rtn = engine.do_eval(ctx, parentData, node["children"][0])

if len(rtn) != 1: # not yet in spec, but per Bryn Rhodes
raise Exception(
"Unary " + sign + " can only be applied to an individual number."
)
raise Exception("Unary " + sign + " can only be applied to an individual number.")

if not util.is_number(rtn[0]):
raise Exception("Unary " + sign + " can only be applied to a number.")
Expand Down Expand Up @@ -301,9 +310,7 @@ def polarity_expression(ctx, parentData, node):
# expressions
"PolarityExpression": polarity_expression,
"IndexerExpression": indexer_expression,
"MembershipExpression": alias_op_expression(
{"contains": "containsOp", "in": "inOp"}
),
"MembershipExpression": alias_op_expression({"contains": "containsOp", "in": "inOp"}),
"TermExpression": term_expression,
"UnionExpression": union_expression,
"InvocationExpression": invocation_expression,
Expand Down
5 changes: 3 additions & 2 deletions fhirpathpy/engine/invocations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"count": {"fn": existence.count_fn},
"repeat": {"fn": filtering.repeat_macro, "arity": {1: ["Expr"]}},
"where": {"fn": filtering.where_macro, "arity": {1: ["Expr"]}},
"extension": {"fn": filtering.extension, "arity": {1: ["String"]}},
"select": {"fn": filtering.select_macro, "arity": {1: ["Expr"]}},
"single": {"fn": filtering.single_fn},
"first": {"fn": filtering.first_fn},
Expand All @@ -42,8 +43,8 @@
"toInteger": {"fn": misc.to_integer},
"toDecimal": {"fn": misc.to_decimal},
"toString": {"fn": misc.to_string},
# toDateTime: {fn: misc.toDateTime},
# toTime: {fn: misc.toTime},
"toDateTime": {"fn": misc.to_date_time},
"toTime": {"fn": misc.to_time},
"indexOf": {"fn": strings.index_of, "arity": {1: ["String"]}, "nullable_input": True},
"substring": {
"fn": strings.substring,
Expand Down
6 changes: 4 additions & 2 deletions fhirpathpy/engine/invocations/equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ def datetime_equality(ctx, x, y):
datetime_x = x[0]
datetime_y = y[0]
if type(datetime_x) not in DATETIME_NODES_LIST:
datetime_x = nodes.FP_DateTime(datetime_x) or nodes.FP_Time(datetime_x)
v_x = util.get_data(datetime_x)
datetime_x = nodes.FP_DateTime(v_x) or nodes.FP_Time(v_x)
if type(datetime_y) not in DATETIME_NODES_LIST:
datetime_y = nodes.FP_DateTime(datetime_y) or nodes.FP_Time(datetime_y)
v_y = util.get_data(datetime_y)
datetime_y = nodes.FP_DateTime(v_y) or nodes.FP_Time(v_y)
return datetime_x.equals(datetime_y)


Expand Down
12 changes: 12 additions & 0 deletions fhirpathpy/engine/invocations/filtering.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numbers
import fhirpathpy.engine.util as util
import fhirpathpy.engine.nodes as nodes

# Contains the FHIRPath Filtering and Projection functions.
# (Section 5.2 of the FHIRPath 1.0.0 specification).
Expand Down Expand Up @@ -115,3 +116,14 @@ def check_fhir_type(ctx, x, tp):

def of_type_fn(ctx, coll, tp):
return list(filter(lambda x: check_fhir_type(ctx, util.get_data(x), tp), coll))


def extension(ctx, data, url):
res = []
for d in data:
element = util.get_data(d)
if isinstance(element, dict):
exts = [e for e in element.get("extension", []) if e["url"] == url]
if len(exts) > 0:
res.append(nodes.ResourceNode.create_node(exts[0], "Extension"))
return res
4 changes: 2 additions & 2 deletions fhirpathpy/engine/invocations/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def to_date_time(ctx, coll):
dateTimeObject = nodes.FP_DateTime(value)

if dateTimeObject:
rtn[0] = dateTimeObject
rtn.append(dateTimeObject)

return rtn

Expand All @@ -113,6 +113,6 @@ def to_time(ctx, coll):
timeObject = nodes.FP_Time(value)

if timeObject:
rtn[0] = timeObject
rtn.append(timeObject)

return rtn
108 changes: 108 additions & 0 deletions tests/cases/extensions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
tests:
# https://www.hl7.org/fhir/fhirpath.html#types
- 'group: Extension and id for primitive types':

- desc: '** id for primitive type'
expression: Functions.attrtrue.id = 'someid'
result:
- true

- desc: '** expression with extension for primitive type 1'
inputfile: patient-example.json
expression: Patient.birthDate.extension.where(url = '').empty()
result:
- true

- desc: '** expression with extension for primitive type 2'
inputfile: patient-example.json
expression: >-
Patient.birthDate.extension
.where(url = 'http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.valueDateTime.toDateTime() = @1974-12-25T14:35:45-05:00
result:
- true

- desc: '** expression with extension for primitive type 3'
inputfile: patient-example.json
model: r4
expression: >-
Patient.birthDate.extension
.where(url = 'http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.value = @1974-12-25T14:35:45-05:00
result:
- true

# https://www.hl7.org/fhir/fhirpath.html#functions
- 'group: Additional functions':
- desc: 'extension(url : string) : collection'

# If the url is empty ({ }), the result is empty.
- desc: '** empty url'
inputfile: patient-example.json
expression: Patient.birthDate.extension('').empty()
result:
- true

# If the input collection is empty ({ }), the result is empty.
- desc: '** empty input collection'
inputfile: patient-example.json
expression: >-
Patient.birthDate1
.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime').empty()
result:
- true

- desc: '** expression with extension() for primitive type (without using FHIR model data)'
inputfile: patient-example.json
expression: >-
Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.valueDateTime.toDateTime() = @1974-12-25T14:35:45-05:00
result:
- true

- desc: '** expression with extension() for primitive type (without using FHIR model data) when only extension is present'
inputfile: patient-example-2.json
expression: >-
Patient.communication.preferred.extension('test').exists()
result:
- true

- desc: '** expression with extension() for primitive type (using FHIR model data) when only extension is present'
inputfile: patient-example-2.json
model: r4
expression: >-
Patient.communication.preferred.extension('test').value.id
result:
- testing

- desc: '** expression with extension() for primitive type (using FHIR model data)'
inputfile: patient-example.json
model: r4
expression: >-
Patient.birthDate.extension('http://hl7.org/fhir/StructureDefinition/patient-birthTime')
.value = @1974-12-25T14:35:45-05:00
result:
- true

- desc: '** value of extension of extension (using FHIR model data)'
model: r4
expression: Functions.attrtrue.extension('url1').extension('url2').value = 'someuri'
result:
- true

- desc: '** id of extension of extension'
expression: Functions.attrtrue.extension('url1').extension('url2').id = 'someid2'
result:
- true

subject:
resourceType: Functions
attrtrue: true
_attrtrue:
id: someid
extension:
- url: url1
extension:
- url: url2
id: someid2
valueUri: someuri
1 change: 1 addition & 0 deletions tests/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def save_to_resources(resources, resource_filename):

save_to_resources(resources, "observation-example.json")
save_to_resources(resources, "patient-example.json")
save_to_resources(resources, "patient-example-2.json")
save_to_resources(resources, "quantity-example.json")
save_to_resources(resources, "questionnaire-example.json")
save_to_resources(resources, "valueset-example-expansion.json")
26 changes: 26 additions & 0 deletions tests/resources/patient-example-2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"resourceType": "Patient",
"communication": [
{
"language": {
"coding": [
{
"system": "urn:ietf:bcp:47",
"code": "nl",
"display": "Dutch"
}
]
},
"_preferred": {
"extension": [
{
"url": "test",
"_valueString": {
"id": "testing"
}
}
]
}
}
]
}
36 changes: 34 additions & 2 deletions tests/test_real.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,37 @@ def pickle_test():
assert reload(resource) == ["Patient/cdf"]




def extension_test():
patient = {
"identifier": [
{
"period": {
"start": "2020-01-01"
},
"system": "http://hl7.org/fhir/sid/us-mbi",
"type": {
"coding": [
{
"code": "MC",
"display": "Patient's Medicare number",
"extension": [
{
"url": "https://bluebutton.cms.gov/resources/codesystem/identifier-currency",
"valueCoding": {
"code": "current",
"display": "Current",
"system": "https://bluebutton.cms.gov/resources/codesystem/identifier-currency"
}
}
],
"system": "http://terminology.hl7.org/CodeSystem/v2-0203"
}
]
},
"value": "7SM0A00AA00"
}
],
"resourceType": "Patient"
}
result = evaluate(patient, "Patient.identifier.where(type.coding.extension('https://bluebutton.cms.gov/resources/codesystem/identifier-currency').valueCoding.code = 'current').where(system = 'http://hl7.org/fhir/sid/us-mbi').value")
assert result == ["7SM0A00AA00"]