3 * Clinical Decision Rules(CDR) engine functions.
5 * These functions should not ever attempt to write to
6 * session variables, because the session_write_close() function
7 * is typically called before utilizing these functions.
10 * @link http://www.open-emr.org
11 * @author Brady Miller <brady.g.miller@gmail.com>
12 * @author Medical Information Integration, LLC
13 * @author Ensofttek, LLC
14 * @copyright Copyright (c) 2010-2018 Brady Miller <brady.g.miller@gmail.com>
15 * @copyright Copyright (c) 2011 Medical Information Integration, LLC
16 * @copyright Copyright (c) 2011 Ensofttek, LLC
17 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
21 require_once(dirname(__FILE__
) . "/patient.inc");
22 require_once(dirname(__FILE__
) . "/forms.inc");
23 require_once(dirname(__FILE__
) . "/options.inc.php");
24 require_once(dirname(__FILE__
) . "/report_database.inc");
27 * Return listing of CDR reminders in log.
29 * @param string $begin_date begin date (optional)
30 * @param string $end_date end date (optional)
31 * @return sqlret sql return query
33 function listingCDRReminderLog($begin_date = '', $end_date = '')
36 if (empty($end_date)) {
37 $end_date=date('Y-m-d H:i:s');
41 $sql = "SELECT `date`, `pid`, `uid`, `category`, `value`, `new_value` FROM `clinical_rules_log` WHERE `date` <= ?";
42 array_push($sqlArray, $end_date);
43 if (!empty($begin_date)) {
44 $sql .= " AND `date` >= ?";
45 array_push($sqlArray, $begin_date);
48 $sql .= " ORDER BY `date` DESC";
50 return sqlStatement($sql, $sqlArray);
54 * Display the clinical summary widget.
56 * @param integer $patient_id pid of selected patient
57 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
58 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
59 * @param string $organize_mode Way to organize the results (default or plans)
60 * @param string $user If a user is set, then will only show rules that user has permission to see.
62 function clinical_summary_widget($patient_id, $mode, $dateTarget = '', $organize_mode = 'default', $user = '')
65 // Set date to current if not set
66 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
68 // Collect active actions
69 $actions = test_rules_clinic('', 'passive_alert', $dateTarget, $mode, $patient_id, '', $organize_mode, array(), 'primary', null, null, $user);
71 // Display the actions
72 $current_targets = array();
73 foreach ($actions as $action) {
74 // Deal with plan names first
75 if (isset($action['is_plan']) && $action['is_plan']) {
77 echo htmlspecialchars(xl("Plan"), ENT_NOQUOTES
) . ": ";
78 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'), $action['id']);
83 // Collect the Rule Title, Rule Developer, Rule Funding Source, and Rule Release and show it when hover over the item.
85 if (!empty($action['rule_id'])) {
86 $rule_title = getListItemTitle("clinical_rules", $action['rule_id']);
87 $ruleData = sqlQuery("SELECT `developer`, `funding_source`, `release_version`, `web_reference` " .
88 "FROM `clinical_rules` " .
89 "WHERE `id`=? AND `pid`=0", array($action['rule_id']));
90 $developer = $ruleData['developer'];
91 $funding_source = $ruleData['funding_source'];
92 $release = $ruleData['release_version'];
93 $web_reference = $ruleData['web_reference'];
94 if (!empty($rule_title)) {
95 $tooltip = xla('Rule Title') . ": " . attr($rule_title) . "
";
98 if (!empty($developer)) {
99 $tooltip .= xla('Rule Developer') . ": " . attr($developer) . "
";
102 if (!empty($funding_source)) {
103 $tooltip .= xla('Rule Funding Source') . ": " . attr($funding_source) . "
";
106 if (!empty($release)) {
107 $tooltip .= xla('Rule Release') . ": " . attr($release);
110 if ((!empty($tooltip)) ||
(!empty($web_reference))) {
111 if (!empty($web_reference)) {
112 $tooltip = "<a href='".attr($web_reference)."' target='_blank' style='white-space: pre-line;' title='".$tooltip."'>?</a>";
114 $tooltip = "<span style='white-space: pre-line;' title='".$tooltip."'>?</span>";
119 if ($action['custom_flag']) {
120 // Start link for reminders that use the custom rules input screen
121 $url = "../rules/patient_data.php?category=".htmlspecialchars($action['category'], ENT_QUOTES
);
122 $url .= "&item=".htmlspecialchars($action['item'], ENT_QUOTES
);
123 echo "<a href='".$url."' class='medium_modal' onclick='return top.restoreSession()'>";
124 } else if ($action['clin_rem_link']) {
125 // Start link for reminders that use the custom rules input screen
126 $pieces_url = parse_url($action['clin_rem_link']);
127 $url_prefix = $pieces_url['scheme'];
128 if ($url_prefix == 'https' ||
$url_prefix == 'http') {
129 echo "<a href='" . $action['clin_rem_link'] .
130 "' class='medium_modal' onclick='return top.restoreSession()'>";
132 echo "<a href='../../../" . $action['clin_rem_link'] .
133 "' class='medium_modal' onclick='return top.restoreSession()'>";
136 // continue since no link is needed
139 // Display Reminder Details
140 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'), $action['category']) .
141 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'), $action['item']);
143 if ($action['custom_flag'] ||
$action['clin_rem_link']) {
144 // End link for reminders that use an html link
148 // Display due status
149 if ($action['due_status']) {
150 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
151 if ($action['due_status'] == "past_due") {
152 echo " (<span style='color:red'>";
153 } else if ($action['due_status'] == "due") {
154 echo " (<span style='color:purple'>";
155 } else if ($action['due_status'] == "not_due") {
156 echo " (<span style='color:green'>";
158 echo " (<span>";
161 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'), $action['due_status']) . "</span>)";
164 // Display the tooltip
165 if (!empty($tooltip)) {
166 echo " ".$tooltip."<br>";
171 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
172 // Only when $mode is reminders-due
173 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log']) {
174 $target_temp = $action['category'].":".$action['item'];
175 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
179 // Compare the current with most recent action log (this function will also log the current actions)
180 // Only when $mode is reminders-due
181 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log']) {
182 $new_targets = compare_log_alerts($patient_id, $current_targets, 'clinical_reminder_widget', $_SESSION['authId']);
183 if (!empty($new_targets) && $GLOBALS['enable_cdr_new_crp']) {
184 // If there are new action(s), then throw a popup (if the enable_cdr_new_crp global is turned on)
185 // Note I am taking advantage of a slight hack in order to run javascript within code that
186 // is being passed via an ajax call by using a dummy image.
187 echo '<img src="../../pic/empty.gif" onload="alert(\''.xls('New Due Clinical Reminders').'\n\n';
188 foreach ($new_targets as $key => $value) {
189 $category_item = explode(":", $key);
190 $category = $category_item[0];
191 $item = $category_item[1];
192 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'), $category) .
193 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'), $item). '\n';
196 echo '\n' . '('. xls('See the Clinical Reminders widget for more details'). ')';
197 echo '\');this.parentNode.removeChild(this);" />';
203 * Display the active screen reminder.
205 * @param integer $patient_id pid of selected patient
206 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
207 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
208 * @param string $organize_mode Way to organize the results (default or plans)
209 * @param string $user If a user is set, then will only show rules that user has permission to see
210 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
211 * @return string html display output.
213 function active_alert_summary($patient_id, $mode, $dateTarget = '', $organize_mode = 'default', $user = '', $test = false)
216 // Set date to current if not set
217 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
219 // Collect active actions
220 $actions = test_rules_clinic('', 'active_alert', $dateTarget, $mode, $patient_id, '', $organize_mode, array(), 'primary', null, null, $user);
222 if (empty($actions)) {
227 $current_targets = array();
229 // Display the actions
230 foreach ($actions as $action) {
231 // Deal with plan names first
232 if ($action['is_plan']) {
233 $returnOutput .= "<br><b>";
234 $returnOutput .= htmlspecialchars(xl("Plan"), ENT_NOQUOTES
) . ": ";
235 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'), $action['id']);
236 $returnOutput .= "</b><br>";
240 // Display Reminder Details
241 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'), $action['category']) .
242 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'), $action['item']);
244 // Display due status
245 if ($action['due_status']) {
246 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
247 if ($action['due_status'] == "past_due") {
248 $returnOutput .= " (<span style='color:red'>";
249 } else if ($action['due_status'] == "due") {
250 $returnOutput .= " (<span style='color:purple'>";
251 } else if ($action['due_status'] == "not_due") {
252 $returnOutput .= " (<span style='color:green'>";
254 $returnOutput .= " (<span>";
257 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'), $action['due_status']) . "</span>)<br>";
259 $returnOutput .= "<br>";
262 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
263 // Only when $mode is reminders-due and $test is FALSE
264 if (($mode == "reminders-due") && ($test === false) && ($GLOBALS['enable_alert_log'])) {
265 $target_temp = $action['category'].":".$action['item'];
266 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
270 // Compare the current with most recent action log (this function will also log the current actions)
271 // Only when $mode is reminders-due and $test is FALSE
272 if (($mode == "reminders-due") && ($test === false) && ($GLOBALS['enable_alert_log'])) {
273 $new_targets = compare_log_alerts($patient_id, $current_targets, 'active_reminder_popup', $_SESSION['authId']);
274 if (!empty($new_targets)) {
275 $returnOutput .="<br>" . xlt('New Items (see above for details)') . ":<br>";
276 foreach ($new_targets as $key => $value) {
277 $category_item = explode(":", $key);
278 $category = $category_item[0];
279 $item = $category_item[1];
280 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'), $category) .
281 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'), $item). '<br>';
286 return $returnOutput;
290 * Process and return allergy conflicts (when a active medication or presciption is on allergy list).
292 * @param integer $patient_id pid of selected patient
293 * @param string $mode either 'all' or 'new' (required)
294 * @param string $user If a user is set, then will only show rules that user has permission to see
295 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
296 * @return array/boolean Array of allergy alerts or FALSE is empty.
298 function allergy_conflict($patient_id, $mode, $user, $test = false)
302 $res_allergies = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='allergy' " .
303 "AND `activity`=1 " .
304 "AND ( `enddate` IS NULL OR `enddate`='' OR `enddate` > NOW() ) " .
305 "AND `pid`=?", array($patient_id));
306 $allergies = array();
307 for ($iter=0; $row=sqlFetchArray($res_allergies); $iter++
) {
308 $allergies[$iter]=$row['title'];
311 // Build sql element of IN for below queries
315 foreach ($allergies as $allergy) {
316 array_push($sqlParam, $allergy);
325 // Check if allergies conflict with medications or prescriptions
326 $conflicts_unique = array();
327 if (!empty($sqlParam)) {
328 $conflicts = array();
329 array_push($sqlParam, $patient_id);
330 $res_meds = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='medication' " .
331 "AND `activity`=1 " .
332 "AND ( `enddate` IS NULL OR `enddate`='' OR `enddate` > NOW() ) " .
333 "AND `title` IN (" . $sqlIN . ") AND `pid`=?", $sqlParam);
334 while ($urow = sqlFetchArray($res_meds)) {
335 array_push($conflicts, $urow['title']);
338 $res_rx = sqlStatement("SELECT `drug` FROM `prescriptions` WHERE `active`=1 " .
339 "AND `drug` IN (" . $sqlIN . ") AND `patient_id`=?", $sqlParam);
340 while ($urow = sqlFetchArray($res_rx)) {
341 array_push($conflicts, $urow['drug']);
344 if (!empty($conflicts)) {
345 $conflicts_unique = array_unique($conflicts);
349 // If there are conflicts, $test is FALSE, and alert logging is on, then run through compare_log_alerts
350 $new_conflicts = array();
351 if ((!empty($conflicts_unique)) && $GLOBALS['enable_alert_log'] && ($test===false)) {
352 $new_conflicts = compare_log_alerts($patient_id, $conflicts_unique, 'allergy_alert', $_SESSION['authId'], $mode);
355 if ($mode == 'all') {
356 if (!empty($conflicts_unique)) {
357 return $conflicts_unique;
361 } else { // $mode = 'new'
362 if (!empty($new_conflicts)) {
363 return $new_conflicts;
371 * Compare current alerts with prior (in order to find new actions)
372 * Also functions to log the actions.
374 * @param integer $patient_id pid of selected patient
375 * @param array $current_targets array of targets
376 * @param string $category clinical_reminder_widget, active_reminder_popup, or allergy_alert
377 * @param integer $userid user id of user.
378 * @param string $log_trigger if 'all', then always log. If 'new', then only trigger log when a new item noted.
379 * @return array array with targets with associated rule.
381 function compare_log_alerts($patient_id, $current_targets, $category = 'clinical_reminder_widget', $userid = '', $log_trigger = 'all')
384 if (empty($userid)) {
385 $userid = $_SESSION['authId'];
388 if (empty($current_targets)) {
389 $current_targets = array();
392 // Collect most recent action_log
393 $prior_targets_sql = sqlQuery("SELECT `value` FROM `clinical_rules_log` " .
394 "WHERE `category` = ? AND `pid` = ? AND `uid` = ? " .
395 "ORDER BY `id` DESC LIMIT 1", array($category,$patient_id,$userid));
396 $prior_targets = array();
397 if (!empty($prior_targets_sql['value'])) {
398 $prior_targets = json_decode($prior_targets_sql['value'], true);
401 // Compare the current with most recent log
402 if (($category == 'clinical_reminder_widget') ||
($category == 'active_reminder_popup')) {
403 //using fancy structure to store multiple elements
404 $new_targets = array_diff_key($current_targets, $prior_targets);
405 } else { // $category == 'allergy_alert'
407 $new_targets = array_diff($current_targets, $prior_targets);
410 // Store current action_log and the new items
411 // If $log_trigger=='all'
412 // or If $log_trigger=='new' and there are new items
413 if (($log_trigger=='all') ||
(($log_trigger=='new') && (!empty($new_targets)))) {
414 $current_targets_json = json_encode($current_targets);
415 $new_targets_json = '';
416 if (!empty($new_targets)) {
417 $new_targets_json = json_encode($new_targets);
420 sqlInsert("INSERT INTO `clinical_rules_log` " .
421 "(`date`,`pid`,`uid`,`category`,`value`,`new_value`) " .
422 "VALUES (NOW(),?,?,?,?,?)", array($patient_id,$userid,$category,$current_targets_json,$new_targets_json));
425 // Return new actions (if there are any)
430 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
432 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
433 * on one patient or patients of one provider). The structure of the returned results is dependent on the
434 * $organize_mode and $mode parameters.
435 * <pre>The results are dependent on the $organize_mode parameter settings
436 * 'default' organize_mode:
437 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
438 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
439 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
440 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
441 * 'plans' organize_mode:
442 * Returns similar to default, but organizes by the active plans
445 * @param integer $provider id of a selected provider. If blank, then will test entire clinic. If 'collate_outer' or 'collate_inner', then will test each provider in entire clinic; outer will nest plans inside collated providers, while inner will nest the providers inside the plans (note inner and outer are only different if organize_mode is set to plans).
446 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2014,amc,amc_2011,amc_2014,patient_reminder). If blank then will test all rules.
447 * @param string/array $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target. If an array, then is holding two dates ('dateBegin' and 'dateTarget').
448 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
449 * @param string $plan test for specific plan only
450 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
451 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
452 * @param string $pat_prov_rel How to choose patients that are related to a chosen provider. 'primary' selects patients that the provider is set as primary provider. 'encounter' selectes patients that the provider has seen. This parameter is only applicable if the $provider parameter is set to a provider or collation setting.
453 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
454 * @param integer $report_id id of report in database (if already bookmarked)
455 * @return array See above for organization structure of the results.
457 function test_rules_clinic_batch_method($provider = '', $type = '', $dateTarget = '', $mode = '', $plan = '', $organize_mode = 'default', $options = array(), $pat_prov_rel = 'primary', $batchSize = '', $report_id = null)
460 // Default to a batchsize, if empty
461 if (empty($batchSize)) {
465 // Collect total number of pertinent patients (to calculate batching parameters)
466 $totalNumPatients = buildPatientArray('', $provider, $pat_prov_rel, null, null, true);
468 // Cycle through the batches and collect/combine results
469 if (($totalNumPatients%
$batchSize) > 0) {
470 // not perfectly divisible
471 $totalNumberBatches = floor($totalNumPatients/$batchSize) +
1;
473 // perfectly divisible
474 $totalNumberBatches = floor($totalNumPatients/$batchSize);
477 // Fix things in the $options array(). This now stores the number of labs to be used in the denominator in the AMC report.
478 // The problem with this variable is that is is added in every batch. So need to fix it by dividing this number by the number
479 // of planned batches(note the fixed array will go into the test_rules_clinic function, however the original will be used
480 // in the report storing/tracking engine.
481 $options_modified=$options;
482 if (!empty($options_modified['labs_manual'])) {
483 $options_modified['labs_manual'] = $options_modified['labs_manual'] / $totalNumberBatches;
486 // Prepare the database to track/store results
487 $fields = array('provider'=>$provider,'mode'=>$mode,'plan'=>$plan,'organize_mode'=>$organize_mode,'pat_prov_rel'=>$pat_prov_rel);
488 if (is_array($dateTarget)) {
489 $fields = array_merge($fields, array('date_target'=>$dateTarget['dateTarget']));
490 $fields = array_merge($fields, array('date_begin'=>$dateTarget['dateBegin']));
492 if (empty($dateTarget)) {
493 $fields = array_merge($fields, array('date_target'=>date("Y-m-d H:i:s")));
495 $fields = array_merge($fields, array('date_target'=>$dateTarget));
499 if (!empty($options)) {
500 foreach ($options as $key => $value) {
501 $fields = array_merge($fields, array($key=>$value));
505 $report_id = beginReportDatabase($type, $fields, $report_id);
506 setTotalItemsReportDatabase($report_id, $totalNumPatients);
508 // Set ability to itemize report if this feature is turned on
509 if (( ($type == "active_alert" ||
$type == "passive_alert") && ($GLOBALS['report_itemizing_standard']) ) ||
510 ( ($type == "cqm" ||
$type == "cqm_2011" ||
$type == "cqm_2014") && ($GLOBALS['report_itemizing_cqm']) ) ||
511 ( ($type == "amc" ||
$type == "amc_2011" ||
$type == "amc_2014" ||
$type == "amc_2014_stage1" ||
$type == "amc_2014_stage2") && ($GLOBALS['report_itemizing_amc']) ) ) {
512 $GLOBALS['report_itemizing_temp_flag_and_id'] = $report_id;
514 $GLOBALS['report_itemizing_temp_flag_and_id'] = 0;
517 for ($i=0; $i<$totalNumberBatches; $i++
) {
518 // If itemization is turned on, then reset the rule id iterator
519 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
520 $GLOBALS['report_itemized_test_id_iterator'] = 1;
523 $dataSheet_batch = test_rules_clinic($provider, $type, $dateTarget, $mode, '', $plan, $organize_mode, $options_modified, $pat_prov_rel, (($batchSize*$i)+
1), $batchSize);
525 // For first cycle, simply copy it to dataSheet
526 $dataSheet = $dataSheet_batch;
529 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
530 //error_log("CDR: ".($batchSize*$i)." records",0);
532 // Integrate batch results into main dataSheet
533 foreach ($dataSheet_batch as $key => $row) {
534 if (!$row['is_sub']) {
535 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
536 $total_patients = $dataSheet[$key]['total_patients'] +
$row['total_patients'];
537 $dataSheet[$key]['total_patients'] = $total_patients;
538 $excluded = $dataSheet[$key]['excluded'] +
$row['excluded'];
539 $dataSheet[$key]['excluded'] = $excluded;
540 $pass_filter = $dataSheet[$key]['pass_filter'] +
$row['pass_filter'];
541 $dataSheet[$key]['pass_filter'] = $pass_filter;
544 $pass_target = $dataSheet[$key]['pass_target'] +
$row['pass_target'];
545 $dataSheet[$key]['pass_target'] = $pass_target;
546 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter, $excluded, $pass_target);
550 //Update database to track results
551 updateReportDatabase($report_id, $total_patients);
554 // Record results in database and send to screen, if applicable.
555 finishReportDatabase($report_id, json_encode($dataSheet));
560 * Process clinic rules.
562 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
563 * on one patient or patients of one provider). The structure of the returned results is dependent on the
564 * $organize_mode and $mode parameters.
565 * <pre>The results are dependent on the $organize_mode parameter settings
566 * 'default' organize_mode:
567 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
568 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
569 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
570 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
571 * 'plans' organize_mode:
572 * Returns similar to default, but organizes by the active plans
575 * @param integer $provider id of a selected provider. If blank, then will test entire clinic. If 'collate_outer' or 'collate_inner', then will test each provider in entire clinic; outer will nest plans inside collated providers, while inner will nest the providers inside the plans (note inner and outer are only different if organize_mode is set to plans).
576 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2104,amc,amc_2011,amc_2014,patient_reminder). If blank then will test all rules.
577 * @param string/array $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target. If an array, then is holding two dates ('dateBegin' and 'dateTarget').
578 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
579 * @param integer $patient_id pid of patient. If blank then will check all patients.
580 * @param string $plan test for specific plan only
581 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
582 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
583 * @param string $pat_prov_rel How to choose patients that are related to a chosen provider. 'primary' selects patients that the provider is set as primary provider. 'encounter' selectes patients that the provider has seen. This parameter is only applicable if the $provider parameter is set to a provider or collation setting.
584 * @param integer $start applicable patient to start at (when batching process)
585 * @param integer $batchSize number of patients to batch (when batching process)
586 * @param string $user If a user is set, then will only show rules that user has permission to see(only applicable for per patient and not when do reports).
587 * @return array See above for organization structure of the results.
589 function test_rules_clinic($provider = '', $type = '', $dateTarget = '', $mode = '', $patient_id = '', $plan = '', $organize_mode = 'default', $options = array(), $pat_prov_rel = 'primary', $start = null, $batchSize = null, $user = '')
592 // If dateTarget is an array, then organize them.
593 if (is_array($dateTarget)) {
594 $dateArray = $dateTarget;
595 $dateTarget = $dateTarget['dateTarget'];
598 // Set date to current if not set
599 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
601 // Prepare the results array
604 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
605 // then run through this function recursively and return results.
606 if (($provider == "collate_outer") ||
($provider == "collate_inner" && $organize_mode != 'plans')) {
607 // First, collect an array of all providers
608 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
609 $ures = sqlStatementCdrEngine($query);
610 // Second, run through each provider recursively
611 while ($urow = sqlFetchArray($ures)) {
612 $newResults = test_rules_clinic($urow['id'], $type, $dateTarget, $mode, $patient_id, $plan, $organize_mode, $options, $pat_prov_rel, $start, $batchSize, $user);
613 if (!empty($newResults)) {
614 $provider_item['is_provider'] = true;
615 $provider_item['prov_lname'] = $urow['lname'];
616 $provider_item['prov_fname'] = $urow['fname'];
617 $provider_item['npi'] = $urow['npi'];
618 $provider_item['federaltaxid'] = $urow['federaltaxid'];
619 array_push($results, $provider_item);
620 $results = array_merge($results, $newResults);
624 // done, so now can return results
628 // If set organize-mode to plans, then collects active plans and run through this
629 // function recursively and return results.
630 if ($organize_mode == "plans") {
631 // First, collect active plans
632 $plans_resolve = resolve_plans_sql($plan, $patient_id);
633 // Second, run through function recursively
634 foreach ($plans_resolve as $plan_item) {
635 // (if collate_inner, then nest a collation of providers within each plan)
636 if ($provider == "collate_inner") {
637 // First, collect an array of all providers
638 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
639 $ures = sqlStatementCdrEngine($query);
640 // Second, run through each provider recursively
641 $provider_results = array();
642 while ($urow = sqlFetchArray($ures)) {
643 $newResults = test_rules_clinic($urow['id'], $type, $dateTarget, $mode, $patient_id, $plan_item['id'], 'default', $options, $pat_prov_rel, $start, $batchSize, $user);
644 if (!empty($newResults)) {
645 $provider_item['is_provider'] = true;
646 $provider_item['prov_lname'] = $urow['lname'];
647 $provider_item['prov_fname'] = $urow['fname'];
648 $provider_item['npi'] = $urow['npi'];
649 $provider_item['federaltaxid'] = $urow['federaltaxid'];
650 array_push($provider_results, $provider_item);
651 $provider_results = array_merge($provider_results, $newResults);
655 if (!empty($provider_results)) {
656 $plan_item['is_plan'] = true;
657 array_push($results, $plan_item);
658 $results = array_merge($results, $provider_results);
661 // (not collate_inner, so do not nest providers within each plan)
662 $newResults = test_rules_clinic($provider, $type, $dateTarget, $mode, $patient_id, $plan_item['id'], 'default', $options, $pat_prov_rel, $start, $batchSize, $user);
663 if (!empty($newResults)) {
664 $plan_item['is_plan'] = true;
665 array_push($results, $plan_item);
666 $results = array_merge($results, $newResults);
671 // done, so now can return results
675 // Collect applicable patient pids
676 $patientData = array();
677 $patientData = buildPatientArray($patient_id, $provider, $pat_prov_rel, $start, $batchSize);
679 // Go through each patient(s)
681 // If in report mode, then tabulate for each rule:
683 // Patients that pass the filter
684 // Patients that pass the target
685 // If in reminders mode, then create reminders for each rule:
686 // Reminder that action is due soon
687 // Reminder that action is due
688 // Reminder that action is post-due
690 //Collect applicable rules
691 // Note that due to a limitation in the this function, the patient_id is explicitly
692 // for grouping items when not being done in real-time or for official reporting.
693 // So for cases such as patient reminders on a clinic scale, the calling function
694 // will actually need rather than pass in a explicit patient_id for each patient in
695 // a separate call to this function.
696 if ($mode != "report") {
697 // Use per patient custom rules (if exist)
698 // Note as discussed above, this only works for single patient instances.
699 $rules = resolve_rules_sql($type, $patient_id, false, $plan, $user);
700 } else { // $mode = "report"
701 // Only use default rules (do not use patient custom rules)
702 $rules = resolve_rules_sql($type, $patient_id, false, $plan, $user);
705 foreach ($rules as $rowRule) {
706 // If using cqm or amc type, then use the hard-coded rules set.
707 // Note these rules are only used in report mode.
708 if ($rowRule['cqm_flag'] ||
$rowRule['amc_flag']) {
709 require_once(dirname(__FILE__
)."/classes/rulesets/ReportManager.php");
710 $manager = new ReportManager();
711 if ($rowRule['amc_flag']) {
712 // Send array of dates ('dateBegin' and 'dateTarget')
713 $tempResults = $manager->runReport($rowRule, $patientData, $dateArray, $options);
716 $tempResults = $manager->runReport($rowRule, $patientData, $dateTarget);
719 if (!empty($tempResults)) {
720 foreach ($tempResults as $tempResult) {
721 array_push($results, $tempResult);
725 // Go on to the next rule
729 // If in reminder mode then need to collect the measurement dates
730 // from rule_reminder table
731 $target_dates = array();
732 if ($mode != "report") {
733 // Calculate the dates to check for
734 if ($type == "patient_reminder") {
735 $reminder_interval_type = "patient_reminder";
736 } else { // $type == "passive_alert" or $type == "active_alert"
737 $reminder_interval_type = "clinical_reminder";
740 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
741 } else { // $mode == "report"
742 // Only use the target date in the report
743 $target_dates[0] = $dateTarget;
752 // Find the number of target groups
753 $targetGroups = returnTargetGroups($rowRule['id']);
755 if ((count($targetGroups) == 1) ||
($mode == "report")) {
756 // If report itemization is turned on, then iterate the rule id iterator
757 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
758 $GLOBALS['report_itemized_test_id_iterator']++
;
761 //skip this section if not report and more than one target group
762 foreach ($patientData as $rowPatient) {
763 // First, deal with deceased patients
764 // (for now will simply skip the patient)
765 // If want to support rules for deceased patients then will need to migrate this below
766 // in target_dates foreach(guessing won't ever need to do this, though).
767 // Note using the dateTarget rather than dateFocus
768 if (is_patient_deceased($rowPatient['pid'], $dateTarget)) {
772 // Count the total patients
775 $dateCounter = 1; // for reminder mode to keep track of which date checking
776 // If report itemization is turned on, reset flag.
777 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
778 $temp_track_pass = 1;
781 foreach ($target_dates as $dateFocus) {
782 //Skip if date is set to SKIP
783 if ($dateFocus == "SKIP") {
788 //Set date counter and reminder token (applicable for reminders only)
789 if ($dateCounter == 1) {
790 $reminder_due = "soon_due";
791 } else if ($dateCounter == 2) {
792 $reminder_due = "due";
793 } else { // $dateCounter == 3
794 $reminder_due = "past_due";
797 // Check if pass filter
798 $passFilter = test_filter($rowPatient['pid'], $rowRule['id'], $dateFocus);
799 if ($passFilter === "EXCLUDED") {
800 // increment EXCLUDED and pass_filter counters
801 // and set as FALSE for reminder functionality.
808 // increment pass filter counter
810 // If report itemization is turned on, trigger flag.
811 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
812 $temp_track_pass = 0;
819 // Check if pass target
820 $passTarget = test_targets($rowPatient['pid'], $rowRule['id'], '', $dateFocus);
822 // increment pass target counter
824 // If report itemization is turned on, then record the "passed" item and set the flag
825 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
826 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
827 $temp_track_pass = 1;
830 // send to reminder results
831 if ($mode == "reminders-all") {
832 // place the completed actions into the reminder return array
833 $actionArray = resolve_action_sql($rowRule['id'], '1');
834 foreach ($actionArray as $action) {
835 $action_plus = $action;
836 $action_plus['due_status'] = "not_due";
837 $action_plus['pid'] = $rowPatient['pid'];
838 $action_plus['rule_id'] = $rowRule['id'];
839 $results = reminder_results_integrate($results, $action_plus);
845 // send to reminder results
846 if ($mode != "report") {
847 // place the uncompleted actions into the reminder return array
848 $actionArray = resolve_action_sql($rowRule['id'], '1');
849 foreach ($actionArray as $action) {
850 $action_plus = $action;
851 $action_plus['due_status'] = $reminder_due;
852 $action_plus['pid'] = $rowPatient['pid'];
853 $action_plus['rule_id'] = $rowRule['id'];
854 $results = reminder_results_integrate($results, $action_plus);
862 // If report itemization is turned on, then record the "failed" item if it did not pass
863 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id']) && !($temp_track_pass)) {
864 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
869 // Calculate and save the data for the rule
870 $percentage = calculate_percentage($pass_filter, $exclude_filter, $pass_target);
871 if ($mode == "report") {
872 $newRow=array('is_main'=>true,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
873 $newRow=array_merge($newRow, $rowRule);
875 // If itemization is turned on, then record the itemized_test_id
876 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
877 $newRow=array_merge($newRow, array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
880 array_push($results, $newRow);
883 // Now run through the target groups if more than one
884 if (count($targetGroups) > 1) {
885 foreach ($targetGroups as $i) {
886 // If report itemization is turned on, then iterate the rule id iterator
887 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
888 $GLOBALS['report_itemized_test_id_iterator']++
;
891 //Reset the target counter
894 foreach ($patientData as $rowPatient) {
895 // First, deal with deceased patients
896 // (for now will simply skip the patient)
897 // If want to support rules for deceased patients then will need to migrate this below
898 // in target_dates foreach(guessing won't ever need to do this, though).
899 // Note using the dateTarget rather than dateFocus
900 if (is_patient_deceased($rowPatient['pid'], $dateTarget)) {
904 $dateCounter = 1; // for reminder mode to keep track of which date checking
905 // If report itemization is turned on, reset flag.
906 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
907 $temp_track_pass = 1;
910 foreach ($target_dates as $dateFocus) {
911 //Skip if date is set to SKIP
912 if ($dateFocus == "SKIP") {
917 //Set date counter and reminder token (applicable for reminders only)
918 if ($dateCounter == 1) {
919 $reminder_due = "soon_due";
920 } else if ($dateCounter == 2) {
921 $reminder_due = "due";
922 } else { // $dateCounter == 3
923 $reminder_due = "past_due";
926 // Check if pass filter
927 $passFilter = test_filter($rowPatient['pid'], $rowRule['id'], $dateFocus);
928 if ($passFilter === "EXCLUDED") {
936 // If report itemization is turned on, trigger flag.
937 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
938 $temp_track_pass = 0;
942 //Check if pass target
943 $passTarget = test_targets($rowPatient['pid'], $rowRule['id'], $i, $dateFocus);
945 // increment pass target counter
947 // If report itemization is turned on, then record the "passed" item and set the flag
948 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
949 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
950 $temp_track_pass = 1;
953 // send to reminder results
954 if ($mode == "reminders-all") {
955 // place the completed actions into the reminder return array
956 $actionArray = resolve_action_sql($rowRule['id'], $i);
957 foreach ($actionArray as $action) {
958 $action_plus = $action;
959 $action_plus['due_status'] = "not_due";
960 $action_plus['pid'] = $rowPatient['pid'];
961 $action_plus['rule_id'] = $rowRule['id'];
962 $results = reminder_results_integrate($results, $action_plus);
968 // send to reminder results
969 if ($mode != "report") {
970 // place the actions into the reminder return array
971 $actionArray = resolve_action_sql($rowRule['id'], $i);
972 foreach ($actionArray as $action) {
973 $action_plus = $action;
974 $action_plus['due_status'] = $reminder_due;
975 $action_plus['pid'] = $rowPatient['pid'];
976 $action_plus['rule_id'] = $rowRule['id'];
977 $results = reminder_results_integrate($results, $action_plus);
985 // If report itemization is turned on, then record the "failed" item if it did not pass
986 if ($GLOBALS['report_itemizing_temp_flag_and_id'] && !($temp_track_pass)) {
987 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
991 // Calculate and save the data for the rule
992 $percentage = calculate_percentage($pass_filter, $exclude_filter, $pass_target);
994 // Collect action for title (just use the first one, if more than one)
995 $actionArray = resolve_action_sql($rowRule['id'], $i);
996 $action = $actionArray[0];
997 if ($mode == "report") {
998 $newRow=array('is_sub'=>true,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
1000 // If itemization is turned on, then record the itemized_test_id
1001 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
1002 $newRow=array_merge($newRow, array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
1005 array_push($results, $newRow);
1016 * Process patient array that is to be tested.
1018 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
1019 * @param integer $patient_id pid of patient. If blank then will check all patients.
1020 * @param string $pat_prov_rel How to choose patients that are related to a chosen provider. 'primary' selects patients that the provider is set as primary provider. 'encounter' selectes patients that the provider has seen. This parameter is only applicable if the $provider parameter is set to a provider or collation setting.
1021 * @param integer $start applicable patient to start at (when batching process)
1022 * @param integer $batchSize number of patients to batch (when batching process)
1023 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
1024 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
1026 function buildPatientArray($patient_id = '', $provider = '', $pat_prov_rel = 'primary', $start = null, $batchSize = null, $onlyCount = false)
1029 if (!empty($patient_id)) {
1030 // only look at the selected patient
1034 $patientData[0]['pid'] = $patient_id;
1037 if (empty($provider)) {
1038 // Look at entire practice
1039 if ($start == null ||
$batchSize == null ||
$onlyCount) {
1040 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
1042 $patientNumber = sqlNumRows($rez);
1046 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start-1),$batchSize));
1049 // Look at an individual physician
1050 if ($pat_prov_rel == 'encounter') {
1051 // Choose patients that are related to specific physician by an encounter
1052 if ($start == null ||
$batchSize == null ||
$onlyCount) {
1053 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
1054 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
1056 $patientNumber = sqlNumRows($rez);
1060 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
1061 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start-1),$batchSize));
1063 } else { //$pat_prov_rel == 'primary'
1064 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
1065 if ($start == null ||
$batchSize == null ||
$onlyCount) {
1066 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1067 "WHERE `providerID`=? ORDER BY `pid`", array($provider));
1069 $patientNumber = sqlNumRows($rez);
1072 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1073 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start-1),$batchSize));
1078 // convert the sql query results into an array if returning the array
1080 for ($iter=0; $row=sqlFetchArray($rez); $iter++
) {
1081 $patientData[$iter]=$row;
1087 // return the number of applicable patients
1088 return $patientNumber;
1090 // return array of patient pids
1091 return $patientData;
1096 * Test filter of a selected rule on a selected patient
1098 * @param integer $patient_id pid of selected patient.
1099 * @param string $rule id(string) of selected rule
1100 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
1101 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
1103 function test_filter($patient_id, $rule, $dateTarget)
1106 // Set date to current if not set
1107 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1109 // Collect patient information
1110 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
1113 // ----------------- INCLUSIONS -----------------
1116 // -------- Age Filter (inclusion) ------------
1117 // Calculate patient age in years and months
1118 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'], $dateTarget);
1119 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'], $dateTarget);
1121 // Min age (year) Filter (assume that there in not more than one of each)
1122 $filter = resolve_filter_sql($rule, 'filt_age_min');
1123 if (!empty($filter)) {
1125 if ($row ['method_detail'] == "year") {
1126 if ($row['value'] && ($row['value'] > $patientAgeYears)) {
1131 if ($row ['method_detail'] == "month") {
1132 if ($row['value'] && ($row['value'] > $patientAgeMonths)) {
1138 // Max age (year) Filter (assume that there in not more than one of each)
1139 $filter = resolve_filter_sql($rule, 'filt_age_max');
1140 if (!empty($filter)) {
1142 if ($row ['method_detail'] == "year") {
1143 if ($row['value'] && ($row['value'] < $patientAgeYears)) {
1148 if ($row ['method_detail'] == "month") {
1149 if ($row['value'] && ($row['value'] < $patientAgeMonths)) {
1155 // -------- Gender Filter (inclusion) ---------
1156 // Gender Filter (assume that there in not more than one of each)
1157 $filter = resolve_filter_sql($rule, 'filt_sex');
1158 if (!empty($filter)) {
1160 if ($row['value'] && ($row['value'] != $patientData['sex'])) {
1165 // -------- Database Filter (inclusion) ------
1167 $filter = resolve_filter_sql($rule, 'filt_database');
1168 if ((!empty($filter)) && !database_check($patient_id, $filter, '', $dateTarget)) {
1172 // -------- Lists Filter (inclusion) ----
1173 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
1174 // surgeries and allergies.
1175 $filter = resolve_filter_sql($rule, 'filt_lists');
1176 if ((!empty($filter)) && !lists_check($patient_id, $filter, $dateTarget)) {
1180 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
1181 // Procedure Target (includes) (may need to include an interval in the future)
1182 $filter = resolve_filter_sql($rule, 'filt_proc');
1183 if ((!empty($filter)) && !procedure_check($patient_id, $filter, '', $dateTarget)) {
1188 // ----------------- EXCLUSIONS -----------------
1191 // -------- Lists Filter (EXCLUSION) ----
1192 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
1193 // surgeries and allergies.
1194 $filter = resolve_filter_sql($rule, 'filt_lists', 0);
1195 if ((!empty($filter)) && lists_check($patient_id, $filter, $dateTarget)) {
1199 // Passed all filters, so return true.
1204 * Return an array containing existing group ids for a rule
1206 * @param string $rule id(string) of rule
1207 * @return array listing of group ids
1209 function returnTargetGroups($rule)
1212 $sql = sqlStatementCdrEngine("SELECT DISTINCT `group_id` FROM `rule_target` " .
1213 "WHERE `id`=?", array($rule));
1216 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1217 array_push($groups, $row['group_id']);
1224 * Test targets of a selected rule on a selected patient
1226 * @param integer $patient_id pid of selected patient.
1227 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
1228 * @param integer $group_id group id of target group
1229 * @param string $dateTarget target date (format Y-m-d H:i:s).
1230 * @return boolean if target passes then true, otherwise false
1232 function test_targets($patient_id, $rule, $group_id = '', $dateTarget)
1235 // -------- Interval Target ----
1236 $interval = resolve_target_sql($rule, $group_id, 'target_interval');
1238 // -------- Database Target ----
1239 // Database Target (includes)
1240 $target = resolve_target_sql($rule, $group_id, 'target_database');
1241 if ((!empty($target)) && !database_check($patient_id, $target, $interval, $dateTarget)) {
1245 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
1246 // Procedure Target (includes)
1247 $target = resolve_target_sql($rule, $group_id, 'target_proc');
1248 if ((!empty($target)) && !procedure_check($patient_id, $target, $interval, $dateTarget)) {
1252 // -------- Appointment Target ----
1253 // Appointment Target (includes) (Specialized functionality for appointment reminders)
1254 $target = resolve_target_sql($rule, $group_id, 'target_appt');
1255 if ((!empty($target)) && appointment_check($patient_id, $dateTarget)) {
1259 // Passed all target tests, so return true.
1264 * Function to return active plans
1266 * @param string $type plan type filter (normal or cqm or blank)
1267 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
1268 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
1269 * @return array active plans
1271 function resolve_plans_sql($type = '', $patient_id = '0', $configurableOnly = false)
1274 if ($configurableOnly) {
1275 // Collect all default, configurable (per patient) plans into an array
1276 // (ie. ignore the cqm rules)
1277 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
1279 // Collect all default plans into an array
1280 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
1283 $returnArray= array();
1284 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1285 array_push($returnArray, $row);
1288 // Now collect the pertinent plans
1289 $newReturnArray = array();
1291 // Need to select rules (use custom if exist)
1292 foreach ($returnArray as $plan) {
1293 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id));
1295 // Decide if use default vs custom plan (preference given to custom plan)
1296 if (!empty($customPlan)) {
1297 if ($type == "cqm") {
1298 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
1301 // merge the custom plan with the default plan
1302 $mergedPlan = array();
1303 foreach ($customPlan as $key => $value) {
1304 if ($value == null && preg_match("/_flag$/", $key)) {
1305 // use default setting
1306 $mergedPlan[$key] = $plan[$key];
1308 // use custom setting
1309 $mergedPlan[$key] = $value;
1313 $goPlan = $mergedPlan;
1319 // Use the chosen plan if set
1320 if (!empty($type)) {
1321 if ($goPlan["${type}_flag"] == 1) {
1322 // active, so use the plan
1323 array_push($newReturnArray, $goPlan);
1326 if ($goPlan['normal_flag'] == 1 ||
1327 $goPlan['cqm_flag'] == 1) {
1328 // active, so use the plan
1329 array_push($newReturnArray, $goPlan);
1334 $returnArray = $newReturnArray;
1336 return $returnArray;
1341 * Function to return a specific plan
1343 * @param string $plan id(string) of plan
1344 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1345 * @return array a plan
1347 function collect_plan($plan, $patient_id = '0')
1350 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id));
1354 * Function to set a specific plan activity for a specific patient
1356 * @param string $plan id(string) of plan
1357 * @param string $type plan filter (normal,cqm)
1358 * @param string $setting activity of plan (yes,no,default)
1359 * @param integer $patient_id pid of selected patient.
1361 function set_plan_activity_patient($plan, $type, $setting, $patient_id)
1364 // Don't allow messing with the default plans here
1365 if ($patient_id == "0") {
1370 if ($setting == "on") {
1372 } else if ($setting == "off") {
1374 } else { // $setting == "default"
1378 // Collect patient specific plan, if already exists.
1379 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
1380 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id));
1382 if (empty($patient_plan)) {
1383 // Create a new patient specific plan with flags all set to default
1384 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1385 sqlStatementCdrEngine($query, array($plan, $patient_id));
1388 // Update patient specific row
1389 $query = "UPDATE `clinical_plans` SET `" . escape_sql_column_name($type."_flag", array("clinical_plans")) . "`= ? WHERE id = ? AND pid = ?";
1390 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id));
1394 * Function to return active rules
1396 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2014,amc_2011,amc_2014,patient_reminder)
1397 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1398 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1399 * @param string $plan collect rules for specific plan
1400 * @param string $user If a user is set, then will only show rules that user has permission to see
1401 * @return array rules
1403 function resolve_rules_sql($type = '', $patient_id = '0', $configurableOnly = false, $plan = '', $user = '')
1406 if ($configurableOnly) {
1407 // Collect all default, configurable (per patient) rules into an array
1408 // (ie. ignore the cqm and amc rules)
1409 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1411 // Collect all default rules into an array
1412 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1415 $returnArray= array();
1416 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1417 array_push($returnArray, $row);
1420 // Now filter rules for plan (if applicable)
1421 if (!empty($plan)) {
1422 $planReturnArray = array();
1423 foreach ($returnArray as $rule) {
1424 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1425 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']));
1426 if (!empty($standardRule)) {
1427 array_push($planReturnArray, $rule);
1431 $returnArray = $planReturnArray;
1434 // Now collect the pertinent rules
1435 $newReturnArray = array();
1437 // Need to select rules (use custom if exist)
1438 foreach ($returnArray as $rule) {
1439 // If user is set, then check if user has access to the rule
1440 if (!empty($user)) {
1441 $access_control = explode(':', $rule['access_control']);
1442 if (!empty($access_control[0]) && !empty($access_control[1])) {
1443 // Section and ACO filters are not empty, so do the test for access.
1444 if (!acl_check($access_control[0], $access_control[1], $user)) {
1445 // User does not have access to this rule, so skip the rule.
1449 // Section or ACO filters are empty, so use default patients:med aco
1450 if (!acl_check('patients', 'med', $user)) {
1451 // User does not have access to this rule, so skip the rule.
1457 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id));
1459 // Decide if use default vs custom rule (preference given to custom rule)
1460 if (!empty($customRule)) {
1461 if ($type == "cqm" ||
$type == "amc") {
1462 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1465 // merge the custom rule with the default rule
1466 $mergedRule = array();
1467 foreach ($customRule as $key => $value) {
1468 if ($value == null && preg_match("/_flag$/", $key)) {
1469 // use default setting
1470 $mergedRule[$key] = $rule[$key];
1472 // use custom setting
1473 $mergedRule[$key] = $value;
1477 $goRule = $mergedRule;
1483 // Use the chosen rule if set
1484 if (!empty($type)) {
1485 if ($goRule["${type}_flag"] == 1) {
1486 // active, so use the rule
1487 array_push($newReturnArray, $goRule);
1490 // no filter, so return the rule
1491 array_push($newReturnArray, $goRule);
1495 $returnArray = $newReturnArray;
1497 return $returnArray;
1501 * Function to return a specific rule
1503 * @param string $rule id(string) of rule
1504 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1505 * @return array rule
1507 function collect_rule($rule, $patient_id = '0')
1510 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id));
1514 * Function to set a specific rule activity for a specific patient
1516 * @param string $rule id(string) of rule
1517 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1518 * @param string $setting activity of rule (yes,no,default)
1519 * @param integer $patient_id pid of selected patient.
1521 function set_rule_activity_patient($rule, $type, $setting, $patient_id)
1524 // Don't allow messing with the default rules here
1525 if ($patient_id == "0") {
1530 if ($setting == "on") {
1532 } else if ($setting == "off") {
1534 } else { // $setting == "default"
1538 //Collect main rule to allow setting of the access_control
1539 $original_query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = 0";
1540 $patient_rule_original = sqlQueryCdrEngine($original_query, array($rule));
1542 // Collect patient specific rule, if already exists.
1543 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1544 $patient_rule = sqlQueryCdrEngine($query, array($rule,$patient_id));
1546 if (empty($patient_rule)) {
1547 // Create a new patient specific rule with flags all set to default
1548 $query = "INSERT into `clinical_rules` (`id`, `pid`, `access_control`) VALUES (?,?,?)";
1549 sqlStatementCdrEngine($query, array($rule, $patient_id, $patient_rule_original['access_control']));
1552 // Update patient specific row
1553 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ?, `access_control` = ? WHERE id = ? AND pid = ?";
1554 sqlStatementCdrEngine($query, array($setting,$patient_rule_original['access_control'],$rule,$patient_id));
1558 * Function to return applicable reminder dates (relative)
1560 * @param string $rule id(string) of selected rule
1561 * @param string $reminder_method string label of filter type
1562 * @return array reminder features
1564 function resolve_reminder_sql($rule, $reminder_method)
1566 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1567 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method));
1569 $returnArray= array();
1570 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1571 array_push($returnArray, $row);
1574 return $returnArray;
1578 * Function to return applicable filters
1580 * @param string $rule id(string) of selected rule
1581 * @param string $filter_method string label of filter type
1582 * @param string $include_flag to allow selection for included or excluded filters
1583 * @return array filters
1585 function resolve_filter_sql($rule, $filter_method, $include_flag = 1)
1587 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1588 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag));
1590 $returnArray= array();
1591 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1592 array_push($returnArray, $row);
1595 return $returnArray;
1599 * Function to return applicable targets
1601 * @param string $rule id(string) of selected rule
1602 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1603 * @param string $target_method string label of target type
1604 * @param string $include_flag to allow selection for included or excluded targets
1605 * @return array targets
1607 function resolve_target_sql($rule, $group_id = '', $target_method, $include_flag = 1)
1611 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1612 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag));
1614 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1615 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag));
1618 $returnArray= array();
1619 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1620 array_push($returnArray, $row);
1623 return $returnArray;
1627 * Function to return applicable actions
1629 * @param string $rule id(string) of selected rule
1630 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1631 * @return array actions
1633 function resolve_action_sql($rule, $group_id = '')
1637 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1638 "FROM `rule_action` as a " .
1639 "JOIN `rule_action_item` as b " .
1640 "ON a.category = b.category AND a.item = b.item " .
1641 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id));
1643 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1644 "FROM `rule_action` as a " .
1645 "JOIN `rule_action_item` as b " .
1646 "ON a.category = b.category AND a.item = b.item " .
1647 "WHERE a.id=?", array($rule));
1650 $returnArray= array();
1651 for ($iter=0; $row=sqlFetchArray($sql); $iter++
) {
1652 array_push($returnArray, $row);
1655 return $returnArray;
1659 * Function to check database filters and targets
1661 * @param string $patient_id pid of selected patient.
1662 * @param array $filter array containing filter/target elements
1663 * @param array $interval array containing interval elements
1664 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1665 * @return boolean true if check passed, otherwise false
1667 function database_check($patient_id, $filter, $interval = '', $dateTarget = '')
1669 $isMatch = false; //matching flag
1671 // Set date to current if not set
1672 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1674 // Unpackage interval information
1675 // (Assume only one for now and only pertinent for targets)
1677 $intervalValue = '';
1678 if (!empty($interval)) {
1679 $intervalType = $interval[0]['value'];
1680 $intervalValue = $interval[0]['interval'];
1684 foreach ($filter as $row) {
1686 // [0]=>special modes
1687 $temp_df = explode("::", $row['value']);
1689 if ($temp_df[0] == "CUSTOM") {
1691 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1692 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1696 // If this is a required entry then return false
1697 if ($row['required_flag']) {
1701 } else if ($temp_df[0] == "LIFESTYLE") {
1703 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1704 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1708 // If this is a required entry then return false
1709 if ($row['required_flag']) {
1716 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1717 if (exist_database_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $temp_df[6], $intervalType, $intervalValue, $dateTarget)) {
1719 if ($cond_loop > 0) { // For multiple condition check
1720 $isMatch = $isMatch && true;
1725 // If this is a required entry then return false
1726 if ($row['required_flag']) {
1735 // return results of check
1740 * Function to check procedure filters and targets
1742 * @param string $patient_id pid of selected patient.
1743 * @param array $filter array containing filter/target elements
1744 * @param array $interval array containing interval elements
1745 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1746 * @return boolean true if check passed, otherwise false
1748 function procedure_check($patient_id, $filter, $interval = '', $dateTarget = '')
1750 $isMatch = false; //matching flag
1752 // Set date to current if not set
1753 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1755 // Unpackage interval information
1756 // (Assume only one for now and only pertinent for targets)
1758 $intervalValue = '';
1759 if (!empty($interval)) {
1760 $intervalType = $interval[0]['value'];
1761 $intervalValue = $interval[0]['interval'];
1764 foreach ($filter as $row) {
1766 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1768 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1769 $temp_df = explode("::", $row['value']);
1770 if (exist_procedure_item($patient_id, $temp_df[0], $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1774 // If this is a required entry then return false
1775 if ($row['required_flag']) {
1781 // return results of check
1786 * Function to check for appointment
1788 * @todo Complete this to allow appointment reminders.
1789 * @param string $patient_id pid of selected patient.
1790 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1791 * @return boolean true if appt exist, otherwise false
1793 function appointment_check($patient_id, $dateTarget = '')
1795 $isMatch = false; //matching flag
1797 // Set date to current if not set (although should always be set)
1798 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1799 $dateTargetRound = date('Y-m-d', $dateTarget);
1802 $currentDate = date('Y-m-d H:i:s');
1803 $currentDateRound = date('Y-m-d', $dateCurrent);
1805 // Basically, if the appointment is within the current date to the target date,
1806 // then return true. (will not send reminders on same day as appointment)
1807 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1808 "openemr_postcalendar_events.pc_title, " .
1809 "openemr_postcalendar_events.pc_eventDate, " .
1810 "openemr_postcalendar_events.pc_startTime, " .
1811 "openemr_postcalendar_events.pc_endTime " .
1812 "FROM openemr_postcalendar_events " .
1813 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1814 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1815 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id));
1817 // return results of check
1819 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1820 // Plan to send back array of appt info (eid, time, date, etc.)
1822 if (sqlNumRows($sql) > 0) {
1830 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1832 * @param string $patient_id pid of selected patient.
1833 * @param array $filter array containing lists filter/target elements
1834 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1835 * @return boolean true if check passed, otherwise false
1837 function lists_check($patient_id, $filter, $dateTarget)
1839 $isMatch = false; //matching flag
1841 // Set date to current if not set
1842 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1844 foreach ($filter as $row) {
1845 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1849 // If this is a required entry then return false
1850 if ($row['required_flag']) {
1856 // return results of check
1861 * Function to check for existance of data in database for a patient
1863 * @param string $patient_id pid of selected patient.
1864 * @param string $table selected mysql table
1865 * @param string $column selected mysql column
1866 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1867 * @param string $data selected data in the mysql database (1)(2)
1868 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1869 * @param integer $num_items_thres number of items threshold
1870 * @param string $intervalType type of interval (ie. year)
1871 * @param integer $intervalValue searched for within this many times of the interval type
1872 * @param string $dateTarget target date(format Y-m-d H:i:s).
1873 * @return boolean true if check passed, otherwise false
1875 * (1) If data ends with **, operators ne/eq are replaced by (NOT)LIKE operators
1876 * (2) If $data contains '#CURDATE#', then it will be converted to the current date.
1879 function exist_database_item($patient_id, $table, $column = '', $data_comp, $data = '', $num_items_comp, $num_items_thres, $intervalType = '', $intervalValue = '', $dateTarget = '')
1882 // Set date to current if not set
1883 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1885 // Collect the correct column label for patient id in the table
1886 $patient_id_label = collect_database_label('pid', $table);
1888 // Get the interval sql query string
1889 $dateSql = sql_interval_string($table, $intervalType, $intervalValue, $dateTarget);
1891 // If just checking for existence (ie. data is empty),
1892 // then simply set the comparison operator to ne.
1897 // get the appropriate sql comparison operator
1898 $compSql = convertCompSql($data_comp);
1900 // custom issues per table can be placed here
1902 if ($table == 'immunizations') {
1903 $customSQL = " AND `added_erroneously` = '0' ";
1906 //adding table list for where condition
1908 if ($table == 'procedure_result') {
1909 $whereTables = ", procedure_order_code, " .
1910 "procedure_order, " .
1911 "procedure_report " ;
1912 $customSQL = " AND procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
1913 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
1914 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
1915 "procedure_result.procedure_report_id = procedure_report.procedure_report_id ";
1919 if (empty($column)) {
1920 // simple search for any table entries
1921 $sql = sqlStatementCdrEngine("SELECT * " .
1922 "FROM `" . add_escape_custom($table) . "` " .
1923 " ". $whereTables. " ".
1924 "WHERE " . add_escape_custom($patient_id_label) . "=? " . $customSQL, array($patient_id));
1926 // mdsupport : Allow trailing '**' in the strings to perform LIKE searches
1927 if ((substr($data, -2)=='**') && (($compSql == "=") ||
($compSql == "!="))) {
1928 $compSql = ($compSql == "!=" ?
" NOT": "")." LIKE CONCAT('%',?,'%') ";
1929 $data = substr_replace($data, '', -2);
1931 $compSql = $compSql . "? ";
1934 if ($whereTables=="" && strpos($table, 'form_')!== false) {
1935 //To handle standard forms starting with form_
1936 //In this case, we are assuming the date field is "date"
1937 $sql =sqlStatementCdrEngine(
1938 "SELECT b.`" . add_escape_custom($column) . "` " .
1940 "LEFT JOIN `" . add_escape_custom($table) . "` " . " b ".
1941 "ON (a.form_id=b.id AND a.formdir LIKE '".add_escape_custom(substr($table, 5))."') ".
1942 "WHERE a.deleted != '1' ".
1943 "AND b.`" .add_escape_custom($column) ."`" . $compSql .
1944 "AND b." . add_escape_custom($patient_id_label) . "=? " . $customSQL
1945 . str_replace("`date`", "b.`date`", $dateSql),
1946 array($data, $patient_id)
1949 // This allows to enter the wild card #CURDATE# in the CDR Demographics filter criteria at the value field
1950 // #CURDATE# is replace by the Current date allowing a dynamic date filtering
1951 if ($data=='#CURDATE#') {
1952 $data = date("Y-m-d");
1955 // search for number of specific items
1956 $sql = sqlStatementCdrEngine("SELECT `" . add_escape_custom($column) . "` " .
1957 "FROM `" . add_escape_custom($table) . "` " .
1958 " " . $whereTables . " " .
1959 "WHERE `" . add_escape_custom($column) . "`" . $compSql .
1960 "AND " . add_escape_custom($patient_id_label) . "=? " . $customSQL .
1961 $dateSql, array($data, $patient_id));
1965 // See if number of returned items passes the comparison
1966 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1970 * Function to check for existence of procedure(s) for a patient
1972 * @param string $patient_id pid of selected patient.
1973 * @param string $proc_title procedure title
1974 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1975 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1976 * @param string $result_data results data (1)
1977 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1978 * @param integer $num_items_thres number of items threshold
1979 * @param string $intervalType type of interval (ie. year)
1980 * @param integer $intervalValue searched for within this many times of the interval type
1981 * @param string $dateTarget target date(format Y-m-d H:i:s).
1982 * @return boolean true if check passed, otherwise false
1984 * (1) If result_data ends with **, operators ne/eq are replaced by (NOT)LIKE operators
1987 function exist_procedure_item($patient_id, $proc_title, $proc_code, $result_comp, $result_data = '', $num_items_comp, $num_items_thres, $intervalType = '', $intervalValue = '', $dateTarget = '')
1990 // Set date to current if not set
1991 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
1993 // Set the table exception (for looking up pertinent date and pid sql columns)
1994 $table = "PROCEDURE-EXCEPTION";
1996 // Collect the correct column label for patient id in the table
1997 $patient_id_label = collect_database_label('pid', $table);
1999 // Get the interval sql query string
2000 $dateSql = sql_interval_string($table, $intervalType, $intervalValue, $dateTarget);
2002 // If just checking for existence (ie result_data is empty),
2003 // then simply set the comparison operator to ne.
2004 if (empty($result_data)) {
2005 $result_comp = "ne";
2008 // get the appropriate sql comparison operator
2009 $compSql = convertCompSql($result_comp);
2011 // explode the code array
2013 if (!empty($proc_code)) {
2014 $codes = explode("||", $proc_code);
2019 // ensure proc_title is at least blank
2020 if (empty($proc_title)) {
2024 // collect specific items (use both title and/or codes) that fulfill request
2025 $sqlBindArray=array();
2026 $sql_query = "SELECT procedure_result.result FROM " .
2027 "procedure_order_code, " .
2028 "procedure_order, " .
2029 "procedure_type, " .
2030 "procedure_report, " .
2031 "procedure_result " .
2033 "procedure_order_code.procedure_code = procedure_type.procedure_code AND " .
2034 "procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
2035 "procedure_order.lab_id = procedure_type.lab_id AND " .
2036 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
2037 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
2038 "procedure_result.procedure_report_id = procedure_report.procedure_report_id AND " .
2039 "procedure_type.procedure_type = 'ord' AND ";
2040 foreach ($codes as $tem) {
2041 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
2042 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
2043 array_push($sqlBindArray, $tem, $tem);
2046 // mdsupport : Allow trailing '**' in the strings to perform LIKE searches
2047 if ((substr($result_data, -2)=='**') && (($compSql == "=") ||
($compSql == "!="))) {
2048 $compSql = ($compSql == "!=" ?
" NOT": "")." LIKE CONCAT('%',?,'%') ";
2049 $result_data = substr_replace($result_data, '', -2);
2051 $compSql = $compSql . "? ";
2054 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
2055 "AND procedure_result.result " . $compSql .
2056 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
2057 array_push($sqlBindArray, $proc_title, $result_data, $patient_id);
2059 $sql = sqlStatementCdrEngine($sql_query, $sqlBindArray);
2061 // See if number of returned items passes the comparison
2062 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2066 * Function to check for existance of data for a patient in the rule_patient_data table
2068 * @param string $patient_id pid of selected patient.
2069 * @param string $category label in category column
2070 * @param string $item label in item column
2071 * @param string $complete label in complete column (YES,NO, or blank)
2072 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
2073 * @param integer $num_items_thres number of items threshold
2074 * @param string $intervalType type of interval (ie. year)
2075 * @param integer $intervalValue searched for within this many times of the interval type
2076 * @param string $dateTarget target date(format Y-m-d H:i:s).
2077 * @return boolean true if check passed, otherwise false
2079 function exist_custom_item($patient_id, $category, $item, $complete, $num_items_comp, $num_items_thres, $intervalType = '', $intervalValue = '', $dateTarget)
2083 $table = 'rule_patient_data';
2085 // Collect the correct column label for patient id in the table
2086 $patient_id_label = collect_database_label('pid', $table);
2088 // Get the interval sql query string
2089 $dateSql = sql_interval_string($table, $intervalType, $intervalValue, $dateTarget);
2091 // search for number of specific items
2092 $sql = sqlStatementCdrEngine("SELECT `result` " .
2093 "FROM `" . add_escape_custom($table) . "` " .
2094 "WHERE `category`=? " .
2096 "AND `complete`=? " .
2097 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
2098 $dateSql, array($category,$item,$complete,$patient_id));
2100 // See if number of returned items passes the comparison
2101 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2105 * Function to check for existance of data for a patient in lifestyle section
2107 * @param string $patient_id pid of selected patient.
2108 * @param string $lifestyle selected label of mysql column of patient history
2109 * @param string $status specific status of selected lifestyle element
2110 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
2111 * @return boolean true if check passed, otherwise false
2113 function exist_lifestyle_item($patient_id, $lifestyle, $status, $dateTarget)
2116 // Set date to current if not set
2117 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
2119 // Collect pertinent history data
2120 $history = getHistoryData($patient_id, $lifestyle, '', $dateTarget);
2123 $stringFlag = strstr($history[$lifestyle], "|".$status);
2124 if (empty($status)) {
2125 // Only ensuring any data has been entered into the field
2129 if ($history[$lifestyle] &&
2130 $history[$lifestyle] != '|0|' &&
2139 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
2140 * allergies, and surgeries.
2142 * @param string $patient_id pid of selected patient.
2143 * @param string $type type (medical_problem, allergy, medication, etc)
2144 * @param string $value value searching for (1)
2145 * @param string $dateTarget target date(format Y-m-d H:i:s).
2146 * @return boolean true if check passed, otherwise false
2148 * (1) If value ends with **, operators ne/eq are replaced by (NOT)LIKE operators
2151 function exist_lists_item($patient_id, $type, $value, $dateTarget)
2154 // Set date to current if not set
2155 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
2157 // Attempt to explode the value into a code type and code (if applicable)
2158 $value_array = explode("::", $value);
2159 if (count($value_array) == 2) {
2160 // Collect the code type and code
2161 $code_type = $value_array[0];
2162 $code = $value_array[1];
2164 // Modify $code for both 'CUSTOM' and diagnosis searches
2165 // Note: Diagnosis is always 'LIKE' and should not have '**'
2166 if (substr($code, -2)=='**') {
2167 $sqloper = " LIKE CONCAT('%',?,'%') ";
2168 $code = substr_replace($code, '', -2);
2173 if ($code_type=='CUSTOM') {
2174 // Deal with custom code type first (title column in lists table)
2175 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2178 "AND `title` $sqloper " .
2179 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2180 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget));
2181 if (!empty($response)) {
2185 // Deal with the set code types (diagnosis column in lists table)
2186 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2189 "AND `diagnosis` LIKE ? " .
2190 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2191 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget));
2192 if (!empty($response)) {
2196 } else { // count($value_array) == 1
2197 // Search the title column in lists table
2198 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
2201 if (substr($value, -2)=='**') {
2202 $sqloper = " LIKE CONCAT('%',?,'%') ";
2203 $value = substr_replace($value, '', -2);
2208 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2211 "AND `title` $sqloper ".
2212 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2213 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget));
2214 if (!empty($response)) {
2218 if ($type == 'medication') { // Special case needed for medication as it need to be looked into current medications (prescriptions table) from ccda import
2219 $response = sqlQueryCdrEngine("SELECT * FROM `prescriptions` where `patient_id` = ? and `drug` $sqloper and `date_added` <= ?", array($patient_id,$value,$dateTarget));
2220 if (!empty($response)) {
2230 * Function to return part of sql query to deal with interval
2232 * @param string $table selected mysql table (or EXCEPTION(s))
2233 * @param string $intervalType type of interval (ie. year)
2234 * @param string $intervalValue searched for within this many times of the interval type
2235 * @param string $dateTarget target date(format Y-m-d H:i:s).
2236 * @return string contains pertinent date interval filter for mysql query
2238 function sql_interval_string($table, $intervalType, $intervalValue, $dateTarget)
2243 // Collect the correct column label for date in the table
2244 $date_label = collect_database_label('date', $table);
2246 // Deal with interval
2247 if (!empty($intervalType)) {
2248 switch ($intervalType) {
2250 $dateSql = "AND (" . add_escape_custom($date_label) .
2251 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2252 "', INTERVAL " . add_escape_custom($intervalValue) .
2253 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
2256 $dateSql = "AND (" . add_escape_custom($date_label) .
2257 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2258 "', INTERVAL " . add_escape_custom($intervalValue) .
2259 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
2262 $dateSql = "AND (" . add_escape_custom($date_label) .
2263 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2264 "', INTERVAL " . add_escape_custom($intervalValue) .
2265 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
2268 $dateSql = "AND (" . add_escape_custom($date_label) .
2269 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2270 "', INTERVAL " . add_escape_custom($intervalValue) .
2271 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
2274 $dateSql = "AND (" . add_escape_custom($date_label) .
2275 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2276 "', INTERVAL " . add_escape_custom($intervalValue) .
2277 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
2280 $dateSql = "AND (" . add_escape_custom($date_label) .
2281 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2282 "', INTERVAL " . add_escape_custom($intervalValue) .
2283 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
2286 $dateSql = "AND (" . add_escape_custom($date_label) .
2287 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2288 "', INTERVAL " . add_escape_custom($intervalValue) .
2289 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
2292 // Flu season to be hard-coded as September thru February
2293 // (Should make this modifiable in the future)
2294 // ($intervalValue is not used)
2295 $dateArray = explode("-", $dateTarget);
2296 $Year = $dateArray[0];
2297 $dateThisYear = $Year . "-09-01";
2298 $dateLastYear = ($Year-1) . "-09-01";
2301 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
2302 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
2304 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
2305 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
2306 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
2310 $dateSql = "AND " . add_escape_custom($date_label) .
2311 " <= '" . add_escape_custom($dateTarget) . "' ";
2314 // return the sql interval string
2319 * Function to collect generic column labels from tables. It currently works for date
2320 * and pid. Will need to expand this as algorithm grows.
2322 * @param string $label element (pid or date)
2323 * @param string $table selected mysql table (or EXCEPTION(s))
2324 * @return string contains official label of selected element
2326 function collect_database_label($label, $table)
2329 if ($table == 'PROCEDURE-EXCEPTION') {
2330 // return cell to get procedure collection
2331 // special case since reuqires joing of multiple
2332 // tables to get this value
2333 if ($label == "pid") {
2334 $returnedLabel = "procedure_order.patient_id";
2335 } else if ($label == "date") {
2336 $returnedLabel = "procedure_report.date_collected";
2338 // unknown label, so return the original label
2339 $returnedLabel = $label;
2341 } else if ($table == 'immunizations') {
2342 // return requested label for immunization table
2343 if ($label == "pid") {
2344 $returnedLabel = "patient_id";
2345 } else if ($label == "date") {
2346 $returnedLabel = "`administered_date`";
2348 // unknown label, so return the original label
2349 $returnedLabel = $label;
2351 } else if ($table == 'prescriptions') {
2352 // return requested label for prescriptions table
2353 if ($label == "pid") {
2354 $returnedLabel = "patient_id";
2355 } else if ($label == "date") {
2356 $returnedLabel = 'date_added';
2358 // unknown label, so return the original label
2359 $returnedLabel = $label;
2361 } else if ($table == 'procedure_result') {
2362 // return requested label for prescriptions table
2363 if ($label == "pid") {
2364 $returnedLabel = "procedure_order.patient_id";
2365 } else if ($label == "date") {
2366 $returnedLabel = "procedure_report.date_collected";
2368 // unknown label, so return the original label
2369 $returnedLabel = $label;
2372 // return requested label for default tables
2373 if ($label == "pid") {
2374 $returnedLabel = "pid";
2375 } else if ($label == "date") {
2376 $returnedLabel = "`date`";
2378 // unknown label, so return the original label
2379 $returnedLabel = $label;
2383 return $returnedLabel;
2387 * Simple function to avoid processing of duplicate actions
2389 * @param string $actions 2-dimensional array with all current active targets
2390 * @param string $action array of selected target to test for duplicate
2391 * @return boolean true if duplicate, false if not duplicate
2393 function is_duplicate_action($actions, $action)
2395 foreach ($actions as $row) {
2396 if ($row['category'] == $action['category'] &&
2397 $row['item'] == $action['item'] &&
2398 $row['value'] == $action['value']) {
2409 * Calculate the reminder dates.
2411 * This function returns an array that contains three elements (each element is a date).
2412 * <pre>The three dates are:
2413 * first date is before the target date (past_due) (default of 1 month)
2414 * second date is the target date (due)
2415 * third date is after the target date (soon_due) (default of 2 weeks)
2418 * @param string $rule id(string) of selected rule
2419 * @param string $dateTarget target date(format Y-m-d H:i:s).
2420 * @param string $type either 'patient_reminder' or 'clinical_reminder'
2421 * @return array see above for description of returned array
2423 function calculate_reminder_dates($rule, $dateTarget = '', $type)
2426 // Set date to current if not set
2427 $dateTarget = ($dateTarget) ?
$dateTarget : date('Y-m-d H:i:s');
2429 // Collect the current date settings (to ensure not skip)
2430 $res = resolve_reminder_sql($rule, $type.'_current');
2433 if ($row ['method_detail'] == "SKIP") {
2434 $dateTarget = "SKIP";
2438 // Collect the past_due date
2439 $past_due_date = "";
2440 $res = resolve_reminder_sql($rule, $type.'_post');
2443 if ($row ['method_detail'] == "week") {
2444 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
2447 if ($row ['method_detail'] == "month") {
2448 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
2451 if ($row ['method_detail'] == "hour") {
2452 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2455 if ($row ['method_detail'] == "SKIP") {
2456 $past_due_date = "SKIP";
2459 // empty settings, so use default of one month
2460 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
2463 // Collect the soon_due date
2464 $soon_due_date = "";
2465 $res = resolve_reminder_sql($rule, $type.'_pre');
2468 if ($row ['method_detail'] == "week") {
2469 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
2472 if ($row ['method_detail'] == "month") {
2473 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
2476 if ($row ['method_detail'] == "hour") {
2477 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2480 if ($row ['method_detail'] == "SKIP") {
2481 $soon_due_date = "SKIP";
2484 // empty settings, so use default of one month
2485 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
2488 // Return the array of three dates
2489 return array($soon_due_date,$dateTarget,$past_due_date);
2493 * Adds an action into the reminder array
2495 * @param array $reminderOldArray Contains the current array of reminders
2496 * @param array $reminderNew Array of a new reminder
2497 * @return array Reminders
2499 function reminder_results_integrate($reminderOldArray, $reminderNew)
2504 // If reminderArray is empty, then insert new reminder
2505 if (empty($reminderOldArray)) {
2506 array_push($results, $reminderNew);
2510 // If duplicate reminder, then replace the old one
2512 foreach ($reminderOldArray as $reminderOld) {
2513 if ($reminderOld['pid'] == $reminderNew['pid'] &&
2514 $reminderOld['category'] == $reminderNew['category'] &&
2515 $reminderOld['item'] == $reminderNew['item']) {
2516 array_push($results, $reminderNew);
2519 array_push($results, $reminderOld);
2523 // If a new reminder, then insert the new reminder
2525 array_push($results, $reminderNew);
2532 * Compares number of items with requested comparison operator
2534 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2535 * @param string $thres Threshold used in comparison
2536 * @param integer $num_items Number of items
2537 * @return boolean Comparison results
2539 function itemsNumberCompare($comp, $thres, $num_items)
2542 if (($comp == "eq") && ($num_items == $thres)) {
2544 } else if (($comp == "ne") && ($num_items != $thres) && ($num_items > 0)) {
2546 } else if (($comp == "gt") && ($num_items > $thres)) {
2548 } else if (($comp == "ge") && ($num_items >= $thres)) {
2550 } else if (($comp == "lt") && ($num_items < $thres) && ($num_items > 0)) {
2552 } else if (($comp == "le") && ($num_items <= $thres) && ($num_items > 0)) {
2560 * Converts a text comparison operator to sql equivalent
2562 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2563 * @return string contains sql compatible comparison operator
2565 function convertCompSql($comp)
2568 if ($comp == "eq") {
2570 } else if ($comp == "ne") {
2572 } else if ($comp == "gt") {
2574 } else if ($comp == "ge") {
2576 } else if ($comp == "lt") {
2578 } else { // ($comp == "le")
2585 * Function to find age in years (with decimal) on the target date
2587 * @param string $dob date of birth
2588 * @param string $target date to calculate age on
2589 * @return float years(decimal) from dob to target(date)
2591 function convertDobtoAgeYearDecimal($dob, $target)
2593 $ageInfo=parseAgeInfo($dob, $target);
2594 return $ageInfo['age'];
2598 * Function to find age in months (with decimal) on the target date
2600 * @param string $dob date of birth
2601 * @param string $target date to calculate age on
2602 * @return float months(decimal) from dob to target(date)
2604 function convertDobtoAgeMonthDecimal($dob, $target)
2606 $ageInfo=parseAgeInfo($dob, $target);
2607 return $ageInfo['age_in_months'];
2611 * Function to calculate the percentage for reports.
2613 * @param integer $pass_filter number of patients that pass filter
2614 * @param integer $exclude_filter number of patients that are excluded
2615 * @param integer $pass_target number of patients that pass target
2616 * @return string Number formatted into a percentage
2618 function calculate_percentage($pass_filt, $exclude_filt, $pass_targ)
2620 if ($pass_filt > 0) {
2621 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100, 4) . xl('%');
2623 $perc = "0". xl('%');