diff --git a/dataset2rdf/utils.py b/dataset2rdf/utils.py index 9646f7257f3236e6f86a17198da5f9374e77ec16..8be08f0f2f6480ce13156cab1e267882666c1ad2 100644 --- a/dataset2rdf/utils.py +++ b/dataset2rdf/utils.py @@ -795,38 +795,40 @@ def parse_active_composed_ofs( ) elif 'restricted to:' in valueset: #valueset = valueset.replace(",", ";") - path_str, elements_str = valueset.split("restricted to:") - property_path = [iri_header + format_property_name(x) for x in path_str.strip().split("->")] - elements = [x.strip() for x in elements_str.split(";")] - parsed_elements = [] - for_standard = None - scope = None - for element in elements: - if standard in {'UCUM'}: - element = parse_element(element) - if element.startswith("for "): - for_standard = extract_code(element, REGEX_MAP["for_standard"]) - if "descendant of:" in element: - scope = "descendant of: " - elif "child of:" in element: - scope = "child of: " - if for_standard and for_standard not in element: - element = f"for {for_standard}: {scope if scope and scope not in element else ''}{element}" - parsed_elements.append(element) - else: - element = f"{scope if scope and scope not in element else ''}{element}" - parsed_elements.append(element) + restricted_to_expressions = parse_restricted_to_expression(valueset) + for expression in restricted_to_expressions: + path_str, elements_str = expression.split("restricted to:") + property_path = [iri_header + format_property_name(x) for x in path_str.strip().split("->")] + elements = [x.strip() for x in elements_str.split(";")] + parsed_elements = [] + for_standard = None + scope = None + for element in elements: + if standard in {'UCUM'}: + element = parse_element(element) + if element.startswith("for "): + for_standard = extract_code(element, REGEX_MAP["for_standard"]) + if "descendant of:" in element: + scope = "descendant of: " + elif "child of:" in element: + scope = "child of: " + if for_standard and for_standard not in element: + element = f"for {for_standard}: {scope if scope and scope not in element else ''}{element}" + parsed_elements.append(element) + else: + element = f"{scope if scope and scope not in element else ''}{element}" + parsed_elements.append(element) - parse_standard_as_restriction( - concept=concept, - prop=prop, - standard=standard, - valueset=";".join(parsed_elements), - property_path=property_path, - prefix=prefix.lower(), - add_info=add_info, - config=config - ) + parse_standard_as_restriction( + concept=concept, + prop=prop, + standard=standard, + valueset=";".join(parsed_elements), + property_path=property_path, + prefix=prefix.lower(), + add_info=add_info, + config=config + ) elif valueset.lower().startswith("unit:"): # UCUM code restriction on prop -> hasUnit -> hasCode @@ -1901,6 +1903,8 @@ def add_code_as_restriction( """ logger_utils.debug("adding code as restriction: " + str(codes) +" for proppath: " + str(prop_path) + " and prop " + str(prop)) r_key = f"{concept.identifier}-{prop.identifier}" + if prop_path: + r_key = f"{r_key}-{'-'.join(prop_path)}" if r_key in concept.restrictions: restriction = concept.restrictions[r_key] else: @@ -2235,3 +2239,31 @@ def compare_composed_of_attributes(dataset: pd.DataFrame, upstream_record: Dict, e = f"Type '{composed_of_type}' for composedOf '{name}' is not the same as the parent composedOf '{upstream_record[COLUMN_MAP['name']]}' with type '{parent_composed_of_type}'" errors.append(e) return errors + + +def parse_restricted_to_expression(valueset: str) -> List: + """ + Parse a given valueset string and check if there are 'restricted to' expressions. + If so, then split the string at the right delimiter and return a list where each + element is a 'restricted to' expression. + + Args: + valueset: The valueset string + + Return: + A list of 'restricted to' expressions + + """ + keywords = {'descendant of', 'child of', 'for '} + prepared_valueset = [] + prepared_value = None + for value in valueset.split(";"): + if 'restricted to' in value: + # First property path restriction + if prepared_value: + prepared_valueset.append(prepared_value) + prepared_value = value + elif any(x not in value for x in keywords): + prepared_value += f";{value}" + prepared_valueset.append(prepared_value) + return prepared_valueset \ No newline at end of file diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d7a0acb652974da2dc606e53ade865644ed896cc..093fef959f4ece197b059af04a6570f90a84c901 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -710,7 +710,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP8", "label": "has p8", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP8": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP8-https://biomedit.ch/rdf/sphn-ontology/sphn#hasUnit-https://biomedit.ch/rdf/sphn-ontology/sphn#hasCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP8", "some_values_from": [], @@ -746,7 +746,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP9", "label": "has p9", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP9": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP9-https://biomedit.ch/rdf/sphn-ontology/sphn#hasUnit-https://biomedit.ch/rdf/sphn-ontology/sphn#hasCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP9", "some_values_from": [], @@ -1089,7 +1089,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasUnit-https://biomedit.ch/rdf/sphn-ontology/sphn#hasCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1133,7 +1133,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasUnit-https://biomedit.ch/rdf/sphn-ontology/sphn#hasCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1175,7 +1175,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasMedicalDevice-https://biomedit.ch/rdf/sphn-ontology/sphn#hasTypeCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1216,7 +1216,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasMedicalDevice-https://biomedit.ch/rdf/sphn-ontology/sphn#hasTypeCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1230,6 +1230,10 @@ def test_parse_active_concept(record, expected): "https://biomedit.ch/rdf/sphn-resource/chop/43.3" ], "scope_notes": {'sphn:hasP18 no subclasses allowed'}, + }, + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", + "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "notes": {'sphn:hasP18 allowed coding system: SNOMED CT, CHOP'} } } @@ -1260,7 +1264,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasMedicalDevice-https://biomedit.ch/rdf/sphn-ontology/sphn#hasTypeCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1272,10 +1276,16 @@ def test_parse_active_concept(record, expected): "http://snomed.info/id/45353", "https://biomedit.ch/rdf/sphn-resource/chop/23.1", "https://biomedit.ch/rdf/sphn-resource/chop/43.3", + ], + "scope_notes": {'sphn:hasP18 no subclasses allowed'}, + }, + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", + "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", + "some_values_from": [ "https://biomedit.ch/rdf/sphn-ontology/sphn#Code", "https://biomedit.ch/rdf/sphn-ontology/sphn#Terminology" ], - "scope_notes": {'sphn:hasP18 no subclasses allowed'}, "notes": {'sphn:hasP18 allowed coding system: SNOMED CT or other'} } } @@ -1306,7 +1316,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasMedicalDevice-https://biomedit.ch/rdf/sphn-ontology/sphn#hasTypeCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1347,7 +1357,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasMedicalDevice-https://biomedit.ch/rdf/sphn-ontology/sphn#hasTypeCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1389,7 +1399,7 @@ def test_parse_active_concept(record, expected): "identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "label": "has p18", "restrictions": { - "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18": { + "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB-https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18-https://biomedit.ch/rdf/sphn-ontology/sphn#hasMedicalDevice-https://biomedit.ch/rdf/sphn-ontology/sphn#hasTypeCode": { "class_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#ConceptB", "property_identifier": "https://biomedit.ch/rdf/sphn-ontology/sphn#hasP18", "property_path": [ @@ -1432,6 +1442,7 @@ def test_parse_active_composed_ofs(record, expected_properties, expected_individ concept = prop.domain[0] if expected_properties["restrictions"]: assert concept.restrictions + print(f">>> CONCEPT RESTRICTIONS:\n{concept.restrictions}") for k,v in expected_properties['restrictions'].items(): assert k in concept.restrictions