edihistory -- claim detail table heading
[openemr.git] / library / edihistory / ibr_era_read.php
blob79f6810af0964d532eacbd2e0b3a94f965aef11a
1 <?php
3 /**
4 * ibr_era_read.php
5 *
6 * Copyright 2012 Kevin McCormick
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; version 3 or later. You should have
16 * received a copy of the GNU General Public License along with this program;
17 * if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * <http://opensource.org/licenses/gpl-license.php>
23 * @author Kevin McCormick
24 * @link: http://www.open-emr.org
25 * @package OpenEMR
26 * @subpackage ediHistory
29 ///**
30 // * a security measure to prevent direct web access to this file
31 // */
32 //// if (!defined('SITE_IN')) die('Direct access not allowed!');
34 /**
35 * ibr_era_code_text ( $code_type, $code_str )
37 * retrieve Qualifier code text from the included file ibr_code_arrays.php
39 * @uses code_arrays()
40 * @param string $code_type one of "|CLMADJ|CLMSTAT|AMT|PER|REF|RA|PLB|"
41 * @param string $code_str a space delimited string of codes e.g. MA15 MA107 MA15
42 * @return array $c_ar[i] = array("Code", "Code text", "", "", "")
44 function ibr_era_code_text ( $code_type, $code_str ) {
45 // $code_str, from ibr_era_claim_vals, is like "45 47 125 " or " MA01 MA27 MA18 "
46 $cd = trim($code_str);
47 $cd_ar = explode(" ", $cd);
49 // since the codes are just appended onto the $code_str
50 // eliminate duplicates
51 $uniq_ar = array_unique($cd_ar);
53 $code_class = new code_arrays();
55 foreach ($uniq_ar as $val ) {
57 switch ($code_type) {
58 case ("CLMADJ"):
59 $c_ar[] = $code_class->get_CODE_CLAIM_ADJUSTMENT($val);
60 continue;
62 case ("RA"):
63 $c_ar[] = $code_class->get_CODE_RA_REMARK($val);
64 continue;
66 case ("PLB"):
67 $c_ar[] = $code_class->get_CODE_PLB_REASON($val);
68 continue;
70 case ("CLMSTAT"):
71 $c_ar[] = $code_class->get_CODE_CLAIM_STATUS($val);
72 continue;
74 case ("PER"):
75 $c_ar[] = $code_class->get_CODE_PER($val);
76 continue;
78 case ("REF"):
79 $c_ar[] = $code_class->get_CODE_REF($val);
80 continue;
82 case ("AMT"):
83 $c_ar[] = $code_class->get_CODE_AMT($val);
84 continue;
86 case ("CAS"):
87 $c_ar[] = $code_class->get_CODE_CAS_GROUP($val);
88 continue;
90 case ("LOC"):
91 $c_ar[] = $code_class->get_CODE_LOCATION($val);
92 continue;
94 default:
95 $c_ar[] = array($val, "Unknown code", "", "", "");
98 return $c_ar;
102 * insert dashes in ten-digit telephone numbers
104 * @param string $str_val the telephone number
105 * @return string the telephone number with dashes
107 function ibr_era_format_telephone ($str_val) {
108 $tel = substr($str_val,0,3) . "-" . substr($str_val,3,3) . "-" . substr($str_val,6);
109 return $tel;
113 * order MM DD YYYY values and insert slashes in eight-digit dates
115 * US MM/DD/YYYY or general YYYY/MM/DD
117 * @param string $str_val the eight-digit date
118 * @param string $pref if 'US' (default) anything else means YYYY/MM/DD
119 * @return string the date with slashes
121 function ibr_era_format_date ($str_val, $pref = "US") {
122 if ($pref == "US") {
123 $dt = substr($str_val,4,2) . "/" . substr($str_val,6) . "/" . substr($str_val,0,4);
124 } else {
125 $dt = substr($str_val,0,4) . "/" . substr($str_val,4,2) . "/" . substr($str_val,6);
127 return $dt;
131 * format monetary amounts with two digits after the decimal place
133 * @todo add other formats
134 * @param string $str_val the amount string
135 * @param string $pref 'US' is default, no other formats available
136 * @return string the telephone number with dashes
138 function ibr_era_format_money ($str_val, $pref = "US") {
139 if (is_numeric($str_val)) {
140 $mny = sprintf("%01.2f", $str_val);
141 } else {
142 $mny = $str_val;
144 return $mny;
149 * The transactional slice of the x12 835 segments array is parsed into a multi-dimensional array
151 * Each transactional section, the ST...SE segments is parsed into an array with the following structure
152 * This array key listing may not be totally current.
153 * <pre>
154 * $ar_clm['BPR']['key']
155 * ['total_pmt']['credit']['method']['payer_id']['date_pmt']['trace']['payer_name']
156 * ['payer_source']['prod_date']['payer_id_num']['payer_id_name']['payer_id_addr2']
157 * ['payer_id_adr3']['payer_contact']['payer_tech']['payer_tech_contact']
158 * ['rdm_trans_code']['rdm_name']['rdm_comm_num']
160 * $ar_clm['LX'][$lx_ct]['TS3']['key']
161 * ['ref_id']['facility_code']['fiscal_per']['claim_ct']
162 * ['chg_tot']['chg_cvd']['chg_noncvd']['chg_denied']
163 * ['amt_prov']['amt_int']['amt_adj']['amt_grr']['amt_msp']
164 * ['chg_bld']['chg_nonlab']['chg_coins']
165 * ['chg_hcpcs_rpt']['chg_hcpcs_pbl']['amt_dedctbl']['amt_prof']
166 * ['amt_msp_pt']['amt_reimb_pt']['pip_ct']['pip_amt']
168 * $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['key']
169 * ['pid']['enctr']['clm_status']['fee']['pmt']['ptresp']['clm_id']['moa']['pt_last']
170 * ['pt_first']['pt_ins_id']['provider_id']['crossover_name']['pr_priority']
171 * ['pr_priority_id']['sbr_last']['sbr_first']['sbr_ins_id']['ins_expired']
172 * ['clm_recieved']['svc_date_per_begin']['svc_date_per_end']['clm_oth_id_descr']['clm_oth_id_num']
173 * ['corr_last']['corr_first']['corr_mi']['corr_ins_id']
174 * ['ref_description']['ref_value']['clm_pr_ver']['clm_pr_ver_num']
175 * ['clm_adj_type']['clm_adj_code']['clm_adj_amt']['clm_adj_total']['clm_adj_qty']
176 * ['clm_amt_code']['clm_amt_amt']['clm_adj_html']
178 * $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['key']
179 * ['svc_enctr']['svc_adj_id']['svc_fee']['svc_pmt'] --['svc_nubc']['svc_units']
180 * ['svc_code']['svc_qty']['svc_adj_type']['svc_adj_code']['svc_adj_amt']['svc_adj_total']
181 * ['svc_adj_qty']['svc_adj_html']['svc_pt_resp']['svc_id_descr']['svc_id_num']
182 * ['prov_id_descr']['prov_id_num']['ref_description']['ref_value']['svc_amt_code']
183 * ['svc_amt_amt']
185 * $ar_clm['PLB'][$plb_ct]['key']
186 * ['provider']['per_date']["identifier_$i"]["amount_$i"]["text_$i"]
187 * </pre>
189 * @todo verify index increment logic for LX-TS3-CLP segments -- works for what I have sen
190 * @param array $ar_st_slice array of era file segments ST...SE or single claim CLP...CLP
191 * @param string $elem_delim element delimiter, usually *, for the segments
192 * @param string $comp_delim delimiter for composite elements, usually :
193 * @return array a multidimentional array of values as described above
195 function ibr_era_claim_vals ( $ar_st_slice, $elem_delim, $comp_delim ) {
198 $ar_clm = array();
199 $svc_ct = -1;
200 $clp_ct = -1;
201 $lx_ct= -1;
202 $lq_ct = 0;
203 $plb_ct = 0;
205 $loop = "0";
207 $seg_ct = 0;
210 // segments in the ST -- SE block BPR|TRN|N1|N3|PER| LX|TS3|CLP|NM1|MIA|MOA|DTM|SVC|CAS|REF|LQ|AMT
211 foreach ( $ar_st_slice as $segtext ) {
213 $seg = explode($elem_delim, $segtext);
215 // do loop and increment resets here
216 if ($seg[0] == "BPR" ) {
217 $loop = "0";
219 if ($seg[0] == "CLP" ) {
220 // if there are no preceeding LX segments, pretend we are in the first one
221 if ($lx_ct == -1) { $lx_ct = 0; }
222 // scv_ct is incremented up on each SVC at top of foreach
223 // assume all related services and adjustments are in following loop 2110
224 $svc_ct = -1;
225 //$adj_tot = 0;
226 $loop = "2100";
227 $clp_ct++;
228 // set claim adjustment variables so that concatenations will work
229 // This is for CAS segment in loop 2100
230 $adj_type2100 = "";
231 $adj_code2100 = "";
232 $adj_amt2100 = "";
233 $adj_total2100 = "";
234 $adj_qty2100 = "";
235 $adj_html2100 = "";
237 $amt_c = "";
238 $amt_a = "";
240 $lq_type = "";
241 $lq_code = "";
243 $refstr = "";
246 if ($seg[0] == "SVC" ) {
247 $svc_ct++;
248 $loop = "2110";
249 //$adj_tot = 0;
250 // for the related CAS segment
251 // service level loop 2110
252 $adj_type2110 = "";
253 $adj_code2110 = "";
254 $adj_amt2110 = "";
255 $adj_total2110 = "";
256 $adj_qty2110 = "";
257 $adj_html2110 = "";
258 $lq_type = "";
259 $lq_code = "";
261 $amt_c = "";
262 $amt_a = "";
264 $refstr = "";
266 if ($seg[0] == "LX" ) {
267 // problem here is LX but no TS3 just clutters HTML table
268 // LX groups claims, but appears useful only when TS3 is next
269 // move $lx_ct++ increment to TS3 segment part;
270 // $lx_ct++;
271 $loop = "2000";
273 if ($seg[0] == "N1" && $seg[1] == "PR") { $loop = "1000A"; }
274 if ($seg[0] == "N1" && $seg[1] == "PE") { $loop = "1000B"; }
276 // now evaluate segments and construct array
278 // BPR segment
279 if ($seg[0] == "BPR" ) {
280 $ar_clm['BPR']['total_pmt'] = sprintf("%01.2f", $seg[2]);
281 $ar_clm['BPR']['credit'] = $seg[3];
282 $ar_clm['BPR']['method'] = $seg[4];
283 $ar_clm['BPR']['payer_id'] = $seg[10];
284 $ar_clm['BPR']['date_pmt'] = ibr_era_format_date($seg[16]);
285 // ['total_pmt']['credit']['method'] ['payer_id']['date_pmt']
286 // ['BPR02'] ['BPR03'] ['BPR04'] ['BPR10'] ['BPR16']
287 continue;
289 // TRN segment
290 if($seg[0] == "TRN" ) {
291 $ar_clm['BPR']['trace'] = $seg[2];
292 //['trace']
293 //['TRN02']
294 continue;
296 // N1 segment
297 if($seg[0] == "N1" && $seg[1] == "PR") {
298 $ar_clm['BPR']['payer_name'] = $seg[2];
299 if ( array_key_exists(4, $seg) ) { $ar_clm['BPR']['payer_id'] = $seg[4];}
301 continue;
303 // N3 segment
304 if($seg[0] == "N3" && $loop == "1000A") {
305 //['payer_id_addr2']['payer_id_adr3']['payer_contact']
306 $ar_clm['BPR']['payer_id_addr2'] = $seg[1];
307 if ( array_key_exists(2, $seg) ) { $ar_clm['BPR']['payer_id_addr2'] .= " {$seg[2]}"; }
309 continue;
311 // N4 segment
312 if ($seg[0] == "N4" && $loop == "1000A") {
313 $ar_clm['BPR']['payer_id_addr3'] = $seg[1];
314 if ( array_key_exists(2, $seg) ) { $ar_clm['BPR']['payer_id_addr3'] .= " {$seg[2]}"; }
315 if ( array_key_exists(3, $seg) ) { $ar_clm['BPR']['payer_id_addr3'] .= " {$seg[3]}"; }
317 continue;
319 // RDM segment 5010
320 if ( $seg[0] == "RDM" ) {
321 $rdm_m = array("BM"=>"By Mail", "EM"=>"E-Mail",
322 "FT"=> "File Transfer", "OL"=>"On-Line");
324 $ar_clm['BPR']['rdm_trans_code'] = isset($rdm_m[$seg[1]]) ? $rdm_m[$seg[1]] : $seg[1];
325 $ar_clm['BPR']['rdm_name'] = $seg[2];
326 $ar_clm['BPR']['rdm_comm_num'] = $seg[3];
328 continue;
331 // TS3 segment
332 if ( $seg[0] == "TS3" ) {
333 // TS3 Provider Summary Information
334 // varying length of this segment per payer
335 // indicated by preceeding LX segment when used
336 // increment $lx_ct so TS3 and related claims are in the same subarray
337 // see also ibr_era_claim_EOB_heading_html for constructing html table header
339 if ($loop == "2000" ) { $lx_ct++; } // a preceeding LX
341 $ts3_ky = array('ts3', 'ref_id', 'facility_code', 'fiscal_per', 'claim_ct',
342 'chg_tot', 'chg_cvd', 'chg_noncvd', 'chg_denied',
343 'amt_prov', 'amt_int', 'amt_adj', 'amt_grr',
344 'amt_msp', 'chg_bld', 'chg_nonlab', 'chg_coins',
345 'chg_hcpcs_rpt','chg_hcpcs_pbl', 'amt_dedctbl',
346 'amt_prof', 'amt_msp_pt', 'amt_reimb_pt',
347 'pip_ct', 'pip_amt');
349 //debug
350 //var_dump($seg);
352 $ct = count($seg);
353 for ($i = 1; $i < $ct; $i++) {
354 //echo "$i {$seg[$i]} " . PHP_EOL;
355 $ar_clm['LX'][$lx_ct]['TS3']["{$ts3_ky[$i]}"] = $seg[$i];
359 continue;
363 // CLP segment ibr_code_arrays -> get_CLAIM_STATUS($code)
364 // 1,2,3,4,5,10, 13, 15, 16, 17, 19, 20, 21, 22, 23, 25, 27
365 if ($seg[0] == "CLP" ) {
366 // make sure $lx_ct is at least 0
367 if ($lx_ct == -1) { $lx_ct++; }
369 $status_ar = array("1"=>"1 Primary", "2"=>"2 Secondary",
370 "3"=>"3 Tertiary", "4"=>"4 Denied",
371 "19"=>"19 Primary/FWD",
372 "20"=>"20 Secondary/FWD",
373 "21"=>"21 Tertiary/FWD",
374 "22"=>"22 Reversal/Refund",
375 "23"=>"23 Not our claim/FWD",
376 "24"=>"24 Predetermination only"
379 // since patient ID and encounter are in form id-enctr
381 $inv_split = csv_pid_enctr_parse($seg[1]);
382 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pid'] = $inv_split['pid']; // substr ( $seg[1], 0, strpos($seg[1], "-") );
383 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['enctr'] = $inv_split['enctr']; // substr ( $seg[1], strpos($seg[1], "-") + 1 );
385 if (array_key_exists($seg[2], $status_ar)){
386 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_status'] = $status_ar[$seg[2]];
387 } else {
388 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_status'] = $seg[2];
391 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['fee'] = sprintf("%01.2f", $seg[3]);
392 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pmt'] = sprintf("%01.2f", $seg[4]);
393 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['ptresp'] = sprintf("%01.2f", $seg[5]);
394 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_id'] = $seg[7];
396 $svc_enctr = $inv_split['enctr']; // use enctr to match service with CLP
398 continue;
401 //MOA segment --I don't think we get the MIA segment for Dr's office
402 if ($seg[0] == "MOA" ) {
403 // Outpatient adjudication information
404 // can't tell how many fields will be given
405 // create a space delimited string for each
406 // and get the text in the html function
407 $moa_str = "";
408 $moa_pct = "";
409 for ($i=1; $i < count($seg); $i++ ) {
410 if ($i == 1 && strlen($seg[$i])) $moa_pct = "Reimb Rate " . sprintf("% 1.0f%%", $seg[$i]);
411 if ($i == 2 && strlen($seg[$i])) $moa_pct .= " Claim HCPCS Payable Amtt " . sprintf("%01.2f", $seg[$i]);
412 if ( $i > 2 && $i < 8 && strlen($seg[$i]) ) { $moa_str .= $seg[$i] . " "; }
413 if ($i == 8 && strlen($seg[$i])) $moa_pct .= " Claim ESRD Pmtt Amt " . sprintf("%01.2f", $seg[$i]);
414 if ($i == 9 && strlen($seg[$i])) $moa_pct .= " Nonpayable Prof Comp Amt " . sprintf("%01.2f", $seg[$i]);
416 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['moa'] = $moa_str;
417 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['moa_amt'] = $moa_pct;
418 continue;
420 // NM1 segment
421 if ($seg[0] == "NM1" ) {
422 // multiple NM1 segments, check qualifier
423 // loop 2100
425 if ($seg[1] == "QC" ) {
426 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pt_last'] = $seg[3];
427 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pt_first'] = $seg[4];
428 if ( isset($seg[8]) && isset($seg[9]) ) {
429 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pt_ins_id'] = $seg[8] . " " . $seg[9];
431 continue;
433 if ($seg[1] == "IL" ) {
434 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['sbr_last'] = $seg[3];
435 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['sbr_first'] = $seg[4];
436 if ( isset($seg[8]) && isset($seg[9]) ) {
437 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['sbr_ins_id'] = $seg[8] . " " . $seg[9];
439 continue;
441 if ($seg[1] == "74" && count($seg) > 3 ) {
442 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['corr_last'] = $seg[3];
443 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['corr_first'] = $seg[4];
444 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['corr_mi'] = $seg[5];
445 if ( isset($seg[8]) && isset($seg[9]) ) {
446 // expect $seg[8] to be 'C' insured's corrected ID
447 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['corr_ins_id'] = ($seg[8] == 'C') ? $seg[9] : $seg[8] ." ". $seg[9];
449 continue;
452 if ($seg[1] == "82" ) {
453 if ( isset($seg[9]) ) {
454 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['provider_id'] = $seg[9];
456 continue;
458 if ($seg[1] == "TT" ) {
459 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['crossover_name'] = $seg[3];
460 continue;
463 if ($seg[1] == "PR" && $loop == "2100") {
464 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pr_priority'] = $seg[1] . ": " . $seg[3];
465 if ( isset($seg[8]) && isset($seg[9]) ) {
466 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pr_priority_id'] = $seg[8] . " " . $seg[9];
468 continue;
470 // this continue should only apply if there was no match
471 continue;
473 // DTM segment
474 // assume qualifier in seg[1] and calendar date in seg[2]
475 if ($seg[0] == "DTM" ) {
476 // dates -- format
477 $fmt_dt = ibr_era_format_date($seg[2]);
479 if ( $loop == "2100" ) {
480 if ($seg[1] == "050" ) {
481 //if ($ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_recieved']) {
482 if ( array_key_exists('clm_recieved', $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]) ) {
483 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['pmt_date'] = $fmt_dt; //probably no such thing
484 } else {
485 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_recieved'] = $fmt_dt;
488 if ($seg[1] == "036" ) { $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['ins_expired'] = $fmt_dt; }
489 if ($seg[1] == "232" ) { $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['svc_date_per_begin'] = $fmt_dt; }
490 if ($seg[1] == "233" ) { $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['svc_date_per_end'] = $fmt_dt; }
492 continue;
494 if ( $loop == "2110" ) {
495 // SVC dates 150,151,472
496 if ($seg[1] == "150" ) { $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_date_begin'] = $fmt_dt; }
497 if ($seg[1] == "151" ) { $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_date_end'] = $fmt_dt; }
498 // service date
499 if ($seg[1] == "472" ) { $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_date'] = $fmt_dt; }
501 continue;
503 // 835 production date
504 if ( $loop == "0" && $seg[1] == "405") {
505 $ar_clm['BPR']['prod_date'] = $fmt_dt;
507 continue;
509 // in case of no match, just skip it
510 continue;
512 // SVC segment
513 if ($seg[0] == "SVC" ) {
514 // account for one or more SVC items
515 // HC, N4, NU, ZZ, HP
517 * The value in SVC03 should equal the value in SVC02
518 * minus all monetary amounts in the
519 * subsequent CAS segments of this loop.
521 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_enctr'] = $svc_enctr;
522 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_id'] = $seg[1];
523 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_fee'] = sprintf("%01.2f", $seg[2]);
524 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_pmt'] = sprintf("%01.2f", $seg[3]);
526 if (array_key_exists(4, $seg)) {
527 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_nubc'] = $seg[4];
529 if (array_key_exists(5, $seg)) {
530 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_units'] = $seg[5];
532 if (array_key_exists(6, $seg) && strlen($seg[6])) {
533 $svc_rev_str = '';
534 if (strpos($seg[6], $comp_delim)) {
535 $svc_rev_codes = array('AD'=>'Am Dental Assoc', 'ER'=>'Jur Specific', 'HC'=>'HCPCS',
536 'HP'=>'HIPPS', 'IV'=>'HIEC', 'N4'=>'NDC542', 'WK'=>'ABC Code');
537 $svc_rev = explode($comp_delim, $seg[6]);
538 if (isset($svc_rev[1])) { $svc_rev_str .= $svc_rev[1]; }
539 if (isset($svc_rev[2])) { $svc_rev_str .= ':'.$svc_rev[2]; }
540 if (isset($svc_rev[3])) { $svc_rev_str .= ':'.$svc_rev[3]; }
541 if (isset($svc_rev[4])) { $svc_rev_str .= ':'.$svc_rev[4]; }
542 if (isset($svc_rev[5])) { $svc_rev_str .= ':'.$svc_rev[5]; }
543 if (isset($svc_rev[6])) { $svc_rev_str .= ':'.$svc_rev[6]; }
545 if (isset($svc_rev[0]) && array_key_exists($svc_rev[0], $svc_rev_codes)) {
546 $svc_rev_str .= $svc_rev_codes[$svc_rev[0]];
548 if (isset($svc_rev[6])) { $svc_rev_str .= ' '.$svc_rev[6]; }
550 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_code'] = $svc_rev_str;
551 } else {
552 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_code'] = $seg[6];
555 if (array_key_exists(7, $seg)) {
556 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_qty'] = $seg[7];
559 continue;
562 // REF segment
563 // ibr_code_arrays -> get_REF_1000($code) get_REF_2100($code) get_REF_2200($code)
564 if ($seg[0] == "REF" ) {
565 // issue of more than one REF per CLP|SVC // EA, 28, 6P,
566 // REF segments are like herding cats
568 $ref_2100 = array('1S'=>'APG', '6R'=>'ProvCtln', 'E9'=>'AttchCd', 'G1'=>'PriorAuth',
569 'G3'=>'PredetBenID', 'LU'=>'Loc', 'RB'=>'RateCd', '1L'=>'Grp/Pol',
570 '1W'=>'MbrID', '9A'=>'ReprRef', '9C'=>'AdjReprRef', 'A6'=>'EIN',
571 'APC'=>'APC', 'BB'=>'Auth', 'CE'=>'Contrct', 'EA'=>'Med RecID',
572 'F8'=>'Orig Ref', 'IG'=>'InsPol', 'SY'=>'SSN', '1A'=>'BC Prv',
573 '1B'=>'BS Prv', '1C'=>'MCR Prv', '1D'=>'MCD Prv', '1G'=>'UPIN',
574 '1H'=>'CHAMPUS ID', 'D3'=>'NABP', 'G2'=>'Prv Com', '1J'=>'Fclty',
575 'HPI'=>'HCFA', 'TJ'=>'TIN', 'EV'=>'RecID' );
577 if ($loop == "2100") {
578 // claim level REF
579 if (array_key_exists($seg[1], $ref_2100)) {
580 $refstr .= ' ' . $ref_2100[$seg[1]];
581 $refstr .= ': ' . $seg[2];
582 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_oth_id_num'] = $refstr;
583 } else {
584 $refstr .= ' ' . $seg[1] . ' ' .$seg[2];
585 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['ref_value'] = $refstr;
588 /* **** old stuff
589 if ( strpos("|1W|9A|9C|A6|BB|CE|EA|F8|G1|G3|IG|SY|28|6P", $seg[1] )) {
590 if (isset($ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_oth_id_descr'])) {
591 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_oth_id_descr'] .= " " . $seg[1];
592 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_oth_id_num'] .= " " . $seg[2];
593 } else {
594 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_oth_id_descr'] = $seg[1];
595 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_oth_id_num'] = $seg[2];
597 continue;
598 } else {
600 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['ref_description'] = $seg[1];
601 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['ref_value'] = $seg[2];
602 continue;
604 * **** end old stuff *****/
607 if ($loop == "2110") {
608 // service level REF
609 if ( $seg[1] == "6R" ) {
610 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_pcn'] = $seg[1];
611 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_pcn_num'] = $seg[2];
612 } elseif ( $seg[1] == "LU" ) {
613 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_loc'] = $seg[1];
614 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_loc_code'] = $seg[2];
615 } elseif (strpos("|1S|APC|BB|E9|G1|G3|RB|0K", $seg[1] )) {
616 if (isset($ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_id_descr'])) {
617 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_id_descr'] .= " " . $seg[1];
618 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_id_num'] .= " " . $seg[2];
619 } else {
620 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_id_descr'] = $seg[1];
621 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_id_num'] = $seg[2];
623 continue;
625 if ( strpos("|1A|1B|1C|1D|1G|1H|D3|G2|HPI|TJ", $seg[1] )) {
626 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['prov_id_descr'] = $seg[1];
627 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['prov_id_num'] = $seg[2];
628 continue;
632 if ($loop == "0") {
633 if ($seg[1] == "F2") {
634 //$ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_pr_ver'] = $seg[1];
635 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_pr_ver'] = 'Ins Adj Ver:';
636 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_pr_ver_num'] = $seg[2];
637 continue;
640 //if ($loop == "1000A") {
642 // $ar_clm['BPR']['ref_description'] = $seg[1];
643 // $ar_clm['BPR']['ref_value'] = $seg[2];
644 // }
645 // debug
646 //echo "REF" . PHP_EOL;
647 //var_dump($seg);
649 continue;
652 // PER segment
653 if ($seg[0] == "PER" ) {
654 if ($loop == "1000A") {
655 if ($seg[1] == "CX") {
656 $ar_clm['BPR']['payer_source'] = $seg[2];
658 if ($seg[3] == "TE") {
659 //$ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['payer_contact'] = substr($seg[4],0,3) . "-" . substr($seg[4],3,3) . "-" . substr($seg[4],6);
660 $ar_clm['BPR']['payer_contact'] = ibr_era_format_telephone($seg[4]);
661 } else {
662 $ar_clm['BPR']['payer_contact'] = $seg[4];
665 if ($seg[1] == "BL") {
666 $pr_tech = '';
667 for ($i=3; $i<count($seg); $i=$i+2) {
668 if ($seg[$i] == "TE" || $seg[$i] == "FX") {
669 $pr_tech .= $seg[$i].' '.ibr_era_format_telephone($seg[$i+1]).' ';
670 } else {
671 $pr_tech .= $seg[$i].' '.$seg[$i+1].' ';
674 $ar_clm['BPR']['payer_tech'] = $seg[2];
675 $ar_clm['BPR']['payer_tech_contact'] = $pr_tech;
678 if ($loop == "2100") {
679 if ($seg[1] == "CX") {
680 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['payer_source'] = $seg[2];
682 if ($seg[3] == "TE") {
683 //$ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['payer_contact'] = substr($seg[4],0,3) . "-" . substr($seg[4],3,3) . "-" . substr($seg[4],6);
684 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['payer_contact'] = ibr_era_format_telephone($seg[4]);
685 } else {
686 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['payer_contact'] = "{$seg[3]} {$seg[4]}";
690 continue;
693 // CAS segment
694 if ($seg[0] == "CAS" ) {
695 // can be in loop 2100 and loop 2110
696 // CAS segment can have multiple adjustments
697 // there is one CAS per adjustment type, but repeated if there are over 6 adjustments of that type
698 // the variables are set to empty strings on loop change, but appended to on segment repetition,
699 // e.g. 2 CAS in loop 2100 -> $adj_type2100 is "CO OA "
700 // for accounting we need CAS['type']['index'] =>['code'] ['amount'] ['quantity']
701 // set adjustment total to 0
702 $adj_tot = 0;
704 $era_cas = array("CO" => "CO Contractual Obligations",
705 "CR" => "CR Corrections and Reversals",
706 "OA" => "OA Other Adjustments",
707 "PI" => "PI Payor Initiated Reductions",
708 "PR" => "PR Patient Responsibility"
711 if ($loop == "2100" ) {
712 // claim level adj_type2100 adj_code2100 adj_amt2100 adj_total2100 adj_qty2100
713 $adj_type2100 .= $seg[1] . " ";
714 $adj_html2100 .= $seg[1] . " ";
715 // multiple adjustments can be listed in the same CAS
716 // collect the information in variables, then assign to array keys
717 $cas_ct = count($seg);
718 for ( $i=2; $i<$cas_ct; $i=$i+3 ) {
719 $adj_code2100 .= $seg[$i] . " ";
720 $adj_amt2100 .= sprintf("%01.2f", $seg[$i+1]) . " ";
721 $adj_total2100 += $seg[$i+1];
722 if ( array_key_exists( $i+2, $seg ) ) { $adj_qty2100 .= $seg[$i+2] . " "; }
724 $adj_html2100 .= $seg[$i];
725 $adj_html2100 .= ": ". sprintf("%01.2f", $seg[$i+1]);
726 $adj_html2100 .= (array_key_exists($i+2, $seg) && strlen(trim($seg[$i+2])) ) ? " x{$seg[$i+2]} | " : " | ";
729 // This is where the accounting can get tricky
730 // for me, the example of this is collecting too high a copay/deductible and having the
731 // insurance company pay less than allowed, with balance paid to patient
732 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_adj_type'] = $adj_type2100;
733 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_adj_code'] = $adj_code2100;
734 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_adj_amt'] = $adj_amt2100;
735 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_adj_total'] = sprintf("%01.2f", $adj_total2100);
736 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_adj_qty'] = $adj_qty2100;
737 // for html loop 2100
738 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_adj_html'] = $adj_html2100 ."(".sprintf("%01.2f", $adj_total2100).")";
739 // $adj_qty2100 may need isset($adj_qty2100) ? $adj_qty2100 : "";
740 // and then unset($adj_qty2100);
742 if ($loop == "2110" ) {
743 // service level adj_type2110 adj_code2110 adj_amt2110 adj_total2110 adj_qty2110
744 // use $svc_ct here since this segment applies to a particular service
746 $adj_type2110 .= $seg[1] . " ";
747 $adj_html2110 .= $seg[1] . " ";
748 // claim adjustment codes are at CAS02, CAS05, CAS08, etc.
749 $cas_ct = count($seg);
750 for ( $i=2; $i<$cas_ct; $i=$i+3 ) {
751 $adj_code2110 .= $seg[$i] . " ";
752 $adj_amt2110 .= sprintf("%01.2f", $seg[$i+1]) . " ";
753 $adj_total2110 += $seg[$i+1];
754 // quantity may be present
755 if ( array_key_exists( $i+2, $seg ) ) { $adj_qty2110 .= $seg[$i+2] . " "; }
756 // formatted for html output
757 $adj_html2110 .= $seg[$i];
758 $adj_html2110 .= ": ". sprintf("%01.2f", $seg[$i+1]);
759 $adj_html2110 .= (array_key_exists($i+2, $seg) && strlen(trim($seg[$i+2])) ) ? " x{$seg[$i+2]} | " : " | ";
761 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_type'] = $adj_type2110;
762 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_code'] = $adj_code2110;
763 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_amt'] = $adj_amt2110;
764 //$adj_tot += $seg[$i+1];
765 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_total'] = sprintf("%01.2f", $adj_total2110);
766 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_qty'] = $adj_qty2110;
767 // for html
768 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_adj_html'] = $adj_html2110 ."(".sprintf("%01.2f", $adj_total2110).")";
770 if ($seg[1] == "PR") {
771 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_pt_resp'] = sprintf("%01.2f", $seg[3]);
775 continue;
778 // LQ segment
779 if ($seg[0] == "LQ" ) {
780 // for concatenation, if LQ segment is repeated in loop 2110
781 $lq_type .= $seg[1] . " ";
782 $lq_code .= $seg[2] . " ";
784 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['rem_type'] = $lq_type;
785 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['rem_code'] = $lq_code;
786 continue;
789 // AMT segment
790 if ($seg[0] == "AMT" ) {
791 // ibr_code_arrays -> get_AMT_CODE($code, $loop)
792 $amt_qual = array("B6"=>"Allowed",
793 "AU"=>"Coverage Amt",
794 "D8"=>"Discount Amt",
795 "F5"=>"Patient Paid",
796 "I"=>"Interest",
797 "NL"=>"Neg Ledger Bal",
798 "T2"=>"Tot Bef Taxes",
799 "DY"=>"Per Day Lim",
800 "KH"=>"DeductedAmt/LateFiling",
801 "NE"=>"Net Billed",
802 "T"=>"Tax",
803 "ZK"=>"MCR/MCD Cat1",
804 "ZL"=>"MCR/MCD Cat2",
805 "ZM"=>"MCR/MCD Cat3",
806 "ZN"=>"MCR/MCD Cat4",
807 "ZO"=>"MCR/MCD Cat5"
810 // use $svc_ct here as well
811 // Qualifiers: Loop 2100 AU|F5|I|NL|ZK|ZL|ZM|ZN|ZO
812 // Loop 2110 B6,KH
814 // since more than one AMT is allowed, accumulate and concatenate
815 // no -- text takes too much space
816 //if (array_key_exists($seg[1], $amt_qual )) {
817 // $amt_a .= $amt_qual[$seg[1]] . ': ';
818 //} else {
819 // $amt_a .= $seg[1] . ': ';
821 $amt_a .= $seg[1] . ': ';
822 // verify numeric quantity for seg[2]
823 $amt_a .= is_numeric($seg[2]) ? ibr_era_format_money(trim($seg[2])) : trim($seg[2]);
824 $amt_a .= ' ';
826 $amt_c .= $seg[1] .' ';
828 if ($loop == "2100") {
829 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_amt_code'] = $amt_c;
830 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['clm_amt_amt'] = $amt_a;
832 if ($loop == "2110") {
833 //$amt_c = $amt_qual[$seg[1]];
834 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_amt_code'] = $amt_c;
835 $ar_clm['LX'][$lx_ct]['CLM'][$clp_ct]['SVC'][$svc_ct]['svc_amt_amt'] = $amt_a;
837 // debug
838 // var_dump($seg);
839 continue;
842 // PLB Segment -- not a claim and perhaps not for biller's eyes
843 // I believe more than one PLB segment per 835 is allowed
844 if ($seg[0] == "PLB" ) {
845 // payents and adjustments that are not claims
846 $ar_clm['PLB'][$plb_ct]['provider'] = $seg[1];
847 $ar_clm['PLB'][$plb_ct]['per_date'] = $seg[2];
848 // multiple identifiers and amounts are possible
849 // PLB03 = Identifier: PLB04 = Amount
850 $seg_ct = count($seg);
851 $itm_ct = 0;
852 for ($i=3; $i<$seg_ct; $i=$i+2) {
853 // the x12 SubItem delimiter is actually needed here
854 $plb_id = explode($comp_delim, $seg[$i]);
855 $plb_txt_ar = ibr_era_code_text ( "PLB", $plb_id[0] );
856 $ar_clm['PLB'][$plb_ct]['itm'][$itm_ct]['text'] = $plb_txt_ar[0][0] . " " . $plb_txt_ar[0][1];
858 $ar_clm['PLB'][$plb_ct]['itm'][$itm_ct]['identifier'] = $seg[$i];
859 $ar_clm['PLB'][$plb_ct]['itm'][$itm_ct]['amount'] = $seg[$i+1];
861 $itm_ct++;
862 // ['provider']['per_date']["identifier_$i"]["amount_$i"]["text_$i"]
864 $plb_ct++;
865 // debug
866 // var_dump($seg);
867 continue;
869 // $loop_seg = $seg[0]; // keep track of previous segment
870 //debug only segments that are not caught by an if block increment the counter
871 $seg_ct++;
872 } // end foreach
873 // debug
874 // var_dump($ar_clm);
876 return $ar_clm;
878 } // end function ibr_era_claim_vals
881 * Display brief summary of single payment
883 * @uses csv_verify_file()
884 * @uses csv_x12_segments()
885 * @uses ibr_era_claim_slice_pos()
886 * @uses ibr_era_claim_vals()
887 * @param string $filename
888 * @param string $clm01
889 * @return string
891 function ibr_era_claim_summary($filename, $clm01) {
892 // create a display for dialogue from csv dataTable
893 if (!$clm01) {
894 csv_edihist_log("ibr_era_claim_popup: invalid claim id");
895 return "empty or invalid claim id <br />" . PHP_EOL;
897 $f_path = csv_verify_file( $filename, "era");
899 if (!$f_path) {
900 csv_edihist_log("ibr_era_claim_popup: failed verification $filename");
901 return "failed verification for $filename <br />" . PHP_EOL;
902 } else {
903 $fn = basename($f_path);
904 $ar_era_segments = csv_x12_segments($f_path, "era", false);
905 if (!is_array($ar_era_segments) || !count($ar_era_segments['segments']) >0 ) {
906 return "failed to get segments for $fn<br />" . PHP_EOL;
907 } else {
908 $ar_segs = $ar_era_segments['segments'];
909 $comp_d = $ar_era_segments['delimiters']['s'];
910 $elem_d = $ar_era_segments['delimiters']['e'];
911 $srch = 'encounter';
912 $ar_clp = array();
913 $ar_clp_slice = ibr_era_claim_slice_pos ($ar_segs, $clm01, $elem_d, $srch);
914 $svc_adj_codes = '';
915 $sp = '';
919 if ( !empty($ar_clp_slice) && is_array($ar_clp_slice)) {
920 foreach($ar_clp_slice as $cs) {
921 // because an encounter can be reported more than once in an era file
922 if (count($cs) == 2) {
923 $clp_segs = array_slice($ar_segs, $cs['start'], $cs['count']);
924 // append segments to $ar_clp
925 foreach($clp_segs as $clp) {
926 $ar_clp[] = $clp;
930 } else {
931 // no segments found for claim
932 csv_edihist_log("ibr_era_html_page: Claim $clm01 not found in $fn");
933 $sp .= "Claim $pid_enctr not found in $fn";
934 return $sp;
936 // now use $ar_clp to create a values array
937 $ar_eob = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d);
938 if (!is_array($ar_eob) && count($ar_eob)) {
939 $sp .= "Error in processing";
940 return $sp;
942 // this is a four column format of the individual claim RA
943 $sp .= "<table class='summaryRA' cols=4><caption>ERA Summary $clm01</caption>".PHP_EOL;
944 $sp .= "<thead><th>Miscellaneous</th><th>Fee</th><th>Pmt</th><th>PtRsp</th></thead>".PHP_EOL;
945 $sp .= "<tbody>".PHP_EOL;
946 foreach ($ar_eob['LX'][0]['CLM'] as $clm) {
948 $corr_str = "";
949 if (array_key_exists("corr_last", $clm)) {
950 if (strlen($clm['corr_last'])) { $corr_str .= "<em>L &nbsp;</em>{$clm['corr_last']}&nbsp;"; }
951 if (strlen($clm['corr_first'])) { $corr_str .= "<em>F &nbsp;</em>{$clm['corr_first']}&nbsp;"; }
952 if (strlen($clm['corr_mi'])) { $corr_str .= "<em>MI &nbsp;</em>{$clm['corr_mi']}&nbsp;"; }
953 if (strlen($clm['corr_ins_id'])) { $corr_str .= "<em>ID &nbsp;</em>{$clm['corr_ins_id']}"; }
955 $sp .= "<tr class='summary'>".PHP_EOL;
956 $sp .= isset($clm['pt_last']) ? "<td>{$clm['pt_last']}, {$clm['pt_first']}</td>" : "<td>&nbsp;</td>";
957 $sp .= isset($clm['enctr']) ? "<td>{$clm['pid']}-{$clm['enctr']}</td>" : "<td>&nbsp;</td>";
958 $sp .= isset($clm['clm_id']) ? "<td>{$clm['clm_id']}</td>" : "<td>&nbsp;</td>";
959 $sp .= ($corr_str) ? "<td>$corr_str</td>" : "<td>&nbsp;</td>";
960 $sp .= PHP_EOL."</tr>".PHP_EOL;
962 if (isset($clm['ins_expired'])) {
963 $sp .= "<tr class='summary'>".PHP_EOL;
964 $sp .= isset($clm['ins_expired']) ? "<td class='denied' colspan=2>Policy Expired: {$clm['ins_expired']}</td>" : "<td colspan=2>&nbsp;</td>";
965 $sp .= isset($clm['pr_priority']) ? "<td class='denied' colspan=2>Payer: {$clm['pr_priority']} {$clm['pr_priority_id']}</td>" : "<td colspan=2>&nbsp;</td>";
966 $sp .= PHP_EOL."</tr>".PHP_EOL;
968 if (isset($clm['sbr_last'])) {
969 $sp .= "<tr class='summary'>".PHP_EOL;
970 $sp .= isset($clm['sbr_last']) ? "<td colspan=2>{$clm['sbr_last']}, {$clm['sbr_first']}</td>" : "<td colspan=2>&nbsp;</td>";
971 $sp .= isset($clm['sbr_first']) ? "<td colspan=2>{$clm['sbr_ins_id']}</td>" : "<td colspan=2>&nbsp;</td>";
972 $sp .= PHP_EOL."</tr>".PHP_EOL;
975 $sp .= "<tr class='summary'>".PHP_EOL;
976 $sp .= isset($clm['clm_status']) ? "<td>{$clm['clm_status']}</td>" : "<td>&nbsp;</td>";
977 $sp .= isset($clm['fee']) ? "<td>{$clm['fee']}</td>" : "<td>&nbsp;</td>";
978 $sp .= isset($clm['pmt']) ? "<td>{$clm['pmt']}</td>" : "<td>&nbsp;</td>";
979 $sp .= isset($clm['ptresp']) ? "<td>{$clm['ptresp']}</td>" : "<td>&nbsp;</td>";
980 $sp .= PHP_EOL."</tr>".PHP_EOL;
981 //$ar_eob['LX']['CLM']['SVC']
982 foreach($clm['SVC'] as $svc) {
983 if (array_key_exists("svc_adj_code", $svc)) {
984 $svc_adj_codes .= $svc['svc_adj_code'];
986 $svcid = isset($svc['svc_date']) ? $svc['svc_date'] : '';
987 $svcid .= isset($svc['svc_adj_id']) ? ' &nbsp;' . $svc['svc_adj_id'] : '';
988 $sp .= "<tr class='summary'>".PHP_EOL;
989 //$sp .= isset($svc['svc_adj_id']) ? "<td>{$svc['svc_adj_id']}</td>" : "<td>&nbsp;</td>";
990 $sp .= "<td>$svcid</td>";
991 $sp .= isset($svc['svc_fee']) ? "<td>{$svc['svc_fee']}</td>" : "<td>&nbsp;</td>";
992 $sp .= isset($svc['svc_pmt']) ? "<td>{$svc['svc_pmt']}</td>" : "<td>&nbsp;</td>";
993 $sp .= isset($svc['svc_pt_resp']) ? "<td>{$svc['svc_pt_resp']}</td>" : "<td>&nbsp;</td>";
994 $sp .= PHP_EOL."</tr>".PHP_EOL;
995 $sp .= "<tr class='summary'>".PHP_EOL;
996 $sp .= isset($svc['prov_id_descr']) ? "<td colspan=2>{$svc['prov_id_descr']} {$svc['prov_id_num']}</td>" : "<td colspan=2>&nbsp;</td>";
997 $sp .= isset($svc['svc_adj_html']) ? "<td colspan=2>{$svc['svc_adj_html']}</td>" : "<td colspan=2>&nbsp;</td>";
998 $sp .= PHP_EOL."</tr>".PHP_EOL;
1000 $sp .= "</tbody>".PHP_EOL;
1003 if ($svc_adj_codes) {
1004 $svc_code_text = ibr_era_code_text ("CLMADJ", $svc_adj_codes );
1005 $sp .= "<table class='summaryRA' cols=4><caption>Service Adjustment Codes</caption>".PHP_EOL;
1006 //$sp .= "<tr class='code'><td colspan=4>Service Adjustment Codes</td></tr>";
1007 foreach($svc_code_text as $cd) {
1008 $sp .= "<tr class=\"svccode\">".PHP_EOL;
1009 $sp .= "<td align=\"center\" cellpadding=\"4\">{$cd[0]}</td> <td colspan=3>{$cd[1]}</td> ".PHP_EOL;
1010 $sp .= "</tr>".PHP_EOL;
1012 $sp .= "</tbody>".PHP_EOL;
1016 return $sp;
1021 * Generate the table heading for the tables created in ibr_era_claim_html ()
1023 * @param string $section default is CLM one of |CLM|BPR|PLB|TS3| section heading for html table
1024 * @param array $fmt_ar default is NULL otherwise TS3 values array is expected
1025 * @return string html table heading <table><caption></caption><thead><tr><th></th></tr></thead>
1027 function ibr_era_claim_EOB_heading_html ( $section = "CLM", $fmt_ar = NULL) {
1028 // generate an html string for the claim EOB table heading
1029 // table should begin with: <table cols=7>
1030 // row 2 status, received, insurance id, subscriber, other ins||service period||crossover
1031 $clm_tbl_head = "<table class=\"era835\"><caption>Claims Detail</caption>
1032 <thead>
1033 <tr align=left>
1034 <th>Status</th> <th>Received</th> <th>InsuranceID</th>
1035 <th>Subscriber</th> <th colspan=2>Period|Other|COB</th>
1036 </tr>
1037 <tr align=left>
1038 <th colspan=6>INS CO Information (if any)</th>
1039 </tr>
1040 <tr align=left>
1041 <th>Provider</th> <th>Claim Adjustment</th> <th>Amount Code</th>
1042 <th colspan=2>Corrections</th> <th>Remarks</th>
1043 </tr>
1044 <tr align=left>
1045 <th>Patient Name</th> <th>PatientID</th> <th>ClaimID</th>
1046 <th>FeeTotal</th> <th>Payment</th> <th>Pt Resp</th>
1047 </tr>
1048 <tr align=left>
1049 <th>SVC_Date</th> <th>Service</th> <th>Allowed</th>
1050 <th>Fee</th> <th>Svc Pmt</th> <th>Pt Resp</th>
1051 </tr>
1052 <tr align=left>
1053 <th>Provider</th> <th colspan=5>Revisions</th>
1054 </tr>
1055 <tr align=left>
1056 <th colspan=2 align=left>Location</th> <th colspan=3 align=left>Service Codes</th>
1057 <th align=left>Remark Codes</th>
1058 </tr>
1059 </thead>".PHP_EOL;
1061 $bpr_tbl_head = "<table class=\"era835\"><caption>Transaction Information</caption>
1062 <thead>
1063 <tr align=center>
1064 <th>Date</th> <th>Payer</th> <th>Payer ID</th>
1065 <th>Payer Source</th> <th>Payer Contact</th>
1066 </tr>
1067 <tr align=center>
1068 <th>Method</th> <th>CR/DB</th> <th>Total Pmt</th> <th>Trace</th> <th>&nbsp;</th>
1069 </tr>
1070 </thead>".PHP_EOL;
1072 $plb_tbl_head = "<table class=\"era835\"><caption>Non-Claim Credit/Debit Information</caption>
1073 <thead>
1074 <tr align=center>
1075 <th>Provider</th> <th>&nbsp;</th> <th>Date</th>
1076 </tr>
1077 <tr align=center>
1078 <th>Amount</th> <th>Identifier</th> <th>Text</th>
1079 </tr>
1080 </thead>".PHP_EOL;
1082 if ($section == "HDR") {
1083 return $hd_file_head;
1085 if ($section == "CLM") {
1086 return $clm_tbl_head;
1088 if ($section == "BPR") {
1089 return $bpr_tbl_head;
1091 if ($section == "PLB") {
1092 return $plb_tbl_head;
1094 if ($section == "TS3") {
1095 // this is a tough one - variable and used differently by payers
1096 // indicated by LX, LX required whenever TS3 or sorted CLP
1097 // the $fmt_ar is just the ts3 values array
1098 $hdr_ar = array('ts3'=>'SegmentID', 'ref_id'=>'ReferenceID',
1099 'facility_code'=>'Facility_Code', 'fiscal_per'=>'Fiscal Period',
1100 'claim_ct'=>'Claim Count', 'chg_tot'=>'Charge Total',
1101 'chg_cvd'=>'Covered Charges', 'chg_noncvd'=>'Non-Covered Amt',
1102 'chg_denied'=>'Denied Amt', 'amt_prov'=>'Provider Amt',
1103 'amt_int'=>'Interest Amt', 'amt_adj'=>'Adjustment Amt',
1104 'amt_grr'=>'Gramm-Rudman', 'amt_msp'=>'MCR MSP Payer',
1105 'chg_bld'=>'Blood Deductible','chg_nonlab'=>'Non-Lab Chrg',
1106 'chg_coins'=>'Coinsurance','chg_hcpcs_rpt'=>'HCPCS Reported',
1107 'chg_hcpcs_pbl'=>'HCPCS Payable', 'amt_dedctbl'=>'Deductible',
1108 'amt_prof'=>'Professional Amt', 'amt_msp_pt'=>'MCR MSP Pt',
1109 'amt_reimb_pt'=>'Patient Reimbursment', 'pip_ct'=>'PIP Claims',
1110 'pip_amt'=>'PIP Amount');
1112 if (is_array($fmt_ar)) {
1113 $idx = 0;
1114 $ts3_tbl_head = "<table class=\"era835\">
1115 <caption>Claim Payment Summary</caption>
1116 <thead>
1117 <tr align=left>";
1118 foreach ($fmt_ar as $ky=>$val) {
1119 if (array_key_exists($ky, $hdr_ar ) ) {
1120 $ts3_tbl_head .= "<th>{$hdr_ar[$ky]}</th>";
1121 $idx++;
1122 if ($idx % 7 == 0) {$ts3_tbl_head .="</tr><tr align=left>";}
1125 $ts3_tbl_head .="</tr>
1126 </thead>".PHP_EOL;
1127 } else {
1128 // no format array
1129 $ts3_tbl_head = "";
1131 return $ts3_tbl_head;
1136 * Create an html page to display selected information in an x12 835 claim remittance file
1138 * This function is called from function ibr_era_html_page() in this script. It calls
1139 * function ibr_era_claim_EOB_heading_html() to generate table headings <thead></thead>
1140 * This function generates a complete web page content with html tags. It relies on the
1141 * ibr_era_claim_vals() function in this php file. The parts of the x12 835 file are divided
1142 * into seperate tables in the page (which could benefit by better design).
1143 * The transaction summary (BPR key) is one table, the provider level payment (PLB key)
1144 * is another, and the claims detail has a claim group summary (TS3 key) and claim detail
1145 * (CLP key). The coded messages are collected and displayed in a table that is to be
1146 * designated as the footer.
1148 * @param array $ar_clpvals the array from ibr_era_claim_vals()
1149 * @param string $fname optional - the filename of the x12 835 remittance advice
1150 * @param string $items ALL, trace #, or claim id
1151 * @return string body of html page
1153 function ibr_era_claim_html ($ar_clpvals, $fname = "835 Remittance Advice", $items = "ALL" ) {
1154 // @param array $ar_clpvals -- from ibr_era_claim_vals
1156 //$ar_clpvals['BPR']['key']
1157 //$ar_clpvals['LX'][$lx_ct]['TS3']['key']
1158 //$ar_clpvals['LX'][$lx_ct]['CLM'][$clp_ct]['key']
1159 //$ar_clpvals['LX'][$lx_ct]['CLM'][$clp_ct]["SVC"][$svc_ct]['key']
1160 //$ar_clpvals['PLB']['key']
1161 // assign values in proper places
1162 $bpr_html = '';
1163 $plb_html = '';
1165 $svc_adj_type = "";
1166 $clm_adj_type = "";
1167 $svc_adj_codes = ""; // store claim and service adjustment codes
1168 $clm_adj_codes = "";
1169 $clm_amt_codes = "";
1170 $lq_rem_codes = "";
1172 $dtl = "";
1173 if ($items == "ALL") {
1174 // we are rendering all the ST--SE in the file
1175 $dtl = "ALL";
1176 } else {
1177 // a specific trace or claim
1178 $dtl = (strlen($items)) ? $items : "Items not specified";
1180 // The TBODY element defines a group of data rows in a table.
1181 // A TABLE must have one or more TBODY elements, which must follow the optional TFOOT.
1182 // The TBODY end tag is always optional.
1183 // generate a heading with the file name -- move heading to ibr_history.php
1184 //$clp_html = "<html>
1185 // <head>
1186 // <link rel=\"stylesheet\" href=\"jscript/style/csv_new.css\" type=\"text/css\" media=\"print, projection, screen\" />
1187 // </head>
1188 $clp_html = "<h4>HTML Rendering of: $fname &nbsp;&nbsp; $dtl</h4>".PHP_EOL;
1190 // issue of proper quoting in html table text
1191 // $clp_html is supposed to hold the entire html text of the claims table
1192 // so that it can be displayed with simple echo $clp_html
1196 // Information on the transaction
1197 if (array_key_exists("BPR", $ar_clpvals ) ) {
1198 $ar_bpr = $ar_clpvals['BPR'];
1199 $clp_html .= ibr_era_claim_EOB_heading_html ("BPR");
1200 //['total_pmt']['credit']['method']['payer_id']['date_pmt']['trace']['payer_name']['payer_source']['payer_contact']
1201 //['payer_tech']['payer_tech_contact']['rdm_trans_code']['rdm_name']['rdm_comm_num']
1202 // to-do: colorize NON payments and Debits['payer_id_name']['payer_id_num']['payer_id_addr2']['payer_id_adr3']['payer_contact']
1204 // $clp_html .= isset($ar_bpr['date_pmt']) ? "<td>{$ar_bpr['date_pmt']}</td>" : "<td>&nbsp;</td>";
1205 // the isset() routine is needed to prevent php NOTICE warnings for missing keys
1206 $clp_html .= "<tbody class=\"bpr\">".PHP_EOL;
1207 $clp_html .= "<tr class=\"bpr\">".PHP_EOL;
1208 $clp_html .= isset($ar_bpr['date_pmt']) ? "<td>{$ar_bpr['date_pmt']}</td>" : "<td>&nbsp;</td>";
1209 $clp_html .= isset($ar_bpr['payer_name']) ? "<td>{$ar_bpr['payer_name']}</td>" : "<td>&nbsp;</td>";
1210 $clp_html .= isset($ar_bpr['payer_id']) ? "<td>{$ar_bpr['payer_id']}</td>" : "<td>&nbsp;</td>";
1211 $clp_html .= isset($ar_bpr['payer_source']) ? "<td>{$ar_bpr['payer_source']}</td>" : "<td>&nbsp;</td>";
1212 $clp_html .= isset($ar_bpr['payer_contact']) ? "<td>{$ar_bpr['payer_contact']}</td>" : "<td>&nbsp;</td>";
1213 $clp_html .= PHP_EOL."</tr>".PHP_EOL;
1214 $clp_html .= PHP_EOL."<tr class=\"bpr\">".PHP_EOL;
1215 $clp_html .= isset($ar_bpr['payer_id_num']) ? "<td>{$ar_bpr['payer_id_num']}</td>" : "<td>&nbsp;</td>";
1216 $clp_html .= isset($ar_bpr['payer_id_addr2']) ? "<td>{$ar_bpr['payer_id_addr2']}</td>" : "<td>&nbsp;</td>";
1217 $clp_html .= isset($ar_bpr['payer_id_addr3']) ? "<td>{$ar_bpr['payer_id_addr3']}</td>" : "<td>&nbsp;</td>";
1218 $clp_html .= isset($ar_bpr['payer_contact']) ? "<td>{$ar_bpr['payer_contact']}</td>" : "<td>&nbsp;</td>";
1219 $clp_html .= isset($ar_bpr['payer_id_name']) ? "<td>{$ar_bpr['payer_id_name']}</td>" : "<td>&nbsp;</td>";
1220 $clp_html .= PHP_EOL."</tr>".PHP_EOL;
1221 if (isset($ar_bpr['payer_tech'])) {
1222 $clp_html .= "<tr class=\"bpr\">".PHP_EOL;
1223 $clp_html .= isset($ar_bpr['payer_tech']) ? "<td>{$ar_bpr['payer_tech']}</td>" : "<td>&nbsp;</td>";
1224 $clp_html .= isset($ar_bpr['payer_tech_contact']) ? "<td colspan=4>{$ar_bpr['payer_tech_contact']}</td>" : "<td colspan=4>&nbsp;</td>";
1225 $clp_html .= PHP_EOL."</tr>".PHP_EOL;
1227 if (isset($ar_bpr['rdm_trans_code'])) {
1228 $clp_html .= "<tr class=\"bpr\">".PHP_EOL;
1229 $clp_html .= isset($ar_bpr['rdm_trans_code']) ? "<td>{$ar_bpr['rdm_trans_code']}</td>" : "<td>&nbsp;</td>";
1230 $clp_html .= isset($ar_bpr['rdm_name']) ? "<td colspan=2>{$ar_bpr['rdm_name']}</td>" : "<td colspan=2>&nbsp;</td>";
1231 $clp_html .= isset($ar_bpr['rdm_comm_num']) ? "<td colspan=2>{$ar_bpr['rdm_comm_num']}</td>" : "<td colspan=2>&nbsp;</td>";
1232 $clp_html .= PHP_EOL."</tr>".PHP_EOL;
1234 $clp_html .= "<tr class=\"bpr\">".PHP_EOL;
1235 $clp_html .= isset($ar_bpr['method']) ? "<td>{$ar_bpr['method']}</td>" : "<td>&nbsp;</td>";
1236 $clp_html .= isset($ar_bpr['credit']) ? "<td>{$ar_bpr['credit']}</td>" : "<td>&nbsp;</td>";
1237 $clp_html .= isset($ar_bpr['total_pmt']) ? "<td>{$ar_bpr['total_pmt']}</td>" : "<td>&nbsp;</td>";
1238 $clp_html .= isset($ar_bpr['trace']) ? "<td colspan=2>{$ar_bpr['trace']}</td>" : "<td>&nbsp;</td>";
1239 $clp_html .= PHP_EOL."</tr>".PHP_EOL;
1240 $clp_html .= "</tbody>".PHP_EOL."</table>".PHP_EOL;
1243 // check for PLB segments
1244 // PLB is a non-claim related financial transaction, such as Medicare bonus, withholdings, garnishment, etc.
1245 if (array_key_exists("PLB", $ar_clpvals ) ) {
1246 $clp_html .= ibr_era_claim_EOB_heading_html ("PLB");
1247 $clp_html .= "<tbody id=\"plb\">";
1248 foreach ($ar_clpvals["PLB"] as $plb) {
1249 $clp_html .= "<tr align=left class=\"plb\">
1250 <td colspan=2>{$plb['provider']}</td>
1251 <td>{$plb['per_date']}</td>
1252 </tr>";
1253 foreach ( $plb['itm'] as $itm) {
1254 $clp_html .= "<tr align=\"left\" class=\"plb\">
1255 <tr>
1256 <td>{$itm['amount']}</td>
1257 <td>{$itm['identifier']}</td>
1258 <td>{$itm['text']}</td>
1259 </tr>";
1260 //['provider']['per_date']["identifier_$i"]["amount_$i"]["text_$i"]
1262 $clp_html .= "
1263 </tbody>
1264 </table>".PHP_EOL;
1267 // now the claims in a table
1268 if (array_key_exists("LX", $ar_clpvals) ) {
1270 foreach($ar_clpvals["LX"] as $ar_lx) {
1271 $lx_ts3 = '';
1272 $lx_head = '';
1273 $lx_body ='';
1274 $lx_foot = '';
1275 if (array_key_exists("TS3", $ar_lx) ) {
1276 // there should be none or one TS3 per LX
1277 $idx = 0;
1279 $lx_ts3 = '';
1280 $lx_ts3 .= ibr_era_claim_EOB_heading_html ("TS3", $ar_lx['TS3']);
1281 $lx_ts3 .= "<tbody id='ts3'><tr class=\"ts3\">";
1283 foreach ($ar_lx['TS3'] as $ts3) {
1284 $lx_ts3 .= "<td>$ts3</td>";
1285 $idx++;
1286 //if ($idx % 7 == 0) { $clp_html .="</tr><tr bgcolor = #EECDA5>";}
1287 if ($idx % 7 == 0) { $clp_html .="</tr><tr class=\"ts3\">";}
1289 $lx_ts3 .= "</tr>
1290 </tbody>
1291 </table>".PHP_EOL;
1292 } // end if (array_key_exists("TS3"
1294 if (array_key_exists("CLM", $ar_lx) ) {
1296 $lx_head .= ibr_era_claim_EOB_heading_html ("CLM");
1297 // index to alternate background colors
1298 $idx = 0;
1299 // information on each claim, the $ar_clpvals['CLM']
1300 foreach ($ar_lx['CLM'] as $val) {
1301 //alternate background colors <tr bgcolor= {$bgc}>
1302 $bgc = ($idx % 2 == 1 ) ? 'clp0' : 'clp1';
1303 $idx++;
1304 // //////////////////////////
1305 // this is where the <tbody id='eraclp'> goes
1306 // so each claim detail will be in its own <tbody> tag
1307 $lx_body .= "<tbody class='eraclp'>".PHP_EOL;
1309 if ( array_key_exists ("SVC", $val) && is_array($val["SVC"]) ) {
1310 // one or more svc arrays
1311 // create $svc_html now, then append to $lx_body
1312 $svc_html = "";
1314 foreach ($val["SVC"] as $sv) {
1316 // row 6 in RA date, code, allowed, fee, payment, pt resp
1317 // get quantity difference, if any; service id is required element
1318 $sv_idcode = isset($sv['svc_qty']) ? $sv['svc_adj_id'].' ('.$sv['svc_qty'].')' : $sv['svc_adj_id'];
1320 $svc_html .= "<tr class=\"{$bgc}\">".PHP_EOL;
1321 $svc_html .= isset($sv['svc_date']) ? "<td>{$sv['svc_date']}</td>" : "<td>&nbsp;</td>";
1322 $svc_html .= isset($sv['svc_adj_id']) ? "<td>$sv_idcode</td>" : "<td>&nbsp;</td>";
1323 $svc_html .= isset($sv['svc_amt_amt']) ? "<td>{$sv['svc_amt_amt']}</td>" : "<td>&nbsp;</td>";
1324 $svc_html .= isset($sv['svc_fee']) ? "<td>{$sv['svc_fee']}</td>" : "<td>&nbsp;</td>";
1325 $svc_html .= isset($sv['svc_pmt']) ? "<td>{$sv['svc_pmt']}</td>" : "<td>&nbsp;</td>";
1326 $svc_html .= isset($sv['svc_pt_resp']) ? "<td>{$sv['svc_pt_resp']}</td>" : "<td>&nbsp;</td>";
1327 $svc_html .= PHP_EOL."</tr>".PHP_EOL;
1329 // row 7 in RA to-do set only if different provider or service item was adjusted svc item,
1330 if (isset($sv['svc_code']) || isset($sv['prov_id_descr'])) {
1331 $svc_html .= "<tr class=\"{$bgc}\">".PHP_EOL;
1332 // provider id different for this service
1333 $svc_html .= isset($sv['prov_id_num']) ? "<td>{$sv['prov_id_descr']} {$sv['prov_id_num']}</td>" : "<td>&nbsp;</td>";
1334 //submitted service replaced by adjudicated service
1335 $svc_html .= isset($sv['svc_code']) ? "<td colspan=5>{$sv['svc_code']}</td>": "<td colspan=5>&nbsp;</td>";
1336 $svc_html .= PHP_EOL."</tr>".PHP_EOL;
1339 // row 7 in RA location, adjustments, remarks
1340 $svc_html .= "<tr class=\"{$bgc}\">";
1341 if ( isset($sv['svc_loc']) ) {
1342 $loc_val = ibr_era_code_text ("LOC", $sv['svc_loc_code'] );
1343 $svc_html .= "<td colspan=2>{$sv['svc_loc']} {$sv['svc_loc_code']} {$loc_val[0][1]} </td>";
1344 } else {
1345 $svc_html .= "<td colspan=2>&nbsp;</td>";
1347 // Service Adjustment Codes added 'svc_adj_amt'
1348 if (array_key_exists("svc_adj_code", $sv)) {
1349 $svc_adj_codes .= $sv['svc_adj_code'];
1350 $svc_adj_type .= $sv['svc_adj_type'];
1351 $svc_html .= "<td align=left colspan=3>&nbsp;<em>Svc Adj:</em>&nbsp; {$sv['svc_adj_html']}</td>";
1352 } else {
1353 $svc_html .= "<td colspan=3>&nbsp;</td>";
1355 // service amount codes with claim amount codes svc_amt_code
1356 if (array_key_exists('svc_amt_code', $sv)) {
1357 $clm_amt_codes .= $sv['svc_amt_code'];
1359 // LQ Health Care Remark codes
1360 if (array_key_exists("rem_code", $sv)) {
1361 $lq_rem_codes .= $sv['rem_code'];
1362 // $rem_val = ibr_era_code_text ("LOC", $sv['rem_code'] );
1363 $svc_html .= "
1364 <td><em>HC Remark</em> {$sv['rem_code']}</td>";
1365 } else {
1366 $svc_html .= "
1367 <td>&nbsp;</td>";
1369 $svc_html .= "
1370 </tr>".PHP_EOL;
1372 } // end foreach ($val["SVC"] as $sv)
1374 } // end if ($ky == "SVC")
1376 // /////////////////////
1377 // reconfiguring the claim detail table
1378 // change clm_html to lx_body in this section
1380 // the first line of the claim detail, mostly CLP
1381 // <th>Status</th> <th>Provider ID</th> <th>Subscriber</th> == "4 Denied"
1382 // <th>Start</th> <th>End</th><th>COB Crossover</th>
1383 // row 1 divider
1384 $lx_body .= "<tr class=\"{$bgc}\">".PHP_EOL;
1385 $lx_body .= "<td colspan=6>&nbsp; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ &nbsp;</td>".PHP_EOL;
1386 $lx_body .= "</tr>".PHP_EOL;
1387 // row 2 status, received, insurance id, subscriber, other ins||service period||crossover
1388 $lx_body .= "<tr class=\"{$bgc}\">".PHP_EOL;
1389 if ( isset($val['clm_status']) && strpos("|4 |22|", substr($val['clm_status'],0,2)) !== FALSE ) {
1390 $lx_body .= isset($val['clm_status']) ? "<td class=\"denied\">{$val['clm_status']}</td>" : "<td>&nbsp;</td>";
1391 } else {
1392 $lx_body .= isset($val['clm_status']) ? "<td>{$val['clm_status']}</td>" : "<td>&nbsp;</td>";
1394 $lx_body .= isset($val['clm_recieved']) ? "<td>{$val['clm_recieved']}</td>" : "<td>&nbsp;</td>";
1395 $lx_body .= isset($val['pt_ins_id']) ? "<td>{$val['pt_ins_id']}</td>" : "<td>&nbsp;</td>";
1396 $lx_body .= isset($val['sbr_last']) ? "<td>{$val['sbr_last']}, {$val['sbr_first']}</td>" : "<td>&nbsp;</td>";
1397 //$lx_body .= isset($val['svc_date_per_begin']) ? "<td>{$val['svc_date_per_begin']}</td>" : "<td>&nbsp;</td>";
1398 // deal with the case where ins co claims another is priority payer
1399 if (isset($val['pr_priority']) ) {
1400 $lx_body .= isset($val['pr_priority']) ? "<td class=\"denied\" colspan=2>{$val['pr_priority']} {$val['pr_priority_id']}</td>" : "<td colspan=2>&nbsp;</td>";
1401 //$lx_body .= isset($val['pr_priority_id']) ? "<td class=\"denied\">{$val['pr_priority_id']}</td>" : "<td>&nbsp;</td>";
1402 } elseif (isset($val['svc_date_per_begin']) ) {
1403 $lx_body .= "<td colspan=2>{$val['svc_date_per_begin']} to {$val['svc_date_per_end']}</td>";
1404 } else {
1405 $lx_body .= isset($val['crossover_name']) ? "<td colspan=2>{$val['crossover_name']}</td>" : "<td colspan=2>&nbsp;</td>";
1407 $lx_body .= PHP_EOL."</tr>".PHP_EOL;
1408 // row 3 otherID, expired||reference, , payer source, payer contact
1409 $lx_body .= "<tr class=\"{$bgc}\">".PHP_EOL;
1410 //$lx_body .= isset($val['clm_oth_id_descr']) ? "<td>{$val['clm_oth_id_descr']}</td>" : "<td>&nbsp;</td>";
1412 if ( isset($val['ins_expired']) ) {
1413 $lx_body .= "<td colspan=3 class=\"denied\">Expiration Date: {$val['ins_expired']}</td>";
1414 } else {
1415 //$lx_body .= isset($val['ref_description']) ? "<td>{$val['ref_description']}</td>" : "<td>&nbsp;</td>";
1416 $lx_body .= isset($val['clm_oth_id_num']) ? "<td colspan=3>{$val['clm_oth_id_num']}</td>" : "<td colspan=3>&nbsp;</td>";
1418 $lx_body .= isset($val['ref_value']) ? "<td>{$val['ref_value']}</td>" : "<td>&nbsp;</td>";
1419 $lx_body .= isset($val['payer_source']) ? "<td>{$val['payer_source']}</td>" : "<td>&nbsp;</td>";
1420 $lx_body .= isset($val['payer_contact']) ? "<td>{$val['payer_contact']}</td>" : "<td>&nbsp;</td>";
1421 $lx_body .= PHP_EOL."</tr>".PHP_EOL;
1422 // row 4 providerID, claimAdj, amount code, corrections, Remark Code
1423 // here we want to add a row of possible code values
1424 // corrected name information
1425 $lx_body .= "<tr class=\"{$bgc}\">".PHP_EOL;
1427 $lx_body .= isset($val['provider_id']) ? "<td>{$val['provider_id']}</td>" : "<td>&nbsp;</td>";
1429 // Claim Adjustment Codes CAS segment at loop 2100 level
1430 if (array_key_exists("clm_adj_code", $val)) {
1431 // expect service adjustment code instead of remark code
1432 // collect the codes so we can get the meaning later
1433 // ['clm_adj_type']['clm_adj_code']['clm_adj_amt']['clm_adj_total']['clm_adj_qty']
1434 // indicates some issue, highlight this with denied style
1435 $svc_adj_codes .= $val['clm_adj_code'];
1436 $clm_adj_type .= $val['clm_adj_type'];
1437 $lx_body .= "<td align=right class=\"denied\"><em>Claim Adj</em> {$val['clm_adj_html']}</td>";
1438 } else {
1439 $lx_body .= "<td>&nbsp;</td>";
1441 // add AMT segment information at claim level, if any
1442 if (array_key_exists("clm_amt_code", $val)) {
1443 // collect values for table footer code text
1444 $clm_amt_codes .= $val['clm_amt_code'];
1445 $lx_body .= "<td>{$val['clm_amt_amt']}</td>";
1446 } else {
1447 $lx_body .= "<td>&nbsp;</td>";
1449 // here we get the corrected names ['corr_last']['corr_first']['corr_mi']['corr_ins_id']
1450 if (array_key_exists("corr_last", $val)) {
1451 // corrected names are usually pointless, but perhaps there are times when they are useful
1452 $corr_str = "";
1453 if (strlen($val['corr_last'])) { $corr_str .= "<em>L &nbsp;</em>{$val['corr_last']}&nbsp;"; }
1454 if (strlen($val['corr_first'])) { $corr_str .= "<em>F &nbsp;</em>{$val['corr_first']}&nbsp;"; }
1455 if (strlen($val['corr_mi'])) { $corr_str .= "<em>MI &nbsp;</em>{$val['corr_mi']}&nbsp;"; }
1456 if (strlen($val['corr_ins_id'])) { $corr_str .= "<em>ID &nbsp;</em>{$val['corr_ins_id']}"; }
1457 $lx_body .= "
1458 <td colspan=2><em>Corrected: </em> $corr_str</td>";
1459 } else {
1460 $lx_body .= "
1461 <td colspan=2>&nbsp;</td>";
1463 // here we get the Claim Adjustment and Remittance Remark codes and text
1464 if (array_key_exists("moa", $val)) {
1465 // $ar_codes = ibr_era_code_text ("RA", $val['moa'] );
1466 // collect the codes so we can get the meaning later
1467 $ra_adj_codes .= $val['moa']; // save these for later
1468 $lx_body .= "
1469 <td align=center><em>Rem Code</em> {$val['moa']}</td>";
1470 } else {
1471 $lx_body .= "<td>&nbsp;</td>";
1473 $lx_body .= PHP_EOL."</tr>".PHP_EOL;
1474 // row 5 provider, amountCode, claimAdjustment, corrections, Remark Codes
1475 $lx_body .= "<tr class=\"{$bgc}\">".PHP_EOL;
1476 $lx_body .= isset($val['pt_last']) ? "<td>{$val['pt_last']}, {$val['pt_first']}</td>" : "<td>&nbsp;</td>";
1477 $lx_body .= isset($val['pid']) ? "<td>{$val['pid']}-{$val['enctr']}</td>" : "<td>&nbsp;</td>";
1478 $lx_body .= isset($val['clm_id']) ? "<td>{$val['clm_id']}</td>" : "<td>&nbsp;</td>";
1479 $lx_body .= isset($val['fee']) ? "<td>{$val['fee']}</td>" : "<td>&nbsp;</td>";
1480 $lx_body .= isset($val['pmt']) ? "<td>{$val['pmt']}</td>" : "<td>&nbsp;</td>";
1481 $lx_body .= isset($val['ptresp']) ? "<td>{$val['ptresp']}</td>" : "<td>&nbsp;</td>";
1482 $lx_body .= PHP_EOL."</tr>".PHP_EOL;
1485 // row 6 MOA information, if any
1486 if (array_key_exists("moa_amt", $val) && strlen($val['moa_amt']) > 0) {
1487 // outpatient adjudication information, medicare claim adjustments, if applicable
1488 // set these out in their own row
1489 $lx_body .= "<tr>".PHP_EOL;
1490 $lx_body .= "<td colspan=6>{$val['moa_amt']}</td>";
1491 $lx_body .= PHP_EOL."</tr>".PHP_EOL;
1494 // add the svc html created above
1495 $lx_body .= $svc_html;
1497 // done with the Claim detail table
1498 // but we will append rows for codes and explanations
1499 // //////////////////
1500 // now we would close the <tbody> $clp_html .= "</tbody>";
1501 $lx_body .= "</tbody>".PHP_EOL;
1503 } // end foreach ($ar_clpvals['CLM'] as $val)
1505 // now rows of codes and explanations
1506 // /////////////////////
1507 // Note that we could end the claim detail table and begin this 'codes' table
1508 // This would be wrapped in the <tfoot> tag
1509 // It would have to be assigned to a different variable, like $foot_html
1510 // When the entire era array has been read, the output html is assembled
1511 // by concatenating <thead> <tfoot> and <tbody>, in that order
1512 // ///////////////
1513 //$clp_html .= "</tbody>";
1514 // the Amount Code values
1515 $lx_foot = "<tfoot>".PHP_EOL;
1516 if ($clm_amt_codes) {
1517 $ar_amt_rem = ibr_era_code_text ("AMT", $clm_amt_codes );
1518 $lx_foot .= "<tr class=\"code\"><td colspan=6>Amount Codes</td></tr>";
1519 foreach ( $ar_amt_rem as $cd ) {
1520 $lx_foot .= "<tr class=\"code\">
1521 <td align=\"center\">{$cd[0]}</td>
1522 <td colspan=5>{$cd[1]}</td>
1523 </tr>";
1525 // reset for next group of claims
1526 $clm_amt_codes = "";
1527 //$lx_foot .= "</tbody>";
1529 // print out the Remark and Adjustment code text
1530 if ($lq_rem_codes) {
1531 $ar_lq_rem = ibr_era_code_text ("RA", $lq_rem_codes );
1532 $lx_foot .= "<tr class=\"code\"><td colspan=6>HC Remark Codes</td></tr>";
1533 foreach ( $ar_lq_rem as $cd ) {
1534 $lx_foot .= "<tr class=\"code\">
1535 <td align=\"center\">{$cd[0]}</td>
1536 <td colspan=5>{$cd[1]}</td>
1537 </tr>";
1539 // reset
1540 $lq_rem_codes = "";
1541 //$lx_foot .= "</tbody>";
1543 if ($clm_adj_type) {
1544 $ar_clm_type = ibr_era_code_text ("CAS", $clm_adj_type );
1545 $lx_foot .= "<tr class=\"code\"><td colspan=6>Adjustment Type</td></tr>";
1546 foreach ( $ar_clm_type as $tp ) {
1547 $lx_foot .= "<tr class=\"code\">
1548 <td align=\"center\">{$tp[0]}</td>
1549 <td colspan=5>{$tp[1]}</td>
1550 </tr>";
1552 // reset
1553 $clm_adj_type = "";
1554 //$lx_foot .= "</tbody>";
1557 if ($ra_adj_codes) {
1558 $ar_clm_codes = ibr_era_code_text ("RA", $ra_adj_codes );
1560 $lx_foot .= "<tr class=\"code\"><td colspan=6>Remark Codes</td></tr>";
1561 foreach ( $ar_clm_codes as $cd ) {
1562 $lx_foot .= "<tr class=\"code\">
1563 <td align=\"center\">{$cd[0]}</td>
1564 <td colspan=5>{$cd[1]}</td>
1565 </tr>";
1567 // reset
1568 $ra_adj_codes = "";
1569 //$lx_foot .= "</tbody>";
1572 if ($svc_adj_type) {
1573 $ar_svc_type = ibr_era_code_text ("CAS", $svc_adj_type );
1574 $lx_foot .= "<tr class=\"code\"><td colspan=6>Adjustment Type</td></tr>";
1575 foreach ( $ar_svc_type as $tp ) {
1576 $lx_foot .= "<tr class=\"code\">
1577 <td align=\"center\">{$tp[0]}</td>
1578 <td colspan=5>{$tp[1]}</td>
1579 </tr>";
1581 // reset
1582 $svc_adj_type = "";
1583 //$lx_foot .= "</tbody>";
1586 if ($svc_adj_codes) {
1587 $ar_svc_codes = ibr_era_code_text ("CLMADJ", $svc_adj_codes );
1588 $lx_foot .= "<tr class=\"code\"><td colspan=6>Service Adjustment Codes</td></tr>";
1589 foreach ( $ar_svc_codes as $cd ) {
1590 $lx_foot .= "<tr class=\"code\">
1591 <td align=\"center\">{$cd[0]}</td>
1592 <td colspan=5>{$cd[1]}</td>
1593 </tr>";
1595 // reset
1596 $svc_adj_codes = "";
1597 //$clp_html .= "</tbody>";
1599 // supposedly ending tags are not required
1600 $lx_foot .= "</tfoot>".PHP_EOL;
1601 } // end if (array_key_exists("CLM", $ar_lx) )
1602 // assemble claims detail table
1603 $clp_html .= $lx_ts3;
1604 $clp_html .= $lx_head;
1605 $clp_html .= $lx_foot;
1606 $clp_html .= $lx_body.PHP_EOL."</table>".PHP_EOL;
1607 } // end foreach($ar_clpvals['LX'][lx_ct] as $ar_lx)
1608 // finish table
1609 //$clp_html .= "</body></html>";
1610 //$clp_html .= "</tbody></table></body></html>";
1611 } // end if (array_key_exists("LX", $ar_clpvals ) )
1613 return $clp_html;
1617 * Identify the segments comprising an ST...SE transaction set and the trace number
1619 * @param array $ar_segments array of segments from x12 835 file
1620 * @param string $e_delim element delimiter
1621 * @return array (start, count, trace) possibly more than one
1623 function ibr_era_st_slice_pos ( $ar_segments, $e_delim="*" ) {
1624 // scan through the segments array
1625 // identify the index on ST and the count to SE
1626 // useful for array_slice function
1627 $ar_st_pos = array();
1628 $st_idx = 0;
1629 $st_pos = -1;
1631 foreach ( $ar_segments as $segtxt ) {
1633 $st_pos++;
1634 if (strpos("|ST*|TRN|SE*", substr($segtxt,0, 3)) !== false) {
1635 $seg = explode($e_delim, $segtxt);
1636 } else {
1637 continue;
1640 if ($seg[0] == "ST") {
1641 $ar_st_pos[$st_idx] = array( "$st_pos", 0, 0 );
1644 if ($seg[0] == "TRN") {
1645 $ar_st_pos[$st_idx][2] = $seg[2];
1648 if ($seg[0] == "SE") {
1649 $ar_st_pos[$st_idx][1] = $seg[1]; // SE01 = segment count
1650 $st_idx++; // increment array index
1654 return $ar_st_pos;
1659 * Identify the segments comprising a claim remittance advice
1661 * Either the pid or encounter are required
1663 * @param array $ar_segments segments array for era file
1664 * @param string $pid_enc pt control (pid-encounter) value we are searching for
1665 * @param string $e_delim the element delimiter in the segments
1666 * @param string $searchtype optional 'encounter' (default) or 'pid' if otherwise
1667 * @return array [$i][start] [count]
1669 function ibr_era_claim_slice_pos ( $ar_segments, $pid_enc, $e_delim='*', $searchtype='encounter' ) {
1671 // scan through segments array and get the positions of the claim
1672 // for array_slice(ar, start, count)
1673 // note: there are instances of the same claim reported more than once
1674 // in an era file, e.g. reversal and restatement
1675 // also, one or more claims for same patient in a single file
1676 // returns [$i](start, count)
1678 if (!$pid_enc || !is_string($pid_enc)) {
1679 csv_edihist_log("ibr_era_claim_slice_test: missing or invalid pid_encounter");
1680 return FALSE;
1682 $ar_clm_pos = array();
1683 $clm_found = FALSE;
1684 $end_str = "|SE$e_delim|LX$e_delim";
1686 $clm_pos = -1;
1687 $test_pos = 0;
1688 $clm_idx = 0;
1690 preg_match('/\D/', $pid_enc, $match2, PREG_OFFSET_CAPTURE);
1691 if (count($match2)) {
1692 $idar = csv_pid_enctr_parse($pid_enc);
1693 if (is_array($idar) && count($idar)) {
1694 $p = $idar['pid'];
1695 $plen = strlen($p);
1696 $e = $idar['enctr'];
1697 $elen = strlen($e);
1698 } else {
1699 csv_edihist_log("ibr_era_claim_slice_test: error parsing pid_encounter $pid_enc");
1700 return FALSE;
1702 } else {
1703 $p = trim($pid_enc);
1704 $e = trim($pid_enc);
1705 $plen = strlen($p);
1706 $elen = strlen($e);
1709 $srchtype = ($searchtype == 'encounter') ? 'enc' : 'pid';
1710 $ponly = ($srchtype == 'pid') ? true : false;
1711 $eonly = ($srchtype == 'enc') ? true : false;
1713 foreach ( $ar_segments as $segtxt ) {
1715 //$seg = explode($e_delim, $segtxt);
1716 $clm_pos++;
1718 if (substr($segtxt, 0, 3) == "CLP") {
1719 $seg = explode($e_delim, $segtxt);
1720 $idstr = $seg[1];
1721 // we are at the next claim following the one we want
1722 if ($clm_found && $test_pos != $clm_pos) {
1723 $ar_clm_pos[$clm_idx]['count'] = $clm_pos - $ar_clm_pos[$clm_idx]['start'];
1724 $clm_idx++;
1725 $clm_found = FALSE;
1727 // we are looking for a match
1728 if ($ponly && substr($idstr, 0, $plen) == $p ) {
1729 $idar = csv_pid_enctr_parse($idstr);
1730 if ($idar['pid'] == $p) {
1731 $ar_clm_pos[$clm_idx]['start'] = $clm_pos;
1732 $clm_found = TRUE;
1733 $test_pos = $clm_pos;
1736 if ($eonly && substr($idstr, -$elen) == $e ) {
1737 $ar_clm_pos[$clm_idx]['start'] = $clm_pos;
1738 $idar = csv_pid_enctr_parse($idstr);
1739 if ($idar['enctr'] == $e) {
1740 $clm_found = TRUE;
1741 $test_pos = $clm_pos;
1745 // we are at an ending segment
1746 if ($clm_found && strpos($end_str, substr($segtxt, 0, 3)) ) {
1747 // if claim is found
1748 if ($test_pos != $clm_pos && isset($ar_clm_pos[$clm_idx]['start']) ) {
1749 $ar_clm_pos[$clm_idx]['count'] = $clm_pos - $ar_clm_pos[$clm_idx]['start'];
1750 $clm_idx++;
1751 $clm_found = FALSE;
1756 return $ar_clm_pos;
1761 * Generates an HTML table Remittance Advice of the contents of the 835 file
1763 * @uses csv_verify_file()
1764 * @uses csv_x12_segments()
1765 * @uses ibr_era_claim_slice_pos()
1766 * @uses ibr_era_st_slice_pos()
1767 * @uses ibr_era_claim_vals()
1768 * @uses ibr_era_claim_html()
1770 * @param string $file_path the full path to the 835 .era file to be processed
1771 * @param int $trn_trace optional trace number
1772 * @param string $pid_enctr optional claim ID number CLM01
1773 * @param string $searchtype optional search ALL, Trace, Pid, Encounter
1774 * @param string $fname actual file name, used with tmp/file names
1775 * @return string html page
1777 function ibr_era_html_page ( $file_path, $trn_trace=0, $pid_enctr=0, $searchtype='ALL', $fname='835 Remittance Advice') {
1779 // divide the era x12 835 file into slices for transaction info and claims info
1781 $ar_eob_str = "";
1782 $is_found = FALSE;
1783 $ar_era_segments = "";
1784 $srchstr = "";
1786 $f_path = csv_verify_file( $file_path, "era");
1788 if (!$f_path) {
1789 csv_edihist_log("ibr_era_html_page: failed verification $file_path");
1790 return "ibr_era_html_page: failed verification for $file_path <br />" . PHP_EOL;
1791 } else {
1792 $fn = basename($f_path);
1793 $ar_era_segments = csv_x12_segments($f_path, "era", FALSE);
1794 if (!is_array($ar_era_segments) || !count($ar_era_segments['segments']) > 0 ) {
1795 return "ibr_era_html_page: failed to get segments for $f_path <br />" . PHP_EOL;
1799 if (strpos(strtolower($searchtype), 'enc') !== false) {
1800 $srch = 'encounter';
1801 } elseif (strpos(strtolower($searchtype), 'pid') !== false) {
1802 $srch = 'pid';
1803 } elseif (strpos(strtolower($searchtype), 'tra') !== false) {
1804 $srch = 'trace';
1805 } else {
1806 $srch = 'all';
1809 $ar_segs = $ar_era_segments['segments'];
1810 $comp_d = $ar_era_segments['delimiters']['s'];
1811 $elem_d = $ar_era_segments['delimiters']['e'];
1813 // select the kind of information we want claim, ST or file
1814 if ( ($srch == 'pid' || $srch == 'encounter') && $pid_enctr) {
1815 // we are looking for a specific claim
1816 $srchstr = ($srch == 'pid') ? "PtID: $pid_enctr" : '';
1817 $srchstr = ($srch == 'encounter') ? "Enctr: $pid_enctr" : $srchstr;
1819 $ar_clp = array();
1820 $ar_clp_slice = ibr_era_claim_slice_pos ($ar_segs, $pid_enctr, $elem_d, $srch);
1822 if ( !empty($ar_clp_slice) ) {
1823 foreach($ar_clp_slice as $cs) {
1824 // because an encounter can be reported more than once in an era file
1825 if (count($cs) == 2) {
1826 // clp_segs is an array of the CLP segments block for the claim (loop 2100 and 2110)
1827 // we simply slice out the claim segments and append each segment to $ar_clp
1828 $clp_segs = array_slice($ar_segs, $cs['start'], $cs['count']);
1829 foreach($clp_segs as $clp) {
1830 $ar_clp[] = $clp;
1834 } else {
1835 // no segments found for claim
1836 $ar_eob_str = "Claim $pid_enctr not found in $fn";
1837 csv_edihist_log("ibr_era_html_page: Claim $pid_enctr not found in $fn");
1839 // segments were found, so use the segments in $ar_clp
1840 if (!empty($ar_clp) ) {
1841 // ar_clp will just be an array of segments for the claim(s) ar_clp[0]=CLP* ... etc
1842 $ar_eob_vals = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d);
1844 $ar_eob_str .= ibr_era_claim_html ($ar_eob_vals, $fname, $srchstr );
1846 } else {
1847 $ar_eob_str = "Claim $srchstr not found in $fn";
1848 csv_edihist_log("ibr_era_html_page: Claim $srchstr not found in $fn");
1850 } else {
1851 // either the entire era file or a specific transaction
1852 // gives us the extents if the ST-SE envelope -- one per check
1853 $ar_st_slices = ibr_era_st_slice_pos ($ar_segs, $elem_d);
1855 if (!$trn_trace) {
1856 // if the entire file is requested, then trn_trace should be 0 , i.e. not given
1857 foreach ($ar_st_slices as $st) {
1859 $ar_clp = array_slice($ar_segs, $st[0], $st[1]);
1860 $ar_eob_vals = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d );
1861 $ar_eob_str .= ibr_era_claim_html ($ar_eob_vals, $fname, "ALL Items" );
1863 csv_edihist_log("ibr_era_html_page: era HTML output for $file_path");
1865 } else {
1866 // a specific transaction trace is requested
1867 foreach ($ar_st_slices as $st) {
1868 if ($st[2] == $trn_trace) {
1870 $is_found = TRUE;
1871 $ar_clp = array_slice($ar_segs, $st[0], $st[1]);
1872 $ar_eob_vals = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d);
1873 $ar_eob_str .= ibr_era_claim_html ($ar_eob_vals, $fname, "Trace: $trn_trace" );
1875 csv_edihist_log("ibr_era_html_page: HTML output for $trn_trace in $fn");
1877 break;
1878 } else {
1879 // there may be trace numbers in ar_st_slices that do not match the requested one
1880 continue;
1882 } // end foreach ($ar_st_slices as $st)
1883 if (!$is_found) {
1884 // error -- trace number was not found in era file
1885 csv_edihist_log("ibr_era_html_page: trace $trn_trace not found in era file $fn TMP: $file_path");
1886 $ar_eob_str = "<p>Trace $trn_trace not found in $fn TMP: $file_path.</p>";
1890 if (!$ar_eob_str) {
1891 $ar_eob_str = "<p>ibr_era_html_page: failed to return anything for Trace $trn_trace Claim $claim_id in $file_path</p>";
1892 csv_edihist_log("ibr_era_html_page: failed to return anything for Trace $trn_trace Claim $claim_id in $file_path");
1894 return $ar_eob_str;
1898 * function ibr_era_data_array() returns the array created in function ibr_era_claim_vals()
1900 * @uses csv_x12_segments()
1901 * @uses ibr_era_claim_slice_pos()
1902 * @uses ibr_era_st_slice_pos()
1903 * @uses ibr_era_claim_vals()
1905 * @param string $file_path full path to x12 835 era file
1906 * @param string $trn_trace trace number for deposit transaction
1907 * @param string $pid the pid, patient account identifier
1908 * @param string $enctr the encounter number
1909 * @return array
1911 function ibr_era_data_array ($file_path, $trn_trace=0, $pid=0, $enctr=0) {
1912 // simply run the file through the function ibr_era_claim_vals ( $ar_st_slice )
1913 // and return the multi-dimensional array
1915 // a duplicate of the ibr_era_html_page() html page function
1916 // without the html output
1917 // get segments, path, and delimiters
1918 $ar_era_segments = csv_x12_segments($file_path, "era", FALSE);
1919 if (!is_array($ar_era_segments) || count($ar_era_segments['segments']) == 0 ) {
1920 csv_edihist_log("ibr_era_data_array: no segments for $file_path");
1921 return FALSE;
1923 $fname = basename($file_path);
1925 $ar_segs = $ar_era_segments['segments'];
1926 $comp_d = $ar_era_segments['delimiters']['s'];
1927 $elem_d = $ar_era_segments['delimiters']['e'];
1929 // select the kind of information we want claim, ST or file
1930 if ($pid || $enctr) {
1931 // we are looking for a specific claim
1932 $ar_clp = array();
1933 $ar_clp_slice = ibr_era_claim_slice_pos ($ar_segs, $pid, $enctr, $elem_d);
1935 if ( !empty($ar_clp_slice) ) {
1936 foreach($ar_clp_slice as $cs) {
1937 // because an encounter can be reported more than once in an era file
1939 if (count($cs) == 2) {
1940 // clp_segs is an array of the CLP segments block for the claim (loop 2100 and 2110)
1941 $clp_segs = array_slice($ar_segs, $cs['start'], $cs['count']);
1942 foreach($clp_segs as $clp) {
1943 $ar_clp[] = $clp;
1947 } else {
1948 csv_edihist_log("ibr_era_data_array: Claim $claim_id not found in $fname");
1949 return FALSE;
1951 // segments for the claim were found
1952 if (!empty($ar_clp) ) {
1953 // ar_clp will just be an array of segments for the claim(s) ar_clp[0]=CLP* ... etc
1954 $ar_eob_vals = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d);
1956 } else {
1957 csv_edihist_log("ibr_era_data_array: Claim $claim_id not found in $fname");
1958 return FALSE;
1960 } else {
1961 // either the entire era file or a specific transaction
1962 // gives us the extents if the ST-SE envelope -- one per check
1963 $ar_st_slices = ibr_era_st_slice_pos ($ar_segs, $elem_d);
1965 $ar_eob_vals = array();
1967 if (!$trn_trace) {
1968 // if the entire file is requested, then trn_trace should be 0 , i.e. not given
1970 foreach ($ar_st_slices as $st) {
1972 $ar_clp = array_slice($ar_segs, $st[0], $st[1]);
1973 $ar_eob_vals[] = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d );
1975 csv_edihist_log("ibr_era_data_array: array output for $fname");
1977 } else {
1978 // a specific transaction trace is requested
1979 foreach ($ar_st_slices as $st) {
1980 if ($st[2] == $trn_trace) {
1982 $is_found = TRUE;
1983 $ar_clp = array_slice($ar_segs, $st[0], $st[1]);
1984 $ar_eob_vals[] = ibr_era_claim_vals($ar_clp, $elem_d, $comp_d);
1986 csv_edihist_log("ibr_era_data_array: array output for $trn_trace in $fname");
1988 break;
1989 } else {
1990 // there may be trace numbers in ar_st_slices that do not match the requested one
1991 continue;
1993 } // end foreach ($ar_st_slices as $st)
1994 if (!$is_found) {
1995 // error -- trace number was not found in era file
1996 csv_edihist_log("ibr_era_data_array: trace $trn_trace not found in era file $fname");
1997 return FALSE;
2001 if ( !isset($ar_eob_vals) || empty($ar_eob_vals) ) {
2002 csv_edihist_log("ibr_era_data_array: failed to generate array for params: File: $fname Trace $trn_trace Claim $claim_id");
2003 return FALSE;
2005 return $ar_eob_vals;
2009 /* =================================
2010 * CSV records functions
2011 * these retrieve a list of new files (not in the .csv file table)
2012 * and create arrays of data for writing to the .csv files
2013 * also, a function to find which file contains a transaction trace
2017 * Parse x12 835 segments for selected data for csv files
2019 * This is like a summary of the information. the returned array has two keys
2020 * <pre>
2021 * $ar_csv[$st_ct]['file']
2022 * 'mtime' 'fname' 'trace' 'payer' claimcount rej_count
2023 * $ar_csv[$st_ct]['claim'][$clp_ct]
2024 * 'name''svc_date''pid''enctr''status''fee''pmt''pt_resp''trace''erafile''claim_id''payer'
2025 * </pre>
2026 * @param array $era_segments the segments array from ibr_era_process_new()
2027 * @return array described above
2029 function ibr_era_csv_file_data ( $era_segments ) {
2031 $era_dir = dirname($era_segments['path']);
2032 $era_fname = basename($era_segments['path']);
2033 $ar_era_segments = $era_segments['segments'];
2034 $elem_d = $era_segments['delimiters']['e'];
2037 $era_mtime = date ("Ymd", filemtime($era_segments['path']));
2039 $denied = 0;
2040 $has_ts3 = FALSE;
2042 $ar_csv = array();
2043 $C = -1; // use for CLP count index
2044 $S = -1; // use for ST count index
2045 $st_seg_ct = 0;
2047 foreach($ar_era_segments as $segtxt) {
2049 $seg = explode($elem_d, $segtxt);
2051 $st_seg_ct++;
2052 // check for loops and set counters
2053 if ($seg[0] == "ST") {
2054 $loopid = "0";
2055 $st_seg_ct = 1;
2056 $S++;
2057 $C = -1;
2058 //$ar_csv[$S]['claim'] = array();
2059 //$ar_csv[$S]['file'] = array();
2060 $denied = 0;
2063 if ($seg[0] == "CLP") {
2064 $loopid = "2100";
2065 $C++;
2068 if ($seg[0] == "SVC") { $loopid = "2110"; }
2069 if ($seg[0] == "PLB") { $loopid = "0"; $C++; }
2070 if ($seg[0] == "SE") { $loopid = "0"; }
2072 // now look at segments for data
2073 if ($seg[0] == "GS") {
2074 $gs_date = $seg[4]; // functional group creation date -- required element
2075 continue;
2077 if ($seg[0] == "BPR") {
2078 $pmt_date = $seg[16]; // settlement date per payer -- required element
2079 continue;
2082 if ($seg[0] == "TRN") {
2083 $trn_trace = strval($seg[2]); // trace number/check number -- required element
2084 continue;
2087 if ($seg[0] == "N1" && $seg[1] == "PR") {
2088 $payer_name = $seg[2]; // payer name -- required element
2089 if ( isset($seg[3]) && $seg[3] == "XV" ) { $payer_id = $seg[4]; } // national plan id payer id -- situational
2090 continue;
2093 if ($seg[0] == "N1" && $seg[1] == "PE") {
2094 $prov_name = $seg[2]; // provider name -- required element
2095 continue;
2097 if ($seg[0] == "TS3" && $trn_ct > -1 ) {
2098 // number of claims is in TS304
2099 $ar_csv[$S]['file']['claims'] = $seg[4]; // 'claims'
2100 $has_ts3 = TRUE;
2103 // OK, now we are getting into the claim remittance advice
2105 if ($seg[0] == "CLP") {
2107 $ar_csv[$S]['claim'][$C]['fee'] = sprintf("%01.02f", $seg[3]);
2109 //$inv_split = preg_split('/\D/', $seg[1], 2, PREG_SPLIT_NO_EMPTY);
2110 $inv_split = csv_pid_enctr_parse($seg[1]);
2112 $ar_csv[$S]['claim'][$C]['pid'] = $inv_split['pid']; // 'pid'
2113 $ar_csv[$S]['claim'][$C]['enctr'] = $inv_split['enctr']; // 'enctr'
2115 $ar_csv[$S]['claim'][$C]['clm01'] = $seg[1];
2117 $ar_csv[$S]['claim'][$C]['status'] = $seg[2];
2118 // increment the denied count if status is 4
2119 if($seg[2]=='4' || $seg[2]=='22' || $seg[2]=='23') { $denied++; }
2121 $ar_csv[$S]['claim'][$C]['status'] = $seg[2]; // 'status'
2123 $ar_csv[$S]['claim'][$C]['fee'] = sprintf("%01.02f", $seg[3]); // 'fee'
2124 $ar_csv[$S]['claim'][$C]['pmt'] = sprintf("%01.02f", $seg[4]); // 'pmt'
2125 if ( isset($seg[4]) ) {
2126 $ar_csv[$S]['claim'][$C]['pt_resp'] = sprintf("%01.02f", $seg[5]); // 'pt_resp'
2127 } else {
2128 $ar_csv[$S]['claim'][$C]['pt_resp'] = '';
2131 if ( isset($seg[7]) ) {
2132 $ar_csv[$S]['claim'][$C]['claim_id'] = trim($seg[7]); //'claim_id'
2133 } else {
2134 $ar_csv[$S]['claim'][$C]['claim_id'] = '';
2136 //['clm01']['status']['fee']['pmt']['pt_resp']['trace']['payer']['erafile']['svc_date']['name']
2138 $ar_csv[$S]['claim'][$C]['trace'] = $trn_trace;
2139 $ar_csv[$S]['claim'][$C]['payer'] = $payer_name;
2140 $ar_csv[$S]['claim'][$C]['erafile'] = $era_fname; //'erafile'
2142 $lastn = '';
2143 $firstn = '';
2144 $midn = '';
2145 continue;
2148 if ($seg[0] == "DTM") {
2149 // get the from-to dates, but replace with the service date
2150 // 232 service from date 233 service to date
2151 // 036 coverage expiration date -- claim denied due to coverage lapse -- look to era rendering
2152 if ($loopid == "2100" && $seg[1] == "232") { $ar_csv[$S]['claim'][$C]['svc_date'] = $seg[2]; }
2153 if ($loopid == "2100" && $seg[1] == "233") { $ar_csv[$S]['claim'][$C]['svc_date'] = $seg[2]; }
2154 // 472 service date
2155 if ($loopid == "2110" && $seg[1] == "472") { $ar_csv[$S]['claim'][$C]['svc_date'] = $seg[2]; }
2157 continue;
2160 if ($seg[0] == "NM1" && $loopid == "2100") {
2161 // QC patient, IL insured, 74 corrected, 82 rendering provider, TT crossover carrier, PR payer, GB other insured
2162 // Try to catch corrected names by inserting CC: before the corrected part
2163 // (seems to be Medicare practice to only give the corrected part in the NM1*74 segment, however, they have it wrong more often than we do)
2164 // (also, other payers are not consistent or give the full name repeated)
2165 if ($seg[1] == "QC") {
2166 $lastn = $seg[3];
2167 $firstn = ', ' . $seg[4];
2168 $midn = (isset($seg[5]) && strlen($seg[5])) ? ', ' . $seg[5]: "";
2170 $ar_csv[$S]['claim'][$C]['name'] = $lastn . $firstn . $midn;
2172 /* **** correction infoprmation is in RA and summary
2173 if ( $seg[1] == "74" ) {
2174 if (strlen($seg[3]) && $lastn != $seg[3]) { $lastn = 'CC:' . $seg[3]; }
2175 if (strlen($seg[4]) && $firstn != $seg[4]) { $firstn = ', CC:' . $seg[4]; }
2176 if (strlen($seg[5]) && $midn != $seg[5]) { $midn = ', CC:' . $seg[5]; }
2178 $ar_csv[$S]['claim'][$C]['name'] = $lastn . $firstn . $midn;
2180 * ***/
2181 continue;
2184 //claims_era.csv array('name', 'svc_date', 'pid', 'enctr', 'status', 'fee', 'pmt', 'pt_resp', 'trace', 'erafile', 'claim_id', 'payer');
2185 //['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
2186 if ($seg[0] == "PLB") {
2187 // payment adjustments to transaction -- just fit it into claim array
2188 // break down PLB03 -- not used
2189 if (strpos($seg[3], $comp_d)) {
2190 $plb03 = explode($comp_d, $seg[3]);
2191 $ptxt_ar = ibr_era_code_text('PLB', $plb03[0]);
2192 $ptxt = $ptxt_ar[1];
2193 $plbid = $plb03[1];
2195 $ar_csv[$S]['claim'][$C]['name'] = "PLB";
2196 $ar_csv[$S]['claim'][$C]['svc_date'] = $seg[2]; // date
2197 $ar_csv[$S]['claim'][$C]['clm01'] = $seg[1]; // provider id
2198 $ar_csv[$S]['claim'][$C]['status'] = ($seg[4] <= 0) ? "C" : "D"; // negative amount is a payment, positive is reduction
2199 $ar_csv[$S]['claim'][$C]['fee'] = $seg[3]; // adjustment identifier
2200 $ar_csv[$S]['claim'][$C]['pmt'] = sprintf("%01.02f", $seg[4]); // monetary amount
2201 $ar_csv[$S]['claim'][$C]['pt_resp'] = isset($seg[5]) ? $seg[5] : ""; // adjustment identifier
2202 $ar_csv[$S]['claim'][$C]['claim_id'] = isset($seg[6]) ? sprintf("%01.02f", $seg[6]) : ""; // monetary amount
2203 $ar_csv[$S]['claim'][$C]['trace'] = $trn_trace;
2204 $ar_csv[$S]['claim'][$C]['payer'] = $payer_name;
2205 $ar_csv[$S]['claim'][$C]['erafile'] = $era_fname;
2207 continue;
2209 // OK, the claim is done. get the file information at the SE segment, end of transatcion set
2210 // $ar_csv['file'][$trn_ct]
2211 // 'mtime' 'fname' 'trace' 'payer' claims rej_count
2212 if ($seg[0] == "SE") {
2213 if ($seg[1] != $st_seg_ct) {
2214 csv_edihist_log ("ibr_era_csv_file_data: segment count mismatch $era_fname SE: {$seg[2]} count: $st_seg_ct");
2217 $ar_csv[$S]['file']['mtime'] = isset($pmt_date) ? $pmt_date : $gs_date;
2218 $ar_csv[$S]['file']['fname'] = $era_fname;
2219 $ar_csv[$S]['file']['trace'] = $trn_trace;
2220 $ar_csv[$S]['file']['payer'] = $payer_name;
2221 $ar_csv[$S]['file']['payer_id'] = isset($payer_id) ? $payer_id : '';
2222 $ar_csv[$S]['file']['claims'] = sprintf('%u', $C+1);
2223 $ar_csv[$S]['file']['rej_count'] = sprintf('%u', $denied);
2224 continue;
2228 } // end foreach($segments array)
2230 return $ar_csv;
2231 } // end function ibr_era_file_csv
2235 * Write the csv data to the csv files
2237 * @uses csv_write_record()
2238 * @see ibr_era_csv_file_data()
2239 * @param array $file_csv_array -- array produced by ibr_era_csv_file_data()
2240 * @return array characters written to files.csv and claims.csv
2242 function ibr_era_csv_write_data ( $file_csv_array, &$str_err ) {
2245 // here we order the data for the csv tables
2246 // expect $file_csv_array[i]['file'] and $file_csv_array[i]['claim'][j]
2248 $csvf = array();
2249 $csvc = array();
2250 $chrsf = 0;
2251 $chrsc = 0;
2253 foreach($file_csv_array as $file_data) {
2254 //$csv_hd_ar['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
2255 if (array_key_exists('file', $file_data) && count($file_data['file']) > 0 ) {
2257 $csvf[0] = $file_data['file']['mtime'];
2258 $csvf[1] = $file_data['file']['fname'];
2259 $csvf[2] = $file_data['file']['trace'];
2260 $csvf[3] = $file_data['file']['claims'];
2261 $csvf[4] = $file_data['file']['rej_count'];
2262 $csvf[5] = $file_data['file']['payer'];
2264 $ar_eraf[] = $csvf;
2265 // if an error in writing, we will have the file names
2266 $str_err .= $arw['file_name'].PHP_EOL;
2268 //['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
2270 if (array_key_exists('claim', $file_data) && count($file_data['claim']) > 0 ) {
2272 foreach($file_data['claim'] as $clm) {
2274 $csvc[0] = $clm['name'];
2275 $csvc[1] = $clm['svc_date'];
2276 $csvc[2] = $clm['clm01'];
2277 $csvc[3] = $clm['status'];
2278 $csvc[4] = $clm['trace'];
2279 $csvc[5] = $clm['erafile'];
2280 $csvc[6] = $clm['claim_id'];
2281 $csvc[7] = $clm['pmt'];
2282 $csvc[8] = $clm['pt_resp'];
2283 $csvc[9] = $clm['payer'];
2285 $ar_erac[] = $csvc;
2290 $chrsf += csv_write_record($ar_eraf, 'era', 'file');
2291 $chrsc += csv_write_record($ar_erac, 'era', 'claim');
2293 return array($chrsf, $chrsc);
2297 /**
2298 * generates html for the uploaded new files output
2300 * @param array $trn_csv_array -- the array of csv data generated in ibr_era_csv_file_data()
2301 * @param bool $err_only -- whether to report claim information only in denied case
2303 function ibr_era_csv_files_html ($trn_csv_array, $err_only=TRUE ) {
2305 $dtl = ($err_only) ? "Errors only" : "All included claims";
2307 $htm_tplcap = "<table class=\"eracsv\" cols=6><caption>ERA Files Summary $dtl</caption>";
2308 $htm_tpl = "<table class=\"eracsv\" cols=6>";
2309 $htm_hdrf = "<thead>
2310 <tr>
2311 <th>File Time</th><th>File Name</th><th>Trace</th>
2312 <th>Payer</th><th>Claims</th><th>Denied</th>
2313 </tr>
2314 </thead>
2315 <tbody>";
2317 // claims detail -- expected to be only for denied claims
2318 $htm_hdrc = "<table class=\"eracsv\" cols=6>
2319 <thead>
2320 <tr class=\"clperr\">
2321 <th>Name</th><th>Svc Date</th><th>Account</th><th>Status</th>
2322 <th>Fee|Pmt|PtResp</th><th>Claim ID</th>
2323 </tr>
2324 </thead>";
2326 $out_html = $htm_tplcap.$htm_hdrf;
2328 $idxc = 0; // if denied claims are output for $err_only
2329 $haserr = 0;
2330 // now create the table body
2331 // $trn_csv_array structure is $trn_csv_array[f#][S#]['file'] $trn_csv_array[f#][S#]['claim'][C#]
2332 // so one 'file' and multiple 'claim' per ST-SE block (the S#)
2333 foreach($trn_csv_array as $fdata) {
2334 // one fdata for each file processed
2335 // one ardata for each transaction trace
2336 $idx = 0;
2337 $idxc = 0;
2338 foreach($fdata as $ardata) {
2339 $file_html = "";
2340 $clm_str = "";
2342 if (array_key_exists('file', $ardata) && count($ardata['file']) > 0 ) {
2343 // $ar_csv_data['file'] 'mtime' 'fname' 'trace' 'payer' claims rej_count
2345 // if a denied claim occurred in the preceding iteration, insert the table heading
2346 if ($haserr) {
2347 $out_html .= $htm_tpl.$htm_hdrf;
2348 $haserr = 0;
2350 $bgc = ($idx % 2 == 1 ) ? 'odd' : 'even';
2351 $idx++;
2353 $file_html .= "<tr class='{$bgc}' >
2354 <td>{$ardata['file']['mtime']}</td>
2355 <td><a target='_blank' href='edi_history_main.php?fvkey={$ardata['file']['fname']}'>{$ardata['file']['fname']}</a></td>
2356 <td><a target='_blank' href='edi_history_main.php?erafn={$ardata['file']['fname']}&trace={$ardata['file']['trace']}'>{$ardata['file']['trace']}</a></td>
2357 <td>{$ardata['file']['payer']}</td>
2358 <td>{$ardata['file']['claims']}</td>
2359 <td>{$ardata['file']['rej_count']}</td>
2360 </tr>";
2363 if (array_key_exists('claim', $ardata) && count($ardata['claim']) > 0 ) {
2365 $haserr = 0;
2366 $clm_str = "";
2368 foreach ($ardata['claim'] as $clm) {
2369 // output err_only is when claim status is a rejected type or payment amount is 0
2370 if ($err_only && (!in_array($clm['status'], array('4', '22', '23')) && intval($clm['pmt']) > 0)) {
2371 continue;
2374 $haserr++;
2375 $bgclm = ($haserr % 2 == 1 ) ? 'fodd' : 'feven';
2376 //array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'claimID', 'File_835', 'Pmt', 'PtResp', 'Payer');
2377 //['clm01']['status']['fee']['pmt']['pt_resp']['trace']['payer']['erafile']['svc_date']['name']
2378 $clm_str .= "
2379 <tr class='{$bgclm}'>
2380 <td>{$clm['name']}</td>
2381 <td>{$clm['svc_date']}</td>
2382 <td>{$clm['clm01']}</td>
2383 <td>{$clm['status']} &nbsp;<a class='clmstatus' target='_blank' href='edi_history_main.php?erafn={$clm['erafile']}&pidenc={$clm['clm01']}&summary=yes'>S</a>&nbsp; <a target='_blank' href='edi_history_main.php?erafn={$clm['erafile']}&pidenc={$clm['clm01']}'>RA</a></td>
2384 <td>{$clm['fee']} | {$clm['pmt']} | {$clm['pt_resp']}</td>
2385 <td>{$clm['claim_id']}</td>
2386 </tr>";
2389 // we have run through the set 'file' and 'claim'
2391 $out_html .= $file_html;
2392 // if we have denied claims, insert the string
2393 if ($haserr) {
2394 $out_html .= $htm_hdrc;
2395 $out_html .= $clm_str;
2396 $out_html .= "</tbody></table>";
2399 } // end foreach($trn_csv_array as $ardata)
2401 //finish off the table
2402 $out_html .= "</tbody></table>";
2404 return $out_html;
2409 * Search claims_era.csv table and find patient ID or encounter and associated filename
2411 * will search for only the pid value or only encounter value
2413 * @uses csv_pid_enctr_parse()
2414 * @uses csv_parameters()
2415 * @param string $encounter the patient control (pid-encounter) number
2416 * @param string $srchtype default is 'encounter'
2417 * @return array [i](pid-enctr, trace, filename) possibly more than one
2419 function ibr_era_find_file_with_pid_enctr ($pid_enctr, $srchtype='encounter' ) {
2421 // return the pid_encounter, trace, and filename, but there may be more than one file, so return an array
2423 if (!$pid_enctr) {
2424 return "invalid encounter data<br />" . PHP_EOL;
2426 $enctr = trim($pid_enctr);
2427 preg_match('/\D/', $enctr, $match2, PREG_OFFSET_CAPTURE);
2428 if (count($match2)) {
2429 $idar = csv_pid_enctr_parse($enctr);
2430 if (is_array($idar) && count($idar)) {
2431 $p = strval($idar['pid']);
2432 $plen = strlen($p);
2433 $e = strval($idar['enctr']);
2434 $elen = strlen($e);
2435 } else {
2436 csv_edihist_log("ibr_era_find_file_with_pid_enctr: error parsing pid_encounter $pid_enctr");
2437 return FALSE;
2439 } else {
2440 $p = strval($enctr);
2441 $e = strval($enctr);
2442 $plen = strlen($p);
2443 $elen = strlen($e);
2445 $ret_ar = array();
2446 //array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'claimID', 'File_835', 'Pmt', 'PtResp', 'Payer');
2448 $params = csv_parameters('era');
2449 $fp = $params['claims_csv'];
2451 if (($fh1 = fopen($fp, "r")) !== FALSE) {
2452 if ($srchtype == 'encounter') {
2453 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
2454 // check for a match
2455 if (substr($data[2], -$elen) == $e) {
2456 // since e=123 will match 1123 and 123
2457 $peval = csv_pid_enctr_parse($data[2]);
2458 if (is_array($peval) && count($peval)) {
2459 if ($peval['enctr'] == $e) {
2460 $ret_ar[] = array($data[2], $data[4], $data[5]);
2465 } else {
2466 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
2467 // check for a match
2468 if (substr($data[2], 0, $plen) == $p) {
2469 // since p=123 will match 1123 and 123
2470 $peval = csv_pid_enctr_parse($data[2]);
2471 if (is_array($peval) && count($peval)) {
2472 if ($peval['pid'] == $p) {
2473 $ret_ar[] = array($data[2], $data[4], $data[5]);
2479 fclose($fh1);
2480 } else {
2481 csv_edihist_log("ibr_era_find_file_with_pid: failed to open file claims_era.csv");
2483 return $ret_ar;
2488 * copy the substring of text containing claim payment information for a claim
2490 * The filename must be found in the files.csv table, i.e. previously processed
2492 * @uses csv_verify_file()
2493 * @uses csv_x12_delimiters()
2494 * @param string $clp_clm_num the pid-encounter value
2495 * @param string $era_file the filename
2496 * @return string newline characters are added to each segment end
2498 function ibr_era_get_clp_text ( $clp_clm_num, $era_file, $html_out=true ) {
2499 // @param string $clp_clm_num -- CLP01 value, pid-encounter
2500 // @param string $era_file path to 835 file containing the $clp_clm_num
2501 // segment block CLP to CLP, SE, LX
2502 // get the substring of the era file containing the ST number
2504 $fp = csv_verify_file($era_file, "era");
2505 if (!$fp) {
2506 csv_edihist_log ("ibr_era_get_clp_text: failed to read $era_file");
2507 $str_clp .= "failed to read $era_file";
2508 return $str_clp;
2509 } else {
2511 $bstr = file_get_contents($fp);
2512 if (!$bstr) {
2513 csv_edihist_log ("ibr_era_get_clp_text: failed to get contents of $era_file");
2514 $str_clp .= "failed to read $era_file";
2515 return $str_clp;
2517 // get the delimiters
2518 $str_isa = substr($bstr, 0, 126);
2520 $ar_delim = csv_x12_delimiters($str_isa, "GS");
2521 $seg_delim = $ar_delim['t'];
2523 $seg_clp = "CLP*" . $clp_clm_num; // CLP segment that begins remittance detail
2525 $clp_pos = strpos($bstr, $seg_clp, 0);
2526 // break it off if $st_pos is not found
2527 if ( $clp_pos == FALSE ) {
2528 csv_edihist_log ("Error: $clp_clm_num not found in $era_file");
2529 $str_clp .= "Error: $clp_clm_num not found in $era_file";
2530 return $str_clp;
2533 $seg_se = "SE*";
2534 $seg_lx = "LX*";
2535 $seg_clpx = "CLP*";
2536 // see if we can find a closing segment
2537 $pos_ar[] = strpos($bstr, $seg_se, $clp_pos); // $se_pos =
2538 $pos_ar[] = strpos($bstr, $seg_lx, $clp_pos); // $lx_pos =
2539 $pos_ar[] = strpos($bstr, $seg_clpx, $clp_pos + 10); //$clpx_pos =
2541 // choose the best closing position, closest to $clp_pos
2542 asort($pos_ar);
2543 foreach ( $pos_ar as $p ) {
2544 // echo "clp_pos $clp_pos pos_ar $p". PHP_EOL;
2545 if ( $p > $clp_pos ) {
2546 $end_pos = $p;
2547 break;
2551 $str_clp = substr($bstr, $clp_pos, $end_pos-$clp_pos);
2553 // add newlines so each segment is on its own line
2554 if ( strpos($str_clp, $seg_delim.PHP_EOL) ) {
2555 // if true, assume the file has newlines ending segments
2556 } else {
2557 // we could get fancy and make an html table or add line numbers
2558 $str_clp = str_replace($seg_delim, $seg_delim.PHP_EOL, $str_clp);
2562 if ($html_out) {
2563 $str_html = "<div class=\"filetext\">";
2564 $str_html .= "<p>$pe &nbsp;&nbsp;".basename($fp)." </p>".PHP_EOL."<pre><code>";
2565 $str_html .= $str_clp;
2566 $str_html .= PHP_EOL."</code></pre>".PHP_EOL."</div>".PHP_EOL;
2567 return $str_html;
2568 } else {
2569 return $str_clp;
2575 * Process new x12 835 files so data is written to csv tables and html output is created
2577 * This is a multi-operational function, calling other functions in this script and in
2578 * csv_record_include.php to list newly uploaded files, parse them for csv data, write
2579 * to the csv data files, and generate html output.
2581 * @uses csv_newfile_list()
2582 * @uses csv_x12_segments()
2583 * @uses ibr_era_csv_file_data()
2584 * @uses ibr_era_csv_write_data()
2585 * @uses ibr_era_csv_files_html()
2587 * @param array $file_array not required, array of new filenames, generated by csv_newfile_list()
2588 * @param boolean $html_out whether to generate html files summary table
2589 * @param boolean $err_only whether to generate html for denied claims
2590 * @return string html output
2592 function ibr_era_process_new ($file_array=NULL, $html_out=TRUE, $err_only=TRUE) {
2594 // get new files from directory and write data to the .csv files
2596 // create html table if $html_out is true
2597 // 'mtime', 'dirname', 'fname', 'trace' 'payer' 'claims'
2598 $html_str = "";
2599 $ar_csv_data = array();
2601 $err_ref = "";
2603 if ( is_array($file_array) && count($file_array) ) {
2604 $ar_newfiles = $file_array;
2605 } else {
2606 $ar_newfiles = csv_newfile_list("era");
2608 // cut it off if there are no new files
2609 if ( count($ar_newfiles) == 0) {
2610 // no new files
2611 if ($html_out) {
2612 // return a message
2613 $html_str = "<p>ibr_era_process_new: No new ERA 835 files found</p>";
2614 return $html_str;
2615 } else {
2616 return FALSE;
2618 } else {
2619 $fcount = count($ar_newfiles);
2621 // we have new files
2622 foreach ($ar_newfiles as $f_era) {
2624 $era_segments = csv_x12_segments($f_era, "era", FALSE);
2626 if (is_array($era_segments) && count($era_segments['segments']) > 0 ) {
2627 $ar_csv_data[] = ibr_era_csv_file_data ( $era_segments );
2629 } else {
2630 csv_edihist_log("ibr_era_process_new: did not get segments for " . basename($full_path));
2631 $htm_hdrc .= "<p>did not get segments for $f_era </p>" . PHP_EOL;
2632 continue;
2635 // now send the data to be written to csv table and html output
2636 foreach($ar_csv_data as $eradata) {
2637 $chars = ibr_era_csv_write_data($eradata, $err_ref);
2638 //$html_out
2639 $html_str .= isset($htm_hdrc) ? $htm_hdrc : "";
2641 if (is_array($chars) ) {
2642 if ($chars[0]) {
2643 csv_edihist_log("ibr_era_process_new: {$chars[0]} characters written to files_era.csv");
2644 } else {
2645 csv_edihist_log("ibr_era_process_new: error writing csv data for files: " .PHP_EOL.$err_ref);
2646 $html_str .= "<p>ibr_era_process_new: error writing csv data</p>";
2648 if ($chars[1]) {
2649 csv_edihist_log("ibr_era_process_new: {$chars[1]} characters written to claims_era.csv");
2650 } else {
2651 csv_edihist_log("ibr_era_process_new: error writing csv data for claims ");
2652 $html_str .= "<p>ibr_era_process_new: error writing csv data</p>";
2657 if ($html_out) {
2658 $html_str .= ibr_era_csv_files_html($ar_csv_data, $err_only);
2659 } elseif ($chars[0] && $chars[1]) {
2660 $html_str .= "x12_835 ERA files: processed $fcount ERA files <br />".PHP_EOL;
2661 } else {
2662 $html_str .= "x12_835 ERA: error writing csv data <br />";
2664 return $html_str;