5 * Copyright 2012 Kevin McCormick Longview Texas
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; version 3 or later. You should have
15 * received a copy of the GNU General Public License along with this program;
16 * if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * <http://opensource.org/licenses/gpl-license.php>
22 * @author Kevin McCormick
23 * @link: http://www.open-emr.org
25 * @subpackage ediHistory
28 // a security measure to prevent direct web access to this file
29 // must be accessed through the main calling script ibr_history.php
30 // from admin at rune-city dot com; found in php manual
31 //if (!defined('SITE_IN')) die('Direct access not allowed!');
34 if (!defined("IBR_DELIMITER")) define("IBR_DELIMITER", "|"); // per Availity edi guide
35 if (!defined("DPR_MSG_DELIM")) define("DPR_MSG_DELIM", "^");
36 //if (!defined("IBR_ENCOUNTER_DIGIT_LENGTH")) define("IBR_ENCOUNTER_DIGIT_LENGTH", "4");
40 * derive 'text' version name from ebr, ibr, and dpr files
42 * specific to Availity LLC -- see their EDI Guide
43 * also added 999 files, since availity practice is 99T for 'text' version
45 * @param string file name of related ebr, ibr, or dpr file
48 function ibr_ebr_ebt_name ($ebr_fname) {
49 // based upon Availity conventions: see availity_edi_guide
50 // the EBR-*-<<seq>>.ebr --> EBT-*-<<seq>>.ebt is the change made here
51 // Availity gives the text version with a sequence number increased by 1
53 // DPR-MULTIPAYER-201204250830-001-320101111-1821101111.dpr
54 // DPT-MULTIPAYER-201204250830-002-320101111-1821101111.dpt
56 // EBR-MULTIPAYER-201204210230-001.ebr
57 // EBT-MULTIPAYER-201204210230-002.ebt
59 // not tested with more complex file names, but it should work
60 // this concept also applies to .997 files, but they are too simple
62 // get the sequence, relying on it being the only 3 digit part of the filename
63 $re_seq = '/-([0-9]{3})[\-|.]{1}/';
64 preg_match ($re_seq, $ebr_fname, $matches);
67 // increment the sequence and reform as 3 byte count
68 $f_seq = ltrim ($m_seq, "0");
70 $f_seq = str_pad($f_seq, 3, "0", STR_PAD_LEFT
);
71 // insert leading "-" to avoid substitution error
72 $m_seq = str_pad($m_seq, 4, "-", STR_PAD_LEFT
);
73 $f_seq = str_pad($f_seq, 4, "-", STR_PAD_LEFT
);
75 // file names will begin with IBR- EBR- DPR-
76 $f_type = strtoupper(substr($ebr_fname, -4));
78 if ($f_type == ".EBR" ) {
79 // replace the EBR and .ebr parts
80 $f_txt_name = str_replace ("EBR-", "EBT-", $ebr_fname);
81 //echo "1: $f_txt_name" . PHP_EOL;
82 $f_txt_name = str_replace (".ebr", ".ebt", $f_txt_name);
83 $f_txt_name = str_replace ($m_seq, $f_seq, $f_txt_name);
84 } elseif ($f_type == ".IBR" ) {
85 // replace the IBR and .ibr parts
86 $f_txt_name = str_replace ("IBR-", "IBT-", $ebr_fname);
87 //echo "1: $f_txt_name" . PHP_EOL;
88 $f_txt_name = str_replace (".ibr", ".ibt", $f_txt_name);
89 // Availity apparently does not change sequence numbers on IBR -- IBT files
90 } elseif ($f_type == ".DPR" ) {
91 // replace the DPR and .dpr parts
92 // (if DPR files are added to this script)
93 $f_txt_name = str_replace ("DPR-", "DPT-", $ebr_fname);
94 //echo "1: $f_txt_name" . PHP_EOL;
95 $f_txt_name = str_replace (".dpr", ".dpt", $f_txt_name);
96 $f_txt_name = str_replace ($m_seq, $f_seq, $f_txt_name);
97 } elseif ($f_type == ".997" ) {
98 // also Availity 999 to 99T files
99 $f_txt_name = str_replace (".999", ".99T", $f_txt_name);
102 // insert the incremented sequence number
103 //$f_txt_name = str_replace ($m_seq, $f_seq, $f_txt_name);
104 // return the text version file name
110 * read the .ibr .ebr or .dpr file into an array of arrays
112 * Clearinghouse specific format, not x12
115 * @param string file path
118 function ibr_ebr_filetoarray ($file_path ) {
120 // since the file is multi-line, use fgets()
122 $fh = fopen($file_path, 'r');
124 while (($buffer = fgets($fh, 4096)) !== false) {
125 $ar_ibr[] = explode ( IBR_DELIMITER
, trim($buffer) );
128 // trigger_error("class.ibr_read.php : failed to read $file_path" );
129 csv_edihist_log( "ibr_ebr_filetoarray Error: failed to read " . $file_path );
135 /* ************ DPR functions *************** */
138 * Find and retrieve the claim response message in the dpr file.
140 * The message for given for a particular claim is returned in
141 * html format. The message is strangely formatted, so the
142 * output may not be as clean as desired.
144 * @uses csv_verify_file()
145 * @uses csv_ebr_filetoarray(
150 function ibr_dpr_message($dpr_filepath, $clm01) {
153 $fp = csv_verify_file($dpr_filepath, 'dpr', false);
155 //$dpr_ar = ibr_ebr_filetoarray ($fp);
156 $dpr_ar = csv_ebr_filetoarray($fp);
158 csv_edihist_log("ibr_dpr_message: file read error $dpr_filepath");
159 $str_html = "Unable to read " . basename($dpr_filepath) . "<br />".PHP_EOL
;
162 if (!is_array($dpr_ar) ||
!count($dpr_ar) ) {
163 csv_edihist_log("ibr_dpr_message: file read error $dpr_filepath");
164 $str_html = "Unable to read " . basename($dpr_filepath) . "<br />".PHP_EOL
;
167 foreach($dpr_ar as $fld) {
168 if ($fld[0] == 'CST' && $fld[2] == $clm01) {
170 $msgstr = str_replace('\\', '', $msgstr);
171 $msgstr = str_replace('^^^', '<br />', $msgstr);
172 $msgstr = str_replace('A^^', '', $msgstr);
173 $msgstr = wordwrap($msgstr, 46, '<br />'.PHP_EOL
);
174 $str_html .= "<p class='ibrmsg'>$msgstr</p>";
180 $str_html .= "Did not find $clm01 in " . basename($fp) . "<br />".PHP_EOL
;
186 * order the claim data array for csv file
188 * @param array individual claim array
191 function ibr_dpr_csv_claims($claim_ar) {
194 // ['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
195 $f_claim_data[0] = $claim_ar['pt_name'];
196 $f_claim_data[1] = $claim_ar['date'];
197 $f_claim_data[2] = $claim_ar['clm01'];
198 $f_claim_data[3] = $claim_ar['status'];
199 $f_claim_data[4] = $claim_ar['batch_name'];
200 $f_claim_data[5] = $claim_ar['dpr_name'];
201 $f_claim_data[6] = $claim_ar['payer_name'];
203 return $f_claim_data;
208 * parse dpr file into an array
210 * @uses ibr_ebr_ebt_name()
211 * @param array the array from csv_ebr_filetoarray()
212 * @param string the file path
215 function ibr_dpr_values ( $ar_dpr_fields, $dpr_filepath) {
216 // the .dpr file is one DPR line for each batch file
217 // followed by a CST for each patient for that payer in the batch file
220 $fname = basename($dpr_filepath);
221 $dpt_name = ibr_ebr_ebt_name ($fname);
223 csv_edihist_log("ibr_dpr_values: no file path provided");
226 if (!is_array($ar_dpr_fields) && count($ar_dpr_fields) > 0) {
227 csv_edihist_log("ibr_dpr_values: no values array provided");
231 $ar_dpr_clm = array();
233 foreach ( $ar_dpr_fields as $fld ) {
235 if ( $fld[0] == "DPR" ) {
236 $batch_ctrl = $fld[3];
237 $batch_name = $fld[6];
240 if ($fld[0] = "CST" ) {
241 // CST line -- there may be one or more for each DPR line, but we just create a line entry for
243 // -- only have a claims_dpr.csv file
245 $ar_pid = preg_split('/\D/', $fld[2], 2, PREG_SPLIT_NO_EMPTY
);
246 if ( count($ar_pid) == 2) {
250 $enctr = substr($fld[2], -IBR_ENCOUNTER_DIGIT_LENGTH
);
251 $pid = substr($fld[2], 0, strlen($fld[2]) - IBR_ENCOUNTER_DIGIT_LENGTH
);
258 $payer_name = $fld[15];
259 // take the fields out of order because the message precedes the status
261 $clm_status = $fld[11]; // Status| "REJ" or "ACK"
264 // DPR_MSG_DELIM is '^' and '^' is a PCRE metacharacter
265 // the DPR layout leaves a lot to be desired, there is no telling what you will get
266 // but it appears to be a distillation of the 277
267 $msg_ar = explode(DPR_MSG_DELIM
, $fld[9]);
268 if (is_array($msg_ar) && count($msg_ar)) {
269 for($i=0; $i<count($msg_ar); $i++
) {
270 $statuspos = strpos($msg_ar[$i], 'Status:');
272 $msg_txt = substr($msg_ar[$i], $statuspos) . ' ' . substr($msg_ar[$i], 0, $statuspos-1);
274 $msg_txt .= (strpos($msg_ar[$i], '\\') === false) ?
$msg_ar[$i] : str_replace('\\', ' ', $msg_ar[$i]);
278 $msg_txt = trim($msg_txt);
280 $clm_ins_id = $fld[12]; // Payer Claim Number|
281 $clm_ins_name = $fld[15]; // Payer Name|
283 //$csv_hd_ar['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer')
284 // add the values to the output array
285 $ar_dpr_clm[] = array(
286 'pt_name' => $pt_name,
289 'status' => $clm_status,
290 'payer_name' => $payer_name,
291 'batch_name' => $batch_name,
292 'dpr_name' => $fname,
293 'dpr_text'=> $dpt_name,
294 'message' => $msg_txt
297 // 'pt_name''date''pid''enctr''ext''status''batch_name''file_text''message'
301 } // end function ibr_dpr_values
305 * Create html table for processing of file
307 * If all claims are accepted then only a message so indicating is shown
309 * @param array data array from ibr_dpr_values()
310 * @param bool only show rejected claims if true
313 function ibr_dpr_html ($ar_dpr_data, $err_only=FALSE) {
320 // the details table heading
321 $clm_hdg = "<table cols=6 class=\"ibr_dpr\">
324 <th>Patient Name</th><th>Date</th><th>Claim</th><th>Account</th>
325 <th>Status</th><th>DPR Name</th><th>BatchFile</th>
328 <th colspan=6 align=left>Message</th>
332 //'pt_name''date''pid''enctr''ext''status''dpr_name''batch_name''file_text''message'
334 if (is_array($ar_dpr_data) && count($ar_dpr_data) > 0) {
335 // create html output for the claims
336 foreach ($ar_dpr_data as $val) {
339 // if $err_only, then only get information on rejected claims
340 // rejected claims will have one or more 3e subarrays
341 if ($val['status'] == 'ACK') { $ack_ct++
; }
342 if ($err_only && $val['status'] == 'ACK') { continue; }
344 if ($val['status'] == 'REJ') { $rj_ct++
; }
346 // alternate row background colors
347 $bgc = ($rj_ct %
2 == 1 ) ?
'odd' : 'even';
349 $clm_html .= "<tr class=\"{$bgc}\">
350 <td>{$val['pt_name']}</td>
351 <td>{$val['date']}</td>
352 <td><a class=\"btclm\" target=\"_blank\" href=\"edi_history_main.php?fvbatch={$val['batch_name']}&btpid={$val['clm01']}\">{$val['clm01']}</a></td>
353 <td><a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?dprfile={$val['dpr_name']}&dprclm={$val['clm01']}\">{$val['status']}</a></td>
354 <td><a target=\"_blank\" href=\"edi_history_main.php?fvkey={$val['dpr_name']}\">{$val['dpr_name']}</a></td>
355 <td title={$val['payer_id']}>{$val['payer_name']}</td>
357 <tr class=\"{$bgc}\">
358 <td colspan=6>{$val['message']}</td>
360 } // end foreach(($ar_cd as $val)
362 // if there were any claims detailed
363 if ( $hasclm && strlen($clm_html) ) {
364 $str_html .= $clm_hdg . $clm_html;
365 // finish the table and add a <p>
366 $str_html .= "</tbody></table>
368 $str_html .="<p class=dpr_notice>Of $idx dpr claims, there were $rj_ct reported REJ and $ack_ct reported ACK </p>";
370 $str_html .="<p class=dpr_notice>All $idx dpr claims reported ACK (accepted)</p>";
379 * main function to process new dpr files
381 * @uses csv_newfile_list()
382 * @uses csv_verify_file()
383 * @uses csv_ebr_filetoarray()
384 * @uses ibr_dpr_values()
385 * @uses csv_write_record()
386 * @uses ibr_dpr_html()
387 * @param array optional files array
388 * @param bool whether to create html output
389 * @param bool whether to only generate output for errors
392 function ibr_dpr_process_new($files_ar=NULL, $html_out=TRUE, $err_only=TRUE ) {
395 // get the new files in an array
396 if ( is_array($files_ar) && count($files_ar) ) {
399 $f_list = csv_newfile_list('dpr');
403 if ( count($f_list) == 0 ) {
405 $html_str .= "<p>No new DPR files found.</p>";
411 $fdprcount = count($f_list);
413 // OK, so we have some files
418 // sort ascending so latest files are last to be output
419 $is_sort = asort($f_list); // returns true on success
422 // Step 2: file data written to csv files
423 // also html string created if $html_out = TRUE
424 foreach ($f_list as $f_name) {
426 $f_path = csv_verify_file($f_name, 'dpr');
428 //$ar_dprfld = ibr_ebr_filetoarray($f_path);
429 $ar_dprfld = csv_ebr_filetoarray($f_path);
432 $ar_dprval = ibr_dpr_values ($ar_dprfld, $f_path);
433 // the $ar_dprfld array is array of arrays
434 // one for each CST line, so we append them to our
435 // array for csv and html output
436 foreach($ar_dprval as $cst) {
438 $ar_csv[] = ibr_dpr_csv_claims($cst);
441 $html_str .= "<p>Error: failed to parse $f_name </p>" .PHP_EOL
;
442 csv_edihist_log("ibr_dpr_process_new: failed to parse $f_name");
445 $html_str .= "<p>Error: invalid path for $f_name </p>" .PHP_EOL
;
446 csv_edihist_log("ibr_dpr_process_new: invalid path for $f_name");
450 //$chrc += csv_write_record($ar_out, 'dpr', 'claim');
451 $chrc +
= csv_write_record($ar_csv, 'dpr', 'claim');
454 $html_str .= ibr_dpr_html($ar_out, $err_only);
456 $html_str .= "DPR files: processed $fdprcount files <br />".PHP_EOL
;
463 /* ******************** EBR/IBR functions ******************** */
466 * locate and retrieve the message for a claim in an ebr or ibr file
468 * @uses csv_verify_file()
469 * @uses csv_ebr_filetoarray()
470 * @param string filename
471 * @param string claim id (pid-encounter) or 'err' for all in file with errors
472 * @return string html formatted paragraph
474 function ibr_ebr_message($ebrfile, $clm01, $batchnm = '') {
475 // a particular encounter may appear more than once in an ibr or ebr file
476 // if it is sent again in a new batch very quickly, depending on how the
477 // clearinghouse aggregated its responses.
478 // Therefore, we need to try and check for batch name match
481 $ext = substr($ebrfile, -3);
482 $fp = csv_verify_file($ebrfile, $ext, false);
484 $fname = basename($fp);
485 $ext = strtolower(substr($fname, -4));
486 //$ebr_ar = ibr_ebr_filetoarray ($fp);
487 $ebr_ar = csv_ebr_filetoarray($fp);
489 csv_edihist_log("ibr_ebr_message: file read error $ebrfile");
490 $str_html = "Unable to read file " . basename($ebrfile) . "<br />".PHP_EOL
;
493 if (!is_array($ebr_ar) ||
!count($ebr_ar) ) {
494 csv_edihist_log("ibr_ebr_message: file read error $ebrfile");
495 $str_html = "Unable to read file " . basename($ebrfile) . "<br />".PHP_EOL
;
498 $usebatch = ($batchnm) ?
true : false;
502 foreach($ebr_ar as $fld) {
503 // since true value can match '1'
504 if (strval($fld[0]) === '1') {
505 $btnm = ($ext == '.ibr') ?
$fld[15] : $fld[8];
507 $isbatch = ($ext == '.ibr') ?
($fld[15] == $batchnm) : ($fld[8] == $batchnm);
512 if ($fld[0] == '3') {
513 if ($usebatch & !$isbatch) {
517 $isfound = ($fld[4] == $clm01);
518 if ($clm01 == 'any' ||
$isfound) {
521 $sts = ($ext == '.ibr') ?
$fld[11] : '';
523 if ($isfound && $ext == '.ibr' && $sts == 'A') {
525 $msgstr = "<p class='ibrmsg'>".PHP_EOL
;
526 $msgstr .= "$nm $pe <br />".PHP_EOL
;
527 $msgstr .= "$btnm <br />".PHP_EOL
;
528 $msgstr .= "Status: $sts<br />".PHP_EOL
;
529 $msgstr .= '</p>'.PHP_EOL
;
536 // there should be only one of these three possibilities, but maybe more than one 3e
537 if ($fld[0] == '3e') {
538 //3e│Error Initiator│R│Error Code – if available, otherwise NA│Error Message | Loop│Segment ID│Element # ││││Version |
539 // different for older 4010, one less field, so loop gives segment, segment gives element, element is blank
540 $sts = ($ext == '.ebr') ?
$fld[2] : $sts;
541 $msgstr .= "<p class='ibrmsg'>".PHP_EOL
;
542 $msgstr .= "$nm $pe <br />".PHP_EOL
;
543 $msgstr .= "$btnm <br />".PHP_EOL
;
544 $msgstr .= "Status: $sts<br />".PHP_EOL
;
545 $msgstr .= "Error Initiator: {$fld[1]} Code: {$fld[3]} <br />".PHP_EOL
;
546 $msgstr .= "Loop: {$fld[5]} Segment: {$fld[6]} Element: {$fld[7]} <br />".PHP_EOL
;
547 $msgstr .= wordwrap($fld[4], 46, "<br />".PHP_EOL
);
548 $msgstr .= "</p>".PHP_EOL
;
549 } elseif ($fld[0] == '3c') {
550 $sts = ($ext == '.ebr') ?
$fld[2] : $sts;
551 $msgstr .= "<p class='ibrmsg'>".PHP_EOL
;
552 $msgstr .= "$nm $pe <br />".PHP_EOL
;
553 $msgstr .= "$btnm <br />".PHP_EOL
;
554 $msgstr .= "Status: $sts<br />".PHP_EOL
;
555 $msgstr .= wordwrap($fld[4], 46, '<br />'.PHP_EOL
);
556 $msgstr .= '</p>'.PHP_EOL
;
557 } elseif ($fld[0] == '3a') {
558 //3a│Bill Type│Allowed Amount│Non-Covered Amount │Deductible Amount │Co-Pay Amount │Co-insurance Amount │Withhold
559 //Amount │Estimated Payment Amount │Patient Liability│Message Code│Message Text││
561 $msgstr .= "<p class='ibrmsg'>".PHP_EOL
;
562 $msgstr .= "$nm $pe <br />".PHP_EOL
;
563 $msgstr .= "$btnm <br />".PHP_EOL
;
564 $msgstr .= "Type: {$fld[1]} <br />".PHP_EOL
;
565 $msgstr .= ($fld[2] =='NA') ?
"" : "Allowed: {$fld[2]}";
566 $msgstr .= ($fld[8] =='NA') ?
"" : " Payment: {$fld[8]}";
567 $msgstr .= ($fld[9] =='NA') ?
"<br />".PHP_EOL
: " Pt Resp: {$fld[9]} <br />".PHP_EOL
;
568 $msgstr .= ($fld[10] =='NA') ?
"" : "Code: {$fld[10]} ";
569 $msgstr .= ($fld[11] =='NA') ?
"" : wordwrap($fld[11], 46, "<br />".PHP_EOL
);
570 $msgstr .= "</p>".PHP_EOL
;
572 } elseif ($clm01 == 'any') {
573 if ($usebatch & !$isbatch) { continue; }
575 if ($fld[0] == '3e') {
577 $msgstr .= "<p class='ibrmsg'>".PHP_EOL
;
578 $msgstr .= "$nm $pe <br />".PHP_EOL
;
579 $msgstr .= "$btnm <br />".PHP_EOL
;
580 $msgstr .= "Error Initiator: {$fld[1]} Code: {$fld[3]} <br />".PHP_EOL
;
581 $msgstr .= "Loop: {$fld[5]} Segment: {$fld[6]} Element: {$fld[7]} <br />".PHP_EOL
;
582 $msgstr .= wordwrap($fld[4], 46, "<br />".PHP_EOL
)."<br />".PHP_EOL
;
583 $msgstr .= "~~~~~~~~<br />".PHP_EOL
;
584 $msgstr .= "</p>".PHP_EOL
;
588 } // end foreach($ebr_ar as $fld)
590 $str_html .= $msgstr;
593 $str_html .= "Did not find $clm01 in $fname <br />".PHP_EOL
;
600 * parse the ebr file format into an array
602 * The array is indexed in different batch files, indicated by line '1'
603 * So a new line will appear in the csv files table for each line '1'
606 * $ar_val[$b]['file']
607 * ['date']['f_name']['clrhsid']['batch']['clm_ct']['clm_rej']['chg_r']
609 * $ar_val[$b]['claims'][$c]
610 * ['pt_name'] ['svcdate']['clm01']['status']['batch']['f_name']['payer']
611 * ['providerid']['prclmnum']['payerid']
613 * ['err_seg']['err_msg']
617 * ['err_type']['err_code']['err_msg']['err_loop']['err_seg']['err_elem']
621 * @see ibr_ebr_process_new_files()
622 * @uses ibr_ebr_ebt_name()
623 * @uses csv_ebr_filetoarray()
624 * @param string path to ebr file
627 function ibr_ebr_values($file_path) {
629 // get file information
630 if (is_readable($file_path)) {
631 // string setlocale ( int $category , array $locale ) // may be needed
632 $path_parts = pathinfo($file_path);
634 // error, unable to read file
635 csv_edihist_log("Error, unable to read file $file_path");
638 $path_parts = pathinfo($file_path);
639 //$ebr_dir = $path_parts['dirname'];
640 $ebr_fname = $path_parts['basename'];
641 $ebr_ext = $path_parts['extension'];
643 if ($ebr_ext != 'ebr') {
644 csv_edihist_log("ibr_ebr_values: incorrect file extension $ebr_ext $ebr_fname");
648 $ebr_mtime = date ("Ymd", filemtime($file_path));
656 // get file contents transformed to array
657 //$ar_ebr = ibr_ebr_filetoarray ($file_path);
658 $ar_ebr = csv_ebr_filetoarray($file_path);
659 if (!is_array($ar_ebr) ||
!count($ar_ebr)) {
660 csv_edihist_log("ibr_ibr_values: failed to read $ebr_fname");
664 foreach($ar_ebr as $ln) {
666 if (strval($ln[0]) === '1') {
667 //['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
671 if (preg_match('/\d{4}\D\d{2}\D\d{2}/', $ln[1])) {
672 $fdate = preg_replace('/\D/', '', $ln[1]);
676 $batch_name = $ln[8];
677 //['date']['f_name']['clrhsid']['batch']['clm_ct']['clm_rej']['chg_r']
678 $ar_val[$b]['file']['date'] = $fdate; // ibr file date
679 $ar_val[$b]['file']['f_name'] = $ebr_fname;
680 $ar_val[$b]['file']['clrhsid'] = $ln[7]; // availity clearinghouse file id
681 $ar_val[$b]['file']['batch'] = $batch_name; // batch file name
692 if (strval($ln[0]) == '2') {
695 $clm_ct +
= intval($ln[2]);
696 $clm_rej +
= intval($ln[6]);
697 $clm_rej_chg +
= floatval($ln[7]);
701 $ar_val[$b]['file']['clm_ct'] = $clm_ct; // claim count
702 $ar_val[$b]['file']['clm_rej'] = $clm_rej; // rejected claims count
703 $ar_val[$b]['file']['chg_r'] = $clm_rej_chg; // rejected charges
707 //['pt_name'] ['svcdate']['clm01']['status']['batch']['f_name']['payer'] ['providerid']['prclmnum']['payerid']['3c']['3e']['3a']
708 if (strval($ln[0]) == '3') {
709 //['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
713 $ar_val[$b]['claims'][$c]['pt_name'] = $ln[1];
714 $ar_val[$b]['claims'][$c]['svcdate'] = $ln[2];
715 $ar_val[$b]['claims'][$c]['clm01'] = $ln[4];
716 $ar_val[$b]['claims'][$c]['batch'] = $batch_name;
717 $ar_val[$b]['claims'][$c]['f_name'] = $ebr_fname;
718 $ar_val[$b]['claims'][$c]['payer'] = $payer;
720 $ar_val[$b]['claims'][$c]['providerid'] = $ln[6];
721 $ar_val[$b]['claims'][$c]['prclmnum'] = $ln[8];
722 $ar_val[$b]['claims'][$c]['payerid'] = $payerid;
727 if (strval($ln[0]) == '3c') {
729 $msg = ''; $err_seg = '';
731 if ($ln[2] != 'A' && $ln[3] == 'NA') {
732 $ar_val[$b]['claims'][$c]['status'] = 'A';
734 $ar_val[$b]['claims'][$c]['status'] = $ln[2];
736 //['err_seg']['err_msg']
737 $err_seg .= (strlen($ln[5]) && $ln[5] != 'NA') ?
$ln[5].' | ' : '';
738 $err_seg .= (strlen($ln[6]) && $ln[6] != 'NA') ?
$ln[6].' | ' : '';
739 $err_seg .= (strlen($ln[7]) && $ln[7] != 'NA') ?
$ln[7].' | ' : '';
741 $msg .= (strlen($ln[1]) && $ln[1] != 'NA') ?
$ln[1].' ' : '';
742 $msg .= (strlen($ln[2]) && $ln[2] != 'NA') ?
'Type: '.$ln[2].' ' : '';
743 $msg .= (strlen($ln[4]) && $ln[4] != 'NA') ?
$ln[4] : '';
745 $ar_val[$b]['claims'][$c]['3c']['err_seg'] = $err_seg;
746 $ar_val[$b]['claims'][$c]['3c']['err_msg'] = $msg;
751 if (strval($ln[0]) == '3e') {
754 if ($ln[2] != 'R' && $ln[3] != 'NA') {
755 $ar_val[$b]['claims'][$c]['status'] = 'R';
757 $ar_val[$b]['claims'][$c]['status'] = $ln[2];
759 //['err_type']['err_code']['err_msg']['err_loop']['err_seg']['err_elem']
760 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_type'] = $ln[1]; // Error Initiator
761 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_code'] = $ln[3]; // Error Code or NA
762 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_msg'] = $ln[4]; // Error Message
763 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_loop'] = $ln[5]; // Loop
764 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_seg'] = $ln[6]; // Segment ID
765 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_elem'] = $ln[7]; // Element #
770 if (strval($ln[0]) == '3a') {
772 $ar_val[$b]['claims'][$c]['status'] = 'A';
774 $msg = ''; $msg_txt = '';
776 $msg .= (strlen($ln[1]) && $ln[1] != 'NA') ?
'Bill Type: '.$ln[1] : '';
777 $msg .= (strlen($ln[2]) && $ln[2] != 'NA') ?
'Allowed: '.$ln[2] : '';
778 $msg .= (strlen($ln[3]) && $ln[3] != 'NA') ?
'Non Covered: '.$ln[3] : '';
779 $msg .= (strlen($ln[4]) && $ln[4] != 'NA') ?
'Deductible '.$ln[4] : '';
780 $msg .= (strlen($ln[5]) && $ln[5] != 'NA') ?
'Co-Pay: '.$ln[5] : '';
781 $msg .= (strlen($ln[6]) && $ln[6] != 'NA') ?
'Co-ins: '.$ln[6] : '';
782 $msg .= (strlen($ln[7]) && $ln[7] != 'NA') ?
'Withhold: '.$ln[7] : '';
783 $msg .= (strlen($ln[8]) && $ln[8] != 'NA') ?
'Est Pmt: '.$ln[8] : '';
784 $msg .= (strlen($ln[9]) && $ln[9] != 'NA') ?
'Pt Rsp: '.$ln[9] : '';
786 $msg_txt .= (strlen($ln[10]) && $ln[10] != 'NA') ?
'Code: '.$ln[10] : '';
787 $msg_txt .= (strlen($ln[11]) && $ln[11] != 'NA') ?
' '.$ln[11] : '';
789 $ar_val[$b]['claims'][$c]['3a']['pmt'] = $msg;
790 $ar_val[$b]['claims'][$c]['3a']['msg'] = $msg_txt;
799 * parse the ibr file format into an array
801 * Very similar to ibr_ebr_values(), with slight differences
802 * The array is indexed in different batch files, indicated by line '1'.
803 * So a new line will appear in the csv files table for each line '1'.
806 * $ar_val[$b]['file']
807 * ['date']['f_name']['clrhsid']['batch']['clm_ct']['clm_rej']['chg_r']
809 * $ar_val[$b]['claims'][$c]
810 * ['pt_name'] ['svcdate']['clm01']['status']['batch']['f_name']['payer']
811 * ['providerid']['bht03']['payerid']
813 * ['err_type']['err_code']['err_msg']['err_loop']['err_seg']['err_elem']
817 * @see ibr_ebr_process_new_files()
818 * @uses ibr_ebr_ebt_name()
819 * @uses csv_ebr_filetoarray()
820 * @param string path to ibr file
823 function ibr_ibr_values($file_path) {
825 // get file information
826 if (is_readable($file_path)) {
827 // string setlocale ( int $category , array $locale ) // may be needed
828 $path_parts = pathinfo($file_path);
830 // error, unable to read file
831 csv_edihist_log("ibr_ibr_values: Error, unable to read file $file_path");
834 $path_parts = pathinfo($file_path);
835 //$ibr_dir = $path_parts['dirname'];
836 $ibr_fname = $path_parts['basename'];
837 $ibr_ext = $path_parts['extension'];
839 if ($ibr_ext != 'ibr') {
840 csv_edihist_log("ibr_ibr_values: incorrect file extension $ibr_ext $ibr_fname");
844 $ibr_mtime = date ("Ymd", filemtime($file_path));
853 // get file contents transformed to array
854 //$ar_ibr = ibr_ebr_filetoarray ($file_path);
855 $ar_ibr = csv_ebr_filetoarray($file_path);
856 if (!is_array($ar_ibr) ||
!count($ar_ibr)) {
857 csv_edihist_log("ibr_ibr_values: failed to read $ibr_fname");
861 foreach($ar_ibr as $ln) {
863 if (strval($ln[0]) === '1') {
864 //['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
868 $batch_name = $ln[15];
871 if (preg_match('/\d{4}\D\d{2}\D\d{2}/', $ln[1])) {
872 $fdate = preg_replace('/\D/', '', $ln[1]);
876 $ar_ih[$b]['file']['date'] = $fdate; // ibr file date
877 $ar_ih[$b]['file']['f_name'] = $ibr_fname;
878 $ar_ih[$b]['file']['btctln'] = $ln[5]; // batch control number [ISA13]
879 $ar_ih[$b]['file']['clm_ct'] = $ln[6]; // claim count
880 //$ar_ih[$b]['file']['chg_s'] = $ln[8]; // submitted charges total [CLM02 ?]
881 $ar_ih[$b]['file']['clm_rej'] = $ln[10]; // rejected claims count
882 $ar_ih[$b]['file']['chg_r'] = $ln[11]; // rejected charges
883 $ar_ih[$b]['file']['clrhsid'] = $ln[14]; // availity clearinghouse file id
884 $ar_ih[$b]['file']['batch'] = $ln[15]; // batch file name
889 if (strval($ln[0]) == '2') {
895 //['pt_name'] ['svcdate']['clm01']['status']['batch']['filename']['payer'] ['providerid']['bht03']['payerid']
896 if (strval($ln[0]) == '3') {
897 //['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
901 $ar_ih[$b]['claims'][$c]['pt_name'] = $ln[1];
902 $ar_ih[$b]['claims'][$c]['svcdate'] = $ln[2];
903 $ar_ih[$b]['claims'][$c]['clm01'] = $ln[4];
904 $ar_ih[$b]['claims'][$c]['status'] = $ln[11];
905 $ar_ih[$b]['claims'][$c]['batch'] = $batch_name;
906 $ar_ih[$b]['claims'][$c]['f_name'] = $ibr_fname;
907 $ar_ih[$b]['claims'][$c]['payer'] = $payer;
909 $ar_ih[$b]['claims'][$c]['providerid'] = $ln[6];
910 $ar_ih[$b]['claims'][$c]['bht03'] = (strlen($ln[10]) >= 9) ?
$ln[10] : $batch_ctl;
911 $ar_ih[$b]['claims'][$c]['payerid'] = $payerid;
916 if (strval($ln[0]) == "3e") {
917 // increment error count -- more than one error is possible
918 // ibr files have a 3e only in error case, no 3a or 3c
921 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_type'] = $ln[1]; // Error Initiator
922 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_code'] = $ln[3]; // Error Code or NA
923 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_msg'] = $ln[4]; // Error Message
924 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_loop'] = $ln[5]; // Loop
925 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_seg'] = $ln[6]; // Segment ID
926 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_elem'] = $ln[7]; // Element #
937 * Create html table for displaying processing results
939 * @param array $ar_data array produced by function ibr_ebr_data_ar
940 * @param $err_only boolean ignore claim information if no 3e subarray is in the claim data
943 function ibr_ebr_html ($ar_data, $err_only=false) {
944 // create an html string for a table to display in a web page
953 $dtl = ($err_only) ?
"Errors only" : "All included claims";
955 // the table heading for files
956 $f_hdg = "<table cols=5 class=\"ibr_ebr\">
957 <caption>IBR-EBR Files Summary {$dtl} </caption>
960 <th>IBR-EBR File</th><th>Date</th>
961 <th>Batch</th><th>Claims</th><th>Rej</th>
966 // the details table heading
967 $clm_hdg = "<table cols=5 class=\"ibr_ebr\">
970 <th>Patient Name</th><th>Date</th><th>CtlNum</th>
971 <th>Status</th><th>Payer Name</th>
974 <th>Type|Code|Loop|Segment|Field</th><th colspan=4>Message</th>
979 // start with the table heading
982 foreach ($ar_data as $ardt) {
984 $bgf = ($idf %
2 == 1 ) ?
'fodd' : 'feven';
987 $ar_hd = isset($ardt['file']) ?
$ardt['file'] : NULL ;
988 $ar_cd = isset($ardt['claims']) ?
$ardt['claims'] : NULL ;
990 if (!$ar_hd && !$ar_cd) {
991 $str_html .= "ibr_ebr_html: empty array or wrong keys <br />" . PHP_EOL
;
994 // if we had a claim detail in last pass, we need to append the files heading
995 if ($hasclm) { $str_html .= $f_hdg; }
997 // if any individual claims detail is to be output this will be set true
1001 //['date']['f_name']['availid']['batch']['clm_acc']['chg_s']['clm_rej']['chg_r']
1002 $str_html .= "<tr class=\"{$bgf}\">" .PHP_EOL
;
1003 $str_html .= "<td>{$ar_hd['date']}</td>
1004 <td><a target=\"_blank\" href=\"edi_history_main.php?fvkey={$ar_hd['f_name']}\">{$ar_hd['f_name']}</a> <a target=\"_blank\" href=\"edi_history_main.php?fvkey={$ar_hd['f_name']}&readable=yes\">Text</a></td>
1005 <td><a target=\"_blank\" href=\"edi_history_main.php?fvkey={$ar_hd['batch']}\">{$ar_hd['batch']}</a></td>
1006 <td>{$ar_hd['clm_ct']}</td>
1007 <td>{$ar_hd['clm_rej']}</td>".PHP_EOL
;
1008 $str_html .= "</tr>" .PHP_EOL
;
1009 // now the individual claims details
1010 //['pt_name'] ['svcdate']['clm01']['status']['batch']['filename']['payer'] ['providerid']['bht03']['payerid']
1012 // create html output for the claims
1013 foreach ($ar_cd as $val) {
1014 // if $err_only, then only get information on claims with 3e
1015 if ($err_only && !array_key_exists("3e", $val) ) { continue; }
1017 // alternate row background colors
1018 $bgc = ($idx %
2 == 1 ) ?
'odd' : 'even';
1020 // since we are here, we have claim details in the output
1023 $clm_html .= "<tr class=\"{$bgc}\">
1024 <td>{$val['pt_name']}</td>
1025 <td>{$val['svcdate']}</td>
1026 <td><a class=\"btclm\" target=\"_blank\" href=\"edi_history_main.php?fvbatch={$val['batch']}&btpid={$val['clm01']}\">{$val['clm01']}</a></td>
1027 <td><a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?ebrfile={$val['f_name']}&ebrclm={$val['clm01']}\">{$val['status']}</a></td>
1028 <td title=\"{$val['payerid']}\">{$val['payer']}</td>
1030 if (array_key_exists("3e", $val)) {
1031 // there may be more than one error reported
1032 foreach ($val['3e'] as $er) {
1033 $clm_html .= "<tr class=\"{$bgc}\">".PHP_EOL
;
1034 $clm_html .= "<td>{$er['err_type']} {$er['err_code']} {$er['err_loop']} {$er['err_seg']} {$er['err_elem']}</td>
1035 <td colspan=4>{$er['err_msg']}</td>
1037 } // end foreach ($val['3e'] as $er)
1038 } elseif (array_key_exists("3a", $val)) {
1039 $clm_html .= "<tr class=\"{$bgc}\">
1040 <td>{$val['3a']['msg']}</td><td colspan=4>{$val['3a']['msg_txt']}</td>
1042 } elseif (array_key_exists("3c", $val)) {
1043 $clm_html .= "<tr class=\"{$bgc}\">
1044 <td>{$val['3c']['err_seg']}</td><td colspan=4>{$val['3c']['err_msg']}</td>
1048 } // end foreach(($ar_cd as $val)
1050 // if there were any claims detailed
1051 if ( $hasclm && strlen($clm_html) ) {
1052 $str_html .= "</tbody>".PHP_EOL
;
1053 $str_html .= $clm_hdg . $clm_html;
1055 } // end if ($ar_cd)
1056 } // end foreach ($ar_data as $ardt)
1059 $str_html .= "</tbody></table>".PHP_EOL
;
1065 * order array of file information for csv table
1067 * @param array file array created elswhere
1070 function ibr_ebr_csv_files($head_ar) {
1071 // the file record csv file
1072 //['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1073 $f_file_data = array();
1074 $f_file_data[0] = $head_ar['date'];
1075 $f_file_data[1] = $head_ar['f_name'];
1076 // put file id as column 2 $f_file_data[2] = $head_ar['availid']
1077 $f_file_data[2] = $head_ar['clrhsid'];
1078 $f_file_data[3] = $head_ar['clm_ct'];
1079 $f_file_data[4] = $head_ar['clm_rej'];
1080 $f_file_data[5] = $head_ar['batch'];
1082 return $f_file_data;
1087 * order the claim data array for csv file
1089 * @param array individual claim array
1092 function ibr_ebr_csv_claims($claim_ar) {
1094 $f_claim_data = array();
1095 //['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer')
1096 $f_claim_data[0] = $claim_ar['pt_name'];
1097 $f_claim_data[1] = $claim_ar['svcdate'];
1098 $f_claim_data[2] = $claim_ar['clm01'];
1099 $f_claim_data[3] = $claim_ar['status'];
1100 $f_claim_data[5] = $claim_ar['batch'];
1101 $f_claim_data[6] = $claim_ar['f_name'];
1102 $f_claim_data[4] = $claim_ar['payer'];
1104 return $f_claim_data;
1109 * the main function for ebr and ibr files in this script
1111 * @uses csv_newfile_list()
1112 * @uses csv_verify_file()
1113 * @uses csv_write_record()
1114 * @uses ibr_ebr_values()
1115 * @uses ibr_ibr_values()
1116 * @uses ibr_ebr_csv_files()
1117 * @uses ibr_ebr_csv_claims()
1118 * @param array optional array of filenames or null
1119 * @param string $extension one of ibr, or ebr; default is ibr
1120 * @param bool $html_out true or false whether html output string should be created and returned
1121 * @param bool $err_only true or false whether html output shows files and only errors or all claims
1122 * @return string|bool html output string or boolean
1124 function ibr_ebr_process_new_files($files_ar=NULL, $extension='ibr', $html_out=TRUE, $err_only=TRUE ) {
1126 // process new ebr or ibr files by calling the functions in this script
1127 // three optional arguments to be passed to functions and used within
1131 // patterned from ibr_batch_read.php
1132 if ( $files_ar === NULL ||
!is_array($files_ar) ||
count($files_ar) == 0) {
1133 $f_new = csv_newfile_list($extension);
1134 } elseif ( is_array($files_ar) && count($files_ar) ) {
1138 if ( count($f_new) == 0 ) {
1140 $html_str .= "<p>IBR/EBR files: no new $extension files <br />";
1146 // we have some new files
1147 // verify and get complete path
1148 foreach($f_new as $fbt) {
1149 $fp = csv_verify_file($fbt, $extension, false);
1153 $html_str .= "verification failed for $fbt <br />".PHP_EOL
;
1154 csv_edihist_log("ibr_ebr_process_new_files: verification failed for $fbt");
1157 $fibrcount = count($f_list);
1159 // initialize variables
1167 // sort ascending so latest files are last to be output
1168 $is_sort = asort($f_list); // returns true on success
1170 // Step 2: file data written to csv files
1171 // also html string created if $html_out = TRUE
1172 foreach ($f_list as $f_name) {
1173 // get the data array for the file
1176 if (substr($f_name, -3) != $extension) {
1177 csv_edihist_log("ibr_ebr_process_new_files: type mismatch $extension " . basename($f_name));
1181 if ($extension == 'ibr') {
1182 $data_vals = ibr_ibr_values($f_name);
1183 } elseif ($extension == 'ebr') {
1184 $data_vals = ibr_ebr_values($f_name);
1186 csv_edihist_log("ibr_ebr_process_new_files: incorrect extension $ext " . basename($f_name));
1190 if (is_array($data_vals) && count($data_vals)) {
1191 foreach($data_vals as $dm) {
1192 $wf[] = ibr_ebr_csv_files($dm['file']);
1193 foreach($dm['claims'] as $cl) {
1195 $wc[] = ibr_ebr_csv_claims($cl);
1197 $data_ar[$idx]['file'] = $dm['file'];
1198 $data_ar[$idx]['claims'] = $dm['claims'];
1204 $chrf +
= csv_write_record($wf, $extension, "file");
1205 $chrc +
= csv_write_record($wc, $extension, "claim");
1208 $html_str .= ibr_ebr_html ($data_ar, $err_only);
1210 $html_str .= "IBR/EBR files: processed $fibrcount $extension files <br />".PHP_EOL
;
1217 * generate output as if file is being processed
1219 * @param string filename
1220 * @param bool display errors only
1223 function ibr_ebr_filetohtml($filepath, $err_only=false) {
1224 // simply create an html output for the file
1228 $ext = substr($filepath, -3);
1229 if ( strpos('|ibr|ebr', $ext) ) {
1230 $fp = csv_verify_file( $filepath, "ebr");
1234 if ($ext == 'ebr') {
1235 $data_ar = ibr_ebr_values($fp);
1236 } elseif ($ext == 'ibr') {
1237 $data_ar = ibr_ibr_values($fp);
1239 csv_edihist_log ("ibr_ebr_filetohtml: invalid extension $ext " . basename($fp) );
1240 return "<p>invalid extension $ext </p>".PHP_EOL
;
1243 $html_str .= ibr_ebr_html ($data_ar, $err_only);
1245 csv_edihist_log ("ibr_ebr_filetohtml: verification failed $filepath");
1246 $html_str .= "Error, validation failed $filepath <br />";