edihistory -- revisions and cleanup of 277 claim status functions
[openemr.git] / library / edihistory / ibr_batch_read.php
blob5e4c614cb359d136f6f6d140d8bc6f3bba11b9e6
1 <?php
2 /**
3 * ibr_batch_read.php
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3 or later.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * <http://opensource.org/licenses/gpl-license.php>
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 * MA 02110-1301, USA.
21 * This file has functions to deal with OpenEMR batch files when identifying
22 * rejected claims in .997 files, from the Availity LLC clearinghouse.
23 * The concept is to use a .csv to store batch file names, control id numbers, and claim counts.
24 * When the Availity .997 file is read, we can get a control number and claim number for
25 * each claim rejected at the initial level -- these are never submitted to payers.
26 * We use the control number to find the claim file and the claim number to find the
27 * encounter and patient id.
28 * Then we can manually review the claim and hopefully resolve the error.
30 * Also a csv file of claim information is created. This allows one to see a list
31 * of all claims created and also allows other scripts to access claim information
32 * more easily.
34 * Also functions to output the text of a particular claim
36 * @link: http://www.open-emr.org
37 * @author Kevin McCormick
38 * @link: http://www.open-emr.org
39 * @package OpenEMR
40 * @subpackage ediHistory
43 // a security measure to prevent direct web access to this file
44 // must be accessed through the main calling script ibr_history.php
45 // from admin at rune-city dot com; found in php manual
46 // if (!defined('SITE_IN')) die('Direct access not allowed!');
48 // these are hardcoded into OpenEMR batch files
49 if (!defined("SEG_ELEM_DELIM")) define( "SEG_ELEM_DELIM" , "*");
50 if (!defined("SEG_TERM_DELIM")) define( "SEG_TERM_DELIM" , "~");
51 //
53 /**
54 * Derives the batch file date from the file name
56 * @param string $file_name - batch file name
57 * @return string date in YYYMMDD format
59 function ibr_batch_datefromfilename($file_name) {
61 // this function relies on OpenEMR batch file name convention
63 $fn = basename($file_name);
65 // try to get file date by file name
66 $isdt = preg_match('/201[0-9]-[01]{1}[0-9]{1}-[0-3]{1}[0-9]{1}/', $fn, $match);
68 $dtstr = str_replace('-', '', $match[0]);
70 if ($isdt && strlen($dtstr) == 8) {
71 $f_date = $dtstr;
72 } else {
73 $f_date = FALSE;
76 return $f_date;
80 /**
81 * creates an array of controlnum => file name
83 * @param int $length=20 the number of most recent files to include
84 * @return array key => value is control_num => file name
86 function ibr_batch_by_ctln($length=20) {
87 //
88 // read the .csv file into an array
89 //
90 // hopefully OpenEMR does not repeat the control_num
91 $p = csv_parameters('batch');
92 //$batch_csv_path = dirname(__FILE__).$p['files_csv'];
93 $batch_csv_path = $p['files_csv'];
94 //array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
95 $ar_ctlid_batch = array();
96 $idx = 0;
97 if (($fh1 = fopen($batch_csv_path, "r")) !== FALSE) {
98 while (($data = fgetcsv($fh1, 1000, ",")) !== FALSE) {
99 $idx++;
100 $ar_ctlid_batch[$data[2]] = $data[1];
101 if ($idx >= $length) { break; }
103 fclose($fh1);
104 } else {
105 return false;
108 return $ar_ctlid_batch;
109 } // end function ibr_batch_by_ctln
113 * look in the csv table claims_batch.csv and find the pid-encounter
115 * @deprecated
116 * @uses csv_search_record()
117 * @param string $bht837 the icn and st numbers concatenated
118 * @return string the pid-encounter, expect only one match
120 function ibr_batch_find_pid_with_ctl_st ($bht837) {
122 $bval = ($bht837 ) ? trim($bht837) : '';
123 if (strlen($bval) == 13) {
124 $b = $bval;
125 } else {
126 csv_edihist_log("ibr_batch_find_pid_with_ctl_st: invalid argument $bht837");
127 return false;
129 //$search = array('s_val'=>$b, 's_col'=>7, 'r_cols'=>array(2));
130 $search = array('s_val'=>$b, 's_col'=>4, 'r_cols'=>array(2));
131 $finfo = csv_search_record('batch', 'claim', $search, "1");
132 if (is_array($finfo) && count($finfo) ) {
133 // we seem to have found something
134 return $finfo[0][0];
135 } else {
136 // nothing found
137 return false;
143 * get the pid-encounter from the batch file using the ST number.
145 * ST number is is how claims are identified in 999 files
147 * @uses csv_parameters()
148 * @uses csv_verify_file()
149 * @param string $st_clm_num the ST02 number in the batch file
150 * @param string $batch_file the batch file name
151 * @return string the pid-encounter
153 function ibr_batch_get_pid ( $st_clm_num, $batch_file ) {
155 // get the pid-encounter from the batch file
157 if (strlen($st_clm_num) == 13) {
158 $st_num = substr($st_clm_num, -4);
159 if (!$batch_file) {
160 $btctln = substr($st_clm_num, 0, 9);
161 $batch_file = csv_file_by_controlnum('batch', $btctln);
163 } elseif ( strlen($st_clm_num) < 4 ) {
164 $st_num = str_pad ($st_clm_num, 4, "0", STR_PAD_LEFT);
165 } else {
166 $st_num = trim($st_clm_num);
169 $bfullpath = csv_verify_file($batch_file, 'batch');
170 if (!$bfullpath) {
171 $str_st = "Error: failed to read $batch_file" . PHP_EOL;
172 return FALSE;
173 } else {
174 $bstr = file_get_contents($bfullpath);
176 if (!$bstr) {
177 $str_st = "Error: failed to read $batch_file" . PHP_EOL;
178 return FALSE;
182 $seg_st = "ST*837*" . $st_num; // particular ST block
184 $seg_clm = "CLM*";
186 $st_pos = strpos($bstr, $seg_st, 0);
187 if ( $st_pos == FALSE ) {
188 csv_edihist_log("ibr_batch_get_pid: $st_num not found in $batch_file" );
189 return FALSE;
191 $clm_pos = strpos($bstr, $seg_clm, $st_pos);
192 // pid-encounter is first element of CLM segment
193 $epos1 = strpos($bstr, "*", $clm_pos);
194 $epos2 = strpos($bstr, "*", $epos1+1);
196 $pe = substr($bstr, $epos1+1, $epos2-$epos1-1);
198 return $pe;
202 * increment loop values ($lpval is a reference)
204 * @param $lptest the prospective loop value
205 * @param &$lpval the present loop value -- reassigned here
206 * @return integer value from strcmp()
208 function ibr_batch_change_loop($lptest, &$lpval) {
210 // strcmp($str1,$str2) Returns < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal.
211 if ( strcmp($lptest, $lpval) > 0) {
212 //echo "$lptest greater than $lpval" .PHP_EOL;
213 $lpval = $lptest;
215 return strcmp($lptest, $lpval);
219 * Create an html table of Loop | Segment for viewing x12 v5010A1 claims
221 * @uses ibr_batch_change_loop()
222 * @param string $st_block substring of batch file ST...SE segments making up a claim
223 * @return string html table
225 function ibr_batch_st_html($st_block, $batchname='', $claimid='', $message='') {
227 // count the segment terminators
228 $seg_ct = substr_count ($st_block, SEG_TERM_DELIM);
229 if (!$seg_ct) {
230 $st_html = "segment terminator error <br />" .PHP_EOL;
231 return $st_html;
233 $capstr = 'Batch Claim';
234 if ($batchname) { $capstr = $batchname; }
235 if ($claimid) { $capstr .= " Claim: $claimid"; }
236 $st_html = "<div class='filetext'>".PHP_EOL; //."<pre><code>".PHP_EOL;
237 $st_html .= ($message) ? "<p>$message</p>".PHP_EOL : '';
238 $st_html .= "<table id='$claimid' cols=3 class='batchst'><caption>$capstr</caption>".PHP_EOL;
239 $st_html .= "<thead>".PHP_EOL."<tr>".PHP_EOL."<th>Loop</th><th>Num</th><th>Segment</th>".PHP_EOL."</tr>".PHP_EOL;
240 $st_html .= "</thead>".PHP_EOL."<tbody>".PHP_EOL;
241 $loop = "";
242 //$hasSBR = FALSE;
243 //$hasCTP = FALSE;
244 //$hasCLM = FALSE;
245 $lx_ct = 0;
246 $idx = 0;
248 //$st_segs = explode("~",$st_block);
249 $st_segs = explode(SEG_TERM_DELIM, $st_block);
250 foreach($st_segs as $sts) {
251 $idx++;
252 if ($idx >= $seg_ct && !$sts) { break; } // the last element of $st_segs may be just \n or empty
253 $segstr = trim($sts);
254 //$seg = explode("*", $segstr );
255 $seg = explode(SEG_ELEM_DELIM, $segstr );
256 if ($seg[0] == "ST") {
257 $loop = '0';
258 //$hasCLM = FALSE;
259 $st_html .= "<tr><td class='btloop'>Header</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
260 continue;
262 if ($seg[0] == "BHT") {
263 $loop = '0';
264 $st_html .= "<tr><td class='btloop'>Begin</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
265 continue;
267 // organize this by loops -- first determine the loop
268 // we do assume the $loop is defined for some choices further down the list
269 // many loops begin with the NM1 segment
270 if ($seg[0] == "NM1") {
271 // SUBMITTER NAME
272 if ($seg[1] == "41") { ibr_batch_change_loop('1000A', $loop); }
273 // RECEIVER NAME
274 if ($seg[1] == "40") { ibr_batch_change_loop('1000B', $loop) ; }
275 // Billing Provider Name
276 if ($seg[1] == "85") {
277 ibr_batch_change_loop('2010AA', $loop);
278 // OTHER PAYER BILLING PROVIDER
279 if (strcmp($loop, '2300') > 0) { ibr_batch_change_loop('2330G', $loop); }
281 // PAY-TO ADDRESS NAME
282 if ($seg[1] == "87") {
283 ibr_batch_change_loop('2010AB', $loop);
284 if (strcmp($loop, '2300') > 0) { ibr_batch_change_loop('2330G', $loop); }
286 // PAY TO PLAN NAME
287 if ($seg[1] == "PE") {
288 ibr_batch_change_loop('2010AC', $loop);
289 if (strcmp($loop, '2300') > 0) { ibr_batch_change_loop('2330B', $loop); }
291 // SUBSCRIBER NAME
292 if ($seg[1] == "IL") {
293 ibr_batch_change_loop('2010BA', $loop);
294 //OTHER SUBSCRIBER NAME
295 if (strcmp($loop, '2300') > 0) { ibr_batch_change_loop('2330A', $loop); }
298 // PAYER NAME
299 if ($seg[1] == "PR") {
300 ibr_batch_change_loop('2010BB', $loop);
301 //OTHER PAYER NAME
302 if (strcmp($loop, '2300') > 0) { ibr_batch_change_loop('2330B', $loop); }
305 // PATIENT NAME
306 if ($seg[1] == "QC") {
307 ibr_batch_change_loop('2010CA', $loop);
308 // patient name is only in 2010CA loop -- applies to all segments in 2300
309 //if (strcmp($loop, '2300') > 0) { ibr_batch_change_loop('2330B', $loop); }
311 // REFERRING PROVIDER NAME
312 if ($seg[1] == "DN" || $seg[1] == "P3" ) {
313 ibr_batch_change_loop('2310A', $loop);
314 // OTHER PAYER REFERRING PROVIDER
315 if (strcmp($loop, '2310A') > 0) { ibr_batch_change_loop('2330C', $loop); }
318 // RENDERING PROVIDER NAME
319 if ($seg[1] == "82") {
320 ibr_batch_change_loop('2310B', $loop);
321 // OTHER PAYER RENDERING PROVIDER
322 if (strcmp($loop, '2310B') > 0) { ibr_batch_change_loop('2330D', $loop); }
323 // RENDERING PROVIDER NAME
324 if (strcmp(substr($loop,0, 4), '2400') > 0) { ibr_batch_change_loop('2420A', $loop); }
326 // SERVICE FACILITY LOCATION
327 if ($seg[1] == "77") {
328 ibr_batch_change_loop('2310C', $loop);
329 // OTHER PAYER SERVICE FACILITY LOCATION
330 if (strcmp($loop, '2310C') > 0) { ibr_batch_change_loop('2330E', $loop); }
332 // SUPERVISING PROVIDER NAME
333 if ($seg[1] == "DQ") {
334 ibr_batch_change_loop('2310D', $loop);
335 // OTHER PAYER SUPERVISING PROVIDER
336 if (strcmp($loop, '2310D') > 0) { ibr_batch_change_loop('2330F', $loop); }
338 // AMBULANCE PICK UP LOCATION
339 if ($seg[1] == "PW") {
340 ibr_batch_change_loop('2310E', $loop);
341 if (strcmp($loop, '2310E') > 0) { ibr_batch_change_loop('2420G', $loop); }
343 // AMBULANCE DROP OFF LOCATION
344 if ($seg[1] == "45") {
345 ibr_batch_change_loop('2310F', $loop);
346 if (strcmp($loop, '2310F') > 0) { ibr_batch_change_loop('2420H', $loop); }
348 // PURCHASED SERVICE PROVIDER NAME
349 if ($seg[1] == "QB") { $loop = '2420B' ; }
350 // ORDERING PROVIDER NAME
351 if ($seg[1] == "DK") { $loop = '2420E' ; }
353 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
354 continue;
357 if ($seg[0] == "HL") {
358 if ($seg[1] == "1") { $loop = '2000A'; }
359 if ($seg[1] > "1") {
360 if ($seg[3] == "22") { $loop = '2000B'; }
361 if ($seg[3] == "23") { $loop = '2000C'; }
362 $haschild = ($seg[4] == "1");
364 $hl_lev = $seg[3];
365 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
366 continue;
369 if ($seg[0] == "CLM") {
370 $loop = '2300';
371 //$hasCLM = TRUE;
372 $lx_ct = 0;
373 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
374 continue;
376 // Subsriber or
377 if ($seg[0] == "SBR") {
378 ibr_batch_change_loop('2000B', $loop);
379 //$loop = ($hl_lev == "22") ? '2000B' : '2320';
380 // OTHER SUBSCRIBER INFORMATION
381 // do not test for loop value, just restart the loop 2320
382 //if (strcmp(substr($loop, 0, 4), '2300') > 0) { ibr_batch_change_loop('2320', $loop); }
383 if (strcmp(substr($loop, 0, 4), '2300') > 0) { $loop = '2320'; }
384 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
385 continue;
389 if ($seg[0] == "CAS") {
390 $loop = ($lx_ct) ? '2430' : '2320';
391 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
392 continue;
395 if ($seg[0] == "LX") {
396 $loop = '2400';
397 $lx_ct++;
398 $newLX = ($seg[1] == $lx_ct) ? TRUE : FALSE;
399 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
400 continue;
402 if ($seg[0] == "SV1") {
403 $loop = '2400';
404 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
405 continue;
408 if ($seg[0] == "CTP") {
409 $loop = '2410';
410 //$hasCTP = TRUE;
411 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
412 continue;
415 if ($seg[0] == "LQ") {
416 $loop = '2440';
417 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
418 continue;
420 if ($seg[0] == "SE") {
421 $loop = '0';
422 $st_html .= "<tr><td class='btloop'>Trailer</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
423 continue;
426 // for all the segments that do not begin loops
427 $st_html .= "<tr><td class='btloop'>$loop</td><td class='btloop'>$idx</td><td class='btseg'>$segstr</td></tr>" .PHP_EOL;
431 $st_html .= "</tbody></table>".PHP_EOL;
432 $st_html .= "<p></p>".PHP_EOL."</div>".PHP_EOL;
434 return $st_html;
439 * Select the substring of ST...SE segments comprising a claim in a batch file.
441 * one of $st_clm_num or $pid_enctr must be provided
442 * the batch file, if unknown and given as '', may be found if a $pid_enctr is supplied
444 * @uses csv_parameters()
445 * @uses csv_verify_file()
446 * @uses ibr_batch_st_html()
447 * @uses csv_file_with_pid_enctr()
448 * @param string $batch_file the batch file name
449 * @param string $st_clm_num the ST02 number in the batch file
450 * @param string $pid_enctr the pid-encounter identifying the claim
451 * @param boolean $plain_text just return the ST...SE segments substring
452 * @return string html table from ibr_batch_st_html()
454 function ibr_batch_get_st_block ($batch_file, $st_clm_num=NULL, $pid_enctr=NULL, $plain_text=FALSE) {
456 // get the substring of the batch file containing the ST number
458 if ( is_null($st_clm_num) && is_null($pid_enctr) ) {
459 csv_edihist_log("ibr_batch_get_st_block: both st_clm_num and pid_enctr were NULL");
460 $out_str .= "ibr_batch_get_st_block: you must provide either ST number or pid-encounter <br />" . PHP_EOL;
461 return $out_str;
464 $out_str = "";
465 $msg_str = '';
466 $btchname = basename($batch_file);
468 $bfullpath = ($batch_file) ? csv_verify_file($batch_file, 'batch') : '';
469 if ($bfullpath) {
470 $out_str .= $batch_file .PHP_EOL;
471 $btchname = basename($bfullpath);
472 } else {
473 // possibly batch file was not determined when parsing a response file
474 if ($batch_file && !$bfullpath) {
475 if ( strlen($batch_file) >= 9 ) {
476 // try control number search
477 $btchname = csv_file_by_controlnum('batch', $batch_file);
478 $bfullpath = csv_verify_file($btchname, 'batch');
479 if (!$bfullpath) {
480 $msg_str .= "batch file not found: $batch_file". PHP_EOL;
482 } else {
483 $msg_str .= "batch file not found: $batch_file". PHP_EOL;
485 } elseif (!$batch_file) {
486 $msg_str .= "batch file name not supplied";
488 if (!$bfullpath && $pid_enctr) {
489 $pe1 = trim($pid_enctr);
490 $btnm1 = csv_file_with_pid_enctr($pe1, 'batch', 'ptctln' );
491 // select the last file found
492 $btnm2 = count($btnm1) ? $btnm1[count($btnm1)-1][1] : FALSE;
493 if ($btnm2) {
494 $bfullpath = csv_verify_file($btnm2, 'batch');
495 for($i=0; $i<count($btnm1); $i++) {
496 $msg_str .= "found {$btnm1[$i][1]}". PHP_EOL;
498 $msg_str .= "using $btnm2".PHP_EOL;
499 } else {
500 $bfullpath = FALSE;
503 } elseif ( !$bfullpath && strlen($st_clm_num) == 13 ) {
504 $ctln = substr($st_clm_num, 0, 9);
505 $stnum = substr($st_clm_num, -4);
506 $btnm1 = csv_file_by_controlnum('batch', $ctln);
507 $bfullpath = ($btnm1) ? csv_verify_file($btnm1, 'batch') : FALSE;
508 } else {
509 csv_edihist_log("ibr_batch_get_st_block: batch file not found");
510 return false;
513 // Now, if we determined the file name
514 $bstr = ($bfullpath) ? file_get_contents($bfullpath) : FALSE;
515 if (!$bstr) {
516 csv_edihist_log("ibr_batch_get_st_block: failed to read file $btchname");
517 return false;
520 if ($pid_enctr) {
521 $pe = trim($pid_enctr);
522 $seg_clm="CLM*$pe*";
523 $seg_st = "ST*837*";
524 $clm_pos = strpos($bstr, $seg_clm, 0);
525 // php directions for strrpos are wrong
526 $st_pos = strrpos(substr($bstr, 0, $clm_pos), $seg_st);
527 $seg_st = substr($bstr, $st_pos, 11);
528 $st_num = substr($seg_st, -4);
529 } elseif ($st_clm_num) {
530 $st_num = substr($st_clm_num, -4) ? substr($st_clm_num, -4) : trim($st_clm_num);
531 if ( strlen($st_num) < 4 ) { $st_num = str_pad ($st_num, 4, "0", STR_PAD_LEFT); }
532 $seg_st = 'ST'.SEG_ELEM_DELIM.'837'.SEG_ELEM_DELIM.$st_num; // particular ST block
533 $st_pos = strpos($bstr, $seg_st, 0);
534 } else {
535 csv_edihist_log("ibr_batch_get_st_block: ST number and Encounter missing");
536 return FALSE;
538 // break it off if $st_pos is not found
539 if ($st_pos === FALSE ) {
540 csv_edihist_log("ibr_batch_get_st_block: ST $st_num not found in $btchname");
541 return FALSE;
544 $seg_se = SEG_TERM_DELIM.'SE'.SEG_ELEM_DELIM;
546 $se1 = strpos($bstr, $seg_se, $st_pos);
547 $se2 = strpos($bstr, SEG_TERM_DELIM, $se1+1 ) + 1;
549 $str_st = substr($bstr, $st_pos, $se2-$st_pos);
551 if ($plain_text) {
552 $out_str = $str_st;
553 } else {
554 $bnm = basename($bfullpath);
555 $clmid = ($pe) ? $pe : $st_num;
556 $out_str = ibr_batch_st_html($str_st, $bnm, $clmid, $msg_str);
559 return $out_str;
563 * Search a batch file to identify a claim by the ST number
565 * Claims identified in .997/999 files must be located in batch files
566 * The .997 file is per a transaction control number in the TA1 segment,
567 * which is used to identify the batch file. However, the TA1 segment may
568 * not be provided in the .999 file, so the batch file may be unknown.
569 * The ST02 and CLM01 values may also be obtrained fromt the .999 file and
570 * we can use those to try and identify the batch file, but we may
571 * identify the wrong file. The .997 file gives the limited information
572 * for the particular claim, so this function is to get more information on the claim.
573 * array($st_num, $enc_pid, $enc_enctr, $enc_pt_lname, $enc_pt_fname, $enc_pt_dob, $batch_file);
575 * @see ibr_997_rejects() in ibr_997_read.php
576 * @todo accept an array of $st_clm_num so batch file needs only one read
577 * @uses csv_file_by_controlnum()
578 * @uses csv_verify_file()
579 * @param string $stnum the ST number of a claim
580 * @param string $batchnam the batch file contents which contain the st_num
581 * @param string $pidenctr the pid-encounter for the desired claim
582 * @return array
584 function ibr_batch_get_st_info ($st_num, $batchname='', $pidenctr='') {
585 //ibr_batch_get_st_info($ak2st02, $bt_file, $ctx302)
586 $stnum = ($st_num) ? strval($st_num) : '';
587 $pe = ($pidenctr) ? strval($pidenctr) : '';
588 $btfname = '';
590 if (!$stnum) {
591 // error - st number not supplied
592 csv_edihist_log("ibr_batch_get_st_info: ST number not supplied");
593 return FALSE;
595 if (strlen($stnum) == 13) {
596 // assume concatenation of isa13 and st02
597 $ctln = substr($stnum, 0, 9);
598 $stnum = substr($stnum, -4);
599 $btfname = csv_file_by_controlnum('batch', $ctln );
600 } elseif (strlen($stnum) < 4 ) {
601 $stnum = str_pad ($stnum, 4, "0", STR_PAD_LEFT);
604 if (strlen($batchname) == 9 && !$btfname) {
605 // assume isa13 control number
606 $btfname = csv_file_by_controlnum('batch', $batchname );
607 } else {
608 $btfname = $batchname;
611 $fp = ($btfname) ? csv_verify_file($btfname, 'batch') : '';
613 if ($fp) {
614 $bstr = file_get_contents($fp);
615 $batch_name = basename($fp);
616 } else {
617 // unable to get the file, quit here
618 csv_edihist_log("ibr_batch_get_st_info: could not read batch file");
619 return FALSE;
622 // $st_clm_num must be in format "000N" like "0004" or "0012"
623 // we are looking for, e.g. ST*837*0017~
624 $seg_st = 'ST'.SEG_ELEM_DELIM.'837'.SEG_ELEM_DELIM.$stnum; // particular ST block
626 $seg_se = SEG_TERM_DELIM."SE".SEG_ELEM_DELIM; // prepend the "~" to avoid SE* as substring in a segment
627 $seg_st2 = SEG_ELEM_DELIM.$st_num.SEG_TERM_DELIM; //second search string *0021* e.g. SE*47*0021*
629 // get segment positions
630 $st_pos = strpos($bstr, $seg_st, 0);
631 // break it off if $st_pos is not found
632 if ( $st_pos == FALSE ) {
633 //echo "ibr_find_claim_enctr $st_num $seg_st not found in file $batch_file" . PHP_EOL;
634 return FALSE;
637 $se_pos = strpos($bstr, $seg_se, $st_pos);
638 $se_pos2 = strpos($bstr, $seg_st2, $se_pos);
639 $se_pos3 = strpos($bstr, SEG_TERM_DELIM, $se_pos2);
641 $seg_block = substr($bstr, $st_pos, $se_pos3-$st_pos+1);
642 $segs_ar = explode(SEG_TERM_DELIM, $seg_block);
644 $has_sbr = FALSE;
645 $has_dep = FALSE;
646 $enc_pt_lname = ''; $enc_pt_fname = ''; $pid_enctr = ''; $svcdate = '';
648 foreach($segs_ar as $seg_str) {
649 // a 'trim($seg_str)' is needed if newlines are present
650 $seg = explode(SEG_ELEM_DELIM, $seg_str);
652 if ($seg[0] == "HL") {
653 if ($seg[3] == "22") { $has_sbr = TRUE; }
654 if ($seg[3] == "23") { $has_dep = TRUE; }
655 continue;
658 if ($seg[0] == "NM1" && ($seg[1] == "IL" || $seg[1] == "QC")) {
659 // name can be in first NM1*IL and blank in second
660 if ($seg[1] == "IL" && !$enc_pt_lname) {
661 $enc_pt_lname = $seg[3];
662 $enc_pt_fname = $seg[4];
664 if ($seg[1] == "QC" && $has_dep) {
665 if ($enc_pt_lname && strlen($seg[3])) {
666 $enc_pt_lname = $seg[3];
667 $enc_pt_fname = $seg[4];
668 } elseif(!$enc_pt_lname) {
669 $enc_pt_lname = $seg[3];
670 $enc_pt_fname = $seg[4];
674 continue;
677 if ($seg[0] == "CLM") {
678 $pid_enctr = $seg[1];
681 if ($seg[0] == 'DTP' && $seg[1] == '472') {
682 $svcdate = $seg[3];
683 // we are done, since DTP segment comes after SVC, near end
684 break;
688 return array($st_num, $pid_enctr, $enc_pt_lname, $enc_pt_fname, $batch_name, $svcdate);
693 * Parse a batch file for data for the csv record
695 * The array returned has a key 'file' which is for the files_batch.csv
696 * and a key 'claim' with a subarray for each claim
698 * @see csv_files_header() for values
699 * @uses csv_x12_segments()
700 * @param string $batch_file name of x12-837 batch file
701 * @return array
703 function ibr_batch_csv_data($batch_file_path) {
705 // read the file and transform it into an array of segment arrays
706 // then loop through the segment arrays and copy desired items
707 // to a data array for writing to the csv_file and csv_claims files
709 // get the segments from csv_record_include function csv_x12_segments
710 $ar_batch = csv_x12_segments($batch_file_path, 'batch', $seg_array = FALSE);
711 // debug
712 //var_dump(array_keys($ar_batch) ) .PHP_EOL;
714 if ( is_array($ar_batch) ) {
715 $batch_file = basename($ar_batch['path']);
716 $elem_d = $ar_batch['delimiters']['e'];
718 } else {
719 csv_edihist_log("ibr_batch_csv_data did not get segments for $batch_file");
720 return FALSE;
723 $ar_data = array();
724 $st_ct = -1;
725 $seg_ct = 0;
727 foreach ( $ar_batch['segments'] as $segtxt) {
728 // debug
729 //echo "$segtxt <br />" .PHP_EOL;
731 $seg = explode($elem_d, $segtxt);
732 // increment segment count for testing
733 $seg_ct++;
735 // these values will occur once per file
736 // $fname = $batch_file; // $ar_data[$st_ct][8] = $batch_file
737 if ($seg[0] == "ISA") {
738 $x12partner = $seg[6];
739 $f_mtime = '20'.$seg[9];
740 $x12version = $seg[12];
741 $ctrl_num = strval($seg[13]); // $ar_data[$st_ct][10] = $ctrl_num
743 continue;
745 if ($seg[0] == 'GS') {
746 $f_mtime = $seg[4];
747 continue;
749 // get GE segment for files data claim count
750 if ($seg[0] == "GE") {
751 $clm_ct = $seg[1];
752 continue;
754 // now we need to create a sub array for each ST block
755 if ($seg[0] == "ST" ) {
756 $st_ct++; // OpenEMR 837 places each claim in an ST block
757 $ln_st = $seg_ct;
758 $st_num = strval($seg[2]); // $ar_data[$st_ct][9] = $st_num;
759 continue;
761 if ($seg[0] == "BHT" ) {
762 $bht03 = $seg[3];
763 $ins_pos = '';
764 // since this is '0123' and not useful, we will construct it
765 // below with $ctrl_num.$st_num
766 continue;
768 if ($seg[0] == "HL") {
769 $hlevel = strval($seg[3]);
770 continue;
772 if ($seg[0] == "SBR") {
773 if (($hlevel == '22' || $hlevel == '23') && !$ins_pos) { $ins_pos = $seg[1]; }
774 //$ins_pos = $seg[1];
775 continue;
777 if ($seg[0] == "CAS") {
778 // on theory that CAS will only appear on secondary claims
779 if ($ins_pos == 'P') { $ins_pos = 'S'; }
781 if ($seg[0] == "NM1" && $seg[1] == "PR" ) {
782 if ($ins_pos == "P") {
783 $ins_primary = $seg[3]; // $ar_data[$st_ct][7] = $ins_primary;
785 if ($ins_pos == "S") {
786 $ins_secondary = $seg[3]; // $ar_data[$st_ct][8] = $ins_secondary;
788 continue;
790 if ( $seg[0] == "NM1" && strpos("|IL|QC", $seg[1]) ) {
791 // The NM1*QC segment is in the batch file if the patient is not
792 // the subscriber, and it comes after the NM1*IL segment,
793 // so get something in either case, but errors can leave blanks
794 $pt_lname = ($pt_lname) ? $pt_lname : $seg[3]; // $ar_data[$st_ct][0] = $pt_lname
795 $pt_fname = ($pt_fname) ? $pt_fname : $seg[4]; // $ar_data[$st_ct][1] = $pt_fname
796 continue;
798 if ( $seg[0] == "NM1" && $seg[1] == '82') {
799 $providerid = $seg[9];
800 continue;
802 if ($seg[0] == "CLM") {
803 $inv_split = preg_split('/\D/', $seg[1], 2, PREG_SPLIT_NO_EMPTY);
804 $pid = $inv_split[0]; // $ar_data[$st_ct][2] = $pid
805 $enctr = $inv_split[1]; // $ar_data[$st_ct][3] = $enctr
806 $clm01 = $seg[1];
807 $fee = $seg[2]; // $ar_data[$st_ct][5] = $fee
808 continue;
810 if ($seg[0] == "DTP" && $seg[1] == "472") {
811 $svc_date = $seg[3]; // $ar_data[$st_ct][4] = $svc_date
812 continue;
814 if ($seg[0] == "AMT" && $seg[1] == "F5") {
815 $pt_paid = $seg[2]; // $ar_data[$st_ct][6] = $pt_paid
816 continue;
818 if ($seg[0] == "SE" ) {
819 // end of ST block, get claim array and go back
820 // debug count lines
821 $ln_ct = $seg[1];
823 $ln_test = $seg_ct - $ln_st + 1;
824 if ($ln_test != $ln_ct) {
825 csv_edihist_log("ibr_batch_csv_data: ST segment count error $ln_test $ln_ct");
827 //['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
828 // now put ar_data in order
830 $ar_data['claim'][$st_ct][0] = $pt_lname . ", " . $pt_fname;
831 $ar_data['claim'][$st_ct][1] = $svc_date;
832 $ar_data['claim'][$st_ct][2] = $clm01;
833 $ar_data['claim'][$st_ct][3] = $ins_pos;
834 $ar_data['claim'][$st_ct][4] = $ctrl_num.$st_num;
835 $ar_data['claim'][$st_ct][5] = $batch_file;
836 $ar_data['claim'][$st_ct][6] = $fee;
837 $ar_data['claim'][$st_ct][7] = isset($pt_paid) ? $pt_paid : "0";
838 $ar_data['claim'][$st_ct][8] = $providerid;
840 // reset variables so there is no carryover
841 // do not reset $batch_file or $ctrl_num
842 $pt_lname = "";
843 $pt_fname = "";
844 $pid = "";
845 $enctr = "";
846 $svc_date = "";
847 $fee = "";
848 $pt_paid = "";
849 $ins_primary = "";
850 $ins_secondary = "";
851 $st_num = "";
852 $bht03 = "";
853 $ins_pos = "";
854 $providerid = "";
855 $clm01 = "";
857 continue;
860 } // end foreach( $ar_batch as $seg)
862 // put the file data array together
863 //$csv_hd_ar['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
864 $ar_data['file'][0] = $f_mtime;
865 $ar_data['file'][1] = $batch_file;
866 $ar_data['file'][2] = $ctrl_num;
867 $ar_data['file'][3] = $clm_ct;
868 $ar_data['file'][4] = $x12partner;
869 //$ar_data['file'][5] = $x12version;
871 return $ar_data;
876 * Process new batch files for csv data and html output
878 * The html output is only a file summary, no claim detail is created
881 * @uses csv_verify_file()
882 * @uses ibr_batch_csv_data()
883 * @uses csv_write_record()
884 * @uses csv_newfile_list()
885 * @param array $file_array optional default is NULL
886 * @param boolean $html_out whether to generate html files summary table
887 * @return string html output only for files table
889 function ibr_batch_process_new ($file_array=NULL, $html_out=TRUE) {
891 // 'mtime', 'dirname', 'fname', 'trace' 'payer' 'claims'
892 $html_str = "";
893 $chars1 = 0;
894 $chars2 = 0;
895 //$chars2 = 0;
896 $idx = 0;
897 $ar_batchf = array();
898 //$need_dir = TRUE;
899 // get the list of new files
900 if ( $file_array === NULL || !is_array($file_array) || count($file_array) == 0) {
901 $ar_newfiles = csv_newfile_list("batch");
902 } else {
903 $ar_newfiles = $file_array;
904 //$need_dir = FALSE;
907 if ( count($ar_newfiles) == 0 ) {
908 if($html_out) {
909 $html_str .= "<p>ibr_batch_process_new: no new batch files <br />";
910 return $html_str;
911 } else {
912 return false;
914 } else {
915 $btfcount = count($ar_newfiles);
917 // we have some new ones
918 // verify and get complete path
919 foreach($ar_newfiles as $fbt) {
920 $fp = csv_verify_file($fbt, 'batch', false);
921 if ($fp) { $ar_batchf[] = $fp; }
924 $p = csv_parameters("batch");
925 $b_dir = $p['directory'];
927 if($html_out) {
928 $html_str .= "<table cols=5 class=\"batch\">
929 <caption>Batch Files CSV</caption>
930 <thead>
931 <tr>
932 <th>File Time</th><th>File Name</th>
933 <th>Control</th><th>Claims</th><th>x12_Partner</th>
934 </tr>
935 </thead>
936 <tbody>";
939 foreach ($ar_batchf as $f_batch) {
941 // increment counter for alternating html backgrounds
942 $bgc = ($idx % 2 == 1 ) ? 'odd' : 'even';
943 $idx++;
945 //$full_path = ($need_dir) ? $b_dir.DIRECTORY_SEPARATOR.$f_batch : $f_batch;
946 // get the file data for csv output
947 //$ar_csv_data = ibr_batch_csv_data($full_path);
948 $ar_csv_data = ibr_batch_csv_data($f_batch);
950 // write to the files_batch csv record
951 $chars1 += csv_write_record($ar_csv_data['file'], 'batch', 'file' );
952 $chars2 += csv_write_record($ar_csv_data['claim'], 'batch', 'claim');
954 // link for viewing file <a href=\"edi_view_file.php?\'fvkey\'=$dta\" target=\"_blank\">$dta</a></td>";
955 if($html_out) {
956 $html_str .= "<tr class=\"$bgc\">
957 <td>{$ar_csv_data['file'][0]}</td>
958 <td><a target='_blank' href='edi_history_main.php?fvkey={$ar_csv_data['file'][1]}'>{$ar_csv_data['file'][1]}</a></td>
959 <td>{$ar_csv_data['file'][2]}</td>
960 <td>{$ar_csv_data['file'][3]}</td>
961 <td>{$ar_csv_data['file'][4]}</td>
962 </tr>";
965 if($html_out) {
966 $html_str .= "</tbody>
967 </table>
968 <p></p>";
971 csv_edihist_log("ibr_batch_process_new: $chars1 characters written to files_batch.csv");
972 csv_edihist_log("ibr_batch_process_new: $chars2 characters written to claims_batch.csv");
974 if($html_out) {
975 return $html_str;
976 } else {
977 return "<p>Batch files: processed $btfcount files </p>";