Fix #5 for MU2 item a8.
[openemr.git] / library / clinical_rules.php
blobf18c40a6dd64fd0fa7d1625bc33cfcba9117b0a9
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 * Display the clinical summary widget.
45 * @param integer $patient_id pid of selected patient
46 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
47 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
48 * @param string $organize_mode Way to organize the results (default or plans)
49 * @param string $user If a user is set, then will only show rules that user has permission to see.
51 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default',$user='') {
53 // Set date to current if not set
54 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
56 // Collect active actions
57 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode, array(),'primary',NULL,NULL,$user);
59 // Display the actions
60 $current_targets = array();
61 foreach ($actions as $action) {
63 // Deal with plan names first
64 if (isset($action['is_plan']) && $action['is_plan']) {
65 echo "<br><b>";
66 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
67 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
68 echo "</b><br>";
69 continue;
72 // Collect the Rule Title, Rule Developer, Rule Funding Source, and Rule Release and show it when hover over the item.
73 $tooltip = '';
74 if (!empty($action['rule_id'])) {
75 $rule_title = getListItemTitle("clinical_rules",$action['rule_id']);
76 $ruleData = sqlQuery("SELECT `developer`, `funding_source`, `release_version`, `web_reference` " .
77 "FROM `clinical_rules` " .
78 "WHERE `id`=? AND `pid`=0", array($action['rule_id']) );
79 $developer = $ruleData['developer'];
80 $funding_source = $ruleData['funding_source'];
81 $release = $ruleData['release_version'];
82 $web_reference = $ruleData['web_reference'];
83 if (!empty($rule_title)) {
84 $tooltip = xla('Rule Title') . ": " . attr($rule_title) . "&#013;";
86 if (!empty($developer)) {
87 $tooltip .= xla('Rule Developer') . ": " . attr($developer) . "&#013;";
89 if (!empty($funding_source)) {
90 $tooltip .= xla('Rule Funding Source') . ": " . attr($funding_source) . "&#013;";
92 if (!empty($release)) {
93 $tooltip .= xla('Rule Release') . ": " . attr($release);
95 if ( (!empty($tooltip)) || (!empty($web_reference)) ) {
96 if (!empty($web_reference)) {
97 $tooltip = "<a href='".attr($web_reference)."' target='_blank' style='white-space: pre-line;' title='".$tooltip."'>?</a>";
99 else {
100 $tooltip = "<span style='white-space: pre-line;' title='".$tooltip."'>?</span>";
105 if ($action['custom_flag']) {
106 // Start link for reminders that use the custom rules input screen
107 $url = "../rules/patient_data.php?category=".htmlspecialchars( $action['category'], ENT_QUOTES);
108 $url .= "&item=".htmlspecialchars( $action['item'], ENT_QUOTES);
109 echo "<a href='".$url."' class='iframe medium_modal' onclick='top.restoreSession()'>";
111 else if ($action['clin_rem_link']) {
112 // Start link for reminders that use the custom rules input screen
113 $pieces_url = parse_url($action['clin_rem_link']);
114 $url_prefix = $pieces_url['scheme'];
115 if($url_prefix == 'https' || $url_prefix == 'http'){
116 echo "<a href='" . $action['clin_rem_link'] .
117 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
118 }else{
119 echo "<a href='../../../" . $action['clin_rem_link'] .
120 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
123 else {
124 // continue since no link is needed
127 // Display Reminder Details
128 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
129 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
131 if ($action['custom_flag'] || $action['clin_rem_link']) {
132 // End link for reminders that use an html link
133 echo "</a>";
136 // Display due status
137 if ($action['due_status']) {
138 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
139 if ($action['due_status'] == "past_due") {
140 echo "&nbsp;&nbsp;(<span style='color:red'>";
142 else if ($action['due_status'] == "due") {
143 echo "&nbsp;&nbsp;(<span style='color:purple'>";
145 else if ($action['due_status'] == "not_due") {
146 echo "&nbsp;&nbsp;(<span style='color:green'>";
148 else {
149 echo "&nbsp;&nbsp;(<span>";
151 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)";
154 // Display the tooltip
155 if (!empty($tooltip)) {
156 echo "&nbsp;".$tooltip."<br>";
158 else {
159 echo "<br>";
162 // Add the target(and rule id and room for future elements as needed) to the $current_targets array.
163 // Only when $mode is reminders-due
164 if ($mode == "reminders-due") {
165 $target_temp = $action['category'].":".$action['item'];
166 $current_targets[$target_temp] = array('rule_id'=>$action['rule_id'],'due_status'=>$action['due_status']);
170 // Compare the current with most recent action log (this function will also log the current actions)
171 // Only when $mode is reminders-due
172 if ($mode == "reminders-due") {
173 $new_targets = compare_clinical_summary_widget($patient_id,$current_targets);
174 if (!empty($new_targets) && $GLOBALS['enable_cdr_new_crp']) {
175 // If there are new action(s), then throw a popup (if the enable_cdr_new_crp global is turned on)
176 // Note I am taking advantage of a slight hack in order to run javascript within code that
177 // is being passed via an ajax call by using a dummy image.
178 echo '<img src="../../pic/empty.gif" onload="alert(\''.xls('New Due Clinical Reminders').'\n\n';
179 foreach ($new_targets as $key => $value) {
180 $category_item = explode(":",$key);
181 $category = $category_item[0];
182 $item = $category_item[1];
183 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$category) .
184 ': ' . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$item). '\n';
186 echo '\n' . '('. xls('See the Clinical Reminders widget for more details'). ')';
187 echo '\');this.parentNode.removeChild(this);" />';
193 * Compare current clinical summary widget actions with prior (in order to find new actions)
194 * Also functions to log the actions.
196 * @param integer $patient_id pid of selected patient
197 * @param array $current_targets array of targets
198 * @param integer $userid user id of user.
199 * @return array array with targets with associated rule.
201 function compare_clinical_summary_widget($patient_id,$current_targets,$userid='') {
203 if (empty($userid)) {
204 $userid = $_SESSION['authId'];
207 if (empty($current_targets)) {
208 $current_targets = array();
211 // Collect most recent action_log
212 $prior_targets_sql = sqlQuery("SELECT `value` FROM `clinical_rules_log` " .
213 "WHERE `category` = 'clinical_reminder_widget' AND `pid` = ? AND `uid` = ? " .
214 "ORDER BY `id` DESC LIMIT 1", array($patient_id,$userid) );
215 $prior_targets = array();
216 if (!empty($prior_targets_sql['value'])) {
217 $prior_targets = json_decode($prior_targets_sql['value'], true);
220 // Store current action_log
221 $current_targets_json = json_encode($current_targets);
222 sqlInsert("INSERT INTO `clinical_rules_log` " .
223 "(`date`,`pid`,`uid`,`category`,`value`) " .
224 "VALUES (NOW(),?,?,'clinical_reminder_widget',?)", array($patient_id,$userid,$current_targets_json) );
226 // Compare the current with most recent action log
227 $new_targets = array_diff_key($current_targets,$prior_targets);
229 // Return news actions (if there are any)
230 return $new_targets;
234 * Display the active screen reminder.
236 * @param integer $patient_id pid of selected patient
237 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
238 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
239 * @param string $organize_mode Way to organize the results (default or plans)
240 * @param string $user If a user is set, then will only show rules that user has permission to see
241 * @return string html display output.
243 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default',$user='') {
245 // Set date to current if not set
246 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
248 // Collect active actions
249 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode, array(),'primary',NULL,NULL,$user);
251 if (empty($actions)) {
252 return false;
255 $returnOutput = "";
257 // Display the actions
258 foreach ($actions as $action) {
260 // Deal with plan names first
261 if ($action['is_plan']) {
262 $returnOutput .= "<br><b>";
263 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
264 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
265 $returnOutput .= "</b><br>";
266 continue;
269 // Display Reminder Details
270 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
271 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
273 // Display due status
274 if ($action['due_status']) {
275 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
276 if ($action['due_status'] == "past_due") {
277 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
279 else if ($action['due_status'] == "due") {
280 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
282 else if ($action['due_status'] == "not_due") {
283 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
285 else {
286 $returnOutput .= "&nbsp;&nbsp;(<span>";
288 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
290 else {
291 $returnOutput .= "<br>";
294 return $returnOutput;
298 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
300 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
301 * on one patient or patients of one provider). The structure of the returned results is dependent on the
302 * $organize_mode and $mode parameters.
303 * <pre>The results are dependent on the $organize_mode parameter settings
304 * 'default' organize_mode:
305 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
306 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
307 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
308 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
309 * 'plans' organize_mode:
310 * Returns similar to default, but organizes by the active plans
311 * </pre>
313 * @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).
314 * @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.
315 * @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').
316 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
317 * @param string $plan test for specific plan only
318 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
319 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
320 * @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.
321 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
322 * @param integer $report_id id of report in database (if already bookmarked)
323 * @return array See above for organization structure of the results.
325 function test_rules_clinic_batch_method($provider='',$type='',$dateTarget='',$mode='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$batchSize='',$report_id=NULL) {
327 // Default to a batchsize, if empty
328 if (empty($batchSize)) {
329 $batchSize=100;
332 // Collect total number of pertinent patients (to calculate batching parameters)
333 $totalNumPatients = buildPatientArray('',$provider,$pat_prov_rel,NULL,NULL,TRUE);
335 // Cycle through the batches and collect/combine results
336 if (($totalNumPatients%$batchSize) > 0) {
337 // not perfectly divisible
338 $totalNumberBatches = floor($totalNumPatients/$batchSize) + 1;
340 else {
341 // perfectly divisible
342 $totalNumberBatches = floor($totalNumPatients/$batchSize);
345 // Fix things in the $options array(). This now stores the number of labs to be used in the denominator in the AMC report.
346 // 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
347 // of planned batches(note the fixed array will go into the test_rules_clinic function, however the original will be used
348 // in the report storing/tracking engine.
349 $options_modified=$options;
350 if (!empty($options_modified['labs_manual'])) {
351 $options_modified['labs_manual'] = $options_modified['labs_manual'] / $totalNumberBatches;
354 // Prepare the database to track/store results
355 $fields = array('provider'=>$provider,'mode'=>$mode,'plan'=>$plan,'organize_mode'=>$organize_mode,'pat_prov_rel'=>$pat_prov_rel);
356 if (is_array($dateTarget)) {
357 $fields = array_merge($fields,array(date_target=>$dateTarget['dateTarget']));
358 $fields = array_merge($fields,array(date_begin=>$dateTarget['dateBegin']));
360 else {
361 if (empty($dateTarget)) {
362 $fields = array_merge($fields,array(date_target=>date("Y-m-d H:i:s")));
364 else {
365 $fields = array_merge($fields,array(date_target=>$dateTarget));
368 if (!empty($options)) {
369 foreach ($options as $key => $value) {
370 $fields = array_merge($fields, array($key=>$value));
373 $report_id = beginReportDatabase($type,$fields,$report_id);
374 setTotalItemsReportDatabase($report_id,$totalNumPatients);
376 // Set ability to itemize report if this feature is turned on
377 if ( ( ($type == "active_alert" || $type == "passive_alert") && ($GLOBALS['report_itemizing_standard']) ) ||
378 ( ($type == "cqm" || $type == "cqm_2011" || $type == "cqm_2014") && ($GLOBALS['report_itemizing_cqm']) ) ||
379 ( ($type == "amc" || $type == "amc_2011" || $type == "amc_2014" || $type == "amc_2014_stage1" || $type == "amc_2014_stage2") && ($GLOBALS['report_itemizing_amc']) ) ) {
380 $GLOBALS['report_itemizing_temp_flag_and_id'] = $report_id;
382 else {
383 $GLOBALS['report_itemizing_temp_flag_and_id'] = 0;
386 for ($i=0;$i<$totalNumberBatches;$i++) {
388 // If itemization is turned on, then reset the rule id iterator
389 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
390 $GLOBALS['report_itemized_test_id_iterator'] = 1;
393 $dataSheet_batch = test_rules_clinic($provider,$type,$dateTarget,$mode,'',$plan,$organize_mode,$options_modified,$pat_prov_rel,(($batchSize*$i)+1),$batchSize);
394 if ($i == 0) {
395 // For first cycle, simply copy it to dataSheet
396 $dataSheet = $dataSheet_batch;
398 else {
399 //debug
400 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
401 //error_log("CDR: ".($batchSize*$i)." records",0);
403 // Integrate batch results into main dataSheet
404 foreach ($dataSheet_batch as $key => $row) {
405 if (!$row['is_sub']) {
406 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
407 $total_patients = $dataSheet[$key]['total_patients'] + $row['total_patients'];
408 $dataSheet[$key]['total_patients'] = $total_patients;
409 $excluded = $dataSheet[$key]['excluded'] + $row['excluded'];
410 $dataSheet[$key]['excluded'] = $excluded;
411 $pass_filter = $dataSheet[$key]['pass_filter'] + $row['pass_filter'];
412 $dataSheet[$key]['pass_filter'] = $pass_filter;
414 $pass_target = $dataSheet[$key]['pass_target'] + $row['pass_target'];
415 $dataSheet[$key]['pass_target'] = $pass_target;
416 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter,$excluded,$pass_target);
419 //Update database to track results
420 updateReportDatabase($report_id,$total_patients);
423 // Record results in database and send to screen, if applicable.
424 finishReportDatabase($report_id,json_encode($dataSheet));
425 return $dataSheet;
429 * Process clinic rules.
431 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
432 * on one patient or patients of one provider). The structure of the returned results is dependent on the
433 * $organize_mode and $mode parameters.
434 * <pre>The results are dependent on the $organize_mode parameter settings
435 * 'default' organize_mode:
436 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
437 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
438 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
439 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
440 * 'plans' organize_mode:
441 * Returns similar to default, but organizes by the active plans
442 * </pre>
444 * @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).
445 * @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.
446 * @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').
447 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
448 * @param integer $patient_id pid of patient. If blank then will check all patients.
449 * @param string $plan test for specific plan only
450 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
451 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
452 * @param string $pat_prov_rel How to choose patients that are related to a chosen provider. 'primary' selects patients that the provider is set as primary provider. 'encounter' selectes patients that the provider has seen. This parameter is only applicable if the $provider parameter is set to a provider or collation setting.
453 * @param integer $start applicable patient to start at (when batching process)
454 * @param integer $batchSize number of patients to batch (when batching process)
455 * @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).
456 * @return array See above for organization structure of the results.
458 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='') {
460 // If dateTarget is an array, then organize them.
461 if (is_array($dateTarget)) {
462 $dateArray = $dateTarget;
463 $dateTarget = $dateTarget['dateTarget'];
466 // Set date to current if not set
467 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
469 // Prepare the results array
470 $results = array();
472 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
473 // then run through this function recursively and return results.
474 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
475 // First, collect an array of all providers
476 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
477 $ures = sqlStatementCdrEngine($query);
478 // Second, run through each provider recursively
479 while ($urow = sqlFetchArray($ures)) {
480 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode,$options,$pat_prov_rel,$start,$batchSize,$user);
481 if (!empty($newResults)) {
482 $provider_item['is_provider'] = TRUE;
483 $provider_item['prov_lname'] = $urow['lname'];
484 $provider_item['prov_fname'] = $urow['fname'];
485 $provider_item['npi'] = $urow['npi'];
486 $provider_item['federaltaxid'] = $urow['federaltaxid'];
487 array_push($results,$provider_item);
488 $results = array_merge($results,$newResults);
491 // done, so now can return results
492 return $results;
495 // If set organize-mode to plans, then collects active plans and run through this
496 // function recursively and return results.
497 if ($organize_mode == "plans") {
498 // First, collect active plans
499 $plans_resolve = resolve_plans_sql($plan,$patient_id);
500 // Second, run through function recursively
501 foreach ($plans_resolve as $plan_item) {
502 // (if collate_inner, then nest a collation of providers within each plan)
503 if ($provider == "collate_inner") {
504 // First, collect an array of all providers
505 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
506 $ures = sqlStatementCdrEngine($query);
507 // Second, run through each provider recursively
508 $provider_results = array();
509 while ($urow = sqlFetchArray($ures)) {
510 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize,$user);
511 if (!empty($newResults)) {
512 $provider_item['is_provider'] = TRUE;
513 $provider_item['prov_lname'] = $urow['lname'];
514 $provider_item['prov_fname'] = $urow['fname'];
515 $provider_item['npi'] = $urow['npi'];
516 $provider_item['federaltaxid'] = $urow['federaltaxid'];
517 array_push($provider_results,$provider_item);
518 $provider_results = array_merge($provider_results,$newResults);
521 if (!empty($provider_results)) {
522 $plan_item['is_plan'] = TRUE;
523 array_push($results,$plan_item);
524 $results = array_merge($results,$provider_results);
527 else {
528 // (not collate_inner, so do not nest providers within each plan)
529 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize,$user);
530 if (!empty($newResults)) {
531 $plan_item['is_plan'] = TRUE;
532 array_push($results,$plan_item);
533 $results = array_merge($results,$newResults);
537 // done, so now can return results
538 return $results;
541 // Collect applicable patient pids
542 $patientData = array();
543 $patientData = buildPatientArray($patient_id,$provider,$pat_prov_rel,$start,$batchSize);
545 // Go through each patient(s)
547 // If in report mode, then tabulate for each rule:
548 // Total Patients
549 // Patients that pass the filter
550 // Patients that pass the target
551 // If in reminders mode, then create reminders for each rule:
552 // Reminder that action is due soon
553 // Reminder that action is due
554 // Reminder that action is post-due
556 //Collect applicable rules
557 // Note that due to a limitation in the this function, the patient_id is explicitly
558 // for grouping items when not being done in real-time or for official reporting.
559 // So for cases such as patient reminders on a clinic scale, the calling function
560 // will actually need rather than pass in a explicit patient_id for each patient in
561 // a separate call to this function.
562 if ($mode != "report") {
563 // Use per patient custom rules (if exist)
564 // Note as discussed above, this only works for single patient instances.
565 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan,$user);
567 else { // $mode = "report"
568 // Only use default rules (do not use patient custom rules)
569 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan,$user);
572 foreach( $rules as $rowRule ) {
574 // If using cqm or amc type, then use the hard-coded rules set.
575 // Note these rules are only used in report mode.
576 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
578 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
579 $manager = new ReportManager();
580 if ($rowRule['amc_flag']) {
581 // Send array of dates ('dateBegin' and 'dateTarget')
582 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
584 else {
585 // Send target date
586 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
588 if (!empty($tempResults)) {
589 foreach ($tempResults as $tempResult) {
590 array_push($results,$tempResult);
594 // Go on to the next rule
595 continue;
598 // If in reminder mode then need to collect the measurement dates
599 // from rule_reminder table
600 $target_dates = array();
601 if ($mode != "report") {
602 // Calculate the dates to check for
603 if ($type == "patient_reminder") {
604 $reminder_interval_type = "patient_reminder";
606 else { // $type == "passive_alert" or $type == "active_alert"
607 $reminder_interval_type = "clinical_reminder";
609 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
611 else { // $mode == "report"
612 // Only use the target date in the report
613 $target_dates[0] = $dateTarget;
616 //Reset the counters
617 $total_patients = 0;
618 $pass_filter = 0;
619 $exclude_filter = 0;
620 $pass_target = 0;
622 // Find the number of target groups
623 $targetGroups = returnTargetGroups($rowRule['id']);
625 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
627 // If report itemization is turned on, then iterate the rule id iterator
628 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
629 $GLOBALS['report_itemized_test_id_iterator']++;
632 //skip this section if not report and more than one target group
633 foreach( $patientData as $rowPatient ) {
635 // First, deal with deceased patients
636 // (for now will simply skip the patient)
637 // If want to support rules for deceased patients then will need to migrate this below
638 // in target_dates foreach(guessing won't ever need to do this, though).
639 // Note using the dateTarget rather than dateFocus
640 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
641 continue;
644 // Count the total patients
645 $total_patients++;
647 $dateCounter = 1; // for reminder mode to keep track of which date checking
648 // If report itemization is turned on, reset flag.
649 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
650 $temp_track_pass = 1;
652 foreach ( $target_dates as $dateFocus ) {
654 //Skip if date is set to SKIP
655 if ($dateFocus == "SKIP") {
656 $dateCounter++;
657 continue;
660 //Set date counter and reminder token (applicable for reminders only)
661 if ($dateCounter == 1) {
662 $reminder_due = "soon_due";
664 else if ($dateCounter == 2) {
665 $reminder_due = "due";
667 else { // $dateCounter == 3
668 $reminder_due = "past_due";
671 // Check if pass filter
672 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
673 if ($passFilter === "EXCLUDED") {
674 // increment EXCLUDED and pass_filter counters
675 // and set as FALSE for reminder functionality.
676 $pass_filter++;
677 $exclude_filter++;
678 $passFilter = FALSE;
680 if ($passFilter) {
681 // increment pass filter counter
682 $pass_filter++;
683 // If report itemization is turned on, trigger flag.
684 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
685 $temp_track_pass = 0;
688 else {
689 $dateCounter++;
690 continue;
693 // Check if pass target
694 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
695 if ($passTarget) {
696 // increment pass target counter
697 $pass_target++;
698 // If report itemization is turned on, then record the "passed" item and set the flag
699 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
700 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
701 $temp_track_pass = 1;
703 // send to reminder results
704 if ($mode == "reminders-all") {
705 // place the completed actions into the reminder return array
706 $actionArray = resolve_action_sql($rowRule['id'],'1');
707 foreach ($actionArray as $action) {
708 $action_plus = $action;
709 $action_plus['due_status'] = "not_due";
710 $action_plus['pid'] = $rowPatient['pid'];
711 $action_plus['rule_id'] = $rowRule['id'];
712 $results = reminder_results_integrate($results, $action_plus);
715 break;
717 else {
718 // send to reminder results
719 if ($mode != "report") {
720 // place the uncompleted actions into the reminder return array
721 $actionArray = resolve_action_sql($rowRule['id'],'1');
722 foreach ($actionArray as $action) {
723 $action_plus = $action;
724 $action_plus['due_status'] = $reminder_due;
725 $action_plus['pid'] = $rowPatient['pid'];
726 $action_plus['rule_id'] = $rowRule['id'];
727 $results = reminder_results_integrate($results, $action_plus);
731 $dateCounter++;
733 // If report itemization is turned on, then record the "failed" item if it did not pass
734 if ($GLOBALS['report_itemizing_temp_flag_and_id'] && !($temp_track_pass)) {
735 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
740 // Calculate and save the data for the rule
741 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
742 if ($mode == "report") {
743 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
744 $newRow=array_merge($newRow,$rowRule);
746 // If itemization is turned on, then record the itemized_test_id
747 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
748 $newRow=array_merge($newRow,array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
751 array_push($results, $newRow);
754 // Now run through the target groups if more than one
755 if (count($targetGroups) > 1) {
756 foreach ($targetGroups as $i) {
758 // If report itemization is turned on, then iterate the rule id iterator
759 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
760 $GLOBALS['report_itemized_test_id_iterator']++;
763 //Reset the target counter
764 $pass_target = 0;
766 foreach( $patientData as $rowPatient ) {
768 // First, deal with deceased patients
769 // (for now will simply skip the patient)
770 // If want to support rules for deceased patients then will need to migrate this below
771 // in target_dates foreach(guessing won't ever need to do this, though).
772 // Note using the dateTarget rather than dateFocus
773 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
774 continue;
777 $dateCounter = 1; // for reminder mode to keep track of which date checking
778 // If report itemization is turned on, reset flag.
779 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
780 $temp_track_pass = 1;
782 foreach ( $target_dates as $dateFocus ) {
784 //Skip if date is set to SKIP
785 if ($dateFocus == "SKIP") {
786 $dateCounter++;
787 continue;
790 //Set date counter and reminder token (applicable for reminders only)
791 if ($dateCounter == 1) {
792 $reminder_due = "soon_due";
794 else if ($dateCounter == 2) {
795 $reminder_due = "due";
797 else { // $dateCounter == 3
798 $reminder_due = "past_due";
801 // Check if pass filter
802 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
803 if ($passFilter === "EXCLUDED") {
804 $passFilter = FALSE;
806 if (!$passFilter) {
807 $dateCounter++;
808 continue;
810 else {
811 // If report itemization is turned on, trigger flag.
812 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
813 $temp_track_pass = 0;
817 //Check if pass target
818 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
819 if ($passTarget) {
820 // increment pass target counter
821 $pass_target++;
822 // If report itemization is turned on, then record the "passed" item and set the flag
823 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
824 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 1, $rowPatient['pid']);
825 $temp_track_pass = 1;
827 // send to reminder results
828 if ($mode == "reminders-all") {
829 // place the completed actions into the reminder return array
830 $actionArray = resolve_action_sql($rowRule['id'],$i);
831 foreach ($actionArray as $action) {
832 $action_plus = $action;
833 $action_plus['due_status'] = "not_due";
834 $action_plus['pid'] = $rowPatient['pid'];
835 $action_plus['rule_id'] = $rowRule['id'];
836 $results = reminder_results_integrate($results, $action_plus);
839 break;
841 else {
842 // send to reminder results
843 if ($mode != "report") {
844 // place the actions into the reminder return array
845 $actionArray = resolve_action_sql($rowRule['id'],$i);
846 foreach ($actionArray as $action) {
847 $action_plus = $action;
848 $action_plus['due_status'] = $reminder_due;
849 $action_plus['pid'] = $rowPatient['pid'];
850 $action_plus['rule_id'] = $rowRule['id'];
851 $results = reminder_results_integrate($results, $action_plus);
855 $dateCounter++;
857 // If report itemization is turned on, then record the "failed" item if it did not pass
858 if ($GLOBALS['report_itemizing_temp_flag_and_id'] && !($temp_track_pass)) {
859 insertItemReportTracker($GLOBALS['report_itemizing_temp_flag_and_id'], $GLOBALS['report_itemized_test_id_iterator'], 0, $rowPatient['pid']);
863 // Calculate and save the data for the rule
864 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
866 // Collect action for title (just use the first one, if more than one)
867 $actionArray = resolve_action_sql($rowRule['id'],$i);
868 $action = $actionArray[0];
869 if ($mode == "report") {
870 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
872 // If itemization is turned on, then record the itemized_test_id
873 if ($GLOBALS['report_itemizing_temp_flag_and_id']) {
874 $newRow=array_merge($newRow,array('itemized_test_id'=>$GLOBALS['report_itemized_test_id_iterator']));
877 array_push($results, $newRow);
883 // Return the data
884 return $results;
888 * Process patient array that is to be tested.
890 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
891 * @param integer $patient_id pid of patient. If blank then will check all patients.
892 * @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.
893 * @param integer $start applicable patient to start at (when batching process)
894 * @param integer $batchSize number of patients to batch (when batching process)
895 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
896 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
898 function buildPatientArray($patient_id='',$provider='',$pat_prov_rel='primary',$start=NULL,$batchSize=NULL,$onlyCount=FALSE) {
900 if (!empty($patient_id)) {
901 // only look at the selected patient
902 if ($onlyCount) {
903 $patientNumber = 1;
905 else {
906 $patientData[0]['pid'] = $patient_id;
909 else {
910 if (empty($provider)) {
911 // Look at entire practice
912 if ($start == NULL || $batchSize == NULL || $onlyCount) {
913 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
914 if ($onlyCount) {
915 $patientNumber = sqlNumRows($rez);
918 else {
919 // batching
920 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start-1),$batchSize));
923 else {
924 // Look at an individual physician
925 if( $pat_prov_rel == 'encounter' ){
926 // Choose patients that are related to specific physician by an encounter
927 if ($start == NULL || $batchSize == NULL || $onlyCount) {
928 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
929 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
930 if ($onlyCount) {
931 $patientNumber = sqlNumRows($rez);
934 else {
935 //batching
936 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
937 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start-1),$batchSize));
940 else { //$pat_prov_rel == 'primary'
941 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
942 if ($start == NULL || $batchSize == NULL || $onlyCount) {
943 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
944 "WHERE `providerID`=? ORDER BY `pid`", array($provider) );
945 if ($onlyCount) {
946 $patientNumber = sqlNumRows($rez);
949 else {
950 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
951 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start-1),$batchSize) );
955 // convert the sql query results into an array if returning the array
956 if(!$onlyCount) {
957 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
958 $patientData[$iter]=$row;
963 if ($onlyCount) {
964 // return the number of applicable patients
965 return $patientNumber;
967 else {
968 // return array of patient pids
969 return $patientData;
974 * Test filter of a selected rule on a selected patient
976 * @param integer $patient_id pid of selected patient.
977 * @param string $rule id(string) of selected rule
978 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
979 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
981 function test_filter($patient_id,$rule,$dateTarget) {
983 // Set date to current if not set
984 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
986 // Collect patient information
987 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
990 // ----------------- INCLUSIONS -----------------
993 // -------- Age Filter (inclusion) ------------
994 // Calculate patient age in years and months
995 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
996 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
998 // Min age (year) Filter (assume that there in not more than one of each)
999 $filter = resolve_filter_sql($rule,'filt_age_min');
1000 if (!empty($filter)) {
1001 $row = $filter[0];
1002 if ($row ['method_detail'] == "year") {
1003 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
1004 return false;
1007 if ($row ['method_detail'] == "month") {
1008 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
1009 return false;
1013 // Max age (year) Filter (assume that there in not more than one of each)
1014 $filter = resolve_filter_sql($rule,'filt_age_max');
1015 if (!empty($filter)) {
1016 $row = $filter[0];
1017 if ($row ['method_detail'] == "year") {
1018 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
1019 return false;
1022 if ($row ['method_detail'] == "month") {
1023 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
1024 return false;
1029 // -------- Gender Filter (inclusion) ---------
1030 // Gender Filter (assume that there in not more than one of each)
1031 $filter = resolve_filter_sql($rule,'filt_sex');
1032 if (!empty($filter)) {
1033 $row = $filter[0];
1034 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
1035 return false;
1039 // -------- Database Filter (inclusion) ------
1040 // Database Filter
1041 $filter = resolve_filter_sql($rule,'filt_database');
1042 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
1044 // -------- Lists Filter (inclusion) ----
1045 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
1046 // surgeries and allergies.
1047 $filter = resolve_filter_sql($rule,'filt_lists');
1048 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
1050 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
1051 // Procedure Target (includes) (may need to include an interval in the future)
1052 $filter = resolve_filter_sql($rule,'filt_proc');
1053 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
1056 // ----------------- EXCLUSIONS -----------------
1059 // -------- Lists Filter (EXCLUSION) ----
1060 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
1061 // surgeries and allergies.
1062 $filter = resolve_filter_sql($rule,'filt_lists',0);
1063 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
1065 // Passed all filters, so return true.
1066 return true;
1070 * Return an array containing existing group ids for a rule
1072 * @param string $rule id(string) of rule
1073 * @return array listing of group ids
1075 function returnTargetGroups($rule) {
1077 $sql = sqlStatementCdrEngine("SELECT DISTINCT `group_id` FROM `rule_target` " .
1078 "WHERE `id`=?", array($rule) );
1080 $groups = array();
1081 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1082 array_push($groups,$row['group_id']);
1084 return $groups;
1088 * Test targets of a selected rule on a selected patient
1090 * @param integer $patient_id pid of selected patient.
1091 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
1092 * @param integer $group_id group id of target group
1093 * @param string $dateTarget target date (format Y-m-d H:i:s).
1094 * @return boolean if target passes then true, otherwise false
1096 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
1098 // -------- Interval Target ----
1099 $interval = resolve_target_sql($rule,$group_id,'target_interval');
1101 // -------- Database Target ----
1102 // Database Target (includes)
1103 $target = resolve_target_sql($rule,$group_id,'target_database');
1104 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
1106 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
1107 // Procedure Target (includes)
1108 $target = resolve_target_sql($rule,$group_id,'target_proc');
1109 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
1111 // -------- Appointment Target ----
1112 // Appointment Target (includes) (Specialized functionality for appointment reminders)
1113 $target = resolve_target_sql($rule,$group_id,'target_appt');
1114 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
1116 // Passed all target tests, so return true.
1117 return true;
1121 * Function to return active plans
1123 * @param string $type plan type filter (normal or cqm or blank)
1124 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
1125 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
1126 * @return array active plans
1128 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
1130 if ($configurableOnly) {
1131 // Collect all default, configurable (per patient) plans into an array
1132 // (ie. ignore the cqm rules)
1133 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
1135 else {
1136 // Collect all default plans into an array
1137 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
1139 $returnArray= array();
1140 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1141 array_push($returnArray,$row);
1144 // Now collect the pertinent plans
1145 $newReturnArray = array();
1147 // Need to select rules (use custom if exist)
1148 foreach ($returnArray as $plan) {
1149 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
1151 // Decide if use default vs custom plan (preference given to custom plan)
1152 if (!empty($customPlan)) {
1153 if ($type == "cqm" ) {
1154 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
1155 $goPlan = $plan;
1157 else {
1158 // merge the custom plan with the default plan
1159 $mergedPlan = array();
1160 foreach ($customPlan as $key => $value) {
1161 if ($value == NULL && preg_match("/_flag$/",$key)) {
1162 // use default setting
1163 $mergedPlan[$key] = $plan[$key];
1165 else {
1166 // use custom setting
1167 $mergedPlan[$key] = $value;
1170 $goPlan = $mergedPlan;
1173 else {
1174 $goPlan = $plan;
1177 // Use the chosen plan if set
1178 if (!empty($type)) {
1179 if ($goPlan["${type}_flag"] == 1) {
1180 // active, so use the plan
1181 array_push($newReturnArray,$goPlan);
1184 else {
1185 if ($goPlan['normal_flag'] == 1 ||
1186 $goPlan['cqm_flag'] == 1) {
1187 // active, so use the plan
1188 array_push($newReturnArray,$goPlan);
1192 $returnArray = $newReturnArray;
1194 return $returnArray;
1199 * Function to return a specific plan
1201 * @param string $plan id(string) of plan
1202 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1203 * @return array a plan
1205 function collect_plan($plan,$patient_id='0') {
1207 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
1212 * Function to set a specific plan activity for a specific patient
1214 * @param string $plan id(string) of plan
1215 * @param string $type plan filter (normal,cqm)
1216 * @param string $setting activity of plan (yes,no,default)
1217 * @param integer $patient_id pid of selected patient.
1219 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
1221 // Don't allow messing with the default plans here
1222 if ($patient_id == "0") {
1223 return;
1226 // Convert setting
1227 if ($setting == "on") {
1228 $setting = 1;
1230 else if ($setting == "off") {
1231 $setting = 0;
1233 else { // $setting == "default"
1234 $setting = NULL;
1237 // Collect patient specific plan, if already exists.
1238 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
1239 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id) );
1241 if (empty($patient_plan)) {
1242 // Create a new patient specific plan with flags all set to default
1243 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1244 sqlStatementCdrEngine($query, array($plan, $patient_id) );
1247 // Update patient specific row
1248 $query = "UPDATE `clinical_plans` SET `" . escape_sql_column_name($type."_flag",array("clinical_plans")) . "`= ? WHERE id = ? AND pid = ?";
1249 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id) );
1254 * Function to return active rules
1256 * @param string $type rule filter (active_alert,passive_alert,cqm,cqm_2011,cqm_2014,amc_2011,amc_2014,patient_reminder)
1257 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1258 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1259 * @param string $plan collect rules for specific plan
1260 * @param string $user If a user is set, then will only show rules that user has permission to see
1261 * @return array rules
1263 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='',$user='') {
1265 if ($configurableOnly) {
1266 // Collect all default, configurable (per patient) rules into an array
1267 // (ie. ignore the cqm and amc rules)
1268 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1270 else {
1271 // Collect all default rules into an array
1272 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1274 $returnArray= array();
1275 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1276 array_push($returnArray,$row);
1279 // Now filter rules for plan (if applicable)
1280 if (!empty($plan)) {
1281 $planReturnArray = array();
1282 foreach ($returnArray as $rule) {
1283 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1284 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
1285 if (!empty($standardRule)) {
1286 array_push($planReturnArray,$rule);
1289 $returnArray = $planReturnArray;
1292 // Now collect the pertinent rules
1293 $newReturnArray = array();
1295 // Need to select rules (use custom if exist)
1296 foreach ($returnArray as $rule) {
1298 // If user is set, then check if user has access to the rule
1299 if (!empty($user)) {
1300 $access_control = explode(':',$rule['access_control']);
1301 if ( !empty($access_control[0]) && !empty($access_control[1]) ) {
1302 // Section and ACO filters are not empty, so do the test for access.
1303 if (!acl_check($access_control[0],$access_control[1],$user)) {
1304 // User does not have access to this rule, so skip the rule.
1305 continue;
1310 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
1312 // Decide if use default vs custom rule (preference given to custom rule)
1313 if (!empty($customRule)) {
1314 if ($type == "cqm" || $type == "amc" ) {
1315 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1316 $goRule = $rule;
1318 else {
1319 // merge the custom rule with the default rule
1320 $mergedRule = array();
1321 foreach ($customRule as $key => $value) {
1322 if ($value == NULL && preg_match("/_flag$/",$key)) {
1323 // use default setting
1324 $mergedRule[$key] = $rule[$key];
1326 else {
1327 // use custom setting
1328 $mergedRule[$key] = $value;
1331 $goRule = $mergedRule;
1334 else {
1335 $goRule = $rule;
1338 // Use the chosen rule if set
1339 if (!empty($type)) {
1340 if ($goRule["${type}_flag"] == 1) {
1341 // active, so use the rule
1342 array_push($newReturnArray,$goRule);
1345 else {
1346 // no filter, so return the rule
1347 array_push($newReturnArray,$goRule);
1350 $returnArray = $newReturnArray;
1352 return $returnArray;
1356 * Function to return a specific rule
1358 * @param string $rule id(string) of rule
1359 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1360 * @return array rule
1362 function collect_rule($rule,$patient_id='0') {
1364 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
1369 * Function to set a specific rule activity for a specific patient
1371 * @param string $rule id(string) of rule
1372 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1373 * @param string $setting activity of rule (yes,no,default)
1374 * @param integer $patient_id pid of selected patient.
1376 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
1378 // Don't allow messing with the default rules here
1379 if ($patient_id == "0") {
1380 return;
1383 // Convert setting
1384 if ($setting == "on") {
1385 $setting = 1;
1387 else if ($setting == "off") {
1388 $setting = 0;
1390 else { // $setting == "default"
1391 $setting = NULL;
1394 //Collect main rule to allow setting of the access_control
1395 $original_query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = 0";
1396 $patient_rule_original = sqlQueryCdrEngine($original_query, array($rule) );
1398 // Collect patient specific rule, if already exists.
1399 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1400 $patient_rule = sqlQueryCdrEngine($query, array($rule,$patient_id) );
1402 if (empty($patient_rule)) {
1403 // Create a new patient specific rule with flags all set to default
1404 $query = "INSERT into `clinical_rules` (`id`, `pid`, `access_control`) VALUES (?,?,?)";
1405 sqlStatementCdrEngine($query, array($rule, $patient_id, $patient_rule_original['access_control']) );
1408 // Update patient specific row
1409 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ?, `access_control` = ? WHERE id = ? AND pid = ?";
1410 sqlStatementCdrEngine($query, array($setting,$patient_rule_original['access_control'],$rule,$patient_id) );
1415 * Function to return applicable reminder dates (relative)
1417 * @param string $rule id(string) of selected rule
1418 * @param string $reminder_method string label of filter type
1419 * @return array reminder features
1421 function resolve_reminder_sql($rule,$reminder_method) {
1422 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1423 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
1425 $returnArray= array();
1426 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1427 array_push($returnArray,$row);
1429 return $returnArray;
1433 * Function to return applicable filters
1435 * @param string $rule id(string) of selected rule
1436 * @param string $filter_method string label of filter type
1437 * @param string $include_flag to allow selection for included or excluded filters
1438 * @return array filters
1440 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1441 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1442 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1444 $returnArray= array();
1445 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1446 array_push($returnArray,$row);
1448 return $returnArray;
1452 * Function to return applicable targets
1454 * @param string $rule id(string) of selected rule
1455 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1456 * @param string $target_method string label of target type
1457 * @param string $include_flag to allow selection for included or excluded targets
1458 * @return array targets
1460 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1462 if ($group_id) {
1463 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1464 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1466 else {
1467 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1468 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1471 $returnArray= array();
1472 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1473 array_push($returnArray,$row);
1475 return $returnArray;
1479 * Function to return applicable actions
1481 * @param string $rule id(string) of selected rule
1482 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1483 * @return array actions
1485 function resolve_action_sql($rule,$group_id='') {
1487 if ($group_id) {
1488 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1489 "FROM `rule_action` as a " .
1490 "JOIN `rule_action_item` as b " .
1491 "ON a.category = b.category AND a.item = b.item " .
1492 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1494 else {
1495 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1496 "FROM `rule_action` as a " .
1497 "JOIN `rule_action_item` as b " .
1498 "ON a.category = b.category AND a.item = b.item " .
1499 "WHERE a.id=?", array($rule) );
1502 $returnArray= array();
1503 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1504 array_push($returnArray,$row);
1506 return $returnArray;
1510 * Function to check database filters and targets
1512 * @param string $patient_id pid of selected patient.
1513 * @param array $filter array containing filter/target elements
1514 * @param array $interval array containing interval elements
1515 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1516 * @return boolean true if check passed, otherwise false
1518 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1519 $isMatch = false; //matching flag
1521 // Set date to current if not set
1522 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1524 // Unpackage interval information
1525 // (Assume only one for now and only pertinent for targets)
1526 $intervalType = '';
1527 $intervalValue = '';
1528 if (!empty($interval)) {
1529 $intervalType = $interval[0]['value'];
1530 $intervalValue = $interval[0]['interval'];
1533 foreach( $filter as $row ) {
1534 // Row description
1535 // [0]=>special modes
1536 $temp_df = explode("::",$row['value']);
1538 if ($temp_df[0] == "CUSTOM") {
1539 // Row description
1540 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1541 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1542 // Record the match
1543 $isMatch = true;
1545 else {
1546 // If this is a required entry then return false
1547 if ($row['required_flag']) return false;
1550 else if ($temp_df[0] == "LIFESTYLE") {
1551 // Row description
1552 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1553 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1554 // Record the match
1555 $isMatch = true;
1557 else {
1558 // If this is a required entry then return false
1559 if ($row['required_flag']) return false;
1562 else {
1563 // Default mode
1564 // Row description
1565 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1566 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)) {
1567 // Record the match
1568 $isMatch = true;
1570 else {
1571 // If this is a required entry then return false
1572 if ($row['required_flag']) return false;
1577 // return results of check
1578 return $isMatch;
1582 * Function to check procedure filters and targets
1584 * @param string $patient_id pid of selected patient.
1585 * @param array $filter array containing filter/target elements
1586 * @param array $interval array containing interval elements
1587 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1588 * @return boolean true if check passed, otherwise false
1590 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1591 $isMatch = false; //matching flag
1593 // Set date to current if not set
1594 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1596 // Unpackage interval information
1597 // (Assume only one for now and only pertinent for targets)
1598 $intervalType = '';
1599 $intervalValue = '';
1600 if (!empty($interval)) {
1601 $intervalType = $interval[0]['value'];
1602 $intervalValue = $interval[0]['interval'];
1605 foreach( $filter as $row ) {
1606 // Row description
1607 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1608 // code description
1609 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1610 $temp_df = explode("::",$row['value']);
1611 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)) {
1612 // Record the match
1613 $isMatch = true;
1615 else {
1616 // If this is a required entry then return false
1617 if ($row['required_flag']) return false;
1621 // return results of check
1622 return $isMatch;
1626 * Function to check for appointment
1628 * @todo Complete this to allow appointment reminders.
1629 * @param string $patient_id pid of selected patient.
1630 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1631 * @return boolean true if appt exist, otherwise false
1633 function appointment_check($patient_id,$dateTarget='') {
1634 $isMatch = false; //matching flag
1636 // Set date to current if not set (although should always be set)
1637 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1638 $dateTargetRound = date('Y-m-d',$dateTarget);
1640 // Set current date
1641 $currentDate = date('Y-m-d H:i:s');
1642 $currentDateRound = date('Y-m-d',$dateCurrent);
1644 // Basically, if the appointment is within the current date to the target date,
1645 // then return true. (will not send reminders on same day as appointment)
1646 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1647 "openemr_postcalendar_events.pc_title, " .
1648 "openemr_postcalendar_events.pc_eventDate, " .
1649 "openemr_postcalendar_events.pc_startTime, " .
1650 "openemr_postcalendar_events.pc_endTime " .
1651 "FROM openemr_postcalendar_events " .
1652 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1653 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1654 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1656 // return results of check
1658 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1659 // Plan to send back array of appt info (eid, time, date, etc.)
1660 // to do this.
1661 if (sqlNumRows($sql) > 0) {
1662 $isMatch = true;
1665 return $isMatch;
1669 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1671 * @param string $patient_id pid of selected patient.
1672 * @param array $filter array containing lists filter/target 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 lists_check($patient_id,$filter,$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 foreach ( $filter as $row ) {
1683 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1684 // Record the match
1685 $isMatch = true;
1687 else {
1688 // If this is a required entry then return false
1689 if ($row['required_flag']) return false;
1693 // return results of check
1694 return $isMatch;
1698 * Function to check for existance of data in database for a patient
1700 * @param string $patient_id pid of selected patient.
1701 * @param string $table selected mysql table
1702 * @param string $column selected mysql column
1703 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1704 * @param string $data selected data in the mysql database
1705 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1706 * @param integer $num_items_thres number of items threshold
1707 * @param string $intervalType type of interval (ie. year)
1708 * @param integer $intervalValue searched for within this many times of the interval type
1709 * @param string $dateTarget target date(format Y-m-d H:i:s).
1710 * @return boolean true if check passed, otherwise false
1712 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1714 // Set date to current if not set
1715 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1717 // Collect the correct column label for patient id in the table
1718 $patient_id_label = collect_database_label('pid',$table);
1720 // Get the interval sql query string
1721 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1723 // If just checking for existence (ie. data is empty),
1724 // then simply set the comparison operator to ne.
1725 if (empty($data)) {
1726 $data_comp = "ne";
1729 // get the appropriate sql comparison operator
1730 $compSql = convertCompSql($data_comp);
1732 // custom issues per table can be placed here
1733 $customSQL = '';
1734 if ($table == 'immunizations') {
1735 $customSQL = " AND `added_erroneously` = '0' ";
1738 // check for items
1739 if (empty($column)) {
1740 // simple search for any table entries
1741 $sql = sqlStatementCdrEngine("SELECT * " .
1742 "FROM `" . add_escape_custom($table) . "` " .
1743 "WHERE `" . add_escape_custom($patient_id_label) . "`=? " . $customSQL, array($patient_id) );
1745 else {
1746 // search for number of specific items
1747 $sql = sqlStatementCdrEngine("SELECT `" . add_escape_custom($column) . "` " .
1748 "FROM `" . add_escape_custom($table) . "` " .
1749 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1750 "AND `" . add_escape_custom($patient_id_label) . "`=? " . $customSQL .
1751 $dateSql, array($data,$patient_id) );
1754 // See if number of returned items passes the comparison
1755 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1759 * Function to check for existence of procedure(s) for a patient
1761 * @param string $patient_id pid of selected patient.
1762 * @param string $proc_title procedure title
1763 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1764 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1765 * @param string $result_data results data
1766 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1767 * @param integer $num_items_thres number of items threshold
1768 * @param string $intervalType type of interval (ie. year)
1769 * @param integer $intervalValue searched for within this many times of the interval type
1770 * @param string $dateTarget target date(format Y-m-d H:i:s).
1771 * @return boolean true if check passed, otherwise false
1773 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1775 // Set date to current if not set
1776 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1778 // Set the table exception (for looking up pertinent date and pid sql columns)
1779 $table = "PROCEDURE-EXCEPTION";
1781 // Collect the correct column label for patient id in the table
1782 $patient_id_label = collect_database_label('pid',$table);
1784 // Get the interval sql query string
1785 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1787 // If just checking for existence (ie result_data is empty),
1788 // then simply set the comparison operator to ne.
1789 if (empty($result_data)) {
1790 $result_comp = "ne";
1793 // get the appropriate sql comparison operator
1794 $compSql = convertCompSql($result_comp);
1796 // explode the code array
1797 $codes= array();
1798 if (!empty($proc_code)) {
1799 $codes = explode("||",$proc_code);
1801 else {
1802 $codes[0] = '';
1805 // ensure proc_title is at least blank
1806 if (empty($proc_title)) {
1807 $proc_title = '';
1810 // collect specific items (use both title and/or codes) that fulfill request
1811 $sqlBindArray=array();
1812 $sql_query = "SELECT procedure_result.result FROM " .
1813 "procedure_order_code, " .
1814 "procedure_order, " .
1815 "procedure_type, " .
1816 "procedure_report, " .
1817 "procedure_result " .
1818 "WHERE " .
1819 "procedure_order_code.procedure_code = procedure_type.procedure_code AND " .
1820 "procedure_order.procedure_order_id = procedure_order_code.procedure_order_id AND " .
1821 "procedure_order.lab_id = procedure_type.lab_id AND " .
1822 "procedure_report.procedure_order_id = procedure_order.procedure_order_id AND " .
1823 "procedure_report.procedure_order_seq = procedure_order_code.procedure_order_seq AND " .
1824 "procedure_result.procedure_report_id = procedure_report.procedure_report_id AND " .
1825 "procedure_type.procedure_type = 'ord' AND ";
1826 foreach ($codes as $tem) {
1827 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
1828 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
1829 array_push($sqlBindArray,$tem,$tem);
1831 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
1832 "AND procedure_result.result " . $compSql . " ? " .
1833 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
1834 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
1836 $sql = sqlStatementCdrEngine($sql_query,$sqlBindArray);
1838 // See if number of returned items passes the comparison
1839 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1843 * Function to check for existance of data for a patient in the rule_patient_data table
1845 * @param string $patient_id pid of selected patient.
1846 * @param string $category label in category column
1847 * @param string $item label in item column
1848 * @param string $complete label in complete column (YES,NO, or blank)
1849 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1850 * @param integer $num_items_thres number of items threshold
1851 * @param string $intervalType type of interval (ie. year)
1852 * @param integer $intervalValue searched for within this many times of the interval type
1853 * @param string $dateTarget target date(format Y-m-d H:i:s).
1854 * @return boolean true if check passed, otherwise false
1856 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1858 // Set the table
1859 $table = 'rule_patient_data';
1861 // Collect the correct column label for patient id in the table
1862 $patient_id_label = collect_database_label('pid',$table);
1864 // Get the interval sql query string
1865 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1867 // search for number of specific items
1868 $sql = sqlStatementCdrEngine("SELECT `result` " .
1869 "FROM `" . add_escape_custom($table) . "` " .
1870 "WHERE `category`=? " .
1871 "AND `item`=? " .
1872 "AND `complete`=? " .
1873 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1874 $dateSql, array($category,$item,$complete,$patient_id) );
1876 // See if number of returned items passes the comparison
1877 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1881 * Function to check for existance of data for a patient in lifestyle section
1883 * @param string $patient_id pid of selected patient.
1884 * @param string $lifestyle selected label of mysql column of patient history
1885 * @param string $status specific status of selected lifestyle element
1886 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1887 * @return boolean true if check passed, otherwise false
1889 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1891 // Set date to current if not set
1892 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1894 // Collect pertinent history data
1895 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1897 // See if match
1898 $stringFlag = strstr($history[$lifestyle], "|".$status);
1899 if (empty($status)) {
1900 // Only ensuring any data has been entered into the field
1901 $stringFlag = true;
1903 if ( $history[$lifestyle] &&
1904 $history[$lifestyle] != '|0|' &&
1905 $stringFlag ) {
1906 return true;
1908 else {
1909 return false;
1914 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
1915 * allergies, and surgeries.
1917 * @param string $patient_id pid of selected patient.
1918 * @param string $type type (medical_problem, allergy, medication, etc)
1919 * @param string $value value searching for
1920 * @param string $dateTarget target date(format Y-m-d H:i:s).
1921 * @return boolean true if check passed, otherwise false
1923 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1925 // Set date to current if not set
1926 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1928 // Attempt to explode the value into a code type and code (if applicable)
1929 $value_array = explode("::",$value);
1930 if (count($value_array) == 2) {
1932 // Collect the code type and code
1933 $code_type = $value_array[0];
1934 $code = $value_array[1];
1936 if ($code_type=='CUSTOM') {
1937 // Deal with custom code type first (title column in lists table)
1938 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1939 "WHERE `type`=? " .
1940 "AND `pid`=? " .
1941 "AND `title`=? " .
1942 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1943 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
1944 if (!empty($response)) return true;
1946 else {
1947 // Deal with the set code types (diagnosis column in lists table)
1948 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1949 "WHERE `type`=? " .
1950 "AND `pid`=? " .
1951 "AND `diagnosis` LIKE ? " .
1952 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1953 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
1954 if (!empty($response)) return true;
1957 else { // count($value_array) == 1
1958 // Search the title column in lists table
1959 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
1960 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1961 "WHERE `type`=? " .
1962 "AND `pid`=? " .
1963 "AND `title`=? ".
1964 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1965 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1966 if (!empty($response)) return true;
1969 return false;
1973 * Function to return part of sql query to deal with interval
1975 * @param string $table selected mysql table (or EXCEPTION(s))
1976 * @param string $intervalType type of interval (ie. year)
1977 * @param string $intervalValue searched for within this many times of the interval type
1978 * @param string $dateTarget target date(format Y-m-d H:i:s).
1979 * @return string contains pertinent date interval filter for mysql query
1981 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1983 $dateSql="";
1985 // Collect the correct column label for date in the table
1986 $date_label = collect_database_label('date',$table);
1988 // Deal with interval
1989 if (!empty($intervalType)) {
1990 switch($intervalType) {
1991 case "year":
1992 $dateSql = "AND (" . add_escape_custom($date_label) .
1993 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1994 "', INTERVAL " . add_escape_custom($intervalValue) .
1995 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1996 break;
1997 case "month":
1998 $dateSql = "AND (" . add_escape_custom($date_label) .
1999 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2000 "', INTERVAL " . add_escape_custom($intervalValue) .
2001 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
2002 break;
2003 case "week":
2004 $dateSql = "AND (" . add_escape_custom($date_label) .
2005 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2006 "', INTERVAL " . add_escape_custom($intervalValue) .
2007 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
2008 break;
2009 case "day":
2010 $dateSql = "AND (" . add_escape_custom($date_label) .
2011 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2012 "', INTERVAL " . add_escape_custom($intervalValue) .
2013 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
2014 break;
2015 case "hour":
2016 $dateSql = "AND (" . add_escape_custom($date_label) .
2017 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2018 "', INTERVAL " . add_escape_custom($intervalValue) .
2019 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
2020 break;
2021 case "minute":
2022 $dateSql = "AND (" . add_escape_custom($date_label) .
2023 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2024 "', INTERVAL " . add_escape_custom($intervalValue) .
2025 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
2026 break;
2027 case "second":
2028 $dateSql = "AND (" . add_escape_custom($date_label) .
2029 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
2030 "', INTERVAL " . add_escape_custom($intervalValue) .
2031 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
2032 break;
2033 case "flu_season":
2034 // Flu season to be hard-coded as September thru February
2035 // (Should make this modifiable in the future)
2036 // ($intervalValue is not used)
2037 $dateArray = explode("-",$dateTarget);
2038 $Year = $dateArray[0];
2039 $dateThisYear = $Year . "-09-01";
2040 $dateLastYear = ($Year-1) . "-09-01";
2041 $dateSql =" " .
2042 "AND ((" .
2043 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
2044 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
2045 "OR (" .
2046 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
2047 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
2048 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
2049 break;
2052 else {
2053 $dateSql = "AND " . add_escape_custom($date_label) .
2054 " <= '" . add_escape_custom($dateTarget) . "' ";
2057 // return the sql interval string
2058 return $dateSql;
2062 * Function to collect generic column labels from tables. It currently works for date
2063 * and pid. Will need to expand this as algorithm grows.
2065 * @param string $label element (pid or date)
2066 * @param string $table selected mysql table (or EXCEPTION(s))
2067 * @return string contains official label of selected element
2069 function collect_database_label($label,$table) {
2071 if ($table == 'PROCEDURE-EXCEPTION') {
2072 // return cell to get procedure collection
2073 // special case since reuqires joing of multiple
2074 // tables to get this value
2075 if ($label == "pid") {
2076 $returnedLabel = "procedure_order.patient_id";
2078 else if ($label == "date") {
2079 $returnedLabel = "procedure_report.date_collected";
2081 else {
2082 // unknown label, so return the original label
2083 $returnedLabel = $label;
2086 else if ($table == 'immunizations') {
2087 // return requested label for immunization table
2088 if ($label == "pid") {
2089 $returnedLabel = "patient_id";
2091 else if ($label == "date") {
2092 $returnedLabel = "`administered_date`";
2094 else {
2095 // unknown label, so return the original label
2096 $returnedLabel = $label;
2099 else {
2100 // return requested label for default tables
2101 if ($label == "pid") {
2102 $returnedLabel = "pid";
2104 else if ($label == "date") {
2105 $returnedLabel = "`date`";
2107 else {
2108 // unknown label, so return the original label
2109 $returnedLabel = $label;
2113 return $returnedLabel;
2117 * Simple function to avoid processing of duplicate actions
2119 * @param string $actions 2-dimensional array with all current active targets
2120 * @param string $action array of selected target to test for duplicate
2121 * @return boolean true if duplicate, false if not duplicate
2123 function is_duplicate_action($actions,$action) {
2124 foreach ($actions as $row) {
2125 if ($row['category'] == $action['category'] &&
2126 $row['item'] == $action['item'] &&
2127 $row['value'] == $action['value']) {
2128 // Is a duplicate
2129 return true;
2133 // Not a duplicate
2134 return false;
2138 * Calculate the reminder dates.
2140 * This function returns an array that contains three elements (each element is a date).
2141 * <pre>The three dates are:
2142 * first date is before the target date (past_due) (default of 1 month)
2143 * second date is the target date (due)
2144 * third date is after the target date (soon_due) (default of 2 weeks)
2145 * </pre>
2147 * @param string $rule id(string) of selected rule
2148 * @param string $dateTarget target date(format Y-m-d H:i:s).
2149 * @param string $type either 'patient_reminder' or 'clinical_reminder'
2150 * @return array see above for description of returned array
2152 function calculate_reminder_dates($rule, $dateTarget='',$type) {
2154 // Set date to current if not set
2155 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
2157 // Collect the current date settings (to ensure not skip)
2158 $res = resolve_reminder_sql($rule, $type.'_current');
2159 if (!empty($res)) {
2160 $row = $res[0];
2161 if ($row ['method_detail'] == "SKIP") {
2162 $dateTarget = "SKIP";
2166 // Collect the past_due date
2167 $past_due_date = "";
2168 $res = resolve_reminder_sql($rule, $type.'_post');
2169 if (!empty($res)) {
2170 $row = $res[0];
2171 if ($row ['method_detail'] == "week") {
2172 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
2174 if ($row ['method_detail'] == "month") {
2175 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
2177 if ($row ['method_detail'] == "hour") {
2178 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2180 if ($row ['method_detail'] == "SKIP") {
2181 $past_due_date = "SKIP";
2184 else {
2185 // empty settings, so use default of one month
2186 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
2189 // Collect the soon_due date
2190 $soon_due_date = "";
2191 $res = resolve_reminder_sql($rule, $type.'_pre');
2192 if (!empty($res)) {
2193 $row = $res[0];
2194 if ($row ['method_detail'] == "week") {
2195 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
2197 if ($row ['method_detail'] == "month") {
2198 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
2200 if ($row ['method_detail'] == "hour") {
2201 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
2203 if ($row ['method_detail'] == "SKIP") {
2204 $soon_due_date = "SKIP";
2207 else {
2208 // empty settings, so use default of one month
2209 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
2212 // Return the array of three dates
2213 return array($soon_due_date,$dateTarget,$past_due_date);
2217 * Adds an action into the reminder array
2219 * @param array $reminderOldArray Contains the current array of reminders
2220 * @param array $reminderNew Array of a new reminder
2221 * @return array Reminders
2223 function reminder_results_integrate($reminderOldArray, $reminderNew) {
2225 $results = array();
2227 // If reminderArray is empty, then insert new reminder
2228 if (empty($reminderOldArray)) {
2229 array_push($results, $reminderNew);
2230 return $results;
2233 // If duplicate reminder, then replace the old one
2234 $duplicate = false;
2235 foreach ($reminderOldArray as $reminderOld) {
2236 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
2237 $reminderOld['category'] == $reminderNew['category'] &&
2238 $reminderOld['item'] == $reminderNew['item']) {
2239 array_push($results, $reminderNew);
2240 $duplicate = true;
2242 else {
2243 array_push($results, $reminderOld);
2247 // If a new reminder, then insert the new reminder
2248 if (!$duplicate) {
2249 array_push($results, $reminderNew);
2252 return $results;
2256 * Compares number of items with requested comparison operator
2258 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2259 * @param string $thres Threshold used in comparison
2260 * @param integer $num_items Number of items
2261 * @return boolean Comparison results
2263 function itemsNumberCompare($comp, $thres, $num_items) {
2265 if ( ($comp == "eq") && ($num_items == $thres) ) {
2266 return true;
2268 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
2269 return true;
2271 else if ( ($comp == "gt") && ($num_items > $thres) ) {
2272 return true;
2274 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
2275 return true;
2277 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
2278 return true;
2280 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
2281 return true;
2283 else {
2284 return false;
2289 * Converts a text comparison operator to sql equivalent
2291 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2292 * @return string contains sql compatible comparison operator
2294 function convertCompSql($comp) {
2296 if ($comp == "eq") {
2297 return "=";
2299 else if ($comp == "ne") {
2300 return "!=";
2302 else if ($comp == "gt") {
2303 return ">";
2305 else if ($comp == "ge") {
2306 return ">=";
2308 else if ($comp == "lt") {
2309 return "<";
2311 else { // ($comp == "le")
2312 return "<=";
2318 * Function to find age in years (with decimal) on the target date
2320 * @param string $dob date of birth
2321 * @param string $target date to calculate age on
2322 * @return float years(decimal) from dob to target(date)
2324 function convertDobtoAgeYearDecimal($dob,$target) {
2325 $ageInfo=parseAgeInfo($dob,$target);
2326 return $ageInfo['age'];
2330 * Function to find age in months (with decimal) on the target date
2332 * @param string $dob date of birth
2333 * @param string $target date to calculate age on
2334 * @return float months(decimal) from dob to target(date)
2336 function convertDobtoAgeMonthDecimal($dob,$target) {
2337 $ageInfo=parseAgeInfo($dob,$target);
2338 return $ageInfo['age_in_months'];
2342 * Function to calculate the percentage for reports.
2344 * @param integer $pass_filter number of patients that pass filter
2345 * @param integer $exclude_filter number of patients that are excluded
2346 * @param integer $pass_target number of patients that pass target
2347 * @return string Number formatted into a percentage
2349 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
2350 if ($pass_filt > 0) {
2351 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
2353 else {
2354 $perc = "0". xl('%');
2356 return $perc;