def value_quantity(g: Graph, subject: Node, obs_fact: ObservationFact) -> List[ObservationFact]: """ Process a FHIR:valueQuantity, recording salient information in obs_fact :param g: Graph containing the quantity :param subject: Subject of the fhir quantity node :param obs_fact: Fact to add the information to :return: Empty list - no additional facts are generated """ units = fhirgraphutils.value(g, subject, FHIR.Quantity.unit) if not units: system = fhirgraphutils.value(g, subject, FHIR.Quantity.system) code = fhirgraphutils.value(g, subject, FHIR.Quantity.code) if code: ns = fhir_namespace_for(system) units = ((ns.upper() + ':') if ns else '') + code comparator = fhirgraphutils.value(g, subject, FHIR.Quantity.comparator) value_ = fhirgraphutils.value(g, subject, FHIR.Quantity.value) if value_ is not None: obs_fact._valtype_cd = valuetype_number obs_fact._nval_num = value_ obs_fact._tval_char = 'E' if comparator is None else comparator_map.get( str(comparator), '?') obs_fact._units_cd = str(units) if units else None else: obs_fact._valtype_cd = valuetype_novalue return []
def __init__(self, g: Graph, tables: Optional[I2B2Tables], patient: URIRef) -> None: """ Generate i2b2 patient dimension and patient_mapping records from PATIENT resources in graph g :param g: Graph containing 0 or more FHIR Patient resources :param tables: i2b2 tables connection if we are using a database (vs. tsv output) :param patient: Graph subject """ assert value(g, patient, FHIR.animal) is None # We don't do animals parsed_resource = parse_fhir_resource_uri(patient) self.patient_mappings = FHIRPatientMapping( tables, parsed_resource.resource, str(parsed_resource.namespace)) active = value(g, patient, FHIR.Patient.active) if active is not None and not active: self.patient_mappings._patient_ide_status = PatientIDEStatus.inactive self.patient_dimension_entry = PatientDimension( self.patient_mappings.patient_num, VitalStatusCd(VitalStatusCd.bd_unknown, VitalStatusCd.dd_unknown)) # Additional attributes that don't go into patient dimension but are recorded as observation facts self._language = None self._marital_status = None self._race = None self._religion = None self.add_patient_information(g, patient)
def value_codeable_concept(g: Graph, subject: Node, obs_fact: ObservationFact) -> List[ObservationFact]: """ Process a FHIR:valueCodeableConcept node. The first FHIR:CodeableConcept.coding entry is recorded as the value for the passed `obs_fact`. Additional obs_fact entries are generated to carry the text, if it exists, and any additional codings. :param g: Graph containing the data :param subject: CodeableConcept node :param obs_fact: target fact(s) :return: Additional obs_facts beyond obs_fact itself """ from i2fhirb2.fhir.fhirobservationfact import FHIRObservationFact rval = [] if obs_fact.modifier_cd != '@': print( f"{obs_fact.concept_cd}, {obs_fact.modifier_cd}: Modifier on modifier!" ) codings = sorted(list(g.objects(subject, FHIR.CodeableConcept.coding))) additional_entries = False if codings: for coding in codings: obs_fact_copy = copy.copy(obs_fact) if obs_fact.modifier_cd == '@': concept_uri = concept_uri_for(g, coding) concept_ns_name = FHIRObservationFact.ns_name_for( concept_uri) if concept_uri else None if concept_ns_name: obs_fact._modifier_cd = concept_ns_name display = fhirgraphutils.value(g, coding, FHIR.Coding.display) if not display: display = fhirgraphutils.value(g, subject, FHIR.CodeableConcept.text) if display: obs_fact._valtype_cd = valuetype_text obs_fact._tval_char = display if additional_entries: rval.append(obs_fact) else: additional_entries = True obs_fact = obs_fact_copy else: text = fhirgraphutils.value(g, subject, FHIR.CodeableConcept.text) if text: obs_fact._valtype_cd = valuetype_text obs_fact._tval_char = text return rval
def concept_uri_for(g: Graph, coding: Node) -> Optional[URIRef]: """ Return the URI for the concept in coding, if any. If a type arc is present, it will be used. Otherwise we will attempt to synthesize one from system and code. :param g: Graph containing the coding node :param coding: node that contains the FHIR.Coding entry :return: URI or None if nothing is available """ uri = g.value(coding, RDF.type) if uri is None: # TODO: Combine this with the rules in fhirtordf - they should do exactly the same thing system = fhirgraphutils.value(g, coding, FHIR.Coding.system) code = fhirgraphutils.value(g, coding, FHIR.Coding.code) if system and code: uri = URIRef(system + ('' if system.endswith(('#', '/')) else '/') + code) return uri
def test_value(self): from fhirtordf.rdfsupport.fhirgraphutils import value g = Graph() g.load(os.path.join(self.base_dir, "account-example.ttl"), format="turtle") s = FHIR['Account/example'] self.assertEqual("example", value(g, s, FHIR.Resource.id)) self.assertEqual(Literal("example"), value(g, s, FHIR.Resource.id, True)) self.assertEqual(FHIR.treeRoot, value(g, s, FHIR.nodeRole)) period = g.value(s, FHIR.Account.period) self.assertEqual(date(2016, 1, 1), value(g, period, FHIR.Period.start)) period_end = g.value(period, FHIR.Period.end) self.assertEqual(date(2016, 6, 30), value(g, period_end, FHIR.value)) self.assertIsNone(value(g, s, FHIR.Account.type)) self.assertIsNone(value(g, s, FHIR.foo))
def process_resource_instance(self, subj: URIRef, mapped_type: FHIR_Resource_type) \ -> Tuple[Optional[FHIRPatientMapping], Optional[FHIRVisitDimension], Optional[datetime]]: patient_id_uri, encounter_id_uri, provider_id = mapped_type.fact_key_for(self._g, subj) if patient_id_uri is not None: parsed_resource = parse_fhir_resource_uri(patient_id_uri) pm = FHIRPatientMapping(self.tables, parsed_resource.resource, str(parsed_resource.namespace)) self.patient_mappings += pm.patient_mapping_entries start_date = value(self._g, subj, FHIR.Observation.effectiveDateTime) if not start_date: start_date = datetime.now() vd = FHIRVisitDimension(subj, pm.patient_num, parsed_resource.resource, str(parsed_resource.namespace), start_date) self.visit_dimensions.append(vd.visit_dimension_entry) self.encounter_mappings += vd.encounter_mappings.encounter_mapping_entries return pm, vd, start_date else: # Just a reference to a resource instance -- drop it return None, None, None
def resource_id(self) -> Optional[str]: return value(self._g, self._resource_uri, FHIR.Resource.id)
def add_patient_information(self, g: Graph, patient: URIRef) -> None: """ Add additional information to the patient :param g: Graph carrying additional facts about the patient :param patient: URI of the actual patient """ if not g.value(patient, FHIR.Patient.animal): # i2b2 doesn't do animals # gender gender = value(g, patient, FHIR.Patient.gender) if gender == "male": self.patient_dimension_entry._sex_cd = 'M' elif gender == "female": self.patient_dimension_entry._sex_cd = 'F' elif gender == "other": self.patient_dimension_entry._sex_cd = 'U' # deceased.deceasedBoolean --> vital_status_code.deathInd isdeceased = value(g, patient, FHIR.Patient.deceasedBoolean) if isdeceased is not None: self.patient_dimension_entry._vital_status_cd = VitalStatusCd.dd_deceased if isdeceased \ else VitalStatusCd.dd_living # deceased.deceasedDateTime --> deathcode / death_date self.deathdate = value(g, patient, FHIR.Patient.deceasedDateTime, True) # birthdate - must be processed after deceased, as deceased goes into age calculation bd = g.value(patient, FHIR.Patient.birthDate) birthdate = None if bd: birthdate = extension( g, bd, "http://hl7.org/fhir/StructureDefinition/patient-birthTime", asLiteral=True) if not birthdate: birthdate = value(g, patient, FHIR.Patient.birthDate, asLiteral=True) self.birthdate = birthdate # address -- use == home / period.end is empty or past deathcode date addresses = g.objects(patient, FHIR.Patient.address) for address in addresses: address_use = value(g, address, FHIR.Address.use) if address_use is None or address_use == "home": period = g.value(address, FHIR.Address.period) if not period or (period and value( g, period, FHIR.Period.end) is None): city = value(g, address, FHIR.Address.city) state = value(g, address, FHIR.Address.state) zipcode = value(g, address, FHIR.Address.postalCode) if zipcode: self.patient_dimension_entry._zip_cd = zipcode if city and state: self.patient_dimension_entry._statecityzip_path = \ 'Zip codes\\' + state + '\\' + city + '\\' + zipcode + '\\' # maritalStatus --> map to 'single', 'married', 'divorced', 'widow', other? marital_stati = codeable_concept_code(g, patient, FHIR.Patient.maritalStatus) for ms in marital_stati: if ms.system == str(V3.MaritalStatus): self._marital_status = marital_stati[0] msc = self._marital_status.code if msc != 'UNK': self.patient_dimension_entry._marital_status_cd = \ 'divorced' if msc in ['A', 'D'] else \ 'married' if msc in ['L', 'M', 'P'] else \ 'widow' if msc in ['W'] else \ 'single' break else: msuri = concept_uri(g, patient, FHIR.Patient.maritalStatus, SNOMEDCT) if msuri: # TODO: figure out what to do with SNOMED id's (terminology service, anyone?) pass # language communications = list( g.objects(patient, FHIR.Patient.communication)) language = None for communication in communications: pref = value(g, communication, FHIR.Patient.communication.preferred) if pref or (pref is None and len(communications) == 1): languages = codeable_concept_code( g, communication, FHIR.Patient.communication.language) if languages: language = languages[0] break if language is not None: self._language = language.code