-
Notifications
You must be signed in to change notification settings - Fork 1
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
🚧 Map Concepts between KF and FHIR #78
Changes from 10 commits
cac3148
9eaa241
81638f9
16c882c
bab1841
2d18c42
86f6cc6
0cae8fb
06d21ff
ddbcbb3
89dbdc2
554f71c
dee356d
f4b1a0b
90c8f41
efe586e
707652e
890cc8b
3d6f29c
a3f204c
c01eda9
e55247c
29b8490
2252e76
8327509
90c936e
38b3a1c
02e6574
3ad4fd1
6bba1f3
0098372
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,3 +112,6 @@ license/ | |
vonk-trial-license.json | ||
appsettings.env | ||
logsettings.env | ||
|
||
# Miscellaneous | ||
.vscode/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Concept-Mappers between Kids First and Phenopackets FHIR | ||
|
||
This directory curates Python modules mapping Kids First concepts to FHIR profiles against the [Phenopackets specifications](https://aehrc.github.io/fhir-phenopackets-ig/index.html). | ||
The following table shows conceptual mappings among KF entities, and Phenopackets and FHIR profiles: | ||
|
||
| Kids First | Phenopackets | FHIR | | ||
|-----------------------|---------------------|-----------------------| | ||
| `family` | `Family` | `Group` | | ||
| `participant` | `Individual` | `Patient` | | ||
| `family_relationship` | `PedigreeNode` | `FamilyMemberHistory` | | ||
| `diagnosis` | `Disease` | `Condition` | | ||
| `phenotype` | `PhenotypicFeature` | `Observation` | | ||
| `biospecimen` | `Biosample` | `Specimen` | | ||
| `genomic_file` | `HtsFile` | `DocumentReference` | | ||
|
||
As an initial pass, we created modules for the following profiles: | ||
|
||
- `Family`; | ||
- `Individual`; | ||
- `PedigreeNode`; | ||
- `Disease`; | ||
- `PhenotypicFeature`; and | ||
- `Biosample` | ||
|
||
In creating the modules, the following attributes were considered: | ||
|
||
- Most of the Phenopackets atrributes from the differential tables if mapped; and | ||
- All the FHIR attributes required (based on cardinality) as well as `'id'` and `'identifier'` |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,94 @@ | ||||||
""" | ||||||
This module maps Kids First biospecimen to Phenopackets Biosample (derived from FHIR Specimen). | ||||||
Please visit https://aehrc.github.io/fhir-phenopackets-ig/StructureDefinition-Biosample.html | ||||||
for the detailed structure definition. | ||||||
""" | ||||||
from kf_lib_data_ingest.common import constants | ||||||
from kf_lib_data_ingest.common.concept_schema import CONCEPT | ||||||
|
||||||
|
||||||
def biosample_status(x): | ||||||
""" | ||||||
http://hl7.org/fhir/R4/valueset-specimen-status.html | ||||||
""" | ||||||
if x == constants.COMMON.FALSE: | ||||||
return 'unavailable' | ||||||
elif x == constants.COMMON.TRUE: | ||||||
return 'available' | ||||||
else: | ||||||
raise Exception('Unknown Biosample status') | ||||||
|
||||||
def biosample_type(x): | ||||||
""" | ||||||
http://terminology.hl7.org/ValueSet/v2-0487 | ||||||
""" | ||||||
if x == constants.SPECIMEN.COMPOSITION.BLOOD: | ||||||
return { | ||||||
'system': 'http://terminology.hl7.org/CodeSystem/v2-0487', | ||||||
'code': 'BLD', | ||||||
'display': 'Whole blood' | ||||||
} | ||||||
elif x == constants.SPECIMEN.COMPOSITION.SALIVA: | ||||||
return { | ||||||
'system': 'http://terminology.hl7.org/CodeSystem/v2-0487', | ||||||
'code': 'SAL', | ||||||
'display': 'Saliva' | ||||||
} | ||||||
elif x == constants.SPECIMEN.COMPOSITION.TISSUE: | ||||||
return { | ||||||
'system': 'http://terminology.hl7.org/CodeSystem/v2-0487', | ||||||
'code': 'TISS', | ||||||
'display': 'Tissue' | ||||||
} | ||||||
else: | ||||||
raise Exception('Unknown Biosample type') | ||||||
|
||||||
|
||||||
biosample = { | ||||||
'resourceType': 'Specimen', | ||||||
'id': CONCEPT.BIOSPECIMEN.TARGET_SERVICE_ID, | ||||||
'meta': { | ||||||
'profile': ['http://ga4gh.fhir.phenopackets/StructureDefinition/Biosample'] | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could have placeholder KF profiles based on the PK profiles to give us more flexibility down the road. We can also copy their "differential" element definitions to our KF profiles (so we have similar and interoperable constraints) and keep our profiles based on the base FHIR profiles. |
||||||
}, | ||||||
'identifier': [ | ||||||
{ | ||||||
'system': 'https://kf-api-dataservice.kidsfirstdrc.org/biospecimens', | ||||||
'value': CONCEPT.BIOSPECIMEN.TARGET_SERVICE_ID | ||||||
}, | ||||||
{ | ||||||
'value': CONCEPT.BIOSPECIMEN.ID | ||||||
} | ||||||
], | ||||||
'accessionIdentifier': [ | ||||||
{ | ||||||
'value': CONCEPT.BIOSPECIMEN.ID | ||||||
} | ||||||
], | ||||||
'status': biosample_status(CONCEPT.BIOSPECIMEN.VISIBLE), | ||||||
'type': { | ||||||
'coding': [ | ||||||
biosample_type(CONCEPT.BIOSPECIMEN.COMPOSITION) | ||||||
], | ||||||
'text': CONCEPT.BIOSPECIMEN.COMPOSITION | ||||||
}, | ||||||
'subject': { | ||||||
'reference': f'Individual/CONCEPT.{PARTICIPANT.TARGET_SERVICE_ID}', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be:
Suggested change
I think the references have to have the FHIR base type in the prefix not the id of the StructureDefinition...I might be wrong. Could someone check the spec and verify? |
||||||
'type': 'Individual', | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be (same reason as above)
Suggested change
|
||||||
'identifier': [ | ||||||
{ | ||||||
'system': 'https://kf-api-dataservice.kidsfirstdrc.org/participants', | ||||||
'value': CONCEPT.PARTICIPANT.TARGET_SERVICE_ID | ||||||
}, | ||||||
{ | ||||||
'value': CONCEPT.PARTICIPANT.ID | ||||||
} | ||||||
], | ||||||
'display': CONCEPT.PARTICIPANT.ID | ||||||
}, | ||||||
'collection': { | ||||||
'quantity': { | ||||||
'value': CONCEPT.BIOSPECIMEN.VOLUME_UL, | ||||||
'unit': 'uL' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should also add a system for the unit. Not sure why the unit wasn't a coding/CodeableConcept in FHIR. |
||||||
} | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
""" | ||
This module maps Kids First diagnosis to Phenopackets Disease (derived from FHIR Condition). | ||
Please visit https://aehrc.github.io/fhir-phenopackets-ig/StructureDefinition-Disease.html | ||
for the detailed structure definition. | ||
""" | ||
from kf_lib_data_ingest.common import constants | ||
from kf_lib_data_ingest.common.concept_schema import CONCEPT | ||
|
||
|
||
def coded_onset(x): | ||
""" | ||
https://aehrc.github.io/fhir-phenopackets-ig/ValueSet-Onset.html | ||
""" | ||
pass | ||
|
||
def tumor_stage(x): | ||
""" | ||
https://aehrc.github.io/fhir-phenopackets-ig/ValueSet-TumorStage.html | ||
""" | ||
pass | ||
|
||
def disease_code(x): | ||
""" | ||
http://hl7.org/fhir/R4/valueset-condition-code.html | ||
""" | ||
pass | ||
|
||
def body_site(x): | ||
""" | ||
http://hl7.org/fhir/R4/valueset-body-site.html | ||
""" | ||
pass | ||
|
||
|
||
disease = { | ||
'resourceType': 'Condition', | ||
'id': CONCEPT.DIAGNOSIS.TARGET_SERVICE_ID, | ||
'meta': { | ||
'profile': ['http://ga4gh.fhir.phenopackets/StructureDefinition/Disease'] | ||
}, | ||
'extension': [ | ||
{ | ||
'text': 'CodedOnset', | ||
'url': 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/CodedOnset', | ||
'valueCodeableConcept': { | ||
'coding': [ | ||
{ | ||
'system': 'http://purl.obolibrary.org/obo/hp.owl', | ||
'code': 'HP:0410280', | ||
'display': 'Pediatric onset' | ||
} | ||
] | ||
} | ||
} | ||
], | ||
'identifier': [ | ||
{ | ||
'system': 'https://kf-api-dataservice.kidsfirstdrc.org/diagnoses', | ||
'value': CONCEPT.DIAGNOSIS.TARGET_SERVICE_ID | ||
}, | ||
{ | ||
'value': CONCEPT.DIAGNOSIS.ID | ||
} | ||
], | ||
'code': { | ||
'coding': None, # condition_code() | ||
'text': CONCEPT.DIAGNOSIS.NAME | ||
}, | ||
'bodySite': None, # body_site() | ||
'subject': { | ||
'reference': f'Patient/{CONCEPT.PARTICIPANT.TARGET_SERVICE_ID}', | ||
'type': 'Patient', | ||
'identifier': [ | ||
{ | ||
'system': 'https://kf-api-dataservice.kidsfirstdrc.org/participants', | ||
'value': CONCEPT.PARTICIPANT.TARGET_SERVICE_ID | ||
}, | ||
{ | ||
'value': CONCEPT.PARTICIPANT.ID | ||
} | ||
], | ||
'display': CONCEPT.PARTICIPANT.ID | ||
}, | ||
'onsetAge': CONCEPT.DIAGNOSIS.EVENT_AGE_DAYS | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the format of KF dates? This has to be coded as a FHIR Quantity if it's days. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. KF doesn't have dates ever. We only have ages specified in days. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
""" | ||
This module maps Kids First family to Phenopackets Family (derived from FHIR Group). | ||
Please visit https://aehrc.github.io/fhir-phenopackets-ig/StructureDefinition-Family.html | ||
for the detailed structure definition. | ||
""" | ||
from kf_lib_data_ingest.common import constants | ||
from kf_lib_data_ingest.common.concept_schema import CONCEPT | ||
|
||
|
||
def family_type(x): | ||
""" | ||
http://hl7.org/fhir/R4/valueset-group-type.html | ||
""" | ||
if x in {constants.SPECIES.DOG}: | ||
return 'animal' | ||
elif x == constants.SPECIES.HUMAN: | ||
return 'person' | ||
else: | ||
raise Exception('Unknown Family type') | ||
|
||
def family_member_type(x): | ||
""" | ||
https://www.hl7.org/fhir/v3/FamilyMember/vs.html | ||
""" | ||
if x == constants.COMMON.TRUE: | ||
return { | ||
'system': 'http://terminology.hl7.org/CodeSystem/v3-RoleCode', | ||
'code': 'CHILD', | ||
'display': 'child' | ||
} | ||
elif x == constants.RELATIONSHIP.FATHER: | ||
return { | ||
'system': 'http://terminology.hl7.org/CodeSystem/v3-RoleCode', | ||
'code': 'FTH', | ||
'display': 'father' | ||
} | ||
elif x == constants.RELATIONSHIP.MOTHER: | ||
return { | ||
'system': 'http://terminology.hl7.org/CodeSystem/v3-RoleCode', | ||
'code': 'MTH', | ||
'display': 'mother' | ||
} | ||
else: | ||
raise Exception('Unknown Family family-member-type') | ||
|
||
def family_member_phenopacket(x): pass | ||
|
||
|
||
family = { | ||
'resourceType': 'Group', | ||
'id': CONCEPT.FAMILY.TARGET_SERVICE_ID, | ||
'meta': { | ||
'profile': ['http://ga4gh.fhir.phenopackets/StructureDefinition/Family'] | ||
}, | ||
'identifier': [ | ||
{ | ||
'system': 'https://kf-api-dataservice.kidsfirstdrc.org/families', | ||
'value': CONCEPT.FAMILY.TARGET_SERVICE_ID | ||
}, | ||
{ | ||
'value': CONCEPT.FAMILY.ID | ||
} | ||
], | ||
'extension': [ | ||
{ | ||
'text': 'PedigreeNodeReference', | ||
'url': 'http://ga4gh.org/fhir/phenopackets/StructureDefinition/PedigreeNodeReference', | ||
'valueReference': None | ||
} | ||
], | ||
'type': family_type(CONCEPT.PARTICIPANT.SPECIES or constants.SPECIES.HUMAN), | ||
'actual': constants.COMMON.TRUE, # defaults to True | ||
'member': [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "member" is missing the "family-member-phenopacket" extension shown here: Also, the new IG doesn't show the ValueSet/CodeSystme that is used in the "family-member-type" extension. I think it was shown in the older IG format. |
||
{ | ||
'id': CONCEPT.PARTICIPANT.TARGET_SERVICE_ID, | ||
'extension': [ | ||
{ | ||
'text': 'family-member-type', | ||
'id': CONCEPT.FAMILY_RELATIONSHIP.TARGET_SERVICE_ID, | ||
'url': 'family-member-type', | ||
'valueCodeableConcept': { | ||
'coding': [ | ||
family_member_type( | ||
CONCEPT.PARTICIPANT.IS_PROBAND or | ||
CONCEPT.FAMILY_RELATIONSHIP.RELATION_FROM_1_TO_2 | ||
) | ||
], | ||
'text': constants.RELATIONSHIP.CHILD | ||
if CONCEPT.PARTICIPANT.IS_PROBAND | ||
else CONCEPT.FAMILY_RELATIONSHIP.RELATION_FROM_1_TO_2 | ||
} | ||
} | ||
], | ||
'entity': { | ||
'reference': f'Individual/{CONCEPT.PARTICIPANT.TARGET_SERVICE_ID}', | ||
'type': 'Individual', | ||
'identifier': [ | ||
{ | ||
'system': 'https://kf-api-dataservice.kidsfirstdrc.org/participants', | ||
'value': CONCEPT.PARTICIPANT.TARGET_SERVICE_ID | ||
}, | ||
{ | ||
'value': CONCEPT.PARTICIPANT.ID | ||
} | ||
], | ||
'display': CONCEPT.PARTICIPANT.ID | ||
} | ||
} | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are these URLs in the comments used? This URL doesn't resolve but it is taken from http://hl7.org/fhir/R4/v2/0487/index.html as the CodeSystem URL.
Also, this is bound as an "example" ValueSet. We probably want to use some other terminology and define our ValueSet.
Terminology choices discussion started on the Slack channel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For all the custom functions defined, the URLs in the docstrings are not for direct use, but for reference. And, when I created this PR, it was before the Phenopackets team has made the recent IG updates and the URLs were valid.
@fiendish will create a PR to this branch which resolves terminology issues.