From 90a2668f08786b90b60e39c416859615a50002bd Mon Sep 17 00:00:00 2001 From: Joe Flack Date: Thu, 30 Jun 2022 19:40:25 -0400 Subject: [PATCH] FHIR Feature - Added: schema/fhir_json.csv: For automation of conversion by utilizing CSV export from curated GoogleSheet. - Update: Comments: Added state of mappings between SSSOM and FHIR ConceptMap --- schema/fhir_json.csv | 169 +++++++++++++++++++++++++++ sssom/writers.py | 267 ++++++++++++++++++++++++++----------------- 2 files changed, 334 insertions(+), 102 deletions(-) create mode 100644 schema/fhir_json.csv diff --git a/schema/fhir_json.csv b/schema/fhir_json.csv new file mode 100644 index 00000000..6a2cc113 --- /dev/null +++ b/schema/fhir_json.csv @@ -0,0 +1,169 @@ +spec:SSSOM,spec:R4,spec:R5 +MappingSet.comment,extension,extension +MappingSet.creator_id,identifier.assigner.reference,identifier.assigner.reference +MappingSet.creator_label,identifier.assigner.display,identifier.assigner.display +MappingSet.creator_label,publisher,publisher +MappingSet.creator_label,contact.name,contact.name +MappingSet.license,copyright,copyright +MappingSet.mapping_date,date,date +MappingSet.mapping_date,identifier.period.start,identifier.period.start +MappingSet.mapping_date,.,approvalDate +MappingSet.mapping_date,.,lastReviewDate +MappingSet.mapping_date,.,effectivePeriod.start +.,.,effectivePeriod.end +MappingSet.mapping_provider,extension,extension +MappingSet.mapping_set_description,description,description +MappingSet.mapping_set_id,url,url +MappingSet.mapping_set_id,title,title +MappingSet.mapping_set_id,name,name +MappingSet.mapping_set_id,identifier.value,identifier.value +MappingSet.mapping_set_source,extension,extension +MappingSet.mapping_set_version,version,version +MappingSet.mapping_tool,extension,extension +MappingSet.mappings,.,. +MappingSet.object_match_field,extension,extension +MappingSet.object_preprocessing,extension,extension +MappingSet.object_source,targetCanonical,targetScopeCanonical +MappingSet.object_source,targetUri,targetScopeUri +MappingSet.object_source,group.target,group.target +MappingSet.object_source_version,group.targetVersion,extension +MappingSet.object_type,extension,extension +MappingSet.other,extension,extension +MappingSet.see_also,extension,extension +MappingSet.subject_match_field,extension,extension +MappingSet.subject_preprocessing,extension,extension +MappingSet.subject_source,sourceCanonical,sourceScopeCanonical +MappingSet.subject_source,sourceUri,sourceScopeUri +MappingSet.subject_source,group.source,group.source +MappingSet.subject_source_version,group.sourceVersion,extension +Mapping.subject_type,group.element.target.extension,group.element.target.extension +Mapping.author_id,group.element.target.extension,group.element.target.extension +Mapping.author_label,group.element.target.extension,group.element.target.extension +Mapping.comment,group.element.target.comment,group.element.target.comment +Mapping.confidence,group.element.target.extension,group.element.target.extension +Mapping.creator_id,group.element.target.extension,group.element.target.extension +Mapping.creator_label,group.element.target.extension,group.element.target.extension +Mapping.license,group.element.target.extension,group.element.target.extension +Mapping.mapping_cardinality,group.element.target.extension,group.element.target.extension +Mapping.mapping_date,group.element.target.extension,group.element.target.extension +Mapping.mapping_justification,group.element.target.extension,group.element.target.extension +Mapping.mapping_provider,group.element.target.extension,group.element.target.extension +Mapping.mapping_tool,group.element.target.extension,group.element.target.extension +Mapping.mapping_tool_version,group.element.target.extension,group.element.target.extension +Mapping.match_string,group.element.target.extension,group.element.target.extension +Mapping.object_category,group.element.target.extension,group.element.target.extension +Mapping.object_id,group.element.target.code,group.element.target.code +Mapping.object_label,group.element.target.display,group.element.target.display +Mapping.object_match_field,group.element.target.extension,group.element.target.extension +Mapping.object_preprocessing,group.element.target.extension,group.element.target.extension +Mapping.object_source,group.element.target.extension,group.element.target.extension +Mapping.object_source_version,group.element.target.extension,group.element.target.extension +Mapping.object_type,group.element.target.extension,group.element.target.extension +Mapping.other,group.element.target.extension,group.element.target.extension +Mapping.predicate_id,group.element.target.equivalence,group.element.target.relationship +.,.,group.element.target.valueSet +Mapping.predicate_id,group.element.target.dependsOn.value,group.element.target.dependsOn.valueCode +Mapping.predicate_id,group.element.target.dependsOn.property,group.element.target.dependsOn.property +.,.,group.element.target.dependsOn.valueSet +Mapping.predicate_id,group.element.target.dependsOn.system,. +.,group.element.target.product,group.element.target.product +Mapping.predicate_label,group.element.target.dependsOn.display,group.element.target.dependsOn.valueString +Mapping.predicate_modifier,.,. +Mapping.reviewer_id,group.element.target.extension,group.element.target.extension +Mapping.reviewer_label,group.element.target.extension,group.element.target.extension +Mapping.see_also,group.element.target.extension,group.element.target.extension +Mapping.semantic_similarity_measure,group.element.target.extension,group.element.target.extension +Mapping.semantic_similarity_score,group.element.target.extension,group.element.target.extension +Mapping.subject_category,group.element.target.extension,group.element.target.extension +Mapping.subject_id,group.element.code,group.element.code +Mapping.subject_label,group.element.display,group.element.display +Mapping.subject_match_field,group.element.target.extension,group.element.target.extension +Mapping.subject_preprocessing,group.element.target.extension,group.element.target.extension +Mapping.subject_source,group.element.target.extension,group.element.target.extension +Mapping.subject_source_version,group.element.target.extension,group.element.target.extension +Mapping.subject_type,group.element.target.extension,group.element.target.extension +.,identifier.period.end,identifier.period.end +MappingSet.mapping_set_id,identifier.system,identifier.system +.,identifier.use,identifier.use +?,identifier.type.text,identifier.type.text +?,identifier.type.coding.system,identifier.type.coding.system +?,identifier.type.coding.version,identifier.type.coding.version +?,identifier.type.coding.code,identifier.type.coding.code +?,identifier.type.coding.display,identifier.type.coding.display +.,identifier.type.coding.userSelected,identifier.type.coding.userSelected +.,identifier.assigner.type,identifier.assigner.type +.,identifier.assigner.identifier,identifier.assigner.reference.identifier +.,status,status +.,experimental,experimental +.,contact.telecom.system,contact.telecom.system +.,contact.telecom.value,contact.telecom.value +.,contact.telecom.use,contact.telecom.use +.,contact.telecom.rank,contact.telecom.rank +.,contact.telecom.period.start,contact.telecom.period.start +.,contact.telecom.period.end,contact.telecom.period.end +.,purpose,purpose +.,.,topic +?,.,author.name +.,.,author.contact.telecom.system +.,.,author.contact.telecom.value +.,.,author.contact.telecom.use +.,.,author.contact.telecom.rank +.,.,author.contact.telecom.period.start +.,.,author.contact.telecom.period.end +?,.,editor.name +.,.,editor.contact.telecom.system +.,.,editor.contact.telecom.value +.,.,editor.contact.telecom.use +.,.,editor.contact.telecom.rank +.,.,editor.contact.telecom.period.start +.,.,editor.contact.telecom.period.end +?,.,reviewer.name +.,.,reviewer.contact.telecom.system +.,.,reviewer.contact.telecom.value +.,.,reviewer.contact.telecom.use +.,.,reviewer.contact.telecom.rank +.,.,reviewer.contact.telecom.period.start +.,.,reviewer.contact.telecom.period.end +?,.,endorser.name +.,.,endorser.contact.telecom.system +.,.,endorser.contact.telecom.value +.,.,endorser.contact.telecom.use +.,.,endorser.contact.telecom.rank +.,.,endorser.contact.telecom.period.start +.,.,endorser.contact.telecom.period.end +?,.,releasedArtifact.type +.,.,releasedArtifact.label +MappingSet.mapping_set_description,.,releasedArtifact.display +?,.,releasedArtifact.citation +.,.,releasedArtifact.classifier.code +.,.,releasedArtifact.classifier.display +.,.,releasedArtifact.classifier.definition +.,.,releasedArtifact.document.contentType +.,.,releasedArtifact.document.language +.,.,releasedArtifact.document.data +MappingSet.mapping_set_id,.,releasedArtifact.document.url +.,.,releasedArtifact.document.size +.,.,releasedArtifact.document.hash +MappingSet.mapping_set_id,.,releasedArtifact.document.title +.,.,releasedArtifact.document.creation +.,.,releasedArtifact.document.height +.,.,releasedArtifact.document.width +.,.,releasedArtifact.document.frames +.,.,releasedArtifact.document.duration +.,.,releasedArtifact.document.pages +.,.,releasedArtifact.resource +.,.,releasedArtifact.resourceReference.reference +.,.,releasedArtifact.resourceReference.type +MappingSet.mapping_set_id,.,releasedArtifact.resourceReference.display +.,.,releasedArtifact.resourceReference.identifier +.,group.element.noMap,. +.,group.element.valueSet,. +.,group.element.target.unmapped.mode,group.element.target.unmapped.mode +Mapping.subject_id,group.element.target.unmapped.code,group.element.target.unmapped.code +Mapping.subject_label,group.element.target.unmapped.display,group.element.target.unmapped.display +.,.,group.element.target.unmapped.valueSet +Mapping.predicate_id,.,group.element.target.unmapped.relationship +.,group.element.target.unmapped.url,group.element.target.unmapped.otherMap +.,useContext.code,useContext.code +.,useContext.valueCodeableConcept,useContext.valueCodeableConcept +.,jurisdiction,jurisdiction \ No newline at end of file diff --git a/sssom/writers.py b/sssom/writers.py index ec0fa14e..23f23924 100644 --- a/sssom/writers.py +++ b/sssom/writers.py @@ -290,6 +290,7 @@ def to_rdf_graph(msdf: MappingSetDataFrame) -> Graph: return graph +# TODO: add to CLI & to these functions: r4 vs r5 param def to_fhir_json(msdf: MappingSetDataFrame) -> Dict: """Convert a mapping set dataframe to a JSON object. @@ -300,13 +301,75 @@ def to_fhir_json(msdf: MappingSetDataFrame) -> Dict: - ConcpetMap::SSSOM mapping spreadsheet: https://docs.google.com/spreadsheets/d/1J19foBAYO8PCHwOfksaIGjNu-q5ILUKFh2HpOCgYle0/edit#gid=1389897118 TODOs - todo: when/how to conform to R5 instead of R4?: https://build.fhir.org/conceptmap.html - TODO: Add additional fields from both specs + todo: when/how to conform to R5 instead of R4?: https://build.fhir.org/conceptmap.html + TODO: Add additional fields from both specs - ConceptMap spec fields: https://www.hl7.org/fhir/r4/conceptmap.html - Joe: Can also utilize: /Users/joeflack4/projects/hapi-fhir-jpaserver-starter/_archive/issues/sssom/example_json/minimal.json - - SSSOM more fields: - - prefix_map - - SSSOM spec fields: https://mapping-commons.github.io/sssom/Mapping/ + - SSSOM https://mapping-commons.github.io/sssom/Mapping/ + author_id,? + author_label,? + comment,group.element.target.comment + confidence,? + creator_id,?,?,See: #1 + creator_label,?,?,See: #1 + license,copyright~,?,#1: If there is any variation for any records in a MappingSet, this may need to be a group.element.target.extension + mapping_cardinality,? + mapping_date,date~,?,See: #1 + mapping_justification,?,group.element.target.extension + mapping_provider,?,?,See: #1 + mapping_tool,?,?,See: #1 + mapping_tool_version,? + match_string,? + object_category,? + object_id,group.element.target.code + object_label,group.element.target.display + object_match_field,?,?,See: #1 + object_preprocessing,?,?,See: #1 + object_source,targetUri;group.target,?,See: #1 + object_source_version,?,?,See: #1 + object_type,?,?,See: #1 + other,?,?,See: #1 + predicate_id,group.element.target.equivalence + predicate_label,? + predicate_modifier,n/a?,n/a?,It is either the case that (a) this will modify predicate_id and thus be mapped to group.element.target.equivalence, or (b) there may be some cases where the predicate_id + modifier is not mappable to anything in 'equivalence'. + reviewer_id,? + reviewer_label,? + see_also,?,?,See: #1 + semantic_similarity_measure,? + semantic_similarity_score,? + subject_category,? + subject_id,group.element.code + subject_label,group.element.display + subject_match_field,?,?,See: #1 + subject_preprocessing,?,?,See: #1 + subject_source,sourceUri;group.source~,?,See: #1 + subject_source_version,?,?,See: #1 + subject_type,?,?,See: #1 + - SSSOM https://mapping-commons.github.io/sssom/MappingSet/ + comment,? + creator_id,? + creator_label,? + license,copyright + mapping_date,date + mapping_provider,? + mapping_set_description,? + mapping_set_id,url + mapping_set_source,? + mapping_set_version,? + mapping_tool,? + mappings,? + object_match_field,? + object_preprocessing,? + object_source,? + object_source_version,? + object_type,? + other,? + see_also,? + subject_match_field,? + subject_preprocessing,? + subject_source,sourceUri;group.source + subject_source_version,? + subject_type,? """ df: pd.DataFrame = msdf.df # Intermediary variables @@ -328,7 +391,7 @@ def to_fhir_json(msdf: MappingSetDataFrame) -> Dict: ], "version": metadata.get("mapping_set_version", ""), "name": name, - "title": name, + "title": name, # TODO -> mapping_set_description? "status": "draft", # todo: when done: draft | active | retired | unknown "experimental": True, # todo: False when converter finished # todo: should this be date of last converted to FHIR json instead? @@ -355,102 +418,102 @@ def to_fhir_json(msdf: MappingSetDataFrame) -> Dict: # }], # "purpose": "", # todo: conceptmap "copyright": metadata.get("license", ""), - "sourceUri": metadata.get("subject_source", ""), # todo: correct? - "targetUri": metadata.get("object_source", ""), # todo: correct? - "group": [ - { - "source": metadata.get("subject_source", ""), # todo: correct? - "target": metadata.get("object_source", ""), # todo: correct? - "element": [ - { - "code": row["subject_id"], - "display": row.get("subject_label", ""), - "target": [ - { - "code": row["object_id"], - "display": row.get("object_label", ""), - # TODO: R4 (try this first) - # relatedto | equivalent | equal | wider | subsumes | narrower | specializes | inexact | unmatched | disjoint - # https://www.hl7.org/fhir/r4/conceptmap.html - # todo: r4: if not found, eventually needs to be `null` or something. check docs to see if nullable, else ask on Zulip - # TODO: R5 Needs to be one of: - # related-to | equivalent | source-is-narrower-than-target | source-is-broader-than-target | not-related-to - # https://www.hl7.org/fhir/r4/valueset-concept-map-equivalence.html - # ill update that next time. i can map SSSOM SKOS/etc mappings to FHIR ones - # and then add the original SSSOM mapping CURIE fields somewhere else - # https://www.hl7.org/fhir/valueset-concept-map-equivalence.html - # https://github.com/mapping-commons/sssom-py/issues/258 - "equivalence": { - # relateedto: The concepts are related to each other, and have at least some overlap - # in meaning, but the exact relationship is not known. - "skos:related": "relatedto", - "skos:relatedMatch": "relatedto", # canonical - # equivalent: The definitions of the concepts mean the same thing (including when - # structural implications of meaning are considered) (i.e. extensionally identical). - "skos:exactMatch": "equivalent", - # equal: The definitions of the concepts are exactly the same (i.e. only grammatical - # differences) and structural implications of meaning are identical or irrelevant - # (i.e. intentionally identical). - "equal": "equal", # todo what's difference between this and above? which to use? - # wider: The target mapping is wider in meaning than the source concept. - "skos:broader": "wider", - "skos:broadMatch": "wider", # canonical - # subsumes: The target mapping subsumes the meaning of the source concept (e.g. the - # source is-a target). - "rdfs:subClassOf": "subsumes", - "owl:subClassOf": "subsumes", - # narrower: The target mapping is narrower in meaning than the source concept. The - # sense in which the mapping is narrower SHALL be described in the comments in this - # case, and applications should be careful when attempting to use these mappings - # operationally. - "skos:narrower": "narrower", - "skos:narrowMatch": "narrower", # canonical - # specializes: The target mapping specializes the meaning of the source concept - # (e.g. the target is-a source). - "sssom:superClassOf": "specializes", - # inexact: The target mapping overlaps with the source concept, but both source and - # target cover additional meaning, or the definitions are imprecise and it is - # uncertain whether they have the same boundaries to their meaning. The sense in - # which the mapping is inexact SHALL be described in the comments in this case, and - # applications should be careful when attempting to use these mappings operationally - "skos:closeMatch": "inexact", - # unmatched: There is no match for this concept in the target code system. - # todo: unmatched: this is more complicated. This will be a combination of - # predicate_id and predicate_modifier (if present). See: - # https://github.com/mapping-commons/sssom/issues/185 - "unmatched": "unmatched", - # disjoint: This is an explicit assertion that there is no mapping between the - # source and target concept. - "owl:disjointWith": "disjoint", - }.get( - row["predicate_id"], row["predicate_id"] - ), # r4 - # "relationship": row['predicate_id'], # r5 - # "comment": '', - "extension": [ - { - # todo: `mapping_justification` consider changing `ValueString` -> `ValueCoding` - # ...that is, if I happen to know the categories/codes for this categorical variable - # ...if i do that, do i also need to upload that coding as a (i) `ValueSet` resource? (or (ii) codeable concept? prolly (i)) - "url": "http://example.org/fhir/StructureDefinition/mapping_justification", - "ValueString": row.get( - "mapping_justification", - row.get("mapping_justification", ""), - ), - } - ], - } - ], - } - for i, row in df.iterrows() - ], - # "unmapped": { # todo: conceptmap - # "mode": "fixed", - # "code": "temp", - # "display": "temp" - # } - } - ], + "sourceUri": metadata.get("subject_source", ""), + "targetUri": metadata.get("object_source", ""), + # TODO: Might want to make each "group" first, if there is more than 1 set of ontology1::ontology2 + # ...within a given MappingSet / set of SSSOM TSV rows. + "group": [{ + "source": metadata.get("subject_source", ""), + "target": metadata.get("object_source", ""), + "element": [ + { + "code": row["subject_id"], + "display": row.get("subject_label", ""), + "target": [ + { + "code": row["object_id"], + "display": row.get("object_label", ""), + # TODO: R4 (try this first) + # relatedto | equivalent | equal | wider | subsumes | narrower | specializes | inexact | unmatched | disjoint + # https://www.hl7.org/fhir/r4/conceptmap.html + # todo: r4: if not found, eventually needs to be `null` or something. check docs to see if nullable, else ask on Zulip + # TODO: R5 Needs to be one of: + # related-to | equivalent | source-is-narrower-than-target | source-is-broader-than-target | not-related-to + # https://www.hl7.org/fhir/r4/valueset-concept-map-equivalence.html + # ill update that next time. i can map SSSOM SKOS/etc mappings to FHIR ones + # and then add the original SSSOM mapping CURIE fields somewhere else + # https://www.hl7.org/fhir/valueset-concept-map-equivalence.html + # https://github.com/mapping-commons/sssom-py/issues/258 + "equivalence": { + # relateedto: The concepts are related to each other, and have at least some overlap + # in meaning, but the exact relationship is not known. + "skos:related": "relatedto", + "skos:relatedMatch": "relatedto", # canonical + # equivalent: The definitions of the concepts mean the same thing (including when + # structural implications of meaning are considered) (i.e. extensionally identical). + "skos:exactMatch": "equivalent", + # equal: The definitions of the concepts are exactly the same (i.e. only grammatical + # differences) and structural implications of meaning are identical or irrelevant + # (i.e. intentionally identical). + "equal": "equal", # todo what's difference between this and above? which to use? + # wider: The target mapping is wider in meaning than the source concept. + "skos:broader": "wider", + "skos:broadMatch": "wider", # canonical + # subsumes: The target mapping subsumes the meaning of the source concept (e.g. the + # source is-a target). + "rdfs:subClassOf": "subsumes", + "owl:subClassOf": "subsumes", + # narrower: The target mapping is narrower in meaning than the source concept. The + # sense in which the mapping is narrower SHALL be described in the comments in this + # case, and applications should be careful when attempting to use these mappings + # operationally. + "skos:narrower": "narrower", + "skos:narrowMatch": "narrower", # canonical + # specializes: The target mapping specializes the meaning of the source concept + # (e.g. the target is-a source). + "sssom:superClassOf": "specializes", + # inexact: The target mapping overlaps with the source concept, but both source and + # target cover additional meaning, or the definitions are imprecise and it is + # uncertain whether they have the same boundaries to their meaning. The sense in + # which the mapping is inexact SHALL be described in the comments in this case, and + # applications should be careful when attempting to use these mappings operationally + "skos:closeMatch": "inexact", + # unmatched: There is no match for this concept in the target code system. + # todo: unmatched: this is more complicated. This will be a combination of + # predicate_id and predicate_modifier (if present). See: + # https://github.com/mapping-commons/sssom/issues/185 + "unmatched": "unmatched", + # disjoint: This is an explicit assertion that there is no mapping between the + # source and target concept. + "owl:disjointWith": "disjoint", + }.get( + row["predicate_id"], row["predicate_id"] + ), # r4 + # "relationship": row['predicate_id'], # r5 + # "comment": '', + "extension": [ + { + # todo: `mapping_justification` consider changing `ValueString` -> `ValueCoding` + # ...that is, if I happen to know the categories/codes for this categorical variable + # ...if i do that, do i also need to upload that coding as a (i) `ValueSet` resource? (or (ii) codeable concept? prolly (i)) + "url": "http://example.org/fhir/StructureDefinition/mapping_justification", + "ValueString": row.get( + "mapping_justification", + row.get("mapping_justification", ""), + ), + } + ], + } + ], + } + for i, row in df.iterrows() + ], + # "unmapped": { # todo: conceptmap + # "mode": "fixed", + # "code": "temp", + # "display": "temp" + # } + }], } # Delete empty fields