Skip to content

Commit

Permalink
Work-around for issue #4.
Browse files Browse the repository at this point in the history
Adds a secondary filter to `rdf_compare`, a filter to skip decimal precision
differences to `base_test_case.py` and invokes the skip in test_all_fhir_elements.

The basic problem is now focused exclusively in test_issue4.
  • Loading branch information
hsolbrig committed Sep 16, 2017
1 parent 26d6b8a commit 6c2a396
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 6 deletions.
8 changes: 6 additions & 2 deletions fhirtordf/rdfsupport/rdfcompare.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
from typing import Optional, List, Set
from typing import Optional, List, Set, Callable

from rdflib import URIRef, Graph, OWL, RDF, BNode
from rdflib.compare import graph_diff
Expand Down Expand Up @@ -114,13 +114,15 @@ def dump_nt_sorted(g: Graph) -> List[str]:
return [l.decode('ascii') for l in sorted(g.serialize(format='nt').splitlines()) if l]


def rdf_compare(g1: Graph, g2: Graph, ignore_owl_version: bool=False, ignore_type_arcs: bool = False) -> str:
def rdf_compare(g1: Graph, g2: Graph, ignore_owl_version: bool=False, ignore_type_arcs: bool = False,
compare_filter: Optional[Callable[[Graph, Graph, Graph], None]]=None) -> str:
"""
Compare graph g1 and g2
:param g1: first graph
:param g2: second graph
:param ignore_owl_version:
:param ignore_type_arcs:
:param compare_filter: Final adjustment for graph difference. Used, for example, to deal with FHIR decimal problems.
:return: List of differences as printable lines or blank if everything matches
"""
def graph_for_subject(g: Graph, subj: Node) -> Graph:
Expand Down Expand Up @@ -155,6 +157,8 @@ def primary_subjects(g: Graph) -> Set[Node]:
s_in_g1 = graph_for_subject(g1, s)
s_in_g2 = graph_for_subject(g2, s)
in_both, in_first, in_second = graph_diff(skolemize(s_in_g1), skolemize(s_in_g2))
if compare_filter:
compare_filter(in_both, in_first, in_second)
if len(list(in_first)) or len(list(in_second)):
rval += "\n\nSubject {} DIFFERENCE: ".format(s) + '=' * 30
if len(in_first):
Expand Down
10 changes: 7 additions & 3 deletions tests/fhir_tests/test_all_fhir_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from fhirtordf.fhir.fhirmetavoc import FHIRMetaVocEntry
from fhirtordf.loaders.fhirresourceloader import FHIRResource
from fhirtordf.rdfsupport.rdfcompare import rdf_compare
from tests.utils.base_test_case import make_and_clear_directory
from tests.utils.base_test_case import make_and_clear_directory, fhir_decimal_issue_filter
from tests.utils.build_test_harness import ValidationTestCase


Expand Down Expand Up @@ -61,7 +61,7 @@ def setUpClass(cls):
and '.schema.' not in fn and '.diff.' not in fn
FHIRInstanceTestCase.base_dir = 'http://hl7.org/fhir'
FHIRInstanceTestCase.max_size = 20 # maximum file size in kb
# FHIRInstanceTestCase.start_at = "valueset-example-intensional"
# FHIRInstanceTestCase.start_at = "imagingstudy-example"


# Comparing to FHIR, so make certain we're doing FHIR dates
Expand All @@ -76,7 +76,11 @@ def json_to_ttl(self: FHIRInstanceTestCase, dirpath: str, fname: str) -> bool:
if os.path.exists(test_ttl_fname):
target = Graph()
target.load(test_ttl_fname, format="turtle")
diffs = rdf_compare(target, source.graph, ignore_owl_version=True, ignore_type_arcs=True)
diffs_nc = rdf_compare(target, source.graph, ignore_owl_version=True, ignore_type_arcs=True)
diffs = rdf_compare(target, source.graph, ignore_owl_version=True, ignore_type_arcs=True,
compare_filter=fhir_decimal_issue_filter)
if diffs_nc and not diffs:
print("---> {} has decimal precision issues".format(fname))
if diffs:
with open(os.path.join(self.output_directory, turtle_fname), 'w') as f:
f.write(diffs)
Expand Down
160 changes: 160 additions & 0 deletions tests/issue_tests/test_issue2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Copyright (c) 2017, Mayo Clinic
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of the Mayo Clinic nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.

import unittest

import os
from jsonasobj import loads
from rdflib import Graph

from fhirtordf.rdfsupport.rdfcompare import rdf_compare
from tests.utils.base_test_case import FHIRGraph

data = """{
"resourceType": "Bundle",
"id": "b248b1b2-1686-4b94-9936-37d7a5f94b51",
"meta": {
"lastUpdated": "2012-05-29T23:45:32Z"
},
"type": "collection",
"entry": [
{
"fullUrl": "http://hl7.org/fhir/Patient/1",
"resource": {
"resourceType": "Patient",
"id": "1",
"meta": {
"lastUpdated": "2012-05-29T23:45:32Z"
},
"text": {
"status": "generated",
"div": ""
},
"identifier": [
{
"type": {
"coding": [
{
"system": "http://hl7.org/fhir/v2/0203",
"code": "SS"
}
]
},
"system": "http://hl7.org/fhir/sid/us-ssn",
"value": "444222222"
}
]
}
}
]
}"""

