/**
 * Parse and save.
 *
 * @param  string  &$pprow   A row from the procedure_providers table.
 * @param  string  &$hl7     The input HL7 text.
 * @return string            Error text, or empty if no errors.
 */
function receive_hl7_results(&$hl7)
{
    if (substr($hl7, 0, 3) != 'MSH') {
        return xl('Input does not begin with a MSH segment');
    }
    // End-of-line delimiter for text in procedure_result.comments
    $commentdelim = "\n";
    $today = time();
    $in_message_id = '';
    $in_ssn = '';
    $in_dob = '';
    $in_lname = '';
    $in_fname = '';
    $in_orderid = 0;
    $in_procedure_code = '';
    $in_report_status = '';
    $in_encounter = 0;
    $porow = false;
    $pcrow = false;
    $procedure_report_id = 0;
    $arep = array();
    // holding area for OBR and its NTE data
    $ares = array();
    // holding area for OBX and its NTE data
    // This is so we know where we are if a segment like NTE that can appear in
    // different places is encountered.
    $context = '';
    // Delimiters
    $d0 = "\r";
    $d1 = substr($hl7, 3, 1);
    // typically |
    $d2 = substr($hl7, 4, 1);
    // typically ^
    $d3 = substr($hl7, 5, 1);
    // typically ~
    $segs = explode($d0, $hl7);
    foreach ($segs as $seg) {
        if (empty($seg)) {
            continue;
        }
        $a = explode($d1, $seg);
        if ($a[0] == 'MSH') {
            $context = $a[0];
            if ($a[8] != 'ORU^R01') {
                return xl('Message type') . " '{$a[8]}' " . xl('does not seem valid');
            }
            $in_message_id = $a[9];
        } else {
            if ($a[0] == 'PID') {
                $context = $a[0];
                rhl7FlushResult($ares);
                // Next line will do something only if there was a report with no results.
                rhl7FlushReport($arep);
                $in_ssn = $a[4];
                $in_dob = $a[7];
                // yyyymmdd format
                $tmp = explode($d2, $a[5]);
                $in_lname = $tmp[0];
                $in_fname = $tmp[1];
            } else {
                if ($a[0] == 'PV1') {
                    // Save placer encounter number if present.
                    if (!empty($a[19])) {
                        $tmp = explode($d2, $a[19]);
                        $in_encounter = intval($tmp[0]);
                    }
                } else {
                    if ($a[0] == 'ORC') {
                        $context = $a[0];
                        rhl7FlushResult($ares);
                        // Next line will do something only if there was a report with no results.
                        rhl7FlushReport($arep);
                        $porow = false;
                        $pcrow = false;
                        if ($a[2]) {
                            $in_orderid = intval($a[2]);
                        }
                    } else {
                        if ($a[0] == 'NTE' && $context == 'ORC') {
                            // TBD? Is this ever used?
                        } else {
                            if ($a[0] == 'OBR') {
                                $context = $a[0];
                                rhl7FlushResult($ares);
                                // Next line will do something only if there was a report with no results.
                                rhl7FlushReport($arep);
                                $procedure_report_id = 0;
                                if ($a[2]) {
                                    $in_orderid = intval($a[2]);
                                }
                                $tmp = explode($d2, $a[4]);
                                $in_procedure_code = $tmp[0];
                                $in_procedure_name = $tmp[1];
                                $in_report_status = rhl7ReportStatus($a[25]);
                                if (empty($porow)) {
                                    $porow = sqlQuery("SELECT * FROM procedure_order WHERE " . "procedure_order_id = ?", array($in_orderid));
                                    // The order must already exist. Currently we do not handle electronic
                                    // results returned for manual orders.
                                    if (empty($porow)) {
                                        return xl('Procedure order') . " '{$in_orderid}' " . xl('was not found');
                                    }
                                    if ($in_encounter) {
                                        if ($porow['encounter_id'] != $in_encounter) {
                                            return xl('Encounter ID') . " '" . $porow['encounter_id'] . "' " . xl('for OBR placer order number') . " '{$in_orderid}' " . xl('does not match the PV1 encounter number') . " '{$in_encounter}'";
                                        }
                                    } else {
                                        // They did not return an encounter number to verify, so more checking
                                        // might be done here to make sure the patient seems to match.
                                    }
                                }
                                // Find the order line item (procedure code) that matches this result.
                                $pcquery = "SELECT pc.* FROM procedure_order_code AS pc " . "WHERE pc.procedure_order_id = ? AND pc.procedure_code = ? " . "ORDER BY procedure_order_seq LIMIT 1";
                                $pcrow = sqlQuery($pcquery, array($in_orderid, $in_procedure_code));
                                if (empty($pcrow)) {
                                    // There is no matching procedure in the order, so it must have been
                                    // added after the original order was sent, either as a manual request
                                    // from the physician or as a "reflex" from the lab.
                                    // procedure_source = '2' indicates this.
                                    sqlInsert("INSERT INTO procedure_order_code SET " . "procedure_order_id = ?, " . "procedure_code = ?, " . "procedure_name = ?, " . "procedure_source = '2'", array($in_orderid, $in_procedure_code, $in_procedure_name));
                                    $pcrow = sqlQuery($pcquery, array($in_orderid, $in_procedure_code));
                                }
                                $arep = array();
                                $arep['procedure_order_id'] = $in_orderid;
                                $arep['procedure_order_seq'] = $pcrow['procedure_order_seq'];
                                $arep['date_collected'] = rhl7DateTime($a[7]);
                                $arep['date_report'] = substr(rhl7DateTime($a[22]), 0, 10);
                                $arep['report_status'] = $in_report_status;
                                $arep['report_notes'] = '';
                            } else {
                                if ($a[0] == 'NTE' && $context == 'OBR') {
                                    $arep['report_notes'] .= rhl7Text($a[3]) . "\n";
                                } else {
                                    if ($a[0] == 'OBX') {
                                        $context = $a[0];
                                        rhl7FlushResult($ares);
                                        if (!$procedure_report_id) {
                                            $procedure_report_id = rhl7FlushReport($arep);
                                        }
                                        $ares = array();
                                        $ares['procedure_report_id'] = $procedure_report_id;
                                        // OBX-5 can be a very long string of text with "~" as line separators.
                                        // The first line of comments is reserved for such things.
                                        if (strlen($a[5]) > 200) {
                                            $ares['result_data_type'] = 'L';
                                            $ares['result'] = '';
                                            $ares['comments'] = rhl7Text($a[5]) . $commentdelim;
                                        } else {
                                            $ares['result_data_type'] = substr($a[2], 0, 1);
                                            // N, S or F
                                            $ares['result'] = rhl7Text($a[5]);
                                            $ares['comments'] = $commentdelim;
                                        }
                                        $tmp = explode($d2, $a[3]);
                                        $ares['result_code'] = rhl7Text($tmp[0]);
                                        $ares['result_text'] = rhl7Text($tmp[1]);
                                        $ares['date'] = rhl7DateTime($a[14]);
                                        $ares['facility'] = rhl7Text($a[15]);
                                        $ares['units'] = rhl7Text($a[6]);
                                        $ares['range'] = rhl7Text($a[7]);
                                        $ares['abnormal'] = rhl7Abnormal($a[8]);
                                        // values are lab dependent
                                        $ares['result_status'] = rhl7ReportStatus($a[11]);
                                    } else {
                                        if ($a[0] == 'NTE' && $context == 'OBX') {
                                            $ares['comments'] .= rhl7Text($a[3]) . $commentdelim;
                                        } else {
                                            return xl('Segment name') . " '{$a[0]}' " . xl('is misplaced or unknown');
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    rhl7FlushResult($ares);
    // Next line will do something only if there was a report with no results.
    rhl7FlushReport($arep);
    return '';
}
/**
 * Parse and save.
 *
 * @param  string  &$hl7      The input HL7 text
 * @param  string  &$matchreq Array of shared patient matching requests
 * @param  int     $lab_id    Lab ID
 * @param  char    $direction B=Bidirectional, R=Results-only
 * @param  bool    $dryrun    True = do not update anything, just report errors
 * @param  array   $matchresp Array of responses to match requests; key is relative segment number,
 *                            value is an existing pid or 0 to specify creating a patient
 * @return array              Array of errors and match requests, if any
 */
function receive_hl7_results(&$hl7, &$matchreq, $lab_id = 0, $direction = 'B', $dryrun = false, $matchresp = NULL)
{
    global $rhl7_return;
    // This will hold returned error messages and related variables.
    $rhl7_return = array();
    $rhl7_return['mssgs'] = array();
    $rhl7_return['needmatch'] = false;
    // indicates if this file is pending a match request
    $rhl7_segnum = 0;
    if (substr($hl7, 0, 3) != 'MSH') {
        return rhl7LogMsg(xl('Input does not begin with a MSH segment'), true);
    }
    // End-of-line delimiter for text in procedure_result.comments
    // Ensoftek: Different labs seem to send different EOLs. Edit HL7 input to a character we know.
    $commentdelim = "\r";
    $hl7 = (string) str_replace(array("\r\n", "\r", "\n"), $commentdelim, $hl7);
    $today = time();
    $in_message_id = '';
    $in_ssn = '';
    $in_dob = '';
    $in_lname = '';
    $in_fname = '';
    $in_orderid = 0;
    $in_procedure_code = '';
    $in_report_status = '';
    $in_encounter = 0;
    $patient_id = 0;
    // for results-only patient matching logic
    $porow = false;
    $pcrow = false;
    $oprow = false;
    $procedure_report_id = 0;
    $arep = array();
    // holding area for OBR and its NTE data
    $ares = array();
    // holding area for OBX and its NTE data
    $code_seq_array = array();
    // tracks sequence numbers of order codes
    $results_category_id = 0;
    // document category ID for lab results
    // This is so we know where we are if a segment like NTE that can appear in
    // different places is encountered.
    $context = '';
    // This will be "ORU" or "MDM".
    $msgtype = '';
    // Stuff collected for MDM documents.
    $mdm_datetime = '';
    $mdm_docname = '';
    $mdm_text = '';
    // Delimiters
    $d0 = $commentdelim;
    // Ensoftek: Convert all EOLs to \r, so we don't have to deal with inconsistencies.
    $d1 = substr($hl7, 3, 1);
    // typically |
    $d2 = substr($hl7, 4, 1);
    // typically ^
    $d3 = substr($hl7, 5, 1);
    // typically ~
    // We'll need the document category IDs for any embedded documents.
    $catrow = sqlQuery("SELECT id FROM categories WHERE name = ?", array($GLOBALS['lab_results_category_name']));
    if (empty($catrow['id'])) {
        return rhl7LogMsg(xl('Document category for lab results does not exist') . ': ' . $GLOBALS['lab_results_category_name'], true);
    } else {
        $results_category_id = $catrow['id'];
        $mdm_category_id = $results_category_id;
        $catrow = sqlQuery("SELECT id FROM categories WHERE name = ?", array($GLOBALS['gbl_mdm_category_name']));
        if (!empty($catrow['id'])) {
            $mdm_category_id = $catrow['id'];
        }
    }
    $segs = explode($d0, $hl7);
    foreach ($segs as $seg) {
        if (empty($seg)) {
            continue;
        }
        // echo "<!-- $dryrun $seg -->\n"; // debugging
        ++$rhl7_segnum;
        $a = explode($d1, $seg);
        if ($a[0] == 'MSH') {
            // The following 2 cases happen only for multiple MSH segments in the same file.
            // But that probably shouldn't happen anyway?
            if ('ORU' == $msgtype && !$dryrun) {
                rhl7FlushResult($ares);
                rhl7FlushReport($arep);
                $ares = array();
                $arep = array();
            }
            if ('MDM' == $msgtype && !$dryrun) {
                $rc = rhl7FlushMDM($patient_id, $mdm_docname, $mdm_datetime, $mdm_text, $mdm_category_id, $oprow ? $oprow['username'] : 0);
                if ($rc) {
                    return rhl7LogMsg($rc);
                }
                $patient_id = 0;
            }
            $context = $a[0];
            // Ensoftek: Could come is as 'ORU^R01^ORU_R01'. Handle all cases when 'ORU^R01' is seen.
            if (strstr($a[8], "ORU^R01")) {
                $msgtype = 'ORU';
            } else {
                if ($a[8] == 'MDM^T02' || $a[8] == 'MDM^T04' || $a[8] == 'MDM^T08') {
                    $msgtype = 'MDM';
                    $mdm_datetime = '';
                    $mdm_docname = '';
                    $mdm_text = '';
                } else {
                    return rhl7LogMsg(xl('MSH.8 message type is not supported') . ": '" . $a[8] . "'", true);
                }
            }
            $in_message_id = $a[9];
        } else {
            if ($a[0] == 'PID') {
                $context = $a[0];
                if ('ORU' == $msgtype && !$dryrun) {
                    // Note these calls do nothing if the passed array is empty.
                    rhl7FlushResult($ares);
                    rhl7FlushReport($arep);
                }
                if ('MDM' == $msgtype && !$dryrun) {
                    $rc = rhl7FlushMDM($patient_id, $mdm_docname, $mdm_datetime, $mdm_text, $mdm_category_id, $oprow ? $oprow['username'] : 0);
                    if ($rc) {
                        return rhl7LogMsg($rc);
                    }
                }
                $ares = array();
                $arep = array();
                $porow = false;
                $pcrow = false;
                $oprow = false;
                $in_orderid = 0;
                $in_ssn = preg_replace('/[^0-9]/', '', $a[4]);
                $in_dob = rhl7Date($a[7]);
                $tmp = explode($d2, $a[5]);
                $in_lname = rhl7Text($tmp[0]);
                $in_fname = rhl7Text($tmp[1]);
                $patient_id = 0;
                // Patient matching is needed for a results-only interface or MDM message type.
                if ('R' == $direction || 'MDM' == $msgtype) {
                    $ptarr = array('ss' => strtoupper($in_ss), 'fname' => strtoupper($in_fname), 'lname' => strtoupper($in_lname), 'DOB' => strtoupper($in_dob));
                    $patient_id = match_patient($ptarr);
                    if ($patient_id == -1) {
                        // Result is indeterminate.
                        // Make a stringified form of $ptarr to use as a key.
                        $ptstring = serialize($ptarr);
                        // Check if the user has specified the patient.
                        if (isset($matchresp[$ptstring])) {
                            // This will be an existing pid, or 0 to specify creating a patient.
                            $patient_id = intval($matchresp[$ptstring]);
                        } else {
                            if ($dryrun) {
                                // Nope, ask the user to match.
                                $matchreq[$ptstring] = true;
                                $rhl7_return['needmatch'] = true;
                            } else {
                                // Should not happen, but it would be bad to abort now.  Create the patient.
                                $patient_id = 0;
                                rhl7LogMsg(xl('Unexpected non-match, creating new patient for segment') . ' ' . $rhl7_segnum, false);
                            }
                        }
                    }
                    if ($patient_id == 0 && !$dryrun) {
                        // We must create the patient.
                        $patient_id = create_skeleton_patient($ptarr);
                    }
                    if ($patient_id == -1) {
                        $patient_id = 0;
                    }
                }
                // end results-only/MDM logic
            } else {
                if ('PV1' == $a[0]) {
                    if ('ORU' == $msgtype) {
                        // Save placer encounter number if present.
                        if ($direction != 'R' && !empty($a[19])) {
                            $tmp = explode($d2, $a[19]);
                            $in_encounter = intval($tmp[0]);
                        }
                    } else {
                        if ('MDM' == $msgtype) {
                            // For documents we want the ordering provider.
                            // Try Referring Provider first.
                            $oprow = match_provider(explode($d2, $a[8]));
                            // If no match, try Other Provider.
                            if (empty($oprow)) {
                                $oprow = match_provider(explode($d2, $a[52]));
                            }
                        }
                    }
                } else {
                    if ('ORC' == $a[0] && 'ORU' == $msgtype) {
                        $context = $a[0];
                        if (!$dryrun) {
                            rhl7FlushResult($ares);
                        }
                        $ares = array();
                        // Next line will do something only if there was a report with no results.
                        if (!$dryrun) {
                            rhl7FlushReport($arep);
                        }
                        $arep = array();
                        $porow = false;
                        $pcrow = false;
                        if ($direction != 'R' && $a[2]) {
                            $in_orderid = intval($a[2]);
                        }
                    } else {
                        if ('TXA' == $a[0] && 'MDM' == $msgtype) {
                            $context = $a[0];
                            $mdm_datetime = rhl7DateTime($a[4]);
                            $mdm_docname = rhl7Text($a[12]);
                        } else {
                            if ($a[0] == 'NTE' && ($context == 'ORC' || $context == 'TXA')) {
                                // Is this ever used?
                            } else {
                                if ('OBR' == $a[0] && 'ORU' == $msgtype) {
                                    $context = $a[0];
                                    if (!$dryrun) {
                                        rhl7FlushResult($ares);
                                    }
                                    $ares = array();
                                    // Next line will do something only if there was a report with no results.
                                    if (!$dryrun) {
                                        rhl7FlushReport($arep);
                                    }
                                    $arep = array();
                                    $procedure_report_id = 0;
                                    if ($direction != 'R' && $a[2]) {
                                        $in_orderid = intval($a[2]);
                                        $porow = false;
                                        $pcrow = false;
                                    }
                                    $tmp = explode($d2, $a[4]);
                                    $in_procedure_code = $tmp[0];
                                    $in_procedure_name = $tmp[1];
                                    $in_report_status = rhl7ReportStatus($a[25]);
                                    //
                                    if ($direction == 'R') {
                                        // $in_orderid will be 0 here.
                                        // Save their order ID to procedure_order.control_id.
                                        // That column will need to change from bigint to varchar.
                                        // Look for an existing order using that plus lab_id.
                                        // Ordering provider is OBR.16 (NPI^Last^First).
                                        // Might not need to create a dummy encounter.
                                        // Need also provider_id (probably), patient_id, date_ordered, lab_id.
                                        // We have observation date/time in OBR.7.
                                        // We have report date/time in OBR.22.
                                        // We do not have an order date.
                                        $external_order_id = empty($a[2]) ? $a[3] : $a[2];
                                        $porow = false;
                                        if ($external_order_id) {
                                            $porow = sqlQuery("SELECT * FROM procedure_order " . "WHERE lab_id = ? AND control_id = ? " . "ORDER BY procedure_order_id DESC LIMIT 1", array($lab_id, $external_order_id));
                                        }
                                        if (!empty($porow)) {
                                            $in_orderid = intval($porow['procedure_order_id']);
                                        } else {
                                            // Create order.
                                            // Need to identify the ordering provider and, if possible, a recent encounter.
                                            $datetime_report = rhl7DateTime($a[22]);
                                            $date_report = substr($datetime_report, 0, 10) . ' 00:00:00';
                                            $encounter_id = 0;
                                            $provider_id = 0;
                                            // Look for the most recent encounter within 30 days of the report date.
                                            $encrow = sqlQuery("SELECT encounter FROM form_encounter WHERE " . "pid = ? AND date <= ? AND DATE_ADD(date, INTERVAL 30 DAY) > ? " . "ORDER BY date DESC, encounter DESC LIMIT 1", array($patient_id, $date_report, $date_report));
                                            if (!empty($encrow)) {
                                                $encounter_id = intval($encrow['encounter']);
                                                $provider_id = intval($encrow['provider_id']);
                                            }
                                            if (!$provider_id) {
                                                // Attempt ordering provider matching by name or NPI.
                                                $oprow = match_provider(explode($d2, $a[16]));
                                                if (!empty($oprow)) {
                                                    $provider_id = intval($oprow['id']);
                                                }
                                            }
                                            if (!$dryrun) {
                                                // Now create the procedure order.
                                                $in_orderid = sqlInsert("INSERT INTO procedure_order SET " . "date_ordered   = ?, " . "provider_id    = ?, " . "lab_id         = ?, " . "date_collected = ?, " . "date_transmitted = ?, " . "patient_id     = ?, " . "encounter_id   = ?, " . "control_id     = ?", array($datetime_report, $provider_id, $lab_id, rhl7DateTime($a[22]), rhl7DateTime($a[7]), $patient_id, $encounter_id, $external_order_id));
                                                // If an encounter was identified then link the order to it.
                                                if ($encounter_id && $in_orderid) {
                                                    addForm($encounter_id, "Procedure Order", $in_orderid, "procedure_order", $patient_id);
                                                }
                                            }
                                        }
                                        // end no $porow
                                    }
                                    // end results-only
                                    if (empty($porow)) {
                                        $porow = sqlQuery("SELECT * FROM procedure_order WHERE " . "procedure_order_id = ?", array($in_orderid));
                                        // The order must already exist. Currently we do not handle electronic
                                        // results returned for manual orders.
                                        if (empty($porow) && !($dryrun && $direction == 'R')) {
                                            return rhl7LogMsg(xl('Procedure order not found') . ": {$in_orderid}", true);
                                        }
                                        if ($in_encounter) {
                                            if ($direction != 'R' && $porow['encounter_id'] != $in_encounter) {
                                                return rhl7LogMsg(xl('Encounter ID') . " '" . $porow['encounter_id'] . "' " . xl('for OBR placer order number') . " '{$in_orderid}' " . xl('does not match the PV1 encounter number') . " '{$in_encounter}'");
                                            }
                                        } else {
                                            // They did not return an encounter number to verify, so more checking
                                            // might be done here to make sure the patient seems to match.
                                        }
                                        // Save the lab's control ID if there is one.
                                        $tmp = explode($d2, $a[3]);
                                        $control_id = $tmp[0];
                                        if ($control_id && empty($porow['control_id'])) {
                                            sqlStatement("UPDATE procedure_order SET control_id = ? WHERE " . "procedure_order_id = ?", array($control_id, $in_orderid));
                                        }
                                        $code_seq_array = array();
                                    }
                                    // Find the order line item (procedure code) that matches this result.
                                    // If there is more than one, then we select the one whose sequence number
                                    // is next after the last sequence number encountered for this procedure
                                    // code; this assumes that result OBRs are returned in the same sequence
                                    // as the corresponding OBRs in the order.
                                    if (!isset($code_seq_array[$in_procedure_code])) {
                                        $code_seq_array[$in_procedure_code] = 0;
                                    }
                                    $pcquery = "SELECT pc.* FROM procedure_order_code AS pc " . "WHERE pc.procedure_order_id = ? AND pc.procedure_code = ? " . "ORDER BY (procedure_order_seq <= ?), procedure_order_seq LIMIT 1";
                                    $pcqueryargs = array($in_orderid, $in_procedure_code, $code_seq_array[$in_procedure_code]);
                                    $pcrow = sqlQuery($pcquery, $pcqueryargs);
                                    if (empty($pcrow)) {
                                        // There is no matching procedure in the order, so it must have been
                                        // added after the original order was sent, either as a manual request
                                        // from the physician or as a "reflex" from the lab.
                                        // procedure_source = '2' indicates this.
                                        if (!$dryrun) {
                                            sqlInsert("INSERT INTO procedure_order_code SET " . "procedure_order_id = ?, " . "procedure_code = ?, " . "procedure_name = ?, " . "procedure_source = '2'", array($in_orderid, $in_procedure_code, $in_procedure_name));
                                            $pcrow = sqlQuery($pcquery, $pcqueryargs);
                                        } else {
                                            // Dry run, make a dummy procedure_order_code row.
                                            $pcrow = array('procedure_order_id' => $in_orderid, 'procedure_order_seq' => 0);
                                        }
                                    }
                                    $code_seq_array[$in_procedure_code] = 0 + $pcrow['procedure_order_seq'];
                                    $arep = array();
                                    $arep['procedure_order_id'] = $in_orderid;
                                    $arep['procedure_order_seq'] = $pcrow['procedure_order_seq'];
                                    $arep['date_collected'] = rhl7DateTime($a[7]);
                                    $arep['date_report'] = rhl7Date($a[22]);
                                    $arep['report_status'] = $in_report_status;
                                    $arep['report_notes'] = '';
                                } else {
                                    if ($a[0] == 'NTE' && $context == 'OBR') {
                                        $arep['report_notes'] .= rhl7Text($a[3]) . "\n";
                                    } else {
                                        if ('OBX' == $a[0] && 'ORU' == $msgtype) {
                                            $context = $a[0];
                                            if (!$dryrun) {
                                                rhl7FlushResult($ares);
                                            }
                                            $ares = array();
                                            if (!$procedure_report_id) {
                                                if (!$dryrun) {
                                                    $procedure_report_id = rhl7FlushReport($arep);
                                                }
                                                $arep = array();
                                            }
                                            $ares['procedure_report_id'] = $procedure_report_id;
                                            $ares['result_data_type'] = substr($a[2], 0, 1);
                                            // N, S, F or E
                                            $ares['comments'] = $commentdelim;
                                            if ($a[2] == 'ED') {
                                                // This is the case of results as an embedded document. We will create
                                                // a normal patient document in the assigned category for lab results.
                                                $tmp = explode($d2, $a[5]);
                                                $fileext = strtolower($tmp[0]);
                                                $filename = date("Ymd_His") . '.' . $fileext;
                                                $data = rhl7DecodeData($tmp[3], $tmp[4]);
                                                if ($data === FALSE) {
                                                    return rhl7LogMsg(xl('Invalid encapsulated data encoding type') . ': ' . $tmp[3]);
                                                }
                                                if (!$dryrun) {
                                                    $d = new Document();
                                                    $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data);
                                                    if ($rc) {
                                                        return rhl7LogMsg($rc);
                                                    }
                                                    $ares['document_id'] = $d->get_id();
                                                }
                                            } else {
                                                if (strlen($a[5]) > 200) {
                                                    // OBX-5 can be a very long string of text with "~" as line separators.
                                                    // The first line of comments is reserved for such things.
                                                    $ares['result_data_type'] = 'L';
                                                    $ares['result'] = '';
                                                    $ares['comments'] = rhl7Text($a[5]) . $commentdelim;
                                                } else {
                                                    $ares['result'] = rhl7Text($a[5]);
                                                }
                                            }
                                            $tmp = explode($d2, $a[3]);
                                            $ares['result_code'] = rhl7Text($tmp[0]);
                                            $ares['result_text'] = rhl7Text($tmp[1]);
                                            $ares['date'] = rhl7DateTime($a[14]);
                                            $ares['facility'] = rhl7Text($a[15]);
                                            // Ensoftek: Units may have mutiple segments(as seen in MU2 samples), parse and take just first segment.
                                            $tmp = explode($d2, $a[6]);
                                            $ares['units'] = rhl7Text($tmp[0]);
                                            $ares['range'] = rhl7Text($a[7]);
                                            $ares['abnormal'] = rhl7Abnormal($a[8]);
                                            // values are lab dependent
                                            $ares['result_status'] = rhl7ReportStatus($a[11]);
                                            // Ensoftek: Performing Organization Details. Goes into "Pending Review/Patient Results--->Notes--->Facility" section.
                                            $performingOrganization = getPerformingOrganizationDetails($a[23], $a[24], $a[25], $d2, $commentdelim);
                                            if (isset($performingOrganization)) {
                                                $ares['facility'] .= $performingOrganization . $commentdelim;
                                            }
                                        } else {
                                            if ('OBX' == $a[0] && 'MDM' == $msgtype) {
                                                $context = $a[0];
                                                if ($a[2] == 'TX') {
                                                    if ($mdm_text !== '') {
                                                        $mdm_text .= "\r\n";
                                                    }
                                                    $mdm_text .= rhl7Text($a[5]);
                                                } else {
                                                    return rhl7LogMsg(xl('Unsupported MDM OBX result type') . ': ' . $a[2]);
                                                }
                                            } else {
                                                if ('ZEF' == $a[0] && 'ORU' == $msgtype) {
                                                    // ZEF segment is treated like an OBX with an embedded Base64-encoded PDF.
                                                    $context = 'OBX';
                                                    if (!$dryrun) {
                                                        rhl7FlushResult($ares);
                                                    }
                                                    $ares = array();
                                                    if (!$procedure_report_id) {
                                                        if (!$dryrun) {
                                                            $procedure_report_id = rhl7FlushReport($arep);
                                                        }
                                                        $arep = array();
                                                    }
                                                    $ares['procedure_report_id'] = $procedure_report_id;
                                                    $ares['result_data_type'] = 'E';
                                                    $ares['comments'] = $commentdelim;
                                                    //
                                                    $fileext = 'pdf';
                                                    $filename = date("Ymd_His") . '.' . $fileext;
                                                    $data = rhl7DecodeData('Base64', $a[2]);
                                                    if ($data === FALSE) {
                                                        return rhl7LogMsg(xl('ZEF segment internal error'));
                                                    }
                                                    if (!$dryrun) {
                                                        $d = new Document();
                                                        $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data);
                                                        if ($rc) {
                                                            return rhl7LogMsg($rc);
                                                        }
                                                        $ares['document_id'] = $d->get_id();
                                                    }
                                                    $ares['date'] = $arep['date_report'];
                                                } else {
                                                    if ('NTE' == $a[0] && 'OBX' == $context && 'ORU' == $msgtype) {
                                                        $ares['comments'] .= rhl7Text($a[3]) . $commentdelim;
                                                    } else {
                                                        if ('SPM' == $a[0] && 'ORU' == $msgtype) {
                                                            if (!$dryrun) {
                                                                rhl7FlushResult($ares);
                                                            }
                                                            $ares = array();
                                                            if (!$procedure_report_id) {
                                                                if (!$dryrun) {
                                                                    $procedure_report_id = rhl7FlushReport($arep);
                                                                }
                                                                $arep = array();
                                                            }
                                                            rhl7UpdateReportWithSpecimen($a, $procedure_report_id, $d2);
                                                        } else {
                                                            if ('TQ1' == $a[0] && 'ORU' == $msgtype) {
                                                                // Ignore and do nothing.
                                                            } else {
                                                                return rhl7LogMsg(xl('Segment name') . " '{$a[0]}' " . xl('is misplaced or unknown'));
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    if ('ORU' == $msgtype && !$dryrun) {
        rhl7FlushResult($ares);
        // Next line does something only for a report with no results.
        rhl7FlushReport($arep);
    }
    if ('MDM' == $msgtype && !$dryrun) {
        $rc = rhl7FlushMDM($patient_id, $mdm_docname, $mdm_datetime, $mdm_text, $mdm_category_id, $oprow ? $oprow['username'] : 0);
        if ($rc) {
            return rhl7LogMsg($rc);
        }
    }
    return $rhl7_return;
}
Example #3
0
/**
 * Parse and save.
 *
 * @param  string  &$hl7      The input HL7 text.
 * @param  char    $direction B=Bidirectional, R=Results-only
 * @param  book    $dryrun    True = do not update anything, just report errors
 * @return array              Array of errors and match requests, if any.
 */
function receive_hl7_results(&$hl7, $lab_id = 0, $direction = 'B', $dryrun = false, $matchresp = NULL)
{
    // This will hold returned error messages and related variables.
    $rhl7_return = array();
    $rhl7_return['mssgs'] = array();
    $rhl7_return['match'] = array();
    $rhl7_segnum = 0;
    if (substr($hl7, 0, 3) != 'MSH') {
        return rhl7LogMsg(xl('Input does not begin with a MSH segment'), true);
    }
    // End-of-line delimiter for text in procedure_result.comments
    $commentdelim = "\n";
    $today = time();
    $in_message_id = '';
    $in_ssn = '';
    $in_dob = '';
    $in_lname = '';
    $in_fname = '';
    $in_orderid = 0;
    $in_procedure_code = '';
    $in_report_status = '';
    $in_encounter = 0;
    $patient_id = 0;
    // for results-only patient matching logic
    $porow = false;
    $pcrow = false;
    $procedure_report_id = 0;
    $arep = array();
    // holding area for OBR and its NTE data
    $ares = array();
    // holding area for OBX and its NTE data
    $code_seq_array = array();
    // tracks sequence numbers of order codes
    $results_category_id = 0;
    // document category ID for lab results
    // This is so we know where we are if a segment like NTE that can appear in
    // different places is encountered.
    $context = '';
    // Delimiters
    $d0 = "\r";
    $d1 = substr($hl7, 3, 1);
    // typically |
    $d2 = substr($hl7, 4, 1);
    // typically ^
    $d3 = substr($hl7, 5, 1);
    // typically ~
    // We'll need the document category ID for any embedded documents.
    $catrow = sqlQuery("SELECT id FROM categories WHERE name = ?", array($GLOBALS['lab_results_category_name']));
    if (empty($catrow['id'])) {
        return rhl7LogMsg(xl('Document category for lab results does not exist') . ': ' . $GLOBALS['lab_results_category_name'], true);
    } else {
        $results_category_id = $catrow['id'];
    }
    $segs = explode($d0, $hl7);
    foreach ($segs as $seg) {
        if (empty($seg)) {
            continue;
        }
        // echo "<!-- $dryrun $seg -->\n"; // debugging
        ++$rhl7_segnum;
        $a = explode($d1, $seg);
        if ($a[0] == 'MSH') {
            $context = $a[0];
            if ($a[8] != 'ORU^R01') {
                return rhl7LogMsg(xl('MSH.8 message type is not valid') . ": '" . $a[8] . "'", true);
            }
            $in_message_id = $a[9];
        } else {
            if ($a[0] == 'PID') {
                $context = $a[0];
                if (!$dryrun) {
                    rhl7FlushResult($ares);
                }
                $ares = array();
                // Next line will do something only if there was a report with no results.
                if (!$dryrun) {
                    rhl7FlushReport($arep);
                }
                $arep = array();
                $porow = false;
                $pcrow = false;
                $in_orderid = 0;
                $in_ssn = preg_replace('/[^0-9]/', '', $a[4]);
                $in_dob = rhl7Date($a[7]);
                $tmp = explode($d2, $a[5]);
                $in_lname = rhl7Text($tmp[0]);
                $in_fname = rhl7Text($tmp[1]);
                $patient_id = 0;
                if ($direction == 'R') {
                    $patient_id = match_patient($in_ss, $in_fname, $in_lname, $in_dob);
                    if ($patient_id == -1) {
                        // Indeterminate, check if the user has specified the patient.
                        if (isset($matchresp[$rhl7_segnum])) {
                            // This will be an existing pid, or 0 to specify creating a patient.
                            $patient_id = intval($matchresp[$rhl7_segnum]);
                        } else {
                            // Nope, ask the user to do so.
                            $rhl7_return['match'][$rhl7_segnum] = array('ss' => $in_ss, 'fname' => $in_fname, 'lname' => $in_lname, 'DOB' => $in_dob);
                        }
                    }
                    if ($patient_id == 0 && !$dryrun) {
                        // We must create the patient.
                        $patient_id = create_skeleton_patient(array('fname' => $in_fname, 'lname' => $in_lname, 'DOB' => $in_dob, 'ss' => $in_ssn));
                    }
                    if ($patient_id == -1) {
                        $patient_id = 0;
                    }
                }
                // end results-only logic
            } else {
                if ($a[0] == 'PV1') {
                    // Save placer encounter number if present.
                    if ($direction != 'R' && !empty($a[19])) {
                        $tmp = explode($d2, $a[19]);
                        $in_encounter = intval($tmp[0]);
                    }
                } else {
                    if ($a[0] == 'ORC') {
                        $context = $a[0];
                        if (!$dryrun) {
                            rhl7FlushResult($ares);
                        }
                        $ares = array();
                        // Next line will do something only if there was a report with no results.
                        if (!$dryrun) {
                            rhl7FlushReport($arep);
                        }
                        $arep = array();
                        $porow = false;
                        $pcrow = false;
                        if ($direction != 'R' && $a[2]) {
                            $in_orderid = intval($a[2]);
                        }
                    } else {
                        if ($a[0] == 'NTE' && $context == 'ORC') {
                            // Is this ever used?
                        } else {
                            if ($a[0] == 'OBR') {
                                $context = $a[0];
                                if (!$dryrun) {
                                    rhl7FlushResult($ares);
                                }
                                $ares = array();
                                // Next line will do something only if there was a report with no results.
                                if (!$dryrun) {
                                    rhl7FlushReport($arep);
                                }
                                $arep = array();
                                $procedure_report_id = 0;
                                if ($direction != 'R' && $a[2]) {
                                    $in_orderid = intval($a[2]);
                                    $porow = false;
                                    $pcrow = false;
                                }
                                $tmp = explode($d2, $a[4]);
                                $in_procedure_code = $tmp[0];
                                $in_procedure_name = $tmp[1];
                                $in_report_status = rhl7ReportStatus($a[25]);
                                //
                                if ($direction == 'R') {
                                    // $in_orderid will be 0 here.
                                    // Save their order ID to procedure_order.control_id.
                                    // That column will need to change from bigint to varchar.
                                    // Look for an existing order using that plus lab_id.
                                    // Ordering provider is OBR.16 (NPI^Last^First).
                                    // Might not need to create a dummy encounter.
                                    // Need also provider_id (probably), patient_id, date_ordered, lab_id.
                                    // We have observation date/time in OBR.7.
                                    // We have report date/time in OBR.22.
                                    // We do not have an order date.
                                    $external_order_id = empty($a[2]) ? $a[3] : $a[2];
                                    $porow = false;
                                    if ($external_order_id) {
                                        $porow = sqlQuery("SELECT * FROM procedure_order " . "WHERE lab_id = ? AND control_id = ? " . "ORDER BY procedure_order_id DESC LIMIT 1", array($lab_id, $external_order_id));
                                    }
                                    if (!empty($porow)) {
                                        $in_orderid = intval($porow['procedure_order_id']);
                                    } else {
                                        // Create order.
                                        // Need to identify the ordering provider and, if possible, a recent encounter.
                                        $datetime_report = rhl7DateTime($a[22]);
                                        $date_report = substr($datetime_report, 0, 10) . ' 00:00:00';
                                        $encounter_id = 0;
                                        $provider_id = 0;
                                        // Look for the most recent encounter within 30 days of the report date.
                                        $encrow = sqlQuery("SELECT encounter FROM form_encounter WHERE " . "pid = ? AND date <= ? AND DATE_ADD(date, INTERVAL 30 DAY) > ? " . "ORDER BY date DESC, encounter DESC LIMIT 1", array($patient_id, $date_report, $date_report));
                                        if (!empty($encrow)) {
                                            $encounter_id = intval($encrow['encounter']);
                                            $provider_id = intval($encrow['provider_id']);
                                        }
                                        if (!$provider_id) {
                                            // Attempt ordering provider matching by name or NPI.
                                            $op_lname = $op_fname = '';
                                            $tmp = explode($d2, $a[16]);
                                            $op_npi = preg_replace('/[^0-9]/', '', $tmp[0]);
                                            if (!empty($tmp[1])) {
                                                $op_lname = $tmp[1];
                                            }
                                            if (!empty($tmp[2])) {
                                                $op_fname = $tmp[2];
                                            }
                                            if ($op_npi || $op_fname && $op_lname) {
                                                if ($op_npi) {
                                                    if ($op_fname && $op_lname) {
                                                        $where = "(npi IS NOT NULL AND npi = ?) OR ((npi IS NULL OR npi = ?) AND lname = ? AND fname = ?)";
                                                        $qarr = array($op_npi, '', $op_lname, $op_fname);
                                                    } else {
                                                        $where = "npi IS NOT NULL AND npi = ?";
                                                        $qarr = array($op_npi);
                                                    }
                                                } else {
                                                    $where = "lname = ? AND fname = ?";
                                                    $qarr = array($op_lname, $op_fname);
                                                }
                                            }
                                            $oprow = sqlQuery("SELECT id FROM users WHERE {$where} " . "ORDER BY active DESC, authorized DESC, username DESC, id LIMIT 1", $qarr);
                                            if (!empty($oprow)) {
                                                $provider_id = intval($oprow['id']);
                                            }
                                        }
                                        if (!$dryrun) {
                                            // Now create the procedure order.
                                            $in_orderid = sqlInsert("INSERT INTO procedure_order SET " . "date_ordered   = ?, " . "provider_id    = ?, " . "lab_id         = ?, " . "date_collected = ?, " . "date_transmitted = ?, " . "patient_id     = ?, " . "encounter_id   = ?, " . "control_id     = ?", array($datetime_report, $provider_id, $lab_id, rhl7DateTime($a[22]), rhl7DateTime($a[7]), $patient_id, $encounter_id, $external_order_id));
                                            // If an encounter was identified then link the order to it.
                                            if ($encounter_id && $in_orderid) {
                                                addForm($encounter_id, "Procedure Order", $in_orderid, "procedure_order", $patient_id);
                                            }
                                        }
                                    }
                                    // end no $porow
                                }
                                // end results-only
                                if (empty($porow)) {
                                    $porow = sqlQuery("SELECT * FROM procedure_order WHERE " . "procedure_order_id = ?", array($in_orderid));
                                    // The order must already exist. Currently we do not handle electronic
                                    // results returned for manual orders.
                                    if (empty($porow) && !($dryrun && $direction == 'R')) {
                                        return rhl7LogMsg(xl('Procedure order not found') . ": {$in_orderid}", true);
                                    }
                                    if ($in_encounter) {
                                        if ($direction != 'R' && $porow['encounter_id'] != $in_encounter) {
                                            return rhl7LogMsg(xl('Encounter ID') . " '" . $porow['encounter_id'] . "' " . xl('for OBR placer order number') . " '{$in_orderid}' " . xl('does not match the PV1 encounter number') . " '{$in_encounter}'");
                                        }
                                    } else {
                                        // They did not return an encounter number to verify, so more checking
                                        // might be done here to make sure the patient seems to match.
                                    }
                                    // Save the lab's control ID if there is one.
                                    $tmp = explode($d2, $a[3]);
                                    $control_id = $tmp[0];
                                    if ($control_id && empty($porow['control_id'])) {
                                        sqlStatement("UPDATE procedure_order SET control_id = ? WHERE " . "procedure_order_id = ?", array($control_id, $in_orderid));
                                    }
                                    $code_seq_array = array();
                                }
                                // Find the order line item (procedure code) that matches this result.
                                // If there is more than one, then we select the one whose sequence number
                                // is next after the last sequence number encountered for this procedure
                                // code; this assumes that result OBRs are returned in the same sequence
                                // as the corresponding OBRs in the order.
                                if (!isset($code_seq_array[$in_procedure_code])) {
                                    $code_seq_array[$in_procedure_code] = 0;
                                }
                                $pcquery = "SELECT pc.* FROM procedure_order_code AS pc " . "WHERE pc.procedure_order_id = ? AND pc.procedure_code = ? " . "ORDER BY (procedure_order_seq <= ?), procedure_order_seq LIMIT 1";
                                $pcqueryargs = array($in_orderid, $in_procedure_code, $code_seq_array[$in_procedure_code]);
                                $pcrow = sqlQuery($pcquery, $pcqueryargs);
                                if (empty($pcrow)) {
                                    // There is no matching procedure in the order, so it must have been
                                    // added after the original order was sent, either as a manual request
                                    // from the physician or as a "reflex" from the lab.
                                    // procedure_source = '2' indicates this.
                                    if (!$dryrun) {
                                        sqlInsert("INSERT INTO procedure_order_code SET " . "procedure_order_id = ?, " . "procedure_code = ?, " . "procedure_name = ?, " . "procedure_source = '2'", array($in_orderid, $in_procedure_code, $in_procedure_name));
                                        $pcrow = sqlQuery($pcquery, $pcqueryargs);
                                    } else {
                                        // Dry run, make a dummy procedure_order_code row.
                                        $pcrow = array('procedure_order_id' => $in_orderid, 'procedure_order_seq' => 0);
                                    }
                                }
                                $code_seq_array[$in_procedure_code] = 0 + $pcrow['procedure_order_seq'];
                                $arep = array();
                                $arep['procedure_order_id'] = $in_orderid;
                                $arep['procedure_order_seq'] = $pcrow['procedure_order_seq'];
                                $arep['date_collected'] = rhl7DateTime($a[7]);
                                $arep['date_report'] = rhl7Date($a[22]);
                                $arep['report_status'] = $in_report_status;
                                $arep['report_notes'] = '';
                            } else {
                                if ($a[0] == 'NTE' && $context == 'OBR') {
                                    $arep['report_notes'] .= rhl7Text($a[3]) . "\n";
                                } else {
                                    if ($a[0] == 'OBX') {
                                        $context = $a[0];
                                        if (!$dryrun) {
                                            rhl7FlushResult($ares);
                                        }
                                        $ares = array();
                                        if (!$procedure_report_id) {
                                            if (!$dryrun) {
                                                $procedure_report_id = rhl7FlushReport($arep);
                                            }
                                            $arep = array();
                                        }
                                        $ares['procedure_report_id'] = $procedure_report_id;
                                        $ares['result_data_type'] = substr($a[2], 0, 1);
                                        // N, S, F or E
                                        $ares['comments'] = $commentdelim;
                                        if ($a[2] == 'ED') {
                                            // This is the case of results as an embedded document. We will create
                                            // a normal patient document in the assigned category for lab results.
                                            $tmp = explode($d2, $a[5]);
                                            $fileext = strtolower($tmp[0]);
                                            $filename = date("Ymd_His") . '.' . $fileext;
                                            $data = rhl7DecodeData($tmp[3], $tmp[4]);
                                            if ($data === FALSE) {
                                                return rhl7LogMsg(xl('Invalid encapsulated data encoding type') . ': ' . $tmp[3]);
                                            }
                                            if (!$dryrun) {
                                                $d = new Document();
                                                $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data);
                                                if ($rc) {
                                                    return rhl7LogMsg($rc);
                                                }
                                                $ares['document_id'] = $d->get_id();
                                            }
                                        } else {
                                            if (strlen($a[5]) > 200) {
                                                // OBX-5 can be a very long string of text with "~" as line separators.
                                                // The first line of comments is reserved for such things.
                                                $ares['result_data_type'] = 'L';
                                                $ares['result'] = '';
                                                $ares['comments'] = rhl7Text($a[5]) . $commentdelim;
                                            } else {
                                                $ares['result'] = rhl7Text($a[5]);
                                            }
                                        }
                                        $tmp = explode($d2, $a[3]);
                                        $ares['result_code'] = rhl7Text($tmp[0]);
                                        $ares['result_text'] = rhl7Text($tmp[1]);
                                        $ares['date'] = rhl7DateTime($a[14]);
                                        $ares['facility'] = rhl7Text($a[15]);
                                        $ares['units'] = rhl7Text($a[6]);
                                        $ares['range'] = rhl7Text($a[7]);
                                        $ares['abnormal'] = rhl7Abnormal($a[8]);
                                        // values are lab dependent
                                        $ares['result_status'] = rhl7ReportStatus($a[11]);
                                    } else {
                                        if ($a[0] == 'ZEF') {
                                            // ZEF segment is treated like an OBX with an embedded Base64-encoded PDF.
                                            $context = 'OBX';
                                            if (!$dryrun) {
                                                rhl7FlushResult($ares);
                                            }
                                            $ares = array();
                                            if (!$procedure_report_id) {
                                                if (!$dryrun) {
                                                    $procedure_report_id = rhl7FlushReport($arep);
                                                }
                                                $arep = array();
                                            }
                                            $ares['procedure_report_id'] = $procedure_report_id;
                                            $ares['result_data_type'] = 'E';
                                            $ares['comments'] = $commentdelim;
                                            //
                                            $fileext = 'pdf';
                                            $filename = date("Ymd_His") . '.' . $fileext;
                                            $data = rhl7DecodeData('Base64', $a[2]);
                                            if ($data === FALSE) {
                                                return rhl7LogMsg(xl('ZEF segment internal error'));
                                            }
                                            if (!$dryrun) {
                                                $d = new Document();
                                                $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data);
                                                if ($rc) {
                                                    return rhl7LogMsg($rc);
                                                }
                                                $ares['document_id'] = $d->get_id();
                                            }
                                            $ares['date'] = $arep['date_report'];
                                        } else {
                                            if ($a[0] == 'NTE' && $context == 'OBX') {
                                                $ares['comments'] .= rhl7Text($a[3]) . $commentdelim;
                                            } else {
                                                return rhl7LogMsg(xl('Segment name') . " '{$a[0]}' " . xl('is misplaced or unknown'));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    if (!$dryrun) {
        rhl7FlushResult($ares);
    }
    // Next line will do something only if there was a report with no results.
    if (!$dryrun) {
        rhl7FlushReport($arep);
    }
    return $rhl7_return;
}
/**
 * Parse and save.
 *
 * @param  string  &$pprow   A row from the procedure_providers table.
 * @param  string  &$hl7     The input HL7 text.
 * @return string            Error text, or empty if no errors.
 */
function receive_hl7_results(&$hl7)
{
    if (substr($hl7, 0, 3) != 'MSH') {
        return xl('Input does not begin with a MSH segment');
    }
    // End-of-line delimiter for text in procedure_result.comments
    $commentdelim = "\n";
    $today = time();
    $in_message_id = '';
    $in_ssn = '';
    $in_dob = '';
    $in_lname = '';
    $in_fname = '';
    $in_orderid = 0;
    $in_procedure_code = '';
    $in_report_status = '';
    $in_encounter = 0;
    $porow = false;
    $pcrow = false;
    $procedure_report_id = 0;
    $arep = array();
    // holding area for OBR and its NTE data
    $ares = array();
    // holding area for OBX and its NTE data
    $code_seq_array = array();
    // tracks sequence numbers of order codes
    // This is so we know where we are if a segment like NTE that can appear in
    // different places is encountered.
    $context = '';
    // Delimiters
    $d0 = "\r";
    $d1 = substr($hl7, 3, 1);
    // typically |
    $d2 = substr($hl7, 4, 1);
    // typically ^
    $d3 = substr($hl7, 5, 1);
    // typically ~
    // We'll need the document category ID for any embedded documents.
    $catrow = sqlQuery("SELECT id FROM categories WHERE name = ?", array($GLOBALS['lab_results_category_name']));
    if (empty($catrow['id'])) {
        return xl('Document category for lab results does not exist') . ': ' . $GLOBALS['lab_results_category_name'];
    }
    $results_category_id = $catrow['id'];
    $segs = explode($d0, $hl7);
    foreach ($segs as $seg) {
        if (empty($seg)) {
            continue;
        }
        $a = explode($d1, $seg);
        if ($a[0] == 'MSH') {
            $context = $a[0];
            if ($a[8] != 'ORU^R01') {
                return xl('Message type') . " '{$a[8]}' " . xl('does not seem valid');
            }
            $in_message_id = $a[9];
        } else {
            if ($a[0] == 'PID') {
                $context = $a[0];
                rhl7FlushResult($ares);
                // Next line will do something only if there was a report with no results.
                rhl7FlushReport($arep);
                $in_ssn = $a[4];
                $in_dob = $a[7];
                // yyyymmdd format
                $tmp = explode($d2, $a[5]);
                $in_lname = $tmp[0];
                $in_fname = $tmp[1];
            } else {
                if ($a[0] == 'PV1') {
                    // Save placer encounter number if present.
                    if (!empty($a[19])) {
                        $tmp = explode($d2, $a[19]);
                        $in_encounter = intval($tmp[0]);
                    }
                } else {
                    if ($a[0] == 'ORC') {
                        $context = $a[0];
                        rhl7FlushResult($ares);
                        // Next line will do something only if there was a report with no results.
                        rhl7FlushReport($arep);
                        $porow = false;
                        $pcrow = false;
                        if ($a[2]) {
                            $in_orderid = intval($a[2]);
                        }
                    } else {
                        if ($a[0] == 'NTE' && $context == 'ORC') {
                            // TBD? Is this ever used?
                        } else {
                            if ($a[0] == 'OBR') {
                                $context = $a[0];
                                rhl7FlushResult($ares);
                                // Next line will do something only if there was a report with no results.
                                rhl7FlushReport($arep);
                                $procedure_report_id = 0;
                                if ($a[2]) {
                                    $in_orderid = intval($a[2]);
                                }
                                $tmp = explode($d2, $a[4]);
                                $in_procedure_code = $tmp[0];
                                $in_procedure_name = $tmp[1];
                                $in_report_status = rhl7ReportStatus($a[25]);
                                if (empty($porow)) {
                                    $porow = sqlQuery("SELECT * FROM procedure_order WHERE " . "procedure_order_id = ?", array($in_orderid));
                                    // The order must already exist. Currently we do not handle electronic
                                    // results returned for manual orders.
                                    if (empty($porow)) {
                                        return xl('Procedure order') . " '{$in_orderid}' " . xl('was not found');
                                    }
                                    if ($in_encounter) {
                                        if ($porow['encounter_id'] != $in_encounter) {
                                            return xl('Encounter ID') . " '" . $porow['encounter_id'] . "' " . xl('for OBR placer order number') . " '{$in_orderid}' " . xl('does not match the PV1 encounter number') . " '{$in_encounter}'";
                                        }
                                    } else {
                                        // They did not return an encounter number to verify, so more checking
                                        // might be done here to make sure the patient seems to match.
                                    }
                                    $code_seq_array = array();
                                }
                                // Find the order line item (procedure code) that matches this result.
                                // If there is more than one, then we select the one whose sequence number
                                // is next after the last sequence number encountered for this procedure
                                // code; this assumes that result OBRs are returned in the same sequence
                                // as the corresponding OBRs in the order.
                                if (!isset($code_seq_array[$in_procedure_code])) {
                                    $code_seq_array[$in_procedure_code] = 0;
                                }
                                $pcquery = "SELECT pc.* FROM procedure_order_code AS pc " . "WHERE pc.procedure_order_id = ? AND pc.procedure_code = ? " . "ORDER BY (procedure_order_seq <= ?), procedure_order_seq LIMIT 1";
                                $pcrow = sqlQuery($pcquery, array($in_orderid, $in_procedure_code, $code_seq_array['$in_procedure_code']));
                                if (empty($pcrow)) {
                                    // There is no matching procedure in the order, so it must have been
                                    // added after the original order was sent, either as a manual request
                                    // from the physician or as a "reflex" from the lab.
                                    // procedure_source = '2' indicates this.
                                    sqlInsert("INSERT INTO procedure_order_code SET " . "procedure_order_id = ?, " . "procedure_code = ?, " . "procedure_name = ?, " . "procedure_source = '2'", array($in_orderid, $in_procedure_code, $in_procedure_name));
                                    $pcrow = sqlQuery($pcquery, array($in_orderid, $in_procedure_code));
                                }
                                $code_seq_array[$in_procedure_code] = 0 + $pcrow['procedure_order_seq'];
                                $arep = array();
                                $arep['procedure_order_id'] = $in_orderid;
                                $arep['procedure_order_seq'] = $pcrow['procedure_order_seq'];
                                $arep['date_collected'] = rhl7DateTime($a[7]);
                                $arep['date_report'] = substr(rhl7DateTime($a[22]), 0, 10);
                                $arep['report_status'] = $in_report_status;
                                $arep['report_notes'] = '';
                            } else {
                                if ($a[0] == 'NTE' && $context == 'OBR') {
                                    $arep['report_notes'] .= rhl7Text($a[3]) . "\n";
                                } else {
                                    if ($a[0] == 'OBX') {
                                        $context = $a[0];
                                        rhl7FlushResult($ares);
                                        if (!$procedure_report_id) {
                                            $procedure_report_id = rhl7FlushReport($arep);
                                        }
                                        $ares = array();
                                        $ares['procedure_report_id'] = $procedure_report_id;
                                        $ares['result_data_type'] = substr($a[2], 0, 1);
                                        // N, S, F or E
                                        $ares['comments'] = $commentdelim;
                                        if ($a[2] == 'ED') {
                                            // This is the case of results as an embedded document. We will create
                                            // a normal patient document in the assigned category for lab results.
                                            $tmp = explode($d2, $a[5]);
                                            $fileext = strtolower($tmp[0]);
                                            $filename = date("Ymd_His") . '.' . $fileext;
                                            $data = rhl7DecodeData($tmp[3], &$tmp[4]);
                                            if ($data === FALSE) {
                                                return xl('Invalid encapsulated data encoding type') . ': ' . $tmp[3];
                                            }
                                            $d = new Document();
                                            $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data);
                                            if ($rc) {
                                                return $rc;
                                            }
                                            // This would be error message text.
                                            $ares['document_id'] = $d->get_id();
                                        } else {
                                            if (strlen($a[5]) > 200) {
                                                // OBX-5 can be a very long string of text with "~" as line separators.
                                                // The first line of comments is reserved for such things.
                                                $ares['result_data_type'] = 'L';
                                                $ares['result'] = '';
                                                $ares['comments'] = rhl7Text($a[5]) . $commentdelim;
                                            } else {
                                                $ares['result'] = rhl7Text($a[5]);
                                            }
                                        }
                                        $tmp = explode($d2, $a[3]);
                                        $ares['result_code'] = rhl7Text($tmp[0]);
                                        $ares['result_text'] = rhl7Text($tmp[1]);
                                        $ares['date'] = rhl7DateTime($a[14]);
                                        $ares['facility'] = rhl7Text($a[15]);
                                        $ares['units'] = rhl7Text($a[6]);
                                        $ares['range'] = rhl7Text($a[7]);
                                        $ares['abnormal'] = rhl7Abnormal($a[8]);
                                        // values are lab dependent
                                        $ares['result_status'] = rhl7ReportStatus($a[11]);
                                    } else {
                                        if ($a[0] == 'ZEF') {
                                            // ZEF segment is treated like an OBX with an embedded Base64-encoded PDF.
                                            $context = 'OBX';
                                            rhl7FlushResult($ares);
                                            if (!$procedure_report_id) {
                                                $procedure_report_id = rhl7FlushReport($arep);
                                            }
                                            $ares = array();
                                            $ares['procedure_report_id'] = $procedure_report_id;
                                            $ares['result_data_type'] = 'E';
                                            $ares['comments'] = $commentdelim;
                                            //
                                            $fileext = 'pdf';
                                            $filename = date("Ymd_His") . '.' . $fileext;
                                            $data = rhl7DecodeData('Base64', $a[2]);
                                            if ($data === FALSE) {
                                                return xl('ZEF segment internal error');
                                            }
                                            $d = new Document();
                                            $rc = $d->createDocument($porow['patient_id'], $results_category_id, $filename, rhl7MimeType($fileext), $data);
                                            if ($rc) {
                                                return $rc;
                                            }
                                            // This would be error message text.
                                            $ares['document_id'] = $d->get_id();
                                            //
                                            $ares['date'] = $arep['date_report'];
                                        } else {
                                            if ($a[0] == 'NTE' && $context == 'OBX') {
                                                $ares['comments'] .= rhl7Text($a[3]) . $commentdelim;
                                            } else {
                                                return xl('Segment name') . " '{$a[0]}' " . xl('is misplaced or unknown');
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    rhl7FlushResult($ares);
    // Next line will do something only if there was a report with no results.
    rhl7FlushReport($arep);
    return '';
}