update readme for recommended nodejs versions (#4389)
[openemr.git] / library / clinical_rules.php
blobad29983de5d9f9d7958ceb010292fddf5f838984
1 <?php
3 /**
4 * Clinical Decision Rules(CDR) engine functions.
6 * @package OpenEMR
7 * @link https://www.open-emr.org
8 * @author Brady Miller <brady.g.miller@gmail.com>
9 * @author Medical Information Integration, LLC
10 * @author Ensofttek, LLC
11 * @copyright Copyright (c) 2010-2019 Brady Miller <brady.g.miller@gmail.com>
12 * @copyright Copyright (c) 2011 Medical Information Integration, LLC
13 * @copyright Copyright (c) 2011 Ensofttek, LLC
14 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
17 require_once(dirname(__FILE__) . "/patient.inc");
18 require_once(dirname(__FILE__) . "/forms.inc");
19 require_once(dirname(__FILE__) . "/options.inc.php");
20 require_once(dirname(__FILE__) . "/report_database.inc");
22 use OpenEMR\Common\Acl\AclMain;
24 /**
25 * Return listing of CDR reminders in log.
27 * @param string $begin_date begin date (optional)
28 * @param string $end_date end date (optional)
29 * @return sqlret sql return query
31 function listingCDRReminderLog($begin_date = '', $end_date = '')
34 if (empty($end_date)) {
35 $end_date = date('Y-m-d H:i:s');
38 $sqlArray = array();
39 $sql = "SELECT `date`, `pid`, `uid`, `category`, `value`, `new_value` FROM `clinical_rules_log` WHERE `date` <= ?";
40 $sqlArray[] = $end_date;
41 if (!empty($begin_date)) {
42 $sql .= " AND `date` >= ?";
43 $sqlArray[] = $begin_date;
46 $sql .= " ORDER BY `date` DESC";
48 return sqlStatement($sql, $sqlArray);
51 /**
52 * Display the clinical summary widget.
54 * @param integer $patient_id pid of selected patient
55 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
56 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
57 * @param string $organize_mode Way to organize the results (default or plans)
58 * @param string $user If a user is set, then will only show rules that user has permission to see.
60 function clinical_summary_widget($patient_id, $mode, $dateTarget = '', $organize_mode = 'default', $user = '')
63 // Set date to current if not set
64 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
66 // Collect active actions
67 $actions = test_rules_clinic('', 'passive_alert', $dateTarget, $mode, $patient_id, '', $organize_mode, array(), 'primary', null, null, $user);
69 // Display the actions
70 $current_targets = array();
71 foreach ($actions as $action) {
72 // Deal with plan names first
73 if (isset($action['is_plan']) && $action['is_plan']) {
74 echo "<br /><b>";
75 echo xlt("Plan") . ": ";
76 echo generate_display_field(array('data_type' => '1','list_id' => 'clinical_plans'), $action['id']);
77 echo "</b><br />";
78 continue;
81 // Collect the Rule Title, Rule Developer, Rule Funding Source, and Rule Release and show it when hover over the item.
82 $tooltip = '';
83 if (!empty($action['rule_id'])) {
84 $rule_title = getListItemTitle("clinical_rules", $action['rule_id']);
85 $ruleData = sqlQuery("SELECT `developer`, `funding_source`, `release_version`, `web_reference` " .
86 "FROM `clinical_rules` " .
87 "WHERE `id`=? AND `pid`=0", array($action['rule_id']));
88 $developer = $ruleData['developer'];
89 $funding_source = $ruleData['funding_source'];
90 $release = $ruleData['release_version'];
91 $web_reference = $ruleData['web_reference'];
92 if (!empty($rule_title)) {
93 $tooltip = xla('Rule Title') . ": " . attr($rule_title) . "&#013;";
96 if (!empty($developer)) {
97 $tooltip .= xla('Rule Developer') . ": " . attr($developer) . "&#013;";
100 if (!empty($funding_source)) {
101 $tooltip .= xla('Rule Funding Source') . ": " . attr($funding_source) . "&#013;";
104 if (!empty($release)) {
105 $tooltip .= xla('Rule Release') . ": " . attr($release);
108 if ((!empty($tooltip)) || (!empty($web_reference))) {
109 if (!empty($web_reference)) {
110 $tooltip = "<a href='" . attr($web_reference) . "' rel='noopener' target='_blank' style='white-space: pre-line;' title='" . $tooltip . "'>?</a>";
111 } else {
112 $tooltip = "<span style='white-space: pre-line;' title='" . $tooltip . "'>?</span>";
117 if ($action['custom_flag']) {
118 // Start link for reminders that use the custom rules input screen
119 $url = "../rules/patient_data.php?category=" . attr_url($action['category']);
120 $url .= "&item=" . attr_url($action['item']);
121 echo "<a href='" . $url . "' class='medium_modal' onclick='return top.restoreSession()'>";
122 } elseif ($action['clin_rem_link']) {
123 // Start link for reminders that use the custom rules input screen
124 $pieces_url = parse_url($action['clin_rem_link']);
125 $url_prefix = $pieces_url['scheme'];
126 if ($url_prefix == 'https' || $url_prefix == 'http') {
127 echo "<a href='" . $action['clin_rem_link'] .
128 "' class='medium_modal' onclick='return top.restoreSession()'>";
129 } else {
130 echo "<a href='../../../" . $action['clin_rem_link'] .
131 "' class='medium_modal' onclick='return top.restoreSession()'>";
133 } else {
134 // continue since no link is needed
137 // Display Reminder Details
138 echo generate_display_field(array('data_type' => '1','list_id' => 'rule_action_category'), $action['category']) .
139 ": " . generate_display_field(array('data_type' => '1','list_id' => 'rule_action'), $action['item']);
141 if ($action['custom_flag'] || $action['clin_rem_link']) {
142 // End link for reminders that use an html link
143 echo "</a>";
146 // Display due status
147 if ($action['due_status']) {
148 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
149 if ($action['due_status'] == "past_due") {
150 echo "&nbsp;&nbsp;(<span style='color:red'>";
151 } elseif ($action['due_status'] == "due") {
152 echo "&nbsp;&nbsp;(<span style='color:purple'>";
153 } elseif ($action['due_status'] == "not_due") {
154 echo "&nbsp;&nbsp;(<span style='color:green'>";
155 } else {
156 echo "&nbsp;&nbsp;(<span>";
159 echo generate_display_field(array('data_type' => '1','list_id' => 'rule_reminder_due_opt'), $action['due_status']) . "</span>)";
162 // Display the tooltip
163 if (!empty($tooltip)) {
164 echo "&nbsp;" . $tooltip . "<br />";
165 } else {
166 echo "<br />";
169 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
170 // Only when $mode is reminders-due
171 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log']) {
172 $target_temp = $action['category'] . ":" . $action['item'];
173 $current_targets[$target_temp] = array('rule_id' => $action['rule_id'],'due_status' => $action['due_status']);
177 // Compare the current with most recent action log (this function will also log the current actions)
178 // Only when $mode is reminders-due
179 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log']) {
180 $new_targets = compare_log_alerts($patient_id, $current_targets, 'clinical_reminder_widget', $_SESSION['authUserID']);
181 if (!empty($new_targets) && $GLOBALS['enable_cdr_new_crp']) {
182 // If there are new action(s), then throw a popup (if the enable_cdr_new_crp global is turned on)
183 // Note I am taking advantage of a slight hack in order to run javascript within code that
184 // is being passed via an ajax call by using a dummy image.
185 echo '<img src="../../pic/empty.gif" onload="alert(\'' . xls('New Due Clinical Reminders') . '\n\n';
186 foreach ($new_targets as $key => $value) {
187 $category_item = explode(":", $key);
188 $category = $category_item[0];
189 $item = $category_item[1];
190 echo generate_display_field(array('data_type' => '1','list_id' => 'rule_action_category'), $category) .
191 ': ' . generate_display_field(array('data_type' => '1','list_id' => 'rule_action'), $item) . '\n';
194 echo '\n' . '(' . xls('See the Clinical Reminders widget for more details') . ')';
195 echo '\');this.parentNode.removeChild(this);" />';
201 * Display the active screen reminder.
203 * @param integer $patient_id pid of selected patient
204 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
205 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
206 * @param string $organize_mode Way to organize the results (default or plans)
207 * @param string $user If a user is set, then will only show rules that user has permission to see
208 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
209 * @return string html display output.
211 function active_alert_summary($patient_id, $mode, $dateTarget = '', $organize_mode = 'default', $user = '', $test = false)
214 // Set date to current if not set
215 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
217 // Collect active actions
218 $actions = test_rules_clinic('', 'active_alert', $dateTarget, $mode, $patient_id, '', $organize_mode, array(), 'primary', null, null, $user);
220 if (empty($actions)) {
221 return false;
224 $returnOutput = "";
225 $current_targets = array();
227 // Display the actions
228 foreach ($actions as $action) {
229 // Deal with plan names first
230 if ($action['is_plan']) {
231 $returnOutput .= "<br /><b>";
232 $returnOutput .= xlt("Plan") . ": ";
233 $returnOutput .= generate_display_field(array('data_type' => '1','list_id' => 'clinical_plans'), $action['id']);
234 $returnOutput .= "</b><br />";
235 continue;
238 // Display Reminder Details
239 $returnOutput .= generate_display_field(array('data_type' => '1','list_id' => 'rule_action_category'), $action['category']) .
240 ": " . generate_display_field(array('data_type' => '1','list_id' => 'rule_action'), $action['item']);
242 // Display due status
243 if ($action['due_status']) {
244 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
245 if ($action['due_status'] == "past_due") {
246 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
247 } elseif ($action['due_status'] == "due") {
248 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
249 } elseif ($action['due_status'] == "not_due") {
250 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
251 } else {
252 $returnOutput .= "&nbsp;&nbsp;(<span>";
255 $returnOutput .= generate_display_field(array('data_type' => '1','list_id' => 'rule_reminder_due_opt'), $action['due_status']) . "</span>)<br />";
256 } else {
257 $returnOutput .= "<br />";
260 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
261 // Only when $mode is reminders-due and $test is FALSE
262 if (($mode == "reminders-due") && ($test === false) && ($GLOBALS['enable_alert_log'])) {
263 $target_temp = $action['category'] . ":" . $action['item'];
264 $current_targets[$target_temp] = array('rule_id' => $action['rule_id'],'due_status' => $action['due_status']);
268 // Compare the current with most recent action log (this function will also log the current actions)
269 // Only when $mode is reminders-due and $test is FALSE
270 if (($mode == "reminders-due") && ($test === false) && ($GLOBALS['enable_alert_log'])) {
271 $new_targets = compare_log_alerts($patient_id, $current_targets, 'active_reminder_popup', $_SESSION['authUserID']);
272 if (!empty($new_targets)) {
273 $returnOutput .= "<br />" . xlt('New Items (see above for details)') . ":<br />";
274 foreach ($new_targets as $key => $value) {
275 $category_item = explode(":", $key);
276 $category = $category_item[0];
277 $item = $category_item[1];
278 $returnOutput .= generate_display_field(array('data_type' => '1','list_id' => 'rule_action_category'), $category) .
279 ': ' . generate_display_field(array('data_type' => '1','list_id' => 'rule_action'), $item) . '<br />';
284 return $returnOutput;
288 * Process and return allergy conflicts (when a active medication or presciption is on allergy list).
290 * @param integer $patient_id pid of selected patient
291 * @param string $mode either 'all' or 'new' (required)
292 * @param string $user If a user is set, then will only show rules that user has permission to see
293 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
294 * @return array/boolean Array of allergy alerts or FALSE is empty.
296 function allergy_conflict($patient_id, $mode, $user, $test = false)
299 // Collect allergies
300 $sqlParam = array();
301 $sqlParam[] = $patient_id;
302 $res_allergies = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='allergy' " .
303 "AND `activity`=1 " .
304 "AND ( " .
305 dateEmptySql('enddate') .
306 "OR `enddate` > NOW() ) " .
307 "AND `pid`=?", $sqlParam);
308 $allergies = array();
309 for ($iter = 0; $row = sqlFetchArray($res_allergies); $iter++) {
310 $allergies[$iter] = $row['title'];
313 // Build sql element of IN for below queries
314 $sqlParam = array();
315 $sqlIN = '';
316 $firstFlag = true;
317 foreach ($allergies as $allergy) {
318 $sqlParam[] = $allergy;
319 if ($firstFlag) {
320 $sqlIN .= "?";
321 $firstFlag = false;
322 } else {
323 $sqlIN .= ",?";
327 // Check if allergies conflict with medications or prescriptions
328 $conflicts_unique = array();
329 if (!empty($sqlParam)) {
330 $conflicts = array();
331 $sqlParam[] = $patient_id;
332 $res_meds = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='medication' " .
333 "AND `activity`=1 " .
334 "AND ( " .
335 dateEmptySql('enddate') .
336 "OR `enddate` > NOW() )" .
337 "AND `title` IN (" . $sqlIN . ") AND `pid`=?", $sqlParam);
338 while ($urow = sqlFetchArray($res_meds)) {
339 $conflicts[] = $urow['title'];
342 $res_rx = sqlStatement("SELECT `drug` FROM `prescriptions` WHERE `active`=1 " .
343 "AND `drug` IN (" . $sqlIN . ") AND `patient_id`=?", $sqlParam);
344 while ($urow = sqlFetchArray($res_rx)) {
345 $conflicts[] = $urow['drug'];
348 if (!empty($conflicts)) {
349 $conflicts_unique = array_unique($conflicts);
353 // If there are conflicts, $test is FALSE, and alert logging is on, then run through compare_log_alerts
354 $new_conflicts = array();
355 if ((!empty($conflicts_unique)) && $GLOBALS['enable_alert_log'] && ($test === false)) {
356 $new_conflicts = compare_log_alerts($patient_id, $conflicts_unique, 'allergy_alert', $_SESSION['authUserID'], $mode);
359 if ($mode == 'all') {
360 if (!empty($conflicts_unique)) {
361 return $conflicts_unique;
362 } else {
363 return false;
365 } else { // $mode = 'new'
366 if (!empty($new_conflicts)) {
367 return $new_conflicts;
368 } else {
369 return false;
375 * Compare current alerts with prior (in order to find new actions)
376 * Also functions to log the actions.
378 * @param integer $patient_id pid of selected patient
379 * @param array $current_targets array of targets
380 * @param string $category clinical_reminder_widget, active_reminder_popup, or allergy_alert
381 * @param integer $userid user id of user.
382 * @param string $log_trigger if 'all', then always log. If 'new', then only trigger log when a new item noted.
383 * @return array array with targets with associated rule.
385 function compare_log_alerts($patient_id, $current_targets, $category = 'clinical_reminder_widget', $userid = '', $log_trigger = 'all')
388 if (empty($userid)) {
389 $userid = $_SESSION['authUserID'];
392 if (empty($current_targets)) {
393 $current_targets = array();
396 // Collect most recent action_log
397 $prior_targets_sql = sqlQuery("SELECT `value` FROM `clinical_rules_log` " .
398 "WHERE `category` = ? AND `pid` = ? AND `uid` = ? " .
399 "ORDER BY `id` DESC LIMIT 1", array($category,$patient_id,$userid));
400 $prior_targets = array();
401 if (!empty($prior_targets_sql['value'])) {
402 $prior_targets = json_decode($prior_targets_sql['value'], true);
405 // Compare the current with most recent log
406 if (($category == 'clinical_reminder_widget') || ($category == 'active_reminder_popup')) {
407 //using fancy structure to store multiple elements
408 $new_targets = array_diff_key($current_targets, $prior_targets);
409 } else { // $category == 'allergy_alert'
410 //using simple array
411 $new_targets = array_diff($current_targets, $prior_targets);
414 // Store current action_log and the new items
415 // If $log_trigger=='all'
416 // or If $log_trigger=='new' and there are new items
417 if (($log_trigger == 'all') || (($log_trigger == 'new') && (!empty($new_targets)))) {
418 $current_targets_json = json_encode($current_targets);
419 $new_targets_json = '';
420 if (!empty($new_targets)) {
421 $new_targets_json = json_encode($new_targets);
424 sqlStatement("INSERT INTO `clinical_rules_log` " .
425 "(`date`,`pid`,`uid`,`category`,`value`,`new_value`) " .
426 "VALUES (NOW(),?,?,?,?,?)", array($patient_id,$userid,$category,$current_targets_json,$new_targets_json));
429 // Return new actions (if there are any)
430 return $new_targets;
434 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
436 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
437 * on one patient or patients of one provider). The structure of the returned results is dependent on the
438 * $organize_mode and $mode parameters.
439 * <pre>The results are dependent on the $organize_mode parameter settings
440 * 'default' organize_mode:
441 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
442 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
443 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
444 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
445 * 'plans' organize_mode:
446 * Returns similar to default, but organizes by the active plans
447 * </pre>
449 * @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).
450 * @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.
451 * @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').
452 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
453 * @param string $plan test for specific plan only
454 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
455 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
456 * @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.
457 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
458 * @param integer $report_id id of report in database (if already bookmarked)
459 * @return array See above for organization structure of the results.
461 function test_rules_clinic_batch_method($provider = '', $type = '', $dateTarget = '', $mode = '', $plan = '', $organize_mode = 'default', $options = array(), $pat_prov_rel = 'primary', $batchSize = '', $report_id = null)
464 // Default to a batchsize, if empty
465 if (empty($batchSize)) {
466 $batchSize = 100;
469 // Collect total number of pertinent patients (to calculate batching parameters)
470 $totalNumPatients = (int)buildPatientArray('', $provider, $pat_prov_rel, null, null, true);
472 // Cycle through the batches and collect/combine results
473 if (($totalNumPatients % $batchSize) > 0) {
474 // not perfectly divisible
475 $totalNumberBatches = floor($totalNumPatients / $batchSize) + 1;
476 } else {
477 // perfectly divisible
478 $totalNumberBatches = floor($totalNumPatients / $batchSize);
481 // Fix things in the $options array(). This now stores the number of labs to be used in the denominator in the AMC report.
482 // 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
483 // of planned batches(note the fixed array will go into the test_rules_clinic function, however the original will be used
484 // in the report storing/tracking engine.
485 $options_modified = $options;
486 if (!empty($options_modified['labs_manual'])) {
487 $options_modified['labs_manual'] = $options_modified['labs_manual'] / $totalNumberBatches;
490 // Prepare the database to track/store results
491 $fields = array('provider' => $provider,'mode' => $mode,'plan' => $plan,'organize_mode' => $organize_mode,'pat_prov_rel' => $pat_prov_rel);
492 if (is_array($dateTarget)) {
493 $fields = array_merge($fields, array('date_target' => $dateTarget['dateTarget']));
494 $fields = array_merge($fields, array('date_begin' => $dateTarget['dateBegin']));
495 } else {
496 if (empty($dateTarget)) {
497 $fields = array_merge($fields, array('date_target' => date("Y-m-d H:i:s")));
498 } else {
499 $fields = array_merge($fields, array('date_target' => $dateTarget));
503 if (!empty($options)) {
504 foreach ($options as $key => $value) {
505 $fields = array_merge($fields, array($key => $value));
509 $report_id = beginReportDatabase($type, $fields, $report_id);
510 setTotalItemsReportDatabase($report_id, $totalNumPatients);
512 // Set ability to itemize report if this feature is turned on
513 if (
514 ( ($type == "active_alert" || $type == "passive_alert") && ($GLOBALS['report_itemizing_standard']) ) ||
515 ( ($type == "cqm" || $type == "cqm_2011" || $type == "cqm_2014") && ($GLOBALS['report_itemizing_cqm']) ) ||
516 ( ($type == "amc" || $type == "amc_2011" || $type == "amc_2014" || $type == "amc_2014_stage1" || $type == "amc_2014_stage2") && ($GLOBALS['report_itemizing_amc']) )
518 $GLOBALS['report_itemizing_temp_flag_and_id'] = $report_id;
519 } else {
520 $GLOBALS['report_itemizing_temp_flag_and_id'] = 0;
523 for ($i = 0; $i < $totalNumberBatches; $i++) {
524 // If itemization is turned on, then reset the rule id iterator
525 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
526 $GLOBALS['report_itemized_test_id_iterator'] = 1;
529 $dataSheet_batch = test_rules_clinic($provider, $type, $dateTarget, $mode, '', $plan, $organize_mode, $options_modified, $pat_prov_rel, (($batchSize * $i) + 1), $batchSize);
530 $dataSheet = array();
531 if ($i == 0) {
532 // For first cycle, simply copy it to dataSheet
533 $dataSheet = $dataSheet_batch;
534 } else {
535 //debug
536 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
537 //error_log("CDR: ".($batchSize*$i)." records",0);
539 // Integrate batch results into main dataSheet
540 foreach ($dataSheet_batch as $key => $row) {
541 if (!$row['is_sub']) {
542 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
543 $total_patients = $dataSheet[$key]['total_patients'] + $row['total_patients'];
544 $dataSheet[$key]['total_patients'] = $total_patients;
545 $excluded = $dataSheet[$key]['excluded'] + $row['excluded'];
546 $dataSheet[$key]['excluded'] = $excluded;
547 $pass_filter = $dataSheet[$key]['pass_filter'] + $row['pass_filter'];
548 $dataSheet[$key]['pass_filter'] = $pass_filter;
551 $pass_target = $dataSheet[$key]['pass_target'] + $row['pass_target'];
552 $dataSheet[$key]['pass_target'] = $pass_target;
553 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter, $excluded, $pass_target);
557 //Update database to track results
558 updateReportDatabase($report_id, ($total_patients ?? null));
561 // Record results in database and send to screen, if applicable.
562 if (!empty($dataSheet)) {
563 finishReportDatabase($report_id, json_encode($dataSheet));
564 return $dataSheet;
565 } else {
566 return [];
571 * Process clinic rules.
573 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
574 * on one patient or patients of one provider). The structure of the returned results is dependent on the
575 * $organize_mode and $mode parameters.
576 * <pre>The results are dependent on the $organize_mode parameter settings
577 * 'default' organize_mode:
578 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
579 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
580 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
581 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
582 * 'plans' organize_mode:
583 * Returns similar to default, but organizes by the active plans
584 * </pre>
586 * @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).
587 * @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.
588 * @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').
589 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
590 * @param integer $patient_id pid of patient. If blank then will check all patients.
591 * @param string $plan test for specific plan only
592 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
593 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
594 * @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.
595 * @param integer $start applicable patient to start at (when batching process)
596 * @param integer $batchSize number of patients to batch (when batching process)
597 * @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).
598 * @return array See above for organization structure of the results.
600 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 = '')
603 // If dateTarget is an array, then organize them.
604 if (is_array($dateTarget)) {
605 $dateArray = $dateTarget;
606 $dateTarget = $dateTarget['dateTarget'];
609 // Set date to current if not set
610 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
612 // Prepare the results array
613 $results = array();
615 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
616 // then run through this function recursively and return results.
617 if (($provider === "collate_outer") || ($provider === "collate_inner" && $organize_mode !== 'plans')) {
618 // First, collect an array of all providers
619 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
620 $ures = sqlStatementCdrEngine($query);
621 // Second, run through each provider recursively
622 while ($urow = sqlFetchArray($ures)) {
623 $newResults = test_rules_clinic($urow['id'], $type, $dateTarget, $mode, $patient_id, $plan, $organize_mode, $options, $pat_prov_rel, $start, $batchSize, $user);
624 if (!empty($newResults)) {
625 $provider_item['is_provider'] = true;
626 $provider_item['prov_lname'] = $urow['lname'];
627 $provider_item['prov_fname'] = $urow['fname'];
628 $provider_item['npi'] = $urow['npi'];
629 $provider_item['federaltaxid'] = $urow['federaltaxid'];
630 $results[] = $provider_item;
631 $results = array_merge($results, $newResults);
635 // done, so now can return results
636 return $results;
639 // If set organize-mode to plans, then collects active plans and run through this
640 // function recursively and return results.
641 if ($organize_mode === "plans") {
642 // First, collect active plans
643 $plans_resolve = resolve_plans_sql($plan, $patient_id);
644 // Second, run through function recursively
645 foreach ($plans_resolve as $plan_item) {
646 // (if collate_inner, then nest a collation of providers within each plan)
647 if ($provider === "collate_inner") {
648 // First, collect an array of all providers
649 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
650 $ures = sqlStatementCdrEngine($query);
651 // Second, run through each provider recursively
652 $provider_results = array();
653 while ($urow = sqlFetchArray($ures)) {
654 $newResults = test_rules_clinic($urow['id'], $type, $dateTarget, $mode, $patient_id, $plan_item['id'], 'default', $options, $pat_prov_rel, $start, $batchSize, $user);
655 if (!empty($newResults)) {
656 $provider_item['is_provider'] = true;
657 $provider_item['prov_lname'] = $urow['lname'];
658 $provider_item['prov_fname'] = $urow['fname'];
659 $provider_item['npi'] = $urow['npi'];
660 $provider_item['federaltaxid'] = $urow['federaltaxid'];
661 $provider_results[] = $provider_item;
662 $provider_results = array_merge($provider_results, $newResults);
666 if (!empty($provider_results)) {
667 $plan_item['is_plan'] = true;
668 $results[] = $plan_item;
669 $results = array_merge($results, $provider_results);
671 } else {
672 // (not collate_inner, so do not nest providers within each plan)
673 $newResults = test_rules_clinic($provider, $type, $dateTarget, $mode, $patient_id, $plan_item['id'], 'default', $options, $pat_prov_rel, $start, $batchSize, $user);
674 if (!empty($newResults)) {
675 $plan_item['is_plan'] = true;
676 $results[] = $plan_item;
677 $results = array_merge($results, $newResults);
682 // done, so now can return results
683 return $results;
686 // Collect applicable patient pids
687 $patientData = buildPatientArray($patient_id, $provider, $pat_prov_rel, $start, $batchSize);
689 // Go through each patient(s)
691 // If in report mode, then tabulate for each rule:
692 // Total Patients
693 // Patients that pass the filter
694 // Patients that pass the target
695 // If in reminders mode, then create reminders for each rule:
696 // Reminder that action is due soon
697 // Reminder that action is due
698 // Reminder that action is post-due
700 //Collect applicable rules
701 // Note that due to a limitation in the this function, the patient_id is explicitly
702 // for grouping items when not being done in real-time or for official reporting.
703 // So for cases such as patient reminders on a clinic scale, the calling function
704 // will actually need rather than pass in a explicit patient_id for each patient in
705 // a separate call to this function.
706 if ($mode != "report") {
707 // Use per patient custom rules (if exist)
708 // Note as discussed above, this only works for single patient instances.
709 $rules = resolve_rules_sql($type, $patient_id, false, $plan, $user);
710 } else { // $mode = "report"
711 // Only use default rules (do not use patient custom rules)
712 $rules = resolve_rules_sql($type, $patient_id, false, $plan, $user);
715 foreach ($rules as $rowRule) {
716 // If using cqm or amc type, then use the hard-coded rules set.
717 // Note these rules are only used in report mode.
718 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
719 require_once(dirname(__FILE__) . "/classes/rulesets/ReportManager.php");
720 $manager = new ReportManager();
721 if ($rowRule['amc_flag']) {
722 // Send array of dates ('dateBegin' and 'dateTarget')
723 $tempResults = $manager->runReport($rowRule, $patientData, $dateArray, $options);
724 } else {
725 // Send target date
726 $tempResults = $manager->runReport($rowRule, $patientData, $dateTarget);
729 if (!empty($tempResults)) {
730 foreach ($tempResults as $tempResult) {
731 $results[] = $tempResult;
735 // Go on to the next rule
736 continue;
739 // If in reminder mode then need to collect the measurement dates
740 // from rule_reminder table
741 $target_dates = array();
742 if ($mode != "report") {
743 // Calculate the dates to check for
744 if ($type == "patient_reminder") {
745 $reminder_interval_type = "patient_reminder";
746 } else { // $type == "passive_alert" or $type == "active_alert"
747 $reminder_interval_type = "clinical_reminder";
750 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
751 } else { // $mode == "report"
752 // Only use the target date in the report
753 $target_dates[0] = $dateTarget;
756 //Reset the counters
757 $total_patients = 0;
758 $pass_filter = 0;
759 $exclude_filter = 0;
760 $pass_target = 0;
762 // Find the number of target groups
763 $targetGroups = returnTargetGroups($rowRule['id']);
765 if ((count($targetGroups) == 1) || ($mode == "report")) {
766 // If report itemization is turned on, then iterate the rule id iterator
767 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
768 $GLOBALS['report_itemized_test_id_iterator']++;
771 //skip this section if not report and more than one target group
772 foreach ($patientData as $rowPatient) {
773 // First, deal with deceased patients
774 // (for now will simply skip the patient)
775 // If want to support rules for deceased patients then will need to migrate this below
776 // in target_dates foreach(guessing won't ever need to do this, though).
777 // Note using the dateTarget rather than dateFocus
778 if (is_patient_deceased($rowPatient['pid'], $dateTarget)) {
779 continue;
782 // Count the total patients
783 $total_patients++;
785 $dateCounter = 1; // for reminder mode to keep track of which date checking
786 // If report itemization is turned on, reset flag.
787 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
788 $temp_track_pass = 1;
791 foreach ($target_dates as $dateFocus) {
792 //Skip if date is set to SKIP
793 if ($dateFocus == "SKIP") {
794 $dateCounter++;
795 continue;
798 //Set date counter and reminder token (applicable for reminders only)
799 if ($dateCounter == 1) {
800 $reminder_due = "soon_due";
801 } elseif ($dateCounter == 2) {
802 $reminder_due = "due";
803 } else { // $dateCounter == 3
804 $reminder_due = "past_due";
807 // Check if pass filter
808 $passFilter = test_filter($rowPatient['pid'], $rowRule['id'], $dateFocus);
809 if ($passFilter === "EXCLUDED") {
810 // increment EXCLUDED and pass_filter counters
811 // and set as FALSE for reminder functionality.
812 $pass_filter++;
813 $exclude_filter++;
814 $passFilter = false;
817 if ($passFilter) {
818 // increment pass filter counter
819 $pass_filter++;
820 // If report itemization is turned on, trigger flag.
821 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
822 $temp_track_pass = 0;
824 } else {
825 $dateCounter++;
826 continue;
829 // Check if pass target
830 $passTarget = test_targets($rowPatient['pid'], $rowRule['id'], '', $dateFocus);
831 if ($passTarget) {
832 // increment pass target counter
833 $pass_target++;
834 // If report itemization is turned on, then record the "passed" item and set the flag
835 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
836 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
837 $temp_track_pass = 1;
840 // send to reminder results
841 if ($mode == "reminders-all") {
842 // place the completed actions into the reminder return array
843 $actionArray = resolve_action_sql($rowRule['id'], '1');
844 foreach ($actionArray as $action) {
845 $action_plus = $action;
846 $action_plus['due_status'] = "not_due";
847 $action_plus['pid'] = $rowPatient['pid'];
848 $action_plus['rule_id'] = $rowRule['id'];
849 $results = reminder_results_integrate($results, $action_plus);
853 break;
854 } else {
855 // send to reminder results
856 if ($mode != "report") {
857 // place the uncompleted actions into the reminder return array
858 $actionArray = resolve_action_sql($rowRule['id'], '1');
859 foreach ($actionArray as $action) {
860 $action_plus = $action;
861 $action_plus['due_status'] = $reminder_due;
862 $action_plus['pid'] = $rowPatient['pid'];
863 $action_plus['rule_id'] = $rowRule['id'];
864 $results = reminder_results_integrate($results, $action_plus);
869 $dateCounter++;
872 // If report itemization is turned on, then record the "failed" item if it did not pass
873 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id']) && !($temp_track_pass)) {
874 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
879 // Calculate and save the data for the rule
880 $percentage = calculate_percentage($pass_filter, $exclude_filter, $pass_target);
881 if ($mode == "report") {
882 $newRow = array('is_main' => true,'total_patients' => $total_patients,'excluded' => $exclude_filter,'pass_filter' => $pass_filter,'pass_target' => $pass_target,'percentage' => $percentage);
883 $newRow = array_merge($newRow, $rowRule);
885 // If itemization is turned on, then record the itemized_test_id
886 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
887 $newRow = array_merge($newRow, array('itemized_test_id' => $GLOBALS['report_itemized_test_id_iterator']));
890 $results[] = $newRow;
893 // Now run through the target groups if more than one
894 if (count($targetGroups) > 1) {
895 foreach ($targetGroups as $i) {
896 // If report itemization is turned on, then iterate the rule id iterator
897 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
898 $GLOBALS['report_itemized_test_id_iterator']++;
901 //Reset the target counter
902 $pass_target = 0;
904 foreach ($patientData as $rowPatient) {
905 // First, deal with deceased patients
906 // (for now will simply skip the patient)
907 // If want to support rules for deceased patients then will need to migrate this below
908 // in target_dates foreach(guessing won't ever need to do this, though).
909 // Note using the dateTarget rather than dateFocus
910 if (is_patient_deceased($rowPatient['pid'], $dateTarget)) {
911 continue;
914 $dateCounter = 1; // for reminder mode to keep track of which date checking
915 // If report itemization is turned on, reset flag.
916 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
917 $temp_track_pass = 1;
920 foreach ($target_dates as $dateFocus) {
921 //Skip if date is set to SKIP
922 if ($dateFocus == "SKIP") {
923 $dateCounter++;
924 continue;
927 //Set date counter and reminder token (applicable for reminders only)
928 if ($dateCounter == 1) {
929 $reminder_due = "soon_due";
930 } elseif ($dateCounter == 2) {
931 $reminder_due = "due";
932 } else { // $dateCounter == 3
933 $reminder_due = "past_due";
936 // Check if pass filter
937 $passFilter = test_filter($rowPatient['pid'], $rowRule['id'], $dateFocus);
938 if ($passFilter === "EXCLUDED") {
939 $passFilter = false;
942 if (!$passFilter) {
943 $dateCounter++;
944 continue;
945 } else {
946 // If report itemization is turned on, trigger flag.
947 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
948 $temp_track_pass = 0;
952 //Check if pass target
953 $passTarget = test_targets($rowPatient['pid'], $rowRule['id'], $i, $dateFocus);
954 if ($passTarget) {
955 // increment pass target counter
956 $pass_target++;
957 // If report itemization is turned on, then record the "passed" item and set the flag
958 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
959 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
960 $temp_track_pass = 1;
963 // send to reminder results
964 if ($mode == "reminders-all") {
965 // place the completed actions into the reminder return array
966 $actionArray = resolve_action_sql($rowRule['id'], $i);
967 foreach ($actionArray as $action) {
968 $action_plus = $action;
969 $action_plus['due_status'] = "not_due";
970 $action_plus['pid'] = $rowPatient['pid'];
971 $action_plus['rule_id'] = $rowRule['id'];
972 $results = reminder_results_integrate($results, $action_plus);
976 break;
977 } else {
978 // send to reminder results
979 if ($mode != "report") {
980 // place the actions into the reminder return array
981 $actionArray = resolve_action_sql($rowRule['id'], $i);
982 foreach ($actionArray as $action) {
983 $action_plus = $action;
984 $action_plus['due_status'] = $reminder_due;
985 $action_plus['pid'] = $rowPatient['pid'];
986 $action_plus['rule_id'] = $rowRule['id'];
987 $results = reminder_results_integrate($results, $action_plus);
992 $dateCounter++;
995 // If report itemization is turned on, then record the "failed" item if it did not pass
996 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id']) && !($temp_track_pass)) {
997 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
1001 // Calculate and save the data for the rule
1002 $percentage = calculate_percentage($pass_filter, $exclude_filter, $pass_target);
1004 // Collect action for title (just use the first one, if more than one)
1005 $actionArray = resolve_action_sql($rowRule['id'], $i);
1006 $action = $actionArray[0];
1007 if ($mode == "report") {
1008 $newRow = array('is_sub' => true,'action_category' => $action['category'],'action_item' => $action['item'],'total_patients' => '','excluded' => '','pass_filter' => '','pass_target' => $pass_target,'percentage' => $percentage);
1010 // If itemization is turned on, then record the itemized_test_id
1011 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
1012 $newRow = array_merge($newRow, array('itemized_test_id' => $GLOBALS['report_itemized_test_id_iterator']));
1015 $results[] = $newRow;
1021 // Return the data
1022 return $results;
1026 * Process patient array that is to be tested.
1028 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
1029 * @param integer $patient_id pid of patient. If blank then will check all patients.
1030 * @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.
1031 * @param integer $start applicable patient to start at (when batching process)
1032 * @param integer $batchSize number of patients to batch (when batching process)
1033 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
1034 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
1036 function buildPatientArray($patient_id = '', $provider = '', $pat_prov_rel = 'primary', $start = null, $batchSize = null, $onlyCount = false)
1039 if (!empty($patient_id)) {
1040 // only look at the selected patient
1041 if ($onlyCount) {
1042 $patientNumber = 1;
1043 } else {
1044 $patientData[0]['pid'] = $patient_id;
1046 } else {
1047 if (empty($provider)) {
1048 // Look at entire practice
1049 if ($start == null || $batchSize == null || $onlyCount) {
1050 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
1051 if ($onlyCount) {
1052 $patientNumber = sqlNumRows($rez);
1054 } else {
1055 // batching
1056 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start - 1),$batchSize));
1058 } else {
1059 // Look at an individual physician
1060 if ($pat_prov_rel == 'encounter') {
1061 // Choose patients that are related to specific physician by an encounter
1062 if ($start == null || $batchSize == null || $onlyCount) {
1063 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` " .
1064 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
1065 if ($onlyCount) {
1066 $patientNumber = sqlNumRows($rez);
1068 } else {
1069 //batching
1070 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` " .
1071 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start - 1),$batchSize));
1073 } else { //$pat_prov_rel == 'primary'
1074 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
1075 if ($start == null || $batchSize == null || $onlyCount) {
1076 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1077 "WHERE `providerID`=? ORDER BY `pid`", array($provider));
1078 if ($onlyCount) {
1079 $patientNumber = sqlNumRows($rez);
1081 } else {
1082 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1083 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start - 1),$batchSize));
1088 // convert the sql query results into an array if returning the array
1089 if (!$onlyCount) {
1090 for ($iter = 0; $row = sqlFetchArray($rez); $iter++) {
1091 $patientData[$iter] = $row;
1096 if ($onlyCount) {
1097 // return the number of applicable patients
1098 return $patientNumber;
1099 } else {
1100 // return array of patient pids
1101 return $patientData;
1106 * Test filter of a selected rule on a selected patient
1108 * @param integer $patient_id pid of selected patient.
1109 * @param string $rule id(string) of selected rule
1110 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
1111 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
1113 function test_filter($patient_id, $rule, $dateTarget)
1116 // Set date to current if not set
1117 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1119 // Collect patient information
1120 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
1123 // ----------------- INCLUSIONS -----------------
1126 // -------- Age Filter (inclusion) ------------
1127 // Calculate patient age in years and months
1128 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'], $dateTarget);
1129 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'], $dateTarget);
1131 // Min age (year) Filter (assume that there in not more than one of each)
1132 $filter = resolve_filter_sql($rule, 'filt_age_min');
1133 if (!empty($filter)) {
1134 $row = $filter[0];
1135 if ($row ['method_detail'] == "year") {
1136 if ($row['value'] && ($row['value'] > $patientAgeYears)) {
1137 return false;
1141 if ($row ['method_detail'] == "month") {
1142 if ($row['value'] && ($row['value'] > $patientAgeMonths)) {
1143 return false;
1148 // Max age (year) Filter (assume that there in not more than one of each)
1149 $filter = resolve_filter_sql($rule, 'filt_age_max');
1150 if (!empty($filter)) {
1151 $row = $filter[0];
1152 if ($row ['method_detail'] == "year") {
1153 if ($row['value'] && ($row['value'] < $patientAgeYears)) {
1154 return false;
1158 if ($row ['method_detail'] == "month") {
1159 if ($row['value'] && ($row['value'] < $patientAgeMonths)) {
1160 return false;
1165 // -------- Gender Filter (inclusion) ---------
1166 // Gender Filter (assume that there in not more than one of each)
1167 $filter = resolve_filter_sql($rule, 'filt_sex');
1168 if (!empty($filter)) {
1169 $row = $filter[0];
1170 if ($row['value'] && ($row['value'] != $patientData['sex'])) {
1171 return false;
1175 // -------- Database Filter (inclusion) ------
1176 // Database Filter
1177 $filter = resolve_filter_sql($rule, 'filt_database');
1178 if ((!empty($filter)) && !database_check($patient_id, $filter, '', $dateTarget)) {
1179 return false;
1182 // -------- Lists Filter (inclusion) ----
1183 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
1184 // surgeries and allergies.
1185 $filter = resolve_filter_sql($rule, 'filt_lists');
1186 if ((!empty($filter)) && !lists_check($patient_id, $filter, $dateTarget)) {
1187 return false;
1190 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
1191 // Procedure Target (includes) (may need to include an interval in the future)
1192 $filter = resolve_filter_sql($rule, 'filt_proc');
1193 if ((!empty($filter)) && !procedure_check($patient_id, $filter, '', $dateTarget)) {
1194 return false;
1198 // ----------------- EXCLUSIONS -----------------
1201 // -------- Lists Filter (EXCLUSION) ----
1202 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
1203 // surgeries and allergies.
1204 $filter = resolve_filter_sql($rule, 'filt_lists', 0);
1205 if ((!empty($filter)) && lists_check($patient_id, $filter, $dateTarget)) {
1206 return "EXCLUDED";
1209 // Passed all filters, so return true.
1210 return true;
1214 * Return an array containing existing group ids for a rule
1216 * @param string $rule id(string) of rule
1217 * @return array listing of group ids
1219 function returnTargetGroups($rule)
1222 $sql = sqlStatementCdrEngine("SELECT DISTINCT `group_id` FROM `rule_target` " .
1223 "WHERE `id`=?", array($rule));
1225 $groups = array();
1226 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1227 $groups[] = $row['group_id'];
1230 return $groups;
1234 * Test targets of a selected rule on a selected patient
1236 * @param integer $patient_id pid of selected patient.
1237 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
1238 * @param integer $group_id group id of target group
1239 * @param string $dateTarget target date (format Y-m-d H:i:s).
1240 * @return boolean if target passes then true, otherwise false
1242 function test_targets($patient_id, $rule, string $group_id = null, $dateTarget = null)
1245 // -------- Interval Target ----
1246 $interval = resolve_target_sql($rule, $group_id, 'target_interval');
1248 // -------- Database Target ----
1249 // Database Target (includes)
1250 $target = resolve_target_sql($rule, $group_id, 'target_database');
1251 if ((!empty($target)) && !database_check($patient_id, $target, $interval, $dateTarget)) {
1252 return false;
1255 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
1256 // Procedure Target (includes)
1257 $target = resolve_target_sql($rule, $group_id, 'target_proc');
1258 if ((!empty($target)) && !procedure_check($patient_id, $target, $interval, $dateTarget)) {
1259 return false;
1262 // -------- Appointment Target ----
1263 // Appointment Target (includes) (Specialized functionality for appointment reminders)
1264 $target = resolve_target_sql($rule, $group_id, 'target_appt');
1266 return !((!empty($target)) && appointment_check($patient_id, $dateTarget));
1270 * Function to return active plans
1272 * @param string $type plan type filter (normal or cqm or blank)
1273 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
1274 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
1275 * @return array active plans
1277 function resolve_plans_sql($type = '', $patient_id = '0', $configurableOnly = false)
1280 if ($configurableOnly) {
1281 // Collect all default, configurable (per patient) plans into an array
1282 // (ie. ignore the cqm rules)
1283 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
1284 } else {
1285 // Collect all default plans into an array
1286 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
1289 $returnArray = array();
1290 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1291 $returnArray[] = $row;
1294 // Now collect the pertinent plans
1295 $newReturnArray = array();
1297 // Need to select rules (use custom if exist)
1298 foreach ($returnArray as $plan) {
1299 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id));
1301 // Decide if use default vs custom plan (preference given to custom plan)
1302 if (!empty($customPlan)) {
1303 if ($type == "cqm") {
1304 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
1305 $goPlan = $plan;
1306 } else {
1307 // merge the custom plan with the default plan
1308 $mergedPlan = array();
1309 foreach ($customPlan as $key => $value) {
1310 if ($value == null && preg_match("/_flag$/", $key)) {
1311 // use default setting
1312 $mergedPlan[$key] = $plan[$key];
1313 } else {
1314 // use custom setting
1315 $mergedPlan[$key] = $value;
1319 $goPlan = $mergedPlan;
1321 } else {
1322 $goPlan = $plan;
1325 // Use the chosen plan if set
1326 if (!empty($type)) {
1327 if ($goPlan["${type}_flag"] == 1) {
1328 // active, so use the plan
1329 $newReturnArray[] = $goPlan;
1331 } else {
1332 if (
1333 $goPlan['normal_flag'] == 1 ||
1334 $goPlan['cqm_flag'] == 1
1336 // active, so use the plan
1337 $newReturnArray[] = $goPlan;
1342 $returnArray = $newReturnArray;
1344 return $returnArray;
1349 * Function to return a specific plan
1351 * @param string $plan id(string) of plan
1352 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1353 * @return array a plan
1355 function collect_plan($plan, $patient_id = '0')
1358 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id));
1362 * Function to set a specific plan activity for a specific patient
1364 * @param string $plan id(string) of plan
1365 * @param string $type plan filter (normal,cqm)
1366 * @param string $setting activity of plan (yes,no,default)
1367 * @param integer $patient_id pid of selected patient.
1369 function set_plan_activity_patient($plan, $type, $setting, $patient_id)
1372 // Don't allow messing with the default plans here
1373 if ($patient_id == "0") {
1374 return;
1377 // Convert setting
1378 if ($setting == "on") {
1379 $setting = 1;
1380 } elseif ($setting == "off") {
1381 $setting = 0;
1382 } else { // $setting == "default"
1383 $setting = null;
1386 // Collect patient specific plan, if already exists.
1387 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
1388 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id));
1390 if (empty($patient_plan)) {
1391 // Create a new patient specific plan with flags all set to default
1392 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1393 sqlStatementCdrEngine($query, array($plan, $patient_id));
1396 // Update patient specific row
1397 $query = "UPDATE `clinical_plans` SET `" . escape_sql_column_name($type . "_flag", array("clinical_plans")) . "`= ? WHERE id = ? AND pid = ?";
1398 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id));
1402 * Function to return active rules
1404 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2014,amc_2011,amc_2014,patient_reminder)
1405 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1406 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1407 * @param string $plan collect rules for specific plan
1408 * @param string $user If a user is set, then will only show rules that user has permission to see
1409 * @return array rules
1411 function resolve_rules_sql($type = '', $patient_id = '0', $configurableOnly = false, $plan = '', $user = '')
1414 if ($configurableOnly) {
1415 // Collect all default, configurable (per patient) rules into an array
1416 // (ie. ignore the cqm and amc rules)
1417 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1418 } else {
1419 // Collect all default rules into an array
1420 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1423 $returnArray = array();
1424 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1425 $returnArray[] = $row;
1428 // Now filter rules for plan (if applicable)
1429 if (!empty($plan)) {
1430 $planReturnArray = array();
1431 foreach ($returnArray as $rule) {
1432 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1433 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']));
1434 if (!empty($standardRule)) {
1435 $planReturnArray[] = $rule;
1439 $returnArray = $planReturnArray;
1442 // Now collect the pertinent rules
1443 $newReturnArray = array();
1445 // Need to select rules (use custom if exist)
1446 foreach ($returnArray as $rule) {
1447 // If user is set, then check if user has access to the rule
1448 if (!empty($user)) {
1449 $access_control = explode(':', $rule['access_control']);
1450 if (!empty($access_control[0]) && !empty($access_control[1])) {
1451 // Section and ACO filters are not empty, so do the test for access.
1452 if (!AclMain::aclCheckCore($access_control[0], $access_control[1], $user)) {
1453 // User does not have access to this rule, so skip the rule.
1454 continue;
1456 } else {
1457 // Section or ACO filters are empty, so use default patients:med aco
1458 if (!AclMain::aclCheckCore('patients', 'med', $user)) {
1459 // User does not have access to this rule, so skip the rule.
1460 continue;
1465 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id));
1467 // Decide if use default vs custom rule (preference given to custom rule)
1468 if (!empty($customRule)) {
1469 if ($type == "cqm" || $type == "amc") {
1470 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1471 $goRule = $rule;
1472 } else {
1473 // merge the custom rule with the default rule
1474 $mergedRule = array();
1475 foreach ($customRule as $key => $value) {
1476 if ($value == null && preg_match("/_flag$/", $key)) {
1477 // use default setting
1478 $mergedRule[$key] = $rule[$key];
1479 } else {
1480 // use custom setting
1481 $mergedRule[$key] = $value;
1485 $goRule = $mergedRule;
1487 } else {
1488 $goRule = $rule;
1491 // Use the chosen rule if set
1492 if (!empty($type)) {
1493 if ($goRule["${type}_flag"] == 1) {
1494 // active, so use the rule
1495 $newReturnArray[] = $goRule;
1497 } else {
1498 // no filter, so return the rule
1499 $newReturnArray[] = $goRule;
1503 $returnArray = $newReturnArray;
1505 return $returnArray;
1509 * Function to return a specific rule
1511 * @param string $rule id(string) of rule
1512 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1513 * @return array rule
1515 function collect_rule($rule, $patient_id = '0')
1518 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id));
1522 * Function to set a specific rule activity for a specific patient
1524 * @param string $rule id(string) of rule
1525 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1526 * @param string $setting activity of rule (yes,no,default)
1527 * @param integer $patient_id pid of selected patient.
1529 function set_rule_activity_patient($rule, $type, $setting, $patient_id)
1532 // Don't allow messing with the default rules here
1533 if ($patient_id == "0") {
1534 return;
1537 // Convert setting
1538 if ($setting == "on") {
1539 $setting = 1;
1540 } elseif ($setting == "off") {
1541 $setting = 0;
1542 } else { // $setting == "default"
1543 $setting = null;
1546 //Collect main rule to allow setting of the access_control
1547 $original_query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = 0";
1548 $patient_rule_original = sqlQueryCdrEngine($original_query, array($rule));
1550 // Collect patient specific rule, if already exists.
1551 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1552 $patient_rule = sqlQueryCdrEngine($query, array($rule,$patient_id));
1554 if (empty($patient_rule)) {
1555 // Create a new patient specific rule with flags all set to default
1556 $query = "INSERT into `clinical_rules` (`id`, `pid`, `access_control`) VALUES (?,?,?)";
1557 sqlStatementCdrEngine($query, array($rule, $patient_id, $patient_rule_original['access_control']));
1560 // Update patient specific row
1561 $query = "UPDATE `clinical_rules` SET `" . escape_sql_column_name($type . "_flag", ["clinical_rules"]) . "`= ?, `access_control` = ? WHERE id = ? AND pid = ?";
1562 sqlStatementCdrEngine($query, array($setting,$patient_rule_original['access_control'],$rule,$patient_id));
1566 * Function to return applicable reminder dates (relative)
1568 * @param string $rule id(string) of selected rule
1569 * @param string $reminder_method string label of filter type
1570 * @return array reminder features
1572 function resolve_reminder_sql($rule, $reminder_method)
1574 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1575 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method));
1577 $returnArray = array();
1578 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1579 $returnArray[] = $row;
1582 return $returnArray;
1586 * Function to return applicable filters
1588 * @param string $rule id(string) of selected rule
1589 * @param string $filter_method string label of filter type
1590 * @param string $include_flag to allow selection for included or excluded filters
1591 * @return array filters
1593 function resolve_filter_sql($rule, $filter_method, $include_flag = 1)
1595 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1596 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag));
1598 $returnArray = array();
1599 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1600 $returnArray[] = $row;
1603 return $returnArray;
1607 * Function to return applicable targets
1609 * @param string $rule id(string) of selected rule
1610 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1611 * @param string $target_method string label of target type
1612 * @param string $include_flag to allow selection for included or excluded targets
1613 * @return array targets
1615 function resolve_target_sql($rule, string $group_id = null, $target_method = '', $include_flag = 1)
1618 if ($group_id) {
1619 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1620 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag));
1621 } else {
1622 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1623 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag));
1626 $returnArray = array();
1627 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1628 $returnArray[] = $row;
1631 return $returnArray;
1635 * Function to return applicable actions
1637 * @param string $rule id(string) of selected rule
1638 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1639 * @return array actions
1641 function resolve_action_sql($rule, $group_id = '')
1644 if ($group_id) {
1645 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1646 "FROM `rule_action` as a " .
1647 "JOIN `rule_action_item` as b " .
1648 "ON a.category = b.category AND a.item = b.item " .
1649 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id));
1650 } else {
1651 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1652 "FROM `rule_action` as a " .
1653 "JOIN `rule_action_item` as b " .
1654 "ON a.category = b.category AND a.item = b.item " .
1655 "WHERE a.id=?", array($rule));
1658 $returnArray = array();
1659 for ($iter = 0; $row = sqlFetchArray($sql); $iter++) {
1660 $returnArray[] = $row;
1663 return $returnArray;
1667 * Function to check database filters and targets
1669 * @param string $patient_id pid of selected patient.
1670 * @param array $filter array containing filter/target elements
1671 * @param array $interval array containing interval elements
1672 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1673 * @return boolean true if check passed, otherwise false
1675 function database_check($patient_id, $filter, $interval = '', $dateTarget = '')
1677 $isMatch = false; //matching flag
1679 // Set date to current if not set
1680 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1682 // Unpackage interval information
1683 // (Assume only one for now and only pertinent for targets)
1684 $intervalType = '';
1685 $intervalValue = '';
1686 if (!empty($interval)) {
1687 $intervalType = $interval[0]['value'];
1688 $intervalValue = $interval[0]['interval'];
1691 $cond_loop = 0;
1692 foreach ($filter as $row) {
1693 // Row description
1694 // [0]=>special modes
1695 $temp_df = explode("::", $row['value']);
1697 if ($temp_df[0] == "CUSTOM") {
1698 // Row description
1699 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1700 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1701 // Record the match
1702 $isMatch = true;
1703 } else {
1704 // If this is a required entry then return false
1705 if ($row['required_flag']) {
1706 return false;
1709 } elseif ($temp_df[0] == "LIFESTYLE") {
1710 // Row description
1711 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1712 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1713 // Record the match
1714 $isMatch = true;
1715 } else {
1716 // If this is a required entry then return false
1717 if ($row['required_flag']) {
1718 return false;
1721 } else {
1722 // Default mode
1723 // Row description
1724 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1725 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)) {
1726 // Record the match
1727 if ($cond_loop > 0) { // For multiple condition check
1728 $isMatch = $isMatch && 1;
1729 } else {
1730 $isMatch = true;
1732 } else {
1733 // If this is a required entry then return false
1734 if ($row['required_flag']) {
1735 return false;
1740 $cond_loop++;
1743 // return results of check
1744 return $isMatch;
1748 * Function to check procedure filters and targets
1750 * @param string $patient_id pid of selected patient.
1751 * @param array $filter array containing filter/target elements
1752 * @param array $interval array containing interval elements
1753 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1754 * @return boolean true if check passed, otherwise false
1756 function procedure_check($patient_id, $filter, $interval = '', $dateTarget = '')
1758 $isMatch = false; //matching flag
1760 // Set date to current if not set
1761 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1763 // Unpackage interval information
1764 // (Assume only one for now and only pertinent for targets)
1765 $intervalType = '';
1766 $intervalValue = '';
1767 if (!empty($interval)) {
1768 $intervalType = $interval[0]['value'];
1769 $intervalValue = $interval[0]['interval'];
1772 foreach ($filter as $row) {
1773 // Row description
1774 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1775 // code description
1776 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1777 $temp_df = explode("::", $row['value']);
1778 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)) {
1779 // Record the match
1780 $isMatch = true;
1781 } else {
1782 // If this is a required entry then return false
1783 if ($row['required_flag']) {
1784 return false;
1789 // return results of check
1790 return $isMatch;
1794 * Function to check for appointment
1796 * @todo Complete this to allow appointment reminders.
1797 * @param string $patient_id pid of selected patient.
1798 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1799 * @return boolean true if appt exist, otherwise false
1801 function appointment_check($patient_id, $dateTarget = '')
1803 $isMatch = false; //matching flag
1805 // Set date to current if not set (although should always be set)
1806 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1807 $dateTargetRound = date('Y-m-d', $dateTarget);
1809 // Set current date
1810 $currentDate = date('Y-m-d H:i:s');
1812 // Basically, if the appointment is within the current date to the target date,
1813 // then return true. (will not send reminders on same day as appointment)
1814 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1815 "openemr_postcalendar_events.pc_title, " .
1816 "openemr_postcalendar_events.pc_eventDate, " .
1817 "openemr_postcalendar_events.pc_startTime, " .
1818 "openemr_postcalendar_events.pc_endTime " .
1819 "FROM openemr_postcalendar_events " .
1820 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1821 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1822 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id));
1824 // return results of check
1826 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1827 // Plan to send back array of appt info (eid, time, date, etc.)
1828 // to do this.
1829 if (sqlNumRows($sql) > 0) {
1830 $isMatch = true;
1833 return $isMatch;
1837 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1839 * @param string $patient_id pid of selected patient.
1840 * @param array $filter array containing lists filter/target elements
1841 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1842 * @return boolean true if check passed, otherwise false
1844 function lists_check($patient_id, $filter, $dateTarget)
1846 $isMatch = false; //matching flag
1848 // Set date to current if not set
1849 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1851 foreach ($filter as $row) {
1852 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1853 // Record the match
1854 $isMatch = true;
1855 } else {
1856 // If this is a required entry then return false
1857 if ($row['required_flag']) {
1858 return false;
1863 // return results of check
1864 return $isMatch;
1868 * Function to check for existance of data in database for a patient
1870 * @param string $patient_id pid of selected patient.
1871 * @param string $table selected mysql table
1872 * @param string $column selected mysql column
1873 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1874 * @param string $data selected data in the mysql database (1)(2)
1875 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1876 * @param integer $num_items_thres number of items threshold
1877 * @param string $intervalType type of interval (ie. year)
1878 * @param integer $intervalValue searched for within this many times of the interval type
1879 * @param string $dateTarget target date(format Y-m-d H:i:s).
1880 * @return boolean true if check passed, otherwise false
1882 * (1) If data ends with **, operators ne/eq are replaced by (NOT)LIKE operators
1883 * (2) If $data contains '#CURDATE#', then it will be converted to the current date.
1886 function exist_database_item($patient_id, $table, string $column = null, $data_comp = '', string $data = null, $num_items_comp = null, $num_items_thres = null, $intervalType = '', $intervalValue = '', $dateTarget = '')
1889 // Set date to current if not set
1890 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1892 // Collect the correct column label for patient id in the table
1893 $patient_id_label = collect_database_label('pid', $table);
1895 // Get the interval sql query string
1896 $dateSql = sql_interval_string($table, $intervalType, $intervalValue, $dateTarget);
1898 // If just checking for existence (ie. data is empty),
1899 // then simply set the comparison operator to ne.
1900 if (empty($data)) {
1901 $data_comp = "ne";
1904 // get the appropriate sql comparison operator
1905 $compSql = convertCompSql($data_comp);
1907 // custom issues per table can be placed here
1908 $customSQL = '';
1909 if ($table == 'immunizations') {
1910 $customSQL = " AND `added_erroneously` = '0' ";
1913 //adding table list for where condition
1914 $whereTables = '';
1915 if ($table == 'procedure_result') {
1916 $whereTables = ", procedure_order_code, " .
1917 "procedure_order, " .
1918 "procedure_report " ;
1919 $customSQL = " AND procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
1920 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
1921 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
1922 "procedure_result.procedure_report_id = procedure_report.procedure_report_id ";
1925 // check for items
1926 if (empty($column)) {
1927 // simple search for any table entries
1928 $sql = sqlStatementCdrEngine("SELECT * " .
1929 "FROM `" . escape_table_name($table) . "` " .
1930 " " . $whereTables . " " .
1931 "WHERE " . add_escape_custom($patient_id_label) . "=? " . $customSQL, array($patient_id));
1932 } else {
1933 // mdsupport : Allow trailing '**' in the strings to perform LIKE searches
1934 if ((substr($data, -2) == '**') && (($compSql == "=") || ($compSql == "!="))) {
1935 $compSql = ($compSql == "!=" ? " NOT" : "") . " LIKE CONCAT('%',?,'%') ";
1936 $data = substr_replace($data, '', -2);
1937 } else {
1938 $compSql = $compSql . "? ";
1941 if ($whereTables == "" && strpos($table, 'form_') !== false) {
1942 //To handle standard forms starting with form_
1943 //In this case, we are assuming the date field is "date"
1944 $sql = sqlStatementCdrEngine(
1945 "SELECT b.`" . escape_sql_column_name($column, [$table]) . "` " .
1946 "FROM forms a " .
1947 "LEFT JOIN `" . escape_table_name($table) . "` " . " b " .
1948 "ON (a.form_id=b.id AND a.formdir LIKE '" . add_escape_custom(substr($table, 5)) . "') " .
1949 "WHERE a.deleted != '1' " .
1950 "AND b.`" . escape_sql_column_name($column, [$table]) . "`" . $compSql .
1951 "AND b." . add_escape_custom($patient_id_label) . "=? " . $customSQL
1952 . str_replace("`date`", "b.`date`", $dateSql),
1953 array($data, $patient_id)
1955 } else {
1956 // This allows to enter the wild card #CURDATE# in the CDR Demographics filter criteria at the value field
1957 // #CURDATE# is replace by the Current date allowing a dynamic date filtering
1958 if ($data == '#CURDATE#') {
1959 $data = date("Y-m-d");
1962 // search for number of specific items
1963 $sql = sqlStatementCdrEngine("SELECT `" . escape_sql_column_name($column, [$table]) . "` " .
1964 "FROM `" . escape_table_name($table) . "` " .
1965 " " . $whereTables . " " .
1966 "WHERE `" . escape_sql_column_name($column, [$table]) . "`" . $compSql .
1967 "AND " . add_escape_custom($patient_id_label) . "=? " . $customSQL .
1968 $dateSql, array($data, $patient_id));
1972 // See if number of returned items passes the comparison
1973 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1977 * Function to check for existence of procedure(s) for a patient
1979 * @param string $patient_id pid of selected patient.
1980 * @param string $proc_title procedure title
1981 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1982 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1983 * @param string $result_data results data (1)
1984 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1985 * @param integer $num_items_thres number of items threshold
1986 * @param string $intervalType type of interval (ie. year)
1987 * @param integer $intervalValue searched for within this many times of the interval type
1988 * @param string $dateTarget target date(format Y-m-d H:i:s).
1989 * @return boolean true if check passed, otherwise false
1991 * (1) If result_data ends with **, operators ne/eq are replaced by (NOT)LIKE operators
1994 function exist_procedure_item($patient_id, $proc_title, $proc_code, $result_comp, string $result_data = null, $num_items_comp = null, $num_items_thres = null, $intervalType = '', $intervalValue = '', $dateTarget = '')
1997 // Set date to current if not set
1998 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2000 // Set the table exception (for looking up pertinent date and pid sql columns)
2001 $table = "PROCEDURE-EXCEPTION";
2003 // Collect the correct column label for patient id in the table
2004 $patient_id_label = collect_database_label('pid', $table);
2006 // Get the interval sql query string
2007 $dateSql = sql_interval_string($table, $intervalType, $intervalValue, $dateTarget);
2009 // If just checking for existence (ie result_data is empty),
2010 // then simply set the comparison operator to ne.
2011 if (empty($result_data)) {
2012 $result_comp = "ne";
2015 // get the appropriate sql comparison operator
2016 $compSql = convertCompSql($result_comp);
2018 // explode the code array
2019 $codes = array();
2020 if (!empty($proc_code)) {
2021 $codes = explode("||", $proc_code);
2022 } else {
2023 $codes[0] = '';
2026 // ensure proc_title is at least blank
2027 if (empty($proc_title)) {
2028 $proc_title = '';
2031 // collect specific items (use both title and/or codes) that fulfill request
2032 $sqlBindArray = array();
2033 $sql_query = "SELECT procedure_result.result FROM " .
2034 "procedure_order_code, " .
2035 "procedure_order, " .
2036 "procedure_type, " .
2037 "procedure_report, " .
2038 "procedure_result " .
2039 "WHERE " .
2040 "procedure_order_code.procedure_code = procedure_type.procedure_code AND " .
2041 "procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
2042 "procedure_order.lab_id = procedure_type.lab_id AND " .
2043 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
2044 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
2045 "procedure_result.procedure_report_id = procedure_report.procedure_report_id AND " .
2046 "procedure_type.procedure_type = 'ord' AND ";
2047 foreach ($codes as $tem) {
2048 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
2049 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
2050 array_push($sqlBindArray, $tem, $tem);
2053 // mdsupport : Allow trailing '**' in the strings to perform LIKE searches
2054 if ((substr($result_data, -2) == '**') && (($compSql == "=") || ($compSql == "!="))) {
2055 $compSql = ($compSql == "!=" ? " NOT" : "") . " LIKE CONCAT('%',?,'%') ";
2056 $result_data = substr_replace($result_data, '', -2);
2057 } else {
2058 $compSql = $compSql . "? ";
2061 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
2062 "AND procedure_result.result " . $compSql .
2063 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
2064 array_push($sqlBindArray, $proc_title, $result_data, $patient_id);
2066 $sql = sqlStatementCdrEngine($sql_query, $sqlBindArray);
2068 // See if number of returned items passes the comparison
2069 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2073 * Function to check for existance of data for a patient in the rule_patient_data table
2075 * @param string $patient_id pid of selected patient.
2076 * @param string $category label in category column
2077 * @param string $item label in item column
2078 * @param string $complete label in complete column (YES,NO, or blank)
2079 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
2080 * @param integer $num_items_thres number of items threshold
2081 * @param string $intervalType type of interval (ie. year)
2082 * @param integer $intervalValue searched for within this many times of the interval type
2083 * @param string $dateTarget target date(format Y-m-d H:i:s).
2084 * @return boolean true if check passed, otherwise false
2086 function exist_custom_item($patient_id, $category, $item, $complete, $num_items_comp, $num_items_thres, string $intervalType = null, string $intervalValue = null, $dateTarget = null)
2089 // Set the table
2090 $table = 'rule_patient_data';
2092 // Collect the correct column label for patient id in the table
2093 $patient_id_label = collect_database_label('pid', $table);
2095 // Get the interval sql query string
2096 $dateSql = sql_interval_string($table, $intervalType, $intervalValue, $dateTarget);
2098 // search for number of specific items
2099 $sql = sqlStatementCdrEngine("SELECT `result` " .
2100 "FROM `" . escape_table_name($table) . "` " .
2101 "WHERE `category`=? " .
2102 "AND `item`=? " .
2103 "AND `complete`=? " .
2104 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
2105 $dateSql, array($category,$item,$complete,$patient_id));
2107 // See if number of returned items passes the comparison
2108 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2112 * Function to check for existance of data for a patient in lifestyle section
2114 * @param string $patient_id pid of selected patient.
2115 * @param string $lifestyle selected label of mysql column of patient history
2116 * @param string $status specific status of selected lifestyle element
2117 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
2118 * @return boolean true if check passed, otherwise false
2120 function exist_lifestyle_item($patient_id, $lifestyle, $status, $dateTarget)
2123 // Set date to current if not set
2124 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2126 // Collect pertinent history data
2127 // If illegal value in $lifestyle, then will die and report error (to prevent security vulnerabilities)
2128 escape_sql_column_name($lifestyle, ['history_data']);
2129 $history = getHistoryData($patient_id, $lifestyle, '', $dateTarget);
2131 // See if match
2132 $stringFlag = strstr(($history[$lifestyle] ?? ''), "|" . $status);
2133 if (empty($status)) {
2134 // Only ensuring any data has been entered into the field
2135 $stringFlag = true;
2138 return !empty($history[$lifestyle]) &&
2139 $history[$lifestyle] != '|0|' &&
2140 $stringFlag;
2144 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
2145 * allergies, and surgeries.
2147 * @param string $patient_id pid of selected patient.
2148 * @param string $type type (medical_problem, allergy, medication, etc)
2149 * @param string $value value searching for (1)
2150 * @param string $dateTarget target date(format Y-m-d H:i:s).
2151 * @return boolean true if check passed, otherwise false
2153 * (1) If value ends with **, operators ne/eq are replaced by (NOT)LIKE operators
2156 function exist_lists_item($patient_id, $type, $value, $dateTarget)
2159 // Set date to current if not set
2160 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2162 // Attempt to explode the value into a code type and code (if applicable)
2163 $value_array = explode("::", $value);
2164 if (count($value_array) == 2) {
2165 // Collect the code type and code
2166 $code_type = $value_array[0];
2167 $code = $value_array[1];
2169 // Modify $code for both 'CUSTOM' and diagnosis searches
2170 // Note: Diagnosis is always 'LIKE' and should not have '**'
2171 if (substr($code, -2) == '**') {
2172 $sqloper = " LIKE CONCAT('%',?,'%') ";
2173 $code = substr_replace($code, '', -2);
2174 } else {
2175 $sqloper = "=?";
2178 if ($code_type == 'CUSTOM') {
2179 // Deal with custom code type first (title column in lists table)
2180 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2181 "WHERE `type`=? " .
2182 "AND `pid`=? " .
2183 "AND `title` $sqloper " .
2184 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2185 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget));
2186 if (!empty($response)) {
2187 return true;
2189 } else {
2190 // Deal with the set code types (diagnosis column in lists table)
2191 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2192 "WHERE `type`=? " .
2193 "AND `pid`=? " .
2194 "AND `diagnosis` LIKE ? " .
2195 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2196 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%" . $code_type . ":" . $code . "%",$dateTarget,$dateTarget,$dateTarget));
2197 if (!empty($response)) {
2198 return true;
2201 } else { // count($value_array) == 1
2202 // Search the title column in lists table
2203 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
2205 // Check for '**'
2206 if (substr($value, -2) == '**') {
2207 $sqloper = " LIKE CONCAT('%',?,'%') ";
2208 $value = substr_replace($value, '', -2);
2209 } else {
2210 $sqloper = "=?";
2213 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2214 "WHERE `type`=? " .
2215 "AND `pid`=? " .
2216 "AND `title` $sqloper " .
2217 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2218 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget));
2219 if (!empty($response)) {
2220 return true;
2223 if ($type == 'medication') { // Special case needed for medication as it need to be looked into current medications (prescriptions table) from ccda import
2224 $response = sqlQueryCdrEngine("SELECT * FROM `prescriptions` where `patient_id` = ? and `drug` $sqloper and `date_added` <= ?", array($patient_id,$value,$dateTarget));
2225 if (!empty($response)) {
2226 return true;
2231 return false;
2235 * Function to return part of sql query to deal with interval
2237 * @param string $table selected mysql table (or EXCEPTION(s))
2238 * @param string $intervalType type of interval (ie. year)
2239 * @param string $intervalValue searched for within this many times of the interval type
2240 * @param string $dateTarget target date(format Y-m-d H:i:s).
2241 * @return string contains pertinent date interval filter for mysql query
2243 function sql_interval_string($table, $intervalType, $intervalValue, $dateTarget)
2246 $dateSql = "";
2248 // Collect the correct column label for date in the table
2249 $date_label = collect_database_label('date', $table);
2251 // Deal with interval
2252 if (!empty($intervalType)) {
2253 switch ($intervalType) {
2254 case "year":
2255 $dateSql = "AND (" . add_escape_custom($date_label) .
2256 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2257 "', INTERVAL " . escape_limit($intervalValue) .
2258 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
2259 break;
2260 case "month":
2261 $dateSql = "AND (" . add_escape_custom($date_label) .
2262 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2263 "', INTERVAL " . escape_limit($intervalValue) .
2264 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
2265 break;
2266 case "week":
2267 $dateSql = "AND (" . add_escape_custom($date_label) .
2268 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2269 "', INTERVAL " . escape_limit($intervalValue) .
2270 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
2271 break;
2272 case "day":
2273 $dateSql = "AND (" . add_escape_custom($date_label) .
2274 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2275 "', INTERVAL " . escape_limit($intervalValue) .
2276 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
2277 break;
2278 case "hour":
2279 $dateSql = "AND (" . add_escape_custom($date_label) .
2280 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2281 "', INTERVAL " . escape_limit($intervalValue) .
2282 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
2283 break;
2284 case "minute":
2285 $dateSql = "AND (" . add_escape_custom($date_label) .
2286 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2287 "', INTERVAL " . escape_limit($intervalValue) .
2288 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
2289 break;
2290 case "second":
2291 $dateSql = "AND (" . add_escape_custom($date_label) .
2292 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2293 "', INTERVAL " . escape_limit($intervalValue) .
2294 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
2295 break;
2296 case "flu_season":
2297 // Flu season to be hard-coded as September thru February
2298 // (Should make this modifiable in the future)
2299 // ($intervalValue is not used)
2300 $dateArray = explode("-", $dateTarget);
2301 $Year = $dateArray[0];
2302 $dateThisYear = $Year . "-09-01";
2303 $dateLastYear = ($Year - 1) . "-09-01";
2304 $dateSql = " " .
2305 "AND ((" .
2306 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
2307 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
2308 "OR (" .
2309 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
2310 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
2311 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
2312 break;
2314 } else {
2315 $dateSql = "AND " . add_escape_custom($date_label) .
2316 " <= '" . add_escape_custom($dateTarget) . "' ";
2319 // return the sql interval string
2320 return $dateSql;
2324 * Function to collect generic column labels from tables. It currently works for date
2325 * and pid. Will need to expand this as algorithm grows.
2327 * @param string $label element (pid or date)
2328 * @param string $table selected mysql table (or EXCEPTION(s))
2329 * @return string contains official label of selected element
2331 function collect_database_label($label, $table)
2334 if ($table == 'PROCEDURE-EXCEPTION') {
2335 // return cell to get procedure collection
2336 // special case since reuqires joing of multiple
2337 // tables to get this value
2338 if ($label == "pid") {
2339 $returnedLabel = "procedure_order.patient_id";
2340 } elseif ($label == "date") {
2341 $returnedLabel = "procedure_report.date_collected";
2342 } else {
2343 // unknown label, so return the original label
2344 $returnedLabel = $label;
2346 } elseif ($table == 'immunizations') {
2347 // return requested label for immunization table
2348 if ($label == "pid") {
2349 $returnedLabel = "patient_id";
2350 } elseif ($label == "date") {
2351 $returnedLabel = "`administered_date`";
2352 } else {
2353 // unknown label, so return the original label
2354 $returnedLabel = $label;
2356 } elseif ($table == 'prescriptions') {
2357 // return requested label for prescriptions table
2358 if ($label == "pid") {
2359 $returnedLabel = "patient_id";
2360 } elseif ($label == "date") {
2361 $returnedLabel = 'date_added';
2362 } else {
2363 // unknown label, so return the original label
2364 $returnedLabel = $label;
2366 } elseif ($table == 'procedure_result') {
2367 // return requested label for prescriptions table
2368 if ($label == "pid") {
2369 $returnedLabel = "procedure_order.patient_id";
2370 } elseif ($label == "date") {
2371 $returnedLabel = "procedure_report.date_collected";
2372 } else {
2373 // unknown label, so return the original label
2374 $returnedLabel = $label;
2376 } elseif ($table == 'openemr_postcalendar_events') {
2377 // return requested label for prescriptions table
2378 if ($label == "pid") {
2379 $returnedLabel = "pc_pid";
2380 } elseif ($label == "date") {
2381 $returnedLabel = "pc_eventdate";
2382 } else {
2383 // unknown label, so return the original label
2384 $returnedLabel = $label;
2386 } else {
2387 // return requested label for default tables
2388 if ($label == "pid") {
2389 $returnedLabel = "pid";
2390 } elseif ($label == "date") {
2391 $returnedLabel = "`date`";
2392 } else {
2393 // unknown label, so return the original label
2394 $returnedLabel = $label;
2398 return $returnedLabel;
2402 * Simple function to avoid processing of duplicate actions
2404 * @param array $actions 2-dimensional array with all current active targets
2405 * @param array $action array of selected target to test for duplicate
2406 * @return boolean true if duplicate, false if not duplicate
2408 function is_duplicate_action($actions, $action)
2410 foreach ($actions as $row) {
2411 if (
2412 $row['category'] == $action['category'] &&
2413 $row['item'] == $action['item'] &&
2414 $row['value'] == $action['value']
2416 // Is a duplicate
2417 return true;
2421 // Not a duplicate
2422 return false;
2426 * Calculate the reminder dates.
2428 * This function returns an array that contains three elements (each element is a date).
2429 * <pre>The three dates are:
2430 * first date is before the target date (past_due) (default of 1 month)
2431 * second date is the target date (due)
2432 * third date is after the target date (soon_due) (default of 2 weeks)
2433 * </pre>
2435 * @param string $rule id(string) of selected rule
2436 * @param string $dateTarget target date(format Y-m-d H:i:s).
2437 * @param string $type either 'patient_reminder' or 'clinical_reminder'
2438 * @return array see above for description of returned array
2440 function calculate_reminder_dates($rule, string $dateTarget = null, $type = null)
2443 // Set date to current if not set
2444 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2446 // Collect the current date settings (to ensure not skip)
2447 $res = resolve_reminder_sql($rule, $type . '_current');
2448 if (!empty($res)) {
2449 $row = $res[0];
2450 if ($row ['method_detail'] == "SKIP") {
2451 $dateTarget = "SKIP";
2455 // Collect the past_due date
2456 $past_due_date = "";
2457 $res = resolve_reminder_sql($rule, $type . '_post');
2458 if (!empty($res)) {
2459 $row = $res[0];
2460 if ($row ['method_detail'] == "week") {
2461 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
2464 if ($row ['method_detail'] == "month") {
2465 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
2468 if ($row ['method_detail'] == "hour") {
2469 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2472 if ($row ['method_detail'] == "SKIP") {
2473 $past_due_date = "SKIP";
2475 } else {
2476 // empty settings, so use default of one month
2477 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
2480 // Collect the soon_due date
2481 $soon_due_date = "";
2482 $res = resolve_reminder_sql($rule, $type . '_pre');
2483 if (!empty($res)) {
2484 $row = $res[0];
2485 if ($row ['method_detail'] == "week") {
2486 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
2489 if ($row ['method_detail'] == "month") {
2490 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
2493 if ($row ['method_detail'] == "hour") {
2494 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2497 if ($row ['method_detail'] == "SKIP") {
2498 $soon_due_date = "SKIP";
2500 } else {
2501 // empty settings, so use default of one month
2502 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
2505 // Return the array of three dates
2506 return array($soon_due_date,$dateTarget,$past_due_date);
2510 * Adds an action into the reminder array
2512 * @param array $reminderOldArray Contains the current array of reminders
2513 * @param array $reminderNew Array of a new reminder
2514 * @return array Reminders
2516 function reminder_results_integrate($reminderOldArray, $reminderNew)
2519 $results = array();
2521 // If reminderArray is empty, then insert new reminder
2522 if (empty($reminderOldArray)) {
2523 $results[] = $reminderNew;
2524 return $results;
2527 // If duplicate reminder, then replace the old one
2528 $duplicate = false;
2529 foreach ($reminderOldArray as $reminderOld) {
2530 if (
2531 $reminderOld['pid'] == $reminderNew['pid'] &&
2532 $reminderOld['category'] == $reminderNew['category'] &&
2533 $reminderOld['item'] == $reminderNew['item']
2535 $results[] = $reminderNew;
2536 $duplicate = true;
2537 } else {
2538 $results[] = $reminderOld;
2542 // If a new reminder, then insert the new reminder
2543 if (!$duplicate) {
2544 $results[] = $reminderNew;
2547 return $results;
2551 * Compares number of items with requested comparison operator
2553 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2554 * @param string $thres Threshold used in comparison
2555 * @param integer $num_items Number of items
2556 * @return boolean Comparison results
2558 function itemsNumberCompare($comp, $thres, $num_items)
2561 if (($comp == "eq") && ($num_items == $thres)) {
2562 return true;
2563 } elseif (($comp == "ne") && ($num_items != $thres) && ($num_items > 0)) {
2564 return true;
2565 } elseif (($comp == "gt") && ($num_items > $thres)) {
2566 return true;
2567 } elseif (($comp == "ge") && ($num_items >= $thres)) {
2568 return true;
2569 } elseif (($comp == "lt") && ($num_items < $thres) && ($num_items > 0)) {
2570 return true;
2571 } elseif (($comp == "le") && ($num_items <= $thres) && ($num_items > 0)) {
2572 return true;
2573 } else {
2574 return false;
2579 * Converts a text comparison operator to sql equivalent
2581 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2582 * @return string contains sql compatible comparison operator
2584 function convertCompSql($comp)
2587 if ($comp == "eq") {
2588 return "=";
2589 } elseif ($comp == "ne") {
2590 return "!=";
2591 } elseif ($comp == "gt") {
2592 return ">";
2593 } elseif ($comp == "ge") {
2594 return ">=";
2595 } elseif ($comp == "lt") {
2596 return "<";
2597 } else { // ($comp == "le")
2598 return "<=";
2604 * Function to find age in years (with decimal) on the target date
2606 * @param string $dob date of birth
2607 * @param string $target date to calculate age on
2608 * @return float years(decimal) from dob to target(date)
2610 function convertDobtoAgeYearDecimal($dob, $target)
2612 $ageInfo = parseAgeInfo($dob, $target);
2613 return $ageInfo['age'];
2617 * Function to find age in months (with decimal) on the target date
2619 * @param string $dob date of birth
2620 * @param string $target date to calculate age on
2621 * @return float months(decimal) from dob to target(date)
2623 function convertDobtoAgeMonthDecimal($dob, $target)
2625 $ageInfo = parseAgeInfo($dob, $target);
2626 return $ageInfo['age_in_months'];
2630 * Function to calculate the percentage for reports.
2632 * @param integer $pass_filter number of patients that pass filter
2633 * @param integer $exclude_filter number of patients that are excluded
2634 * @param integer $pass_target number of patients that pass target
2635 * @return string Number formatted into a percentage
2637 function calculate_percentage($pass_filt, $exclude_filt, $pass_targ)
2639 if ($pass_filt > 0) {
2640 $perc = number_format(($pass_targ / ($pass_filt - $exclude_filt)) * 100, 4) . xl('%');
2641 } else {
2642 $perc = "0" . xl('%');
2645 return $perc;