expected = """@prefix fhir: <http://hl7.org/fhir/> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix v2-0203: <http://hl7.org/fhir/v2/0203> .
@prefix w5: <http://hl7.org/fhir/w5#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<http://hl7.org/fhir/Bundle/b248b1b2-1686-4b94-9936-37d7a5f94b51> a fhir:Bundle ;
fhir:nodeRole fhir:treeRoot ;
fhir:Bundle.entry [
fhir:Bundle.entry.fullUrl [
fhir:value "http://hl7.org/fhir/Patient/1"
] ;
fhir:Bundle.entry.resource <http://hl7.org/fhir/Patient/1> ;
fhir:index "0"^^xsd:integer
] ;
fhir:Bundle.type [
fhir:value "collection"
] ;
fhir:Resource.id [
fhir:value "b248b1b2-1686-4b94-9936-37d7a5f94b51"
] ;
fhir:Resource.meta [
fhir:Meta.lastUpdated [
fhir:value "2012-05-29T23:45:32+00:00"^^xsd:dateTime
]
] .
<http://hl7.org/fhir/Bundle/b248b1b2-1686-4b94-9936-37d7a5f94b51.ttl> a owl:Ontology ;
owl:imports fhir:fhir.ttl .
<http://hl7.org/fhir/Patient/1> a fhir:Patient ;
fhir:DomainResource.text [
fhir:Narrative.div "" ;
fhir:Narrative.status [
fhir:value "generated"
]
] ;
fhir:Patient.identifier [
fhir:index "0"^^xsd:integer ;
fhir:Identifier.system [
fhir:value "http://hl7.org/fhir/sid/us-ssn"
] ;
fhir:Identifier.type [
fhir:CodeableConcept.coding [
a <http://hl7.org/fhir/v2/0203/SS> ;
fhir:index "0"^^xsd:integer ;
fhir:Coding.code [
fhir:value "SS"
] ;
fhir:Coding.system [
fhir:value "http://hl7.org/fhir/v2/0203"
]
]
] ;
fhir:Identifier.value [
fhir:value "444222222"
]
] ;
fhir:Resource.id [
fhir:value "1"
] ;
fhir:Resource.meta [
fhir:Meta.lastUpdated [
fhir:value "2012-05-29T23:45:32+00:00"^^xsd:dateTime
]
] .
"""


class Issue2TestCase(unittest.TestCase):
def test_reference(self):
test_json = loads(data)
from fhirtordf.loaders.fhirresourceloader import FHIRResource
test_rdf = FHIRResource(FHIRGraph(), None, "http://hl7.org/fhir", test_json)
g = test_rdf.graph
expected_graph = Graph()
diffs = rdf_compare(expected_graph, test_rdf.graph, ignore_owl_version=True, ignore_type_arcs=True)
self.assertEqual("", diffs)

if __name__ == '__main__':
unittest.main()
85 changes: 85 additions & 0 deletions tests/issue_tests/test_issue4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright (c) 2017, Mayo Clinic
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# Neither the name of the Mayo Clinic nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.

import unittest

from jsonasobj import loads
from rdflib import URIRef, Literal, XSD

from fhirtordf.rdfsupport.namespaces import FHIR
from tests.utils.base_test_case import FHIRGraph

json_data = """{
"resourceType": "VisionPrescription",
"id": "33123",
"text": {
"status": "generated",
"div": "(cut)"
},
"dispense": [
{
"product": {
"coding": [
{
"system": "http://hl7.org/fhir/ex-visionprescriptionproduct",
"code": "lens"
}
]
},
"eye": "right",
"sphere": -2.00,
"prism": 0.5,
"base": "down",
"add": 2.00
}
]
}
"""


class Issue4TestCase(unittest.TestCase):
def test_decimal(self):
test_json = loads(json_data)
from fhirtordf.loaders.fhirresourceloader import FHIRResource
test_rdf = FHIRResource(FHIRGraph(), None, "http://hl7.org/fhir", test_json)
g = test_rdf.graph
# rdflib supports decimal precision if you create the data as a string.
self.assertNotEqual(Literal("2.00", datatype=XSD.decimal), Literal("2.0", datatype=XSD.decimal))
self.assertEqual(Literal(2.00), Literal(2.0))

# FHIR requires that *all* decimals use the first form. While the diopter example above
# would work, issues arise in situations where numbers really ARE decimal...
self.assertEqual(Literal("2.00", datatype=XSD.decimal),
g.value(
g.value(
g.value(URIRef("http://hl7.org/fhir/VisionPrescription/33123"),
FHIR.VisionPrescription.dispense),
FHIR.VisionPrescription.dispense.add), FHIR.value))

if __name__ == '__main__':
unittest.main()
17 changes: 16 additions & 1 deletion tests/utils/base_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from typing import Union

from dateutil.parser import parse
from rdflib import Graph
from rdflib import Graph, Literal, XSD

test_directory = os.path.join(os.path.split(os.path.abspath(__file__))[0], '..')
test_data_directory = os.path.join(test_directory, 'data')
Expand Down Expand Up @@ -86,3 +86,18 @@ def make_and_clear_directory(directory: str):
os.makedirs(directory)
with open(safety_file, "w") as f:
f.write("Generated for safety. Must be present for test to clear this directory.")


def fhir_decimal_issue_filter(in_both: Graph, in_first: Graph, in_second: Graph) -> None:
""" FHIR currently requires a non-standard JSON parser that can differentiate between '"x": 1.0' and '"x": 1.00'
The filter below treats RDF representation of both as the same, and is used to make decimal values
pass unit tests
"""
for s, p, o in list(in_first):
o_2 = in_second.value(s, p)
if o_2 and isinstance(o_2, Literal) and o_2.datatype == XSD.decimal and \
isinstance(o, Literal) and o.datatype == XSD.decimal:
if o.value == o_2.value:
in_both.add((s, p, o))
in_first.remove((s, p, o))
in_second.remove((s, p, o_2))

0 comments on commit 6c2a396

Please sign in to comment.