Cleaned up the logout script
[openemr.git] / library / edihistory / ibr_997_read.php
blob15accdf14c8ac026ae38dff1ccf383aa99f97f6c
1 <?php
2 /**
3 * ibr_997_read.php
4 *
5 * Copyright 2012 Kevin McCormick, Longview, Texas
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 * <http://opensource.org/licenses/gpl-license.php>
22 * @author Kevin McCormick
23 * @link: http://www.open-emr.org
24 * @package OpenEMR
25 * @subpackage ediHistory
29 ////////////////////////////////////////////////////////////////
30 // a security measure to prevent direct web access to this file
31 // must be accessed through the main calling script ibr_history.php
32 // from admin at rune-city dot com; found in php manual
33 // if (!defined('SITE_IN')) die('Direct access not allowed!');
35 ///////////////////////////////////////////////////////////////
37 /**
38 * error code values in AK or IK segments
40 * @param string the segment field ak304, ak403, ak501
41 * @param string the code
42 * @return string
44 function ibr_997_code_text ( $ak_seg_field, $ak_code ) {
45 // the Availity 997 file has codes with certain errors
46 // which correspond to the messages in these arrays
48 $aktext['ak304'] = array(
49 '1' => 'Unrecognized segment ID',
50 '2' => 'Unexpected segment',
51 '3' => 'Mandatory segment missing',
52 '4' => 'Loop occurs over maximum times',
53 '5' => 'Segment exceeds maximum use',
54 '6' => 'Segment not in defined transaction set',
55 '7' => 'Segment not in proper sequence',
56 '8' => 'Segment has field errors',
57 'I4' => 'Segment not used in implementation',
58 'I6' => 'Implementation dependent segment missing',
59 'I7' => 'Implementation loop occurs less than minimum times',
60 'I8' => 'Implementation segment below minimum use',
61 'I9' => 'Implementation dependent not used segment present'
64 $aktext['ak403'] = array(
65 '1' => 'Mandatory data element missing',
66 '2' => 'Conditional required data element missing',
67 '3' => 'Too many data elements',
68 '4' => 'Data element too short',
69 '5' => 'Data element too long',
70 '6' => 'Invalid character in data element',
71 '7' => 'Invalid code value',
72 '8' => 'Invalid date',
73 '9' => 'Invalid time',
74 '10' => 'Exclusion condition violated - segment includes two values that should not occur together',
75 '12' => 'Too many repetitions',
76 '13' => 'Too many components',
77 'I10' => 'Implementation not used',
78 'I11' => 'Implementation too few repetitions',
79 'I12' => 'Implementation pattern match failure',
80 'I13' => 'Implementation dependent not used data element present',
81 'I6' => 'Code value not used in implimentation',
82 'I9' => 'Implementation dependent data element missing'
85 $aktext['ak501'] = array(
86 'A' => 'Accepted advised',
87 'E' => 'Accepted, but errors were noted',
88 'M' => 'Rejected, message authentication code (MAC) failed',
89 'P' => 'Partially Accepted',
90 'R' => 'Rejected advised',
91 'W' => 'Rejected, assurance failed validity tests',
92 'X' => 'Rejected, content after decryption could not be analyzed'
95 $aktext['ak502'] = array(
96 '1' => 'Functional Group not supported',
97 '2' => 'Functional Group Version not supported',
98 '3' => 'Functional Group Trailer missing',
99 '4' => 'Group Control Number in the Functional Group Header and Trailer do not agree',
100 '5' => 'Number of included Transaction Sets does not match actual count',
101 '6' => 'Group Control Number violates syntax',
102 '10' => 'Authentication Key Name unknown',
103 '11' => 'Encryption Key Name unknown',
104 '12' => 'Requested Service (Authentication or Encryption) not available',
105 '13' => 'Unknown security recipient',
106 '14' => 'Unknown security originator',
107 '15' => 'Syntax error in decrypted text',
108 '16' => 'Security not supported',
109 '17' => 'Incorrect message length (Encryption only)',
110 '18' => 'Message authentication code failed',
111 '19' => 'Functional Group Control Number not unique within Interchange',
112 '23' => 'S3E Security End Segment missing for S3S Security Start Segment',
113 '24' => 'S3S Security Start Segment missing for S3E Security End Segment',
114 '25' => 'S4E Security End Segment missing for S4S Security Start Segment',
115 '26' => 'S4S Security Start Segment missing for S4E Security End Segment',
116 'I6' => 'Implementation dependent segment missing',
119 if ( array_key_exists($ak_seg_field, $aktext) && array_key_exists($ak_code, $aktext[$ak_seg_field]) ){
120 return $aktext[$ak_seg_field][$ak_code];
121 } else {
122 return "";
124 } // end function ibr_997_code_text
128 * code values for TA1 segment
130 * @param string the code
131 * @return string
133 function ibr_997_ta1_code($code) {
134 // codes in TA1 segment elements 4 and 5, since codes are distinct form, all values in one array
136 $ta1code = array('A' => 'Interchange accepted with no errors.',
137 'R' => 'Interchange rejected because of errors. Sender must resubmit file.',
138 'E' => 'Interchange accepted, but errors are noted. Sender must not resubmit file.',
139 '000' => 'No error',
140 '001' => 'The Interchange Control Number in the header and trailer do not match. Use the value from the header in the acknowledgment.',
141 '002' => 'This Standard as noted in the Control Standards Identifier is not supported.',
142 '003' => 'This Version of the controls is not supported',
143 '004' => 'The Segment Terminator is invalid',
144 '005' => 'Invalid Interchange ID Qualifier for sender',
145 '006' => 'Invalid Interchange Sender ID',
146 '007' => 'Invalid Interchange ID Qualifier for receiver',
147 '008' => 'Invalid Interchange Receiver ID',
148 '009' => 'Unknown Interchange Receiver ID',
149 '010' => 'Invalid Authorization Information Qualifier value',
150 '011' => 'Invalid Authorization Information value',
151 '012' => 'Invalid Security Information Qualifier value',
152 '013' => 'Invalid Security Information value',
153 '014' => 'Invalid Interchange Date value',
154 '015' => 'Invalid Interchange Time value',
155 '016' => 'Invalid Interchange Standards Identifier value',
156 '017' => 'Invalid Interchange Version ID value',
157 '018' => 'Invalid Interchange Control Number',
158 '019' => 'Invalid Acknowledgment Requested value',
159 '020' => 'Invalid Test Indicator value',
160 '021' => 'Invalid Number of Included Group value',
161 '022' => 'Invalid control structure',
162 '023' => 'Improper (Premature) end-of-file (Transmission)',
163 '024' => 'Invalid Interchange Content (e.g., invalid GS Segment)',
164 '025' => 'Duplicate Interchange Control Number',
165 '026' => 'Invalid Data Element Separator',
166 '027' => 'Invalid Component Element Separator',
167 '028' => 'Invalid delivery date in Deferred Delivery Request',
168 '029' => 'Invalid delivery time in Deferred Delivery Request',
169 '030' => 'Invalid delivery time Code in Deferred Delivery Request',
170 '031' => 'Invalid grade of Service Code'
172 if ( array_key_exists($code, $ta1code) ) {
173 return $ta1code[$code];
174 } else {
175 return "Code $code not found in TA1 codes table. <br />";
181 * parse x12 997/999 file into an array
183 * <pre>
184 * return array['file']['key']
185 * ['f997_time']['f997_file']['f997_97t']['f997_ta1ctrl']
186 * ['f997_initver']['f997_clm_reject']['f997_clm_count']
187 * ['f997_clm_indc']['f997_batch']
188 * return array['claims']['key']
189 * ['clmr_status']['clmr_ftime']['clmr_st_num']['clmr_control_num']
190 * ['clmr_997file']['clmr_97T']['batch_file']['clmr_gs_num']
191 * ['clmr_errseg']['clmr_errtxt']['clmr_busunit']['clmr_errcopy']
192 * </pre>
194 * @todo refine data collection -- need experience to know what to keep
195 * @uses csv_file_by_controlnum()
196 * @param array $seg_ar -- the segment array produced by csv_x12_segments
197 * @return array
199 function ibr_997_parse($seg_ar) {
201 $ar_997_segments = $seg_ar['segments'];
202 $fname = basename($seg_ar['path']);
203 $elem_d = $seg_ar['delimiters']['e'];
204 $sub_d = $seg_ar['delimiters']['s'];
205 $rep_d = $seg_ar['delimiters']['r'];
207 $ar_var = array();
208 $ar_reject = array();
209 $trans_id = "";
210 $loopid = "";
212 $bt_file = "";
213 $ta1ctlnum = ""; // IMHO, 997/999 files are pretty worthless without TA1, but just in case
215 $ta1date = "";
216 $ta1time = "";
217 $ta1ack = "";
218 $ta1note = "";
220 $st_ct = 0; $idx = 0;
222 $ar_997file = array();
223 //$f97T_name = $fname;
224 if (substr($fname, -4) == ".997") { $f97T_name = str_replace ( ".997", ".97T", $fname); }
225 if (substr($fname, -4) == ".999") { $f97T_name = str_replace ( ".999", ".99T", $fname); }
227 foreach($ar_997_segments as $seg_997 ) {
229 $idx++;
230 $ar_seg = explode($elem_d, $seg_997);
232 // evaluate particular segments
233 if ( $ar_seg[0] == "ISA" ) {
234 // ISA segment -- get submitter ID, sender ID
235 $isasender = $ar_seg[6]; // AV09311993
236 $isareciever = $ar_seg[8]; // 030240928
237 $isadate = $ar_seg[9];
238 $isatime = $ar_seg[10];
239 $repseparator = ($ar_seg[11] == "U") ? "" : $ar_seg[11]; // 999 expect "^"
240 $isactlver = $ar_seg[12];
241 $isactlnum = $ar_seg[13]; // Must match IEA02
242 $sub_elem = $ar_seg[16];
244 continue;
247 if ( $ar_seg[0] == "TA1" ) {
248 // TA1 segment -- get the control number$ta1ctlnum$ta1date$ta1time$ta1ack$ta1note$bt_file
249 //$ta1ctlnum = strval($ar_seg[1]); // Interchange Control Number --> batch file ISA13
250 // all digits, possible leading zero, must be a string
251 $ta1ctlnum = strval($ar_seg[1]);
252 $ta1date = $ar_seg[2];
253 $ta1time = $ar_seg[3];
254 $ta1ack = $ar_seg[4];
255 if ($ar_seg[4] == "E") { $ta104msg = "Errors - correct individually"; }
256 if ($ar_seg[4] == "R") { $ta104msg = "Errors - correct and resubmit file"; }
257 $ta1note = $ar_seg[5];
258 if ($ar_seg[5] != "000") { $ta105msg = ibr_997_ta1_code($ar_seg[5]); }
259 //$ta1ack $ta1note$ta104msg$ta105msg
260 // get the batch file from function in batch_file_csv.php
261 //$bt_file = ibr_batch_find_file_with_controlnum($ta1ctlnum);
262 $bt_file = csv_file_by_controlnum('batch', $ta1ctlnum);
263 if (!$bt_file) { $bt_file = $ta1ctlnum; }
265 continue;
268 if ( $ar_seg[0] == "GS" ) {
269 $gsid = $ar_seg[1]; // FA or 999 Implementation Acknowledgement (5010A1)
270 $gssender = $ar_seg[2]; //
271 $gsreciever = $ar_seg[3]; //
272 $gsdate = $ar_seg[4];
273 $gstime = $ar_seg[5];
275 continue;
278 if ( $ar_seg[0] == "GE" ) {
279 $gecount = $ar_seg[1];
280 $gectlnum = $ar_seg[2];
281 if ($gecount != $st_ct) {
282 csv_edihist_log("ibr_997_parse: ST count mismatch $gecount $st_ct in $fname");
283 echo "ibr_997_parse: ST count mismatch $gecount $st_ct in $fname".PHP_EOL;
286 continue;
289 if ( $ar_seg[0] == "ST" ) {
290 $stversion = $ar_seg[1]; // 997 or 999
291 $stnum = $ar_seg[2]; // count of ST segments in this file
292 $st_ct++;
293 $idx = 1;
295 continue;
298 if ( $ar_seg[0] == "SE" ) {
299 $secount = $ar_seg[1]; // count of segments in the ST block
300 $senum = $ar_seg[2]; // should match $stnum or ST02
301 if ($secount != $idx) {
302 csv_edihist_log("ibr_997_parse: Segment count mismatch $idx $secount in $fname");
303 echo "ibr_997_parse: Segment count mismatch $idx $secount in $fname".PHP_EOL;
306 continue;
309 if ( $ar_seg[0] == "AK1" ) {
310 // beginning of acknowledgments
311 $ak1ver = $ar_seg[1]; // GS01 from batch, expect HC
312 $ak1gs06 = $ar_seg[2]; // AK102 = GS06 from batch, expect 1
314 $loopid = "0";
315 continue;
318 if ( $ar_seg[0] == "AK9" ) {
319 // end of acknowledgments
320 $ak9status = $ar_seg[1]; // A, R, or P, or E
321 $ak9batchct = $ar_seg[2]; // transaction count in in batch file
322 $ak9processed = $ar_seg[3]; // transaction count by Availity
323 $ak9acceptct = $ar_seg[4]; // transactions accepted by Availity
324 // ignore AK905 -- do not check for header errors
325 // since Availity says this is rarely reported
326 // and will probably produce an ACK response anyway
328 //echo "AK9: {$ar_seg[1]} {$ar_seg[2]} {$ar_seg[3]} {$ar_seg[4]} " . PHP_EOL;
329 continue;
332 if ( $ar_seg[0] == "AK2" ) {
333 // this segment describes accept/reject status of each claim
334 // if 276 claim status is sent by OpenEMR,
335 // then need to account for that possibility here
336 // it is probably to sent results to a different csv file
338 $ak2version = $ar_seg[1]; // 837 or 276 assume 837 here
339 $ak2st02 = sprintf("%04d", $ar_seg[2]); // AK202 = ST02 in batch file ST segment
341 // reset values for new entry
342 $ak5statustxt = "";
343 $ak3seg = ""; // error segment
344 $ak3line = ""; // error segment position (segments from ST = 1)
345 $ak3loop = ""; // loop identifier code e.g. 2300B
346 $ak3type = ""; // error code
347 $ak4pos = "";
348 $ak3typetxt = "";
349 $ak3msg = "";
351 $ctx301 = ""; // business unit (CLM01, NM1, etc.)
352 $ctx302 = ""; // business unit value pid-enctr, name, etc.
353 $ctx303 = ""; // probably not used
354 $ctx401 = ""; // business unit (CLM01, NM1, etc.)
355 $ctx402 = ""; // business unit value pid-enctr
356 $ctx403 = ""; // probably not used
357 $ctx_ct = 0;
358 $ctx01 = "";
360 $ak4pos = ""; // IK401-1 offending element position in segment
361 $ak4comppos = ""; // IK401-2 offending component position in element
362 $ak4rep = ""; // IK401-3 offending component repetition position
363 $ak4elem = ""; // IK402 offending component repetition position
364 $ak4cause = ""; // IK403 code for error
365 $ak4causetxt = ""; // text explanation from ibr_997_code_txt array
366 $ak4data = ""; // IK404 copy of bad data element
367 $ak4msg = "";
369 $ak5status = ""; // status of transaction set
371 // these are just left here in case it turns out that the IK5 segment
372 // has more useful information than just status--like testing new batch generators
373 //$ak501 = ""; // status of transaction set
374 //$ak502 = ""; // implementation error found
375 //$ak503 = ""; // transaction set syntax error code
376 //$ak504 = ""; // transaction set syntax error code
377 //$ak505 = ""; // transaction set syntax error code
378 //$ak506 = ""; // transaction set syntax error code
380 $loopid = "2000";
382 continue;
385 // Segment IK3 in the 5010 999 replaces AK3 in 4010 997
386 // If IK3, then new segment CTX may appear
387 if ( $ar_seg[0] == "AK3" || $ar_seg[0] == "IK3" ) {
388 $ak3seg = $ar_seg[1]; // segment name with error in batch file
389 $ak3line= $ar_seg[2]; // segment line in ST...SE in batch file
390 $ak3loop = $ar_seg[3]; // loop identifier code e.g. 2300B
391 $ak3type = $ar_seg[4]; // error code
392 // call $ak304($ak3type); for text
393 $ak3typetxt = ibr_997_code_text('ak304', $ak3type);
395 $ak3msg .= $ak3loop . ' ' . $ak3seg . ' ' . $ak3line;
396 // set loopid and reset ctx segment count
397 $loopid = "2100";
398 $ctx_ct = 0;
399 continue;
402 // If IK4, then no AK4, IK4 in 999 replaces AK4 997
403 // If IK4, then segment CTX may appear
404 if ( $ar_seg[0] == "AK4" || $ar_seg[0] == "IK4") {
405 // IK401 is possibly a composite element $ik4pos:$ik4comppos:$ik4rep
406 // for now, just get the first part, the element, such as '2' for error in N302
407 $sub = strpos($ar_seg[1], $sub_d);
408 if ($sub) {
409 $ak4pos = explode($sub_d, $ar_seg[1]);
410 $ak4msg .= ($ak4msg) ? ' pos '.$ak4pos[0] : 'pos '.$ak4pos[0];
411 $ak4msg .= (isset($ak4pos[1])) ? ' sub '.$ak4pos[1] : '';
412 $ak4msg .= (isset($ak4pos[2])) ? ' rep '.$ak4pos[2] : '';
413 } else {
414 $ak4msg .= ($ak4msg) ? ' pos '.$ar_seg[1] : 'pos '.$ar_seg[1];
416 $ak4elem = $ar_seg[2]; // Data element numbers defined in X12N Data Element Dictionary
417 $ak4cause = $ar_seg[3]; // error code
418 // call $ak403($ak4cause); for text
419 $ak4causetxt = ibr_997_code_text ("ak403", $ak4cause );
420 if ( array_key_exists(4, $ar_seg) ) { $ak4data = $ar_seg[4]; }
423 $loopid = "2110";
424 continue;
427 if ($ar_seg[0] == "CTX" ) {
428 // SITUATIONAL TRIGGER
429 // CTX02 is segment ID CTX03 is segment count
430 // Business Unit Identifier i.e. element ID
431 // CTX01 is components ELEM:VALUE, ex. CLM01:pid-encounter
432 // response to 269 270 271 274 276 277 835 then CTX01 = TRN02
433 // response to 274 275 278 then CTX01 = NM109
434 // response to 837 then CTX01 = CLM01
435 if ($loopid == "2100") {
436 if ( strpos($ar_seg[1], 'SITUATIONAL') ) {
437 // try and get the segment ID and segment number
438 // there is more, but it is overkill for us
439 $ctx301 .= isset($ar_seg[2]) ? $ar_seg[2] : '';
440 $ctx301 .= isset($ar_seg[3]) ? ' ' . $ar_seg[3] . ' ': '';
441 continue;
443 $sub = strpos($ar_seg[1], $sub_d);
444 if ($sub) {
445 // Business Unit Identifier
446 $ctx301 .= substr($ar_seg[1], 0, $sub); // business unit (CLM01, NM1, etc.)
447 $ctx302 .= substr($ar_seg[1], $sub+1); // business unit value pid-enctr
449 } else {
450 $ctx301 .= $ar_seg[1];
452 continue;
453 } elseif ($loopid == "2110") {
454 if ( strpos($ar_seg[1], 'SITUATIONAL') ) {
455 // try and get the segment ID and segment number
456 // there is more, but it is overkill for us
457 $ctx401 .= isset($ar_seg[2]) ? $ar_seg[2] : '';
458 $ctx401 .= isset($ar_seg[3]) ? ' ' . $ar_seg[3] . ' ': '';
459 } else {
460 $sub = strpos($ar_seg[1], $sub_d);
461 if ($sub) {
462 $ctx401 .= substr($ar_seg[1], 0, $sub); // business unit (CLM01, NM1, etc.)
463 $ctx402 .= substr($ar_seg[1], $sub+1); // business unit value pid-enctr
464 } else {
465 $ctx401 .= $ar_seg[1];
468 continue;
469 } else {
470 $ctx01 = $ar_seg[1];
471 if ( array_key_exists(2, $ar_seg) ) {$ctx02 = $ar_seg[2]; }
474 // increment ctx count, not used, but maybe in future revisions
475 $ctx_ct++;
477 continue;
480 if ( $ar_seg[0] == "IK5" || $ar_seg[0] == "AK5" ) {
481 $ak5status = $ar_seg[1]; // A, E, or R
482 $ak5statustxt = ibr_997_code_text ("ak501", $ak5status);
483 // Only report information on errors
484 // since AK5 gives the status of each claim
485 // we take this as the sign to gather error information
486 // Problems with accepted claims show up as .ebr rejects or claim denials
487 if ($ak5status != "A" ) {
488 // not perfect: claim rejected or issue noted
489 // index array
490 $rct = count($ar_reject);
491 // get more information on the claim
492 // possibly add check for $isadate, since 999 date must be >= batch date
493 $cml_info = ibr_batch_get_st_info($ak2st02, $bt_file);
494 if (is_array($cml_info)) {
495 $pt_name = $cml_info[2].', '.$cml_info[3];
496 if (!$bt_file) { $bt_file = $cml_info[4]; }
497 $bt_svcd = $cml_info[5];
498 if (!$ctx302) { $ctx302 = $cml_info[1]; }
499 } else {
500 if (!$ctx302) { $ctx302 = $ta1ctlnum.$ak2st02; }
502 //array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'err_seg', 'File_997', 'Ctn_837', 'err_copy', 'FileTxt');
503 $ar_reject[$rct]['pt_name'] = isset($pt_name) ? $pt_name : "NF";
504 $ar_reject[$rct]['svc_date'] = isset($bt_svcd) ? $bt_svcd : "NF";
505 $ar_reject[$rct]['pid_enctr'] = isset($ctx302) ? $ctx302 : "NF";
506 $ar_reject[$rct]['clm_status'] = $ak5status;
507 $ar_reject[$rct]['ak_num'] = $ak2st02;
508 $ar_reject[$rct]['err_seg'] = $ak3msg.' '.$ak4msg;
509 $ar_reject[$rct]['file_997'] = $fname;
510 $ar_reject[$rct]['btcntrl'] = $ta1ctlnum;
511 $ar_reject[$rct]['err_copy'] = $ak3typetxt . ' | ' . $ak4causetxt;
512 $ar_reject[$rct]['file_97T'] = $f97T_name;
516 continue;
517 } //
519 if ( $ar_seg[0] == "IEA" ) {
520 $ieacount = $ar_seg[1];
521 $ieactlnum = $ar_seg[2];
522 if ($ieactlnum == $isactlnum) {
523 // we are done
524 $isa_match = TRUE;
525 } else {
526 echo "ibr_997_parse Error: IEA Segment did not match to ISA segment.";
529 continue;
532 } // end foreach($ar_997 as $seg )
534 $rjct_ct = $ak9processed - $ak9acceptct;
536 $ar_997file['f997_time'] = isset($gsdate) ? $gsdate : $ftime;
537 $ar_997file['f997_file'] = isset($fname) ? $fname : '';
538 $ar_997file['f997_isactl'] = isset($isactlnum) ? $isactlnum : '';
539 $ar_997file['f997_ta1ctrl'] = isset($ta1ctlnum) ? $ta1ctlnum : '';
540 $ar_997file['f997_initver'] = isset($ak2version ) ? $ak2version : $ta1ack;
541 $ar_997file['f997_clm_reject'] = isset($rjct_ct) ? $rjct_ct : '';
542 $ar_997file['f997_clm_count'] = isset($ak9processed) ? $ak9processed : '';
543 $ar_997file['f997_clm_indc'] = isset($ak9batchct) ? $ak9batchct : '';
544 $ar_997file['f997_batch'] = isset($bt_file) ? $bt_file : '';
546 return array('file' => $ar_997file, 'claims' => $ar_reject);
547 } // end function ibr_997_parse($path_997)
550 * trim the claims and files array down for csv file
552 * @param array
553 * @param string
554 * @return array
556 function ibr_997_data_csv($ar_claims, $type='claim') {
557 // trim the claims array down for csv file
558 $ar_csv = array();
560 if (!is_array($ar_claims) || ! count($ar_claims) ) {
561 return false;
563 $idx=0;
564 if ($type == 'claim') {
565 foreach($ar_claims as $rjc) {
566 $ar_csv[$idx]['pt_name'] = $rjc['pt_name'];
567 $ar_csv[$idx]['svc_date'] = $rjc['svc_date'];
568 $ar_csv[$idx]['pid_enctr'] = $rjc['pid_enctr'];
569 $ar_csv[$idx]['clm_status'] = $rjc['clm_status'];
570 $ar_csv[$idx]['ak_num'] = $rjc['ak_num'];
571 $ar_csv[$idx]['file_997'] = $rjc['file_997'];
572 $ar_csv[$idx]['btcntrl'] = $rjc['btcntrl'];
573 $ar_csv[$idx]['err_seg'] = $rjc['err_seg'];
575 $idx++;
577 } else {
578 // files -- once for each file
579 $ar_csv['f997_time'] = $ar_claims['f997_time'];
580 $ar_csv['f997_file'] = $ar_claims['f997_file'];
581 $ar_csv['f997_isactl'] = $ar_claims['f997_isactl'];
582 $ar_csv['f997_ta1ctrl'] = $ar_claims['f997_ta1ctrl'];
583 $ar_csv['f997_clm_reject'] = $ar_claims['f997_clm_reject'];
586 return $ar_csv;
591 * create html table summarizing the 997/999 file
593 * @param array $ar_data
594 * @param bool $err_only
595 * @return string
597 function ibr_997_file_data_html($ar_data, $err_only=TRUE) {
599 $idx = 0;
600 $idf = 0;
601 $clm_html = "";
603 // File Name 97T Name Control Num Claims Rejected
604 $str_html = "<table class=\"f997\" cols=6><caption>997 Files Summary</caption>
605 <thead>
606 <tr>
607 <th>File Name</th><th>Batch Ctl Num</th>
608 <th>Claims</th><th>Rejected</th><th>Batch</th>
609 </tr>
610 </thead>";
612 foreach ($ar_data as $arh ) {
613 // alternate
614 $bgf = ($idf % 2 == 1) ? 'odd' : 'even';
615 $idf++;
616 // do the files table first, then add reject claims, if any
617 $ar_df = $arh['file'];
618 $fname = $ar_df['f997_file'];
620 // file information row<a href=\"edi_history_main.php?fvkey={$ar_hd['filename']}\" target=\"_blank\">{$ar_hd['filename']}</a></td>
621 $str_html .= "<tbody>
622 <tr class=\"{$bgf}\">
623 <td><a target=\"_blank\" href=\"edi_history_main.php?fvkey={$ar_df['f997_file']}\">{$ar_df['f997_file']}</a> &nbsp; <a target=\"_blank\" href=\"edi_history_main.php?fvkey={$ar_df['f997_file']}&readable=yes\">Text</a></td>
624 <td><a target=\"_blank\" href=\"edi_history_main.php?btctln={$ar_df['f997_ta1ctrl']}\">{$ar_df['f997_ta1ctrl']}</a></td>
625 <td>{$ar_df['f997_clm_count']}</td>
626 <td><a class=\"clmstatus\" target=\"_blank\" href=\"edi_history_main.php?fv997={$ar_df['f997_file']}&err997=yes\">{$ar_df['f997_clm_reject']}</a></td>
627 <td>{$ar_df['f997_batch']}</td>
628 </tr>";
630 // rejected claims information row
631 if ( isset($arh['claims']) && count($arh['claims']) ) {
632 //array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'err_seg', 'File_997', 'Ctn_837', 'err_copy', 'FileTxt');
633 //['pt_name']['svc_date']['pid_enctr'] ['clm_status']['ak_num']['err_seg']['file_997']['cntrl_num']['err_copy']['file_97T']
634 foreach ($arh['claims'] as $clmr) {
635 if ($err_only) { if ($clmr['clm_status'] != "R") continue; }
636 // alternate
637 $bgc = ($idx % 2 == 1) ? 'odd' : 'even';
638 $idx++;
639 //'pt_name''date''batch_file''cntrl_num''st_num''clm_status''pid_enctr''err_elem''err_seg''err_copy''file_text'
640 $batchctln = isset($clmr['btcntrl']) ? $clmr['btcntrl'] : '';
642 $clm_html .= "<tr class=\"{$bgc}\">";
643 $clm_html .= isset($clmr['pt_name']) ? "<td>{$clmr['pt_name']}</td>" : "<td>&nbsp;</td>";
644 $clm_html .= isset($clmr['pid_enctr']) ? "<td><a class='btclm' target='_blank' href='edi_history_main.php?fvbatch=$batchctln&btpid={$clmr['pid_enctr']}&stnum={$clmr['ak_num']}'>{$clmr['pid_enctr']}</a></td>" : "<td>&nbsp;</td>";
645 $clm_html .= isset($clmr['clm_status']) ? "<td>{$clmr['clm_status']}</td>" : "<td>&nbsp;</td>";
646 $clm_html .= isset($clmr['ak_num']) ? "<td><a class='clmstatus' href='edi_history_main.php?fv997={$clmr['file_997']}&aknum={$clmr['ak_num']}' target='_blank'>{$clmr['ak_num']}</a></td>" : "<td>&nbsp;</td>";
647 $clm_html .= isset($clmr['batch_file']) ? "<td><a target='_blank' href='edi_history_main.php?fvkey={$clmr['batch_file']}'>{$clmr['batch_file']}</a></td>" : "<td>&nbsp;</td>";
648 $clm_html .= isset($clmr['err_seg']) ? "<td>{$clmr['err_seg']}</td> </tr>" : "<td>&nbsp;</td> </tr>";
650 $clm_html .= isset($clmr['err_copy']) ? " <tr class=\"{$bgc}\"><td colspan=6> &nbsp {$clmr['err_copy']}</td></tr>" : "<td>&nbsp;</td>";
651 } // end foreach($arh['claims'] as $clmr)
652 } // end if ( isset($ar_data['claims']) && count($ar_data['claims']) )
653 } // end foreach ($ar_data as $arh )
655 if ( $clm_html ) {
656 // we have some rejected claims to report on
657 // make a header row, but no caption
658 $str_html .= "<table class=\"f997\" cols=6>
659 <thead>
660 <tr>
661 <th>Name</th><th>Account</th><th>Status</th>
662 <th>ST Num</th><th>Batch</th><th>Segment</th>
663 </tr>
664 <tr>
665 <th colspan=6>Message</th>
666 </tr>
667 </thead>
668 <tbody>";
670 $str_html .= $clm_html;
673 $str_html .= "</tbody>
674 </table>
675 <p></p>";
677 return $str_html;
681 * Html output for errors in 997/999 files
683 * @uses csv_file_by_controlnum()
684 * @uses ibr_batch_get_st_info()
685 * @uses ibr_997_code_text()
687 * @param array $aksegments
688 * @param array $delims
689 * @param string $btisa13
690 * @param bool $html
691 * @return string
693 function ibr_997_akhtml($aksegments, $delims, $btisa13='', $html=true) {
695 $str_html = '';
696 if ( !is_array($aksegments) || !count($aksegments)) {
697 $str_html .= "<p class='ak999stat'>No rejected claims found in file</p>".PHP_EOL;
698 csv_edihist_log("ibr_997_akhtml: No rejected claims or invalid segments array");
699 return $str_html;
702 $btctlnum = ($btisa13) ? $btisa13 : 'unknown';
703 $bt_file = ($btisa13) ? csv_file_by_controlnum('batch', $btisa13) : 'unknown';
705 $elem_d = $delims['e'];
706 $sub_d = $delims['s'];
707 $isfound = false;
708 $ak997 = array();
709 $ak_pos = 0;
710 $idx = -1;
711 foreach($aksegments as $segstr) {
712 $idx++;
713 $seg = explode($elem_d, $segstr);
714 if ($seg[0] == 'AK2') {
715 $batchst = sprintf("%04d", $seg[2]);
716 $str_html .= "<p class='ak999stat'>".PHP_EOL;
717 $str_html .= "ST: $batchst <br /> (837 ICN: $btctlnum $bt_file)<br /><br />";
718 if ($bt_file && $bt_file != 'unknown') {
719 $stinfo = ibr_batch_get_st_info($batchst, $bt_file);
720 if (count($stinfo) > 0) {
721 $str_html .= "Name: {$stinfo[2]}, {$stinfo[3]} Ctl: {$stinfo[1]}<br /><br />".PHP_EOL;
724 continue;
726 // format IK3, CTX, IK4 segments
727 if ( $seg[0] == 'AK3' ||$seg[0] == 'IK3') {
728 $str_html .= "Loop: {$seg[3]}, Line: {$seg[2]}, Segment: {$seg[1]} <br />".PHP_EOL;
729 $ak3err = ibr_997_code_text('ak304', $seg[4]);
730 $str_html .= "Code: {$seg[4]} $ak3err <br />".PHP_EOL;
732 if ( $seg[0] == "AK4" || $seg[0] == "IK4") {
733 // IK401 is possibly a composite element $ik4pos:$ik4comppos:$ik4rep
734 // for now, just get the first part, the element, such as '2' for error in N302
735 $ak4msg = ''; $ak4data = ''; $ak4err = '';
736 $sub = strpos($seg[1], $sub_d);
737 if ($sub) {
738 $ak4pos = explode($sub_d, $seg[1]);
739 $ak4msg .= ($ak4msg) ? ' pos '.$ak4pos[0] : 'pos '.$ak4pos[0];
740 $ak4msg .= (isset($ak4pos[1])) ? ' sub '.$ak4pos[1] : '';
741 $ak4msg .= (isset($ak4pos[2])) ? ' rep '.$ak4pos[2] : '';
742 } else {
743 $ak4msg .= ($ak4msg) ? ' pos '.$seg[1] : 'pos '.$seg[1];
745 $ak4elem = $ar_seg[2]; // Data element numbers defined in X12N Data Element Dictionary
746 $ak4cause = $ar_seg[3]; // error code
748 $ak4err = ibr_997_code_text ("ak403", $seg[3] );
749 if ( array_key_exists(4, $seg) ) { $ak4data = $seg[4]; }
750 $str_html .= "Data error: $ak4msg <br />".PHP_EOL;
751 $str_html .= "Code: {$seg[3]} $ak4err <br />".PHP_EOL;
752 $str_html .= ($ak4data) ? "Data: $ak4data <br />".PHP_EOL : '';
754 if ( $seg[0] == "CTX") {
755 if (strpos($seg[1], 'SITUATIONAL')) {
756 $str_html .= "{$seg[1]} -- Line: {$seg[3]} Position: {$seg[5]} <br />".PHP_EOL;
757 } else {
758 if (strpos($seg[1], $sub_d)) {
759 $busid = explode($sub_d, $seg[1]);
760 if ($busid[0] == 'CLM01') {
761 $str_html .= "Patient Ctl Num: {$busid[1]} <br />".PHP_EOL;
762 } else {
763 $str_html .= "Identifier: {$busid[0]} Value: {$busid[1]} <br />".PHP_EOL;
765 } else {
766 $str_html .= "Identifier: {$seg[1]} <br />".PHP_EOL;
770 if ( $seg[0] == "AK5" || $seg[0] == "IK5" ) {
771 $ak5txt = ibr_997_code_text ("ak501", $seg[1]);
772 $str_html .= "Status: {$seg[1]} $ak5txt <br />".PHP_EOL;
773 $str_html .= "</p>".PHP_EOL;
776 return $str_html;
780 * Scan through the 997/999 file and report on errors
782 * @uses csv_x12_segments()
783 * @uses ibr_997_akhtml()
784 * @param string $filename
785 * @param string $ak2num optional
786 * @param bool $html_out
787 * @return string
789 function ibr_997_errscan($filename, $ak2num='', $html_out=true) {
791 $x12seg = csv_x12_segments($filename, 'f997', false);
792 if ( !$x12seg || ! isset($x12seg['segments']) ) {
793 $str_html = "failed to get segments for " . basename($filename).PHP_EOL;
794 csv_edihist_log("ibr_997_get_akblock: failed to get segments for $filename");
795 return $str_html;
798 $str_html = '';
799 $akst = ($ak2num) ? sprintf("%04d", $ak2num) : false;
801 $elem_d = $x12seg['delimiters']['e'];
802 $sub_d = $x12seg['delimiters']['s'];
803 $isfound = false;
804 $errslice = array();
805 $btctlnum = '';
806 $ak_pos = 0;
807 $idx = -1;
809 foreach($x12seg['segments'] as $segstr) {
810 $idx++;
811 $segid = substr($segstr, 0, 4);
813 if ($segid == 'TA1'.$elem_d) {
814 $seg = explode($elem_d, $segstr);
815 $btctlnum = strval($seg[1]);
816 continue;
818 if ($segid == 'AK2'.$elem_d) {
819 $ak2pos = $idx;
820 if ($akst) {
821 $seg = explode($elem_d, $segstr);
822 if ($seg[2] == $akst) { $isfound = true; }
824 continue;
826 if ($segid == 'AK5'.$elem_d || $segid == 'IK5'.$elem_d) {
827 $seg_count = $idx - $ak2pos + 1;
828 if ($isfound) {
829 $errslice[] = array($ak2pos, $seg_count);
830 break;
831 } elseif (!$akst && substr($segstr, 4, 1) != 'A') {
832 $errslice[] = array($ak2pos, $seg_count);
836 if (count($errslice)) {
837 foreach($errslice as $er) {
838 $aksegs = array_slice($x12seg['segments'], $er[0], $er[1]);
839 $str_html .= ibr_997_akhtml($aksegs, $x12seg['delimiters'], $btctlnum, $html_out);
841 } else {
842 $fn = basename($filename);
843 $str_html .= "<p>No rejected claims indicated in $fn</p>".PHP_EOL;
846 return $str_html;
852 * process new 997/999 files
854 * @uses csv_newfile_list()
855 * @uses csv_verify_file()
856 * @uses csv_parameters()
857 * @param array $file_array -- optional, this array is sent from the ibr_io.php script
858 * @param bool $html_out -- whether to produce and return html output
859 * @param bool $err_only -- whether to generate claim information only for errors (ignored)
860 * @return string
862 function ibr_997_process_new($file_array = NULL, $html_out = TRUE, $err_only = TRUE) {
864 $ret_str = "";
865 $chr_ct1 = 0;
866 $chr_ct2 = 0;
867 $new_997 = array();
868 $need_dir = TRUE;
870 if (is_null($file_array) || empty($file_array) ) {
871 // directory will need to be prepended to name
872 $new_997 = csv_newfile_list('f997');
873 } elseif (is_array($file_array) && count($file_array) ) {
874 // files that are not verified will just be ignored
875 foreach ($file_array as $finp) {
876 $fp = csv_verify_file($finp, 'f997');
877 if ($fp) { $new_997[] = $fp; }
879 $need_dir = FALSE;
882 if (count($new_997) == 0 ) {
883 $ret_str = "<p>ibr_997_process_new: no new 997/999 files. </p>";
884 return $ret_str;
885 } else {
886 $f997count = count($new_997);
889 $ar_htm = array();
891 $params = csv_parameters("f997");
892 $tdir = dirname(__FILE__).$params['directory'];
894 // get batch files parameters, we need the batch_dir
895 $bp = csv_parameters("batch");
896 $batch_dir = dirname(__FILE__).$bp['directory'];
898 foreach ($new_997 as $f997) {
899 // make the file path
900 $fpath = ($need_dir) ? $tdir . DIRECTORY_SEPARATOR . $f997 : $f997;
901 // get file m-time
902 //$ftime = date('Ymd:His', filemtime($fpath));
903 // read file into string
904 $f_str = file_get_contents($fpath);
905 if ($f_str) {
906 // transform file contents into segment arrays
907 $ar_seg = csv_x12_segments($fpath, "f997", FALSE );
909 if (!$ar_seg) {
910 // file was rejected
911 csv_edihist_log("ibr_997_process_new: failed to get segments for $fpath");
912 $ret_str .= "ibr_997_process_new: failed to get segments for $fpath</p>" .PHP_EOL;
913 continue;
915 // parse arrays into data arrays
916 $ar_data = ibr_997_parse($ar_seg);
917 // $ar_data = array("file" => $ar_997file, "rejects" => $ar_reject);
919 $csv_claims = ibr_997_data_csv($ar_data['file'], 'file');
920 if ($csv_claims) {
921 $chr_ct1 += csv_write_record($csv_claims, "f997", "file");
922 } else {
923 csv_edihist_log("ibr_997_process_new: error with files csv array");
926 if ( isset($ar_data['claims']) && count($ar_data['claims']) ) {
927 // only add to claims_997.csv if there are rejected claims
928 $csv_claims = ibr_997_data_csv($ar_data['claims'], 'claim');
929 if ($csv_claims) {
930 $chr_ct2 += csv_write_record($csv_claims, "f997", "claim");
931 } else {
932 csv_edihist_log("ibr_997_process_new: error with claims csv array");
937 // save all the ar_datas for html output
938 if ($html_out) { $ar_htm[] = $ar_data; }
940 } else {
941 $ret_str .= "<p>ibr_997_process_new: failed to read $fpath </p>" .PHP_EOL;
945 csv_edihist_log("ibr_997_process_new: $chr_ct1 characters written to files_997.csv");
946 csv_edihist_log("ibr_997_process_new: $chr_ct2 characters written to claims_997.csv");
948 if ($html_out) {
949 // generate html output and return that
950 $ret_str .= ibr_997_file_data_html($ar_htm, $err_only);
951 } else {
952 $ret_str .= "x12_999 files: processed $f997count x12-999 files <br />";
956 return $ret_str;