All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.linuxforhealth.hl7.data.SimpleDataValueResolver Maven / Gradle / Ivy

/*
 * (C) Copyright IBM Corp. 2020, 2022
 *
 * SPDX-License-Identifier: Apache-2.0
 */
package io.github.linuxforhealth.hl7.data;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;

import ca.uhn.hl7v2.model.v26.datatype.CE;
import ca.uhn.hl7v2.model.v26.datatype.CWE;
import ca.uhn.hl7v2.model.v26.datatype.DT;
import ca.uhn.hl7v2.model.v26.datatype.DTM;
import ca.uhn.hl7v2.model.v26.datatype.PPN;
import ca.uhn.hl7v2.model.v26.datatype.TS;
import ca.uhn.hl7v2.model.v26.datatype.XCN;
import ca.uhn.hl7v2.model.v26.group.VXU_V04_OBSERVATION;
import ca.uhn.hl7v2.model.v26.group.VXU_V04_ORDER;
import ca.uhn.hl7v2.model.v26.segment.OBX;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.model.Varies;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.hl7.fhir.dstu3.model.codesystems.MedicationRequestCategory;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCriticality;
import org.hl7.fhir.r4.model.DiagnosticReport.DiagnosticReportStatus;
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.r4.model.MedicationRequest;
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestStatus;
import org.hl7.fhir.r4.model.Specimen.SpecimenStatus;
import org.hl7.fhir.r4.model.codesystems.V3ActCode;
import org.hl7.fhir.r4.model.codesystems.V3MaritalStatus;
import org.hl7.fhir.r4.model.codesystems.ConditionCategory;
import org.hl7.fhir.r4.model.codesystems.MessageReasonEncounter;
import org.hl7.fhir.r4.model.codesystems.NameUse;
import org.hl7.fhir.r4.model.codesystems.SubscriberRelationship;
import org.hl7.fhir.r4.model.codesystems.V3ReligiousAffiliation;
import org.hl7.fhir.r4.model.codesystems.V3RoleCode;
import org.hl7.fhir.r4.model.codesystems.DiagnosisRole;
import org.hl7.fhir.r4.model.codesystems.ConditionClinical;
import org.hl7.fhir.r4.model.codesystems.ConditionVerStatus;
import org.hl7.fhir.r4.model.codesystems.CompositionStatus;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.github.linuxforhealth.api.ResourceValue;
import io.github.linuxforhealth.core.terminology.Hl7v2Mapping;
import io.github.linuxforhealth.core.terminology.SimpleCode;
import io.github.linuxforhealth.core.terminology.TerminologyLookup;
import io.github.linuxforhealth.core.terminology.UrlLookup;
import io.github.linuxforhealth.hl7.data.date.DateUtil;

