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' && $usebatch) {
505 $isbatch = ($ext == '.ibr') ?
($fld[15] == $batchnm) : ($fld[8] == $batchnm);
506 $btnm = ($ext == '.ibr') ?
$fld[15] : $fld[8];
510 if ($fld[0] == '3') {
511 if ($usebatch & !$isbatch) {
515 $isfound = ($fld[4] == $clm01);
516 if ($clm01 == 'any' ||
$isfound) {
519 $sts = ($ext == '.ibr') ?
$fld[11] : '';
521 if ($isfound && $ext == '.ibr' && $sts == 'A') {
523 $msgstr = "<p class='ibrmsg'>".PHP_EOL
;
524 $msgstr .= "$nm $pe <br />".PHP_EOL
;
525 $msgstr .= "$btnm <br />".PHP_EOL
;
526 $msgstr .= "Status: $sts<br />".PHP_EOL
;
527 $msgstr .= '</p>'.PHP_EOL
;
534 // there should be only one of these three possibilities, but maybe more than one 3e
535 if ($fld[0] == '3e') {
536 //3e│Error Initiator│R│Error Code – if available, otherwise NA│Error Message | Loop│Segment ID│Element # ││││Version |
537 // different for older 4010, one less field, so loop gives segment, segment gives element, element is blank
538 $sts = ($ext == '.ebr') ?
$fld[2] : $sts;
539 $msgstr = "<p class='ibrmsg'>".PHP_EOL
;
540 $msgstr .= "$nm $pe <br />".PHP_EOL
;
541 $msgstr .= "$btnm <br />".PHP_EOL
;
542 $msgstr .= "Status: $sts<br />".PHP_EOL
;
543 $msgstr .= "Error Initiator: {$fld[1]} Code: {$fld[3]} <br />".PHP_EOL
;
544 $msgstr .= "Loop: {$fld[5]} Segment: {$fld[6]} Element: {$fld[7]} <br />".PHP_EOL
;
545 $msgstr .= wordwrap($fld[4], 46, "<br />".PHP_EOL
);
546 $msgstr .= "</p>".PHP_EOL
;
547 } elseif ($fld[0] == '3c') {
548 $sts = ($ext == '.ebr') ?
$fld[2] : $sts;
549 $msgstr = "<p class='ibrmsg'>".PHP_EOL
;
550 $msgstr .= "$nm $pe <br />".PHP_EOL
;
551 $msgstr .= "$btnm <br />".PHP_EOL
;
552 $msgstr .= "Status: $sts<br />".PHP_EOL
;
553 $msgstr .= wordwrap($fld[4], 46, '<br />'.PHP_EOL
);
554 $msgstr .= '</p>'.PHP_EOL
;
555 } elseif ($fld[0] == '3a') {
556 //3a│Bill Type│Allowed Amount│Non-Covered Amount │Deductible Amount │Co-Pay Amount │Co-insurance Amount │Withhold
557 //Amount │Estimated Payment Amount │Patient Liability│Message Code│Message Text││
559 $msgstr = "<p class='ibrmsg'>".PHP_EOL
;
560 $msgstr .= "$nm $pe <br />".PHP_EOL
;
561 $msgstr .= "$btnm <br />".PHP_EOL
;
562 $msgstr .= "Type: {$fld[1]} <br />".PHP_EOL
;
563 $msgstr .= ($fld[2] =='NA') ?
"" : "Allowed: {$fld[2]}";
564 $msgstr .= ($fld[8] =='NA') ?
"" : " Payment: {$fld[8]}";
565 $msgstr .= ($fld[9] =='NA') ?
"<br />".PHP_EOL
: " Pt Resp: {$fld[9]} <br />".PHP_EOL
;
566 $msgstr .= ($fld[10] =='NA') ?
"" : "Code: {$fld[10]} ";
567 $msgstr .= ($fld[11] =='NA') ?
"" : wordwrap($fld[11], 46, "<br />".PHP_EOL
);
568 $msgstr .= "</p>".PHP_EOL
;
570 } elseif ($clm01 == 'any') {
571 if ($usebatch & !$isbatch) { continue; }
573 if ($fld[0] == '3e') {
575 $msgstr .= "<p class='ibrmsg'>".PHP_EOL
;
576 $msgstr .= "$nm $pe <br />".PHP_EOL
;
577 $msgstr .= "$btnm <br />".PHP_EOL
;
578 $msgstr .= "Error Initiator: {$fld[1]} Code: {$fld[3]} <br />".PHP_EOL
;
579 $msgstr .= "Loop: {$fld[5]} Segment: {$fld[6]} Element: {$fld[7]} <br />".PHP_EOL
;
580 $msgstr .= wordwrap($fld[4], 46, "<br />".PHP_EOL
)."<br />".PHP_EOL
;
581 $msgstr .= "~~~~~~~~<br />".PHP_EOL
;
582 $msgstr .= "</p>".PHP_EOL
;
586 } // end foreach($ebr_ar as $fld)
588 $str_html .= $msgstr;
591 $str_html .= "Did not find $clm01 in $fname <br />".PHP_EOL
;
598 * parse the ebr file format into an array
600 * The array is indexed in different batch files, indicated by line '1'
601 * So a new line will appear in the csv files table for each line '1'
604 * $ar_val[$b]['file']
605 * ['date']['f_name']['clrhsid']['batch']['clm_ct']['clm_rej']['chg_r']
607 * $ar_val[$b]['claims'][$c]
608 * ['pt_name'] ['svcdate']['clm01']['status']['batch']['f_name']['payer']
609 * ['providerid']['prclmnum']['payerid']
611 * ['err_seg']['err_msg']
615 * ['err_type']['err_code']['err_msg']['err_loop']['err_seg']['err_elem']
619 * @see ibr_ebr_process_new_files()
620 * @uses ibr_ebr_ebt_name()
621 * @uses csv_ebr_filetoarray()
622 * @param string path to ebr file
625 function ibr_ebr_values($file_path) {
627 // get file information
628 if (is_readable($file_path)) {
629 // string setlocale ( int $category , array $locale ) // may be needed
630 $path_parts = pathinfo($file_path);
632 // error, unable to read file
633 csv_edihist_log("Error, unable to read file $file_path");
636 $path_parts = pathinfo($file_path);
637 //$ebr_dir = $path_parts['dirname'];
638 $ebr_fname = $path_parts['basename'];
639 $ebr_ext = $path_parts['extension'];
641 if ($ebr_ext != 'ebr') {
642 csv_edihist_log("ibr_ebr_values: incorrect file extension $ebr_ext $ebr_fname");
646 $ebr_mtime = date ("Ymd", filemtime($file_path));
654 // get file contents transformed to array
655 //$ar_ebr = ibr_ebr_filetoarray ($file_path);
656 $ar_ebr = csv_ebr_filetoarray($file_path);
657 if (!is_array($ar_ebr) ||
!count($ar_ebr)) {
658 csv_edihist_log("ibr_ibr_values: failed to read $ebr_fname");
662 foreach($ar_ebr as $ln) {
664 if (strval($ln[0]) == '1') {
665 //['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
669 if (preg_match('/\d{4}\D\d{2}\D\d{2}/', $ln[1])) {
670 $fdate = preg_replace('/\D/', '', $ln[1]);
674 $batch_name = $ln[8];
675 //['date']['f_name']['clrhsid']['batch']['clm_ct']['clm_rej']['chg_r']
676 $ar_val[$b]['file']['date'] = $fdate; // ibr file date
677 $ar_val[$b]['file']['f_name'] = $ebr_fname;
678 $ar_val[$b]['file']['clrhsid'] = $ln[7]; // availity clearinghouse file id
679 $ar_val[$b]['file']['batch'] = $batch_name; // batch file name
690 if (strval($ln[0]) == '2') {
693 $clm_ct +
= intval($ln[2]);
694 $clm_rej +
= intval($ln[6]);
695 $clm_rej_chg +
= floatval($ln[7]);
699 $ar_val[$b]['file']['clm_ct'] = $clm_ct; // claim count
700 $ar_val[$b]['file']['clm_rej'] = $clm_rej; // rejected claims count
701 $ar_val[$b]['file']['chg_r'] = $clm_rej_chg; // rejected charges
705 //['pt_name'] ['svcdate']['clm01']['status']['batch']['f_name']['payer'] ['providerid']['prclmnum']['payerid']['3c']['3e']['3a']
706 if (strval($ln[0]) == '3') {
707 //['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
711 $ar_val[$b]['claims'][$c]['pt_name'] = $ln[1];
712 $ar_val[$b]['claims'][$c]['svcdate'] = $ln[2];
713 $ar_val[$b]['claims'][$c]['clm01'] = $ln[4];
714 $ar_val[$b]['claims'][$c]['batch'] = $batch_name;
715 $ar_val[$b]['claims'][$c]['f_name'] = $ebr_fname;
716 $ar_val[$b]['claims'][$c]['payer'] = $payer;
718 $ar_val[$b]['claims'][$c]['providerid'] = $ln[6];
719 $ar_val[$b]['claims'][$c]['prclmnum'] = $ln[8];
720 $ar_val[$b]['claims'][$c]['payerid'] = $payerid;
725 if (strval($ln[0]) == '3c') {
727 $msg = ''; $err_seg = '';
729 if ($ln[2] != 'A' && $ln[3] == 'NA') {
730 $ar_val[$b]['claims'][$c]['status'] = 'A';
732 $ar_val[$b]['claims'][$c]['status'] = $ln[2];
734 //['err_seg']['err_msg']
735 $err_seg .= (strlen($ln[5]) && $ln[5] != 'NA') ?
$ln[5].' | ' : '';
736 $err_seg.= (strlen($ln[6]) && $ln[6] != 'NA') ?
$ln[6].' | ' : '';
737 $err_seg .= (strlen($ln[7]) && $ln[7] != 'NA') ?
$ln[7].' | ' : '';
739 $msg .= (strlen($ln[1]) && $ln[1] != 'NA') ?
$ln[1].' ' : '';
740 $msg .= (strlen($ln[2]) && $ln[2] != 'NA') ?
'Type: '.$ln[2].' ' : '';
741 $msg .= (strlen($ln[4]) && $ln[4] != 'NA') ?
$ln[4] : '';
743 $ar_val[$b]['claims'][$c]['3c']['err_seg'] = $err_seg;
744 $ar_val[$b]['claims'][$c]['3c']['err_msg'] = $msg;
749 if (strval($ln[0]) == '3e') {
752 if ($ln[2] != 'R' && $ln[3] != 'NA') {
753 $ar_val[$b]['claims'][$c]['status'] = 'R';
755 $ar_val[$b]['claims'][$c]['status'] = $ln[2];
757 //['err_type']['err_code']['err_msg']['err_loop']['err_seg']['err_elem']
758 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_type'] = $ln[1]; // Error Initiator
759 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_code'] = $ln[3]; // Error Code or NA
760 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_msg'] = $ln[4]; // Error Message
761 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_loop'] = $ln[5]; // Loop
762 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_seg'] = $ln[6]; // Segment ID
763 $ar_val[$b]['claims'][$c]['3e'][$err_ct]['err_elem'] = $ln[7]; // Element #
768 if (strval($ln[0]) == '3a') {
770 $ar_val[$b]['claims'][$c]['status'] = 'A';
772 $msg = ''; $msg_txt = '';
774 $msg .= (strlen($ln[1]) && $ln[1] != 'NA') ?
'Bill Type: '.$ln[1] : '';
775 $msg .= (strlen($ln[2]) && $ln[2] != 'NA') ?
'Allowed: '.$ln[2] : '';
776 $msg .= (strlen($ln[3]) && $ln[3] != 'NA') ?
'Non Covered: '.$ln[3] : '';
777 $msg .= (strlen($ln[4]) && $ln[4] != 'NA') ?
'Deductible '.$ln[4] : '';
778 $msg .= (strlen($ln[5]) && $ln[5] != 'NA') ?
'Co-Pay: '.$ln[5] : '';
779 $msg .= (strlen($ln[6]) && $ln[6] != 'NA') ?
'Co-ins: '.$ln[6] : '';
780 $msg .= (strlen($ln[7]) && $ln[7] != 'NA') ?
'Withhold: '.$ln[7] : '';
781 $msg .= (strlen($ln[8]) && $ln[8] != 'NA') ?
'Est Pmt: '.$ln[8] : '';
782 $msg .= (strlen($ln[9]) && $ln[9] != 'NA') ?
'Pt Rsp: '.$ln[9] : '';
784 $msg_txt .= (strlen($ln[10]) && $ln[10] != 'NA') ?
'Code: '.$ln[10] : '';
785 $msg_txt .= (strlen($ln[11]) && $ln[11] != 'NA') ?
' '.$ln[11] : '';
787 $ar_val[$b]['claims'][$c]['3a']['pmt'] = $msg;
788 $ar_val[$b]['claims'][$c]['3a']['msg'] = $msg_txt;
797 * parse the ibr file format into an array
799 * Very similar to ibr_ebr_values(), with slight differences
800 * The array is indexed in different batch files, indicated by line '1'.
801 * So a new line will appear in the csv files table for each line '1'.
804 * $ar_val[$b]['file']
805 * ['date']['f_name']['clrhsid']['batch']['clm_ct']['clm_rej']['chg_r']
807 * $ar_val[$b]['claims'][$c]
808 * ['pt_name'] ['svcdate']['clm01']['status']['batch']['f_name']['payer']
809 * ['providerid']['bht03']['payerid']
811 * ['err_type']['err_code']['err_msg']['err_loop']['err_seg']['err_elem']
815 * @see ibr_ebr_process_new_files()
816 * @uses ibr_ebr_ebt_name()
817 * @uses csv_ebr_filetoarray()
818 * @param string path to ibr file
821 function ibr_ibr_values($file_path) {
823 // get file information
824 if (is_readable($file_path)) {
825 // string setlocale ( int $category , array $locale ) // may be needed
826 $path_parts = pathinfo($file_path);
828 // error, unable to read file
829 csv_edihist_log("ibr_ibr_values: Error, unable to read file $file_path");
832 $path_parts = pathinfo($file_path);
833 //$ibr_dir = $path_parts['dirname'];
834 $ibr_fname = $path_parts['basename'];
835 $ibr_ext = $path_parts['extension'];
837 if ($ibr_ext != 'ibr') {
838 csv_edihist_log("ibr_ibr_values: incorrect file extension $ibr_ext $ibr_fname");
842 $ibr_mtime = date ("Ymd", filemtime($file_path));
851 // get file contents transformed to array
852 //$ar_ibr = ibr_ebr_filetoarray ($file_path);
853 $ar_ibr = csv_ebr_filetoarray($file_path);
854 if (!is_array($ar_ibr) ||
!count($ar_ibr)) {
855 csv_edihist_log("ibr_ibr_values: failed to read $ibr_fname");
859 foreach($ar_ibr as $ln) {
861 if (strval($ln[0]) == '1') {
862 //['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
866 $batch_name = $ln[15];
869 if (preg_match('/\d{4}\D\d{2}\D\d{2}/', $ln[1])) {
870 $fdate = preg_replace('/\D/', '', $ln[1]);
874 $ar_ih[$b]['file']['date'] = $fdate; // ibr file date
875 $ar_ih[$b]['file']['f_name'] = $ibr_fname;
876 $ar_ih[$b]['file']['btctln'] = $ln[5]; // batch control number [ISA13]
877 $ar_ih[$b]['file']['clm_ct'] = $ln[6]; // claim count
878 //$ar_ih[$b]['file']['chg_s'] = $ln[8]; // submitted charges total [CLM02 ?]
879 $ar_ih[$b]['file']['clm_rej'] = $ln[10]; // rejected claims count
880 $ar_ih[$b]['file']['chg_r'] = $ln[11]; // rejected charges
881 $ar_ih[$b]['file']['clrhsid'] = $ln[14]; // availity clearinghouse file id
882 $ar_ih[$b]['file']['batch'] = $ln[15]; // batch file name
887 if (strval($ln[0]) == '2') {
893 //['pt_name'] ['svcdate']['clm01']['status']['batch']['filename']['payer'] ['providerid']['bht03']['payerid']
894 if (strval($ln[0]) == '3') {
895 //['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
899 $ar_ih[$b]['claims'][$c]['pt_name'] = $ln[1];
900 $ar_ih[$b]['claims'][$c]['svcdate'] = $ln[2];
901 $ar_ih[$b]['claims'][$c]['clm01'] = $ln[4];
902 $ar_ih[$b]['claims'][$c]['status'] = $ln[11];
903 $ar_ih[$b]['claims'][$c]['batch'] = $batch_name;
904 $ar_ih[$b]['claims'][$c]['f_name'] = $ibr_fname;
905 $ar_ih[$b]['claims'][$c]['payer'] = $payer;
907 $ar_ih[$b]['claims'][$c]['providerid'] = $ln[6];
908 $ar_ih[$b]['claims'][$c]['bht03'] = (strlen($ln[10]) >= 9) ?
$ln[10] : $batch_ctl;
909 $ar_ih[$b]['claims'][$c]['payerid'] = $payerid;
914 if (strval($ln[0]) == "3e") {
915 // increment error count -- more than one error is possible
916 // ibr files have a 3e only in error case, no 3a or 3c
919 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_type'] = $ln[1]; // Error Initiator
920 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_code'] = $ln[3]; // Error Code or NA
921 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_msg'] = $ln[4]; // Error Message
922 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_loop'] = $ln[5]; // Loop
923 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_seg'] = $ln[6]; // Segment ID
924 $ar_ih[$b]['claims'][$c]['3e'][$err_ct]['err_elem'] = $ln[7]; // Element #
935 * Create html table for displaying processing results
937 * @param array $ar_data array produced by function ibr_ebr_data_ar
938 * @param $err_only boolean ignore claim information if no 3e subarray is in the claim data
941 function ibr_ebr_html ($ar_data, $err_only=false) {
942 // create an html string for a table to display in a web page
943 //$ar_hd = $ar_data['head'];
944 //$ar_cd = $ar_data['claims'];
952 $dtl = ($err_only) ?
"Errors only" : "All included claims";
954 // the table heading for files
955 $f_hdg = "<table cols=6 class=\"ibr_ebr\">
956 <caption>IBR-EBR Files Summary {$dtl} </caption>
959 <th>IBR-EBR File</th><th>Date</th>
960 <th>Batch</th><th>Claims</th><th>Rej</th><th> </th>
965 // the details table heading
966 $clm_hdg = "<table cols=6 class=\"ibr_ebr\">
969 <th>Patient Name</th><th>Date</th><th>CtlNum</th>
970 <th>Status</th><th>Payer Name</th><th>Type Code Loop Segment Field</th>
973 <th colspan=6 align=left>Message</th>
978 // start with the table heading
981 foreach ($ar_data as $ardt) {
983 $bgf = ($idf %
2 == 1 ) ?
'fodd' : 'feven';
986 $ar_hd = isset($ardt['file']) ?
$ardt['file'] : NULL ;
987 $ar_cd = isset($ardt['claims']) ?
$ardt['claims'] : NULL ;
989 if (!$ar_hd && !$ar_cd) {
990 $str_html .= "ibr_ebr_html: empty array or wrong keys <br />" . PHP_EOL
;
993 // if we had a claim detail, we need to append the files heading
994 if ($hasclm) { $str_html .= $f_hdg; }
996 // if any individual claims detail is to be output this will be set true
1000 //['date']['f_name']['availid']['batch']['clm_acc']['chg_s']['clm_rej']['chg_r']
1001 $str_html .= "<tr class=\"{$bgf}\">" .PHP_EOL
;
1002 $str_html .= "<td>{$ar_hd['date']}</td>
1003 <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>
1004 <td><a target=\"_blank\" href=\"edi_history_main.php?fvkey={$ar_hd['batch']}\">{$ar_hd['batch']}</a></td>
1005 <td>{$ar_hd['clm_ct']}</td>
1006 <td>{$ar_hd['clm_rej']}</td>
1007 <td> </td>" .PHP_EOL
;
1008 $str_html .= "</tr>" .PHP_EOL
;
1009 //{$ar_hd['ft_name']}
1011 // now the individual claims details
1012 //['pt_name'] ['svcdate']['clm01']['status']['batch']['filename']['payer'] ['providerid']['bht03']['payerid']
1014 // create html output for the claims
1015 foreach ($ar_cd as $val) {
1016 // if $err_only, then only get information on claims with 3e
1017 if ($err_only && !array_key_exists("3e", $val) ) { continue; }
1019 // alternate row background colors
1020 $bgc = ($idx %
2 == 1 ) ?
'odd' : 'even';
1022 // since we are here, we have claim details in the output
1025 $clm_html .= "<tr class=\"{$bgc}\">
1026 <td>{$val['pt_name']}</td>
1027 <td>{$val['svcdate']}</td>
1028 <td><a class=\"btclm\" target=\"_blank\" href=\"edi_history_main.php?fvbatch={$val['batch']}&btpid={$val['clm01']}\">{$val['clm01']}</a></td>
1029 <td><a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?ebrfile={$val['f_name']}&ebrclm={$val['clm01']}\">{$val['status']}</a></td>
1030 <td title=\"{$val['payerid']}\">{$val['payer_name']}</td>";
1032 // do not finish the row here, test for 3e, 3a, or 3c
1034 if (array_key_exists("3e", $val)) {
1035 // there may be more than one error reported
1036 foreach ($val['3e'] as $er) {
1037 $clm_html .= "<td>{$er['err_type']} {$er['err_code']} {$er['err_loop']} {$er['err_seg']} {$er['err_elem']}</td>
1039 <tr class=\"{$bgc}\" >
1040 <td colspan=6>{$er['err_msg']}</td>
1042 } // end foreach ($val['3e'] as $er)
1043 } elseif (array_key_exists("3a", $val)) {
1044 $clm_html .= "<td>payment</td>
1047 <td colspan=6>{$val['3a']['msg']}</td>
1049 <tr class=\"{$bgc}\">
1050 <td colspan=6>{$val['3a']['msg_txt']}</td>
1052 } elseif (array_key_exists("3c", $val)) {
1053 $clm_html .= "<td>{$val['3c']['err_seg']}</td>
1055 <tr class=\"{$bgc}\">
1056 <td colspan=6>{$val['3c']['err_msg']}</td>
1059 // ibr files only report 3e
1060 $clm_html .= "<td> </td>";
1063 } // end foreach(($ar_cd as $val)
1065 // if there were any claims detailed
1066 if ( $hasclm && strlen($clm_html) ) {
1067 $str_html .= "</tbody>".PHP_EOL
;
1068 $str_html .= $clm_hdg . $clm_html;
1070 } // end if ($ar_cd)
1071 } // end foreach ($ar_data as $ardt)
1073 // finish the table and add a <p>
1074 $str_html .= "</tbody></table>
1081 * order array of file information for csv table
1083 * @param array file array created elswhere
1086 function ibr_ebr_csv_files($head_ar) {
1087 // the file record csv file
1088 //['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1089 $f_file_data = array();
1090 $f_file_data[0] = $head_ar['date'];
1091 $f_file_data[1] = $head_ar['f_name'];
1092 // put file id as column 2 $f_file_data[2] = $head_ar['availid']
1093 $f_file_data[2] = $head_ar['clrhsid'];
1094 $f_file_data[3] = $head_ar['clm_ct'];
1095 $f_file_data[4] = $head_ar['clm_rej'];
1096 $f_file_data[5] = $head_ar['batch'];
1098 return $f_file_data;
1103 * order the claim data array for csv file
1105 * @param array individual claim array
1108 function ibr_ebr_csv_claims($claim_ar) {
1110 $f_claim_data = array();
1111 //['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer')
1112 $f_claim_data[0] = $claim_ar['pt_name'];
1113 $f_claim_data[1] = $claim_ar['svcdate'];
1114 $f_claim_data[2] = $claim_ar['clm01'];
1115 $f_claim_data[3] = $claim_ar['status'];
1116 $f_claim_data[5] = $claim_ar['batch'];
1117 $f_claim_data[6] = $claim_ar['f_name'];
1118 $f_claim_data[4] = $claim_ar['payer'];
1120 return $f_claim_data;
1125 * the main function for ebr and ibr files in this script
1127 * @uses csv_newfile_list()
1128 * @uses csv_verify_file()
1129 * @uses csv_write_record()
1130 * @uses ibr_ebr_values()
1131 * @uses ibr_ibr_values()
1132 * @uses ibr_ebr_csv_files()
1133 * @uses ibr_ebr_csv_claims()
1134 * @param array optional array of filenames or null
1135 * @param string $extension one of ibr, or ebr; default is ibr
1136 * @param bool $html_out true or false whether html output string should be created and returned
1137 * @param bool $err_only true or false whether html output shows files and only errors or all claims
1138 * @return string|bool html output string or boolean
1140 function ibr_ebr_process_new_files($files_ar=NULL, $extension='ibr', $html_out=TRUE, $err_only=TRUE ) {
1142 // process new ebr or ibr files by calling the functions in this script
1143 // three optional arguments to be passed to functions and used within
1147 // patterned from ibr_batch_read.php
1148 if ( $files_ar === NULL ||
!is_array($files_ar) ||
count($files_ar) == 0) {
1149 $f_new = csv_newfile_list($extension);
1150 } elseif ( is_array($files_ar) && count($files_ar) ) {
1154 if ( count($f_new) == 0 ) {
1156 $html_str .= "<p>ibr_ebr_process_new: no new $extension files <br />";
1162 // we have some new files
1163 // verify and get complete path
1164 foreach($f_new as $fbt) {
1165 $fp = csv_verify_file($fbt, $extension, false);
1166 if ($fp) { $f_list[] = $fp; }
1168 $fibrcount = count($f_list);
1170 // initialize variables
1178 // sort ascending so latest files are last to be output
1179 $is_sort = asort($f_list); // returns true on success
1181 // Step 2: file data written to csv files
1182 // also html string created if $html_out = TRUE
1183 foreach ($f_list as $f_name) {
1184 // get the data array for the file
1187 if (substr($f_name, -3) != $extension) {
1188 csv_edihist_log("ibr_ebr_process_new_files: type mismatch $extension " . basename($f_name));
1192 if ($extension == 'ibr') {
1193 $data_vals = ibr_ibr_values($f_name);
1194 } elseif ($extension == 'ebr') {
1195 $data_vals = ibr_ebr_values($f_name);
1197 csv_edihist_log("ibr_ebr_process_new_files: incorrect extension $ext " . basename($f_name));
1201 if (is_array($data_vals) && count($data_vals)) {
1202 foreach($data_vals as $dm) {
1203 $wf[] = ibr_ebr_csv_files($dm['file']);
1204 foreach($dm['claims'] as $cl) {
1206 $wc[] = ibr_ebr_csv_claims($cl);
1213 $chrf +
= csv_write_record($wf, $extension, "file");
1214 $chrc +
= csv_write_record($wc, $extension, "claim");
1217 //$html_str .= ibr_ebr_html ($data_ar, $err_only);
1218 $html_str .= ibr_ebr_html ($data_vals, $err_only);
1220 $html_str .= "IBR/EBR files: processed $fibrcount $extension files <br />".PHP_EOL
;
1227 * generate output as if file is being processed
1229 * @param string filename
1230 * @param bool display errors only
1233 function ibr_ebr_filetohtml($filepath, $err_only=false) {
1234 // simply create an html output for the file
1238 $ext = substr($filepath, -3);
1239 if ( strpos('|ibr|ebr', $ext) ) {
1240 $fp = csv_verify_file( $filepath, "ebr");
1244 if ($ext == 'ebr') {
1245 $data_ar = ibr_ebr_values($fp);
1246 } elseif ($ext == 'ibr') {
1247 $data_ar = ibr_ibr_values($fp);
1249 csv_edihist_log ("ibr_ebr_filetohtml: invalid extension $ext " . basename($fp) );
1250 return "<p>invalid extension $ext </p>".PHP_EOL
;
1253 $html_str .= ibr_ebr_html ($data_ar, $err_only);
1255 csv_edihist_log ("ibr_ebr_filetohtml: verification failed $filepath");
1256 $html_str .= "Error, validation failed $filepath <br />";