new datepicker in encounter
[openemr.git] / library / clinical_rules.php
blob20422a9f08bd6dfdb53a0f1d63e508749b968431
1 <?php
2 /**
3 * Clinical Decision Rules(CDR) engine functions.
5 * These functions should not ever attempt to write to
6 * session variables, because the session_write_close() function
7 * is typically called before utilizing these functions.
9 * Copyright (C) 2010-2012 Brady Miller <brady@sparmy.com>
10 * Copyright (C) 2011 Medical Information Integration, LLC
11 * Copyright (C) 2011 Ensofttek, LLC
13 * LICENSE: This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
24 * @package OpenEMR
25 * @author Brady Miller <brady@sparmy.com>
26 * @author Medical Information Integration, LLC
27 * @author Ensofttek, LLC
28 * @link http://www.open-emr.org
31 require_once(dirname(__FILE__) . "/patient.inc");
32 require_once(dirname(__FILE__) . "/forms.inc");
33 require_once(dirname(__FILE__) . "/options.inc.php");
34 require_once(dirname(__FILE__) . "/report_database.inc");
36 /**
37 * Return listing of CDR reminders in log.
39 * @param string $begin_date begin date (optional)
40 * @param string $end_date end date (optional)
41 * @return sqlret sql return query
43 function listingCDRReminderLog($begin_date='',$end_date='') {
45 if (empty($end_date)) {
46 $end_date=date('Y-m-d H:i:s');
49 $sqlArray = array();
50 $sql = "SELECT `date`, `pid`, `uid`, `category`, `value`, `new_value` FROM `clinical_rules_log` WHERE `date` <= ?";
51 array_push($sqlArray,$end_date);
52 if (!empty($begin_date)) {
53 $sql .= " AND `date` >= ?";
54 array_push($sqlArray,$begin_date);
56 $sql .= " ORDER BY `date` DESC";
58 return sqlStatement($sql,$sqlArray);
62 /**
63 * Display the clinical summary widget.
65 * @param integer $patient_id pid of selected patient
66 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
67 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
68 * @param string $organize_mode Way to organize the results (default or plans)
69 * @param string $user If a user is set, then will only show rules that user has permission to see.
71 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default',$user='') {
73 // Set date to current if not set
74 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
76 // Collect active actions
77 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode, array(),'primary',NULL,NULL,$user);
79 // Display the actions
80 $current_targets = array();
81 foreach ($actions as $action) {
83 // Deal with plan names first
84 if (isset($action['is_plan']) && $action['is_plan']) {
85 echo "<br><b>";
86 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
87 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
88 echo "</b><br>";
89 continue;
92 // Collect the Rule Title, Rule Developer, Rule Funding Source, and Rule Release and show it when hover over the item.
93 $tooltip = '';
94 if (!empty($action['rule_id'])) {
95 $rule_title = getListItemTitle("clinical_rules",$action['rule_id']);
96 $ruleData = sqlQuery("SELECT `developer`, `funding_source`, `release_version`, `web_reference` " .
97 "FROM `clinical_rules` " .
98 "WHERE `id`=? AND `pid`=0", array($action['rule_id']) );
99 $developer = $ruleData['developer'];
100 $funding_source = $ruleData['funding_source'];
101 $release = $ruleData['release_version'];
102 $web_reference = $ruleData['web_reference'];
103 if (!empty($rule_title)) {
104 $tooltip = xla('Rule Title') . ": " . attr($rule_title) . "&#013;";
106 if (!empty($developer)) {
107 $tooltip .= xla('Rule Developer') . ": " . attr($developer) . "&#013;";
109 if (!empty($funding_source)) {
110 $tooltip .= xla('Rule Funding Source') . ": " . attr($funding_source) . "&#013;";
112 if (!empty($release)) {
113 $tooltip .= xla('Rule Release') . ": " . attr($release);
115 if ( (!empty($tooltip)) || (!empty($web_reference)) ) {
116 if (!empty($web_reference)) {
117 $tooltip = "<a href='".attr($web_reference)."' target='_blank' style='white-space: pre-line;' title='".$tooltip."'>?</a>";
119 else {
120 $tooltip = "<span style='white-space: pre-line;' title='".$tooltip."'>?</span>";
125 if ($action['custom_flag']) {
126 // Start link for reminders that use the custom rules input screen
127 $url = "../rules/patient_data.php?category=".htmlspecialchars( $action['category'], ENT_QUOTES);
128 $url .= "&item=".htmlspecialchars( $action['item'], ENT_QUOTES);
129 echo "<a href='".$url."' class='iframe medium_modal' onclick='top.restoreSession()'>";
131 else if ($action['clin_rem_link']) {
132 // Start link for reminders that use the custom rules input screen
133 $pieces_url = parse_url($action['clin_rem_link']);
134 $url_prefix = $pieces_url['scheme'];
135 if($url_prefix == 'https' || $url_prefix == 'http'){
136 echo "<a href='" . $action['clin_rem_link'] .
137 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
138 }else{
139 echo "<a href='../../../" . $action['clin_rem_link'] .
140 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
143 else {
144 // continue since no link is needed
147 // Display Reminder Details
148 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
149 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
151 if ($action['custom_flag'] || $action['clin_rem_link']) {
152 // End link for reminders that use an html link
153 echo "</a>";
156 // Display due status
157 if ($action['due_status']) {
158 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
159 if ($action['due_status'] == "past_due") {
160 echo "&nbsp;&nbsp;(<span style='color:red'>";
162 else if ($action['due_status'] == "due") {
163 echo "&nbsp;&nbsp;(<span style='color:purple'>";
165 else if ($action['due_status'] == "not_due") {
166 echo "&nbsp;&nbsp;(<span style='color:green'>";
168 else {
169 echo "&nbsp;&nbsp;(<span>";
171 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)";
174 // Display the tooltip
175 if (!empty($tooltip)) {
176 echo "&nbsp;".$tooltip."<br>";
178 else {
179 echo "<br>";
182 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
183 // Only when $mode is reminders-due
184 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log']) {
185 $target_temp = $action['category'].":".$action['item'];
186 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
190 // Compare the current with most recent action log (this function will also log the current actions)
191 // Only when $mode is reminders-due
192 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log'] ) {
193 $new_targets = compare_log_alerts($patient_id,$current_targets,'clinical_reminder_widget',$_SESSION['authId']);
194 if (!empty($new_targets) && $GLOBALS['enable_cdr_new_crp']) {
195 // If there are new action(s), then throw a popup (if the enable_cdr_new_crp global is turned on)
196 // Note I am taking advantage of a slight hack in order to run javascript within code that
197 // is being passed via an ajax call by using a dummy image.
198 echo '<img src="../../pic/empty.gif" onload="alert(\''.xls('New Due Clinical Reminders').'\n\n';
199 foreach ($new_targets as $key => $value) {
200 $category_item = explode(":",$key);
201 $category = $category_item[0];
202 $item = $category_item[1];
203 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$category) .
204 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$item). '\n';
206 echo '\n' . '('. xls('See the Clinical Reminders widget for more details'). ')';
207 echo '\');this.parentNode.removeChild(this);" />';
213 * Display the active screen reminder.
215 * @param integer $patient_id pid of selected patient
216 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
217 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
218 * @param string $organize_mode Way to organize the results (default or plans)
219 * @param string $user If a user is set, then will only show rules that user has permission to see
220 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
221 * @return string html display output.
223 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default',$user='',$test=FALSE) {
225 // Set date to current if not set
226 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
228 // Collect active actions
229 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode, array(),'primary',NULL,NULL,$user);
231 if (empty($actions)) {
232 return false;
235 $returnOutput = "";
236 $current_targets = array();
238 // Display the actions
239 foreach ($actions as $action) {
241 // Deal with plan names first
242 if ($action['is_plan']) {
243 $returnOutput .= "<br><b>";
244 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
245 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
246 $returnOutput .= "</b><br>";
247 continue;
250 // Display Reminder Details
251 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
252 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
254 // Display due status
255 if ($action['due_status']) {
256 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
257 if ($action['due_status'] == "past_due") {
258 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
260 else if ($action['due_status'] == "due") {
261 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
263 else if ($action['due_status'] == "not_due") {
264 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
266 else {
267 $returnOutput .= "&nbsp;&nbsp;(<span>";
269 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
271 else {
272 $returnOutput .= "<br>";
275 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
276 // Only when $mode is reminders-due and $test is FALSE
277 if (($mode == "reminders-due") && ($test === FALSE) && ($GLOBALS['enable_alert_log'])) {
278 $target_temp = $action['category'].":".$action['item'];
279 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
283 // Compare the current with most recent action log (this function will also log the current actions)
284 // Only when $mode is reminders-due and $test is FALSE
285 if (($mode == "reminders-due") && ($test === FALSE) && ($GLOBALS['enable_alert_log'])) {
286 $new_targets = compare_log_alerts($patient_id,$current_targets,'active_reminder_popup',$_SESSION['authId']);
287 if (!empty($new_targets)) {
288 $returnOutput .="<br>" . xlt('New Items (see above for details)') . ":<br>";
289 foreach ($new_targets as $key => $value) {
290 $category_item = explode(":",$key);
291 $category = $category_item[0];
292 $item = $category_item[1];
293 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$category) .
294 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$item). '<br>';
299 return $returnOutput;
303 * Process and return allergy conflicts (when a active medication or presciption is on allergy list).
305 * @param integer $patient_id pid of selected patient
306 * @param string $mode either 'all' or 'new' (required)
307 * @param string $user If a user is set, then will only show rules that user has permission to see
308 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
309 * @return array/boolean Array of allergy alerts or FALSE is empty.
311 function allergy_conflict($patient_id,$mode,$user,$test=FALSE) {
313 // Collect allergies
314 $res_allergies = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='allergy' " .
315 "AND `activity`=1 " .
316 "AND ( `enddate` IS NULL OR `enddate`='' OR `enddate` > NOW() ) " .
317 "AND `pid`=?", array($patient_id));
318 $allergies = array();
319 for($iter=0; $row=sqlFetchArray($res_allergies); $iter++) {
320 $allergies[$iter]=$row['title'];
323 // Build sql element of IN for below queries
324 $sqlParam = array();
325 $sqlIN = '';
326 $firstFlag = TRUE;
327 foreach ($allergies as $allergy) {
328 array_push($sqlParam,$allergy);
329 if ($firstFlag) {
330 $sqlIN .= "?";
331 $firstFlag = FALSE;
333 else {
334 $sqlIN .= ",?";
338 // Check if allergies conflict with medications or prescriptions
339 $conflicts_unique = array();
340 if (!empty($sqlParam)) {
341 $conflicts = array();
342 array_push($sqlParam,$patient_id);
343 $res_meds = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='medication' " .
344 "AND `activity`=1 " .
345 "AND ( `enddate` IS NULL OR `enddate`='' OR `enddate` > NOW() ) " .
346 "AND `title` IN (" . $sqlIN . ") AND `pid`=?", $sqlParam);
347 while ($urow = sqlFetchArray($res_meds)) {
348 array_push($conflicts, $urow['title']);
350 $res_rx = sqlStatement("SELECT `drug` FROM `prescriptions` WHERE `active`=1 " .
351 "AND `drug` IN (" . $sqlIN . ") AND `patient_id`=?", $sqlParam);
352 while ($urow = sqlFetchArray($res_rx)) {
353 array_push($conflicts, $urow['drug']);
355 if (!empty($conflicts)) {
356 $conflicts_unique = array_unique($conflicts);
360 // If there are conflicts, $test is FALSE, and alert logging is on, then run through compare_log_alerts
361 $new_conflicts = array();
362 if ( (!empty($conflicts_unique)) && $GLOBALS['enable_alert_log'] && ($test===FALSE) ) {
363 $new_conflicts = compare_log_alerts($patient_id,$conflicts_unique,'allergy_alert',$_SESSION['authId'],$mode);
366 if ($mode == 'all') {
367 if (!empty($conflicts_unique)) {
368 return $conflicts_unique;
370 else {
371 return FALSE;
374 else { // $mode = 'new'
375 if (!empty($new_conflicts)) {
376 return $new_conflicts;
378 else {
379 return FALSE;
385 * Compare current alerts with prior (in order to find new actions)
386 * Also functions to log the actions.
388 * @param integer $patient_id pid of selected patient
389 * @param array $current_targets array of targets
390 * @param string $category clinical_reminder_widget, active_reminder_popup, or allergy_alert
391 * @param integer $userid user id of user.
392 * @param string $log_trigger if 'all', then always log. If 'new', then only trigger log when a new item noted.
393 * @return array array with targets with associated rule.
395 function compare_log_alerts($patient_id,$current_targets,$category='clinical_reminder_widget',$userid='',$log_trigger='all') {
397 if (empty($userid)) {
398 $userid = $_SESSION['authId'];
401 if (empty($current_targets)) {
402 $current_targets = array();
405 // Collect most recent action_log
406 $prior_targets_sql = sqlQuery("SELECT `value` FROM `clinical_rules_log` " .
407 "WHERE `category` = ? AND `pid` = ? AND `uid` = ? " .
408 "ORDER BY `id` DESC LIMIT 1", array($category,$patient_id,$userid) );
409 $prior_targets = array();
410 if (!empty($prior_targets_sql['value'])) {
411 $prior_targets = json_decode($prior_targets_sql['value'], true);
414 // Compare the current with most recent log
415 if ( ($category == 'clinical_reminder_widget') || ($category == 'active_reminder_popup') ) {
416 //using fancy structure to store multiple elements
417 $new_targets = array_diff_key($current_targets,$prior_targets);
419 else { // $category == 'allergy_alert'
420 //using simple array
421 $new_targets = array_diff($current_targets,$prior_targets);
424 // Store current action_log and the new items
425 // If $log_trigger=='all'
426 // or If $log_trigger=='new' and there are new items
427 if ( ($log_trigger=='all') || (($log_trigger=='new') && (!empty($new_targets))) ) {
428 $current_targets_json = json_encode($current_targets);
429 $new_targets_json = '';
430 if (!empty($new_targets)) {
431 $new_targets_json = json_encode($new_targets);
433 sqlInsert("INSERT INTO `clinical_rules_log` " .
434 "(`date`,`pid`,`uid`,`category`,`value`,`new_value`) " .
435 "VALUES (NOW(),?,?,?,?,?)", array($patient_id,$userid,$category,$current_targets_json,$new_targets_json) );
438 // Return new actions (if there are any)
439 return $new_targets;
443 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
445 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
446 * on one patient or patients of one provider). The structure of the returned results is dependent on the
447 * $organize_mode and $mode parameters.
448 * <pre>The results are dependent on the $organize_mode parameter settings
449 * 'default' organize_mode:
450 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
451 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
452 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
453 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
454 * 'plans' organize_mode:
455 * Returns similar to default, but organizes by the active plans
456 * </pre>
458 * @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).
459 * @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.
460 * @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').
461 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
462 * @param string $plan test for specific plan only
463 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
464 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
465 * @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.
466 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
467 * @param integer $report_id id of report in database (if already bookmarked)
468 * @return array See above for organization structure of the results.
470 function test_rules_clinic_batch_method($provider='',$type='',$dateTarget='',$mode='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$batchSize='',$report_id=NULL) {
472 // Default to a batchsize, if empty
473 if (empty($batchSize)) {
474 $batchSize=100;
477 // Collect total number of pertinent patients (to calculate batching parameters)
478 $totalNumPatients = buildPatientArray('',$provider,$pat_prov_rel,NULL,NULL,TRUE);
480 // Cycle through the batches and collect/combine results
481 if (($totalNumPatients%$batchSize) > 0) {
482 // not perfectly divisible
483 $totalNumberBatches = floor($totalNumPatients/$batchSize) + 1;
485 else {
486 // perfectly divisible
487 $totalNumberBatches = floor($totalNumPatients/$batchSize);
490 // Fix things in the $options array(). This now stores the number of labs to be used in the denominator in the AMC report.
491 // 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
492 // of planned batches(note the fixed array will go into the test_rules_clinic function, however the original will be used
493 // in the report storing/tracking engine.
494 $options_modified=$options;
495 if (!empty($options_modified['labs_manual'])) {
496 $options_modified['labs_manual'] = $options_modified['labs_manual'] / $totalNumberBatches;
499 // Prepare the database to track/store results
500 $fields = array('provider'=>$provider,'mode'=>$mode,'plan'=>$plan,'organize_mode'=>$organize_mode,'pat_prov_rel'=>$pat_prov_rel);
501 if (is_array($dateTarget)) {
502 $fields = array_merge($fields,array(date_target=>$dateTarget['dateTarget']));
503 $fields = array_merge($fields,array(date_begin=>$dateTarget['dateBegin']));
505 else {
506 if (empty($dateTarget)) {
507 $fields = array_merge($fields,array(date_target=>date("Y-m-d H:i:s")));
509 else {
510 $fields = array_merge($fields,array(date_target=>$dateTarget));
513 if (!empty($options)) {
514 foreach ($options as $key => $value) {
515 $fields = array_merge($fields, array($key=>$value));
518 $report_id = beginReportDatabase($type,$fields,$report_id);
519 setTotalItemsReportDatabase($report_id,$totalNumPatients);
521 // Set ability to itemize report if this feature is turned on
522 if ( ( ($type == "active_alert" || $type == "passive_alert") && ($GLOBALS['report_itemizing_standard']) ) ||
523 ( ($type == "cqm" || $type == "cqm_2011" || $type == "cqm_2014") && ($GLOBALS['report_itemizing_cqm']) ) ||
524 ( ($type == "amc" || $type == "amc_2011" || $type == "amc_2014" || $type == "amc_2014_stage1" || $type == "amc_2014_stage2") && ($GLOBALS['report_itemizing_amc']) ) ) {
525 $GLOBALS['report_itemizing_temp_flag_and_id'] = $report_id;
527 else {
528 $GLOBALS['report_itemizing_temp_flag_and_id'] = 0;
531 for ($i=0;$i<$totalNumberBatches;$i++) {
533 // If itemization is turned on, then reset the rule id iterator
534 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
535 $GLOBALS['report_itemized_test_id_iterator'] = 1;
538 $dataSheet_batch = test_rules_clinic($provider,$type,$dateTarget,$mode,'',$plan,$organize_mode,$options_modified,$pat_prov_rel,(($batchSize*$i)+1),$batchSize);
539 if ($i == 0) {
540 // For first cycle, simply copy it to dataSheet
541 $dataSheet = $dataSheet_batch;
543 else {
544 //debug
545 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
546 //error_log("CDR: ".($batchSize*$i)." records",0);
548 // Integrate batch results into main dataSheet
549 foreach ($dataSheet_batch as $key => $row) {
550 if (!$row['is_sub']) {
551 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
552 $total_patients = $dataSheet[$key]['total_patients'] + $row['total_patients'];
553 $dataSheet[$key]['total_patients'] = $total_patients;
554 $excluded = $dataSheet[$key]['excluded'] + $row['excluded'];
555 $dataSheet[$key]['excluded'] = $excluded;
556 $pass_filter = $dataSheet[$key]['pass_filter'] + $row['pass_filter'];
557 $dataSheet[$key]['pass_filter'] = $pass_filter;
559 $pass_target = $dataSheet[$key]['pass_target'] + $row['pass_target'];
560 $dataSheet[$key]['pass_target'] = $pass_target;
561 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter,$excluded,$pass_target);
564 //Update database to track results
565 updateReportDatabase($report_id,$total_patients);
568 // Record results in database and send to screen, if applicable.
569 finishReportDatabase($report_id,json_encode($dataSheet));
570 return $dataSheet;
574 * Process clinic rules.
576 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
577 * on one patient or patients of one provider). The structure of the returned results is dependent on the
578 * $organize_mode and $mode parameters.
579 * <pre>The results are dependent on the $organize_mode parameter settings
580 * 'default' organize_mode:
581 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
582 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
583 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
584 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
585 * 'plans' organize_mode:
586 * Returns similar to default, but organizes by the active plans
587 * </pre>
589 * @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).
590 * @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.
591 * @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').
592 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
593 * @param integer $patient_id pid of patient. If blank then will check all patients.
594 * @param string $plan test for specific plan only
595 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
596 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
597 * @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.
598 * @param integer $start applicable patient to start at (when batching process)
599 * @param integer $batchSize number of patients to batch (when batching process)
600 * @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).
601 * @return array See above for organization structure of the results.
603 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='') {
605 // If dateTarget is an array, then organize them.
606 if (is_array($dateTarget)) {
607 $dateArray = $dateTarget;
608 $dateTarget = $dateTarget['dateTarget'];
611 // Set date to current if not set
612 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
614 // Prepare the results array
615 $results = array();
617 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
618 // then run through this function recursively and return results.
619 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
620 // First, collect an array of all providers
621 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
622 $ures = sqlStatementCdrEngine($query);
623 // Second, run through each provider recursively
624 while ($urow = sqlFetchArray($ures)) {
625 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode,$options,$pat_prov_rel,$start,$batchSize,$user);
626 if (!empty($newResults)) {
627 $provider_item['is_provider'] = TRUE;
628 $provider_item['prov_lname'] = $urow['lname'];
629 $provider_item['prov_fname'] = $urow['fname'];
630 $provider_item['npi'] = $urow['npi'];
631 $provider_item['federaltaxid'] = $urow['federaltaxid'];
632 array_push($results,$provider_item);
633 $results = array_merge($results,$newResults);
636 // done, so now can return results
637 return $results;
640 // If set organize-mode to plans, then collects active plans and run through this
641 // function recursively and return results.
642 if ($organize_mode == "plans") {
643 // First, collect active plans
644 $plans_resolve = resolve_plans_sql($plan,$patient_id);
645 // Second, run through function recursively
646 foreach ($plans_resolve as $plan_item) {
647 // (if collate_inner, then nest a collation of providers within each plan)
648 if ($provider == "collate_inner") {
649 // First, collect an array of all providers
650 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
651 $ures = sqlStatementCdrEngine($query);
652 // Second, run through each provider recursively
653 $provider_results = array();
654 while ($urow = sqlFetchArray($ures)) {
655 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize,$user);
656 if (!empty($newResults)) {
657 $provider_item['is_provider'] = TRUE;
658 $provider_item['prov_lname'] = $urow['lname'];
659 $provider_item['prov_fname'] = $urow['fname'];
660 $provider_item['npi'] = $urow['npi'];
661 $provider_item['federaltaxid'] = $urow['federaltaxid'];
662 array_push($provider_results,$provider_item);
663 $provider_results = array_merge($provider_results,$newResults);
666 if (!empty($provider_results)) {
667 $plan_item['is_plan'] = TRUE;
668 array_push($results,$plan_item);
669 $results = array_merge($results,$provider_results);
672 else {
673 // (not collate_inner, so do not nest providers within each plan)
674 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize,$user);
675 if (!empty($newResults)) {
676 $plan_item['is_plan'] = TRUE;
677 array_push($results,$plan_item);
678 $results = array_merge($results,$newResults);
682 // done, so now can return results
683 return $results;
686 // Collect applicable patient pids
687 $patientData = array();
688 $patientData = buildPatientArray($patient_id,$provider,$pat_prov_rel,$start,$batchSize);
690 // Go through each patient(s)
692 // If in report mode, then tabulate for each rule:
693 // Total Patients
694 // Patients that pass the filter
695 // Patients that pass the target
696 // If in reminders mode, then create reminders for each rule:
697 // Reminder that action is due soon
698 // Reminder that action is due
699 // Reminder that action is post-due
701 //Collect applicable rules
702 // Note that due to a limitation in the this function, the patient_id is explicitly
703 // for grouping items when not being done in real-time or for official reporting.
704 // So for cases such as patient reminders on a clinic scale, the calling function
705 // will actually need rather than pass in a explicit patient_id for each patient in
706 // a separate call to this function.
707 if ($mode != "report") {
708 // Use per patient custom rules (if exist)
709 // Note as discussed above, this only works for single patient instances.
710 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan,$user);
712 else { // $mode = "report"
713 // Only use default rules (do not use patient custom rules)
714 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan,$user);
717 foreach( $rules as $rowRule ) {
719 // If using cqm or amc type, then use the hard-coded rules set.
720 // Note these rules are only used in report mode.
721 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
723 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
724 $manager = new ReportManager();
725 if ($rowRule['amc_flag']) {
726 // Send array of dates ('dateBegin' and 'dateTarget')
727 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
729 else {
730 // Send target date
731 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
733 if (!empty($tempResults)) {
734 foreach ($tempResults as $tempResult) {
735 array_push($results,$tempResult);
739 // Go on to the next rule
740 continue;
743 // If in reminder mode then need to collect the measurement dates
744 // from rule_reminder table
745 $target_dates = array();
746 if ($mode != "report") {
747 // Calculate the dates to check for
748 if ($type == "patient_reminder") {
749 $reminder_interval_type = "patient_reminder";
751 else { // $type == "passive_alert" or $type == "active_alert"
752 $reminder_interval_type = "clinical_reminder";
754 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
756 else { // $mode == "report"
757 // Only use the target date in the report
758 $target_dates[0] = $dateTarget;
761 //Reset the counters
762 $total_patients = 0;
763 $pass_filter = 0;
764 $exclude_filter = 0;
765 $pass_target = 0;
767 // Find the number of target groups
768 $targetGroups = returnTargetGroups($rowRule['id']);
770 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
772 // If report itemization is turned on, then iterate the rule id iterator
773 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
774 $GLOBALS['report_itemized_test_id_iterator']++;
777 //skip this section if not report and more than one target group
778 foreach( $patientData as $rowPatient ) {
780 // First, deal with deceased patients
781 // (for now will simply skip the patient)
782 // If want to support rules for deceased patients then will need to migrate this below
783 // in target_dates foreach(guessing won't ever need to do this, though).
784 // Note using the dateTarget rather than dateFocus
785 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
786 continue;
789 // Count the total patients
790 $total_patients++;
792 $dateCounter = 1; // for reminder mode to keep track of which date checking
793 // If report itemization is turned on, reset flag.
794 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id'])) {
795 $temp_track_pass = 1;
797 foreach ( $target_dates as $dateFocus ) {
799 //Skip if date is set to SKIP
800 if ($dateFocus == "SKIP") {
801 $dateCounter++;
802 continue;
805 //Set date counter and reminder token (applicable for reminders only)
806 if ($dateCounter == 1) {
807 $reminder_due = "soon_due";
809 else if ($dateCounter == 2) {
810 $reminder_due = "due";
812 else { // $dateCounter == 3
813 $reminder_due = "past_due";
816 // Check if pass filter
817 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
818 if ($passFilter === "EXCLUDED") {
819 // increment EXCLUDED and pass_filter counters
820 // and set as FALSE for reminder functionality.
821 $pass_filter++;
822 $exclude_filter++;
823 $passFilter = FALSE;
825 if ($passFilter) {
826 // increment pass filter counter
827 $pass_filter++;
828 // If report itemization is turned on, trigger flag.
829 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
830 $temp_track_pass = 0;
833 else {
834 $dateCounter++;
835 continue;
838 // Check if pass target
839 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
840 if ($passTarget) {
841 // increment pass target counter
842 $pass_target++;
843 // If report itemization is turned on, then record the "passed" item and set the flag
844 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
845 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
846 $temp_track_pass = 1;
848 // send to reminder results
849 if ($mode == "reminders-all") {
850 // place the completed actions into the reminder return array
851 $actionArray = resolve_action_sql($rowRule['id'],'1');
852 foreach ($actionArray as $action) {
853 $action_plus = $action;
854 $action_plus['due_status'] = "not_due";
855 $action_plus['pid'] = $rowPatient['pid'];
856 $action_plus['rule_id'] = $rowRule['id'];
857 $results = reminder_results_integrate($results, $action_plus);
860 break;
862 else {
863 // send to reminder results
864 if ($mode != "report") {
865 // place the uncompleted actions into the reminder return array
866 $actionArray = resolve_action_sql($rowRule['id'],'1');
867 foreach ($actionArray as $action) {
868 $action_plus = $action;
869 $action_plus['due_status'] = $reminder_due;
870 $action_plus['pid'] = $rowPatient['pid'];
871 $action_plus['rule_id'] = $rowRule['id'];
872 $results = reminder_results_integrate($results, $action_plus);
876 $dateCounter++;
878 // If report itemization is turned on, then record the "failed" item if it did not pass
879 if (!empty($GLOBALS['report_itemizing_temp_flag_and_id']) && !($temp_track_pass)) {
880 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
885 // Calculate and save the data for the rule
886 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
887 if ($mode == "report") {
888 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
889 $newRow=array_merge($newRow,$rowRule);
891 // If itemization is turned on, then record the itemized_test_id
892 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
893 $newRow=array_merge($newRow,array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
896 array_push($results, $newRow);
899 // Now run through the target groups if more than one
900 if (count($targetGroups) > 1) {
901 foreach ($targetGroups as $i) {
903 // If report itemization is turned on, then iterate the rule id iterator
904 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
905 $GLOBALS['report_itemized_test_id_iterator']++;
908 //Reset the target counter
909 $pass_target = 0;
911 foreach( $patientData as $rowPatient ) {
913 // First, deal with deceased patients
914 // (for now will simply skip the patient)
915 // If want to support rules for deceased patients then will need to migrate this below
916 // in target_dates foreach(guessing won't ever need to do this, though).
917 // Note using the dateTarget rather than dateFocus
918 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
919 continue;
922 $dateCounter = 1; // for reminder mode to keep track of which date checking
923 // If report itemization is turned on, reset flag.
924 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
925 $temp_track_pass = 1;
927 foreach ( $target_dates as $dateFocus ) {
929 //Skip if date is set to SKIP
930 if ($dateFocus == "SKIP") {
931 $dateCounter++;
932 continue;
935 //Set date counter and reminder token (applicable for reminders only)
936 if ($dateCounter == 1) {
937 $reminder_due = "soon_due";
939 else if ($dateCounter == 2) {
940 $reminder_due = "due";
942 else { // $dateCounter == 3
943 $reminder_due = "past_due";
946 // Check if pass filter
947 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
948 if ($passFilter === "EXCLUDED") {
949 $passFilter = FALSE;
951 if (!$passFilter) {
952 $dateCounter++;
953 continue;
955 else {
956 // If report itemization is turned on, trigger flag.
957 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
958 $temp_track_pass = 0;
962 //Check if pass target
963 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
964 if ($passTarget) {
965 // increment pass target counter
966 $pass_target++;
967 // If report itemization is turned on, then record the "passed" item and set the flag
968 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
969 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
970 $temp_track_pass = 1;
972 // send to reminder results
973 if ($mode == "reminders-all") {
974 // place the completed actions into the reminder return array
975 $actionArray = resolve_action_sql($rowRule['id'],$i);
976 foreach ($actionArray as $action) {
977 $action_plus = $action;
978 $action_plus['due_status'] = "not_due";
979 $action_plus['pid'] = $rowPatient['pid'];
980 $action_plus['rule_id'] = $rowRule['id'];
981 $results = reminder_results_integrate($results, $action_plus);
984 break;
986 else {
987 // send to reminder results
988 if ($mode != "report") {
989 // place the actions into the reminder return array
990 $actionArray = resolve_action_sql($rowRule['id'],$i);
991 foreach ($actionArray as $action) {
992 $action_plus = $action;
993 $action_plus['due_status'] = $reminder_due;
994 $action_plus['pid'] = $rowPatient['pid'];
995 $action_plus['rule_id'] = $rowRule['id'];
996 $results = reminder_results_integrate($results, $action_plus);
1000 $dateCounter++;
1002 // If report itemization is turned on, then record the "failed" item if it did not pass
1003 if ($GLOBALS['report_itemizing_temp_flag_and_id'] && !($temp_track_pass)) {
1004 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
1008 // Calculate and save the data for the rule
1009 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
1011 // Collect action for title (just use the first one, if more than one)
1012 $actionArray = resolve_action_sql($rowRule['id'],$i);
1013 $action = $actionArray[0];
1014 if ($mode == "report") {
1015 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
1017 // If itemization is turned on, then record the itemized_test_id
1018 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
1019 $newRow=array_merge($newRow,array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
1022 array_push($results, $newRow);
1028 // Return the data
1029 return $results;
1033 * Process patient array that is to be tested.
1035 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
1036 * @param integer $patient_id pid of patient. If blank then will check all patients.
1037 * @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.
1038 * @param integer $start applicable patient to start at (when batching process)
1039 * @param integer $batchSize number of patients to batch (when batching process)
1040 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
1041 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
1043 function buildPatientArray($patient_id='',$provider='',$pat_prov_rel='primary',$start=NULL,$batchSize=NULL,$onlyCount=FALSE) {
1045 if (!empty($patient_id)) {
1046 // only look at the selected patient
1047 if ($onlyCount) {
1048 $patientNumber = 1;
1050 else {
1051 $patientData[0]['pid'] = $patient_id;
1054 else {
1055 if (empty($provider)) {
1056 // Look at entire practice
1057 if ($start == NULL || $batchSize == NULL || $onlyCount) {
1058 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
1059 if ($onlyCount) {
1060 $patientNumber = sqlNumRows($rez);
1063 else {
1064 // batching
1065 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start-1),$batchSize));
1068 else {
1069 // Look at an individual physician
1070 if( $pat_prov_rel == 'encounter' ){
1071 // Choose patients that are related to specific physician by an encounter
1072 if ($start == NULL || $batchSize == NULL || $onlyCount) {
1073 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
1074 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
1075 if ($onlyCount) {
1076 $patientNumber = sqlNumRows($rez);
1079 else {
1080 //batching
1081 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
1082 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start-1),$batchSize));
1085 else { //$pat_prov_rel == 'primary'
1086 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
1087 if ($start == NULL || $batchSize == NULL || $onlyCount) {
1088 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1089 "WHERE `providerID`=? ORDER BY `pid`", array($provider) );
1090 if ($onlyCount) {
1091 $patientNumber = sqlNumRows($rez);
1094 else {
1095 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1096 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start-1),$batchSize) );
1100 // convert the sql query results into an array if returning the array
1101 if(!$onlyCount) {
1102 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
1103 $patientData[$iter]=$row;
1108 if ($onlyCount) {
1109 // return the number of applicable patients
1110 return $patientNumber;
1112 else {
1113 // return array of patient pids
1114 return $patientData;
1119 * Test filter of a selected rule on a selected patient
1121 * @param integer $patient_id pid of selected patient.
1122 * @param string $rule id(string) of selected rule
1123 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
1124 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
1126 function test_filter($patient_id,$rule,$dateTarget) {
1128 // Set date to current if not set
1129 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1131 // Collect patient information
1132 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
1135 // ----------------- INCLUSIONS -----------------
1138 // -------- Age Filter (inclusion) ------------
1139 // Calculate patient age in years and months
1140 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
1141 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
1143 // Min age (year) Filter (assume that there in not more than one of each)
1144 $filter = resolve_filter_sql($rule,'filt_age_min');
1145 if (!empty($filter)) {
1146 $row = $filter[0];
1147 if ($row ['method_detail'] == "year") {
1148 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
1149 return false;
1152 if ($row ['method_detail'] == "month") {
1153 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
1154 return false;
1158 // Max age (year) Filter (assume that there in not more than one of each)
1159 $filter = resolve_filter_sql($rule,'filt_age_max');
1160 if (!empty($filter)) {
1161 $row = $filter[0];
1162 if ($row ['method_detail'] == "year") {
1163 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
1164 return false;
1167 if ($row ['method_detail'] == "month") {
1168 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
1169 return false;
1174 // -------- Gender Filter (inclusion) ---------
1175 // Gender Filter (assume that there in not more than one of each)
1176 $filter = resolve_filter_sql($rule,'filt_sex');
1177 if (!empty($filter)) {
1178 $row = $filter[0];
1179 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
1180 return false;
1184 // -------- Database Filter (inclusion) ------
1185 // Database Filter
1186 $filter = resolve_filter_sql($rule,'filt_database');
1187 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
1189 // -------- Lists Filter (inclusion) ----
1190 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
1191 // surgeries and allergies.
1192 $filter = resolve_filter_sql($rule,'filt_lists');
1193 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
1195 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
1196 // Procedure Target (includes) (may need to include an interval in the future)
1197 $filter = resolve_filter_sql($rule,'filt_proc');
1198 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
1201 // ----------------- EXCLUSIONS -----------------
1204 // -------- Lists Filter (EXCLUSION) ----
1205 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
1206 // surgeries and allergies.
1207 $filter = resolve_filter_sql($rule,'filt_lists',0);
1208 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
1210 // Passed all filters, so return true.
1211 return true;
1215 * Return an array containing existing group ids for a rule
1217 * @param string $rule id(string) of rule
1218 * @return array listing of group ids
1220 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 array_push($groups,$row['group_id']);
1229 return $groups;
1233 * Test targets of a selected rule on a selected patient
1235 * @param integer $patient_id pid of selected patient.
1236 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
1237 * @param integer $group_id group id of target group
1238 * @param string $dateTarget target date (format Y-m-d H:i:s).
1239 * @return boolean if target passes then true, otherwise false
1241 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
1243 // -------- Interval Target ----
1244 $interval = resolve_target_sql($rule,$group_id,'target_interval');
1246 // -------- Database Target ----
1247 // Database Target (includes)
1248 $target = resolve_target_sql($rule,$group_id,'target_database');
1249 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
1251 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
1252 // Procedure Target (includes)
1253 $target = resolve_target_sql($rule,$group_id,'target_proc');
1254 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
1256 // -------- Appointment Target ----
1257 // Appointment Target (includes) (Specialized functionality for appointment reminders)
1258 $target = resolve_target_sql($rule,$group_id,'target_appt');
1259 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
1261 // Passed all target tests, so return true.
1262 return true;
1266 * Function to return active plans
1268 * @param string $type plan type filter (normal or cqm or blank)
1269 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
1270 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
1271 * @return array active plans
1273 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
1275 if ($configurableOnly) {
1276 // Collect all default, configurable (per patient) plans into an array
1277 // (ie. ignore the cqm rules)
1278 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
1280 else {
1281 // Collect all default plans into an array
1282 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
1284 $returnArray= array();
1285 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1286 array_push($returnArray,$row);
1289 // Now collect the pertinent plans
1290 $newReturnArray = array();
1292 // Need to select rules (use custom if exist)
1293 foreach ($returnArray as $plan) {
1294 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
1296 // Decide if use default vs custom plan (preference given to custom plan)
1297 if (!empty($customPlan)) {
1298 if ($type == "cqm" ) {
1299 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
1300 $goPlan = $plan;
1302 else {
1303 // merge the custom plan with the default plan
1304 $mergedPlan = array();
1305 foreach ($customPlan as $key => $value) {
1306 if ($value == NULL && preg_match("/_flag$/",$key)) {
1307 // use default setting
1308 $mergedPlan[$key] = $plan[$key];
1310 else {
1311 // use custom setting
1312 $mergedPlan[$key] = $value;
1315 $goPlan = $mergedPlan;
1318 else {
1319 $goPlan = $plan;
1322 // Use the chosen plan if set
1323 if (!empty($type)) {
1324 if ($goPlan["${type}_flag"] == 1) {
1325 // active, so use the plan
1326 array_push($newReturnArray,$goPlan);
1329 else {
1330 if ($goPlan['normal_flag'] == 1 ||
1331 $goPlan['cqm_flag'] == 1) {
1332 // active, so use the plan
1333 array_push($newReturnArray,$goPlan);
1337 $returnArray = $newReturnArray;
1339 return $returnArray;
1344 * Function to return a specific plan
1346 * @param string $plan id(string) of plan
1347 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1348 * @return array a plan
1350 function collect_plan($plan,$patient_id='0') {
1352 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
1357 * Function to set a specific plan activity for a specific patient
1359 * @param string $plan id(string) of plan
1360 * @param string $type plan filter (normal,cqm)
1361 * @param string $setting activity of plan (yes,no,default)
1362 * @param integer $patient_id pid of selected patient.
1364 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
1366 // Don't allow messing with the default plans here
1367 if ($patient_id == "0") {
1368 return;
1371 // Convert setting
1372 if ($setting == "on") {
1373 $setting = 1;
1375 else if ($setting == "off") {
1376 $setting = 0;
1378 else { // $setting == "default"
1379 $setting = NULL;
1382 // Collect patient specific plan, if already exists.
1383 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
1384 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id) );
1386 if (empty($patient_plan)) {
1387 // Create a new patient specific plan with flags all set to default
1388 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1389 sqlStatementCdrEngine($query, array($plan, $patient_id) );
1392 // Update patient specific row
1393 $query = "UPDATE `clinical_plans` SET `" . escape_sql_column_name($type."_flag",array("clinical_plans")) . "`= ? WHERE id = ? AND pid = ?";
1394 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id) );
1399 * Function to return active rules
1401 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2014,amc_2011,amc_2014,patient_reminder)
1402 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1403 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1404 * @param string $plan collect rules for specific plan
1405 * @param string $user If a user is set, then will only show rules that user has permission to see
1406 * @return array rules
1408 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='',$user='') {
1410 if ($configurableOnly) {
1411 // Collect all default, configurable (per patient) rules into an array
1412 // (ie. ignore the cqm and amc rules)
1413 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1415 else {
1416 // Collect all default rules into an array
1417 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1419 $returnArray= array();
1420 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1421 array_push($returnArray,$row);
1424 // Now filter rules for plan (if applicable)
1425 if (!empty($plan)) {
1426 $planReturnArray = array();
1427 foreach ($returnArray as $rule) {
1428 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1429 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
1430 if (!empty($standardRule)) {
1431 array_push($planReturnArray,$rule);
1434 $returnArray = $planReturnArray;
1437 // Now collect the pertinent rules
1438 $newReturnArray = array();
1440 // Need to select rules (use custom if exist)
1441 foreach ($returnArray as $rule) {
1443 // If user is set, then check if user has access to the rule
1444 if (!empty($user)) {
1445 $access_control = explode(':',$rule['access_control']);
1446 if ( !empty($access_control[0]) && !empty($access_control[1]) ) {
1447 // Section and ACO filters are not empty, so do the test for access.
1448 if (!acl_check($access_control[0],$access_control[1],$user)) {
1449 // User does not have access to this rule, so skip the rule.
1450 continue;
1453 else {
1454 // Section or ACO filters are empty, so use default patients:med aco
1455 if (!acl_check('patients','med',$user)) {
1456 // User does not have access to this rule, so skip the rule.
1457 continue;
1462 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
1464 // Decide if use default vs custom rule (preference given to custom rule)
1465 if (!empty($customRule)) {
1466 if ($type == "cqm" || $type == "amc" ) {
1467 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1468 $goRule = $rule;
1470 else {
1471 // merge the custom rule with the default rule
1472 $mergedRule = array();
1473 foreach ($customRule as $key => $value) {
1474 if ($value == NULL && preg_match("/_flag$/",$key)) {
1475 // use default setting
1476 $mergedRule[$key] = $rule[$key];
1478 else {
1479 // use custom setting
1480 $mergedRule[$key] = $value;
1483 $goRule = $mergedRule;
1486 else {
1487 $goRule = $rule;
1490 // Use the chosen rule if set
1491 if (!empty($type)) {
1492 if ($goRule["${type}_flag"] == 1) {
1493 // active, so use the rule
1494 array_push($newReturnArray,$goRule);
1497 else {
1498 // no filter, so return the rule
1499 array_push($newReturnArray,$goRule);
1502 $returnArray = $newReturnArray;
1504 return $returnArray;
1508 * Function to return a specific rule
1510 * @param string $rule id(string) of rule
1511 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1512 * @return array rule
1514 function collect_rule($rule,$patient_id='0') {
1516 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
1521 * Function to set a specific rule activity for a specific patient
1523 * @param string $rule id(string) of rule
1524 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1525 * @param string $setting activity of rule (yes,no,default)
1526 * @param integer $patient_id pid of selected patient.
1528 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
1530 // Don't allow messing with the default rules here
1531 if ($patient_id == "0") {
1532 return;
1535 // Convert setting
1536 if ($setting == "on") {
1537 $setting = 1;
1539 else if ($setting == "off") {
1540 $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 `" . add_escape_custom($type) . "_flag`= ?, `access_control` = ? WHERE id = ? AND pid = ?";
1562 sqlStatementCdrEngine($query, array($setting,$patient_rule_original['access_control'],$rule,$patient_id) );
1567 * Function to return applicable reminder dates (relative)
1569 * @param string $rule id(string) of selected rule
1570 * @param string $reminder_method string label of filter type
1571 * @return array reminder features
1573 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 array_push($returnArray,$row);
1581 return $returnArray;
1585 * Function to return applicable filters
1587 * @param string $rule id(string) of selected rule
1588 * @param string $filter_method string label of filter type
1589 * @param string $include_flag to allow selection for included or excluded filters
1590 * @return array filters
1592 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1593 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1594 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1596 $returnArray= array();
1597 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1598 array_push($returnArray,$row);
1600 return $returnArray;
1604 * Function to return applicable targets
1606 * @param string $rule id(string) of selected rule
1607 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1608 * @param string $target_method string label of target type
1609 * @param string $include_flag to allow selection for included or excluded targets
1610 * @return array targets
1612 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1614 if ($group_id) {
1615 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1616 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1618 else {
1619 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1620 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1623 $returnArray= array();
1624 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1625 array_push($returnArray,$row);
1627 return $returnArray;
1631 * Function to return applicable actions
1633 * @param string $rule id(string) of selected rule
1634 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1635 * @return array actions
1637 function resolve_action_sql($rule,$group_id='') {
1639 if ($group_id) {
1640 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1641 "FROM `rule_action` as a " .
1642 "JOIN `rule_action_item` as b " .
1643 "ON a.category = b.category AND a.item = b.item " .
1644 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1646 else {
1647 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1648 "FROM `rule_action` as a " .
1649 "JOIN `rule_action_item` as b " .
1650 "ON a.category = b.category AND a.item = b.item " .
1651 "WHERE a.id=?", array($rule) );
1654 $returnArray= array();
1655 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1656 array_push($returnArray,$row);
1658 return $returnArray;
1662 * Function to check database filters and targets
1664 * @param string $patient_id pid of selected patient.
1665 * @param array $filter array containing filter/target elements
1666 * @param array $interval array containing interval elements
1667 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1668 * @return boolean true if check passed, otherwise false
1670 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1671 $isMatch = false; //matching flag
1673 // Set date to current if not set
1674 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1676 // Unpackage interval information
1677 // (Assume only one for now and only pertinent for targets)
1678 $intervalType = '';
1679 $intervalValue = '';
1680 if (!empty($interval)) {
1681 $intervalType = $interval[0]['value'];
1682 $intervalValue = $interval[0]['interval'];
1684 $cond_loop = 0;
1685 foreach( $filter as $row ) {
1687 // Row description
1688 // [0]=>special modes
1689 $temp_df = explode("::",$row['value']);
1691 if ($temp_df[0] == "CUSTOM") {
1692 // Row description
1693 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1694 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1695 // Record the match
1696 $isMatch = true;
1698 else {
1699 // If this is a required entry then return false
1700 if ($row['required_flag']) return false;
1703 else if ($temp_df[0] == "LIFESTYLE") {
1704 // Row description
1705 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1706 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1707 // Record the match
1708 $isMatch = true;
1710 else {
1711 // If this is a required entry then return false
1712 if ($row['required_flag']) return false;
1715 else {
1716 // Default mode
1717 // Row description
1718 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1719 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)) {
1720 // Record the match
1721 if($cond_loop > 0) // For multiple condition check
1722 $isMatch = $isMatch && true;
1723 else
1724 $isMatch = true;
1726 else {
1727 // If this is a required entry then return false
1728 if ($row['required_flag']) return false;
1731 $cond_loop++;
1734 // return results of check
1735 return $isMatch;
1739 * Function to check procedure filters and targets
1741 * @param string $patient_id pid of selected patient.
1742 * @param array $filter array containing filter/target elements
1743 * @param array $interval array containing interval elements
1744 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1745 * @return boolean true if check passed, otherwise false
1747 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1748 $isMatch = false; //matching flag
1750 // Set date to current if not set
1751 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1753 // Unpackage interval information
1754 // (Assume only one for now and only pertinent for targets)
1755 $intervalType = '';
1756 $intervalValue = '';
1757 if (!empty($interval)) {
1758 $intervalType = $interval[0]['value'];
1759 $intervalValue = $interval[0]['interval'];
1762 foreach( $filter as $row ) {
1763 // Row description
1764 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1765 // code description
1766 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1767 $temp_df = explode("::",$row['value']);
1768 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)) {
1769 // Record the match
1770 $isMatch = true;
1772 else {
1773 // If this is a required entry then return false
1774 if ($row['required_flag']) return false;
1778 // return results of check
1779 return $isMatch;
1783 * Function to check for appointment
1785 * @todo Complete this to allow appointment reminders.
1786 * @param string $patient_id pid of selected patient.
1787 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1788 * @return boolean true if appt exist, otherwise false
1790 function appointment_check($patient_id,$dateTarget='') {
1791 $isMatch = false; //matching flag
1793 // Set date to current if not set (although should always be set)
1794 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1795 $dateTargetRound = date('Y-m-d',$dateTarget);
1797 // Set current date
1798 $currentDate = date('Y-m-d H:i:s');
1799 $currentDateRound = date('Y-m-d',$dateCurrent);
1801 // Basically, if the appointment is within the current date to the target date,
1802 // then return true. (will not send reminders on same day as appointment)
1803 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1804 "openemr_postcalendar_events.pc_title, " .
1805 "openemr_postcalendar_events.pc_eventDate, " .
1806 "openemr_postcalendar_events.pc_startTime, " .
1807 "openemr_postcalendar_events.pc_endTime " .
1808 "FROM openemr_postcalendar_events " .
1809 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1810 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1811 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1813 // return results of check
1815 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1816 // Plan to send back array of appt info (eid, time, date, etc.)
1817 // to do this.
1818 if (sqlNumRows($sql) > 0) {
1819 $isMatch = true;
1822 return $isMatch;
1826 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1828 * @param string $patient_id pid of selected patient.
1829 * @param array $filter array containing lists filter/target elements
1830 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1831 * @return boolean true if check passed, otherwise false
1833 function lists_check($patient_id,$filter,$dateTarget) {
1834 $isMatch = false; //matching flag
1836 // Set date to current if not set
1837 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1839 foreach ( $filter as $row ) {
1840 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1841 // Record the match
1842 $isMatch = true;
1844 else {
1845 // If this is a required entry then return false
1846 if ($row['required_flag']) return false;
1850 // return results of check
1851 return $isMatch;
1855 * Function to check for existance of data in database for a patient
1857 * @param string $patient_id pid of selected patient.
1858 * @param string $table selected mysql table
1859 * @param string $column selected mysql column
1860 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1861 * @param string $data selected data in the mysql database (1)(2)
1862 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1863 * @param integer $num_items_thres number of items threshold
1864 * @param string $intervalType type of interval (ie. year)
1865 * @param integer $intervalValue searched for within this many times of the interval type
1866 * @param string $dateTarget target date(format Y-m-d H:i:s).
1867 * @return boolean true if check passed, otherwise false
1869 * (1) If data ends with **, operators ne/eq are replaced by (NOT)LIKE operators
1870 * (2) If $data contains '#CURDATE#', then it will be converted to the current date.
1873 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1875 // Set date to current if not set
1876 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1878 // Collect the correct column label for patient id in the table
1879 $patient_id_label = collect_database_label('pid',$table);
1881 // Get the interval sql query string
1882 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1884 // If just checking for existence (ie. data is empty),
1885 // then simply set the comparison operator to ne.
1886 if (empty($data)) {
1887 $data_comp = "ne";
1890 // get the appropriate sql comparison operator
1891 $compSql = convertCompSql($data_comp);
1893 // custom issues per table can be placed here
1894 $customSQL = '';
1895 if ($table == 'immunizations') {
1896 $customSQL = " AND `added_erroneously` = '0' ";
1899 //adding table list for where condition
1900 $whereTables = '';
1901 if($table == 'procedure_result'){
1902 $whereTables = ", procedure_order_code, " .
1903 "procedure_order, " .
1904 "procedure_report " ;
1905 $customSQL = " AND procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
1906 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
1907 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
1908 "procedure_result.procedure_report_id = procedure_report.procedure_report_id ";
1911 // check for items
1912 if (empty($column)) {
1913 // simple search for any table entries
1914 $sql = sqlStatementCdrEngine("SELECT * " .
1915 "FROM `" . add_escape_custom($table) . "` " .
1916 " ". $whereTables. " ".
1917 "WHERE " . add_escape_custom($patient_id_label) . "=? " . $customSQL, array($patient_id) );
1919 else {
1920 // mdsupport : Allow trailing '**' in the strings to perform LIKE searches
1921 if ( (substr($data,-2)=='**') && (($compSql == "=") || ($compSql == "!=")) ) {
1922 $compSql = ($compSql == "!=" ? " NOT": "")." LIKE CONCAT('%',?,'%') ";
1923 $data = substr_replace($data, '', -2);
1924 } else {
1925 $compSql = $compSql . "? ";
1927 if ($whereTables=="" && strpos($table, 'form_')!== false){
1928 //To handle standard forms starting with form_
1929 //In this case, we are assuming the date field is "date"
1930 $sql =sqlStatementCdrEngine(
1931 "SELECT b.`" . add_escape_custom($column) . "` " .
1932 "FROM forms a ".
1933 "LEFT JOIN `" . add_escape_custom($table) . "` " . " b ".
1934 "ON (a.form_id=b.id AND a.formdir LIKE '".add_escape_custom(substr($table, 5))."') ".
1935 "WHERE a.deleted != '1' ".
1936 "AND b.`" .add_escape_custom($column) ."`" . $compSql .
1937 "AND b." . add_escape_custom($patient_id_label) . "=? " . $customSQL
1938 . str_replace("`date`", "b.`date`", $dateSql)
1939 ,array($data, $patient_id));
1941 else {
1942 // This allows to enter the wild card #CURDATE# in the CDR Demographics filter criteria at the value field
1943 // #CURDATE# is replace by the Current date allowing a dynamic date filtering
1944 if ($data=='#CURDATE#') {
1945 $data = date("Y-m-d");
1947 // search for number of specific items
1948 $sql = sqlStatementCdrEngine("SELECT `" . add_escape_custom($column) . "` " .
1949 "FROM `" . add_escape_custom($table) . "` " .
1950 " " . $whereTables . " " .
1951 "WHERE `" . add_escape_custom($column) . "`" . $compSql .
1952 "AND " . add_escape_custom($patient_id_label) . "=? " . $customSQL .
1953 $dateSql, array($data, $patient_id));
1957 // See if number of returned items passes the comparison
1958 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1962 * Function to check for existence of procedure(s) for a patient
1964 * @param string $patient_id pid of selected patient.
1965 * @param string $proc_title procedure title
1966 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1967 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1968 * @param string $result_data results data (1)
1969 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1970 * @param integer $num_items_thres number of items threshold
1971 * @param string $intervalType type of interval (ie. year)
1972 * @param integer $intervalValue searched for within this many times of the interval type
1973 * @param string $dateTarget target date(format Y-m-d H:i:s).
1974 * @return boolean true if check passed, otherwise false
1976 * (1) If result_data ends with **, operators ne/eq are replaced by (NOT)LIKE operators
1979 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1981 // Set date to current if not set
1982 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1984 // Set the table exception (for looking up pertinent date and pid sql columns)
1985 $table = "PROCEDURE-EXCEPTION";
1987 // Collect the correct column label for patient id in the table
1988 $patient_id_label = collect_database_label('pid',$table);
1990 // Get the interval sql query string
1991 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1993 // If just checking for existence (ie result_data is empty),
1994 // then simply set the comparison operator to ne.
1995 if (empty($result_data)) {
1996 $result_comp = "ne";
1999 // get the appropriate sql comparison operator
2000 $compSql = convertCompSql($result_comp);
2002 // explode the code array
2003 $codes= array();
2004 if (!empty($proc_code)) {
2005 $codes = explode("||",$proc_code);
2007 else {
2008 $codes[0] = '';
2011 // ensure proc_title is at least blank
2012 if (empty($proc_title)) {
2013 $proc_title = '';
2016 // collect specific items (use both title and/or codes) that fulfill request
2017 $sqlBindArray=array();
2018 $sql_query = "SELECT procedure_result.result FROM " .
2019 "procedure_order_code, " .
2020 "procedure_order, " .
2021 "procedure_type, " .
2022 "procedure_report, " .
2023 "procedure_result " .
2024 "WHERE " .
2025 "procedure_order_code.procedure_code = procedure_type.procedure_code AND " .
2026 "procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
2027 "procedure_order.lab_id = procedure_type.lab_id AND " .
2028 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
2029 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
2030 "procedure_result.procedure_report_id = procedure_report.procedure_report_id AND " .
2031 "procedure_type.procedure_type = 'ord' AND ";
2032 foreach ($codes as $tem) {
2033 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
2034 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
2035 array_push($sqlBindArray,$tem,$tem);
2037 // mdsupport : Allow trailing '**' in the strings to perform LIKE searches
2038 if ( (substr($result_data,-2)=='**') && (($compSql == "=") || ($compSql == "!=")) ) {
2039 $compSql = ($compSql == "!=" ? " NOT": "")." LIKE CONCAT('%',?,'%') ";
2040 $result_data = substr_replace($result_data, '', -2);
2041 } else {
2042 $compSql = $compSql . "? ";
2044 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
2045 "AND procedure_result.result " . $compSql .
2046 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
2047 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
2049 $sql = sqlStatementCdrEngine($sql_query,$sqlBindArray);
2051 // See if number of returned items passes the comparison
2052 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2056 * Function to check for existance of data for a patient in the rule_patient_data table
2058 * @param string $patient_id pid of selected patient.
2059 * @param string $category label in category column
2060 * @param string $item label in item column
2061 * @param string $complete label in complete column (YES,NO, or blank)
2062 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
2063 * @param integer $num_items_thres number of items threshold
2064 * @param string $intervalType type of interval (ie. year)
2065 * @param integer $intervalValue searched for within this many times of the interval type
2066 * @param string $dateTarget target date(format Y-m-d H:i:s).
2067 * @return boolean true if check passed, otherwise false
2069 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
2071 // Set the table
2072 $table = 'rule_patient_data';
2074 // Collect the correct column label for patient id in the table
2075 $patient_id_label = collect_database_label('pid',$table);
2077 // Get the interval sql query string
2078 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
2080 // search for number of specific items
2081 $sql = sqlStatementCdrEngine("SELECT `result` " .
2082 "FROM `" . add_escape_custom($table) . "` " .
2083 "WHERE `category`=? " .
2084 "AND `item`=? " .
2085 "AND `complete`=? " .
2086 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
2087 $dateSql, array($category,$item,$complete,$patient_id) );
2089 // See if number of returned items passes the comparison
2090 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2094 * Function to check for existance of data for a patient in lifestyle section
2096 * @param string $patient_id pid of selected patient.
2097 * @param string $lifestyle selected label of mysql column of patient history
2098 * @param string $status specific status of selected lifestyle element
2099 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
2100 * @return boolean true if check passed, otherwise false
2102 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
2104 // Set date to current if not set
2105 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2107 // Collect pertinent history data
2108 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
2110 // See if match
2111 $stringFlag = strstr($history[$lifestyle], "|".$status);
2112 if (empty($status)) {
2113 // Only ensuring any data has been entered into the field
2114 $stringFlag = true;
2116 if ( $history[$lifestyle] &&
2117 $history[$lifestyle] != '|0|' &&
2118 $stringFlag ) {
2119 return true;
2121 else {
2122 return false;
2127 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
2128 * allergies, and surgeries.
2130 * @param string $patient_id pid of selected patient.
2131 * @param string $type type (medical_problem, allergy, medication, etc)
2132 * @param string $value value searching for (1)
2133 * @param string $dateTarget target date(format Y-m-d H:i:s).
2134 * @return boolean true if check passed, otherwise false
2136 * (1) If value ends with **, operators ne/eq are replaced by (NOT)LIKE operators
2139 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
2141 // Set date to current if not set
2142 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2144 // Attempt to explode the value into a code type and code (if applicable)
2145 $value_array = explode("::",$value);
2146 if (count($value_array) == 2) {
2148 // Collect the code type and code
2149 $code_type = $value_array[0];
2150 $code = $value_array[1];
2152 // Modify $code for both 'CUSTOM' and diagnosis searches
2153 // Note: Diagnosis is always 'LIKE' and should not have '**'
2154 if (substr($code,-2)=='**') {
2155 $sqloper = " LIKE CONCAT('%',?,'%') ";
2156 $code = substr_replace($code, '', -2);
2157 } else {
2158 $sqloper = "=?";
2161 if ($code_type=='CUSTOM') {
2162 // Deal with custom code type first (title column in lists table)
2163 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2164 "WHERE `type`=? " .
2165 "AND `pid`=? " .
2166 "AND `title` $sqloper " .
2167 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2168 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
2169 if (!empty($response)) return true;
2171 else {
2172 // Deal with the set code types (diagnosis column in lists table)
2173 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2174 "WHERE `type`=? " .
2175 "AND `pid`=? " .
2176 "AND `diagnosis` LIKE ? " .
2177 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2178 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
2179 if (!empty($response)) return true;
2182 else { // count($value_array) == 1
2183 // Search the title column in lists table
2184 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
2186 // Check for '**'
2187 if (substr($value,-2)=='**') {
2188 $sqloper = " LIKE CONCAT('%',?,'%') ";
2189 $value = substr_replace($value, '', -2);
2190 } else {
2191 $sqloper = "=?";
2194 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2195 "WHERE `type`=? " .
2196 "AND `pid`=? " .
2197 "AND `title` $sqloper ".
2198 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2199 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
2200 if (!empty($response)) return true;
2202 if($type == 'medication'){ // Special case needed for medication as it need to be looked into current medications (prescriptions table) from ccda import
2203 $response = sqlQueryCdrEngine("SELECT * FROM `prescriptions` where `patient_id` = ? and `drug` $sqloper and `date_added` <= ?", array($patient_id,$value,$dateTarget));
2204 if(!empty($response)) return true;
2208 return false;
2212 * Function to return part of sql query to deal with interval
2214 * @param string $table selected mysql table (or EXCEPTION(s))
2215 * @param string $intervalType type of interval (ie. year)
2216 * @param string $intervalValue searched for within this many times of the interval type
2217 * @param string $dateTarget target date(format Y-m-d H:i:s).
2218 * @return string contains pertinent date interval filter for mysql query
2220 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
2222 $dateSql="";
2224 // Collect the correct column label for date in the table
2225 $date_label = collect_database_label('date',$table);
2227 // Deal with interval
2228 if (!empty($intervalType)) {
2229 switch($intervalType) {
2230 case "year":
2231 $dateSql = "AND (" . add_escape_custom($date_label) .
2232 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2233 "', INTERVAL " . add_escape_custom($intervalValue) .
2234 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
2235 break;
2236 case "month":
2237 $dateSql = "AND (" . add_escape_custom($date_label) .
2238 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2239 "', INTERVAL " . add_escape_custom($intervalValue) .
2240 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
2241 break;
2242 case "week":
2243 $dateSql = "AND (" . add_escape_custom($date_label) .
2244 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2245 "', INTERVAL " . add_escape_custom($intervalValue) .
2246 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
2247 break;
2248 case "day":
2249 $dateSql = "AND (" . add_escape_custom($date_label) .
2250 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2251 "', INTERVAL " . add_escape_custom($intervalValue) .
2252 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
2253 break;
2254 case "hour":
2255 $dateSql = "AND (" . add_escape_custom($date_label) .
2256 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2257 "', INTERVAL " . add_escape_custom($intervalValue) .
2258 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
2259 break;
2260 case "minute":
2261 $dateSql = "AND (" . add_escape_custom($date_label) .
2262 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2263 "', INTERVAL " . add_escape_custom($intervalValue) .
2264 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
2265 break;
2266 case "second":
2267 $dateSql = "AND (" . add_escape_custom($date_label) .
2268 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2269 "', INTERVAL " . add_escape_custom($intervalValue) .
2270 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
2271 break;
2272 case "flu_season":
2273 // Flu season to be hard-coded as September thru February
2274 // (Should make this modifiable in the future)
2275 // ($intervalValue is not used)
2276 $dateArray = explode("-",$dateTarget);
2277 $Year = $dateArray[0];
2278 $dateThisYear = $Year . "-09-01";
2279 $dateLastYear = ($Year-1) . "-09-01";
2280 $dateSql =" " .
2281 "AND ((" .
2282 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
2283 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
2284 "OR (" .
2285 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
2286 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
2287 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
2288 break;
2291 else {
2292 $dateSql = "AND " . add_escape_custom($date_label) .
2293 " <= '" . add_escape_custom($dateTarget) . "' ";
2296 // return the sql interval string
2297 return $dateSql;
2301 * Function to collect generic column labels from tables. It currently works for date
2302 * and pid. Will need to expand this as algorithm grows.
2304 * @param string $label element (pid or date)
2305 * @param string $table selected mysql table (or EXCEPTION(s))
2306 * @return string contains official label of selected element
2308 function collect_database_label($label,$table) {
2310 if ($table == 'PROCEDURE-EXCEPTION') {
2311 // return cell to get procedure collection
2312 // special case since reuqires joing of multiple
2313 // tables to get this value
2314 if ($label == "pid") {
2315 $returnedLabel = "procedure_order.patient_id";
2317 else if ($label == "date") {
2318 $returnedLabel = "procedure_report.date_collected";
2320 else {
2321 // unknown label, so return the original label
2322 $returnedLabel = $label;
2325 else if ($table == 'immunizations') {
2326 // return requested label for immunization table
2327 if ($label == "pid") {
2328 $returnedLabel = "patient_id";
2330 else if ($label == "date") {
2331 $returnedLabel = "`administered_date`";
2333 else {
2334 // unknown label, so return the original label
2335 $returnedLabel = $label;
2338 else if ($table == 'prescriptions'){
2339 // return requested label for prescriptions table
2340 if ($label == "pid") {
2341 $returnedLabel = "patient_id";
2343 else if ($label == "date") {
2344 $returnedLabel = 'date_added';
2346 else{
2347 // unknown label, so return the original label
2348 $returnedLabel = $label;
2351 else if($table == 'procedure_result'){
2352 // return requested label for prescriptions table
2353 if ($label == "pid") {
2354 $returnedLabel = "procedure_order.patient_id";
2356 else if ($label == "date") {
2357 $returnedLabel = "procedure_report.date_collected";
2359 else {
2360 // unknown label, so return the original label
2361 $returnedLabel = $label;
2364 else {
2365 // return requested label for default tables
2366 if ($label == "pid") {
2367 $returnedLabel = "pid";
2369 else if ($label == "date") {
2370 $returnedLabel = "`date`";
2372 else {
2373 // unknown label, so return the original label
2374 $returnedLabel = $label;
2378 return $returnedLabel;
2382 * Simple function to avoid processing of duplicate actions
2384 * @param string $actions 2-dimensional array with all current active targets
2385 * @param string $action array of selected target to test for duplicate
2386 * @return boolean true if duplicate, false if not duplicate
2388 function is_duplicate_action($actions,$action) {
2389 foreach ($actions as $row) {
2390 if ($row['category'] == $action['category'] &&
2391 $row['item'] == $action['item'] &&
2392 $row['value'] == $action['value']) {
2393 // Is a duplicate
2394 return true;
2398 // Not a duplicate
2399 return false;
2403 * Calculate the reminder dates.
2405 * This function returns an array that contains three elements (each element is a date).
2406 * <pre>The three dates are:
2407 * first date is before the target date (past_due) (default of 1 month)
2408 * second date is the target date (due)
2409 * third date is after the target date (soon_due) (default of 2 weeks)
2410 * </pre>
2412 * @param string $rule id(string) of selected rule
2413 * @param string $dateTarget target date(format Y-m-d H:i:s).
2414 * @param string $type either 'patient_reminder' or 'clinical_reminder'
2415 * @return array see above for description of returned array
2417 function calculate_reminder_dates($rule, $dateTarget='',$type) {
2419 // Set date to current if not set
2420 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2422 // Collect the current date settings (to ensure not skip)
2423 $res = resolve_reminder_sql($rule, $type.'_current');
2424 if (!empty($res)) {
2425 $row = $res[0];
2426 if ($row ['method_detail'] == "SKIP") {
2427 $dateTarget = "SKIP";
2431 // Collect the past_due date
2432 $past_due_date = "";
2433 $res = resolve_reminder_sql($rule, $type.'_post');
2434 if (!empty($res)) {
2435 $row = $res[0];
2436 if ($row ['method_detail'] == "week") {
2437 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
2439 if ($row ['method_detail'] == "month") {
2440 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
2442 if ($row ['method_detail'] == "hour") {
2443 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2445 if ($row ['method_detail'] == "SKIP") {
2446 $past_due_date = "SKIP";
2449 else {
2450 // empty settings, so use default of one month
2451 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
2454 // Collect the soon_due date
2455 $soon_due_date = "";
2456 $res = resolve_reminder_sql($rule, $type.'_pre');
2457 if (!empty($res)) {
2458 $row = $res[0];
2459 if ($row ['method_detail'] == "week") {
2460 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
2462 if ($row ['method_detail'] == "month") {
2463 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
2465 if ($row ['method_detail'] == "hour") {
2466 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2468 if ($row ['method_detail'] == "SKIP") {
2469 $soon_due_date = "SKIP";
2472 else {
2473 // empty settings, so use default of one month
2474 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
2477 // Return the array of three dates
2478 return array($soon_due_date,$dateTarget,$past_due_date);
2482 * Adds an action into the reminder array
2484 * @param array $reminderOldArray Contains the current array of reminders
2485 * @param array $reminderNew Array of a new reminder
2486 * @return array Reminders
2488 function reminder_results_integrate($reminderOldArray, $reminderNew) {
2490 $results = array();
2492 // If reminderArray is empty, then insert new reminder
2493 if (empty($reminderOldArray)) {
2494 array_push($results, $reminderNew);
2495 return $results;
2498 // If duplicate reminder, then replace the old one
2499 $duplicate = false;
2500 foreach ($reminderOldArray as $reminderOld) {
2501 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
2502 $reminderOld['category'] == $reminderNew['category'] &&
2503 $reminderOld['item'] == $reminderNew['item']) {
2504 array_push($results, $reminderNew);
2505 $duplicate = true;
2507 else {
2508 array_push($results, $reminderOld);
2512 // If a new reminder, then insert the new reminder
2513 if (!$duplicate) {
2514 array_push($results, $reminderNew);
2517 return $results;
2521 * Compares number of items with requested comparison operator
2523 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2524 * @param string $thres Threshold used in comparison
2525 * @param integer $num_items Number of items
2526 * @return boolean Comparison results
2528 function itemsNumberCompare($comp, $thres, $num_items) {
2530 if ( ($comp == "eq") && ($num_items == $thres) ) {
2531 return true;
2533 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
2534 return true;
2536 else if ( ($comp == "gt") && ($num_items > $thres) ) {
2537 return true;
2539 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
2540 return true;
2542 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
2543 return true;
2545 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
2546 return true;
2548 else {
2549 return false;
2554 * Converts a text comparison operator to sql equivalent
2556 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2557 * @return string contains sql compatible comparison operator
2559 function convertCompSql($comp) {
2561 if ($comp == "eq") {
2562 return "=";
2564 else if ($comp == "ne") {
2565 return "!=";
2567 else if ($comp == "gt") {
2568 return ">";
2570 else if ($comp == "ge") {
2571 return ">=";
2573 else if ($comp == "lt") {
2574 return "<";
2576 else { // ($comp == "le")
2577 return "<=";
2583 * Function to find age in years (with decimal) on the target date
2585 * @param string $dob date of birth
2586 * @param string $target date to calculate age on
2587 * @return float years(decimal) from dob to target(date)
2589 function convertDobtoAgeYearDecimal($dob,$target) {
2590 $ageInfo=parseAgeInfo($dob,$target);
2591 return $ageInfo['age'];
2595 * Function to find age in months (with decimal) on the target date
2597 * @param string $dob date of birth
2598 * @param string $target date to calculate age on
2599 * @return float months(decimal) from dob to target(date)
2601 function convertDobtoAgeMonthDecimal($dob,$target) {
2602 $ageInfo=parseAgeInfo($dob,$target);
2603 return $ageInfo['age_in_months'];
2607 * Function to calculate the percentage for reports.
2609 * @param integer $pass_filter number of patients that pass filter
2610 * @param integer $exclude_filter number of patients that are excluded
2611 * @param integer $pass_target number of patients that pass target
2612 * @return string Number formatted into a percentage
2614 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
2615 if ($pass_filt > 0) {
2616 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100,4) . xl('%');
2618 else {
2619 $perc = "0". xl('%');
2621 return $perc;