public class SimpleDataValueResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleDataValueResolver.class);
    private static final String INVALID_CODE_MESSAGE_FULL = "Invalid input: code '%s' could not be mapped to values in system '%s' with original display '%s' and version '%s'.";
    private static final String INVALID_CODE_MESSAGE_SHORT = "Invalid input: code '%s' could not be mapped to values in system '%s'.";

    public static final ValueExtractor DATE = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (val != null) {
            return DateUtil.formatToDate(val);
        }
        return null;
    };

    public static final ValueExtractor STRING = (Object value) -> {
        return Hl7DataHandlerUtil.getStringValue(value);
    };

    public static final ValueExtractor STRING_ALL = (Object value) -> {
        return Hl7DataHandlerUtil.getStringValue(value, true);
    };

    public static final ValueExtractor VALID_ID = (Object value) -> {
        String strValue = Hl7DataHandlerUtil.getStringValue(value);
        if (strValue != null) {
            strValue = strValue.toLowerCase();
            return strValue.replaceAll("[^a-zA-Z0-9.]", "-");
        }
        return null;
    };

    // Clean up of SSN currently removes any dashes.
    public static final ValueExtractor CLEAN_SSN = (Object value) -> {
        String strValue = Hl7DataHandlerUtil.getStringValue(value);
        if (strValue != null) {
            return strValue.replaceAll("\\-", "");
        }
        return null;
    };

    public static final ValueExtractor URI_VAL = (Object value) -> {
        try {
            String val = Hl7DataHandlerUtil.getStringValue(value);
            if (val != null && isValidUUID(val)) {
                return new URI("urn", "uuid", val);
            } else {
                return null;
            }
        } catch (IllegalArgumentException | URISyntaxException e) {
            LOGGER.warn("Value not valid URI, value: {}", value);
            
            return null;
        }
    };

    // Creates a display name; currently only handles XCN and PPN as input
    public static final ValueExtractor PERSON_DISPLAY_NAME = (Object value) -> {
        StringBuilder sb = new StringBuilder();
        String valprefix = null;
        String valfirst = null;
        String valmiddle = null;
        String valfamily = null;
        String valsuffix = null;

        if (value instanceof XCN) {
            XCN xcn = (XCN) value;
            valprefix = Hl7DataHandlerUtil.getStringValue(xcn.getPrefixEgDR());
            valfirst = Hl7DataHandlerUtil.getStringValue(xcn.getGivenName());
            valmiddle = Hl7DataHandlerUtil.getStringValue(xcn.getSecondAndFurtherGivenNamesOrInitialsThereof());
            valfamily = Hl7DataHandlerUtil.getStringValue(xcn.getFamilyName());
            valsuffix = Hl7DataHandlerUtil.getStringValue(xcn.getSuffixEgJRorIII());

        } else if (value instanceof PPN) {
            PPN ppn = (PPN) value;
            valprefix = Hl7DataHandlerUtil.getStringValue(ppn.getPrefixEgDR());
            valfirst = Hl7DataHandlerUtil.getStringValue(ppn.getGivenName());
            valmiddle = Hl7DataHandlerUtil.getStringValue(ppn.getSecondAndFurtherGivenNamesOrInitialsThereof());
            valfamily = Hl7DataHandlerUtil.getStringValue(ppn.getFamilyName());
            valsuffix = Hl7DataHandlerUtil.getStringValue(ppn.getSuffixEgJRorIII());
        }

        if (valprefix != null) {
            sb.append(valprefix).append(" ");
        }
        if (valfirst != null) {
            sb.append(valfirst).append(" ");
        }
        if (valmiddle != null) {
            sb.append(valmiddle).append(" ");
        }
        if (valfamily != null) {
            sb.append(valfamily).append(" ");
        }
        if (valsuffix != null) {
            sb.append(valsuffix).append(" ");
        }
        String name = sb.toString();
        if (StringUtils.isNotBlank(name)) {
            return name.trim();
        } else
            return null;
    };

    public static final ValueExtractor ADMINISTRATIVE_GENDER_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, AdministrativeGender.class);
        if (code != null) {
            return code;
        } else if (val == null) {
            return null;
        } else {
            return AdministrativeGender.UNKNOWN.toCode();
        }
    };

    public static final ValueExtractor MEDREQ_STATUS_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, MedicationRequest.MedicationRequestStatus.class);
        if (code != null) {
            return code;
        }
        return "unknown"; // when the HL7 status codes get mapped in v2toFhirMapping, we will return code. "unknown" is being returned because the hl7 message is not mapped to fhir yet.
    };

    public static final ValueExtractor MEDREQ_CATEGORY_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, MedicationRequestCategory.class);
        if (code != null) {
            MedicationRequestCategory category = MedicationRequestCategory.fromCode(code);
            return new SimpleCode(code, "http://terminology.hl7.org/CodeSystem/medicationrequest-category",
                    category.getDisplay()); // category.getSystem() returns http://hl7.org/fhir/medication-request-category which is invalid so we hardcode the system with the proper url
        } else
            return null;
    };

    public static final ValueExtractor OBSERVATION_STATUS_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, ObservationStatus.class);
    };

    // Handlers for searching Immunization.education siblings.
    public static final ValueExtractor FIND_EDUCATION_REFERENCE_TEXT = (Object value) -> {
        return getSelectedSiblingObservationTEXTfromObxGroup(value, "30956-7");
    };

    public static final ValueExtractor FIND_EDUCATION_DOCUMENT_TYPE_TEXT = (Object value) -> {
        return getSelectedSiblingObservationTEXTfromObxGroup(value, "69764-9");
    };

    public static final ValueExtractor FIND_EDUCATION_PUBLICATION_DATE = (Object value) -> {
        return getSelectedSiblingObservationDATEfromObxGroup(value, "29768-9");
    };

    public static final ValueExtractor FIND_EDUCATION_PRESENTATION_DATE = (Object value) -> {
        return getSelectedSiblingObservationDATEfromObxGroup(value, "29769-7");
    };

    // Typed handler for CE & CWE return values from Immunization.education siblings
    private static final String getSelectedSiblingObservationTEXTfromObxGroup(Object valueObx, String siblingCode) {
        Object obj = getSelectedSiblingObservationVALUEfromObxGroup(valueObx, siblingCode);
        // If we got an object, tease out the text value
        if (obj != null) {
            String returnVal = null;
            if (obj instanceof CE) {
                returnVal = Hl7DataHandlerUtil.getStringValue(((CE) obj).getText());
            }
            if (obj instanceof CWE) {
                returnVal = Hl7DataHandlerUtil.getStringValue(((CWE) obj).getText());
            }
            // If there was no text value in the object, or the object was not an expected type default to "unspecified"
            return returnVal != null ? returnVal : "unspecified";
        }
        // If no object was found, return null 
        return null;
    }

    // Typed handler for DT & TS return values from Immunization.education siblings
    private static final String getSelectedSiblingObservationDATEfromObxGroup(Object valueObx, String siblingCode) {
        Object obj = getSelectedSiblingObservationVALUEfromObxGroup(valueObx, siblingCode);
        if (obj instanceof DT) {
            return DateUtil.formatToDate(((DT)obj).getValue());
        }
        if (obj instanceof TS) {
            return DateUtil.formatToDate(((TS)obj).getTime().toString());
        }
        if (obj instanceof DTM) {
            String dateString = ((DTM)obj).getValue();
            // DTMs potentially have more information than we need.  Truncate after day, or return null.
            return dateString.length() > 7 ? DateUtil.formatToDate(dateString.substring(0,8)) : null;
        }
        return null;
    }

    // Common handler for searching Immunization.education siblings.  Used for immunization records where the 
    // immunization, the publication, and presentation are in different sibling OBX's grouped together by OBX.4.
    // This special purpose routine finds the grandparent object, so we can search the correct sibling objects,
    // which prevents search overreach and datableed.
    // The return value is an unformatted object. It is up to the caller to check type and extract content to string.
    private static final Object getSelectedSiblingObservationVALUEfromObxGroup(Object valueObx, String siblingCode) {
        if (valueObx instanceof OBX) {
            try {
                OBX obx = (OBX) valueObx;
                // Get the group number from OBX.4
                String obx4GroupNum = obx.getObx4_ObservationSubID().getValueOrEmpty();
                // OBX is contained in an OBSERVATION. Get the OBSERVATION by requesting the parent.  
                VXU_V04_OBSERVATION containingObservation = (VXU_V04_OBSERVATION) obx.getParent();
                // The parent of the OBSERVATION is a VXU_V04_ORDER
                VXU_V04_ORDER containingOrder = (VXU_V04_ORDER) containingObservation.getParent();
                // Get the repeating 'sibling' OBSERVATION objects
                List observations = containingOrder.getOBSERVATIONAll();
                for (VXU_V04_OBSERVATION obsIter : observations) {
                    // For each OBSERVATION, get the OBX
                    OBX obsIterObx = obsIter.getOBX();
                    // If OBX.4 is the same group AND OBX.3.1 matches the siblingCode we want 
                    if (obx4GroupNum.equals(obsIterObx.getObx4_ObservationSubID().getValueOrEmpty()) &&
                            obsIterObx.getObx3_ObservationIdentifier().getCwe1_Identifier().getValueOrEmpty()
                                    .equals(siblingCode)) {
                        if (obsIterObx.getObservationValueReps() > 0) {
                            return obsIterObx.getObservationValue(0).getData();
                        }
                        return null;
                    }
                }
            } catch (HL7Exception e) {
                
                return null; // If something fails
            }
        }
        return null;
    }

    public static final ValueExtractor OBSERVATION_STATUS_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, ObservationStatus.class);
        if (code != null) {
            ObservationStatus status = ObservationStatus.fromCode(code);
            return new SimpleCode(code, status.getSystem(), status.getDisplay());
        } else {
            // If code is null, it means the code wasn't known in our table, and can't be looked up.
            // Make a message in the display.
            String theSystem = ObservationStatus.REGISTERED.getSystem();
            return new SimpleCode(null, theSystem, String.format(INVALID_CODE_MESSAGE_SHORT, val, theSystem));
        }
    };

    public static final ValueExtractor SERVICE_REQUEST_STATUS = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, ServiceRequestStatus.class);
    };

    public static final ValueExtractor CONDITION_CATEGORY_CODES = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (val != null) {
            ConditionCategory status = ConditionCategory.fromCode(val);
            return new SimpleCode(val, status.getSystem(), status.getDisplay());
        } else {
            return null;
        }
    };

    public static final ValueExtractor RELIGIOUS_AFFILIATION_FHIR_CC = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, V3ReligiousAffiliation.class);
        String text = Hl7DataHandlerUtil.getOriginalDisplayText(value);
        String version = Hl7DataHandlerUtil.getVersion(value);

        if (code != null) {
            V3ReligiousAffiliation status = V3ReligiousAffiliation.fromCode(code);
            return new SimpleCode(code, status.getSystem(), status.getDisplay());
        } else {
            // // If code is null, it means the code wasn't known in our table, and can't be looked up.
            // // Make a message in the display.
            String religionSystem = V3ReligiousAffiliation._1028.getSystem();
            return new SimpleCode(null, religionSystem,
                    String.format(INVALID_CODE_MESSAGE_FULL, val, religionSystem, text, version));
        }
    };

    // Diagnosis Use needs special handling because the three input codes, A, F, and W
    // need different systems because FHIR has a system for A, but not F and W.
    // We check the value and based on the value, do the lookup in the right system.
    // Note: values that come in know the table based on the field position.
    public static final ValueExtractor DIAGNOSIS_USE = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (val != null && (val.equals("F") || val.equals("W"))) {
            // Process as table v2-0052
            String table = Hl7DataHandlerUtil.getTableNumber(value);
            if (table != null && val != null) {
                // Found table and a code. Try looking it up.
                SimpleCode coding = TerminologyLookup.lookup(table, val);
                // A non-null, non-empty value in display means a good code from lookup.
                if (coding != null && coding.getDisplay() != null && !coding.getDisplay().isEmpty()) {
                    return coding;
                }
                // Without a good table lookup, falls through to simple code handling below
            }
        }
        // Otherwise process as a DiagnosisRole mapping (handles unknown codes)
        String code = getFHIRCode(val, DiagnosisRole.class);
        if (code != null) {
            DiagnosisRole use = DiagnosisRole.fromCode(code);
            return new SimpleCode(code, use.getSystem(), use.getDisplay());
        } else {
            return new SimpleCode(val, null, null);
        }
    };

    public static final ValueExtractor CONDITION_CLINICAL_STATUS_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);

        //Test to see if val is a valid code.
        ConditionClinical use = null;
        try {
            use = ConditionClinical.fromCode(val);
            LOGGER.info("Found ConditionClinical code for '{}'.", val);
        } catch (FHIRException e) {
            LOGGER.warn("Could not find ConditionClinical code for '{}'.", val);
        }

        if (use != null) { // if it is then setup the simple code
            return new SimpleCode(val, use.getSystem(), use.getDisplay());
        } else { // otherwise we don't want the code at all
            return null;
        }
    };

    public static final ValueExtractor CONDITION_VERIFICATION_STATUS_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);

        //Test to see if val is a valid code.
        ConditionVerStatus use = null;
        try {
            use = ConditionVerStatus.fromCode(val);
            LOGGER.info("Found ConditionVerStatus code for '{}'.", val);
        } catch (FHIRException e) {
            LOGGER.warn("Could not find ConditionVerStatus code for '{}'.", val);
        }
        if (use != null) { // if it is then setup the simple code
            return new SimpleCode(val, use.getSystem(), use.getDisplay());
        } else { // otherwise we don't want the code at all
            return null;
        }
    };

    public static final ValueExtractor ACT_ENCOUNTER_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, V3ActCode.class);
        String version = Hl7DataHandlerUtil.getVersion(value);
        if (code != null) {
            V3ActCode act = V3ActCode.fromCode(code);
            return new SimpleCode(code, act.getSystem(), act.getDisplay(), version);
        } else if (val != null) { // if code does not map but is present, use the "val" as the code
            return new SimpleCode(val, null, null);
        } else
            return null;
    };

    public static final ValueExtractor SPECIMEN_STATUS_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, SpecimenStatus.class);
    };

    public static final ValueExtractor DOC_REF_DOC_STATUS_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, CompositionStatus.class);
    };

    public static final ValueExtractor NAME_USE_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, NameUse.class);
    };

    public static final ValueExtractor ENCOUNTER_MODE_ARRIVAL_DISPLAY = (Object value) -> {
        return getFHIRCode(Hl7DataHandlerUtil.getStringValue(value), "EncounterModeOfArrivalDisplay");
    };

    // Relationships are coded, mapped, and recoded in two different DIRECTIONS.  
    // See detailed notes in v2ToFhirMapping maps of V3RoleCode and SubscriberRelationship

    // Maps from IN1.17 to http://terminology.hl7.org/CodeSystem/v3-RoleCode
    // Used for Coverage.relationship
    public static final ValueExtractor POLICYHOLDER_RELATIONSHIP_IN117 = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, "PolicyholderRelationshipIN117");
        String version = Hl7DataHandlerUtil.getVersion(value);
        if (code != null) {
            V3RoleCode relationship = V3RoleCode.fromCode(code);
            return new SimpleCode(code, relationship.getSystem(), relationship.getDisplay(), version);
        } else {
            // If code is not found in our mapping, return the code itself with no system or display. 
            return new SimpleCode(val, null, null, null);
        }
    };

    // Maps from IN2.72 to http://terminology.hl7.org/CodeSystem/v3-RoleCode
    // Used for Coverage.relationship
    public static final ValueExtractor POLICYHOLDER_RELATIONSHIP_IN272 = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, "PolicyholderRelationshipIN272");
        String version = Hl7DataHandlerUtil.getVersion(value);
        if (code != null) {
            V3RoleCode relationship = V3RoleCode.fromCode(code);
            return new SimpleCode(code, relationship.getSystem(), relationship.getDisplay(), version);
        } else {
            // If code is not found in our mapping, return the code itself with no system or display. 
            return new SimpleCode(val, null, null, null);
        }
    };

    // Maps from IN1.17 to http://terminology.hl7.org/CodeSystem/subscriber-relationship
    // Used for RelatedPerson.relationship.
    public static final ValueExtractor SUBSCRIBER_RELATIONSHIP_IN117 = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, "SubscriberRelationshipIN117");
        String version = Hl7DataHandlerUtil.getVersion(value);
        if (code != null) {
            SubscriberRelationship relationship = SubscriberRelationship.fromCode(code);
            return new SimpleCode(code, relationship.getSystem(), relationship.getDisplay(), version);
        } else {
            // If code is not found in our mapping, return the code itself with no system or display. 
            return new SimpleCode(val, null, null, null);
        }
    };

    // Maps from IN2.72 to http://terminology.hl7.org/CodeSystem/subscriber-relationship
    // Used for RelatedPerson.relationship.
    public static final ValueExtractor SUBSCRIBER_RELATIONSHIP_IN272 = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, "SubscriberRelationshipIN272");
        String version = Hl7DataHandlerUtil.getVersion(value);
        if (code != null) {
            SubscriberRelationship relationship = SubscriberRelationship.fromCode(code);
            return new SimpleCode(code, relationship.getSystem(), relationship.getDisplay(), version);
        } else {
            // If code is not found in our mapping, return the code itself with no system or display. 
            return new SimpleCode(val, null, null, null);
        }
    };

    // Maps from IN1.17 or IN2.72 to a boolean string TRUE if RelatedPerson should be created
    public static final ValueExtractor RELATED_PERSON_NEEDED_IN117 = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, "RelatedPersonNeededIN117");
    };

    // Maps from IN1.17 or IN2.72 to a boolean string TRUE if RelatedPerson should be created
    public static final ValueExtractor RELATED_PERSON_NEEDED_IN272 = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, "RelatedPersonNeededIN272");
    };

    public static final ValueExtractor MARITAL_STATUS = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String text = Hl7DataHandlerUtil.getOriginalDisplayText(value);
        String code = getFHIRCode(val, V3MaritalStatus.class);
        String version = Hl7DataHandlerUtil.getVersion(value);
        if (code != null) {
            V3MaritalStatus mar = V3MaritalStatus.fromCode(code);
            return new SimpleCode(code, mar.getSystem(), mar.getDisplay(), version);
        } else {
            // If code is null, it means the code wasn't known in our table, and can't be looked up.
            // Make a message in the display.
            String theSystem = V3MaritalStatus.M.getSystem();
            return new SimpleCode(null, theSystem,
                    String.format(INVALID_CODE_MESSAGE_FULL, val, theSystem, text, version));
        }
    };

    public static final ValueExtractor BOOLEAN = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (null == val) {
            return false;
        }
        return BooleanUtils.toBoolean(val);

    };

    public static final ValueExtractor INTEGER = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (null == val) {
            return null;
        }
        if (NumberUtils.isCreatable(val)) {
            return NumberUtils.createInteger(val);
        } else {
            LOGGER.warn("Value {} for INTEGER is not a valid number so returning null.", value);
            return null;
        }
    };

    public static final ValueExtractor FLOAT = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (null == val) {
            return null;
        }
        if (NumberUtils.isCreatable(val)) {
            return NumberUtils.createFloat(val);
        } else {
            LOGGER.warn("Value {} for DECIMAL is not a valid number so returning null.", value);
            return null;
        }
    };

    public static final ValueExtractor UUID_VAL = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getUUID(val);
    };

    public static final ValueExtractor BASE64_BINARY = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return Base64.getEncoder().encodeToString(val.getBytes());
    };

    public static final ValueExtractor OBJECT = (Object value) -> {
        return value;
    };

    // Special case of a SYSTEM V2.  Identifiers allow unknown codes.
    // When an unknown code is detected, return a null so that the text is displayed instead.
    // Only a known code returns a coding.
    public static final ValueExtractor CODING_SYSTEM_V2_IDENTIFIER = (Object value) -> {
        value = checkForAndUnwrapVariesObject(value);
        String table = Hl7DataHandlerUtil.getTableNumber(value);
        String code = Hl7DataHandlerUtil.getStringValue(value);
        if (table != null && code != null) {
            // Found table and a code. Try looking it up.
            SimpleCode coding = TerminologyLookup.lookup(table, code);
            // A non-null, non-empty value in display means a good code from lookup.
            if (coding != null && coding.getDisplay() != null && !coding.getDisplay().isEmpty()) {
                return coding;
            }
        }
        // All other situations return null.  
        return null;
    };

    // Special case of a SYSTEM V2.  User-defined tables IS allow unknown codes.
    // When an unknown code is detected, return a coding with the code but no system, 
    // so the code is retained. Only a known code returns a system.
    // Compare function CODING_SYSTEM_V2_IDENTIFIER for slightly different behavior 
    public static final ValueExtractor CODING_SYSTEM_V2_IS_USER_DEFINED_TABLE = (Object value) -> {
        value = checkForAndUnwrapVariesObject(value);
        String table = Hl7DataHandlerUtil.getTableNumber(value);
        String code = Hl7DataHandlerUtil.getStringValue(value);
        if (table != null && code != null) {
            // Found table and a code. Try looking it up.
            SimpleCode coding = TerminologyLookup.lookup(table, code);
            // A non-null, non-empty value in display means a good code from lookup.
            if (coding != null && coding.getDisplay() != null && !coding.getDisplay().isEmpty()) {
                return coding;
            } else {
                // Otherwise, must be a user-defined code, so return the code without a system
                return new SimpleCode(code, null, null, null);
            }
        } else if (table == null && code != null) {
            // Handle a code with unknown table
            return new SimpleCode(code, null, null, null);
        }
        // All other situations return null.  
        return null;
    };

    public static final ValueExtractor CODING_SYSTEM_V2_ALTERNATE = (Object value) -> {
        value = checkForAndUnwrapVariesObject(value);
        // ensure we have a CWE
        if (value instanceof CWE) {
            CWE cwe = (CWE) value;
            String table = Hl7DataHandlerUtil.getStringValue(cwe.getCwe6_NameOfAlternateCodingSystem());
            String code = Hl7DataHandlerUtil.getStringValue(cwe.getCwe4_AlternateIdentifier());
            String text = Hl7DataHandlerUtil.getStringValue(cwe.getCwe5_AlternateText());
            String version = Hl7DataHandlerUtil.getStringValue(cwe.getCwe8_AlternateCodingSystemVersionID());
            return commonCodingSystemV2(table, code, text, version);
        }
        return null;
    };

    public static final ValueExtractor CODING_SYSTEM_V2 = (Object value) -> {
        value = checkForAndUnwrapVariesObject(value);
        String table = Hl7DataHandlerUtil.getTableNumber(value);
        String code = Hl7DataHandlerUtil.getStringValue(value);
        String text = Hl7DataHandlerUtil.getOriginalDisplayText(value);
        String version = Hl7DataHandlerUtil.getVersion(value);
        return commonCodingSystemV2(table, code, text, version);
    };

    public static final ValueExtractor UNIT_SYSTEM = (Object value) -> {
        value = checkForAndUnwrapVariesObject(value);
        String table = Hl7DataHandlerUtil.getTableNumber(value);
        String code = Hl7DataHandlerUtil.getStringValue(value);
        String text = Hl7DataHandlerUtil.getOriginalDisplayText(value);
        String version = Hl7DataHandlerUtil.getVersion(value);

        SimpleCode codingSystem = commonCodingSystemV2(table, code, text, version);
        if (codingSystem.getSystem() != null) {
            return codingSystem.getSystem();
        }
        return "http://unitsofmeasure.org";

    };

    // For OBX.5 and other dynamic encoded fields, the real class is wrapped in the Varies class, and must be extracted from data
    private static final Object checkForAndUnwrapVariesObject(Object value) {
        if (value instanceof Varies) {
            Varies v = (Varies) value;
            value = v.getData();
        }
        return value;
    }

    private static final SimpleCode commonCodingSystemV2(String table, String code, String text, String version) {
        if (table != null && code != null) {
            // Found table and a code. Try looking it up.
            SimpleCode coding = TerminologyLookup.lookup(table, code);
            if (coding != null) {
                String display = coding.getDisplay();
                // Successful display confirms a valid code and system 
                if (display != null) {

                    if (display.isEmpty()) {
                        // We have a table, code, but unknown display, so we can't tell if it's good, use the original display text
                        coding = new SimpleCode(coding.getCode(), coding.getSystem(), text, version);
                    }
                    coding.setVersion(version);
                    // We have a table, code, and display, so code was valid
                    return coding;
                } else {
                    // Display was not found; create an error message in the display text
                    return new SimpleCode(null, coding.getSystem(),
                            String.format(INVALID_CODE_MESSAGE_FULL, code, coding.getSystem(), text, version));
                }
            } else {
                // No success looking up the code, build our own fall-back system using table name
                return new SimpleCode(code, "urn:id:" + table, text, version);
            }
        } else if (code != null) {
            // A code but no system: build a simple systemless code
            return new SimpleCode(code, null, text);
        } else {
            return null;
        }
    }

    public static final ValueExtractor BUILD_IDENTIFIER_FROM_CWE = (Object value) -> {
        if (value instanceof Varies) {
            Varies variesValue = ((Varies) value);
            if (variesValue.getData() instanceof CWE) {
                value = (CWE) variesValue.getData();
            }
        }
        if (value instanceof CWE) {
            CWE newValue = ((CWE) value);
            String identifier = newValue.getCwe1_Identifier().toString();
            String text = newValue.getCwe2_Text().toString();
            String codingSystem = newValue.getCwe3_NameOfCodingSystem().toString();
            if (identifier != null) {
                if (codingSystem != null) {
                    String join = identifier + "-" + codingSystem;
                    return join;
                } else {
                    return identifier;
                }
            } else
                return text;
        }

        return null;
    };

    public static final ValueExtractor ALLERGY_INTOLERANCE_CRITICALITY_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, AllergyIntoleranceCriticality.class);
        if (code != null) {
            return code;
        } else {
            return null;
        }
    };

    public static final ValueExtractor ALLERGY_INTOLERANCE_CATEGORY_CODE_FHIR = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return getFHIRCode(val, AllergyIntoleranceCategory.class);
    };

    public static final ValueExtractor DOSE_VALUE = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);

        if (val.startsWith("999")) {
            // 999 is used when the vaccination is refused. In this case we should return null.
            return null;
        } else
            return val;

    };

    public static final ValueExtractor DOSE_SYSTEM = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String url = UrlLookup.getAssociatedUrl(val);
        if (url != null) {
            return url;
        } else {
            if (val != null && val.length() > 0) {
                return "urn:id:" + val.replace(" ", "_");
            }
        }
        return null;
    };

    public static final ValueExtractor SYSTEM_URL = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        return UrlLookup.getAssociatedUrl(val);
    };

    // Convert an authority string to a valid system value
    // Prepend "urn:id:"; convert any spaces to underscores
    public static final ValueExtractor SYSTEM_ID = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (val != null && val.length() > 0) {
            return "urn:id:" + val.replace(" ", "_");
        } else {
            return null;
        }

    };

    public static final ValueExtractor> ARRAY = (Object value) -> {
        if (value != null) {
            List list = new ArrayList<>();
            list.add(value);
            return list;
        }
        return null;
    };

    public static final ValueExtractor RELATIVE_REFERENCE = (Object value) -> {
        Map mapValue = null;
        if (value instanceof Map) {
            mapValue = (Map) value;
        } else if (value instanceof ResourceValue) {
            ResourceValue rv = (ResourceValue) value;
            mapValue = rv.getResource();
        }
        if (mapValue != null) {
            String type = Hl7DataHandlerUtil.getStringValue(mapValue.get("resourceType"));
            String refId = Hl7DataHandlerUtil.getStringValue(mapValue.get("id"));
            if (StringUtils.isNotBlank(type) && StringUtils.isNotBlank(refId)) {
                return type + "/" + refId;
            }
        }
        return null;
    };

    public static final ValueExtractor DIAGNOSTIC_REPORT_STATUS_CODES = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, DiagnosticReportStatus.class);
        if (code != null) {
            return code;
        } else {
            return DiagnosticReportStatus.UNKNOWN.toCode();
        }
    };

    public static final ValueExtractor NAMED_UUID = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        if (StringUtils.isNotBlank(val)) {
            return UUID.nameUUIDFromBytes(val.getBytes());
        }
        return null;
    };

    public static final ValueExtractor MESSAGE_REASON_ENCOUNTER = (Object value) -> {
        String val = Hl7DataHandlerUtil.getStringValue(value);
        String code = getFHIRCode(val, MessageReasonEncounter.class);
        if (code != null) {
            MessageReasonEncounter en = MessageReasonEncounter.fromCode(code);
            return new SimpleCode(code, en.getSystem(), en.getDisplay());
        } else {
            // If code is null, it means the code wasn't known in our table, and can't be looked up.
            // Make a message in the display.
            String theSystem = MessageReasonEncounter.ADMIT.getSystem();
            return new SimpleCode(null, theSystem, String.format(INVALID_CODE_MESSAGE_SHORT, val, theSystem));
        }
    };

    public static final ValueExtractor PATIENT_INSTRUCTION = (Object value) -> {
        if (value instanceof CWE) {
            CWE cwe = (CWE) value;
            String cwe1 = cwe.getCwe1_Identifier().toString();
            String cwe2 = cwe.getCwe2_Text().toString();
            if (cwe1 != null) {
                if (cwe2 != null) {
                    return cwe1 + ":" + cwe2;
                }
            } else if (cwe1 == null) {
                return cwe2;
            }
        }
        return null;
    };

    private SimpleDataValueResolver() {
    }

    private static UUID getUUID(String value) {
        if (value != null) {
            try {
                return UUID.fromString(value);
            } catch (IllegalArgumentException e) {
                LOGGER.warn("Value not valid UUID");
                
                return null;
            }
        } else {
            LOGGER.info("Value for UUID is null");
            
            return null;
        }
    }

    private static boolean isValidUUID(String val) {
        try {
            UUID.fromString(val);
            return true;
        } catch (IllegalArgumentException e) {
            LOGGER.warn("Could not extract valid UUID - not a valid UUID");
            
            return false;
        }
    }

    public static String getFHIRCode(String hl7Value, Class fhirConceptClassName) {
        return getFHIRCode(hl7Value, fhirConceptClassName.getSimpleName());
    }

    public static String getFHIRCode(String hl7Value, String fhirMappingConceptName) {
        if (hl7Value != null) {
            Map mapping = Hl7v2Mapping.getMapping(fhirMappingConceptName);
            if (mapping != null && !mapping.isEmpty()) {
                return mapping.get(StringUtils.upperCase(hl7Value, Locale.ENGLISH));
            } else {
                return null;
            }
        } else {
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy