installation bug fix
[openemr.git] / library / edihistory / edih_csv_inc.php
blob209a20729724b2ca8d493334554b43b63687aa7b
1 <?php
2 /**
3 * edih_csv_inc.php
5 * Copyright 2012 Kevin McCormick
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; version 3 or later. You should have
15 * received a copy of the GNU General Public License along with this program;
16 * if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * <http://opensource.org/licenses/gpl-license.php>
20 * @author Kevin McCormick
21 * @link: http://www.open-emr.org
22 * @package OpenEMR
23 * @subpackage ediHistory
27 * The purpose of this file is to hold functions of general utility for
28 * my edi_claim_history project. It began as a php "class" but I am now
29 * thinking that instantiating the class is too much bother and probably
30 * a waste of memory, since the contents of the file have to be read into
31 * memory anyway.
33 * <pre>
34 * ******* important *********
35 * function csv_parameters($type="ALL")
36 * This function must have the correct values or nothing will work
37 * function csv_verify_file( $file_path, $type, $val_array=FALSE )
38 * critical for file verification and x12 parsing
39 * function (in ibr_uploads.php) ibr_upload_match_file($param_ar, $fidx, &$html_str)
40 * contains a regular expression that must be correct
42 * Also, the constant IBR_HISTORY_DIR must be correct
43 * **************************
44 * </pre>
46 * The claim_history x12 files are claim (837) acknowledgement (997/999) claim status (277) and claim payment (835)
47 * Also eligibility request (270) and eligibility response (271)
49 * <pre>
50 * Basic workflow:
51 * Each file type has a row in the array from csv_paramaters()
52 * type directory files_csv claims_csv column regex
54 * 1. open submitted file in edih_x12_class to verify and produce properties
55 * 2. Read the parameters array and choose the parameters using 'type'
56 * 2. Search the matched type 'directory' for the filename files matching the 'regex' regular expressions and
57 * compare the results to the files listed in the 'files_csv' files.csv record -- unmatched files are "new"
58 * 3. Each "new" x12 file should be read by csv_x12_segments -- returns array('path', 'delimiters', 'segments')
59 * ibr, ebr, ack -- basically Availity formats have their own read functions
60 * 4. Pass the array to various functions which parse for claims information
61 * 5. Write the results to files.csv or claims.csv and create html output for display
63 * 6. Other outputs as called for in ibr_history.php -- from user input from claim_history.html
64 * </pre>
66 * Key usability issue is the "new" files are in the users home directory -- downloaded there
67 * while the OpenEMR is on the server -- so there is a basic issue of access to the files
69 * The ibr_uploads.php script handles uploads of zip archives or multiple file uploads
71 * The csv data files are just php written .csv files, so anything different may cause errors
72 * You can open and edit them in OpenOffice, but you must save them in "original format"
74 * TO_DO Some type of "find in files" search would be helpful for locating all references to a claim, patient, etc.
75 * [ grep -nHIrF 'findtext']
77 * TO_DO functions to zip old files, put them aside, and remove them from csv tables
80 ///**
81 // * a security measure to prevent direct web access to this file
82 // */
83 // if (!defined('SITE_IN')) die('Direct access not allowed!');
84 // $GLOBALS['OE_EDIH_DIR'] $GLOBALS['OE_SITE_DIR']
86 /* *********** GLOBALS used for testing only **********
88 // //$GLOBALS['OE_SITE_DIR'].'/edi/history';
89 //$OE_SITES_BASE = $GLOBALS['OE_SITE_DIR'];
90 //$OE_SITE_DIR = $OE_SITES_BASE.'/testing';
91 //$OE_EDIH_DIR = $OE_SITE_DIR.'/edi/history';
93 /* ***********
95 /**
96 * Constant that is checked in included files to prevent direct access.
97 * concept taken from Joomla
99 define('_EDIH', 1);
100 //DIRECTORY_SEPARATOR;
101 if (!defined('DS')) define('DS', DIRECTORY_SEPARATOR);
104 * Log messages to the log file
106 * @param string $msg_str the log message
107 * @return int number of characters written
109 function csv_edihist_log ( $msg_str ) {
111 //$dir = dirname(__FILE__).DS.'log';
112 //$dir = $GLOBALS['OE_EDIH_DIR'].DS.'log';
113 //$logfile = $GLOBALS['OE_EDIH_DIR'] . "/log/edi_history_log.txt";
114 $logfile = 'edih_log_'.date('Y-m-d').'.txt';
115 $dir = csv_edih_basedir().DS.'log';
116 $rslt = 0;
117 if ( is_string($msg_str) && strlen($msg_str) ) {
118 $tm = date('Ymd:Hms') . ' ' . $msg_str . PHP_EOL;
120 $rslt = file_put_contents($dir.DS.$logfile, $tm, FILE_APPEND);
121 } else {
123 $fnctn = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
124 csv_edihist_log ('invalid message string '.$fnctn);
127 return $rslt; // number of characters written
131 * read the edi_history_log.txt file into an
132 * html formatted ordered list
134 * @return string
136 function csv_log_html($logname='') {
137 $html_str = "<div class='filetext'>".PHP_EOL."<ol class='logview'>".PHP_EOL;
138 $fp = csv_edih_basedir().DS.'log'.DS.$logname;
139 if ( is_file($fp) ) {
140 $fh = fopen( $fp, 'r');
141 if ($fh !== FALSE) {
142 while (($buffer = fgets($fh)) !== false) {
143 $html_str .= "<li>".$buffer."</li>".PHP_EOL;
145 $html_str .= "</ol>".PHP_EOL."</div>".PHP_EOL;
146 if (!feof($fh)) {
147 $html_str .= "<p>Error in logfile: unexpected file ending</p>".PHP_EOL;
149 fclose($fh);
150 } else {
151 $html_str = "<p>Error: unable to open log file</p>".PHP_EOL;
154 return $html_str;
159 * list log files and store old logs in an archive
161 * @param bool
162 * @return array (json)
164 function csv_log_manage($list=true) {
166 //$dir = dirname(__FILE__).DS.'log';
167 $dir = csv_edih_basedir().DS.'log';
168 $list_ar = array();
169 $old_ar = array();
170 $lognames = scandir($dir);
171 if ($list) {
172 foreach($lognames as $log) {
173 if (!strpos($log, '_log_')) { continue; }
174 $list_ar[] = $log;
176 $s = (count($list_ar)) ? rsort($list_ar) : false;
178 return json_encode($list_ar);
180 } else {
181 // list is false, must be archive
182 $datetime1 = date_create(date('Y-m-d'));
184 foreach($lognames as $log) {
185 if ($log == '.' || $log == '..') { continue; }
187 $pos1 = strrpos($log, '_');
188 if ($pos1) {
189 $ldate = substr($log, $pos1+1, 10);
190 $datetime2 = date_create($ldate);
191 $interval = date_diff($datetime1, $datetime2);
192 //echo '== date difference '.$ldate.' '.$interval->format('%R%a days').PHP_EOL;
193 if ($interval->format('%R%a') < -7) {
194 // older log files are put in zip archive
195 if ( is_file($dir.DS.$log) ) { $old_ar[] = $log; }
201 $ok = false;
202 $archname = $dir.DS.'edih-log-archive.zip';
203 $filelimit = 200;
205 if (count($old_ar)) {
206 $zip = new ZipArchive;
207 if (is_file($archname)) {
208 $ok = $zip->open($archname, ZipArchive::CHECKCONS);
209 } else {
210 $ok = $zip->open($archname, ZipArchive::CREATE);
213 if ($ok) {
214 if ($zip->numFiles >= $filelimit) {
215 $zip->close();
216 $dte = $datetime1->format('Ymd');
217 $ok = rename($dir.DS.$archname, $dir.DS.$dte.'_'.$archname);
218 csv_edihist_log('csv_log_archive: rename full archive '.$dte.'_'.$archname );
219 if ($ok) {
220 $ok = $zip->open($archname, ZipArchive::CREATE);
221 if (!$ok) {
222 csv_edihist_log('csv_log_archive: cannot create '.$archname);
224 } else {
225 csv_edihist_log('csv_log_archive: cannot rename '.$archname);
229 if ($ok) {
230 foreach($old_ar as $lg) {
231 if (is_file($dir.DS.$lg)) {
232 $a = $zip->addFile($dir.DS.$lg, $lg);
233 if ($a) {
234 csv_edihist_log('csv_log_archive: add to archive '.$lg );
235 } else {
236 csv_edihist_log('csv_log_archive: error archiving '.$lg );
240 $c = $zip->close();
241 if ($c) {
242 foreach($old_ar as $lg) {
243 $u = unlink($dir.DS.$lg);
244 if ($u) {
245 continue;
246 } else {
247 csv_edihist_log('csv_log_archive: error removing '.$dir.DS.$lg);
250 } else {
251 csv_edihist_log('csv_log_archive: error closing log file archive');
253 } else {
254 csv_edihist_log('csv_log_manage: error failed to open '.$archname);
259 return json_encode($old_ar);
264 * open or save a user notes file
266 * @param string
267 * @param bool
268 * @return string
270 function csv_notes_file($content='', $open=true) {
272 $str_html = '';
273 //$fp = $GLOBALS['OE_EDIH_DIR'].'/edi_notes.txt';
274 $fp = csv_edih_basedir().DS.'archive'.DS.'edi_notes.txt';
275 if (! is_writable($fp) ) {
276 $fh = fopen( $fp, 'a+b');
277 fclose($fh);
279 // for retrieving notes
280 if ($open) {
281 // if contents were previously deleted by user and file is empty,
282 // the text 'empty' is put in content in save operation
283 $ftxt = file_get_contents($fp);
284 if ($ftxt === false) {
285 $str_html .= 'csv_notes_file: file error <br>'.PHP_EOL;
286 csv_edihist_log('csv_notes_file: file error');
288 if (substr($ftxt, 0, 5) == 'empty' && strlen($ftxt) == 5) {
289 $ftxt = '## '. date("F j, Y, g:i a");
290 } elseif (!$ftxt) {
291 $ftxt = '## '. date("F j, Y, g:i a");
293 $str_html .= PHP_EOL.$ftxt.PHP_EOL;
294 // next stanza for saving content
295 } elseif (strlen($content)) {
296 //echo "csv_notes_file: we have content<br>".PHP_EOL;
297 // use finfo php class
298 if ( class_exists('finfo') ) {
299 $finfo = new finfo(FILEINFO_MIME);
300 $mimeinfo = $finfo->buffer($content);
301 if ( strncmp($mimeinfo, 'text/plain; charset=us-ascii', 28) !== 0 ) {
302 csv_edihist_log('csv_notes_file: invalid mime-type '.$mimeinfo);
303 $str_html = 'csv_notes_file: invalid mime-type <br>'.$mimeinfo;
305 return $str_html;
307 } elseif (preg_match('/[^\x20-\x7E\x0A\x0D]|(<\?)|(<%)|(<asp)|(<ASP)|(#!)|(\$\{)|(<scr)|(<SCR)/', $content, $matches, PREG_OFFSET_CAPTURE)) {
308 csv_edihist_log('csv_notes_file: Filtered character in file content -- character: '.$matches[0][0].' position: '.$matches[0][1]);
309 $str_html .= 'Filtered character in file content not accepted <br>'. PHP_EOL;
310 $str_html .= ' character: ' . $matches[0][0] . ' position: ' . $matches[0][1] . '<br>' . PHP_EOL;
312 return $str_html;
314 } else {
315 $ftxt = ($content) ? $content : 'empty';
316 $saved = file_put_contents($fp, $ftxt);
317 $str_html .= ($saved) ? '<p>Save Error with notes file</p>' : '<p>Notes content saved</p>';
320 return $str_html;
324 * generates path to edi history files
326 * @return string|bool directory path
328 function csv_edih_basedir() {
329 // should be something like /var/www/htdocs/openemr/sites/default
330 if (isset($GLOBALS['OE_SITE_DIR'])) {
331 // debug
332 //echo 'csv_edih_basedir OE_SITE_DIR '.$GLOBALS['OE_SITE_DIR'].'<br>'.PHP_EOL;
333 return $GLOBALS['OE_SITE_DIR'].DS.'edi'.DS.'history';
334 } else {
335 csv_edihist_log('csv_edih_basedir: failed to obtain OpenEMR Site directory');
336 return false;
341 * generates path to edi_history tmp dir for file upload operations
343 * @uses csv_edih_basedir()
344 * @return string directory path
346 function csv_edih_tmpdir() {
348 $bdir = csv_edih_basedir();
349 $tdir = ($bdir) ? $bdir.DS.'tmp' : false;
350 //$systmp = sys_get_temp_dir();
351 //$systmp = stripcslashes($systmp);
352 //$systdir = $systmp.DS.'edihist';
353 //if ( $tdir && (is_dir($tdir) || mkdir($tdir, 0755) ) ) {
354 if ( $tdir) {
355 return $tdir;
356 } else {
357 return false;
364 * Initial setup function
366 * Create the directory tree and write the column headers into the csv files
367 * This function will accept a directory argument and it appends the value
368 * from IBR_HISTORY_DIR to the path. Then a directory for each type of file
369 * and the csv files are created under that.
371 * @uses csv_parameters()
372 * @uses csv_table_header()
373 * @uses csv_edih_basedir()
375 * @param string &$out_str referenced, should be created in calling function
376 * @return boolean
378 function csv_setup() {
380 $isOK = false;
381 $out_str = '';
382 $chr = 0;
383 // $GLOBALS['OE_SITE_DIR'] should be like /var/www/htdocs/openemr/sites/default
384 $sitedir = $GLOBALS['OE_SITE_DIR'];
385 //$sitedir = csv_edih_basedir();
387 if (is_readable($sitedir)) {
388 $basedir = $sitedir.DS.'edi';
389 $edihist_dir = $basedir.DS.'history';
390 $csv_dir = $edihist_dir.DS.'csv';
391 $archive_dir = $edihist_dir.DS.'archive';
392 $log_dir = $edihist_dir.DS.'log';
393 $tmp_dir = $edihist_dir.DS.'tmp';
394 } else {
395 //csv_edihist_log('setup: failed to obtain OpenEMR Site directory');
396 echo 'setup: failed to obtain OpenEMR Site directory<br>'.PHP_EOL;
397 return false;
400 if (is_writable($basedir) ) {
401 $isOK = true;
402 //csv_edihist_log('setup: directory '.$basedir);
403 $out_str .= 'EDI_History Setup should not overwrite existing data.<br>'.PHP_EOL;
404 $out_str .= 'Setup: directory '.$basedir.'<br>'.PHP_EOL;
406 if (is_dir($edihist_dir) || mkdir($edihist_dir, 0755)) {
407 $out_str .= 'created folder '.$edihist_dir.'<br>'.PHP_EOL;
408 $isOK = true;
409 if (is_dir($csv_dir) || mkdir($csv_dir, 0755) ) {
410 $out_str .= 'created folder '.$csv_dir.'<br>'.PHP_EOL;
411 $isOK = true;
412 } else {
413 $isOK = false;
414 $out_str .= 'Setup: Failed to create csv folder... '.'<br>'.PHP_EOL;
415 die('Failed to create csv folder... '.$archive_dir);
417 if (is_dir($archive_dir) || mkdir($archive_dir, 0755) ) {
418 $out_str .= 'created folder '.$archive_dir.'<br>'.PHP_EOL;
419 $isOK = true;
420 } else {
421 $isOK = false;
422 $out_str .= 'Setup: Failed to create archive folder... '.'<br>'.PHP_EOL;
423 die('Failed to create archive folder... ');
425 if (is_dir($log_dir) || mkdir($log_dir, 0755) ) {
426 $out_str .= 'created folder '.$log_dir.'<br>'.PHP_EOL;
427 $isOK = true;
428 } else {
429 $isOK = false;
430 $out_str .= 'Setup: Failed to create log folder... '.'<br>'.PHP_EOL;
431 die('Failed to create log folder... ');
433 if (is_dir($tmp_dir) || mkdir($tmp_dir, 0755) ) {
434 $out_str .= 'created folder '.$tmp_dir.PHP_EOL;
435 $isOK = true;
436 } else {
437 $isOK = false;
438 $out_str .= 'Setup: Failed to create tmp folder... '.'<br>'.PHP_EOL;
439 die('Failed to create tmp folder... ');
441 } else {
442 $isOK = false;
443 $out_str .= 'Setup failed: cannot write to folder '.$basedir.'<br>'.PHP_EOL;
444 die('Setup failed: cannot write to '.$basedir);
446 } else {
447 $isOK = false;
448 $out_str .= 'Setup: Failed to create history folder... '.'<br>'.PHP_EOL;
449 die('Failed to create history folder... '.$edihist_dir);
451 if ($isOK) {
452 $p_ar = csv_parameters('ALL');
453 $old_csv = array('f837'=>'batch', 'f835'=>'era');
454 foreach ($p_ar as $key=>$val) {
455 // rename existing csv files to old_filename
456 if (is_dir($csv_dir)) {
457 if ($dh = opendir($csv_dir)) {
458 while (($file = readdir($dh)) !== false) {
459 if (is_file($csv_dir.DS.$file) && strpos($file, 'csv')) {
460 $rn = rename($csv_dir.DS.$file, $csv_dir.DS.'old_'.$file);
461 if ($rn) {
462 $out_str .= 'renamed csv/'.$file.' to old_'.$file.'<br />'.PHP_EOL;
463 } else {
464 $out_str .= 'attempt to rename csv/'.$file.' failed<br />'.PHP_EOL;
471 // make the edi files storage subdirs
472 $tp = $p_ar[$key]['type'];
473 $type_dir = $p_ar[$key]['directory'];
475 if (is_dir($type_dir)) {
476 $out_str .= 'folder for '.$tp.' exists '.$type_dir.'<br>'.PHP_EOL;
477 } elseif (mkdir($type_dir, 0755)) {
478 if ($tp == 'f835') {
479 // in upgrade case the f835 directory should not exist
480 // move 'era' files from /era to /f835
481 if (is_dir($edihist_dir.DS.'era')) {
482 $fct = 0; $rct = 0;
483 if ($dh = opendir($edihist_dir.DS.'era')) {
484 while (($file = readdir($dh)) !== false) {
485 if (is_file($edihist_dir.DS.'era'.DS.$file)) {
486 $rct++;
487 $rn = rename($edihist_dir.DS.'era'.DS.$file, $type_dir.DS.$file);
488 $fct = ($rn) ? $fct + 1 : $fct;
492 $out_str .= 'created type folder '.$type_dir.' and moved '.$fct.' of '.$rct.' files from /era<br>'.PHP_EOL;
494 } else {
495 $out_str .= 'created type folder '.$type_dir.'<br>'.PHP_EOL;
498 } else {
499 $out_str .= 'Setup failed to create directory for '.$tp.'<br>'.PHP_EOL;
502 } else {
503 $out_str .= 'Setup failed: Can not create directories <br>'.PHP_EOL;
506 if ($isOK) {
507 csv_edihist_log($out_str);
508 return true;
509 } else {
510 return $out_str;
516 * Empty all contents of tmp dir /edi/history/tmp
518 * @uses csv_edih_tmpdir()
519 * @param none
520 * @return bool
522 function csv_clear_tmpdir() {
524 $tmpdir = csv_edih_tmpdir();
525 if ( basename($tmpdir) != 'tmp' ) {
526 csv_edihist_log ( 'tmp dir not /edi/history/tmp');
527 return false;
529 $tmp_files = scandir($tmpdir);
530 if (count($tmp_files) > 2) {
531 foreach($tmp_files as $idx=>$tmpf) {
532 if ($tmpf == "." || $tmpf == "..") {
533 // can't delete . and ..
534 continue;
535 } elseif (is_file($tmpdir.DS.$tmpf) ) {
536 unlink($tmpdir.DS.$tmpf);
537 } elseif(is_dir($tmpdir.DS.$tmpf)) {
538 $tdir_ar = scandir($tmpdir.DS.$tmpf);
539 foreach($tdir_ar as $tfn) {
540 if ($tfn == "." || $tfn == "..") {
541 continue;
542 } elseif (is_file($tmpdir.DS.$tmpf.DS.$tfn)) {
543 unlink($tmpdir.DS.$tmpf.DS.$tfn);
546 rmdir($tmpdir.DS.$tmpf);
550 $tmp_files = scandir($tmpdir);
551 if (count($tmp_files) > 2) {
552 csv_edihist_log ('tmp dir contents remain in ... /edi/history/tmp');
553 return false;
554 } else {
555 return true;
560 * open and verify a default edih_x12_file object
562 * @uses csv_check_filepath()
564 * @param string filepath or filename
565 * @parm string file x12 type
566 * @return object edih_x12_file class
568 function csv_check_x12_obj($filepath, $type='') {
570 $x12obj = false;
571 $ok = false;
573 $fp = csv_check_filepath($filepath, $type);
575 if ($fp) {
576 $x12obj = new edih_x12_file($fp);
577 if ( 'edih_x12_file' == get_class($x12obj) ) {
578 if ($x12obj->edih_valid() == 'ovigs') {
579 $ok = count( $x12obj->edih_segments() );
580 $ok = ($ok) ? count( $x12obj->edih_envelopes() ) : false;
581 $ok = ($ok) ? count( $x12obj->edih_delimiters() ) : false;
582 if (!$ok) {
583 csv_edihist_log("csv_check_x12_obj: object missing properties [$filepath]");
584 csv_edihist_log( $x12obj->edih_message() );
585 return false;
587 } else {
588 csv_edihist_log("csv_check_x12_obj: invalid object $filepath");
589 return false;
591 } else {
592 csv_edihist_log("csv_check_x12_obj: object not edih_x12_file $filepath");
593 return false;
595 } else {
596 csv_edihist_log("csv_check_x12_obj: invalid file path $filepath");
597 return false;
600 return $x12obj;
604 * Check that the file path we are working with is a readable file.
606 * If it is a file we have uploaded and we have only the file name
607 * this function will type the file and find it in the uploaded files directories
608 * and return the complete path.
610 * @uses csv_parameters()
611 * @param string $filename name of a file that is one of our types
612 * @param string $type optional; one of our file types
613 * @return string either an empty string or a readable filepath
615 function csv_check_filepath($filename, $type='ALL') {
617 // if file is readable, just return it
618 if ( is_file($filename) && is_readable($filename) ) {
619 return $filename;
622 $goodpath = '';
623 $fp = '';
624 $fn = basename($filename);
626 if ($type && $type != 'ALL') {
627 $p = csv_parameters($type);
628 if (is_array($p) && array_key_exists('type', $p) ) {
629 $fp = $p['directory'].DS.$fn;
631 } else {
632 $p_ar = csv_parameters("ALL");
633 foreach ($p_ar as $tp=>$par) {
634 if ( !$p_ar[$tp]['regex'] || !preg_match($p_ar[$tp]['regex'], $fn) ) {
635 continue;
636 } else {
637 $fp = $p_ar[$tp]['directory'].DS.$fn;
638 break;
642 if ( is_file($fp) && is_readable($fp) ) { $goodpath = realpath($fp); }
644 return $goodpath;
648 * verify file type parameter
650 * @param string file type
651 * @param bool return GS02 code or fXXX
652 * @return string file type or empty
654 function csv_file_type($type, $gs_code=false) {
656 if (!$type) {
657 csv_edihist_log('csv_file_type: invalid or missing type argument '.$type);
658 return false;
659 } else {
660 $tp_type = (string)$type;
663 if ( strpos('|f837|batch|HC', $tp_type) ) {
664 $tp = ($gs_code) ? 'HC' : 'f837';
665 } elseif ( strpos('|f835|era|HP', $tp_type) ) {
666 $tp = ($gs_code) ? 'HP' : 'f835';
667 } elseif ( strpos('|f999|f997|ack|ta1|FA', $tp_type) ) {
668 $tp = ($gs_code) ? 'FA' : 'f997';
669 } elseif ( strpos('|f277|HN', $tp_type) ) {
670 $tp = ($gs_code) ? 'HN' : 'f277';
671 } elseif ( strpos('|f276|HR', $tp_type) ) {
672 $tp = ($gs_code) ? 'HR' : 'f276';
673 } elseif ( strpos('|f271|HB', $tp_type) ) {
674 $tp = ($gs_code) ? 'HB' : 'f271';
675 } elseif ( strpos('|f270|HS', $tp_type) ) {
676 $tp = ($gs_code) ? 'HS' : 'f270';
677 } elseif ( strpos('|f278|HI', $tp_type) ) {
678 $tp = ($gs_code) ? 'HI' : 'f278';
679 } else {
680 $tp = '';
683 if ( !$tp) {
684 csv_edihist_log('csv_file_type error: incorrect type '.$tp_type);
686 return $tp;
691 * The array that holds the various parameters used in dealing with files
693 * A key function since it holds the paths, columns, etc.
694 * Unfortunately, there is an issue with matching the type in * the case of the
695 * values '997', '277', '999', etc, becasue these strings may be recast
696 * from strings to integers, so the 'type' originally supplied is lost.
697 * This introduces an inconsistency when the 'type' is used in comparison tests.
698 * We call the csv_file_type() function to return a usable file type identifier.
699 * The 'datecolumn' and 'fncolumn' entries are used in csv_to_html() to filter by date
700 * or place links to files.
702 * @param string $type -- default = ALL or one of batch, ibr, ebr, dpr, f997, f277, era, ack, text
703 * @return array
705 function csv_parameters($type='ALL') {
707 // This will need the OpenEMR 'oe_site_dir' to replace global
709 $p_ar = array();
711 $tp = ($type === 'ALL') ? $type : csv_file_type($type);
712 if (!$tp) {
713 csv_edihist_log('csv_parameters() error: incorrect type '.$type);
714 return $p_ar;
716 //$edihist_dir = $GLOBALS['OE_SITE_DIR'].'/edi/history';
717 $edihist_dir = csv_edih_basedir();
719 // the batch file directory is a special case - decide whether to use OpenEMR batch files or make our own copies
720 // OpenEMR copies each batch file to sites/default/edi and this project never writes to that directory
721 // batch reg ex -- '/20[01][0-9]-[01][0-9]-[0-3][0-9]-[0-9]{4}-batch*\.txt/' '/\d{4}-\d{2}-\d{2}-batch*\.txt$/'
723 $p_ar['f837'] = array('type'=>'f837', 'directory'=>$GLOBALS['OE_SITE_DIR'].DS.'edi', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f837.csv',
724 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f837.csv', 'filedate'=>'Date', 'claimdate'=>'SvcDate', 'regex'=>'/\-batch(.*)\.txt$/');
726 //$p_ar['csv'] = array("type"=>'csv', "directory"=>$edihist_dir.'/csv', "claims_csv"=>'ibr_parameters.csv',
727 // "files_csv"=>'', "column"=>'', "regex"=>'/\.csv$/');
728 $p_ar['f997'] = array('type'=>'f997', 'directory'=>$edihist_dir.DS.'f997', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f997.csv',
729 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f997.csv', 'filedate'=>'Date', 'claimdate'=>'RspDate', 'regex'=>'/\.(99[79]|ta1|ack)$/i');
730 $p_ar['f276'] = array('type'=>'f276', 'directory'=>$edihist_dir.DS.'f276', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f276.csv',
731 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f276.csv', 'filedate'=>'Date', 'claimdate'=>'ReqDate', 'regex'=>'/\.276([ei]br)?$/');
732 $p_ar['f277'] = array('type'=>'f277', 'directory'=>$edihist_dir.DS.'f277', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f277.csv',
733 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f277.csv', 'filedate'=>'Date', 'claimdate'=>'SvcDate', 'regex'=>'/\.277([ei]br)?$/i');
734 $p_ar['f270'] = array('type'=>'f270', 'directory'=>$edihist_dir.DS.'f270', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f270.csv',
735 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f270.csv', 'filedate'=>'Date', 'claimdate'=>'ReqDate', 'regex'=>'/\.270([ei]br)?$/i');
736 $p_ar['f271'] = array('type'=>'f271', 'directory'=>$edihist_dir.DS.'f271', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f271.csv',
737 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f271.csv', 'filedate'=>'Date', 'claimdate'=>'RspDate', 'regex'=>'/\.271([ei]br)?$/i');
738 $p_ar['f278'] = array('type'=>'f278', 'directory'=>$edihist_dir.DS.'f278', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f278.csv',
739 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f278.csv', 'filedate'=>'Date', 'claimdate'=>'FileDate', 'regex'=>'/\.278/');
740 // OpenEMR stores era files, but the naming scheme is confusing, so we will just use our own directory for them
741 $p_ar['f835'] = array('type'=>'f835', 'directory'=>$edihist_dir.DS.'f835', 'claims_csv'=>$edihist_dir.DS.'csv'.DS.'claims_f835.csv',
742 'files_csv'=>$edihist_dir.DS.'csv'.DS.'files_f835.csv', 'filedate'=>'Date', 'claimdate'=>'SvcDate', 'regex'=>'/835[0-9]{5}\.835*|\.(era|ERA|835)$/i');
744 if ( array_key_exists($tp, $p_ar) ) {
745 return $p_ar[$tp];
746 } else {
747 return $p_ar;
752 * determine if a csv table has data for select dropdown
754 * @param string default 'json'
755 * @return array json if argument is 'json'
757 function csv_table_select_list($outtp='json') {
758 $optlist = array();
759 $labels = array('f835'=>'Payments', 'f837'=>'Claims', 'batch'=>'Claims', 'f277'=>'Status', 'f276'=>'Status Req',
760 'f997'=>'Ack','f271'=>'Benefit', 'f270'=>'Benefit Req', 'f278'=>'Auth');
762 $edihist_dir = csv_edih_basedir(); // $GLOBALS['OE_SITE_DIR'].'/edi/history'
763 $csvdir = $edihist_dir.DS.'csv';
764 $tbllist = scandir($csvdir);
765 $idx = 0;
766 foreach($tbllist as $csvf) {
767 if ($csvf == "." || $csvf == ".." ) { continue; }
768 if (strpos($csvf, 'old') === 0) { continue; }
769 if (filesize($csvdir.DS.$csvf) < 70) { continue; }
770 if (substr($csvf, -1) == '~') { continue; }
771 $finfo = pathinfo($csvdir.DS.$csvf);
772 $fn = $finfo['filename'];
773 // e.g. files_f997
774 $tp = explode('_', $fn);
775 //$lblkey = $labels[$tp[1]];
776 $optlist[$tp[0]][$tp[1]]['fname'] = $fn;
777 $optlist[$tp[0]][$tp[1]]['desc'] = $tp[0].'-'.$labels[$tp[1]]; //$tp[1] .' '.$tp[0];
778 $idx++;
780 if ($outtp == 'json') {
781 return json_encode($optlist);
782 } else {
783 return $optlist;
788 * list existing archive files
790 * @param string default 'json'
791 * @return array json if argument is 'json'
793 function csv_archive_select_list($outtp='json') {
795 $flist = array();
796 $archdir = csv_edih_basedir().DS.'archive';
798 // debug
799 csv_edihist_log("csv_archive_select_list: using $archdir");
801 $scan = scandir($archdir);
802 if (is_array($scan) && count($scan)) {
803 foreach($scan as $s) {
804 if ($s == '.' || $s == '..') {
805 continue;
806 } elseif (strpos($s, 'note')) {
807 continue;
808 } else {
809 $flist[] = $s;
813 if ($outtp == 'json') {
814 return json_encode($flist);
815 } else {
816 return $flist;
821 * List files in the directory for the given type
823 * Write an entry in the log if an file is in the directory
824 * that does not match the type
826 * @uses csv_parameters()
827 * @param string $type a type from our list
828 * @return array
830 function csv_dirfile_list($type) {
831 // return false if location is not appropriate
832 $tp = csv_file_type($type);
833 if (!$tp) {
834 csv_edihist_log("csv_dirfile_list error: incorrect type $type");
835 return false;
837 $params = csv_parameters($tp);
838 if (empty($params) || csv_singlerecord_test($params) == false ) {
839 csv_edihist_log("csv_dirfile_list() error: incorrect type $type");
840 return false;
842 $search_dir = $params['directory'];
843 $ext_re = $params['regex'];
844 $dirfiles = array();
846 if (is_dir($search_dir)) {
847 if ($dh = opendir($search_dir)) {
848 while (($file = readdir($dh)) !== false) {
849 if ($file == '.' || $file == '..') {
850 continue;
851 } elseif ($tp == 'f837' && ($file == 'history' || $file == 'README.txt')) {
852 continue;
853 } elseif (is_file($search_dir.DS.$file) ) {
854 $dirfiles[] = $file;
855 } else {
856 if ($tp == 'f837' && $file == 'history') { continue; }
857 csv_edihist_log("csv_dirfile_list $type : not a file $file");
860 } else {
861 csv_edihist_log("csv_dirfile_list $type : error in scan $search_dir");
863 } else {
864 csv_edihist_log("csv_dirfile_list $type : not a directory $search_dir");
867 return $dirfiles;
868 } // end function
872 * List files that are in the csv record
874 * @uses csv_parameters()
875 * @uses csv_table_header()
877 * @param string $type -- one of our types
878 * @return array
880 function csv_processed_files_list($type) {
882 $tp = csv_file_type($type);
883 if (!$tp) {
884 csv_edihist_log("csv_processed_files_list: incorrect type $type");
885 return false;
887 $processed_files = array();
888 $param = csv_parameters($tp);
889 $hdr_ar = csv_table_header($tp, 'file');
890 if ( is_array($hdr_ar) ) {
891 foreach($hdr_ar as $k=>$hd) {
892 if ($hd == 'FileName') { $csv_col = $k; break; }
895 $csv_col = (isset($csv_col)) ? $csv_col : 1;
896 $csv_file = $param['files_csv'];
897 //if ($tp == 'dpr') {
898 //$csv_file = $param['claims_csv'];
899 //$csv_col = '5';
900 //} else {
901 //$csv_file = $param['files_csv'];
904 //$idx = 0;
905 if (is_file($csv_file)) {
906 if (($fh1 = fopen( $csv_file, "r" )) !== FALSE) {
907 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
908 $processed_files[] = $data[$csv_col];
910 //if ($idx) { $processed_files[] = $data[$csv_col]; }
911 // skip the header row
912 //$idx++;
914 fclose($fh1);
915 } else {
916 csv_edihist_log ("csv_list_processed_files: failed to access $csv_file" );
917 return false;
919 } else {
920 // first run - no file exists
921 csv_edihist_log("csv_processed_files_list: csv file does not exist ".basename($csv_file));
923 // remove the header row, but avoid NULL or false
924 $ret_ar = (empty($processed_files)) ? $processed_files : array_slice($processed_files, 1);
925 return $ret_ar;
926 } // end function
930 * Give an array of files in the storage directories that are not in the csv record
932 * @param string $type -- one of our types
933 * @return array
935 function csv_newfile_list($type) {
937 $ar_new = array();
938 $tp = csv_file_type($type);
939 if (!$tp) {
940 csv_edihist_log('csv_newfile_list: incorrect type '.$type);
941 return false;
944 $dir_files = csv_dirfile_list($tp);
945 $csv_files = csv_processed_files_list($tp);
947 // $dir_files should come first in array_diff()
948 if (empty($dir_files)) {
949 $ar_new = array();
950 } elseif (empty($csv_files) || is_null($csv_files)) {
951 $ar_new = $dir_files;
952 } else {
953 $ar_new = array_diff($dir_files, $csv_files);
956 return $ar_new;
960 * Parse 997 IK3 error segment to identify segment causing rejection
961 * The error segment string is specially created in edih_997_csv_data()
962 * Simple analysis, but the idea is just to identify the bad segment
964 * @param string error segment from edih_997_csv_data()
965 * @param bool true if only the 1st segmentID is wanted
966 * return array|string
968 function edih_errseg_parse($err_seg, $id=false) {
969 // ['err_seg'] = '|IK3*segID*segpos*loop*errcode*bht03syn|CTX-IK3*transID*segID*segpos*elempos
970 // |IK4*elempos*errcode*elem*|CTX-IK4*segID*segpos*elempos
972 // note: multiple IK3 segments are allowed in 997/999 x12
974 $ret_ar = array();
975 if ( !$err_seg || strpos($err_seg, 'IK3') === false) {
976 csv_edihist_log('edih_errseg_parse: invalid argument');
977 return $ret_ar;
979 //'|IK3*segID*segpos*loop*errcode*bht03syn|CTX-IK3*segID*segPos*loopLS*elemPos:compositePos:repPos
980 // revised: 123456789004*IK3*segID*segpos[*segID*segpos*segID*segpos]
981 $ik = explode('*', $err_seg);
982 foreach($ik as $i=>$k) {
983 switch((int)$i) {
984 case 0:$ret_ar['trace'] = $k; break;
985 case 1: break; // IK3
986 case 2: $ret_ar['id'][] = $k; break; // segment ID
987 case 3: $ret_ar['err'][] = $k; break; // segment position
988 case 4: $ret_ar['id'][] = $k; break;
989 case 5: $ret_ar['err'][] = $k; break;
990 case 6: $ret_ar['id'][] = $k; break;
991 case 7: $ret_ar['err'][] = $k; break;
995 return $ret_ar;
999 * Order the csv data array according to the csv table heading row
1000 * so the data to be added to csv table rows are correctly ordered
1001 * the supplied data should be in an array with thie structure
1002 * array['icn'] ['file'][i]['key'] ['claim'][i]['key'] ['type']['type']
1004 * @uses csv_table_header()
1006 * @param array data_ar data array from edih_XXX_csv_data()
1007 * @return array|bool ordered array or false on error
1009 function edih_csv_order($csvdata) {
1011 $wrcsv = array();
1012 $order_ar = array();
1014 foreach($csvdata as $icn=>$data) {
1015 // [icn]['type']['file']['claim']
1016 $ft = $data['type'];
1017 $wrcsv[$icn]['type'] = $ft;
1019 foreach($data as $key=>$val) {
1020 if ($key == 'type') { continue; }
1021 $order_ar[$icn][$key] = csv_table_header($ft, $key);
1022 $ct = count($order_ar[$icn][$key]);
1023 foreach($val as $k=>$rcrd) {
1025 foreach($order_ar[$icn][$key] as $ky=>$vl) {
1026 $wrcsv[$icn][$key][$k][$ky] = $rcrd[$vl];
1031 return $wrcsv;
1035 * insert dashes in ten-digit telephone numbers
1037 * @param string $str_val the telephone number
1038 * @return string the telephone number with dashes
1040 function edih_format_telephone ($str_val) {
1041 $strtel = (string)$str_val;
1042 $strtel = preg_replace('/\D/', '', $strtel);
1043 if ( strlen($strtel) != 10 ) {
1044 csv_edihist_log('edih_format_telephone: invalid argument: '.$str_val);
1045 return $str_val;
1046 } else {
1047 $tel = substr($strtel,0,3) . "-" . substr($strtel,3,3) . "-" . substr($strtel,6);
1049 return $tel;
1053 * order MM DD YYYY values and insert slashes in eight-digit dates
1055 * US MM/DD/YYYY or general YYYY-MM-DD
1057 * @param string $str_val the eight-digit date
1058 * @param string $pref if 'US' (default) anything else means YYYY-MM-DD
1059 * @return string the date with slashes
1061 function edih_format_date ($str_val, $pref = "Y-m-d") {
1062 $strdt = (string)$str_val;
1063 $strdt = preg_replace('/\D/', '', $strdt);
1064 $dt = '';
1065 if (strlen($strdt) == 6) {
1066 $tdy = date('Ymd');
1067 if ($pref == "US") {
1068 // assume mmddyy
1069 $strdt = substr($tdy,0,2).substr($strdt,-2).substr($strdt,0,4);
1070 } else {
1071 // assume yymmdd
1072 $strdt = substr($tdy,0,2).$strdt;
1075 if ($pref == "US") {
1076 $dt = substr($strdt,4,2) . "/" . substr($strdt,6) . "/" . substr($strdt,0,4);
1077 } else {
1078 $dt = substr($strdt,0,4) . "-" . substr($strdt,4,2) . "-" . substr($strdt,6);
1080 return $dt;
1084 * format monetary amounts with two digits after the decimal place
1086 * @todo add other formats
1087 * @param string $str_val the amount string
1088 * @return string the telephone number with dashes
1090 function edih_format_money ($str_val) {
1092 if ($str_val || $str_val === '0') {
1093 $mny = sprintf("$%01.2f", $str_val);
1094 } else {
1095 $mny = $str_val;
1097 return $mny;
1101 * format percentage amounts with % sign
1102 * typical example ".50" from x12 edi segment element
1104 * @param string $str_val the amount string
1105 * @return string the value as a percentage
1107 function edih_format_percent ($str_val) {
1108 $val = (float)$str_val;
1109 if (is_float($val)) {
1110 $pct = $val*100 . '%';
1111 } else {
1112 $pct = $str_val.'%';
1114 return $pct;
1118 * HTML string for table thead element
1120 * @uses csv_table_header()
1121 * @param string
1122 * @param string
1123 * @return string
1125 function csv_thead_html($file_type, $csv_type, $tblhd=null) {
1127 if (is_array($tblhd) & count($tblhd) ) {
1128 $hvals = $tblhd;
1129 } else {
1130 $hvals = csv_table_header($file_type, $csv_type);
1132 if ( is_array($hvals) && count($hvals) ) {
1133 $str_html = '';
1134 } else {
1135 return false;
1137 $str_html .= "<thead>".PHP_EOL."<tr>".PHP_EOL;
1138 foreach($hvals as $val) {
1139 $str_html .="<th>$val</th>";
1141 $str_html .= PHP_EOL."</tr>".PHP_EOL."</thead>".PHP_EOL;
1143 return $str_html;
1148 * Give the column headings for the csv files
1150 * @uses csv_file_type()
1151 * @param string $file_type one of our edi types
1152 * @param string $csv_type either 'file' or 'claim'
1153 * @return array
1155 function csv_table_header($file_type, $csv_type) {
1157 $ft = csv_file_type($file_type);
1158 $ct = strpos('|file', $csv_type) ? 'file' : $csv_type;
1159 $ct = strpos('|claim', $ct) ? 'claim' : $ct;
1161 $hdr = array();
1162 if (!$ft || !$ct ) {
1163 csv_edihist_log ('csv_table_header error: incorrect file ['.$file_type.']or csv ['.$csv_type.'] type');
1164 return $hdr;
1167 if ($ct === 'file') {
1168 switch((string)$ft) {
1169 //case 'ack': $hdr = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code'); break;
1170 //case 'ebr': $hdr = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); break;
1171 //case 'ibr': $hdr = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch'); break;
1173 case 'f837': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); break;
1174 case 'ta1': $hdr = array('Date', 'FileName', 'Control', 'Trace', 'Code'); break;
1175 case 'f997': $hdr = array('Date', 'FileName', 'Control', 'Trace', 'RspType', 'RejCt'); break;
1176 case 'f276': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); break;
1177 case 'f277': $hdr = array('Date', 'FileName', 'Control', 'Accept', 'AccAmt', 'Reject', 'RejAmt'); break;
1178 case 'f270': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'x12Partner'); break;
1179 case 'f271': $hdr = array('Date', 'FileName', 'Control', 'Claim_ct', 'Reject', 'Payer'); break;
1180 case 'f278': $hdr = array('Date', 'FileName', 'Control', 'TrnCount', 'Auth', 'Payer'); break;
1181 case 'f835': $hdr = array('Date', 'FileName', 'Control', 'Trace', 'Claim_ct', 'Denied', 'Payer'); break;
1183 } elseif ($ct === 'claim') {
1184 switch((string)$ft) {
1185 //case 'ebr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break;
1186 //case 'ibr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break;
1187 //case 'dpr': $hdr = array('PtName','SvcDate', 'CLM01', 'Status', 'Batch', 'FileName', 'Payer'); break;
1189 case 'f837': $hdr = array('PtName', 'SvcDate', 'CLM01', 'InsLevel', 'BHT03', 'FileName', 'Fee', 'PtPaid', 'Provider' ); break;
1190 case 'f997': $hdr = array('PtName', 'RspDate', 'Trace', 'Status', 'Control', 'FileName', 'RspType', 'err_seg'); break;
1191 case 'f276': $hdr = array('PtName', 'SvcDate', 'CLM01', 'ClaimID', 'BHT03', 'FileName', 'Payer', 'Trace'); break;
1192 case 'f277': $hdr = array('PtName', 'SvcDate', 'CLM01', 'Status', 'BHT03', 'FileName', 'Payer', 'Trace'); break;
1193 case 'f270': $hdr = array('PtName', 'ReqDate', 'Trace', 'InsBnft', 'BHT03', 'FileName', 'Payer'); break;
1194 case 'f271': $hdr = array('PtName', 'RspDate', 'Trace', 'Status', 'BHT03', 'FileName', 'Payer'); break;
1195 case 'f278': $hdr = array('PtName', 'FileDate', 'Trace', 'Status', 'BHT03', 'FileName', 'Auth', 'Payer'); break;
1196 case 'f835': $hdr = array('PtName', 'SvcDate', 'CLM01', 'Status', 'Trace', 'FileName', 'ClaimID', 'Pmt', 'PtResp', 'Payer'); break;
1198 } else {
1199 // unexpected error
1200 csv_edihist_log ('edih_csv_table_header() error: failed to match file type ['.$ft.'] or csv type ['.$ct.']');
1201 return false;
1203 if (count($hdr) ) {
1204 return $hdr;
1205 } else {
1206 return false;
1211 function csv_files_header($file_type, $csv_type) {
1213 $tp = csv_file_type($type);
1214 if (!$tp) {
1215 csv_edihist_log('csv_files_header: incorrect type '.$file_type);
1216 return false;
1218 if (!strpos('|file|claim', $csv_type) ) {
1219 csv_edihist_log('csv_files_header error: incorrect csv type '.$csv_type);
1220 return false;
1223 $ft = strpos('|277', $file_type) ? 'f277' : $file_type;
1224 $ft = strpos('|835', $file_type) ? 'era' : $ft;
1225 $ft = strpos('|837', $file_type) ? 'batch' : $ft;
1226 $ft = strpos('|999|997|ack|ta1', $file_type) ? 'f997' : $ft;
1228 $csv_hd_ar = array();
1229 // dataTables: | 'date' | 'file_name (link)' | 'file_text (link fmt)' | 'claim_ct' | 'reject_ct' |
1230 $csv_hd_ar['ack']['file'] = array('Date', 'FileName', 'isa13', 'ta1ctrl', 'Code');
1231 $csv_hd_ar['ebr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1232 $csv_hd_ar['ibr']['file'] = array('Date', 'FileName', 'clrhsid', 'claim_ct', 'reject_ct', 'Batch');
1234 // dataTables: | 'date' | 'file_name (link)' | 'file_text (link fmt)' | 'claim_ct' | 'partner' |
1235 $csv_hd_ar['batch']['file'] = array('Date', 'FileName', 'Ctn_837', 'claim_ct', 'x12_partner');
1236 $csv_hd_ar['ta1']['file'] = array('Date', 'FileName', 'Ctn_ta1', 'ta1ctrl', 'Code');
1237 $csv_hd_ar['f997']['file'] = array('Date', 'FileName', 'Ctn_999', 'ta1ctrl', 'RejCt');
1238 $csv_hd_ar['f277']['file'] = array('Date', 'FileName', 'Ctn_277', 'Accept', 'AccAmt', 'Reject', 'RejAmt');
1239 $csv_hd_ar['f270']['file'] = array('Date', 'FileName', 'Ctn_270', 'claim_ct', 'x12_partner');
1240 $csv_hd_ar['f271']['file'] = array('Date', 'FileName', 'Ctn_271', 'claim_ct', 'Denied', 'Payer');
1241 $csv_hd_ar['era']['file'] = array('Date', 'FileName', 'Trace', 'claim_ct', 'Denied', 'Payer');
1243 // dataTables: | 'pt_name' | 'svc_date' | 'clm01 (link clm)' | 'status (mouseover)' | b f t (links to files) | message (mouseover) |
1244 $csv_hd_ar['ebr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1245 $csv_hd_ar['ibr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1246 $csv_hd_ar['dpr']['claim'] = array('PtName','SvcDate', 'clm01', 'Status', 'Batch', 'FileName', 'Payer');
1248 // dataTables: | 'pt_name' | 'svc_date' | 'clm01 (link clm)' | 'status (mouseover)' | 'bht03_837 (link rsp)' | message (mouseover) |
1249 $csv_hd_ar['batch']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'Ctn_837', 'File_837', 'Fee', 'PtPaid', 'Provider' );
1250 $csv_hd_ar['f997']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'ak_num', 'File_997', 'Ctn_837', 'err_seg');
1251 $csv_hd_ar['f277']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_277', 'File_277', 'payer_name', 'claim_id', 'bht03_837');
1252 $csv_hd_ar['f270']['claim'] = array('PtName', 'SvcDate', 'clm01', 'InsLevel', 'st_270', 'File_270', 'payer_name', 'bht03_270');
1253 $csv_hd_ar['f271']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'st_271', 'File_271', 'payer_name', 'bht03_270');
1254 $csv_hd_ar['era']['claim'] = array('PtName', 'SvcDate', 'clm01', 'Status', 'trace', 'File_835', 'claimID', 'Pmt', 'PtResp', 'Payer');
1256 return $csv_hd_ar[$ft][$csv_type];
1261 * adapted from http://scratch99.com/web-development/javascript/convert-bytes-to-mb-kb/
1263 * @param int
1265 * @return string
1267 function csv_convert_bytes($bytes) {
1268 $sizes = array('Bytes', 'KB', 'MB', 'GB', 'TB');
1269 if ($bytes == 0) { return 'n/a'; }
1270 $i = floor( log($bytes) / log(1024) );
1271 //$i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
1272 if ($i == 0) {
1273 return $bytes.' '.$sizes[$i];
1274 } else {
1275 return round($bytes / pow(1024, $i), 1).' '.$sizes[$i];
1280 * Determine whether an array is multidimensional
1282 * @param array
1283 * @return bool false if arrayis multidimensional
1285 function csv_singlerecord_test ( $array ) {
1286 // the two versions of count() are compared
1287 // if the array has a sub-array, count recursive is greater
1288 if ( is_array($array) ) {
1289 $is_sngl = count($array, COUNT_RECURSIVE ) == count( $array, COUNT_NORMAL);
1290 } else {
1291 $is_sngl = false;
1294 return $is_sngl;
1298 * give first and last index keys for an array
1300 * @param array
1301 * @return array
1303 function csv_array_bounds($array) {
1304 // get the segment array bounds
1305 $ret_ar = array();
1306 if (is_array($array) && count($array)) {
1307 if (reset($array) !== false) { $ret_ar[0] = key($array); }
1308 if (end($array) !== false) { $ret_ar[1] = key($array); }
1310 return $ret_ar;
1314 * return a csv file as an associative array
1315 * the first row is the header or array keys for the row
1316 * array structure:
1317 * array[i]=>array(hdr0=>csvrow[0], hdr1=>csvrow[1], hdr2=>csvrow[2], ...)
1319 * @param string file type e.g. f837
1320 * @param string csv type claim or file
1321 * @return array
1323 function csv_assoc_array($file_type, $csv_type) {
1325 if (!$file_type || !$csv_type) {
1326 csv_edihist_log('csv_assoc_array; invalid arguments ft: '.$file_type.' csvt: '.$csv_type);
1327 return false;
1329 $csv_ar = array();
1330 $h = array();
1331 $fp = '';
1333 $param = csv_parameters($file_type);
1334 $fcsv = (strpos($csv_type, 'aim')) ? 'claims_csv' : 'files_csv';
1336 $fp = (isset($param[$fcsv])) ? $param[$fcsv] : '';
1337 if (!is_file($fp)) {
1338 csv_edihist_log('csv_assoc_array; invalid csv file '.basename($fp));
1339 return $csv_ar;
1341 $ct = 0;
1342 $row = 0;
1343 $ky = -1;
1344 if (($fh = fopen($fp, "rb")) !== false) {
1345 while (($data = fgetcsv($fh, 2048, ",")) !== false) {
1346 if ( is_null($data) ) { continue; }
1347 if ($row) {
1348 for($i=0; $i<$ct; $i++) {
1349 $csv_ar[$ky][$h[$i]] = $data[$i];
1351 } else {
1352 $ct = count($data);
1353 $h = $data;
1355 $row++;
1356 $ky++;
1358 fclose($fh);
1359 } else {
1360 // invalid file path
1361 csv_edihist_log('csv_assoc_array; invalid file path '.$fp);
1362 return false;
1365 return $csv_ar;
1370 * A multidimensional array will be flattened to a single row.
1372 * @param array $array array to be flattened
1373 * @return array
1375 function csv_array_flatten($array) {
1377 if (!is_array($array)) {return FALSE;}
1378 $result = array();
1379 foreach ($array as $key => $value) {
1380 if (is_array($value)) {
1381 $result = array_merge($result, csv_array_flatten($value));
1382 } else {
1383 $result[$key] = $value;
1386 return $result;
1391 * Write parsed data from edi x12 files to csv file
1393 * @uses csv_parameters()
1394 * @usescsv_table_header()
1396 * @param array data array from parse functions
1397 * @return bool true if no error
1399 function edih_csv_write($csv_data) {
1401 if ( ! (is_array($csv_data) && count($csv_data)) ){
1402 csv_edihist_log('edih_csv_write(): invalid data array');
1403 return false;
1406 foreach($csv_data as $icn=>$isa) {
1407 // should be array[icn] => [file][j][key] [claim][j][key] [type]
1408 $ft = ( isset($isa['type']) ) ? $isa['type'] : '';
1409 if (!$ft) {
1410 csv_edihist_log('edih_csv_write(): invalid file type');
1411 continue;
1414 $param = csv_parameters($ft);
1415 $f_hdr = csv_table_header($ft, 'file');
1416 $c_hdr = csv_table_header($ft, 'claim');
1417 if (is_array($param)) {
1418 // if either csv files does not exist, create them both
1419 // all unlisted files in type directory will be processed on next process round
1420 if (is_file($param['files_csv']) && (filesize($param['files_csv']) > 20)) {
1421 csv_edihist_log('edih_csv_write: csv check for files csv '.$ft);
1422 } else {
1423 $nfcsv = $param['files_csv'];
1424 $fh = fopen($nfcsv, 'wb');
1425 if ($fh !== false) {
1426 fputcsv($fh, $f_hdr);
1427 fclose($fh);
1428 chmod($nfcsv, 0600);
1430 csv_edihist_log('edih_csv_write: created files_csv file for '.$ft);
1432 if (is_file($param['claims_csv']) && filesize($param['claims_csv'])) {
1433 csv_edihist_log('edih_csv_write: csv check for claims csv '.$ft);
1434 } else {
1435 $nfcsv = $param['claims_csv'];
1436 $fh = fopen($nfcsv, 'wb');
1437 if ($fh !== false) {
1438 fputcsv($fh, $c_hdr);
1439 fclose($fh);
1440 chmod($nfcsv, 0600);
1442 csv_edihist_log('edih_csv_write: created claims_csv file for '.$ft);
1444 } else {
1445 csv_edihist_log('edih_csv_write: parameters error for type '.$ft);
1446 return false;
1449 foreach($isa as $key=>$data) {
1450 if ($key == 'type') { continue; }
1451 // get the csv file path from parameters
1452 $fp = ($key == 'file') ? $param['files_csv'] : $param['claims_csv'];
1453 // get the csv row header
1454 $order_ar = ($key == 'file') ? $f_hdr : $c_hdr;
1455 $ct = count($order_ar);
1456 $chrs = 0;
1457 $rws = 0;
1459 $fh = fopen( $fp, 'ab');
1460 if (is_resource($fh)) {
1461 // to assure proper order of data in each row, the
1462 // csv row is assembled by matching keys to the header row
1463 foreach($data as $ky=>$row) {
1464 $csvrow = array();
1465 for ($i=0; $i<$ct; $i++) {
1466 $csvrow[$i] = $row[$order_ar[$i]];
1468 $chrs += fputcsv ( $fh , $csvrow );
1469 $rws++;
1471 } else {
1472 csv_edihist_log('edih_csv_write(): failed to open '.$fp);
1473 return false;
1476 csv_edihist_log('edih_csv_write() wrote '.$rws.' rows to '.basename($fp));
1480 return $rws;
1485 * Search a csv record file and return the row or values from selected columns
1487 * This function requires that the $search_ar parameter be an array
1488 * with keys ['s_val']['s_col']['r_cols'], and 'r_cols' is an array
1489 * 's_val' is the search value, s_col is the column to check, r_cols is an array
1490 * of column numbers from which values are returned. If r_cols is not an array,
1491 * then the entire row will be returned. If the 'expect' parameter is 1, then
1492 * the search will stop after the first success and return the result. Otherwise, the
1493 * entire file will be searched.
1494 * ex: csv_search_record('batch', 'claim', array('s_val'=>'0024', 's_col'=>9, 'r_cols'=>array(1, 2, 7)), "1" )
1496 * @uses csv_parameters()
1497 * @param string $file_type
1498 * @param string $csv_type
1499 * @param array $search_ar
1500 * @param mixed $expect
1501 * @return array
1503 function csv_search_record($file_type, $csv_type, $search_ar, $expect='1') {
1505 csv_edihist_log("csv_search_record: ".strval($file_type)." ".strval($csv_type)." ".strval($search_ar['s_val']));
1507 $tp = csv_file_type($file_type);
1508 if (!$tp) {
1509 csv_edihist_log("csv_search_record: incorrect type $file_type");
1510 return false;
1513 $params = csv_parameters($tp);
1515 if ($csv_type == 'claim') {
1516 $fp = $params['claims_csv'];
1517 } elseif ($csv_type == 'file') {
1518 $fp = $params['files_csv'];
1519 } else {
1520 csv_edihist_log('csv_search_record: incorrect csv type '.$csv_type);
1521 return FALSE;
1524 if (!is_array($search_ar) || array_keys($search_ar) != array('s_val', 's_col', 'r_cols')) {
1525 csv_edihist_log('csv_search_record: invalid search criteria');
1526 return FALSE;
1528 $sv = $search_ar['s_val'];
1529 $sc = $search_ar['s_col'];
1530 $rv = (is_array($search_ar['r_cols']) && count($search_ar['r_cols'])) ? $search_ar['r_cols'] : 'all';
1531 $ret_ar = array();
1532 $idx = 0;
1533 if (($fh1 = fopen($fp, "r")) !== false) {
1534 while (($data = fgetcsv($fh1)) !== false) {
1535 // check for a match
1536 if ($data[$sc] == $sv) {
1537 if ($rv == 'all') {
1538 $ret_ar[$idx] = $data;
1539 } else {
1540 // now loop through the 'r_cols' array for data index
1541 $dct = count($data);
1542 foreach($rv as $c) {
1543 // make sure we don't access a non-existing index
1544 if ($c >= $dct) { continue; }
1546 $ret_ar[$idx][] = $data[$c];
1549 $idx++;
1550 if ($expect == '1') { break; }
1553 fclose($fh1);
1554 } else {
1555 csv_edihist_log('csv_search_record: failed to open '.$fp);
1556 return false;
1558 if (empty($ret_ar) ) {
1559 return false;
1560 } else {
1561 return $ret_ar;
1566 * Search the 'claims' csv table for the patient control and find the associated file name
1568 * Searchtype
1569 * In 'claims' csv tables, clm01 is position 2, ISA13 number is pos 4, and filename is pos 5;
1570 * Since we are interested usually in the filename, ISA13 is irrelevant usually.
1572 * @uses csv_parameters()
1573 * @uses csv_pid_enctr_parse()
1574 * @param string patient control-- pid-encounter, encounter, or pid
1575 * @param string filetype -- x12 type or f837, f277, etc
1576 * @param string search type encounter, pid, or clm01
1577 * @return array|bool [i] data row array or empty on error
1579 function csv_file_by_enctr($clm01, $filetype='f837') {
1581 // return array of [i](pid_encounter, filename), there may be more than one file
1583 if (!$clm01) {
1584 return 'invalid encounter data<br>' . PHP_EOL;
1587 $ret_ar = array();
1588 $ft = csv_file_type($filetype);
1590 if (!$ft) {
1591 csv_edihist_log('csv_file_by_enctr: incorrect file type '.$filetype);
1592 return $ret_ar;
1593 } else {
1594 $params = csv_parameters($ft);
1595 //$fp = isset($params['claims_csv']) ? dirname(__FILE__).$params['claims_csv'] : false;
1596 $fp = isset($params['claims_csv']) ? $params['claims_csv'] : false;
1597 $h_ar = csv_table_header($ft, 'claim');
1598 $hct = count($h_ar);
1599 if (!$fp) {
1600 csv_edihist_log('csv_file_by_enctr: incorrect file type '.$filetype);
1601 return $ret_ar;
1605 $enct = csv_pid_enctr_parse(strval($clm01));
1606 $p = (isset($enct['pid'])) ? $enct['pid'] : '';
1607 $e = (isset($enct['enctr'])) ? $enct['enctr'] : '';
1608 if ($p && $e) {
1609 $pe = $p.'-'.$e;
1610 $srchtype = '';
1611 } elseif ($e) {
1612 $srchtype = 'encounter';
1613 } elseif ($p) {
1614 $srchtype = 'pid';
1615 } else {
1616 csv_edihist_log('csv_file_by_enctr: unable to determine encounter value '.$clm01);
1617 return 'unable to determine encounter value '.$clm01.'<br />'.PHP_EOL;
1619 // OpenEMR creates CLM01 as nnn-nnn in genX12 batch
1620 //$pm = preg_match('/\D/', $enctr, $match2, PREG_OFFSET_CAPTURE);
1621 $val = array();
1622 //array_combine ( array $keys , array $values )
1623 // in 'claims' csv tables, clm01 is position 2 and filename is position 5
1624 if (($fh1 = fopen($fp, "r")) !== FALSE) {
1625 if ($srchtype == 'encounter') {
1626 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
1627 // check for a match
1628 if (strpos($data[2], $e)) {
1629 $te = substr($data[2], strpos($data[2],'-')+1);
1630 if (strcmp($te, $e) === 0) {
1631 for ($i=0; $i<$hct; $i++) { $val[$h_ar[$i]] = $data[$i]; }
1632 $ret_ar[] = $val; // array_combine($h_ar, $data);
1636 } elseif ($srchtype == 'pid') {
1637 while (($data = fgetcsv($fh1, 1024, ',')) !== FALSE) {
1638 if (strpos($data[2], $p) !== false) {
1639 $te = (strpos($data[2], '-')) ? substr($data[2], 0, strpos($data[2],'-')) : '';
1640 if (strcmp($te, $p) === 0) {
1641 for ($i=0; $i<$hct; $i++) { $val[$h_ar[$i]] = $data[$i]; }
1642 $ret_ar[] = $val; // $ret_ar[] = array_combine($h_ar, $data);
1646 } else {
1647 while (($data = fgetcsv($fh1, 1024, ",")) !== FALSE) {
1648 // check for a match
1649 if ( strcmp($data[2], $pe) === 0 ) {
1650 for ($i=0; $i<$hct; $i++) { $val[$h_ar[$i]] = $data[$i]; }
1651 $ret_ar[] = $val; // $ret_ar[] = array_combine($h_ar, $data);
1655 fclose($fh1);
1656 } else {
1657 csv_edihist_log('csv_file_by_enctr: failed to open csv file '.basename($fp));
1658 return false;
1660 return $ret_ar;
1665 * get the x12 file containing the control_num ISA13
1667 * @todo the csv for x12 files 999, 277, 835, 837 must have the control number
1669 * @uses csv_search_record()
1670 * @param string $control_num the interchange control number, isa13
1671 * @return string the file name
1673 function csv_file_by_controlnum($type, $control_num) {
1674 // get the batch file containing the control_num
1676 $tp = csv_file_type($type);
1678 $hdr = csv_table_header($tp, 'file');
1679 $scol = array_search('Control', $hdr);
1680 $rcol = array_search('FileName', $hdr);
1682 // $search_ar should have keys ['s_val']['s_col'] array(['r_cols'][])
1683 // like "batch', 'claim, array(9, '0024', array(1, 2, 7))
1684 //$csv_hd_ar['batch']['file'] = array('time', 'file_name', 'control_num', 'claims', 'x12_partner', 'x12_version');
1686 $fn = '';
1687 $ctln = (strlen($control_num) >= 9) ? substr($control_num, 0, 9) : $control_num;
1688 $search = array('s_val'=>$ctln, 's_col'=>$scol, 'r_cols'=>array($rcol));
1689 $result = csv_search_record($tp, 'file', $search, "1");
1690 if (is_array($result) && count($result[0]) == 1) {
1691 $fn = $result[0][0];
1693 return $fn;
1698 * Search the csv table to obtain the file name for a given
1699 * trace value (835 / 997 999 type only)
1701 * Note: the 997/999 trace is the ISA13 of a batch file
1704 * @param string trace value (TRN02, TA101, or BHT03)
1705 * @param string from type (default is f835)
1706 * @param string to type (default is f835)
1707 * @return string file name or empty string
1709 function csv_file_by_trace($trace, $from_type='f835', $to_type='f837') {
1710 // get the file referenced by the trace value
1712 $ft = ($from_type) ? csv_file_type($from_type) : '';
1713 $tt = ($to_type) ? csv_file_type($to_type) : '';
1714 $fn = '';
1715 $csv_type = '';
1716 $type = '';
1717 $search = array();
1719 csv_edihist_log("csv_file_by_trace: $trace from $ft to $tt");
1721 // $search_ar should have keys ['s_val']['s_col'] array(['r_cols'])
1722 // like "f837', 'claim, array(9, '0024', array(1, 2, 7))
1724 if ($ft == 'f835') {
1725 // trace payment to status or claim
1726 $search = array('s_val'=>$trace, 's_col'=>3, 'r_cols'=>'All');
1727 $type = $tt;
1728 $csv_type = 'file';
1729 } elseif ($ft == 'f997') {
1730 // trace ACK to batch file
1731 $icn = (is_numeric($trace) && strlen($trace) >= 9) ? substr($trace, 0, 9) : $trace;
1732 $search = array('s_val'=>$icn, 's_col'=>2, 'r_cols'=>'All');
1733 $type = $tt;
1734 $csv_type = 'file';
1735 } elseif ($ft == 'f277') {
1736 // trace status to status req or claim
1737 if ($tt == 'f276') {
1738 $search = array('s_val'=>$trace, 's_col'=>7, 'r_cols'=>'All');
1739 $type = $tt;
1740 $csv_type = 'claim';
1741 } elseif ($tt == 'f837') {
1742 // expect CLM01 for trace value
1743 $search = array('s_val'=>$trace, 's_col'=>2, 'r_cols'=>'All');
1744 $type = $tt;
1745 $csv_type = 'claim';
1747 } elseif ($ft == 'f271') {
1748 // trace benefit to benefit req
1749 if ($tt == 'f270') {
1750 $search = array('s_val'=>$trace, 's_col'=>2, 'r_cols'=>'All');
1751 $type = $tt;
1752 $csv_type = 'claim';
1754 } elseif ($ft == 'f278') {
1755 // trace auth to auth req
1756 $search = array('s_val'=>$trace, 's_col'=>2, 'r_cols'=>'All');
1757 $type = 'f278';
1758 $csv_type = 'claim';
1759 } else {
1760 csv_edihist_log('csv_file_by_trace: incorrect file type '.$file_type);
1761 return $fn;
1764 if ($type && $csv_type && $search) {
1765 $result = csv_search_record($type, $csv_type, $search, false);
1766 if (is_array($result) && count($result)) {
1767 if ($ft == 'f278') {
1768 foreach($result as $r) {
1769 if ($r[6] == 'Rsp' || $r[6] == 'Reply') {
1770 $fn = $result[0][5];
1771 break;
1774 } elseif ($csv_type == 'claim') {
1775 $fn = $result[0][5];
1776 } else {
1777 $fn = $result[0][1];
1779 } else {
1780 csv_edihist_log("csv_file_by_trace: search failed $type csv $csv_type for trace $trace $from_type $to_type");
1782 } else {
1783 csv_edihist_log("csv_file_by_trace: error type $type csv $csv_type for trace $trace $from_type $to_type");
1785 return $fn;
1789 * list claim records with Denied or Reject status in given file
1791 * @param string
1792 * @param string
1794 * @return array
1796 function csv_denied_by_file($filetype, $filename, $trace='') {
1798 $ret_ar = array();
1799 $ft = csv_file_type($filetype);
1800 if (strpos('|f997|f271|f277|f835', $ft)) {
1801 $param = csv_parameters($ft);
1802 $csv_file = $param['claims_csv'];
1803 } else {
1804 csv_edihist_log("csv_errors_by_file: incorrect file type $filetype");
1805 return $ret_ar;
1808 csv_edihist_log("csv_errors_by_file: $ft searching $filename with trace $trace");
1810 if (($fh1 = fopen($csv_file, "r")) !== false) {
1811 if ($ft == 'f835') {
1812 while (($data = fgetcsv($fh1, 1024, ",")) !== false) {
1813 // check filename, then status
1814 if ($trace) {
1815 if ($data[4] == $trace) {
1816 if (!in_array($data[3], array('1', '2', '3', '19', '20', '21')) ) { $ret_ar[] = $data; }
1818 } elseif ($data[5] == $filename) {
1819 if (!in_array($data[3], array('1', '2', '3', '19', '20', '21')) ) { $ret_ar[] = $data; }
1823 } elseif ($ft == 'f277') {
1824 while (($data = fgetcsv($fh1, 1024, ",")) !== false) {
1825 if ($data[5] == $filename) {
1826 if ( !strpos('|A1|A2|A5', substr($data[3], 0, 2))) {
1827 $ret_ar[] = $data;
1831 } elseif (strpos('|f997|f999|f271', $ft)) {
1832 while (($data = fgetcsv($fh1, 1024, ",")) !== false) {
1833 if ($data[5] == $filename) {
1834 if ($data[3] !== 'A') {
1835 $ret_ar[] = $data;
1839 } else {
1840 csv_edihist_log("csv_errors_by_file: file type did not match $filetype");
1842 fclose($fh1);
1845 return $ret_ar;
1850 * A function to try and assure the pid-encounter is correctly parsed
1852 * assume a format of pid-encounter, since that is sent in the OpenEMR x12 837
1854 * @param string $pid_enctr the value from element CLM01
1855 * return array array('pid' => $pid, 'enctr' => $enc)
1857 function csv_pid_enctr_parse( $pid_enctr ) {
1858 // evaluate the patient account field
1860 if (!$pid_enctr || !is_string($pid_enctr) ) {
1861 csv_edihist_log("csv_pid_enctr_parse: invalid argument");
1862 return false;
1864 $pval = trim($pid_enctr);
1865 if ( strpos($pval, '-') ) {
1866 $pid = substr($pval, 0, strpos($pval, '-'));
1867 $enc = substr($pval, strpos($pval, '-')+1);
1868 } elseif ( ctype_digit($pval) ) {
1869 if ( preg_match('/(19|20)\d{2}[01]\d{1}[0-3]\d{1}/', $pval) ) {
1870 $enc = $pval;
1871 } else {
1872 $enc = ( strlen($pval) ) >= ENCOUNTER_MIN_DIGIT_LENGTH ? $pval : '';
1873 $pid = '';
1875 } elseif ( preg_match('/\D/', $pval, $match2, PREG_OFFSET_CAPTURE) ) {
1876 $inv_split = (count($match2)) ? preg_split('/\D/', $pval, 2, PREG_SPLIT_NO_EMPTY) : false;
1877 if ($inv_split) {
1878 $pid = $inv_split[0];
1879 $enc = $inv_split[1];
1881 } else {
1882 $enc = ( strlen($pval) ) >= ENCOUNTER_MIN_DIGIT_LENGTH ? $pval : '';
1883 $pid = '';
1885 return array('pid' => $pid, 'enctr' => $enc);