Improved performance and memory management of CDR engine.
[openemr.git] / library / clinical_rules.php
blob0703137eda82127f8bf39bc5bad6d74764d548a9
1 <?php
2 /**
3 * Clinical Decision Rules(CDR) engine functions.
5 * Copyright (C) 2010-2012 Brady Miller <brady@sparmy.com>
6 * Copyright (C) 2011 Medical Information Integration, LLC
7 * Copyright (C) 2011 Ensofttek, LLC
9 * LICENSE: This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
20 * @package OpenEMR
21 * @author Brady Miller <brady@sparmy.com>
22 * @author Medical Information Integration, LLC
23 * @author Ensofttek, LLC
24 * @link http://www.open-emr.org
27 require_once(dirname(__FILE__) . "/patient.inc");
28 require_once(dirname(__FILE__) . "/forms.inc");
29 require_once(dirname(__FILE__) . "/formdata.inc.php");
30 require_once(dirname(__FILE__) . "/options.inc.php");
32 /**
33 * Display the clinical summary widget.
35 * @param integer $patient_id pid of selected patient
36 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
37 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
38 * @param string $organize_mode Way to organize the results (default or plans)
40 function clinical_summary_widget($patient_id,$mode,$dateTarget='',$organize_mode='default') {
42 // Set date to current if not set
43 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
45 // Collect active actions
46 $actions = test_rules_clinic('','passive_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
48 // Display the actions
49 foreach ($actions as $action) {
51 // Deal with plan names first
52 if (isset($action['is_plan']) &&$action['is_plan']) {
53 echo "<br><b>";
54 echo htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
55 echo generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
56 echo "</b><br>";
57 continue;
60 if ($action['custom_flag']) {
61 // Start link for reminders that use the custom rules input screen
62 echo "<a href='../rules/patient_data.php?category=" .
63 htmlspecialchars( $action['category'], ENT_QUOTES) . "&item=" .
64 htmlspecialchars( $action['item'], ENT_QUOTES) .
65 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
67 else if ($action['clin_rem_link']) {
68 // Start link for reminders that use the custom rules input screen
69 echo "<a href='../../../" . $action['reminder_message'] .
70 "' class='iframe medium_modal' onclick='top.restoreSession()'>";
72 else {
73 // continue, since no link will be created
76 // Display Reminder Details
77 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
78 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
80 if ($action['custom_flag'] || $action['clin_rem_link']) {
81 // End link for reminders that use an html link
82 echo "</a>";
85 // Display due status
86 if ($action['due_status']) {
87 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
88 if ($action['due_status'] == "past_due") {
89 echo "&nbsp;&nbsp;(<span style='color:red'>";
91 else if ($action['due_status'] == "due") {
92 echo "&nbsp;&nbsp;(<span style='color:purple'>";
94 else if ($action['due_status'] == "not_due") {
95 echo "&nbsp;&nbsp;(<span style='color:green'>";
97 else {
98 echo "&nbsp;&nbsp;(<span>";
100 echo generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
102 else {
103 echo "<br>";
110 * Display the active screen reminder.
112 * @param integer $patient_id pid of selected patient
113 * @param string $mode choose either 'reminders-all' or 'reminders-due' (required)
114 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
115 * @param string $organize_mode Way to organize the results (default or plans)
116 * @return string html display output.
118 function active_alert_summary($patient_id,$mode,$dateTarget='',$organize_mode='default') {
120 // Set date to current if not set
121 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
123 // Collect active actions
124 $actions = test_rules_clinic('','active_alert',$dateTarget,$mode,$patient_id,'',$organize_mode);
126 if (empty($actions)) {
127 return false;
130 $returnOutput = "";
132 // Display the actions
133 foreach ($actions as $action) {
135 // Deal with plan names first
136 if ($action['is_plan']) {
137 $returnOutput .= "<br><b>";
138 $returnOutput .= htmlspecialchars( xl("Plan"), ENT_NOQUOTES) . ": ";
139 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'clinical_plans'),$action['id']);
140 $returnOutput .= "</b><br>";
141 continue;
144 // Display Reminder Details
145 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_action_category'),$action['category']) .
146 ": " . generate_display_field(array('data_type'=>'1','list_id'=>'rule_action'),$action['item']);
148 // Display due status
149 if ($action['due_status']) {
150 // Color code the status (red for past due, purple for due, green for not due and black for soon due)
151 if ($action['due_status'] == "past_due") {
152 $returnOutput .= "&nbsp;&nbsp;(<span style='color:red'>";
154 else if ($action['due_status'] == "due") {
155 $returnOutput .= "&nbsp;&nbsp;(<span style='color:purple'>";
157 else if ($action['due_status'] == "not_due") {
158 $returnOutput .= "&nbsp;&nbsp;(<span style='color:green'>";
160 else {
161 $returnOutput .= "&nbsp;&nbsp;(<span>";
163 $returnOutput .= generate_display_field(array('data_type'=>'1','list_id'=>'rule_reminder_due_opt'),$action['due_status']) . "</span>)<br>";
165 else {
166 $returnOutput .= "<br>";
169 return $returnOutput;
173 * Process clinic rules via a batching method to improve performance and decrease memory overhead.
175 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
176 * on one patient or patients of one provider). The structure of the returned results is dependent on the
177 * $organize_mode and $mode parameters.
178 * <pre>The results are dependent on the $organize_mode parameter settings
179 * 'default' organize_mode:
180 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
181 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
182 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
183 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
184 * 'plans' organize_mode:
185 * Returns similar to default, but organizes by the active plans
186 * </pre>
188 * @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).
189 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
190 * @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').
191 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
192 * @param string $plan test for specific plan only
193 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
194 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
195 * @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.
196 * @param integer $batchSize number of patients to batch (default is 100; plan to optimize this default setting in the future)
197 * @return array See above for organization structure of the results.
199 function test_rules_clinic_batch_method($provider='',$type='',$dateTarget='',$mode='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$batchSize=100) {
201 // Collect total number of pertinent patients (to calculate batching parameters)
202 $totalNumPatients = buildPatientArray('',$provider,$pat_prov_rel,NULL,NULL,TRUE);
204 // Cycle through the batches and collect/combine results
205 if (($totalNumPatients%$batchSize) > 0) {
206 // not perfectly divisible
207 $totalNumberBatches = floor($totalNumPatients/$batchSize) + 1;
209 else {
210 // perfectly divisible
211 $totalNumberBatches = floor($totalNumPatients/$batchSize);
213 for ($i=0;$i<$totalNumberBatches;$i++) {
214 $dataSheet_batch = test_rules_clinic($provider,$type,$dateTarget,$mode,'',$plan,$organize_mode,$options,$pat_prov_rel,(($batchSize*$i)+1),$batchSize);
215 if ($i == 0) {
216 // For first cycle, simply copy it to dataSheet
217 $dataSheet = $dataSheet_batch;
219 else {
220 //debug
221 //error_log("CDR: ".print_r($dataSheet,TRUE),0);
222 //error_log("CDR: ".($batchSize*$i)." records",0);
224 // Integrate batch results into main dataSheet
225 foreach ($dataSheet_batch as $key => $row) {
226 if (!$row['is_sub']) {
227 //skip this stuff for the sub entries (and use previous main entry in percentage calculation)
228 $total_patients = $dataSheet[$key]['total_patients'] + $row['total_patients'];
229 $dataSheet[$key]['total_patients'] = $total_patients;
230 $excluded = $dataSheet[$key]['excluded'] + $row['excluded'];
231 $dataSheet[$key]['excluded'] = $excluded;
232 $pass_filter = $dataSheet[$key]['pass_filter'] + $row['pass_filter'];
233 $dataSheet[$key]['pass_filter'] = $pass_filter;
235 $pass_target = $dataSheet[$key]['pass_target'] + $row['pass_target'];
236 $dataSheet[$key]['pass_target'] = $pass_target;
237 $dataSheet[$key]['percentage'] = calculate_percentage($pass_filter,$excluded,$pass_target);
242 return $dataSheet;
246 * Process clinic rules.
248 * Test the clinic rules of entire clinic and create a report or patient reminders (can also test
249 * on one patient or patients of one provider). The structure of the returned results is dependent on the
250 * $organize_mode and $mode parameters.
251 * <pre>The results are dependent on the $organize_mode parameter settings
252 * 'default' organize_mode:
253 * Returns a two-dimensional array of results organized by rules (dependent on the following $mode settings):
254 * 'reminders-due' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
255 * 'reminders-all' mode - returns an array of reminders (action array elements plus a 'pid' and 'due_status')
256 * 'report' mode - returns an array of rows for the Clinical Quality Measures (CQM) report
257 * 'plans' organize_mode:
258 * Returns similar to default, but organizes by the active plans
259 * </pre>
261 * @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).
262 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder). If blank then will test all rules.
263 * @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').
264 * @param string $mode choose either 'report' or 'reminders-all' or 'reminders-due' (required)
265 * @param integer $patient_id pid of patient. If blank then will check all patients.
266 * @param string $plan test for specific plan only
267 * @param string $organize_mode Way to organize the results (default, plans). See above for organization structure of the results.
268 * @param array $options can hold various option (for now, used to hold the manual number of labs for the AMC report)
269 * @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.
270 * @param integer $start applicable patient to start at (when batching process)
271 * @param integer $batchSize number of patients to batch (when batching process)
272 * @return array See above for organization structure of the results.
274 function test_rules_clinic($provider='',$type='',$dateTarget='',$mode='',$patient_id='',$plan='',$organize_mode='default',$options=array(),$pat_prov_rel='primary',$start=NULL,$batchSize=NULL) {
276 // If dateTarget is an array, then organize them.
277 if (is_array($dateTarget)) {
278 $dateArray = $dateTarget;
279 $dateTarget = $dateTarget['dateTarget'];
282 // Set date to current if not set
283 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
285 // Prepare the results array
286 $results = array();
288 // If set the $provider to collate_outer (or collate_inner without plans organize mode),
289 // then run through this function recursively and return results.
290 if (($provider == "collate_outer") || ($provider == "collate_inner" && $organize_mode != 'plans')) {
291 // First, collect an array of all providers
292 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
293 $ures = sqlStatementCdrEngine($query);
294 // Second, run through each provider recursively
295 while ($urow = sqlFetchArray($ures)) {
296 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan,$organize_mode,$options,$pat_prov_rel,$start,$batchSize);
297 if (!empty($newResults)) {
298 $provider_item['is_provider'] = TRUE;
299 $provider_item['prov_lname'] = $urow['lname'];
300 $provider_item['prov_fname'] = $urow['fname'];
301 $provider_item['npi'] = $urow['npi'];
302 $provider_item['federaltaxid'] = $urow['federaltaxid'];
303 array_push($results,$provider_item);
304 $results = array_merge($results,$newResults);
307 // done, so now can return results
308 return $results;
311 // If set organize-mode to plans, then collects active plans and run through this
312 // function recursively and return results.
313 if ($organize_mode == "plans") {
314 // First, collect active plans
315 $plans_resolve = resolve_plans_sql($plan,$patient_id);
316 // Second, run through function recursively
317 foreach ($plans_resolve as $plan_item) {
318 // (if collate_inner, then nest a collation of providers within each plan)
319 if ($provider == "collate_inner") {
320 // First, collect an array of all providers
321 $query = "SELECT id, lname, fname, npi, federaltaxid FROM users WHERE authorized = 1 ORDER BY lname, fname";
322 $ures = sqlStatementCdrEngine($query);
323 // Second, run through each provider recursively
324 $provider_results = array();
325 while ($urow = sqlFetchArray($ures)) {
326 $newResults = test_rules_clinic($urow['id'],$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize);
327 if (!empty($newResults)) {
328 $provider_item['is_provider'] = TRUE;
329 $provider_item['prov_lname'] = $urow['lname'];
330 $provider_item['prov_fname'] = $urow['fname'];
331 $provider_item['npi'] = $urow['npi'];
332 $provider_item['federaltaxid'] = $urow['federaltaxid'];
333 array_push($provider_results,$provider_item);
334 $provider_results = array_merge($provider_results,$newResults);
337 if (!empty($provider_results)) {
338 $plan_item['is_plan'] = TRUE;
339 array_push($results,$plan_item);
340 $results = array_merge($results,$provider_results);
343 else {
344 // (not collate_inner, so do not nest providers within each plan)
345 $newResults = test_rules_clinic($provider,$type,$dateTarget,$mode,$patient_id,$plan_item['id'],'default',$options,$pat_prov_rel,$start,$batchSize);
346 if (!empty($newResults)) {
347 $plan_item['is_plan'] = TRUE;
348 array_push($results,$plan_item);
349 $results = array_merge($results,$newResults);
353 // done, so now can return results
354 return $results;
357 // Collect applicable patient pids
358 $patientData = array();
359 $patientData = buildPatientArray($patient_id,$provider,$pat_prov_rel,$start,$batchSize);
361 // Go through each patient(s)
363 // If in report mode, then tabulate for each rule:
364 // Total Patients
365 // Patients that pass the filter
366 // Patients that pass the target
367 // If in reminders mode, then create reminders for each rule:
368 // Reminder that action is due soon
369 // Reminder that action is due
370 // Reminder that action is post-due
372 //Collect applicable rules
373 // Note that due to a limitation in the this function, the patient_id is explicitly
374 // for grouping items when not being done in real-time or for official reporting.
375 // So for cases such as patient reminders on a clinic scale, the calling function
376 // will actually need rather than pass in a explicit patient_id for each patient in
377 // a separate call to this function.
378 if ($mode != "report") {
379 // Use per patient custom rules (if exist)
380 // Note as discussed above, this only works for single patient instances.
381 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
383 else { // $mode = "report"
384 // Only use default rules (do not use patient custom rules)
385 $rules = resolve_rules_sql($type,$patient_id,FALSE,$plan);
388 foreach( $rules as $rowRule ) {
390 // If using cqm or amc type, then use the hard-coded rules set.
391 // Note these rules are only used in report mode.
392 if ($rowRule['cqm_flag'] || $rowRule['amc_flag']) {
394 require_once( dirname(__FILE__)."/classes/rulesets/ReportManager.php");
395 $manager = new ReportManager();
396 if ($rowRule['amc_flag']) {
397 // Send array of dates ('dateBegin' and 'dateTarget')
398 $tempResults = $manager->runReport( $rowRule, $patientData, $dateArray, $options );
400 else {
401 // Send target date
402 $tempResults = $manager->runReport( $rowRule, $patientData, $dateTarget );
404 if (!empty($tempResults)) {
405 foreach ($tempResults as $tempResult) {
406 array_push($results,$tempResult);
410 // Go on to the next rule
411 continue;
414 // If in reminder mode then need to collect the measurement dates
415 // from rule_reminder table
416 $target_dates = array();
417 if ($mode != "report") {
418 // Calculate the dates to check for
419 if ($type == "patient_reminder") {
420 $reminder_interval_type = "patient_reminder";
422 else { // $type == "passive_alert" or $type == "active_alert"
423 $reminder_interval_type = "clinical_reminder";
425 $target_dates = calculate_reminder_dates($rowRule['id'], $dateTarget, $reminder_interval_type);
427 else { // $mode == "report"
428 // Only use the target date in the report
429 $target_dates[0] = $dateTarget;
432 //Reset the counters
433 $total_patients = 0;
434 $pass_filter = 0;
435 $exclude_filter = 0;
436 $pass_target = 0;
438 // Find the number of target groups
439 $targetGroups = returnTargetGroups($rowRule['id']);
441 if ( (count($targetGroups) == 1) || ($mode == "report") ) {
442 //skip this section if not report and more than one target group
443 foreach( $patientData as $rowPatient ) {
445 // Count the total patients
446 $total_patients++;
448 $dateCounter = 1; // for reminder mode to keep track of which date checking
449 foreach ( $target_dates as $dateFocus ) {
451 //Skip if date is set to SKIP
452 if ($dateFocus == "SKIP") {
453 $dateCounter++;
454 continue;
457 //Set date counter and reminder token (applicable for reminders only)
458 if ($dateCounter == 1) {
459 $reminder_due = "soon_due";
461 else if ($dateCounter == 2) {
462 $reminder_due = "due";
464 else { // $dateCounter == 3
465 $reminder_due = "past_due";
468 // First, deal with deceased patients
469 // (for now will simply not pass the filter, but can add a database item
470 // if ever want to create rules for dead people)
471 // Could also place this function at the total_patients level if wanted.
472 // (But then would lose the option of making rules for dead people)
473 // Note using the dateTarget rather than dateFocus
474 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
475 continue;
478 // Check if pass filter
479 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
480 if ($passFilter === "EXCLUDED") {
481 // increment EXCLUDED and pass_filter counters
482 // and set as FALSE for reminder functionality.
483 $pass_filter++;
484 $exclude_filter++;
485 $passFilter = FALSE;
487 if ($passFilter) {
488 // increment pass filter counter
489 $pass_filter++;
491 else {
492 $dateCounter++;
493 continue;
496 // Check if pass target
497 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],'',$dateFocus);
498 if ($passTarget) {
499 // increment pass target counter
500 $pass_target++;
501 // send to reminder results
502 if ($mode == "reminders-all") {
503 // place the completed actions into the reminder return array
504 $actionArray = resolve_action_sql($rowRule['id'],'1');
505 foreach ($actionArray as $action) {
506 $action_plus = $action;
507 $action_plus['due_status'] = "not_due";
508 $action_plus['pid'] = $rowPatient['pid'];
509 $results = reminder_results_integrate($results, $action_plus);
512 break;
514 else {
515 // send to reminder results
516 if ($mode != "report") {
517 // place the uncompleted actions into the reminder return array
518 $actionArray = resolve_action_sql($rowRule['id'],'1');
519 foreach ($actionArray as $action) {
520 $action_plus = $action;
521 $action_plus['due_status'] = $reminder_due;
522 $action_plus['pid'] = $rowPatient['pid'];
523 $results = reminder_results_integrate($results, $action_plus);
527 $dateCounter++;
532 // Calculate and save the data for the rule
533 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
534 if ($mode == "report") {
535 $newRow=array('is_main'=>TRUE,'total_patients'=>$total_patients,'excluded'=>$exclude_filter,'pass_filter'=>$pass_filter,'pass_target'=>$pass_target,'percentage'=>$percentage);
536 $newRow=array_merge($newRow,$rowRule);
537 array_push($results, $newRow);
540 // Now run through the target groups if more than one
541 if (count($targetGroups) > 1) {
542 foreach ($targetGroups as $i) {
544 //Reset the target counter
545 $pass_target = 0;
547 foreach( $patientData as $rowPatient ) {
549 $dateCounter = 1; // for reminder mode to keep track of which date checking
550 foreach ( $target_dates as $dateFocus ) {
552 //Skip if date is set to SKIP
553 if ($dateFocus == "SKIP") {
554 $dateCounter++;
555 continue;
558 //Set date counter and reminder token (applicable for reminders only)
559 if ($dateCounter == 1) {
560 $reminder_due = "soon_due";
562 else if ($dateCounter == 2) {
563 $reminder_due = "due";
565 else { // $dateCounter == 3
566 $reminder_due = "past_due";
569 // First, deal with deceased patients
570 // (for now will simply not pass the filter, but can add a database item
571 // if ever want to create rules for dead people)
572 // Could also place this function at the total_patients level if wanted.
573 // (But then would lose the option of making rules for dead people)
574 // Note using the dateTarget rather than dateFocus
575 if (is_patient_deceased($rowPatient['pid'],$dateTarget)) {
576 continue;
579 // Check if pass filter
580 $passFilter = test_filter($rowPatient['pid'],$rowRule['id'],$dateFocus);
581 if ($passFilter === "EXCLUDED") {
582 $passFilter = FALSE;
584 if (!$passFilter) {
585 // increment pass filter counter
586 $dateCounter++;
587 continue;
590 //Check if pass target
591 $passTarget = test_targets($rowPatient['pid'],$rowRule['id'],$i,$dateFocus);
592 if ($passTarget) {
593 // increment pass target counter
594 $pass_target++;
595 // send to reminder results
596 if ($mode == "reminders-all") {
597 // place the completed actions into the reminder return array
598 $actionArray = resolve_action_sql($rowRule['id'],$i);
599 foreach ($actionArray as $action) {
600 $action_plus = $action;
601 $action_plus['due_status'] = "not_due";
602 $action_plus['pid'] = $rowPatient['pid'];
603 $results = reminder_results_integrate($results, $action_plus);
606 break;
608 else {
609 // send to reminder results
610 if ($mode != "report") {
611 // place the actions into the reminder return array
612 $actionArray = resolve_action_sql($rowRule['id'],$i);
613 foreach ($actionArray as $action) {
614 $action_plus = $action;
615 $action_plus['due_status'] = $reminder_due;
616 $action_plus['pid'] = $rowPatient['pid'];
617 $results = reminder_results_integrate($results, $action_plus);
621 $dateCounter++;
625 // Calculate and save the data for the rule
626 $percentage = calculate_percentage($pass_filter,$exclude_filter,$pass_target);
628 // Collect action for title (just use the first one, if more than one)
629 $actionArray = resolve_action_sql($rowRule['id'],$i);
630 $action = $actionArray[0];
631 if ($mode == "report") {
632 $newRow=array('is_sub'=>TRUE,'action_category'=>$action['category'],'action_item'=>$action['item'],'total_patients'=>'','excluded'=>'','pass_filter'=>'','pass_target'=>$pass_target,'percentage'=>$percentage);
633 array_push($results, $newRow);
639 // Return the data
640 return $results;
644 * Process patient array that is to be tested.
646 * @param integer $provider id of a selected provider. If blank, then will test entire clinic.
647 * @param integer $patient_id pid of patient. If blank then will check all patients.
648 * @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.
649 * @param integer $start applicable patient to start at (when batching process)
650 * @param integer $batchSize number of patients to batch (when batching process)
651 * @param boolean $onlyCount If true, then will just return the total number of applicable records (ignores batching parameters)
652 * @return array/integer Array of patient pid values or number total pertinent patients (if $onlyCount is TRUE)
654 function buildPatientArray($patient_id='',$provider='',$pat_prov_rel='primary',$start=NULL,$batchSize=NULL,$onlyCount=FALSE) {
656 if (!empty($patient_id)) {
657 // only look at the selected patient
658 if ($onlyCount) {
659 $patientNumber = 1;
661 else {
662 $patientData[0]['pid'] = $patient_id;
665 else {
666 if (empty($provider)) {
667 // Look at entire practice
668 if ($start == NULL || $batchSize == NULL || $onlyCount) {
669 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid`");
670 if ($onlyCount) {
671 $patientNumber = sqlNumRows($rez);
674 else {
675 // batching
676 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` ORDER BY `pid` LIMIT ?,?", array(($start-1),$batchSize));
679 else {
680 // Look at an individual physician
681 if( $pat_prov_rel == 'encounter' ){
682 // Choose patients that are related to specific physician by an encounter
683 if ($start == NULL || $batchSize == NULL || $onlyCount) {
684 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
685 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid`", array($provider,$provider));
686 if ($onlyCount) {
687 $patientNumber = sqlNumRows($rez);
690 else {
691 //batching
692 $rez = sqlStatementCdrEngine("SELECT DISTINCT `pid` FROM `form_encounter` ".
693 " WHERE `provider_id`=? OR `supervisor_id`=? ORDER BY `pid` LIMIT ?,?", array($provider,$provider,($start-1),$batchSize));
696 else { //$pat_prov_rel == 'primary'
697 // Choose patients that are assigned to the specific physician (primary physician in patient demographics)
698 if ($start == NULL || $batchSize == NULL || $onlyCount) {
699 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
700 "WHERE `providerID`=? ORDER BY `pid`", array($provider) );
701 if ($onlyCount) {
702 $patientNumber = sqlNumRows($rez);
705 else {
706 $rez = sqlStatementCdrEngine("SELECT `pid` FROM `patient_data` " .
707 "WHERE `providerID`=? ORDER BY `pid` LIMIT ?,?", array($provider,($start-1),$batchSize) );
711 // convert the sql query results into an array if returning the array
712 if(!$onlyCount) {
713 for($iter=0; $row=sqlFetchArray($rez); $iter++) {
714 $patientData[$iter]=$row;
719 if ($onlyCount) {
720 // return the number of applicable patients
721 return $patientNumber;
723 else {
724 // return array of patient pids
725 return $patientData;
730 * Test filter of a selected rule on a selected patient
732 * @param integer $patient_id pid of selected patient.
733 * @param string $rule id(string) of selected rule
734 * @param string $dateTarget target date (format Y-m-d H:i:s). If blank then will test with current date as target.
735 * @return boolean/string if pass filter then TRUE; if excluded then 'EXCLUDED'; if not pass filter then FALSE
737 function test_filter($patient_id,$rule,$dateTarget) {
739 // Set date to current if not set
740 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
742 // Collect patient information
743 $patientData = getPatientData($patient_id, "sex, DATE_FORMAT(DOB,'%Y %m %d') as DOB_TS");
746 // ----------------- INCLUSIONS -----------------
749 // -------- Age Filter (inclusion) ------------
750 // Calculate patient age in years and months
751 $patientAgeYears = convertDobtoAgeYearDecimal($patientData['DOB_TS'],$dateTarget);
752 $patientAgeMonths = convertDobtoAgeMonthDecimal($patientData['DOB_TS'],$dateTarget);
754 // Min age (year) Filter (assume that there in not more than one of each)
755 $filter = resolve_filter_sql($rule,'filt_age_min');
756 if (!empty($filter)) {
757 $row = $filter[0];
758 if ($row ['method_detail'] == "year") {
759 if ( $row['value'] && ($row['value'] > $patientAgeYears) ) {
760 return false;
763 if ($row ['method_detail'] == "month") {
764 if ( $row['value'] && ($row['value'] > $patientAgeMonths) ) {
765 return false;
769 // Max age (year) Filter (assume that there in not more than one of each)
770 $filter = resolve_filter_sql($rule,'filt_age_max');
771 if (!empty($filter)) {
772 $row = $filter[0];
773 if ($row ['method_detail'] == "year") {
774 if ( $row['value'] && ($row['value'] < $patientAgeYears) ) {
775 return false;
778 if ($row ['method_detail'] == "month") {
779 if ( $row['value'] && ($row['value'] < $patientAgeMonths) ) {
780 return false;
785 // -------- Gender Filter (inclusion) ---------
786 // Gender Filter (assume that there in not more than one of each)
787 $filter = resolve_filter_sql($rule,'filt_sex');
788 if (!empty($filter)) {
789 $row = $filter[0];
790 if ( $row['value'] && ($row['value'] != $patientData['sex']) ) {
791 return false;
795 // -------- Database Filter (inclusion) ------
796 // Database Filter
797 $filter = resolve_filter_sql($rule,'filt_database');
798 if ((!empty($filter)) && !database_check($patient_id,$filter,'',$dateTarget)) return false;
800 // -------- Lists Filter (inclusion) ----
801 // Set up lists filter, which is fully customizable and currently includes diagnoses, meds,
802 // surgeries and allergies.
803 $filter = resolve_filter_sql($rule,'filt_lists');
804 if ((!empty($filter)) && !lists_check($patient_id,$filter,$dateTarget)) return false;
806 // -------- Procedure (labs,imaging,test,procedures,etc) Filter (inlcusion) ----
807 // Procedure Target (includes) (may need to include an interval in the future)
808 $filter = resolve_filter_sql($rule,'filt_proc');
809 if ((!empty($filter)) && !procedure_check($patient_id,$filter,'',$dateTarget)) return false;
812 // ----------------- EXCLUSIONS -----------------
815 // -------- Lists Filter (EXCLUSION) ----
816 // Set up lists EXCLUSION filter, which is fully customizable and currently includes diagnoses, meds,
817 // surgeries and allergies.
818 $filter = resolve_filter_sql($rule,'filt_lists',0);
819 if ((!empty($filter)) && lists_check($patient_id,$filter,$dateTarget)) return "EXCLUDED";
821 // Passed all filters, so return true.
822 return true;
826 * Return an array containing existing group ids for a rule
828 * @param string $rule id(string) of rule
829 * @return array listing of group ids
831 function returnTargetGroups($rule) {
833 $sql = sqlStatementCdrEngine("SELECT DISTINCT `group_id` FROM `rule_target` " .
834 "WHERE `id`=?", array($rule) );
836 $groups = array();
837 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
838 array_push($groups,$row['group_id']);
840 return $groups;
844 * Test targets of a selected rule on a selected patient
846 * @param integer $patient_id pid of selected patient.
847 * @param string $rule id(string) of selected rule (if blank, then will ignore grouping)
848 * @param integer $group_id group id of target group
849 * @param string $dateTarget target date (format Y-m-d H:i:s).
850 * @return boolean if target passes then true, otherwise false
852 function test_targets($patient_id,$rule,$group_id='',$dateTarget) {
854 // -------- Interval Target ----
855 $interval = resolve_target_sql($rule,$group_id,'target_interval');
857 // -------- Database Target ----
858 // Database Target (includes)
859 $target = resolve_target_sql($rule,$group_id,'target_database');
860 if ((!empty($target)) && !database_check($patient_id,$target,$interval,$dateTarget)) return false;
862 // -------- Procedure (labs,imaging,test,procedures,etc) Target ----
863 // Procedure Target (includes)
864 $target = resolve_target_sql($rule,$group_id,'target_proc');
865 if ((!empty($target)) && !procedure_check($patient_id,$target,$interval,$dateTarget)) return false;
867 // -------- Appointment Target ----
868 // Appointment Target (includes) (Specialized functionality for appointment reminders)
869 $target = resolve_target_sql($rule,$group_id,'target_appt');
870 if ((!empty($target)) && appointment_check($patient_id,$dateTarget)) return false;
872 // Passed all target tests, so return true.
873 return true;
877 * Function to return active plans
879 * @param string $type plan type filter (normal or cqm or blank)
880 * @param integer $patient_id pid of selected patient. (if custom plan does not exist then will use the default plan)
881 * @param boolean $configurableOnly true if only want the configurable (per patient) plans (ie. ignore cqm plans)
882 * @return array active plans
884 function resolve_plans_sql($type='',$patient_id='0',$configurableOnly=FALSE) {
886 if ($configurableOnly) {
887 // Collect all default, configurable (per patient) plans into an array
888 // (ie. ignore the cqm rules)
889 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 AND `cqm_flag` !=1 ORDER BY `id`");
891 else {
892 // Collect all default plans into an array
893 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_plans` WHERE `pid`=0 ORDER BY `id`");
895 $returnArray= array();
896 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
897 array_push($returnArray,$row);
900 // Now collect the pertinent plans
901 $newReturnArray = array();
903 // Need to select rules (use custom if exist)
904 foreach ($returnArray as $plan) {
905 $customPlan = sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan['id'],$patient_id) );
907 // Decide if use default vs custom plan (preference given to custom plan)
908 if (!empty($customPlan)) {
909 if ($type == "cqm" ) {
910 // For CQM , do not use custom plans (these are to create standard clinic wide reports)
911 $goPlan = $plan;
913 else {
914 // merge the custom plan with the default plan
915 $mergedPlan = array();
916 foreach ($customPlan as $key => $value) {
917 if ($value == NULL && preg_match("/_flag$/",$key)) {
918 // use default setting
919 $mergedPlan[$key] = $plan[$key];
921 else {
922 // use custom setting
923 $mergedPlan[$key] = $value;
926 $goPlan = $mergedPlan;
929 else {
930 $goPlan = $plan;
933 // Use the chosen plan if set
934 if (!empty($type)) {
935 if ($goPlan["${type}_flag"] == 1) {
936 // active, so use the plan
937 array_push($newReturnArray,$goPlan);
940 else {
941 if ($goPlan['normal_flag'] == 1 ||
942 $goPlan['cqm_flag'] == 1) {
943 // active, so use the plan
944 array_push($newReturnArray,$goPlan);
948 $returnArray = $newReturnArray;
950 return $returnArray;
955 * Function to return a specific plan
957 * @param string $plan id(string) of plan
958 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
959 * @return array a plan
961 function collect_plan($plan,$patient_id='0') {
963 return sqlQueryCdrEngine("SELECT * FROM `clinical_plans` WHERE `id`=? AND `pid`=?", array($plan,$patient_id) );
968 * Function to set a specific plan activity for a specific patient
970 * @param string $plan id(string) of plan
971 * @param string $type plan filter (normal,cqm)
972 * @param string $setting activity of plan (yes,no,default)
973 * @param integer $patient_id pid of selected patient.
975 function set_plan_activity_patient($plan,$type,$setting,$patient_id) {
977 // Don't allow messing with the default plans here
978 if ($patient_id == "0") {
979 return;
982 // Convert setting
983 if ($setting == "on") {
984 $setting = 1;
986 else if ($setting == "off") {
987 $setting = 0;
989 else { // $setting == "default"
990 $setting = NULL;
993 // Collect patient specific plan, if already exists.
994 $query = "SELECT * FROM `clinical_plans` WHERE `id` = ? AND `pid` = ?";
995 $patient_plan = sqlQueryCdrEngine($query, array($plan,$patient_id) );
997 if (empty($patient_plan)) {
998 // Create a new patient specific plan with flags all set to default
999 $query = "INSERT into `clinical_plans` (`id`, `pid`) VALUES (?,?)";
1000 sqlStatementCdrEngine($query, array($plan, $patient_id) );
1003 // Update patient specific row
1004 $query = "UPDATE `clinical_plans` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
1005 sqlStatementCdrEngine($query, array($setting,$plan,$patient_id) );
1010 * Function to return active rules
1012 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1013 * @param integer $patient_id pid of selected patient. (if custom rule does not exist then will use the default rule)
1014 * @param boolean $configurableOnly true if only want the configurable (per patient) rules (ie. ignore cqm and amc rules)
1015 * @param string $plan collect rules for specific plan
1016 * @return array rules
1018 function resolve_rules_sql($type='',$patient_id='0',$configurableOnly=FALSE,$plan='') {
1020 if ($configurableOnly) {
1021 // Collect all default, configurable (per patient) rules into an array
1022 // (ie. ignore the cqm and amc rules)
1023 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 AND `cqm_flag` !=1 AND `amc_flag` !=1 ORDER BY `id`");
1025 else {
1026 // Collect all default rules into an array
1027 $sql = sqlStatementCdrEngine("SELECT * FROM `clinical_rules` WHERE `pid`=0 ORDER BY `id`");
1029 $returnArray= array();
1030 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1031 array_push($returnArray,$row);
1034 // Now filter rules for plan (if applicable)
1035 if (!empty($plan)) {
1036 $planReturnArray = array();
1037 foreach ($returnArray as $rule) {
1038 $standardRule = sqlQueryCdrEngine("SELECT * FROM `clinical_plans_rules` " .
1039 "WHERE `plan_id`=? AND `rule_id`=?", array($plan,$rule['id']) );
1040 if (!empty($standardRule)) {
1041 array_push($planReturnArray,$rule);
1044 $returnArray = $planReturnArray;
1047 // Now collect the pertinent rules
1048 $newReturnArray = array();
1050 // Need to select rules (use custom if exist)
1051 foreach ($returnArray as $rule) {
1052 $customRule = sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule['id'],$patient_id) );
1054 // Decide if use default vs custom rule (preference given to custom rule)
1055 if (!empty($customRule)) {
1056 if ($type == "cqm" || $type == "amc" ) {
1057 // For CQM and AMC, do not use custom rules (these are to create standard clinic wide reports)
1058 $goRule = $rule;
1060 else {
1061 // merge the custom rule with the default rule
1062 $mergedRule = array();
1063 foreach ($customRule as $key => $value) {
1064 if ($value == NULL && preg_match("/_flag$/",$key)) {
1065 // use default setting
1066 $mergedRule[$key] = $rule[$key];
1068 else {
1069 // use custom setting
1070 $mergedRule[$key] = $value;
1073 $goRule = $mergedRule;
1076 else {
1077 $goRule = $rule;
1080 // Use the chosen rule if set
1081 if (!empty($type)) {
1082 if ($goRule["${type}_flag"] == 1) {
1083 // active, so use the rule
1084 array_push($newReturnArray,$goRule);
1087 else {
1088 // no filter, so return the rule
1089 array_push($newReturnArray,$goRule);
1092 $returnArray = $newReturnArray;
1094 return $returnArray;
1098 * Function to return a specific rule
1100 * @param string $rule id(string) of rule
1101 * @param integer $patient_id pid of selected patient. (if set to 0, then will return the default rule).
1102 * @return array rule
1104 function collect_rule($rule,$patient_id='0') {
1106 return sqlQueryCdrEngine("SELECT * FROM `clinical_rules` WHERE `id`=? AND `pid`=?", array($rule,$patient_id) );
1111 * Function to set a specific rule activity for a specific patient
1113 * @param string $rule id(string) of rule
1114 * @param string $type rule filter (active_alert,passive_alert,cqm,amc,patient_reminder)
1115 * @param string $setting activity of rule (yes,no,default)
1116 * @param integer $patient_id pid of selected patient.
1118 function set_rule_activity_patient($rule,$type,$setting,$patient_id) {
1120 // Don't allow messing with the default rules here
1121 if ($patient_id == "0") {
1122 return;
1125 // Convert setting
1126 if ($setting == "on") {
1127 $setting = 1;
1129 else if ($setting == "off") {
1130 $setting = 0;
1132 else { // $setting == "default"
1133 $setting = NULL;
1136 // Collect patient specific rule, if already exists.
1137 $query = "SELECT * FROM `clinical_rules` WHERE `id` = ? AND `pid` = ?";
1138 $patient_rule = sqlQueryCdrEngine($query, array($rule,$patient_id) );
1140 if (empty($patient_rule)) {
1141 // Create a new patient specific rule with flags all set to default
1142 $query = "INSERT into `clinical_rules` (`id`, `pid`) VALUES (?,?)";
1143 sqlStatementCdrEngine($query, array($rule, $patient_id) );
1146 // Update patient specific row
1147 $query = "UPDATE `clinical_rules` SET `" . add_escape_custom($type) . "_flag`= ? WHERE id = ? AND pid = ?";
1148 sqlStatementCdrEngine($query, array($setting,$rule,$patient_id) );
1153 * Function to return applicable reminder dates (relative)
1155 * @param string $rule id(string) of selected rule
1156 * @param string $reminder_method string label of filter type
1157 * @return array reminder features
1159 function resolve_reminder_sql($rule,$reminder_method) {
1160 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value` FROM `rule_reminder` " .
1161 "WHERE `id`=? AND `method`=?", array($rule, $reminder_method) );
1163 $returnArray= array();
1164 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1165 array_push($returnArray,$row);
1167 return $returnArray;
1171 * Function to return applicable filters
1173 * @param string $rule id(string) of selected rule
1174 * @param string $filter_method string label of filter type
1175 * @param string $include_flag to allow selection for included or excluded filters
1176 * @return array filters
1178 function resolve_filter_sql($rule,$filter_method,$include_flag=1) {
1179 $sql = sqlStatementCdrEngine("SELECT `method_detail`, `value`, `required_flag` FROM `rule_filter` " .
1180 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $filter_method, $include_flag) );
1182 $returnArray= array();
1183 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1184 array_push($returnArray,$row);
1186 return $returnArray;
1190 * Function to return applicable targets
1192 * @param string $rule id(string) of selected rule
1193 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1194 * @param string $target_method string label of target type
1195 * @param string $include_flag to allow selection for included or excluded targets
1196 * @return array targets
1198 function resolve_target_sql($rule,$group_id='',$target_method,$include_flag=1) {
1200 if ($group_id) {
1201 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1202 "WHERE `id`=? AND `group_id`=? AND `method`=? AND `include_flag`=?", array($rule, $group_id, $target_method, $include_flag) );
1204 else {
1205 $sql = sqlStatementCdrEngine("SELECT `value`, `required_flag`, `interval` FROM `rule_target` " .
1206 "WHERE `id`=? AND `method`=? AND `include_flag`=?", array($rule, $target_method, $include_flag) );
1209 $returnArray= array();
1210 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1211 array_push($returnArray,$row);
1213 return $returnArray;
1217 * Function to return applicable actions
1219 * @param string $rule id(string) of selected rule
1220 * @param integer $group_id group id of target group (if blank, then will ignore grouping)
1221 * @return array actions
1223 function resolve_action_sql($rule,$group_id='') {
1225 if ($group_id) {
1226 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.clin_rem_link, b.reminder_message, b.custom_flag " .
1227 "FROM `rule_action` as a " .
1228 "JOIN `rule_action_item` as b " .
1229 "ON a.category = b.category AND a.item = b.item " .
1230 "WHERE a.id=? AND a.group_id=?", array($rule,$group_id) );
1232 else {
1233 $sql = sqlStatementCdrEngine("SELECT b.category, b.item, b.value, b.custom_flag " .
1234 "FROM `rule_action` as a " .
1235 "JOIN `rule_action_item` as b " .
1236 "ON a.category = b.category AND a.item = b.item " .
1237 "WHERE a.id=?", array($rule) );
1240 $returnArray= array();
1241 for($iter=0; $row=sqlFetchArray($sql); $iter++) {
1242 array_push($returnArray,$row);
1244 return $returnArray;
1248 * Function to check database filters and targets
1250 * @param string $patient_id pid of selected patient.
1251 * @param array $filter array containing filter/target elements
1252 * @param array $interval array containing interval elements
1253 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1254 * @return boolean true if check passed, otherwise false
1256 function database_check($patient_id,$filter,$interval='',$dateTarget='') {
1257 $isMatch = false; //matching flag
1259 // Set date to current if not set
1260 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1262 // Unpackage interval information
1263 // (Assume only one for now and only pertinent for targets)
1264 $intervalType = '';
1265 $intervalValue = '';
1266 if (!empty($interval)) {
1267 $intervalType = $interval[0]['value'];
1268 $intervalValue = $interval[0]['interval'];
1271 foreach( $filter as $row ) {
1272 // Row description
1273 // [0]=>special modes
1274 $temp_df = explode("::",$row['value']);
1276 if ($temp_df[0] == "CUSTOM") {
1277 // Row description
1278 // [0]=>special modes(CUSTOM) [1]=>category [2]=>item [3]=>complete? [4]=>number of hits comparison [5]=>number of hits
1279 if (exist_custom_item($patient_id, $temp_df[1], $temp_df[2], $temp_df[3], $temp_df[4], $temp_df[5], $intervalType, $intervalValue, $dateTarget)) {
1280 // Record the match
1281 $isMatch = true;
1283 else {
1284 // If this is a required entry then return false
1285 if ($row['required_flag']) return false;
1288 else if ($temp_df[0] == "LIFESTYLE") {
1289 // Row description
1290 // [0]=>special modes(LIFESTYLE) [1]=>column [2]=>status
1291 if (exist_lifestyle_item($patient_id, $temp_df[1], $temp_df[2], $dateTarget)) {
1292 // Record the match
1293 $isMatch = true;
1295 else {
1296 // If this is a required entry then return false
1297 if ($row['required_flag']) return false;
1300 else {
1301 // Default mode
1302 // Row description
1303 // [0]=>special modes(BLANK) [1]=>table [2]=>column [3]=>value comparison [4]=>value [5]=>number of hits comparison [6]=>number of hits
1304 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)) {
1305 // Record the match
1306 $isMatch = true;
1308 else {
1309 // If this is a required entry then return false
1310 if ($row['required_flag']) return false;
1315 // return results of check
1316 return $isMatch;
1320 * Function to check procedure filters and targets
1322 * @param string $patient_id pid of selected patient.
1323 * @param array $filter array containing filter/target elements
1324 * @param array $interval array containing interval elements
1325 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1326 * @return boolean true if check passed, otherwise false
1328 function procedure_check($patient_id,$filter,$interval='',$dateTarget='') {
1329 $isMatch = false; //matching flag
1331 // Set date to current if not set
1332 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1334 // Unpackage interval information
1335 // (Assume only one for now and only pertinent for targets)
1336 $intervalType = '';
1337 $intervalValue = '';
1338 if (!empty($interval)) {
1339 $intervalType = $interval[0]['value'];
1340 $intervalValue = $interval[0]['interval'];
1343 foreach( $filter as $row ) {
1344 // Row description
1345 // [0]=>title [1]=>code [2]=>value comparison [3]=>value [4]=>number of hits comparison [5]=>number of hits
1346 // code description
1347 // <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.
1348 $temp_df = explode("::",$row['value']);
1349 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)) {
1350 // Record the match
1351 $isMatch = true;
1353 else {
1354 // If this is a required entry then return false
1355 if ($row['required_flag']) return false;
1359 // return results of check
1360 return $isMatch;
1364 * Function to check for appointment
1366 * @todo Complete this to allow appointment reminders.
1367 * @param string $patient_id pid of selected patient.
1368 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1369 * @return boolean true if appt exist, otherwise false
1371 function appointment_check($patient_id,$dateTarget='') {
1372 $isMatch = false; //matching flag
1374 // Set date to current if not set (although should always be set)
1375 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1376 $dateTargetRound = date('Y-m-d',$dateTarget);
1378 // Set current date
1379 $currentDate = date('Y-m-d H:i:s');
1380 $currentDateRound = date('Y-m-d',$dateCurrent);
1382 // Basically, if the appointment is within the current date to the target date,
1383 // then return true. (will not send reminders on same day as appointment)
1384 $sql = sqlStatementCdrEngine("SELECT openemr_postcalendar_events.pc_eid, " .
1385 "openemr_postcalendar_events.pc_title, " .
1386 "openemr_postcalendar_events.pc_eventDate, " .
1387 "openemr_postcalendar_events.pc_startTime, " .
1388 "openemr_postcalendar_events.pc_endTime " .
1389 "FROM openemr_postcalendar_events " .
1390 "WHERE openemr_postcalendar_events.pc_eventDate > ? " .
1391 "AND openemr_postcalendar_events.pc_eventDate <= ? " .
1392 "AND openemr_postcalendar_events.pc_pid = ?", array($currentDate,$dateTarget,$patient_id) );
1394 // return results of check
1396 // TODO: Figure out how to have multiple appointment and changing appointment reminders.
1397 // Plan to send back array of appt info (eid, time, date, etc.)
1398 // to do this.
1399 if (sqlNumRows($sql) > 0) {
1400 $isMatch = true;
1403 return $isMatch;
1407 * Function to check lists filters and targets. Customizable and currently includes diagnoses, medications, allergies and surgeries.
1409 * @param string $patient_id pid of selected patient.
1410 * @param array $filter array containing lists filter/target elements
1411 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1412 * @return boolean true if check passed, otherwise false
1414 function lists_check($patient_id,$filter,$dateTarget) {
1415 $isMatch = false; //matching flag
1417 // Set date to current if not set
1418 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1420 foreach ( $filter as $row ) {
1421 if (exist_lists_item($patient_id, $row['method_detail'], $row['value'], $dateTarget)) {
1422 // Record the match
1423 $isMatch = true;
1425 else {
1426 // If this is a required entry then return false
1427 if ($row['required_flag']) return false;
1431 // return results of check
1432 return $isMatch;
1436 * Function to check for existance of data in database for a patient
1438 * @param string $patient_id pid of selected patient.
1439 * @param string $table selected mysql table
1440 * @param string $column selected mysql column
1441 * @param string $data_comp data comparison (eq,ne,gt,ge,lt,le)
1442 * @param string $data selected data in the mysql database
1443 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1444 * @param integer $num_items_thres number of items threshold
1445 * @param string $intervalType type of interval (ie. year)
1446 * @param integer $intervalValue searched for within this many times of the interval type
1447 * @param string $dateTarget target date(format Y-m-d H:i:s).
1448 * @return boolean true if check passed, otherwise false
1450 function exist_database_item($patient_id,$table,$column='',$data_comp,$data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1452 // Set date to current if not set
1453 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1455 // Collect the correct column label for patient id in the table
1456 $patient_id_label = collect_database_label('pid',$table);
1458 // Get the interval sql query string
1459 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1461 // If just checking for existence (ie. data is empty),
1462 // then simply set the comparison operator to ne.
1463 if (empty($data)) {
1464 $data_comp = "ne";
1467 // get the appropriate sql comparison operator
1468 $compSql = convertCompSql($data_comp);
1470 // check for items
1471 if (empty($column)) {
1472 // simple search for any table entries
1473 $sql = sqlStatementCdrEngine("SELECT * " .
1474 "FROM `" . add_escape_custom($table) . "` " .
1475 "WHERE `" . add_escape_custom($patient_id_label) . "`=?", array($patient_id) );
1477 else {
1478 // search for number of specific items
1479 $sql = sqlStatementCdrEngine("SELECT `" . add_escape_custom($column) . "` " .
1480 "FROM `" . add_escape_custom($table) . "` " .
1481 "WHERE `" . add_escape_custom($column) ."`" . $compSql . "? " .
1482 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1483 $dateSql, array($data,$patient_id) );
1486 // See if number of returned items passes the comparison
1487 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1491 * Function to check for existence of procedure(s) for a patient
1493 * @param string $patient_id pid of selected patient.
1494 * @param string $proc_title procedure title
1495 * @param string $proc_code procedure identifier code (array of <type(ICD9,CPT4)>:<identifier>||<type(ICD9,CPT4)>:<identifier>||<identifier> etc.)
1496 * @param string $results_comp results comparison (eq,ne,gt,ge,lt,le)
1497 * @param string $result_data results data
1498 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1499 * @param integer $num_items_thres number of items threshold
1500 * @param string $intervalType type of interval (ie. year)
1501 * @param integer $intervalValue searched for within this many times of the interval type
1502 * @param string $dateTarget target date(format Y-m-d H:i:s).
1503 * @return boolean true if check passed, otherwise false
1505 function exist_procedure_item($patient_id,$proc_title,$proc_code,$result_comp,$result_data='',$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget='') {
1507 // Set date to current if not set
1508 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1510 // Set the table exception (for looking up pertinent date and pid sql columns)
1511 $table = "PROCEDURE-EXCEPTION";
1513 // Collect the correct column label for patient id in the table
1514 $patient_id_label = collect_database_label('pid',$table);
1516 // Get the interval sql query string
1517 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1519 // If just checking for existence (ie result_data is empty),
1520 // then simply set the comparison operator to ne.
1521 if (empty($result_data)) {
1522 $result_comp = "ne";
1525 // get the appropriate sql comparison operator
1526 $compSql = convertCompSql($result_comp);
1528 // explode the code array
1529 $codes= array();
1530 if (!empty($proc_code)) {
1531 $codes = explode("||",$proc_code);
1533 else {
1534 $codes[0] = '';
1537 // ensure proc_title is at least blank
1538 if (empty($proc_title)) {
1539 $proc_title = '';
1542 // collect specific items (use both title and/or codes) that fulfill request
1543 $sqlBindArray=array();
1544 $sql_query = "SELECT procedure_result.result " .
1545 "FROM `procedure_type`, " .
1546 "`procedure_order`, " .
1547 "`procedure_report`, " .
1548 "`procedure_result` " .
1549 "WHERE procedure_type.procedure_type_id = procedure_order.procedure_type_id " .
1550 "AND procedure_order.procedure_order_id = procedure_report.procedure_order_id " .
1551 "AND procedure_report.procedure_report_id = procedure_result.procedure_report_id " .
1552 "AND ";
1553 foreach ($codes as $tem) {
1554 $sql_query .= "( ( (procedure_type.standard_code = ? AND procedure_type.standard_code != '') " .
1555 "OR (procedure_type.procedure_code = ? AND procedure_type.procedure_code != '') ) OR ";
1556 array_push($sqlBindArray,$tem,$tem);
1558 $sql_query .= "(procedure_type.name = ? AND procedure_type.name != '') ) " .
1559 "AND procedure_result.result " . $compSql . " ? " .
1560 "AND " . add_escape_custom($patient_id_label) . " = ? " . $dateSql;
1561 array_push($sqlBindArray,$proc_title,$result_data,$patient_id);
1562 $sql = sqlStatementCdrEngine($sql_query,$sqlBindArray);
1564 // See if number of returned items passes the comparison
1565 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1569 * Function to check for existance of data for a patient in the rule_patient_data table
1571 * @param string $patient_id pid of selected patient.
1572 * @param string $category label in category column
1573 * @param string $item label in item column
1574 * @param string $complete label in complete column (YES,NO, or blank)
1575 * @param string $num_items_comp number items comparison (eq,ne,gt,ge,lt,le)
1576 * @param integer $num_items_thres number of items threshold
1577 * @param string $intervalType type of interval (ie. year)
1578 * @param integer $intervalValue searched for within this many times of the interval type
1579 * @param string $dateTarget target date(format Y-m-d H:i:s).
1580 * @return boolean true if check passed, otherwise false
1582 function exist_custom_item($patient_id,$category,$item,$complete,$num_items_comp,$num_items_thres,$intervalType='',$intervalValue='',$dateTarget) {
1584 // Set the table
1585 $table = 'rule_patient_data';
1587 // Collect the correct column label for patient id in the table
1588 $patient_id_label = collect_database_label('pid',$table);
1590 // Get the interval sql query string
1591 $dateSql = sql_interval_string($table,$intervalType,$intervalValue,$dateTarget);
1593 // search for number of specific items
1594 $sql = sqlStatementCdrEngine("SELECT `result` " .
1595 "FROM `" . add_escape_custom($table) . "` " .
1596 "WHERE `category`=? " .
1597 "AND `item`=? " .
1598 "AND `complete`=? " .
1599 "AND `" . add_escape_custom($patient_id_label) . "`=? " .
1600 $dateSql, array($category,$item,$complete,$patient_id) );
1602 // See if number of returned items passes the comparison
1603 return itemsNumberCompare($num_items_comp, $num_items_thres, sqlNumRows($sql));
1607 * Function to check for existance of data for a patient in lifestyle section
1609 * @param string $patient_id pid of selected patient.
1610 * @param string $lifestyle selected label of mysql column of patient history
1611 * @param string $status specific status of selected lifestyle element
1612 * @param string $dateTarget target date(format Y-m-d H:i:s). blank is current date.
1613 * @return boolean true if check passed, otherwise false
1615 function exist_lifestyle_item($patient_id,$lifestyle,$status,$dateTarget) {
1617 // Set date to current if not set
1618 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1620 // Collect pertinent history data
1621 $history = getHistoryData($patient_id, $lifestyle,'',$dateTarget);
1623 // See if match
1624 $stringFlag = strstr($history[$lifestyle], "|".$status);
1625 if (empty($status)) {
1626 // Only ensuring any data has been entered into the field
1627 $stringFlag = true;
1629 if ( $history[$lifestyle] &&
1630 $history[$lifestyle] != '|0|' &&
1631 $stringFlag ) {
1632 return true;
1634 else {
1635 return false;
1640 * Function to check for lists item of a patient. Fully customizable and includes diagnoses, medications,
1641 * allergies, and surgeries.
1643 * @param string $patient_id pid of selected patient.
1644 * @param string $type type (medical_problem, allergy, medication, etc)
1645 * @param string $value value searching for
1646 * @param string $dateTarget target date(format Y-m-d H:i:s).
1647 * @return boolean true if check passed, otherwise false
1649 function exist_lists_item($patient_id,$type,$value,$dateTarget) {
1651 // Set date to current if not set
1652 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1654 // Attempt to explode the value into a code type and code (if applicable)
1655 $value_array = explode("::",$value);
1656 if (count($value_array) == 2) {
1658 // Collect the code type and code
1659 $code_type = $value_array[0];
1660 $code = $value_array[1];
1662 if ($code_type=='CUSTOM') {
1663 // Deal with custom code type first (title column in lists table)
1664 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1665 "WHERE `type`=? " .
1666 "AND `pid`=? " .
1667 "AND `title`=? " .
1668 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1669 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$code,$dateTarget,$dateTarget,$dateTarget) );
1670 if (!empty($response)) return true;
1672 else {
1673 // Deal with the set code types (diagnosis column in lists table)
1674 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1675 "WHERE `type`=? " .
1676 "AND `pid`=? " .
1677 "AND `diagnosis` LIKE ? " .
1678 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1679 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,"%".$code_type.":".$code."%",$dateTarget,$dateTarget,$dateTarget) );
1680 if (!empty($response)) return true;
1683 else { // count($value_array) == 1
1684 // Search the title column in lists table
1685 // Yes, this is essentially the same as the code type listed as CUSTOM above. This provides flexibility and will ensure compatibility.
1686 $response = sqlQueryCdrEngine("SELECT * FROM `lists` " .
1687 "WHERE `type`=? " .
1688 "AND `pid`=? " .
1689 "AND `title`=? ".
1690 "AND ( (`begdate` IS NULL AND `date`<=?) OR (`begdate` IS NOT NULL AND `begdate`<=?) ) " .
1691 "AND ( (`enddate` IS NULL) OR (`enddate` IS NOT NULL AND `enddate`>=?) )", array($type,$patient_id,$value,$dateTarget,$dateTarget,$dateTarget) );
1692 if (!empty($response)) return true;
1695 return false;
1699 * Function to return part of sql query to deal with interval
1701 * @param string $table selected mysql table (or EXCEPTION(s))
1702 * @param string $intervalType type of interval (ie. year)
1703 * @param string $intervalValue searched for within this many times of the interval type
1704 * @param string $dateTarget target date(format Y-m-d H:i:s).
1705 * @return string contains pertinent date interval filter for mysql query
1707 function sql_interval_string($table,$intervalType,$intervalValue,$dateTarget) {
1709 $dateSql="";
1711 // Collect the correct column label for date in the table
1712 $date_label = collect_database_label('date',$table);
1714 // Deal with interval
1715 if (!empty($intervalType)) {
1716 switch($intervalType) {
1717 case "year":
1718 $dateSql = "AND (" . add_escape_custom($date_label) .
1719 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1720 "', INTERVAL " . add_escape_custom($intervalValue) .
1721 " YEAR) AND '" . add_escape_custom($dateTarget) . "') ";
1722 break;
1723 case "month":
1724 $dateSql = "AND (" . add_escape_custom($date_label) .
1725 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1726 "', INTERVAL " . add_escape_custom($intervalValue) .
1727 " MONTH) AND '" . add_escape_custom($dateTarget) . "') ";
1728 break;
1729 case "week":
1730 $dateSql = "AND (" . add_escape_custom($date_label) .
1731 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1732 "', INTERVAL " . add_escape_custom($intervalValue) .
1733 " WEEK) AND '" . add_escape_custom($dateTarget) . "') ";
1734 break;
1735 case "day":
1736 $dateSql = "AND (" . add_escape_custom($date_label) .
1737 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1738 "', INTERVAL " . add_escape_custom($intervalValue) .
1739 " DAY) AND '" . add_escape_custom($dateTarget) . "') ";
1740 break;
1741 case "hour":
1742 $dateSql = "AND (" . add_escape_custom($date_label) .
1743 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1744 "', INTERVAL " . add_escape_custom($intervalValue) .
1745 " HOUR) AND '" . add_escape_custom($dateTarget) . "') ";
1746 break;
1747 case "minute":
1748 $dateSql = "AND (" . add_escape_custom($date_label) .
1749 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1750 "', INTERVAL " . add_escape_custom($intervalValue) .
1751 " MINUTE) AND '" . add_escape_custom($dateTarget) . "') ";
1752 break;
1753 case "second":
1754 $dateSql = "AND (" . add_escape_custom($date_label) .
1755 " BETWEEN DATE_SUB('" . add_escape_custom($dateTarget) .
1756 "', INTERVAL " . add_escape_custom($intervalValue) .
1757 " SECOND) AND '" . add_escape_custom($dateTarget) . "') ";
1758 break;
1759 case "flu_season":
1760 // Flu season to be hard-coded as September thru February
1761 // (Should make this modifiable in the future)
1762 // ($intervalValue is not used)
1763 $dateArray = explode("-",$dateTarget);
1764 $Year = $dateArray[0];
1765 $dateThisYear = $Year . "-09-01";
1766 $dateLastYear = ($Year-1) . "-09-01";
1767 $dateSql =" " .
1768 "AND ((" .
1769 "MONTH('" . add_escape_custom($dateTarget) . "') < 9 " .
1770 "AND " . add_escape_custom($date_label) . " >= '" . $dateLastYear . "' ) " .
1771 "OR (" .
1772 "MONTH('" . add_escape_custom($dateTarget) . "') >= 9 " .
1773 "AND " . add_escape_custom($date_label) . " >= '" . $dateThisYear . "' ))" .
1774 "AND " . add_escape_custom($date_label) . " <= '" . add_escape_custom($dateTarget) . "' ";
1775 break;
1778 else {
1779 $dateSql = "AND " . add_escape_custom($date_label) .
1780 " <= '" . add_escape_custom($dateTarget) . "' ";
1783 // return the sql interval string
1784 return $dateSql;
1788 * Function to collect generic column labels from tables. It currently works for date
1789 * and pid. Will need to expand this as algorithm grows.
1791 * @param string $label element (pid or date)
1792 * @param string $table selected mysql table (or EXCEPTION(s))
1793 * @return string contains official label of selected element
1795 function collect_database_label($label,$table) {
1797 if ($table == 'PROCEDURE-EXCEPTION') {
1798 // return cell to get procedure collection
1799 // special case since reuqires joing of multiple
1800 // tables to get this value
1801 if ($label == "pid") {
1802 $returnedLabel = "procedure_order.patient_id";
1804 else if ($label == "date") {
1805 $returnedLabel = "procedure_report.date_collected";
1807 else {
1808 // unknown label, so return the original label
1809 $returnedLabel = $label;
1812 else if ($table == 'immunizations') {
1813 // return requested label for immunization table
1814 if ($label == "pid") {
1815 $returnedLabel = "patient_id";
1817 else if ($label == "date") {
1818 $returnedLabel = "`administered_date`";
1820 else {
1821 // unknown label, so return the original label
1822 $returnedLabel = $label;
1825 else {
1826 // return requested label for default tables
1827 if ($label == "pid") {
1828 $returnedLabel = "pid";
1830 else if ($label == "date") {
1831 $returnedLabel = "`date`";
1833 else {
1834 // unknown label, so return the original label
1835 $returnedLabel = $label;
1839 return $returnedLabel;
1843 * Simple function to avoid processing of duplicate actions
1845 * @param string $actions 2-dimensional array with all current active targets
1846 * @param string $action array of selected target to test for duplicate
1847 * @return boolean true if duplicate, false if not duplicate
1849 function is_duplicate_action($actions,$action) {
1850 foreach ($actions as $row) {
1851 if ($row['category'] == $action['category'] &&
1852 $row['item'] == $action['item'] &&
1853 $row['value'] == $action['value']) {
1854 // Is a duplicate
1855 return true;
1859 // Not a duplicate
1860 return false;
1864 * Calculate the reminder dates.
1866 * This function returns an array that contains three elements (each element is a date).
1867 * <pre>The three dates are:
1868 * first date is before the target date (past_due) (default of 1 month)
1869 * second date is the target date (due)
1870 * third date is after the target date (soon_due) (default of 2 weeks)
1871 * </pre>
1873 * @param string $rule id(string) of selected rule
1874 * @param string $dateTarget target date(format Y-m-d H:i:s).
1875 * @param string $type either 'patient_reminder' or 'clinical_reminder'
1876 * @return array see above for description of returned array
1878 function calculate_reminder_dates($rule, $dateTarget='',$type) {
1880 // Set date to current if not set
1881 $dateTarget = ($dateTarget) ? $dateTarget : date('Y-m-d H:i:s');
1883 // Collect the current date settings (to ensure not skip)
1884 $res = resolve_reminder_sql($rule, $type.'_current');
1885 if (!empty($res)) {
1886 $row = $res[0];
1887 if ($row ['method_detail'] == "SKIP") {
1888 $dateTarget = "SKIP";
1892 // Collect the past_due date
1893 $past_due_date = "";
1894 $res = resolve_reminder_sql($rule, $type.'_post');
1895 if (!empty($res)) {
1896 $row = $res[0];
1897 if ($row ['method_detail'] == "week") {
1898 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " week"));
1900 if ($row ['method_detail'] == "month") {
1901 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " month"));
1903 if ($row ['method_detail'] == "hour") {
1904 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1906 if ($row ['method_detail'] == "SKIP") {
1907 $past_due_date = "SKIP";
1910 else {
1911 // empty settings, so use default of one month
1912 $past_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -1 month"));
1915 // Collect the soon_due date
1916 $soon_due_date = "";
1917 $res = resolve_reminder_sql($rule, $type.'_pre');
1918 if (!empty($res)) {
1919 $row = $res[0];
1920 if ($row ['method_detail'] == "week") {
1921 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " week"));
1923 if ($row ['method_detail'] == "month") {
1924 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +" . $row ['value'] . " month"));
1926 if ($row ['method_detail'] == "hour") {
1927 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " -" . $row ['value'] . " hour"));
1929 if ($row ['method_detail'] == "SKIP") {
1930 $soon_due_date = "SKIP";
1933 else {
1934 // empty settings, so use default of one month
1935 $soon_due_date = date("Y-m-d H:i:s", strtotime($dateTarget . " +2 week"));
1938 // Return the array of three dates
1939 return array($soon_due_date,$dateTarget,$past_due_date);
1943 * Adds an action into the reminder array
1945 * @param array $reminderOldArray Contains the current array of reminders
1946 * @param array $reminderNew Array of a new reminder
1947 * @return array Reminders
1949 function reminder_results_integrate($reminderOldArray, $reminderNew) {
1951 $results = array();
1953 // If reminderArray is empty, then insert new reminder
1954 if (empty($reminderOldArray)) {
1955 array_push($results, $reminderNew);
1956 return $results;
1959 // If duplicate reminder, then replace the old one
1960 $duplicate = false;
1961 foreach ($reminderOldArray as $reminderOld) {
1962 if ( $reminderOld['pid'] == $reminderNew['pid'] &&
1963 $reminderOld['category'] == $reminderNew['category'] &&
1964 $reminderOld['item'] == $reminderNew['item']) {
1965 array_push($results, $reminderNew);
1966 $duplicate = true;
1968 else {
1969 array_push($results, $reminderOld);
1973 // If a new reminder, then insert the new reminder
1974 if (!$duplicate) {
1975 array_push($results, $reminderNew);
1978 return $results;
1982 * Compares number of items with requested comparison operator
1984 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
1985 * @param string $thres Threshold used in comparison
1986 * @param integer $num_items Number of items
1987 * @return boolean Comparison results
1989 function itemsNumberCompare($comp, $thres, $num_items) {
1991 if ( ($comp == "eq") && ($num_items == $thres) ) {
1992 return true;
1994 else if ( ($comp == "ne") && ($num_items != $thres) && ($num_items > 0) ) {
1995 return true;
1997 else if ( ($comp == "gt") && ($num_items > $thres) ) {
1998 return true;
2000 else if ( ($comp == "ge") && ($num_items >= $thres) ) {
2001 return true;
2003 else if ( ($comp == "lt") && ($num_items < $thres) && ($num_items > 0) ) {
2004 return true;
2006 else if ( ($comp == "le") && ($num_items <= $thres) && ($num_items > 0) ) {
2007 return true;
2009 else {
2010 return false;
2015 * Converts a text comparison operator to sql equivalent
2017 * @param string $comp Comparison operator(eq,ne,gt,ge,lt,le)
2018 * @return string contains sql compatible comparison operator
2020 function convertCompSql($comp) {
2022 if ($comp == "eq") {
2023 return "=";
2025 else if ($comp == "ne") {
2026 return "!=";
2028 else if ($comp == "gt") {
2029 return ">";
2031 else if ($comp == "ge") {
2032 return ">=";
2034 else if ($comp == "lt") {
2035 return "<";
2037 else { // ($comp == "le")
2038 return "<=";
2043 * Function to find age in years (with decimal) on the target date
2045 * @param string $dob date of birth
2046 * @param string $target date to calculate age on
2047 * @return float years(decimal) from dob to target(date)
2049 function convertDobtoAgeYearDecimal($dob,$target) {
2051 // Prepare dob (Y M D)
2052 $dateDOB = explode(" ",$dob);
2054 // Prepare target (Y-M-D H:M:S)
2055 $dateTargetTemp = explode(" ",$target);
2056 $dateTarget = explode("-",$dateTargetTemp[0]);
2058 // Collect differences
2059 $iDiffYear = $dateTarget[0] - $dateDOB[0];
2060 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
2061 $iDiffDay = $dateTarget[2] - $dateDOB[2];
2063 // If birthday has not happen yet for this year, subtract 1.
2064 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
2066 $iDiffYear--;
2069 // Ensure diffYear is not less than 0
2070 if ($iDiffYear < 0) $iDiffYear = 0;
2072 return $iDiffYear;
2076 * Function to find age in months (with decimal) on the target date
2078 * @param string $dob date of birth
2079 * @param string $target date to calculate age on
2080 * @return float months(decimal) from dob to target(date)
2082 function convertDobtoAgeMonthDecimal($dob,$target) {
2084 // Prepare dob (Y M D)
2085 $dateDOB = explode(" ",$dob);
2087 // Prepare target (Y-M-D H:M:S)
2088 $dateTargetTemp = explode(" ",$target);
2089 $dateTarget = explode("-",$dateTargetTemp[0]);
2091 // Collect differences
2092 $iDiffYear = $dateTarget[0] - $dateDOB[0];
2093 $iDiffMonth = $dateTarget[1] - $dateDOB[1];
2094 $iDiffDay = $dateTarget[2] - $dateDOB[2];
2096 // If birthday has not happen yet for this year, subtract 1.
2097 if ($iDiffMonth < 0 || ($iDiffMonth == 0 && $iDiffDay < 0))
2099 $iDiffYear--;
2102 // Ensure diffYear is not less than 0
2103 if ($iDiffYear < 0) $iDiffYear = 0;
2105 return (12 * $iDiffYear) + $iDiffMonth;
2109 * Function to calculate the percentage for reports.
2111 * @param integer $pass_filter number of patients that pass filter
2112 * @param integer $exclude_filter number of patients that are excluded
2113 * @param integer $pass_target number of patients that pass target
2114 * @return string Number formatted into a percentage
2116 function calculate_percentage($pass_filt,$exclude_filt,$pass_targ) {
2117 if ($pass_filt > 0) {
2118 $perc = number_format(($pass_targ/($pass_filt-$exclude_filt))*100) . xl('%');
2120 else {
2121 $perc = "0". xl('%');
2123 return $perc;