dump db version
[openemr.git] / interface / orders / procedure_stats.php
blob658d9c9110615a9a196765330f3e9b288cb8dbbc
1 <?php
2 /**
3 * This module creates statistical reports related to lab tests and
4 * other procedure orders.
6 * @package OpenEMR
7 * @link http://www.open-emr.org
8 * @author Rod Roark <rod@sunsetsystems.com>
9 * @author Roberto Vasquez <robertogagliotta@gmail.com>
10 * @author Brady Miller <brady.g.miller@gmail.com>
11 * @copyright Copyright (c) 2010-2016 Rod Roark <rod@sunsetsystems.com>
12 * @copyright Copyright (c) 2015 Roberto Vasquez <robertogagliotta@gmail.com>
13 * @copyright Copyright (c) 2017 Brady Miller <brady.g.miller@gmail.com>
14 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
18 require_once("../globals.php");
19 require_once("../../library/patient.inc");
20 require_once("../../library/acl.inc");
21 require_once("../../custom/code_types.inc.php");
22 require_once "$srcdir/options.inc.php";
24 // Might want something different here.
26 if (! acl_check('acct', 'rep')) {
27 die(xlt("Unauthorized access."));
30 $from_date = isset($_POST['form_from_date']) ? DateToYYYYMMDD($_POST['form_from_date']) : '0000-00-00';
31 $to_date = isset($_POST['form_to_date']) ? DateToYYYYMMDD($_POST['form_to_date']) : date('Y-m-d');
32 $form_by = $_POST['form_by']; // this is a scalar
33 $form_show = $_POST['form_show']; // this is an array
34 $form_facility = isset($_POST['form_facility']) ? $_POST['form_facility'] : '';
35 $form_sexes = isset($_POST['form_sexes']) ? $_POST['form_sexes'] : '3';
36 $form_output = isset($_POST['form_output']) ? 0 + $_POST['form_output'] : 1;
38 if (empty($form_by)) {
39 $form_by = '4';
42 if (empty($form_show)) {
43 $form_show = array('1');
46 // One of these is chosen as the left column, or Y-axis, of the report.
48 $report_title = xl('Procedure Statistics Report');
49 $arr_by = array(
50 4 => xl('Specific Result'),
51 5 => xl('Followups Indicated'),
54 // This will become the array of reportable values.
55 $areport = array();
57 // This accumulates the bottom line totals.
58 $atotals = array();
60 $arr_show = array(
61 // '.total' => array('title' => 'Total Positives'),
62 '.tneg' => array('title' => 'Total Negatives'),
63 '.age' => array('title' => 'Age Category'),
64 ); // info about selectable columns
66 $arr_titles = array(); // will contain column headers
68 // Query layout_options table to generate the $arr_show table.
69 // Table key is the field ID.
70 $lres = sqlStatement("SELECT field_id, title, data_type, list_id, description " .
71 "FROM layout_options WHERE " .
72 "form_id = 'DEM' AND uor > 0 AND field_id NOT LIKE 'em%' " .
73 "ORDER BY group_id, seq, title");
74 while ($lrow = sqlFetchArray($lres)) {
75 $fid = $lrow['field_id'];
76 if ($fid == 'fname' || $fid == 'mname' || $fid == 'lname') {
77 continue;
80 $arr_show[$fid] = $lrow;
81 $arr_titles[$fid] = array();
84 // Compute age in years given a DOB and "as of" date.
86 function getAge($dob, $asof = '')
88 if (empty($asof)) {
89 $asof = date('Y-m-d');
92 $a1 = explode('-', substr($dob, 0, 10));
93 $a2 = explode('-', substr($asof, 0, 10));
94 $age = $a2[0] - $a1[0];
95 if ($a2[1] < $a1[1] || ($a2[1] == $a1[1] && $a2[2] < $a1[2])) {
96 --$age;
99 // echo "<!-- $dob $asof $age -->\n"; // debugging
100 return $age;
103 $cellcount = 0;
105 function genStartRow($att)
107 global $cellcount, $form_output;
108 if ($form_output != 3) {
109 echo " <tr $att>\n";
112 $cellcount = 0;
115 function genEndRow()
117 global $form_output;
118 if ($form_output == 3) {
119 echo "\n";
120 } else {
121 echo " </tr>\n";
125 function getListTitle($list, $option)
127 $row = sqlQuery("SELECT title FROM list_options WHERE " .
128 "list_id = ? AND option_id = ? AND activity = 1", array($list, $option));
129 if (empty($row['title'])) {
130 return $option;
133 return $row['title'];
136 // Usually this generates one cell, but allows for two or more.
138 function genAnyCell($data, $right = false, $class = '')
140 global $cellcount, $form_output;
141 if (!is_array($data)) {
142 $data = array(0 => $data);
145 foreach ($data as $datum) {
146 if ($form_output == 3) {
147 if ($cellcount) {
148 echo ',';
151 echo '"' . attr($datum) . '"';
152 } else {
153 echo " <td";
154 if ($class) {
155 echo " class='" . attr($class) . "'";
158 if ($right) {
159 echo " align='right'";
162 echo ">" . text($datum) . "</td>\n";
165 ++$cellcount;
169 function genHeadCell($data, $right = false)
171 genAnyCell($data, $right, 'dehead');
174 // Create an HTML table cell containing a numeric value, and track totals.
176 function genNumCell($num, $cnum)
178 global $atotals, $form_output;
179 $atotals[$cnum] += $num;
180 if (empty($num) && $form_output != 3) {
181 $num = '&nbsp;';
184 genAnyCell($num, true, 'detail');
187 // Helper function called after the reporting key is determined for a row.
189 function loadColumnData($key, $row)
191 global $areport, $arr_titles, $arr_show;
193 // If no result, do nothing.
194 if (empty($row['abnormal'])) {
195 return;
198 // If first instance of this key, initialize its arrays.
199 if (empty($areport[$key])) {
200 $areport[$key] = array();
201 $areport[$key]['.prp'] = 0; // previous pid
202 $areport[$key]['.wom'] = 0; // number of positive results for women
203 $areport[$key]['.men'] = 0; // number of positive results for men
204 $areport[$key]['.neg'] = 0; // number of negative results
205 $areport[$key]['.age'] = array(0,0,0,0,0,0,0,0,0); // age array
206 foreach ($arr_show as $askey => $dummy) {
207 if (substr($askey, 0, 1) == '.') {
208 continue;
211 $areport[$key][$askey] = array();
215 // Flag this patient as having been encountered for this report row.
216 $areport[$key]['.prp'] = $row['pid'];
218 // Collect abnormal results only, except for a column of total negatives.
219 if ($row['abnormal'] == 'no') {
220 ++$areport[$key]['.neg'];
221 return;
224 // Increment the correct sex category.
225 if (strcasecmp($row['sex'], 'Male') == 0) {
226 ++$areport[$key]['.men'];
227 } else {
228 ++$areport[$key]['.wom'];
231 // Increment the correct age category.
232 $age = getAge($row['DOB'], $row['date_ordered']);
233 $i = min(intval(($age - 5) / 5), 8);
234 if ($age < 11) {
235 $i = 0;
238 ++$areport[$key]['.age'][$i];
240 // For each patient attribute to report, this increments the array item
241 // whose key is the attribute's value. This works well for list-based
242 // attributes. A key of "Unspecified" is used where the attribute has
243 // no assigned value.
244 foreach ($arr_show as $askey => $dummy) {
245 if (substr($askey, 0, 1) == '.') {
246 continue;
249 $status = empty($row[$askey]) ? 'Unspecified' : $row[$askey];
250 $areport[$key][$askey][$status] += 1;
251 $arr_titles[$askey][$status] += 1;
255 // This is called for each row returned from the query.
257 function process_result_code($row)
259 global $areport, $arr_titles, $form_by;
261 // Specific Results. One row for each result name.
263 if ($form_by === '4') {
264 $key = $row['order_name'] . ' / ' . $row['result_name'];
265 loadColumnData($key, $row);
266 } // Recommended followup services.
268 else if ($form_by === '5') {
269 if (!empty($row['related_code'])) {
270 $relcodes = explode(';', $row['related_code']);
271 foreach ($relcodes as $codestring) {
272 if ($codestring === '') {
273 continue;
276 // list($codetype, $code) = explode(':', $codestring);
277 // if ($codetype !== 'IPPF') continue;
278 $key = $codestring;
279 loadColumnData($key, $row);
283 } // end function process_result_code()
285 // If we are doing the CSV export then generate the needed HTTP headers.
286 // Otherwise generate HTML.
288 if ($form_output == 3) {
289 header("Pragma: public");
290 header("Expires: 0");
291 header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
292 header("Content-Type: application/force-download");
293 header("Content-Disposition: attachment; filename=service_statistics_report.csv");
294 header("Content-Description: File Transfer");
295 } else {
297 <html>
298 <head>
299 <?php html_header_show(); ?>
300 <title><?php echo text($report_title); ?></title>
302 <link rel="stylesheet" href="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker/build/jquery.datetimepicker.min.css">
304 <style type="text/css">
305 body { font-family:sans-serif; font-size:10pt; font-weight:normal }
306 .dehead { color:#000000; font-family:sans-serif; font-size:10pt; font-weight:bold }
307 .detail { color:#000000; font-family:sans-serif; font-size:10pt; font-weight:normal }
308 </style>
309 <script type="text/javascript" src="../../library/textformat.js?v=<?php echo $v_js_includes; ?>"></script>
310 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery/dist/jquery.min.js"></script>
311 <script type="text/javascript" src="<?php echo $GLOBALS['assets_static_relative']; ?>/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js"></script>
313 <script language="JavaScript">
314 var mypcc = '<?php echo $GLOBALS['phone_country_code'] ?>';
316 $(document).ready(function() {
317 $('.datepicker').datetimepicker({
318 <?php $datetimepicker_timepicker = false; ?>
319 <?php $datetimepicker_showseconds = false; ?>
320 <?php $datetimepicker_formatInput = true; ?>
321 <?php require($GLOBALS['srcdir'] . '/js/xl/jquery-datetimepicker-2-5-4.js.php'); ?>
322 <?php // can add any additional javascript settings to datetimepicker here; need to prepend first setting with a comma ?>
325 </script>
327 </head>
329 <body leftmargin='0' topmargin='0' marginwidth='0' marginheight='0'>
331 <center>
333 <h2><?php echo $report_title; ?></h2>
335 <form name='theform' method='post' action='procedure_stats.php' onsubmit='return top.restoreSession()'>
336 <input type="hidden" name="csrf_token_form" value="<?php echo attr(collectCsrfToken()); ?>" />
338 <table border='0' cellspacing='5' cellpadding='1'>
340 <tr>
341 <td valign='top' class='dehead' nowrap>
342 <?php echo xlt('Rows'); ?>:
343 </td>
344 <td valign='top' class='detail'>
345 <select name='form_by' title='Left column of report'>
346 <?php
347 foreach ($arr_by as $key => $value) {
348 echo " <option value='" . attr($key) . "'";
349 if ($key == $form_by) {
350 echo " selected";
353 echo ">" . text($value) . "</option>\n";
356 </select>
357 </td>
358 <td valign='top' class='dehead' nowrap>
359 <?php echo xlt('Filters'); ?>:
360 </td>
361 <td rowspan='2' colspan='2' class='detail'
362 style='border-style:solid;border-width:1px;border-color:#cccccc'>
363 <table>
364 <tr>
365 <td valign='top' class='detail' nowrap>
366 <?php echo xlt('Sex'); ?>:
367 </td>
368 <td class='detail' valign='top'>
369 <select name='form_sexes' title='<?php echo xla('To filter by sex'); ?>'>
370 <?php
371 foreach (array(3 => xl('Men and Women'), 1 => xl('Women Only'), 2 => xl('Men Only')) as $key => $value) {
372 echo " <option value='" . attr($key) . "'";
373 if ($key == $form_sexes) {
374 echo " selected";
377 echo ">" . text($value) . "</option>\n";
380 </select>
381 </td>
382 </tr>
383 <tr>
384 <td valign='top' class='detail' nowrap>
385 <?php echo xlt('Facility'); ?>:
386 </td>
387 <td valign='top' class='detail'>
388 <?php dropdown_facility($form_facility, 'form_facility', false); ?>
389 </td>
390 </tr>
391 <tr>
392 <td colspan='2' class='detail' nowrap>
393 <?php echo xlt('From'); ?>
394 <input type='text' class='datepicker' name='form_from_date' id='form_from_date' size='10' value='<?php echo attr(oeFormatShortDate($from_date)); ?>'>
395 <?php echo xlt('To'); ?>
396 <input type='text' class='datepicker' name='form_to_date' id='form_to_date' size='10' value='<?php echo attr(oeFormatShortDate($to_date)); ?>'>
397 </td>
398 </tr>
399 </table>
400 </td>
401 </tr>
402 <tr>
403 <td valign='top' class='dehead' nowrap>
404 <?php echo xlt('Columns'); ?>:
405 </td>
406 <td valign='top' class='detail'>
407 <select name='form_show[]' size='4' multiple
408 title='<?php echo xla('Hold down Ctrl to select multiple items'); ?>'>
409 <?php
410 foreach ($arr_show as $key => $value) {
411 $title = $value['title'];
412 if (empty($title) || $key == 'title') {
413 $title = $value['description'];
416 echo " <option value='" . attr($key) . "'";
417 if (is_array($form_show) && in_array($key, $form_show)) {
418 echo " selected";
421 echo ">" . text($title) . "</option>\n";
424 </select>
425 </td>
426 </tr>
427 <tr>
428 <td valign='top' class='dehead' nowrap>
429 <?php echo xlt('To'); ?>:
430 </td>
431 <td colspan='3' valign='top' class='detail' nowrap>
432 <?php
433 foreach (array(1 => xl('Screen'), 2 => xl('Printer'), 3 => xl('Export File')) as $key => $value) {
434 echo " <input type='radio' name='form_output' value='" . attr($key) . "'";
435 if ($key == $form_output) {
436 echo ' checked';
439 echo " />" . text($value) . " &nbsp;";
442 </td>
443 <td align='right' valign='top' class='detail' nowrap>
444 <input type='submit' name='form_submit' value='<?php echo xla('Submit'); ?>'
445 title='<?php echo xla('Click to generate the report'); ?>' />
446 </td>
447 </tr>
448 <tr>
449 <td colspan='5' height="1">
450 </td>
451 </tr>
452 </table>
453 <?php
454 } // end not export
456 if ($_POST['form_submit']) {
457 if (!verifyCsrfToken($_POST["csrf_token_form"])) {
458 csrfNotVerified();
461 $pd_fields = '';
462 foreach ($arr_show as $askey => $asval) {
463 if (substr($askey, 0, 1) == '.') {
464 continue;
467 if ($askey == 'regdate' || $askey == 'sex' || $askey == 'DOB' ||
468 $askey == 'lname' || $askey == 'fname' || $askey == 'mname' ||
469 $askey == 'contrastart' || $askey == 'referral_source') {
470 continue;
473 $pd_fields .= ', pd.' . escape_sql_column_name($askey, array('patient_data'));
476 $sexcond = '';
477 if ($form_sexes == '1') {
478 $sexcond = "AND pd.sex NOT LIKE 'Male' ";
479 } else if ($form_sexes == '2') {
480 $sexcond = "AND pd.sex LIKE 'Male' ";
483 // This gets us all results, with encounter and patient
484 // info attached and grouped by patient and encounter.
486 $sqlBindArray = array();
488 $query = "SELECT " .
489 "po.patient_id, po.encounter_id, po.date_ordered, " .
490 "po.provider_id, pd.regdate, " .
491 "pd.sex, pd.DOB, pd.lname, pd.fname, pd.mname, " .
492 "pd.contrastart, pd.referral_source$pd_fields, " .
493 "ps.abnormal, " .
494 // "pto.name AS order_name, ptr.name AS result_name, ptr.related_code " .
495 "pc.procedure_name AS order_name, ptr.name AS result_name, ptr.related_code " .
496 "FROM procedure_order AS po " .
497 "JOIN form_encounter AS fe ON fe.pid = po.patient_id AND fe.encounter = po.encounter_id " .
498 "JOIN patient_data AS pd ON pd.pid = po.patient_id $sexcond" .
499 "JOIN procedure_order_code AS pc ON pc.procedure_order_id = po.procedure_order_id " .
500 "JOIN procedure_report AS pr ON pr.procedure_order_id = po.procedure_order_id " .
501 "AND pr.procedure_order_seq = pc.procedure_order_seq " .
502 "AND pr.date_report IS NOT NULL " .
503 "JOIN procedure_result AS ps ON ps.procedure_report_id = pr.procedure_report_id " .
504 "AND ps.result_status = 'final' " .
505 // "JOIN procedure_type AS pto ON pto.procedure_type_id = pc.procedure_type_id " .
506 "JOIN procedure_type AS ptr ON ptr.lab_id = po.lab_id AND ptr.procedure_code = ps.result_code " .
507 "AND ptr.procedure_type LIKE 'res%' " .
508 "WHERE po.date_ordered IS NOT NULL AND po.date_ordered >= ? " .
509 "AND po.date_ordered <= ? ";
511 array_push($sqlBindArray, $from_date, $to_date);
513 if ($form_facility) {
514 $query .= "AND fe.facility_id = ? ";
515 array_push($sqlBindArray, $form_facility);
518 $query .= "ORDER BY fe.pid, fe.encounter, ps.result_code"; // needed?
520 $res = sqlStatement($query, $sqlBindArray);
522 while ($row = sqlFetchArray($res)) {
523 process_result_code($row);
526 // Sort everything by key for reporting.
527 ksort($areport);
528 foreach ($arr_titles as $atkey => $dummy) {
529 ksort($arr_titles[$atkey]);
532 if ($form_output != 3) {
533 echo "<table border='0' cellpadding='1' cellspacing='2' width='98%'>\n";
534 } // end not csv export
536 genStartRow("bgcolor='#dddddd'");
538 // genHeadCell($arr_by[$form_by]);
539 // If the key is an MA or IPPF code, then add a column for its description.
540 if ($form_by === '5') {
541 genHeadCell(array($arr_by[$form_by], xl('Description')));
542 } else {
543 genHeadCell($arr_by[$form_by]);
546 // Generate headings for values to be shown.
547 foreach ($form_show as $value) {
548 // if ($value == '.total') { // Total Positives
549 // genHeadCell(xl('Total'));
550 // }
551 if ($value == '.tneg') { // Total Negatives
552 genHeadCell(xl('Negatives'));
553 } else if ($value == '.age') { // Age
554 genHeadCell(xl('0-10'), true);
555 genHeadCell(xl('11-14'), true);
556 genHeadCell(xl('15-19'), true);
557 genHeadCell(xl('20-24'), true);
558 genHeadCell(xl('25-29'), true);
559 genHeadCell(xl('30-34'), true);
560 genHeadCell(xl('35-39'), true);
561 genHeadCell(xl('40-44'), true);
562 genHeadCell(xl('45+'), true);
563 } else if ($arr_show[$value]['list_id']) {
564 foreach ($arr_titles[$value] as $key => $dummy) {
565 genHeadCell(getListTitle($arr_show[$value]['list_id'], $key), true);
567 } else if (!empty($arr_titles[$value])) {
568 foreach ($arr_titles[$value] as $key => $dummy) {
569 genHeadCell($key, true);
574 if ($form_output != 3) {
575 genHeadCell(xl('Positives'), true);
578 genEndRow();
580 $encount = 0;
582 foreach ($areport as $key => $varr) {
583 $bgcolor = (++$encount & 1) ? "#ddddff" : "#ffdddd";
585 $dispkey = $key;
587 // If the key is an MA or IPPF code, then get its description.
588 if ($form_by === '5') {
589 list($codetype, $code) = explode(':', $key);
590 $type = $code_types[$codetype]['id'];
591 $dispkey = array($key, '');
592 $crow = sqlQuery("SELECT code_text FROM codes WHERE " .
593 "code_type = ? AND code = ? ORDER BY id LIMIT 1", array($type, $code));
594 if (!empty($crow['code_text'])) {
595 $dispkey[1] = $crow['code_text'];
599 genStartRow("bgcolor='" . attr($bgcolor) . "'");
601 genAnyCell($dispkey, false, 'detail');
603 // This is the column index for accumulating column totals.
604 $cnum = 0;
605 $totalsvcs = $areport[$key]['.wom'] + $areport[$key]['.men'];
607 // Generate data for this row.
608 foreach ($form_show as $value) {
609 // if ($value == '.total') { // Total Positives
610 // genNumCell($totalsvcs, $cnum++);
611 // }
612 if ($value == '.tneg') { // Total Negatives
613 genNumCell($areport[$key]['.neg'], $cnum++);
614 } else if ($value == '.age') { // Age
615 for ($i = 0; $i < 9; ++$i) {
616 genNumCell($areport[$key]['.age'][$i], $cnum++);
618 } else if (!empty($arr_titles[$value])) {
619 foreach ($arr_titles[$value] as $title => $dummy) {
620 genNumCell($areport[$key][$value][$title], $cnum++);
625 // Write the Total column data.
626 if ($form_output != 3) {
627 $atotals[$cnum] += $totalsvcs;
628 genAnyCell($totalsvcs, true, 'dehead');
631 genEndRow();
632 } // end foreach
634 if ($form_output != 3) {
635 // Generate the line of totals.
636 genStartRow("bgcolor='#dddddd'");
638 // genHeadCell(xl('Totals'));
639 // If the key is an MA or IPPF code, then add a column for its description.
640 if ($form_by === '5') {
641 genHeadCell(array(xl('Totals'), ''));
642 } else {
643 genHeadCell(xl('Totals'));
646 for ($cnum = 0; $cnum < count($atotals); ++$cnum) {
647 genHeadCell($atotals[$cnum], true);
650 genEndRow();
651 // End of table.
652 echo "</table>\n";
654 } // end of if refresh or export
656 if ($form_output != 3) {
658 </form>
659 </center>
661 <script language='JavaScript'>
662 <?php if ($form_output == 2) { ?>
663 var win = top.printLogPrint ? top : opener.top;
664 win.printLogPrint(window);
665 <?php } ?>
666 </script>
668 </body>
669 </html>
670 <?php
671 } // end not export