fixes to prior commit
[openemr.git] / library / clinical_rules.php
blob3aba9f621168bb1592af5d40ac4ab07e98508b8f
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__) . "/formdata.inc.php");
34 require_once(dirname(__FILE__) . "/options.inc.php");
35 require_once(dirname(__FILE__) . "/report_database.inc");
37 // This is only pertinent for users of php versions less than 5.2
38 // (ie. this wrapper is only loaded when php version is less than
39 // 5.2; otherwise the native php json functions are used)
40 require_once(dirname(__FILE__) . "/jsonwrapper/jsonwrapper.php");
42 /**
43 * Return listing of CDR reminders in log.
45 * @param string $begin_date begin date (optional)
46 * @param string $end_date end date (optional)
47 * @return sqlret sql return query
49 function listingCDRReminderLog($begin_date='',$end_date='') {
51 if (empty($end_date)) {
52 $end_date=date('Y-m-d H:i:s');
55 $sqlArray = array();
56 $sql = "SELECT `date`, `pid`, `uid`, `category`, `value`, `new_value` FROM `clinical_rules_log` WHERE `date` <= ?";
57 array_push($sqlArray,$end_date);
58 if (!empty($begin_date)) {
59 $sql .= " AND `date` >= ?";
60 array_push($sqlArray,$begin_date);
62 $sql .= " ORDER BY `date` DESC";
64 return sqlStatement($sql,$sqlArray);
68 /**
69 * Display the clinical summary widget.
71 * @param integer $patient_id pid of selected patient
72 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
73 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
74 * @param string $organize_mode Way to organize the results (default or plans)
75 * @param string $user If a user is set, then will only show rules that user has permission to see.
77 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default',$user='') {
79 // Set date to current if not set
80 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
82 // Collect active actions
83 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode, array(),'primary',NULL,NULL,$user);
85 // Display the actions
86 $current_targets = array();
87 foreach ($actions as $action) {
89 // Deal with plan names first
90 if (isset($action['is_plan']) && $action['is_plan']) {
91 echo "<br><b>";
92 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
93 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
94 echo "</b><br>";
95 continue;
98 // Collect the Rule Title, Rule Developer, Rule Funding Source, and Rule Release and show it when hover over the item.
99 $tooltip = '';
100 if (!empty($action['rule_id'])) {
101 $rule_title = getListItemTitle("clinical_rules",$action['rule_id']);
102 $ruleData = sqlQuery("SELECT `developer`, `funding_source`, `release_version`, `web_reference` " .
103 "FROM `clinical_rules` " .
104 "WHERE `id`=? AND `pid`=0", array($action['rule_id']) );
105 $developer = $ruleData['developer'];
106 $funding_source = $ruleData['funding_source'];
107 $release = $ruleData['release_version'];
108 $web_reference = $ruleData['web_reference'];
109 if (!empty($rule_title)) {
110 $tooltip = xla('Rule Title') . ": " . attr($rule_title) . "&#013;";
112 if (!empty($developer)) {
113 $tooltip .= xla('Rule Developer') . ": " . attr($developer) . "&#013;";
115 if (!empty($funding_source)) {
116 $tooltip .= xla('Rule Funding Source') . ": " . attr($funding_source) . "&#013;";
118 if (!empty($release)) {
119 $tooltip .= xla('Rule Release') . ": " . attr($release);
121 if ( (!empty($tooltip)) || (!empty($web_reference)) ) {
122 if (!empty($web_reference)) {
123 $tooltip = "<a href='".attr($web_reference)."' target='_blank' style='white-space: pre-line;' title='".$tooltip."'>?</a>";
125 else {
126 $tooltip = "<span style='white-space: pre-line;' title='".$tooltip."'>?</span>";
131 if ($action['custom_flag']) {
132 // Start link for reminders that use the custom rules input screen
133 $url = "../rules/patient_data.php?category=".htmlspecialchars( $action['category'], ENT_QUOTES);
134 $url .= "&item=".htmlspecialchars( $action['item'], ENT_QUOTES);
135 echo "<a href='".$url."' class='iframe medium_modal' onclick='top.restoreSession()'>";
137 else if ($action['clin_rem_link']) {
138 // Start link for reminders that use the custom rules input screen
139 $pieces_url = parse_url($action['clin_rem_link']);
140 $url_prefix = $pieces_url['scheme'];
141 if($url_prefix == 'https' || $url_prefix == 'http'){
142 echo "<a href='" . $action['clin_rem_link'] .
143 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
144 }else{
145 echo "<a href='../../../" . $action['clin_rem_link'] .
146 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
149 else {
150 // continue since no link is needed
153 // Display Reminder Details
154 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
155 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
157 if ($action['custom_flag'] || $action['clin_rem_link']) {
158 // End link for reminders that use an html link
159 echo "</a>";
162 // Display due status
163 if ($action['due_status']) {
164 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
165 if ($action['due_status'] == "past_due") {
166 echo "&nbsp;&nbsp;(<span style='color:red'>";
168 else if ($action['due_status'] == "due") {
169 echo "&nbsp;&nbsp;(<span style='color:purple'>";
171 else if ($action['due_status'] == "not_due") {
172 echo "&nbsp;&nbsp;(<span style='color:green'>";
174 else {
175 echo "&nbsp;&nbsp;(<span>";
177 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)";
180 // Display the tooltip
181 if (!empty($tooltip)) {
182 echo "&nbsp;".$tooltip."<br>";
184 else {
185 echo "<br>";
188 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
189 // Only when $mode is reminders-due
190 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log']) {
191 $target_temp = $action['category'].":".$action['item'];
192 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
196 // Compare the current with most recent action log (this function will also log the current actions)
197 // Only when $mode is reminders-due
198 if ($mode == "reminders-due" && $GLOBALS['enable_alert_log'] ) {
199 $new_targets = compare_log_alerts($patient_id,$current_targets,'clinical_reminder_widget',$_SESSION['authId']);
200 if (!empty($new_targets) && $GLOBALS['enable_cdr_new_crp']) {
201 // If there are new action(s), then throw a popup (if the enable_cdr_new_crp global is turned on)
202 // Note I am taking advantage of a slight hack in order to run javascript within code that
203 // is being passed via an ajax call by using a dummy image.
204 echo '<img src="../../pic/empty.gif" onload="alert(\''.xls('New Due Clinical Reminders').'\n\n';
205 foreach ($new_targets as $key => $value) {
206 $category_item = explode(":",$key);
207 $category = $category_item[0];
208 $item = $category_item[1];
209 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$category) .
210 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$item). '\n';
212 echo '\n' . '('. xls('See the Clinical Reminders widget for more details'). ')';
213 echo '\');this.parentNode.removeChild(this);" />';
219 * Display the active screen reminder.
221 * @param integer $patient_id pid of selected patient
222 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
223 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
224 * @param string $organize_mode Way to organize the results (default or plans)
225 * @param string $user If a user is set, then will only show rules that user has permission to see
226 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
227 * @return string html display output.
229 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default',$user='',$test=FALSE) {
231 // Set date to current if not set
232 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
234 // Collect active actions
235 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode, array(),'primary',NULL,NULL,$user);
237 if (empty($actions)) {
238 return false;
241 $returnOutput = "";
242 $current_targets = array();
244 // Display the actions
245 foreach ($actions as $action) {
247 // Deal with plan names first
248 if ($action['is_plan']) {
249 $returnOutput .= "<br><b>";
250 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
251 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
252 $returnOutput .= "</b><br>";
253 continue;
256 // Display Reminder Details
257 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
258 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
260 // Display due status
261 if ($action['due_status']) {
262 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
263 if ($action['due_status'] == "past_due") {
264 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
266 else if ($action['due_status'] == "due") {
267 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
269 else if ($action['due_status'] == "not_due") {
270 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
272 else {
273 $returnOutput .= "&nbsp;&nbsp;(<span>";
275 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
277 else {
278 $returnOutput .= "<br>";
281 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
282 // Only when $mode is reminders-due and $test is FALSE
283 if (($mode == "reminders-due") && ($test === FALSE) && ($GLOBALS['enable_alert_log'])) {
284 $target_temp = $action['category'].":".$action['item'];
285 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
289 // Compare the current with most recent action log (this function will also log the current actions)
290 // Only when $mode is reminders-due and $test is FALSE
291 if (($mode == "reminders-due") && ($test === FALSE) && ($GLOBALS['enable_alert_log'])) {
292 $new_targets = compare_log_alerts($patient_id,$current_targets,'active_reminder_popup',$_SESSION['authId']);
293 if (!empty($new_targets)) {
294 $returnOutput .="<br>" . xlt('New Items (see above for details)') . ":<br>";
295 foreach ($new_targets as $key => $value) {
296 $category_item = explode(":",$key);
297 $category = $category_item[0];
298 $item = $category_item[1];
299 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$category) .
300 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$item). '<br>';
305 return $returnOutput;
309 * Process and return allergy conflicts (when a active medication or presciption is on allergy list).
311 * @param integer $patient_id pid of selected patient
312 * @param string $mode either 'all' or 'new' (required)
313 * @param string $user If a user is set, then will only show rules that user has permission to see
314 * @param string $test Set to true when only checking if there are alerts (skips the logging then)
315 * @return array/boolean Array of allergy alerts or FALSE is empty.
317 function allergy_conflict($patient_id,$mode,$user,$test=FALSE) {
319 // Collect allergies
320 $res_allergies = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='allergy' " .
321 "AND `activity`=1 " .
322 "AND ( `enddate` IS NULL OR `enddate`='' OR `enddate` > NOW() ) " .
323 "AND `pid`=?", array($patient_id));
324 $allergies = array();
325 for($iter=0; $row=sqlFetchArray($res_allergies); $iter++) {
326 $allergies[$iter]=$row['title'];
329 // Build sql element of IN for below queries
330 $sqlParam = array();
331 $sqlIN = '';
332 $firstFlag = TRUE;
333 foreach ($allergies as $allergy) {
334 array_push($sqlParam,$allergy);
335 if ($firstFlag) {
336 $sqlIN .= "?";
337 $firstFlag = FALSE;
339 else {
340 $sqlIN .= ",?";
344 // Check if allergies conflict with medications or prescriptions
345 $conflicts_unique = array();
346 if (!empty($sqlParam)) {
347 $conflicts = array();
348 array_push($sqlParam,$patient_id);
349 $res_meds = sqlStatement("SELECT `title` FROM `lists` WHERE `type`='medication' " .
350 "AND `activity`=1 " .
351 "AND ( `enddate` IS NULL OR `enddate`='' OR `enddate` > NOW() ) " .
352 "AND `title` IN (" . $sqlIN . ") AND `pid`=?", $sqlParam);
353 while ($urow = sqlFetchArray($res_meds)) {
354 array_push($conflicts, $urow['title']);
356 $res_rx = sqlStatement("SELECT `drug` FROM `prescriptions` WHERE `active`=1 " .
357 "AND `drug` IN (" . $sqlIN . ") AND `patient_id`=?", $sqlParam);
358 while ($urow = sqlFetchArray($res_rx)) {
359 array_push($conflicts, $urow['drug']);
361 if (!empty($conflicts)) {
362 $conflicts_unique = array_unique($conflicts);
366 // If there are conflicts, $test is FALSE, and alert logging is on, then run through compare_log_alerts
367 $new_conflicts = array();
368 if ( (!empty($conflicts_unique)) && $GLOBALS['enable_alert_log'] && ($test===FALSE) ) {
369 $new_conflicts = compare_log_alerts($patient_id,$conflicts_unique,'allergy_alert',$_SESSION['authId'],$mode);
372 if ($mode == 'all') {
373 if (!empty($conflicts_unique)) {
374 return $conflicts_unique;
376 else {
377 return FALSE;
380 else { // $mode = 'new'
381 if (!empty($new_conflicts)) {
382 return $new_conflicts;
384 else {
385 return FALSE;
391 * Compare current alerts with prior (in order to find new actions)
392 * Also functions to log the actions.
394 * @param integer $patient_id pid of selected patient
395 * @param array $current_targets array of targets
396 * @param string $category clinical_reminder_widget, active_reminder_popup, or allergy_alert
397 * @param integer $userid user id of user.
398 * @param string $log_trigger if 'all', then always log. If 'new', then only trigger log when a new item noted.
399 * @return array array with targets with associated rule.
401 function compare_log_alerts($patient_id,$current_targets,$category='clinical_reminder_widget',$userid='',$log_trigger='all') {
403 if (empty($userid)) {
404 $userid = $_SESSION['authId'];
407 if (empty($current_targets)) {
408 $current_targets = array();
411 // Collect most recent action_log
412 $prior_targets_sql = sqlQuery("SELECT `value` FROM `clinical_rules_log` " .
413 "WHERE `category` = ? AND `pid` = ? AND `uid` = ? " .
414 "ORDER BY `id` DESC LIMIT 1", array($category,$patient_id,$userid) );
415 $prior_targets = array();
416 if (!empty($prior_targets_sql['value'])) {
417 $prior_targets = json_decode($prior_targets_sql['value'], true);
420 // Compare the current with most recent log
421 if ( ($category == 'clinical_reminder_widget') || ($category == 'active_reminder_popup') ) {
422 //using fancy structure to store multiple elements
423 $new_targets = array_diff_key($current_targets,$prior_targets);
425 else { // $category == 'allergy_alert'
426 //using simple array
427 $new_targets = array_diff($current_targets,$prior_targets);
430 // Store current action_log and the new items
431 // If $log_trigger=='all'
432 // or If $log_trigger=='new' and there are new items
433 if ( ($log_trigger=='all') || (($log_trigger=='new') && (!empty($new_targets))) ) {
434 $current_targets_json = json_encode($current_targets);
435 $new_targets_json = '';
436 if (!empty($new_targets)) {
437 $new_targets_json = json_encode($new_targets);
439 sqlInsert("INSERT INTO `clinical_rules_log` " .
440 "(`date`,`pid`,`uid`,`category`,`value`,`new_value`) " .
441 "VALUES (NOW(),?,?,?,?,?)", array($patient_id,$userid,$category,$current_targets_json,$new_targets_json) );
444 // Return new actions (if there are any)
445 return $new_targets;
449 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
451 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
452 * on one patient or patients of one provider). The structure of the returned results is dependent on the
453 * $organize_mode and $mode parameters.
454 * <pre>The results are dependent on the $organize_mode parameter settings
455 * 'default' organize_mode:
456 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
457 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
458 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
459 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
460 * 'plans' organize_mode:
461 * Returns similar to default, but organizes by the active plans
462 * </pre>
464 * @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).
465 * @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.
466 * @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').
467 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
468 * @param string $plan test for specific plan only
469 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
470 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
471 * @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.
472 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
473 * @param integer $report_id id of report in database (if already bookmarked)
474 * @return array See above for organization structure of the results.
476 function test_rules_clinic_batch_method($provider='',$type='',$dateTarget='',$mode='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$batchSize='',$report_id=NULL) {
478 // Default to a batchsize, if empty
479 if (empty($batchSize)) {
480 $batchSize=100;
483 // Collect total number of pertinent patients (to calculate batching parameters)
484 $totalNumPatients = buildPatientArray('',$provider,$pat_prov_rel,NULL,NULL,TRUE);
486 // Cycle through the batches and collect/combine results
487 if (($totalNumPatients%$batchSize) > 0) {
488 // not perfectly divisible
489 $totalNumberBatches = floor($totalNumPatients/$batchSize) + 1;
491 else {
492 // perfectly divisible
493 $totalNumberBatches = floor($totalNumPatients/$batchSize);
496 // Fix things in the $options array(). This now stores the number of labs to be used in the denominator in the AMC report.
497 // 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
498 // of planned batches(note the fixed array will go into the test_rules_clinic function, however the original will be used
499 // in the report storing/tracking engine.
500 $options_modified=$options;
501 if (!empty($options_modified['labs_manual'])) {
502 $options_modified['labs_manual'] = $options_modified['labs_manual'] / $totalNumberBatches;
505 // Prepare the database to track/store results
506 $fields = array('provider'=>$provider,'mode'=>$mode,'plan'=>$plan,'organize_mode'=>$organize_mode,'pat_prov_rel'=>$pat_prov_rel);
507 if (is_array($dateTarget)) {
508 $fields = array_merge($fields,array(date_target=>$dateTarget['dateTarget']));
509 $fields = array_merge($fields,array(date_begin=>$dateTarget['dateBegin']));
511 else {
512 if (empty($dateTarget)) {
513 $fields = array_merge($fields,array(date_target=>date("Y-m-d H:i:s")));
515 else {
516 $fields = array_merge($fields,array(date_target=>$dateTarget));
519 if (!empty($options)) {
520 foreach ($options as $key => $value) {
521 $fields = array_merge($fields, array($key=>$value));
524 $report_id = beginReportDatabase($type,$fields,$report_id);
525 setTotalItemsReportDatabase($report_id,$totalNumPatients);
527 // Set ability to itemize report if this feature is turned on
528 if ( ( ($type == "active_alert" || $type == "passive_alert") && ($GLOBALS['report_itemizing_standard']) ) ||
529 ( ($type == "cqm" || $type == "cqm_2011" || $type == "cqm_2014") && ($GLOBALS['report_itemizing_cqm']) ) ||
530 ( ($type == "amc" || $type == "amc_2011" || $type == "amc_2014" || $type == "amc_2014_stage1" || $type == "amc_2014_stage2") && ($GLOBALS['report_itemizing_amc']) ) ) {
531 $GLOBALS['report_itemizing_temp_flag_and_id'] = $report_id;
533 else {
534 $GLOBALS['report_itemizing_temp_flag_and_id'] = 0;
537 for ($i=0;$i<$totalNumberBatches;$i++) {
539 // If itemization is turned on, then reset the rule id iterator
540 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
541 $GLOBALS['report_itemized_test_id_iterator'] = 1;
544 $dataSheet_batch = test_rules_clinic($provider,$type,$dateTarget,$mode,'',$plan,$organize_mode,$options_modified,$pat_prov_rel,(($batchSize*$i)+1),$batchSize);
545 if ($i == 0) {
546 // For first cycle, simply copy it to dataSheet
547 $dataSheet = $dataSheet_batch;
549 else {
550 //debug
551 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
552 //error_log("CDR: ".($batchSize*$i)." records",0);
554 // Integrate batch results into main dataSheet
555 foreach ($dataSheet_batch as $key => $row) {
556 if (!$row['is_sub']) {
557 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
558 $total_patients = $dataSheet[$key]['total_patients'] + $row['total_patients'];
559 $dataSheet[$key]['total_patients'] = $total_patients;
560 $excluded = $dataSheet[$key]['excluded'] + $row['excluded'];
561 $dataSheet[$key]['excluded'] = $excluded;
562 $pass_filter = $dataSheet[$key]['pass_filter'] + $row['pass_filter'];
563 $dataSheet[$key]['pass_filter'] = $pass_filter;
565 $pass_target = $dataSheet[$key]['pass_target'] + $row['pass_target'];
566 $dataSheet[$key]['pass_target'] = $pass_target;
567 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter,$excluded,$pass_target);
570 //Update database to track results
571 updateReportDatabase($report_id,$total_patients);
574 // Record results in database and send to screen, if applicable.
575 finishReportDatabase($report_id,json_encode($dataSheet));
576 return $dataSheet;
580 * Process clinic rules.
582 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
583 * on one patient or patients of one provider). The structure of the returned results is dependent on the
584 * $organize_mode and $mode parameters.
585 * <pre>The results are dependent on the $organize_mode parameter settings
586 * 'default' organize_mode:
587 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
588 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
589 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
590 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
591 * 'plans' organize_mode:
592 * Returns similar to default, but organizes by the active plans
593 * </pre>
595 * @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).
596 * @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.
597 * @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').
598 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
599 * @param integer $patient_id pid of patient. If blank then will check all patients.
600 * @param string $plan test for specific plan only
601 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
602 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
603 * @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.
604 * @param integer $start applicable patient to start at (when batching process)
605 * @param integer $batchSize number of patients to batch (when batching process)
606 * @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).
607 * @return array See above for organization structure of the results.
609 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='') {
611 // If dateTarget is an array, then organize them.
612 if (is_array($dateTarget)) {
613 $dateArray = $dateTarget;
614 $dateTarget = $dateTarget['dateTarget'];
617 // Set date to current if not set
618 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
620 // Prepare the results array
621 $results = array();
623 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
624 // then run through this function recursively and return results.
625 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
626 // First, collect an array of all providers
627 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
628 $ures = sqlStatementCdrEngine($query);
629 // Second, run through each provider recursively
630 while ($urow = sqlFetchArray($ures)) {
631 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode,$options,$pat_prov_rel,$start,$batchSize,$user);
632 if (!empty($newResults)) {
633 $provider_item['is_provider'] = TRUE;
634 $provider_item['prov_lname'] = $urow['lname'];
635 $provider_item['prov_fname'] = $urow['fname'];
636 $provider_item['npi'] = $urow['npi'];
637 $provider_item['federaltaxid'] = $urow['federaltaxid'];
638 array_push($results,$provider_item);
639 $results = array_merge($results,$newResults);
642 // done, so now can return results
643 return $results;
646 // If set organize-mode to plans, then collects active plans and run through this
647 // function recursively and return results.
648 if ($organize_mode == "plans") {
649 // First, collect active plans
650 $plans_resolve = resolve_plans_sql($plan,$patient_id);
651 // Second, run through function recursively
652 foreach ($plans_resolve as $plan_item) {
653 // (if collate_inner, then nest a collation of providers within each plan)
654 if ($provider == "collate_inner") {
655 // First, collect an array of all providers
656 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
657 $ures = sqlStatementCdrEngine($query);
658 // Second, run through each provider recursively
659 $provider_results = array();
660 while ($urow = sqlFetchArray($ures)) {
661 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize,$user);
662 if (!empty($newResults)) {
663 $provider_item['is_provider'] = TRUE;
664 $provider_item['prov_lname'] = $urow['lname'];
665 $provider_item['prov_fname'] = $urow['fname'];
666 $provider_item['npi'] = $urow['npi'];
667 $provider_item['federaltaxid'] = $urow['federaltaxid'];
668 array_push($provider_results,$provider_item);
669 $provider_results = array_merge($provider_results,$newResults);
672 if (!empty($provider_results)) {
673 $plan_item['is_plan'] = TRUE;
674 array_push($results,$plan_item);
675 $results = array_merge($results,$provider_results);
678 else {
679 // (not collate_inner, so do not nest providers within each plan)
680 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize,$user);
681 if (!empty($newResults)) {
682 $plan_item['is_plan'] = TRUE;
683 array_push($results,$plan_item);
684 $results = array_merge($results,$newResults);
688 // done, so now can return results
689 return $results;
692 // Collect applicable patient pids
693 $patientData = array();
694 $patientData = buildPatientArray($patient_id,$provider,$pat_prov_rel,$start,$batchSize);
696 // Go through each patient(s)
698 // If in report mode, then tabulate for each rule:
699 // Total Patients
700 // Patients that pass the filter
701 // Patients that pass the target
702 // If in reminders mode, then create reminders for each rule:
703 // Reminder that action is due soon
704 // Reminder that action is due
705 // Reminder that action is post-due
707 //Collect applicable rules
708 // Note that due to a limitation in the this function, the patient_id is explicitly
709 // for grouping items when not being done in real-time or for official reporting.
710 // So for cases such as patient reminders on a clinic scale, the calling function
711 // will actually need rather than pass in a explicit patient_id for each patient in
712 // a separate call to this function.
713 if ($mode != "report") {
714 // Use per patient custom rules (if exist)
715 // Note as discussed above, this only works for single patient instances.
716 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan,$user);
718 else { // $mode = "report"
719 // Only use default rules (do not use patient custom rules)
720 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan,$user);
723 foreach( $rules as $rowRule ) {
725 // If using cqm or amc type, then use the hard-coded rules set.
726 // Note these rules are only used in report mode.
727 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
729 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
730 $manager = new ReportManager();
731 if ($rowRule['amc_flag']) {
732 // Send array of dates ('dateBegin' and 'dateTarget')
733 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
735 else {
736 // Send target date
737 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
739 if (!empty($tempResults)) {
740 foreach ($tempResults as $tempResult) {
741 array_push($results,$tempResult);
745 // Go on to the next rule
746 continue;
749 // If in reminder mode then need to collect the measurement dates
750 // from rule_reminder table
751 $target_dates = array();
752 if ($mode != "report") {
753 // Calculate the dates to check for
754 if ($type == "patient_reminder") {
755 $reminder_interval_type = "patient_reminder";
757 else { // $type == "passive_alert" or $type == "active_alert"
758 $reminder_interval_type = "clinical_reminder";
760 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
762 else { // $mode == "report"
763 // Only use the target date in the report
764 $target_dates[0] = $dateTarget;
767 //Reset the counters
768 $total_patients = 0;
769 $pass_filter = 0;
770 $exclude_filter = 0;
771 $pass_target = 0;
773 // Find the number of target groups
774 $targetGroups = returnTargetGroups($rowRule['id']);
776 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
778 // If report itemization is turned on, then iterate the rule id iterator
779 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
780 $GLOBALS['report_itemized_test_id_iterator']++;
783 //skip this section if not report and more than one target group
784 foreach( $patientData as $rowPatient ) {
786 // First, deal with deceased patients
787 // (for now will simply skip the patient)
788 // If want to support rules for deceased patients then will need to migrate this below
789 // in target_dates foreach(guessing won't ever need to do this, though).
790 // Note using the dateTarget rather than dateFocus
791 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
792 continue;
795 // Count the total patients
796 $total_patients++;
798 $dateCounter = 1; // for reminder mode to keep track of which date checking
799 // If report itemization is turned on, reset flag.
800 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
801 $temp_track_pass = 1;
803 foreach ( $target_dates as $dateFocus ) {
805 //Skip if date is set to SKIP
806 if ($dateFocus == "SKIP") {
807 $dateCounter++;
808 continue;
811 //Set date counter and reminder token (applicable for reminders only)
812 if ($dateCounter == 1) {
813 $reminder_due = "soon_due";
815 else if ($dateCounter == 2) {
816 $reminder_due = "due";
818 else { // $dateCounter == 3
819 $reminder_due = "past_due";
822 // Check if pass filter
823 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
824 if ($passFilter === "EXCLUDED") {
825 // increment EXCLUDED and pass_filter counters
826 // and set as FALSE for reminder functionality.
827 $pass_filter++;
828 $exclude_filter++;
829 $passFilter = FALSE;
831 if ($passFilter) {
832 // increment pass filter counter
833 $pass_filter++;
834 // If report itemization is turned on, trigger flag.
835 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
836 $temp_track_pass = 0;
839 else {
840 $dateCounter++;
841 continue;
844 // Check if pass target
845 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
846 if ($passTarget) {
847 // increment pass target counter
848 $pass_target++;
849 // If report itemization is turned on, then record the "passed" item and set the flag
850 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
851 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
852 $temp_track_pass = 1;
854 // send to reminder results
855 if ($mode == "reminders-all") {
856 // place the completed actions into the reminder return array
857 $actionArray = resolve_action_sql($rowRule['id'],'1');
858 foreach ($actionArray as $action) {
859 $action_plus = $action;
860 $action_plus['due_status'] = "not_due";
861 $action_plus['pid'] = $rowPatient['pid'];
862 $action_plus['rule_id'] = $rowRule['id'];
863 $results = reminder_results_integrate($results, $action_plus);
866 break;
868 else {
869 // send to reminder results
870 if ($mode != "report") {
871 // place the uncompleted actions into the reminder return array
872 $actionArray = resolve_action_sql($rowRule['id'],'1');
873 foreach ($actionArray as $action) {
874 $action_plus = $action;
875 $action_plus['due_status'] = $reminder_due;
876 $action_plus['pid'] = $rowPatient['pid'];
877 $action_plus['rule_id'] = $rowRule['id'];
878 $results = reminder_results_integrate($results, $action_plus);
882 $dateCounter++;
884 // If report itemization is turned on, then record the "failed" item if it did not pass
885 if ($GLOBALS['report_itemizing_temp_flag_and_id'] && !($temp_track_pass)) {
886 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
891 // Calculate and save the data for the rule
892 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
893 if ($mode == "report") {
894 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
895 $newRow=array_merge($newRow,$rowRule);
897 // If itemization is turned on, then record the itemized_test_id
898 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
899 $newRow=array_merge($newRow,array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
902 array_push($results, $newRow);
905 // Now run through the target groups if more than one
906 if (count($targetGroups) > 1) {
907 foreach ($targetGroups as $i) {
909 // If report itemization is turned on, then iterate the rule id iterator
910 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
911 $GLOBALS['report_itemized_test_id_iterator']++;
914 //Reset the target counter
915 $pass_target = 0;
917 foreach( $patientData as $rowPatient ) {
919 // First, deal with deceased patients
920 // (for now will simply skip the patient)
921 // If want to support rules for deceased patients then will need to migrate this below
922 // in target_dates foreach(guessing won't ever need to do this, though).
923 // Note using the dateTarget rather than dateFocus
924 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
925 continue;
928 $dateCounter = 1; // for reminder mode to keep track of which date checking
929 // If report itemization is turned on, reset flag.
930 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
931 $temp_track_pass = 1;
933 foreach ( $target_dates as $dateFocus ) {
935 //Skip if date is set to SKIP
936 if ($dateFocus == "SKIP") {
937 $dateCounter++;
938 continue;
941 //Set date counter and reminder token (applicable for reminders only)
942 if ($dateCounter == 1) {
943 $reminder_due = "soon_due";
945 else if ($dateCounter == 2) {
946 $reminder_due = "due";
948 else { // $dateCounter == 3
949 $reminder_due = "past_due";
952 // Check if pass filter
953 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
954 if ($passFilter === "EXCLUDED") {
955 $passFilter = FALSE;
957 if (!$passFilter) {
958 $dateCounter++;
959 continue;
961 else {
962 // If report itemization is turned on, trigger flag.
963 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
964 $temp_track_pass = 0;
968 //Check if pass target
969 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
970 if ($passTarget) {
971 // increment pass target counter
972 $pass_target++;
973 // If report itemization is turned on, then record the "passed" item and set the flag
974 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
975 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
976 $temp_track_pass = 1;
978 // send to reminder results
979 if ($mode == "reminders-all") {
980 // place the completed actions into the reminder return array
981 $actionArray = resolve_action_sql($rowRule['id'],$i);
982 foreach ($actionArray as $action) {
983 $action_plus = $action;
984 $action_plus['due_status'] = "not_due";
985 $action_plus['pid'] = $rowPatient['pid'];
986 $action_plus['rule_id'] = $rowRule['id'];
987 $results = reminder_results_integrate($results, $action_plus);
990 break;
992 else {
993 // send to reminder results
994 if ($mode != "report") {
995 // place the actions into the reminder return array
996 $actionArray = resolve_action_sql($rowRule['id'],$i);
997 foreach ($actionArray as $action) {
998 $action_plus = $action;
999 $action_plus['due_status'] = $reminder_due;
1000 $action_plus['pid'] = $rowPatient['pid'];
1001 $action_plus['rule_id'] = $rowRule['id'];
1002 $results = reminder_results_integrate($results, $action_plus);
1006 $dateCounter++;
1008 // If report itemization is turned on, then record the "failed" item if it did not pass
1009 if ($GLOBALS['report_itemizing_temp_flag_and_id'] && !($temp_track_pass)) {
1010 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
1014 // Calculate and save the data for the rule
1015 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
1017 // Collect action for title (just use the first one, if more than one)
1018 $actionArray = resolve_action_sql($rowRule['id'],$i);
1019 $action = $actionArray[0];
1020 if ($mode == "report") {
1021 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
1023 // If itemization is turned on, then record the itemized_test_id
1024 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
1025 $newRow=array_merge($newRow,array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
1028 array_push($results, $newRow);
1034 // Return the data
1035 return $results;
1039 * Process patient array that is to be tested.
1041 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
1042 * @param integer $patient_id pid of patient. If blank then will check all patients.
1043 * @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.
1044 * @param integer $start applicable patient to start at (when batching process)
1045 * @param integer $batchSize number of patients to batch (when batching process)
1046 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
1047 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
1049 function buildPatientArray($patient_id='',$provider='',$pat_prov_rel='primary',$start=NULL,$batchSize=NULL,$onlyCount=FALSE) {
1051 if (!empty($patient_id)) {
1052 // only look at the selected patient
1053 if ($onlyCount) {
1054 $patientNumber = 1;
1056 else {
1057 $patientData[0]['pid'] = $patient_id;
1060 else {
1061 if (empty($provider)) {
1062 // Look at entire practice
1063 if ($start == NULL || $batchSize == NULL || $onlyCount) {
1064 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
1065 if ($onlyCount) {
1066 $patientNumber = sqlNumRows($rez);
1069 else {
1070 // batching
1071 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start-1),$batchSize));
1074 else {
1075 // Look at an individual physician
1076 if( $pat_prov_rel == 'encounter' ){
1077 // Choose patients that are related to specific physician by an encounter
1078 if ($start == NULL || $batchSize == NULL || $onlyCount) {
1079 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
1080 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
1081 if ($onlyCount) {
1082 $patientNumber = sqlNumRows($rez);
1085 else {
1086 //batching
1087 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
1088 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start-1),$batchSize));
1091 else { //$pat_prov_rel == 'primary'
1092 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
1093 if ($start == NULL || $batchSize == NULL || $onlyCount) {
1094 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1095 "WHERE `providerID`=? ORDER BY `pid`", array($provider) );
1096 if ($onlyCount) {
1097 $patientNumber = sqlNumRows($rez);
1100 else {
1101 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
1102 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start-1),$batchSize) );
1106 // convert the sql query results into an array if returning the array
1107 if(!$onlyCount) {
1108 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
1109 $patientData[$iter]=$row;
1114 if ($onlyCount) {
1115 // return the number of applicable patients
1116 return $patientNumber;
1118 else {
1119 // return array of patient pids
1120 return $patientData;
1125 * Test filter of a selected rule on a selected patient
1127 * @param integer $patient_id pid of selected patient.
1128 * @param string $rule id(string) of selected rule
1129 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
1130 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
1132 function test_filter($patient_id,$rule,$dateTarget) {
1134 // Set date to current if not set
1135 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1137 // Collect patient information
1138 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
1141 // ----------------- INCLUSIONS -----------------
1144 // -------- Age Filter (inclusion) ------------
1145 // Calculate patient age in years and months
1146 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
1147 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
1149 // Min age (year) Filter (assume that there in not more than one of each)
1150 $filter = resolve_filter_sql($rule,'filt_age_min');
1151 if (!empty($filter)) {
1152 $row = $filter[0];
1153 if ($row ['method_detail'] == "year") {
1154 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
1155 return false;
1158 if ($row ['method_detail'] == "month") {
1159 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
1160 return false;
1164 // Max age (year) Filter (assume that there in not more than one of each)
1165 $filter = resolve_filter_sql($rule,'filt_age_max');
1166 if (!empty($filter)) {
1167 $row = $filter[0];
1168 if ($row ['method_detail'] == "year") {
1169 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
1170 return false;
1173 if ($row ['method_detail'] == "month") {
1174 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
1175 return false;
1180 // -------- Gender Filter (inclusion) ---------
1181 // Gender Filter (assume that there in not more than one of each)
1182 $filter = resolve_filter_sql($rule,'filt_sex');
1183 if (!empty($filter)) {
1184 $row = $filter[0];
1185 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
1186 return false;
1190 // -------- Database Filter (inclusion) ------
1191 // Database Filter
1192 $filter = resolve_filter_sql($rule,'filt_database');
1193 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
1195 // -------- Lists Filter (inclusion) ----
1196 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
1197 // surgeries and allergies.
1198 $filter = resolve_filter_sql($rule,'filt_lists');
1199 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
1201 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
1202 // Procedure Target (includes) (may need to include an interval in the future)
1203 $filter = resolve_filter_sql($rule,'filt_proc');
1204 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
1207 // ----------------- EXCLUSIONS -----------------
1210 // -------- Lists Filter (EXCLUSION) ----
1211 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
1212 // surgeries and allergies.
1213 $filter = resolve_filter_sql($rule,'filt_lists',0);
1214 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
1216 // Passed all filters, so return true.
1217 return true;
1221 * Return an array containing existing group ids for a rule
1223 * @param string $rule id(string) of rule
1224 * @return array listing of group ids
1226 function returnTargetGroups($rule) {
1228 $sql = sqlStatementCdrEngine("SELECT DISTINCT `group_id` FROM `rule_target` " .
1229 "WHERE `id`=?", array($rule) );
1231 $groups = array();
1232 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1233 array_push($groups,$row['group_id']);
1235 return $groups;
1239 * Test targets of a selected rule on a selected patient
1241 * @param integer $patient_id pid of selected patient.
1242 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
1243 * @param integer $group_id group id of target group
1244 * @param string $dateTarget target date (format Y-m-d H:i:s).
1245 * @return boolean if target passes then true, otherwise false
1247 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
1249 // -------- Interval Target ----
1250 $interval = resolve_target_sql($rule,$group_id,'target_interval');
1252 // -------- Database Target ----
1253 // Database Target (includes)
1254 $target = resolve_target_sql($rule,$group_id,'target_database');
1255 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
1257 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
1258 // Procedure Target (includes)
1259 $target = resolve_target_sql($rule,$group_id,'target_proc');
1260 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
1262 // -------- Appointment Target ----
1263 // Appointment Target (includes) (Specialized functionality for appointment reminders)
1264 $target = resolve_target_sql($rule,$group_id,'target_appt');
1265 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
1267 // Passed all target tests, so return true.
1268 return true;
1272 * Function to return active plans
1274 * @param string $type plan type filter (normal or cqm or blank)
1275 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
1276 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
1277 * @return array active plans
1279 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
1281 if ($configurableOnly) {
1282 // Collect all default, configurable (per patient) plans into an array
1283 // (ie. ignore the cqm rules)
1284 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
1286 else {
1287 // Collect all default plans into an array
1288 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
1290 $returnArray= array();
1291 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1292 array_push($returnArray,$row);
1295 // Now collect the pertinent plans
1296 $newReturnArray = array();
1298 // Need to select rules (use custom if exist)
1299 foreach ($returnArray as $plan) {
1300 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
1302 // Decide if use default vs custom plan (preference given to custom plan)
1303 if (!empty($customPlan)) {
1304 if ($type == "cqm" ) {
1305 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
1306 $goPlan = $plan;
1308 else {
1309 // merge the custom plan with the default plan
1310 $mergedPlan = array();
1311 foreach ($customPlan as $key => $value) {
1312 if ($value == NULL && preg_match("/_flag$/",$key)) {
1313 // use default setting
1314 $mergedPlan[$key] = $plan[$key];
1316 else {
1317 // use custom setting
1318 $mergedPlan[$key] = $value;
1321 $goPlan = $mergedPlan;
1324 else {
1325 $goPlan = $plan;
1328 // Use the chosen plan if set
1329 if (!empty($type)) {
1330 if ($goPlan["${type}_flag"] == 1) {
1331 // active, so use the plan
1332 array_push($newReturnArray,$goPlan);
1335 else {
1336 if ($goPlan['normal_flag'] == 1 ||
1337 $goPlan['cqm_flag'] == 1) {
1338 // active, so use the plan
1339 array_push($newReturnArray,$goPlan);
1343 $returnArray = $newReturnArray;
1345 return $returnArray;
1350 * Function to return a specific plan
1352 * @param string $plan id(string) of plan
1353 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1354 * @return array a plan
1356 function collect_plan($plan,$patient_id='0') {
1358 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
1363 * Function to set a specific plan activity for a specific patient
1365 * @param string $plan id(string) of plan
1366 * @param string $type plan filter (normal,cqm)
1367 * @param string $setting activity of plan (yes,no,default)
1368 * @param integer $patient_id pid of selected patient.
1370 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
1372 // Don't allow messing with the default plans here
1373 if ($patient_id == "0") {
1374 return;
1377 // Convert setting
1378 if ($setting == "on") {
1379 $setting = 1;
1381 else if ($setting == "off") {
1382 $setting = 0;
1384 else { // $setting == "default"
1385 $setting = NULL;
1388 // Collect patient specific plan, if already exists.
1389 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
1390 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id) );
1392 if (empty($patient_plan)) {
1393 // Create a new patient specific plan with flags all set to default
1394 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1395 sqlStatementCdrEngine($query, array($plan, $patient_id) );
1398 // Update patient specific row
1399 $query = "UPDATE `clinical_plans` SET `" . escape_sql_column_name($type."_flag",array("clinical_plans")) . "`= ? WHERE id = ? AND pid = ?";
1400 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id) );
1405 * Function to return active rules
1407 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2014,amc_2011,amc_2014,patient_reminder)
1408 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1409 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1410 * @param string $plan collect rules for specific plan
1411 * @param string $user If a user is set, then will only show rules that user has permission to see
1412 * @return array rules
1414 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='',$user='') {
1416 if ($configurableOnly) {
1417 // Collect all default, configurable (per patient) rules into an array
1418 // (ie. ignore the cqm and amc rules)
1419 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1421 else {
1422 // Collect all default rules into an array
1423 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1425 $returnArray= array();
1426 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1427 array_push($returnArray,$row);
1430 // Now filter rules for plan (if applicable)
1431 if (!empty($plan)) {
1432 $planReturnArray = array();
1433 foreach ($returnArray as $rule) {
1434 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1435 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
1436 if (!empty($standardRule)) {
1437 array_push($planReturnArray,$rule);
1440 $returnArray = $planReturnArray;
1443 // Now collect the pertinent rules
1444 $newReturnArray = array();
1446 // Need to select rules (use custom if exist)
1447 foreach ($returnArray as $rule) {
1449 // If user is set, then check if user has access to the rule
1450 if (!empty($user)) {
1451 $access_control = explode(':',$rule['access_control']);
1452 if ( !empty($access_control[0]) && !empty($access_control[1]) ) {
1453 // Section and ACO filters are not empty, so do the test for access.
1454 if (!acl_check($access_control[0],$access_control[1],$user)) {
1455 // User does not have access to this rule, so skip the rule.
1456 continue;
1459 else {
1460 // Section or ACO filters are empty, so use default patients:med aco
1461 if (!acl_check('patients','med',$user)) {
1462 // User does not have access to this rule, so skip the rule.
1463 continue;
1468 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
1470 // Decide if use default vs custom rule (preference given to custom rule)
1471 if (!empty($customRule)) {
1472 if ($type == "cqm" || $type == "amc" ) {
1473 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1474 $goRule = $rule;
1476 else {
1477 // merge the custom rule with the default rule
1478 $mergedRule = array();
1479 foreach ($customRule as $key => $value) {
1480 if ($value == NULL && preg_match("/_flag$/",$key)) {
1481 // use default setting
1482 $mergedRule[$key] = $rule[$key];
1484 else {
1485 // use custom setting
1486 $mergedRule[$key] = $value;
1489 $goRule = $mergedRule;
1492 else {
1493 $goRule = $rule;
1496 // Use the chosen rule if set
1497 if (!empty($type)) {
1498 if ($goRule["${type}_flag"] == 1) {
1499 // active, so use the rule
1500 array_push($newReturnArray,$goRule);
1503 else {
1504 // no filter, so return the rule
1505 array_push($newReturnArray,$goRule);
1508 $returnArray = $newReturnArray;
1510 return $returnArray;
1514 * Function to return a specific rule
1516 * @param string $rule id(string) of rule
1517 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1518 * @return array rule
1520 function collect_rule($rule,$patient_id='0') {
1522 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
1527 * Function to set a specific rule activity for a specific patient
1529 * @param string $rule id(string) of rule
1530 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1531 * @param string $setting activity of rule (yes,no,default)
1532 * @param integer $patient_id pid of selected patient.
1534 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
1536 // Don't allow messing with the default rules here
1537 if ($patient_id == "0") {
1538 return;
1541 // Convert setting
1542 if ($setting == "on") {
1543 $setting = 1;
1545 else if ($setting == "off") {
1546 $setting = 0;
1548 else { // $setting == "default"
1549 $setting = NULL;
1552 //Collect main rule to allow setting of the access_control
1553 $original_query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = 0";
1554 $patient_rule_original = sqlQueryCdrEngine($original_query, array($rule) );
1556 // Collect patient specific rule, if already exists.
1557 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1558 $patient_rule = sqlQueryCdrEngine($query, array($rule,$patient_id) );
1560 if (empty($patient_rule)) {
1561 // Create a new patient specific rule with flags all set to default
1562 $query = "INSERT into `clinical_rules` (`id`, `pid`, `access_control`) VALUES (?,?,?)";
1563 sqlStatementCdrEngine($query, array($rule, $patient_id, $patient_rule_original['access_control']) );
1566 // Update patient specific row
1567 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ?, `access_control` = ? WHERE id = ? AND pid = ?";
1568 sqlStatementCdrEngine($query, array($setting,$patient_rule_original['access_control'],$rule,$patient_id) );
1573 * Function to return applicable reminder dates (relative)
1575 * @param string $rule id(string) of selected rule
1576 * @param string $reminder_method string label of filter type
1577 * @return array reminder features
1579 function resolve_reminder_sql($rule,$reminder_method) {
1580 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1581 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
1583 $returnArray= array();
1584 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1585 array_push($returnArray,$row);
1587 return $returnArray;
1591 * Function to return applicable filters
1593 * @param string $rule id(string) of selected rule
1594 * @param string $filter_method string label of filter type
1595 * @param string $include_flag to allow selection for included or excluded filters
1596 * @return array filters
1598 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1599 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1600 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1602 $returnArray= array();
1603 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1604 array_push($returnArray,$row);
1606 return $returnArray;
1610 * Function to return applicable targets
1612 * @param string $rule id(string) of selected rule
1613 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1614 * @param string $target_method string label of target type
1615 * @param string $include_flag to allow selection for included or excluded targets
1616 * @return array targets
1618 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1620 if ($group_id) {
1621 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1622 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1624 else {
1625 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1626 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1629 $returnArray= array();
1630 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1631 array_push($returnArray,$row);
1633 return $returnArray;
1637 * Function to return applicable actions
1639 * @param string $rule id(string) of selected rule
1640 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1641 * @return array actions
1643 function resolve_action_sql($rule,$group_id='') {
1645 if ($group_id) {
1646 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1647 "FROM `rule_action` as a " .
1648 "JOIN `rule_action_item` as b " .
1649 "ON a.category = b.category AND a.item = b.item " .
1650 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1652 else {
1653 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1654 "FROM `rule_action` as a " .
1655 "JOIN `rule_action_item` as b " .
1656 "ON a.category = b.category AND a.item = b.item " .
1657 "WHERE a.id=?", array($rule) );
1660 $returnArray= array();
1661 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1662 array_push($returnArray,$row);
1664 return $returnArray;
1668 * Function to check database filters and targets
1670 * @param string $patient_id pid of selected patient.
1671 * @param array $filter array containing filter/target elements
1672 * @param array $interval array containing interval elements
1673 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1674 * @return boolean true if check passed, otherwise false
1676 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1677 $isMatch = false; //matching flag
1679 // Set date to current if not set
1680 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1682 // Unpackage interval information
1683 // (Assume only one for now and only pertinent for targets)
1684 $intervalType = '';
1685 $intervalValue = '';
1686 if (!empty($interval)) {
1687 $intervalType = $interval[0]['value'];
1688 $intervalValue = $interval[0]['interval'];
1691 foreach( $filter as $row ) {
1692 // Row description
1693 // [0]=>special modes
1694 $temp_df = explode("::",$row['value']);
1696 if ($temp_df[0] == "CUSTOM") {
1697 // Row description
1698 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1699 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1700 // Record the match
1701 $isMatch = true;
1703 else {
1704 // If this is a required entry then return false
1705 if ($row['required_flag']) return false;
1708 else if ($temp_df[0] == "LIFESTYLE") {
1709 // Row description
1710 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1711 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1712 // Record the match
1713 $isMatch = true;
1715 else {
1716 // If this is a required entry then return false
1717 if ($row['required_flag']) return false;
1720 else {
1721 // Default mode
1722 // Row description
1723 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1724 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)) {
1725 // Record the match
1726 $isMatch = true;
1728 else {
1729 // If this is a required entry then return false
1730 if ($row['required_flag']) return false;
1735 // return results of check
1736 return $isMatch;
1740 * Function to check procedure filters and targets
1742 * @param string $patient_id pid of selected patient.
1743 * @param array $filter array containing filter/target elements
1744 * @param array $interval array containing interval elements
1745 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1746 * @return boolean true if check passed, otherwise false
1748 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1749 $isMatch = false; //matching flag
1751 // Set date to current if not set
1752 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1754 // Unpackage interval information
1755 // (Assume only one for now and only pertinent for targets)
1756 $intervalType = '';
1757 $intervalValue = '';
1758 if (!empty($interval)) {
1759 $intervalType = $interval[0]['value'];
1760 $intervalValue = $interval[0]['interval'];
1763 foreach( $filter as $row ) {
1764 // Row description
1765 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1766 // code description
1767 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1768 $temp_df = explode("::",$row['value']);
1769 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)) {
1770 // Record the match
1771 $isMatch = true;
1773 else {
1774 // If this is a required entry then return false
1775 if ($row['required_flag']) return false;
1779 // return results of check
1780 return $isMatch;
1784 * Function to check for appointment
1786 * @todo Complete this to allow appointment reminders.
1787 * @param string $patient_id pid of selected patient.
1788 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1789 * @return boolean true if appt exist, otherwise false
1791 function appointment_check($patient_id,$dateTarget='') {
1792 $isMatch = false; //matching flag
1794 // Set date to current if not set (although should always be set)
1795 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1796 $dateTargetRound = date('Y-m-d',$dateTarget);
1798 // Set current date
1799 $currentDate = date('Y-m-d H:i:s');
1800 $currentDateRound = date('Y-m-d',$dateCurrent);
1802 // Basically, if the appointment is within the current date to the target date,
1803 // then return true. (will not send reminders on same day as appointment)
1804 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1805 "openemr_postcalendar_events.pc_title, " .
1806 "openemr_postcalendar_events.pc_eventDate, " .
1807 "openemr_postcalendar_events.pc_startTime, " .
1808 "openemr_postcalendar_events.pc_endTime " .
1809 "FROM openemr_postcalendar_events " .
1810 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1811 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1812 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1814 // return results of check
1816 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1817 // Plan to send back array of appt info (eid, time, date, etc.)
1818 // to do this.
1819 if (sqlNumRows($sql) > 0) {
1820 $isMatch = true;
1823 return $isMatch;
1827 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1829 * @param string $patient_id pid of selected patient.
1830 * @param array $filter array containing lists filter/target elements
1831 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1832 * @return boolean true if check passed, otherwise false
1834 function lists_check($patient_id,$filter,$dateTarget) {
1835 $isMatch = false; //matching flag
1837 // Set date to current if not set
1838 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1840 foreach ( $filter as $row ) {
1841 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1842 // Record the match
1843 $isMatch = true;
1845 else {
1846 // If this is a required entry then return false
1847 if ($row['required_flag']) return false;
1851 // return results of check
1852 return $isMatch;
1856 * Function to check for existance of data in database for a patient
1858 * @param string $patient_id pid of selected patient.
1859 * @param string $table selected mysql table
1860 * @param string $column selected mysql column
1861 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1862 * @param string $data selected data in the mysql database
1863 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1864 * @param integer $num_items_thres number of items threshold
1865 * @param string $intervalType type of interval (ie. year)
1866 * @param integer $intervalValue searched for within this many times of the interval type
1867 * @param string $dateTarget target date(format Y-m-d H:i:s).
1868 * @return boolean true if check passed, otherwise false
1870 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1872 // Set date to current if not set
1873 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1875 // Collect the correct column label for patient id in the table
1876 $patient_id_label = collect_database_label('pid',$table);
1878 // Get the interval sql query string
1879 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1881 // If just checking for existence (ie. data is empty),
1882 // then simply set the comparison operator to ne.
1883 if (empty($data)) {
1884 $data_comp = "ne";
1887 // get the appropriate sql comparison operator
1888 $compSql = convertCompSql($data_comp);
1890 // custom issues per table can be placed here
1891 $customSQL = '';
1892 if ($table == 'immunizations') {
1893 $customSQL = " AND `added_erroneously` = '0' ";
1896 // check for items
1897 if (empty($column)) {
1898 // simple search for any table entries
1899 $sql = sqlStatementCdrEngine("SELECT * " .
1900 "FROM `" . add_escape_custom($table) . "` " .
1901 "WHERE `" . add_escape_custom($patient_id_label) . "`=? " . $customSQL, array($patient_id) );
1903 else {
1904 // search for number of specific items
1905 $sql = sqlStatementCdrEngine("SELECT `" . add_escape_custom($column) . "` " .
1906 "FROM `" . add_escape_custom($table) . "` " .
1907 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1908 "AND `" . add_escape_custom($patient_id_label) . "`=? " . $customSQL .
1909 $dateSql, array($data,$patient_id) );
1912 // See if number of returned items passes the comparison
1913 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1917 * Function to check for existence of procedure(s) for a patient
1919 * @param string $patient_id pid of selected patient.
1920 * @param string $proc_title procedure title
1921 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1922 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1923 * @param string $result_data results data
1924 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1925 * @param integer $num_items_thres number of items threshold
1926 * @param string $intervalType type of interval (ie. year)
1927 * @param integer $intervalValue searched for within this many times of the interval type
1928 * @param string $dateTarget target date(format Y-m-d H:i:s).
1929 * @return boolean true if check passed, otherwise false
1931 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1933 // Set date to current if not set
1934 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1936 // Set the table exception (for looking up pertinent date and pid sql columns)
1937 $table = "PROCEDURE-EXCEPTION";
1939 // Collect the correct column label for patient id in the table
1940 $patient_id_label = collect_database_label('pid',$table);
1942 // Get the interval sql query string
1943 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1945 // If just checking for existence (ie result_data is empty),
1946 // then simply set the comparison operator to ne.
1947 if (empty($result_data)) {
1948 $result_comp = "ne";
1951 // get the appropriate sql comparison operator
1952 $compSql = convertCompSql($result_comp);
1954 // explode the code array
1955 $codes= array();
1956 if (!empty($proc_code)) {
1957 $codes = explode("||",$proc_code);
1959 else {
1960 $codes[0] = '';
1963 // ensure proc_title is at least blank
1964 if (empty($proc_title)) {
1965 $proc_title = '';
1968 // collect specific items (use both title and/or codes) that fulfill request
1969 $sqlBindArray=array();
1970 $sql_query = "SELECT procedure_result.result FROM " .
1971 "procedure_order_code, " .
1972 "procedure_order, " .
1973 "procedure_type, " .
1974 "procedure_report, " .
1975 "procedure_result " .
1976 "WHERE " .
1977 "procedure_order_code.procedure_code = procedure_type.procedure_code AND " .
1978 "procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
1979 "procedure_order.lab_id = procedure_type.lab_id AND " .
1980 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
1981 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
1982 "procedure_result.procedure_report_id = procedure_report.procedure_report_id AND " .
1983 "procedure_type.procedure_type = 'ord' AND ";
1984 foreach ($codes as $tem) {
1985 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
1986 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
1987 array_push($sqlBindArray,$tem,$tem);
1989 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
1990 "AND procedure_result.result " . $compSql . " ? " .
1991 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
1992 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
1994 $sql = sqlStatementCdrEngine($sql_query,$sqlBindArray);
1996 // See if number of returned items passes the comparison
1997 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2001 * Function to check for existance of data for a patient in the rule_patient_data table
2003 * @param string $patient_id pid of selected patient.
2004 * @param string $category label in category column
2005 * @param string $item label in item column
2006 * @param string $complete label in complete column (YES,NO, or blank)
2007 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
2008 * @param integer $num_items_thres number of items threshold
2009 * @param string $intervalType type of interval (ie. year)
2010 * @param integer $intervalValue searched for within this many times of the interval type
2011 * @param string $dateTarget target date(format Y-m-d H:i:s).
2012 * @return boolean true if check passed, otherwise false
2014 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
2016 // Set the table
2017 $table = 'rule_patient_data';
2019 // Collect the correct column label for patient id in the table
2020 $patient_id_label = collect_database_label('pid',$table);
2022 // Get the interval sql query string
2023 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
2025 // search for number of specific items
2026 $sql = sqlStatementCdrEngine("SELECT `result` " .
2027 "FROM `" . add_escape_custom($table) . "` " .
2028 "WHERE `category`=? " .
2029 "AND `item`=? " .
2030 "AND `complete`=? " .
2031 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
2032 $dateSql, array($category,$item,$complete,$patient_id) );
2034 // See if number of returned items passes the comparison
2035 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
2039 * Function to check for existance of data for a patient in lifestyle section
2041 * @param string $patient_id pid of selected patient.
2042 * @param string $lifestyle selected label of mysql column of patient history
2043 * @param string $status specific status of selected lifestyle element
2044 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
2045 * @return boolean true if check passed, otherwise false
2047 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
2049 // Set date to current if not set
2050 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2052 // Collect pertinent history data
2053 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
2055 // See if match
2056 $stringFlag = strstr($history[$lifestyle], "|".$status);
2057 if (empty($status)) {
2058 // Only ensuring any data has been entered into the field
2059 $stringFlag = true;
2061 if ( $history[$lifestyle] &&
2062 $history[$lifestyle] != '|0|' &&
2063 $stringFlag ) {
2064 return true;
2066 else {
2067 return false;
2072 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
2073 * allergies, and surgeries.
2075 * @param string $patient_id pid of selected patient.
2076 * @param string $type type (medical_problem, allergy, medication, etc)
2077 * @param string $value value searching for
2078 * @param string $dateTarget target date(format Y-m-d H:i:s).
2079 * @return boolean true if check passed, otherwise false
2081 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
2083 // Set date to current if not set
2084 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2086 // Attempt to explode the value into a code type and code (if applicable)
2087 $value_array = explode("::",$value);
2088 if (count($value_array) == 2) {
2090 // Collect the code type and code
2091 $code_type = $value_array[0];
2092 $code = $value_array[1];
2094 if ($code_type=='CUSTOM') {
2095 // Deal with custom code type first (title column in lists table)
2096 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2097 "WHERE `type`=? " .
2098 "AND `pid`=? " .
2099 "AND `title`=? " .
2100 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2101 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
2102 if (!empty($response)) return true;
2104 else {
2105 // Deal with the set code types (diagnosis column in lists table)
2106 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2107 "WHERE `type`=? " .
2108 "AND `pid`=? " .
2109 "AND `diagnosis` LIKE ? " .
2110 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2111 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
2112 if (!empty($response)) return true;
2115 else { // count($value_array) == 1
2116 // Search the title column in lists table
2117 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
2118 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
2119 "WHERE `type`=? " .
2120 "AND `pid`=? " .
2121 "AND `title`=? ".
2122 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
2123 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
2124 if (!empty($response)) return true;
2127 return false;
2131 * Function to return part of sql query to deal with interval
2133 * @param string $table selected mysql table (or EXCEPTION(s))
2134 * @param string $intervalType type of interval (ie. year)
2135 * @param string $intervalValue searched for within this many times of the interval type
2136 * @param string $dateTarget target date(format Y-m-d H:i:s).
2137 * @return string contains pertinent date interval filter for mysql query
2139 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
2141 $dateSql="";
2143 // Collect the correct column label for date in the table
2144 $date_label = collect_database_label('date',$table);
2146 // Deal with interval
2147 if (!empty($intervalType)) {
2148 switch($intervalType) {
2149 case "year":
2150 $dateSql = "AND (" . add_escape_custom($date_label) .
2151 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2152 "', INTERVAL " . add_escape_custom($intervalValue) .
2153 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
2154 break;
2155 case "month":
2156 $dateSql = "AND (" . add_escape_custom($date_label) .
2157 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2158 "', INTERVAL " . add_escape_custom($intervalValue) .
2159 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
2160 break;
2161 case "week":
2162 $dateSql = "AND (" . add_escape_custom($date_label) .
2163 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2164 "', INTERVAL " . add_escape_custom($intervalValue) .
2165 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
2166 break;
2167 case "day":
2168 $dateSql = "AND (" . add_escape_custom($date_label) .
2169 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2170 "', INTERVAL " . add_escape_custom($intervalValue) .
2171 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
2172 break;
2173 case "hour":
2174 $dateSql = "AND (" . add_escape_custom($date_label) .
2175 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2176 "', INTERVAL " . add_escape_custom($intervalValue) .
2177 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
2178 break;
2179 case "minute":
2180 $dateSql = "AND (" . add_escape_custom($date_label) .
2181 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2182 "', INTERVAL " . add_escape_custom($intervalValue) .
2183 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
2184 break;
2185 case "second":
2186 $dateSql = "AND (" . add_escape_custom($date_label) .
2187 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2188 "', INTERVAL " . add_escape_custom($intervalValue) .
2189 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
2190 break;
2191 case "flu_season":
2192 // Flu season to be hard-coded as September thru February
2193 // (Should make this modifiable in the future)
2194 // ($intervalValue is not used)
2195 $dateArray = explode("-",$dateTarget);
2196 $Year = $dateArray[0];
2197 $dateThisYear = $Year . "-09-01";
2198 $dateLastYear = ($Year-1) . "-09-01";
2199 $dateSql =" " .
2200 "AND ((" .
2201 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
2202 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
2203 "OR (" .
2204 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
2205 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
2206 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
2207 break;
2210 else {
2211 $dateSql = "AND " . add_escape_custom($date_label) .
2212 " <= '" . add_escape_custom($dateTarget) . "' ";
2215 // return the sql interval string
2216 return $dateSql;
2220 * Function to collect generic column labels from tables. It currently works for date
2221 * and pid. Will need to expand this as algorithm grows.
2223 * @param string $label element (pid or date)
2224 * @param string $table selected mysql table (or EXCEPTION(s))
2225 * @return string contains official label of selected element
2227 function collect_database_label($label,$table) {
2229 if ($table == 'PROCEDURE-EXCEPTION') {
2230 // return cell to get procedure collection
2231 // special case since reuqires joing of multiple
2232 // tables to get this value
2233 if ($label == "pid") {
2234 $returnedLabel = "procedure_order.patient_id";
2236 else if ($label == "date") {
2237 $returnedLabel = "procedure_report.date_collected";
2239 else {
2240 // unknown label, so return the original label
2241 $returnedLabel = $label;
2244 else if ($table == 'immunizations') {
2245 // return requested label for immunization table
2246 if ($label == "pid") {
2247 $returnedLabel = "patient_id";
2249 else if ($label == "date") {
2250 $returnedLabel = "`administered_date`";
2252 else {
2253 // unknown label, so return the original label
2254 $returnedLabel = $label;
2257 else {
2258 // return requested label for default tables
2259 if ($label == "pid") {
2260 $returnedLabel = "pid";
2262 else if ($label == "date") {
2263 $returnedLabel = "`date`";
2265 else {
2266 // unknown label, so return the original label
2267 $returnedLabel = $label;
2271 return $returnedLabel;
2275 * Simple function to avoid processing of duplicate actions
2277 * @param string $actions 2-dimensional array with all current active targets
2278 * @param string $action array of selected target to test for duplicate
2279 * @return boolean true if duplicate, false if not duplicate
2281 function is_duplicate_action($actions,$action) {
2282 foreach ($actions as $row) {
2283 if ($row['category'] == $action['category'] &&
2284 $row['item'] == $action['item'] &&
2285 $row['value'] == $action['value']) {
2286 // Is a duplicate
2287 return true;
2291 // Not a duplicate
2292 return false;
2296 * Calculate the reminder dates.
2298 * This function returns an array that contains three elements (each element is a date).
2299 * <pre>The three dates are:
2300 * first date is before the target date (past_due) (default of 1 month)
2301 * second date is the target date (due)
2302 * third date is after the target date (soon_due) (default of 2 weeks)
2303 * </pre>
2305 * @param string $rule id(string) of selected rule
2306 * @param string $dateTarget target date(format Y-m-d H:i:s).
2307 * @param string $type either 'patient_reminder' or 'clinical_reminder'
2308 * @return array see above for description of returned array
2310 function calculate_reminder_dates($rule, $dateTarget='',$type) {
2312 // Set date to current if not set
2313 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2315 // Collect the current date settings (to ensure not skip)
2316 $res = resolve_reminder_sql($rule, $type.'_current');
2317 if (!empty($res)) {
2318 $row = $res[0];
2319 if ($row ['method_detail'] == "SKIP") {
2320 $dateTarget = "SKIP";
2324 // Collect the past_due date
2325 $past_due_date = "";
2326 $res = resolve_reminder_sql($rule, $type.'_post');
2327 if (!empty($res)) {
2328 $row = $res[0];
2329 if ($row ['method_detail'] == "week") {
2330 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
2332 if ($row ['method_detail'] == "month") {
2333 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
2335 if ($row ['method_detail'] == "hour") {
2336 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2338 if ($row ['method_detail'] == "SKIP") {
2339 $past_due_date = "SKIP";
2342 else {
2343 // empty settings, so use default of one month
2344 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
2347 // Collect the soon_due date
2348 $soon_due_date = "";
2349 $res = resolve_reminder_sql($rule, $type.'_pre');
2350 if (!empty($res)) {
2351 $row = $res[0];
2352 if ($row ['method_detail'] == "week") {
2353 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
2355 if ($row ['method_detail'] == "month") {
2356 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
2358 if ($row ['method_detail'] == "hour") {
2359 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2361 if ($row ['method_detail'] == "SKIP") {
2362 $soon_due_date = "SKIP";
2365 else {
2366 // empty settings, so use default of one month
2367 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
2370 // Return the array of three dates
2371 return array($soon_due_date,$dateTarget,$past_due_date);
2375 * Adds an action into the reminder array
2377 * @param array $reminderOldArray Contains the current array of reminders
2378 * @param array $reminderNew Array of a new reminder
2379 * @return array Reminders
2381 function reminder_results_integrate($reminderOldArray, $reminderNew) {
2383 $results = array();
2385 // If reminderArray is empty, then insert new reminder
2386 if (empty($reminderOldArray)) {
2387 array_push($results, $reminderNew);
2388 return $results;
2391 // If duplicate reminder, then replace the old one
2392 $duplicate = false;
2393 foreach ($reminderOldArray as $reminderOld) {
2394 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
2395 $reminderOld['category'] == $reminderNew['category'] &&
2396 $reminderOld['item'] == $reminderNew['item']) {
2397 array_push($results, $reminderNew);
2398 $duplicate = true;
2400 else {
2401 array_push($results, $reminderOld);
2405 // If a new reminder, then insert the new reminder
2406 if (!$duplicate) {
2407 array_push($results, $reminderNew);
2410 return $results;
2414 * Compares number of items with requested comparison operator
2416 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2417 * @param string $thres Threshold used in comparison
2418 * @param integer $num_items Number of items
2419 * @return boolean Comparison results
2421 function itemsNumberCompare($comp, $thres, $num_items) {
2423 if ( ($comp == "eq") && ($num_items == $thres) ) {
2424 return true;
2426 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
2427 return true;
2429 else if ( ($comp == "gt") && ($num_items > $thres) ) {
2430 return true;
2432 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
2433 return true;
2435 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
2436 return true;
2438 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
2439 return true;
2441 else {
2442 return false;
2447 * Converts a text comparison operator to sql equivalent
2449 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2450 * @return string contains sql compatible comparison operator
2452 function convertCompSql($comp) {
2454 if ($comp == "eq") {
2455 return "=";
2457 else if ($comp == "ne") {
2458 return "!=";
2460 else if ($comp == "gt") {
2461 return ">";
2463 else if ($comp == "ge") {
2464 return ">=";
2466 else if ($comp == "lt") {
2467 return "<";
2469 else { // ($comp == "le")
2470 return "<=";
2476 * Function to find age in years (with decimal) on the target date
2478 * @param string $dob date of birth
2479 * @param string $target date to calculate age on
2480 * @return float years(decimal) from dob to target(date)
2482 function convertDobtoAgeYearDecimal($dob,$target) {
2483 $ageInfo=parseAgeInfo($dob,$target);
2484 return $ageInfo['age'];
2488 * Function to find age in months (with decimal) on the target date
2490 * @param string $dob date of birth
2491 * @param string $target date to calculate age on
2492 * @return float months(decimal) from dob to target(date)
2494 function convertDobtoAgeMonthDecimal($dob,$target) {
2495 $ageInfo=parseAgeInfo($dob,$target);
2496 return $ageInfo['age_in_months'];
2500 * Function to calculate the percentage for reports.
2502 * @param integer $pass_filter number of patients that pass filter
2503 * @param integer $exclude_filter number of patients that are excluded
2504 * @param integer $pass_target number of patients that pass target
2505 * @return string Number formatted into a percentage
2507 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
2508 if ($pass_filt > 0) {
2509 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
2511 else {
2512 $perc = "0". xl('%');
2514 return $perc;